Reputation: 3701
I'd like to get a temporary GitLab token to run a simple script. I do not want a personal access token, nor project access token as that is too many clicks for the user. How do I log in or sign in? GitLab does not support password login anymore and one has to use OAuth. But that means I have to receive an HTTP redirect, running a web server!
Upvotes: 0
Views: 1422
Reputation: 3701
GitLab Oauth dialogue can be very quick once authenticated in your browser. It doesn't even ask for confirmation in default configuration. The user doesn't then have to leave the command line. The little trouble is a pile of authentication browser tabs/windows left behind.
Only one HTTP request needs to be processed. We do not need a web server for that. Netcat can accept connection, give us the request and respond as well so that the user gets some feedback. Against the recommendation, this goes unencrypted. It is on a loopback device, though. Risk should be low. To up the security here, we'd need a proper browser app that can handle a custom protocol. WebView can be used to that effect, but then we're out of simple shell.
Curl (or wget) handles the necessary API calls.
This is a simple OAuth client written in a (posix?) shell:
#!/bin/sh
# fail fast
set -e
# defaults
app_id="$GITLAB_APP_ID"
app_port="8001"
app_addr="127.0.0.1"
api_v4=""
test -z "$GITLAB_HOST" || api_v4="https://${GITLAB_HOST}/api/v4"
test -z "$GITLAB_API_V4" || api_v4="$GITLAB_API_V4"
# display usage info help text
help () {
cat <<-EOHELP
Sign into GitLab on ${api_v4:-GITLAB_API_V4} with application '${app_id:-GITLAB_APP_ID}'
Requires: nc, curl, jq, x-www-browser|open, mktemp, posix(sh, cat, cut, head, rm, grep)
It is meant to work the same in a pipeline as well as on local environment.
It will use the CI_JOB_TOKEN set by GitLab runner or request OAuth from the user.
For OAuth, make the app with [api] access, not confidential, expire tokens: yes,
redirect URI should be set to http://127.0.0.1:8001
Args:
-h|--help this text
-a|--app_id set GitLab app id [GITLAB_APP_ID]
-4|--api_v4 set GitLab API v4 address [GITLAB_API_V4]
or set it with a GITLAB_HOST FQDN[:port]
-s|--server set GitLab API v4 address with a GITLAB_HOST FQDN[:port]
EOHELP
}
# parse command arguments
parse_args () {
while test "$#" -ne 0
do
case "$1" in
-h|--help) help; exit 0 ;;
-a|--app_id) app_id="$2"; shift 2 ;;
-a=*|--app_id=*) app_id="${1#*=}"; shift 1 ;;
-4|--api_v4) api_v4="$2"; shift 2 ;;
-4=*|--api_v4=*) api_v4="${1#*=}"; shift 1 ;;
-s|--server) api_v4="https://$2/api/v4"; shift 2 ;;
-s=*|--server=*) api_v4="https://${1#*=}/api/v4"; shift 1 ;;
*) ( echo "Unexpected arg '$1'"; echo; help ) >&2; exit 1 ;;
esac
done
}
auth_netcat () {
# compatible way to invoke different nc flavors: 1) gnu/bsd/mac 2) busybox
nc -l "$1" "$2" 2>/dev/null || nc -l "$1:$2"
}
auth_browse () {
# compatible way to open a default browser
x-www-browser "$@" || open "$@"
}
authenticate () {
# Use CI_JOB_TOKEN in a pipeline as gitlab_token if available or get oauth access token
if [ -n "$CI_JOB_TOKEN" ]; then
gitlab_user="gitlab-ci-token"
gitlab_token="$CI_JOB_TOKEN"
else
: "${api_v4:?specify a GitLab API v4 URL}"
: "${app_id:?specify a GitLab app ID}"
: "${app_port:?specify the app port}"
: "${app_addr:?specify the app address}"
echo "Getting token"
auth_file="$(mktemp)"
# Start netcat as a local web server to receive the auth code
auth_netcat "$app_addr" "$app_port" <<-EORSP >"$auth_file" &
HTTP/1.0 200 OK
Content-Length: 13
Authenticated
EORSP
auth_pid=$!
# If netcat started, proceed with requesting auth code
if kill -0 "$auth_pid"; then
auth_state="$auth_pid"
auth_url="${api_v4%/api/v4}/oauth/authorize?response_type=code&scope=api"
auth_url="$auth_url&client_id=$app_id"
auth_url="$auth_url&redirect_uri=http://$app_addr:$app_port"
auth_url="$auth_url&state=$auth_state"
auth_browse "$auth_url" &&
echo "Authentication window opened in your browser:" ||
echo "Authenticate:"
echo " $auth_url"
fi
# Wait for netcat to receive the code and then request access token and user name
if wait "$auth_pid" 2>/dev/null; then
auth_code="$(head -1 "$auth_file" | grep -Eo 'code=[^&]+' | cut -d= -f2)"
echo "claiming access token"
gitlab_token="$(
curl -sSLf "${api_v4%/api/v4}/oauth/token" \
-F "client_id=$app_id" \
-F "code=$auth_code" \
-F "grant_type=authorization_code" \
-F "redirect_uri=http://$app_addr:$app_port" |
jq -r '.access_token'
)"
echo "getting current user name"
gitlab_user="$(
curl -sSLf "${api_v4}/user/" \
-H "Authorization: Bearer $gitlab_token" |
jq -r '.username'
)"
rm "$auth_file"
else
echo "authentication FAILED"
fi
fi
}
if parse_args "$@" && authenticate >&2
then
echo "GITLAB_USER=${gitlab_user:?}"
echo "GITLAB_TOKEN=${gitlab_token:?}"
fi
Tbh, this Q&A is a somewhat more sophisticated gist to keep this handy and available to others. Improvements welcome!
If you copy&paste, convert spaces to tabs so the heredoc doesn't break
Upvotes: 2