Skip to content

Instantly share code, notes, and snippets.

@smoser
Last active July 16, 2024 22:27
Show Gist options
  • Save smoser/568f03b41efe80f57cea4605beec71ac to your computer and use it in GitHub Desktop.
Save smoser/568f03b41efe80f57cea4605beec71ac to your computer and use it in GitHub Desktop.

chainctl token helper

I set out with the goal of just running something 'login-images 30m' and having it force me to log in (in --headless mode) to each of the configured endpoints, and then running it again would refresh the token, or force me to auth again if i had to in able to ensure 30m of peace.

When refresh tokens are correctly working, the end goal would be to run this on ssh login with a value of something like '4h' (4 hours) and then start a timer of some sort that just kept refreshing in the background.

This is what I have right now.

  • login-images -c/--check will show status

    $ login-images -c
    console-api.enforce.dev - 0m
    apk.cgr.dev - 0m
    cgr.dev - 48m
    
  • login-images without args will login to the audience provided or the builtin list

    Below, the was good for 58 minutes so it did not force a new login. The other two sites needed login.

    $ login-images 
    console-api.enforce.dev had 0m wanted 30m
    Authenticating...
    Enter the verification code MZDD-XVMP in your browser at: https://auth.chainguard.dev/activate
    Code will be valid for 900 seconds
    Token received!
    Successfully exchanged token.
    Valid! Id: aa4458f1e77704575053c079c9b3b6db8bb47642
    console-api.enforce.dev - good for 59m
    apk.cgr.dev had 0m wanted 30m
    Authenticating...
    Enter the verification code QCDR-XFWJ in your browser at: https://auth.chainguard.dev/activate
    Code will be valid for 900 seconds
    Token received!
    Successfully exchanged token.
    Valid! Id: aa4458f1e77704575053c079c9b3b6db8bb47642
    apk.cgr.dev - good for 60m
    cgr.dev - good for 58m
    

Notes

  • Refresh tokens basically can't be used here, at least with --headless (mono/#18280) and they don't get listed in status (mono/#18279).
#!/bin/bash
# shellcheck disable=SC2015,SC2039,SC2166,SC3043
VERBOSITY=${VERBOSITY:-1}
HEADLESS="--headless"
rf() {
"$@" || {
stderr "failed [$?]:" "$@"
exit 1;
}
}
stderr() { echo "$@" 1>&2; }
debug() { [ $VERBOSITY -lt 1 ] || stderr "$@"; }
is_coreutils_date() {
if [ -z "$_IS_COREUTILS_DATE" ]; then
date --help 2>&1 | grep -q "coreutils" &&
_IS_COREUTILS_DATE=true || _IS_COREUTILS_DATE=false
fi
case "$_IS_COREUTILS_DATE" in
true) return 0;;
false) return 1;;
esac
}
tounix() {
local date="$1"
if is_coreutils_date; then
# wierd, output says has numerical timezone (offset) string (EDT or UTC).
date=${date% [A-Z][A-Z][A-Z]}
rf date --date="$date" "+%s"
else
rf date -d"$date" -D"%Y-%m-%d %H:%M:%S %z %Z" "+%s"
fi
}
getttl() {
local audience="$1" rc="" stout=""
set -- chainctl auth status --quick --output=json --audience="$audience"
stout=$("$@")
rc=$?
if [ $rc -ne 0 -a $rc -ne 1 ]; then
stderr "failed execute[$rc]: $*"
[ -z "$stout" ] || stderr "output: ${stout}"
return $rc
fi
local valid="" reason=""
valid=$(echo "$stout" | jq -r .valid) || {
stderr "fail execute[$rc]: $*"
stderr "produced invalid json output"
return 1
}
[ "$valid" = "true" -o "$valid" = "false" ] ||
{ stderr "unexpected output from $*. valid='$valid'"; return 1; }
[ "$valid" = "false" ] && {
_RET=0
_RET_OUT="$stout"
return 0
}
set -- chainctl auth status --output=json --audience="$audience"
stout=$("$@") || {
rc=$?
stderr "fail execute[$rc] (passed with --quick): $*"
[ -z "$stout" ] || stderr "output: ${stout}"
return 1
}
local expire="" exps="" nows=""
expire=$(echo "$stout" | jq -r .expiry) &&
[ -n "$expire" ] || {
stderr "failed to get expiry info from status"
return 1
}
exps=$(tounix "$expire") || {
stderr "failed to convert $expire for audience=$audience"
return 1
}
nows=$(date "+%s") || fail "date +%s failed"
email=$(echo "$stout" | jq -r .email)
#stderr "$audience [$email] good for $(((exps-nows)/60))m"
_RET=$((exps-nows))
_RET_OUT="$stout"
}
tok_with_ttl() {
local audience="$1" wantttl="$2"
local ttl=""
getttl "$audience" || return 1
ttl="$_RET"
if [ $ttl -gt $wantttl ]; then
echo "$audience - good for $((ttl/60))m"
return 0
fi
debug "$audience had $((ttl/60))m wanted $((wantttl/60))m"
chainctl auth login "$HEADLESS" --audience="$audience" || return
getttl "$audience" || return
echo "$audience - good for $((_RET/60))m"
}
if [ -f ~/.docker/config.json ]; then
out=$(jq -r '.credHelpers."cgr.dev"' < ~/.docker/config.json)
[ $? -eq 0 ] || exit 1
if [ "$out" != "cgr" ]; then
rf chainctl auth configure-docker "$HEADLESS"
fi
# seems like this doesn't use the same token?
fi
check=false
if [ "$1" = "--check" -o "$1" = "-c" ]; then
check=true
shift
fi
want_ttl=$((30*60))
case "$1" in
--want=*) want_ttl=$((60*${1#--want=})); shift;;
--want|-w) want_ttl=$((60*$2)); shift 2;;
-w[0-9]*) want_ttl=$((60*${1#-w})); shift;;
esac
if [ $# -eq 0 ]; then
set -- console-api.enforce.dev apk.cgr.dev cgr.dev
fi
if [ "$check" = "true" ]; then
for audience in "$@"; do
rf getttl "$audience"
echo "$audience - $((_RET/60))m"
done
exit
fi
for audience in "$@"; do
rf tok_with_ttl "$audience" "$want_ttl"
done
exit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment