Skip to content
Snippets Groups Projects
check_gitlab_tokens 8.18 KiB
#!/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

# ----------------------------------------------------------------------