#!/bin/bash # ====================================================================== # # Check Gitlab tokens of groups and projects. # It warns if tokens expire soon. # # requirements: # - rest-api-client - https://git-repo.iml.unibe.ch/iml-open-source/bash-rest-api-client # - curl # # ---------------------------------------------------------------------- # 2024-10-29 v1.0 <axel.hahn@unibe.ch> # 2024-10-30 v1.1 <axel.hahn@unibe.ch> GITLAB_TOKEN=SKIP responds OK without tests # 2024-11-20 v1.2 <axel.hahn@unibe.ch> Update rest api client # ====================================================================== cd "$( dirname "$0" )" || exit . "$( dirname $0 )/inc_pluginfunctions" || exit 1 export self_APPVERSION=1.2 sSkipvalue="SKIP" GITLAB_API='https://gitlab.example.com/api/v4' GITLAB_TOKEN="$sSkipvalue" GITLAB_CONFIG=/etc/icinga2/gitlab.cfg REST_CLIENT="/opt/rest-api-client/http.class.sh" projectUrls= OUT_TOKENS=/tmp/gitlab-tokens__$USER.json OUT_USERS=/tmp/gitlab-users__$USER.json typeset -i iSince=395 typeset -i iTokenCount typeset -i iTokensFound NL=" " typeset -i iWarnLimit=30 typeset -i iCriticalLimit=10 typeset -i iCountWarn=0 typeset -i iCountCritical=0 output="" # ---------------------------------------------------------------------- # functions # ---------------------------------------------------------------------- # Show help function showHelp(){ local _self; _self=$(basename $0) cat <<EOF $( ph.showImlHelpHeader ) Check gitlab tokens and warn if tokens expire soon. This check fetches the gitlbab tokens created in the last $iSince days from the Gitlab API. It skips - personal access tokens of users - revoked tokens The script can run several seconds depending on count of tokens, projects and users. Maybe you want to call it with a longer interval. SYNTAX: $_self [OPTIONS] OPTIONS: -h or --help show this help. -w VALUE warning level (default: $iWarnLimit) -c VALUE critical level (default: $iCriticalLimit) -g FILE path to GITLAB_CONFIG; default: $GITLAB_CONFIG There you can set/ override: GITLAB_API='${GITLAB_API}' GITLAB_CONFIG=<TOKEN> REST_CLIENT="${REST_CLIENT}" -r FILE path to REST api client default: $REST_CLIENT The parameter overrides the variable REST_CLIENT. -s DAYS Number of days for max age of token; default: $iSince PARAMETERS: None. EXAMPLES: $_self -w 28 -c 7 Set other warning and critical level $_self -g ./gitlab.cfg Set a custom gitlab config file $_self -r /opt/bash-api-client/bash-api-client.sh Set a custom gitlab config file EOF } # Fetch data from gitlab api with multiple page requests # # param string url # param string output file # param int optional: number of items per page; default: 100 function _getPagesToFile(){ local url="$1" local outfile="$2" local iPerPage=${3:-100} local page=0 test -f "${outfile}" && rm "${outfile}" grep -q "?" <<< "$url" || url="${url}?" while true; do (( page++ )) pageUrl="$url&per_page=${iPerPage}&page=${page}" # echo "Request: $pageUrl" http.makeRequest "$pageUrl" if ! http.isOk > /dev/null; then echo "ERROR: Request failed: $pageUrl" http.getResponseHeader http.getResponse ph.abort fi # if response is "[]" then we are done if ! http.getResponse | grep -q "^\[\]$"; then http.getResponse >> "${outfile}" else break fi done } # Get web link of a project to see its tokens # # global string projectUrls string to cache project name + link # # param string username eg. projects_14_bot_nnnnnn # return string function getWeblink(){ local myusername="$1" sType=$( cut -f 1 -d "_" <<< "$myusername" )s # sType is one of users|projects sId2=$( cut -f 2 -d "_" <<< "$myusername" ) local data local myurl if ! grep -q "^/$sType/$sId2:" <<< "$projectUrls" ; then http.makeRequest "/$sType/$sId2" if ! http.isOk > /dev/null; then echo "ERROR: Request /$sType/$sId2 failed: $pageUrl" http.getResponseHeader http.getResponse ph.abort fi data="$( http.getResponse )" myname=$( getKey "$data" "name" ) myurl=$( getKey "$data" "web_url" ) if [ -n "$myurl" ]; then projectUrls+="/$sType/$sId2:$myname <$myurl/-/settings/access_tokens>$NL" fi fi grep "^/$sType/$sId2:" <<< "$projectUrls" | cut -f 2- -d ":" } # Get a single value from json # param string json data # param string key # return string function getKey(){ jq -r ".$2" <<< "$1" | grep -v "null" } # ---------------------------------------------------------------------- # MAIN # ---------------------------------------------------------------------- # --- check param -h case "$1" in "--help"|"-h") showHelp exit 0 ;; *) esac REST_CLIENT=$( ph.getValueWithParam $REST_CLIENT r "$@") GITLAB_CONFIG=$( ph.getValueWithParam $GITLAB_CONFIG g "$@") # --- check requirements ph.require curl . "${GITLAB_CONFIG}" || ph.abort "UNKNOWN: Could not source gitlab config $GITLAB_CONFIG" if [ "$GITLAB_TOKEN" = "$sSkipvalue" ]; then ph.status "The check was configured to skip: GITLAB_TOKEN=$sSkipvalue" ph.exit fi . "${REST_CLIENT}" || ph.abort "UNKNOWN: Could not source $REST_CLIENT" http.help >/dev/null || ph.abort "UNKNOWN: http functions not available. Check -r $REST_CLIENT." iWarnLimit=$( ph.getValueWithParam $iWarnLimit w "$@") iCriticalLimit=$( ph.getValueWithParam $iCriticalLimit c "$@") iSince=$( ph.getValueWithParam $iSince s "$@") http.init http.addHeader "PRIVATE-TOKEN: $GITLAB_TOKEN" http.setBaseUrl "$GITLAB_API" startdate="$( date +%Y-%m-%dT00:00:00Z --date "$iSince days ago")" sDateWarn="$( date +%Y%m%d --date "${iWarnLimit} days" )" sDateCritical="$( date +%Y%m%d --date "${iCriticalLimit} days" )" # get all tokens # see https://docs.gitlab.com/ee/api/personal_access_tokens.html _getPagesToFile "/personal_access_tokens/?revoked=false&created_after=${startdate}" "$OUT_TOKENS" # get all users # see https://docs.gitlab.com/ee/api/users.html _getPagesToFile "/users?exclude_humans=true&exclude_external=true&exclude_internal=true&created_after=${startdate}&search=_bot_" "$OUT_USERS" # IDs / Einträge zählen: iTokenCount=$( jq ".[].id " < "$OUT_TOKENS" | wc -l ) iTokensFound=0 for i in $( seq 1 $iTokenCount ) do # get nth token entry="$( jq ".[$i]" < "$OUT_TOKENS")" # hide non active tokens if [ "$( getKey "$entry" "active" )" = "false" ]; then continue fi # hide tokens without name sName=$( getKey "$entry" "name" ) if [ -z "$sName" ]; then continue fi # hide tokens referencing a username that doesn't contain "_[number]_bot_" sUserid=$( getKey "$entry" "user_id" ) myusername="$( jq ".[] | select(.id == $sUserid)" < "$OUT_USERS" | jq ".username" | cut -f 1-3 -d "_" | tr -d '"') " if ! grep -q "_[0-9]*_bot" <<< "$myusername" ; then continue fi iTokensFound+=1 # check expiration sExpire=$( getKey "$entry" "expires_at" ) # remove "-" from date to get an integer sExpire2=${sExpire//\-} sStatus="OK " if [ "$sExpire2" -le "$sDateWarn" ]; then if [ "$sExpire2" -le "$sDateCritical" ]; then iCountCritical+=1 sStatus="Critical" else iCountWarn+=1 sStatus="Warning " fi fi myproject="$( getWeblink "$myusername" )" output+="$sExpire $sStatus $sName - $myproject${NL}" done if [ $iCountCritical -gt 0 ]; then ph.setStatus "critical" elif [ $iCountWarn -gt 0 ]; then ph.setStatus "warning" else ph.setStatus "ok" fi ph.status "$iTokensFound Gitlab Tokens (max $iSince days old) .. critical: $iCountCritical ($iCriticalLimit days) .. warnings: $iCountWarn ($iWarnLimit days)" echo echo "$output" # cleanup rm -f "$OUT_TOKENS" "$OUT_USERS" ph.exit # ----------------------------------------------------------------------