Skip to content

Instantly share code, notes, and snippets.

@SeaJaredCode
Last active December 2, 2023 07:25
Show Gist options
  • Save SeaJaredCode/652d9ae66ef936bbefdea8d966cc54e4 to your computer and use it in GitHub Desktop.
Save SeaJaredCode/652d9ae66ef936bbefdea8d966cc54e4 to your computer and use it in GitHub Desktop.
Command line support for getting tokens from Google IAP, with ArgoCD example for working with token.

Overview

The file iap_credentials_helper.sh is broadly applicable to fetch tokens for use by CLI tools that can't navigate Google's IAP via the API.

ArgoCD Wrapper Use

This wrapper is intended to be a very lightweight addition to the standard argocd command. Specifically, removing the need to type `-- header: Bearer ${IAP_TOKEN}" (or similar) onto every command!

It also covers initially fetching IAP info and retrieving valid tokens to pass through the IAP.

For whatever reason, my setup is not happy with HTTP2 and I have to use --grpc-web, but yours may vary. Also, depending on how you configured RBAC with IAP, you may need to pass in --sso.

So, with this wrapper you can login as usual and the all the token fetching will work itself out.

argocd login MYSERVER.URL --sso --grpcweb     # Not happy trying to use HTTP2(?)
argocd app list                               # or whatever you want to do...

Unresolved logging.sh dependency

I didn't realize when I published it, but it refers to a helper script for logging. All the info debug warn and error references will need to be changed to echo (or deleted!).

# This is intended to be sourced to add the functions to your shell. It will "wrap" calls
# to argocd with the required token, retrieve tokens if there aren't any, and/or refresh them
# if they are expired or invalid.
argocd_iap_helper() {
PROJECT_ID="YOUR_PROJECT_HERE"
CLIENT_NAME="CLIENT_NAME" # Partial name to identify in in the list of oauth clients in your project
CONFIG=~/.config/argocd. # Location to put the cached credentials files
iap_helper="~/bin/iap-credentials-helper.sh"
ARGS=()
ARGS+=("--project" "$PROJECT_ID")
ARGS+=("--client" "$CLIENT_NAME")
ARGS+=("--config" "$CONFIG")
[ -n "$1" ] && ARGS+=("--refresh")
"$iap_helper" "${ARGS[@]}"
}
argocd() {
# If --refresh is passed, refresh the credentials and exit
if [ "$#" -eq 1 ] && [ "$1" == "--refresh" ]; then
argocd_iap_helper --refresh
return 0
fi
IAP_TOKEN=$(argocd_iap_helper)
command argocd "$@" --header "Proxy-Authorization: Bearer ${IAP_TOKEN}"
# If the command fails, refresh the credentials and try again
ARGOCD_EXIT_CODE=$?
if [ $ARGOCD_EXIT_CODE -eq 20 ]; then
read -n 1 -t 5 -p "That may be a permissions error. Would you like me to refresh your credentials and try again? [y/N]" retry
printf "\n"
case "$retry" in
Y | y )
argocd_iap_helper --refresh
argocd "$@";;
No )
return $ARGOCD_EXIT_CODE;;
esac
fi
}
export -f argocd_iap_helper argocd
#!/usr/bin/env bash
set -e
[ "$(type -t info)" == "function" ] || . "~/bin/logging.sh"
show_help() {
cat <<EOF
Usage: $(basename "$0") [options] [project_id] [client_name] [config_dir]
Options:
-p, --project <project_id> Project id
-c, --client <client_name> Client name
-f, --config <config_dir> Config directory
-r, --refresh Force refresh of credentials
-v, --verbose Verbose output
-d, --debug Debug output
-h, --help Show this help message and exit
EOF
}
while [ "$#" -gt 0 ]; do
case "$1" in
-p | --project)
PROJECT_ID="$2"
shift;;
-c | --client)
CLIENT_NAME="$2"
shift;;
-f | --config)
CONFIG="$2"
shift;;
-r | --refresh)
FORCE_REFRESH=1;;
-v | --verbose | -d | --debug)
DEBUG=1;;
-h | --help)
show_help; exit 0;;
*)
break ;;
esac
shift
done
[ -n "$1" ] && [ -z "$PROJECT_ID" ] && PROJECT_ID="$1" && shift
[ -n "$1" ] && [ -z "$CLIENT_NAME" ] && CLIENT_NAME="$1" && shift
[ -n "$1" ] && [ -z "$CONFIG" ] && CONFIG="$1" && shift
if [ "$#" -gt 0 ]; then
show_help
error "Unknown arguments ($#): >$*<"
exit 1
fi
[ -z "$PROJECT_ID" ] && show_help && error "Missing project id" && exit 1
[ -z "$CLIENT_NAME" ] && show_help && error "Missing client name" && exit 1
CONFIG=${CONFIG:-~/.config/$CLIENT_NAME}
CLIENT_INFO=$CONFIG/iap_client_info.json
CREDS_FILE=$CONFIG/iap_creds.json
REQUEST_CAP=$CONFIG/oauth_cap.json
get_iap_client_info() {
info "Getting iap client info"
# Assumes there is only one brand in the project
BRAND_NAME="$(gcloud iap oauth-brands list --project "$PROJECT_ID" --format=json | jq -r '.[0].name')"
gcloud iap oauth-clients list --project="$PROJECT_ID" "$BRAND_NAME" --format=json |
jq ".[] | select(.displayName | contains(\"$CLIENT_NAME\"))" > "$CLIENT_INFO"
}
run_server() {
python3 - <<'__HERE'
from http.server import BaseHTTPRequestHandler, HTTPServer
class SimpleHandler(BaseHTTPRequestHandler):
# Disable logging for each request
def log_request(self, code="-", size="-"):
pass
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(b"Got it. Thanks! (You can close this tab)\n")
print(f"{self.requestline}")
server_address = ("", 4444)
httpd = HTTPServer(server_address, SimpleHandler)
httpd.handle_request()
__HERE
}
refresh_iap_creds() {
info "Refreshing IAP creds"
get_client_info
debug "Starting local server"
run_server > "$REQUEST_CAP" &
SERVER_PID=$!
debug "Calling oauth login with client id: $CLIENT_ID"
open "https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&response_type=code&scope=openid%20email&access_type=offline&redirect_uri=http://localhost:4444&cred_ref=true"
debug "Waiting for server to get code"
wait $SERVER_PID
CODE="$(grep 'GET' "$REQUEST_CAP" | awk -F'code=|&' '{print $2}')"
rm "$REQUEST_CAP"
debug "Received code: $CODE"
# curl to get oauth token
curl -s \
--data client_id=$CLIENT_ID \
--data client_secret=$CLIENT_SECRET \
--data code=$CODE \
--data redirect_uri=http://localhost:4444 \
--data grant_type=authorization_code \
https://oauth2.googleapis.com/token > "$CREDS_FILE"
}
get_client_info() {
debug "Checking for cached iap client info"
[ -f "$CLIENT_INFO" ] || get_iap_client_info
CLIENT_ID="$(jq -r '.name' "$CLIENT_INFO" | awk -F/ '{print $NF}')"
CLIENT_SECRET="$(jq -r '.secret' "$CLIENT_INFO")"
}
debug "PROJECT_ID: $PROJECT_ID"
debug "CLIENT_NAME: $CLIENT_NAME"
debug "CONFIG: $CONFIG"
debug "FORCE_REFRESH: $FORCE_REFRESH"
debug "DEBUG: $DEBUG"
[ -n "$FORCE_REFRESH" ] && refresh_iap_creds && exit 0
# Resolve credentials from cache, if possible, or by retrieving them
debug "Checking for cached iap creds"
[ -f "$CREDS_FILE" ] || refresh_iap_creds
jq -r '.id_token' "$CREDS_FILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment