diff --git a/Makefile b/Makefile index 4c02e1d1..edf58beb 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ GEN = oidc-gen ADD = oidc-add CLIENT = oidc-token KEYCHAIN = oidc-keychain +TOKENSH = oidc-tokensh AGENT_SERVICE = oidc-agent-service PROMPT = oidc-prompt @@ -334,6 +335,7 @@ endif SIMPLECSS_FILE := $(shell CSS="/usr/share/simple.css/simple.min.css" && [ -f "$$CSS" ] || CSS="$(PROMPT_SRCDIR)/html/static/css/lib/simple.min.css" ; echo "$$CSS") ifndef ANY_MSYS KEYCHAIN_SOURCES := $(SRCDIR)/$(KEYCHAIN)/$(KEYCHAIN) +TOKENSH_SOURCES := $(SRCDIR)/$(TOKENSH)/$(TOKENSH) AGENTSERVICE_SRCDIR := $(SRCDIR)/$(AGENT_SERVICE) endif endif @@ -421,7 +423,7 @@ else ifdef MINGW build: shared_lib $(APILIB)/liboidc-agent.a else -build: $(BINDIR)/$(AGENT) $(BINDIR)/$(GEN) $(BINDIR)/$(ADD) $(BINDIR)/$(CLIENT) $(BINDIR)/$(AGENT_SERVICE) $(BINDIR)/$(KEYCHAIN) $(BINDIR)/$(PROMPT) +build: $(BINDIR)/$(AGENT) $(BINDIR)/$(GEN) $(BINDIR)/$(ADD) $(BINDIR)/$(CLIENT) $(BINDIR)/$(AGENT_SERVICE) $(BINDIR)/$(KEYCHAIN) $(BINDIR)/$(TOKENSH) $(BINDIR)/$(PROMPT) endif endif @@ -508,6 +510,10 @@ $(BINDIR)/$(KEYCHAIN): $(KEYCHAIN_SOURCES) $(BINDIR) @cat $(KEYCHAIN_SOURCES) >$@ && chmod 755 $@ @echo "Building "$@" complete!" +$(BINDIR)/$(TOKENSH): $(TOKENSH_SOURCES) $(BINDIR) + @cat $(TOKENSH_SOURCES) >$@ && chmod 755 $@ + @echo "Building "$@" complete!" + $(BINDIR)/$(AGENT_SERVICE): $(AGENTSERVICE_SRCDIR)/$(AGENT_SERVICE) $(AGENTSERVICE_SRCDIR)/options $(BINDIR) @sed -n '/OIDC_INCLUDE/!p;//q' $< >$@ @sed 's!/etc/oidc-agent!$(CONFIG_AFTER_INST_PATH)/oidc-agent!' $(AGENTSERVICE_SRCDIR)/options | sed 's!/usr/bin/oidc-agent!$(BIN_AFTER_INST_PATH)/bin/$(AGENT)!' >>$@ @@ -541,7 +547,7 @@ endif ifndef ANY_MSYS .PHONY: install_bin -install_bin: $(BIN_PATH)/bin/$(AGENT) $(BIN_PATH)/bin/$(GEN) $(BIN_PATH)/bin/$(ADD) $(BIN_PATH)/bin/$(CLIENT) $(BIN_PATH)/bin/$(KEYCHAIN) $(BIN_PATH)/bin/$(AGENT_SERVICE) $(PROMPT_BIN_PATH)/bin/$(PROMPT) +install_bin: $(BIN_PATH)/bin/$(AGENT) $(BIN_PATH)/bin/$(GEN) $(BIN_PATH)/bin/$(ADD) $(BIN_PATH)/bin/$(CLIENT) $(BIN_PATH)/bin/$(KEYCHAIN) $(BIN_PATH)/bin/$(TOKENSH) $(BIN_PATH)/bin/$(AGENT_SERVICE) $(PROMPT_BIN_PATH)/bin/$(PROMPT) @echo "Installed binaries" .PHONY: install_conf @@ -549,7 +555,7 @@ install_conf: $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIGD) $(CONFIG_PATH)/oidc-a @echo "Installed config files" .PHONY: install_bash -install_bash: $(BASH_COMPLETION_PATH)/$(AGENT) $(BASH_COMPLETION_PATH)/$(GEN) $(BASH_COMPLETION_PATH)/$(ADD) $(BASH_COMPLETION_PATH)/$(CLIENT) $(BASH_COMPLETION_PATH)/$(AGENT_SERVICE) $(BASH_COMPLETION_PATH)/$(KEYCHAIN) +install_bash: $(BASH_COMPLETION_PATH)/$(AGENT) $(BASH_COMPLETION_PATH)/$(GEN) $(BASH_COMPLETION_PATH)/$(ADD) $(BASH_COMPLETION_PATH)/$(CLIENT) $(BASH_COMPLETION_PATH)/$(AGENT_SERVICE) $(BASH_COMPLETION_PATH)/$(KEYCHAIN) $(BASH_COMPLETION_PATH)/$(TOKENSH) @echo "Installed bash completion" .PHONY: install_man @@ -650,6 +656,9 @@ $(BIN_PATH)/bin/$(CLIENT): $(BINDIR)/$(CLIENT) $(BIN_PATH)/bin $(BIN_PATH)/bin/$(KEYCHAIN): $(BINDIR)/$(KEYCHAIN) $(BIN_PATH)/bin @install -p $< $@ +$(BIN_PATH)/bin/$(TOKENSH): $(BINDIR)/$(TOKENSH) $(BIN_PATH)/bin + @install -p $< $@ + $(BIN_PATH)/bin/$(AGENT_SERVICE): $(BINDIR)/$(AGENT_SERVICE) $(BIN_PATH)/bin @install -p $< $@ @@ -686,6 +695,9 @@ $(BASH_COMPLETION_PATH)/$(CLIENT): $(BASH_COMPLETION_PATH) $(BASH_COMPLETION_PATH)/$(KEYCHAIN): $(BASH_COMPLETION_PATH) @ln -s $(AGENT) $@ +$(BASH_COMPLETION_PATH)/$(TOKENSH): $(BASH_COMPLETION_PATH) + @ln -s $(AGENT) $@ + $(BASH_COMPLETION_PATH)/$(AGENT_SERVICE): $(CONFDIR)/bash-completion/oidc-agent-service $(BASH_COMPLETION_PATH) @install -p -m 644 $< $@ @@ -777,6 +789,7 @@ uninstall_bin: @$(rm) $(BIN_PATH)/bin/$(ADD) @$(rm) $(BIN_PATH)/bin/$(CLIENT) @$(rm) $(BIN_PATH)/bin/$(KEYCHAIN) + @$(rm) $(BIN_PATH)/bin/$(TOKENSH) @$(rm) $(BIN_PATH)/bin/$(AGENT_SERVICE) @$(rm) $(PROMPT_BIN_PATH)/bin/$(PROMPT) @echo "Uninstalled binaries" @@ -808,6 +821,7 @@ uninstall_bashcompletion: @$(rm) $(BASH_COMPLETION_PATH)/$(AGENT) @$(rm) $(BASH_COMPLETION_PATH)/$(AGENT_SERVICE) @$(rm) $(BASH_COMPLETION_PATH)/$(KEYCHAIN) + @$(rm) $(BASH_COMPLETION_PATH)/$(TOKENSH) @echo "Uninstalled bash completion" endif diff --git a/gitbook/oidc-tokensh/general.md b/gitbook/oidc-tokensh/general.md new file mode 100644 index 00000000..2500f07e --- /dev/null +++ b/gitbook/oidc-tokensh/general.md @@ -0,0 +1,23 @@ +## General Usage + +`oidc-tokensh` is a tool to ensure that valid Access Tokens are always +available in a location such as `$XDG_RUNTIME_DIR/bt_u$ID`, +`/tmp/bt_u$ID`, or `$BEARER_TOKEN_FILE` just as specified +. + +`oidc-tokensh` provides an "almost drop-in replacement" for `httokensh` of +the [htgettoken](https://github.com/fermitools/htgettoken) tool package. + +`oidc-tokensh` starts a new shell through `oidc-agent` and prompts the user +for the passphrase of the `oidc-agent shortname` that will be loaded. + +The user may specify the `shortname` with the `--oidc ` option. +If only one `shortname` is configured, this one will be used by default. + +``` +Usage: oidc-tokensh [--oidc ] [-- ] +``` + + +See [Detailed Information About All +Options](options.md) for more information. diff --git a/gitbook/oidc-tokensh/options.md b/gitbook/oidc-tokensh/options.md new file mode 100644 index 00000000..7d96117f --- /dev/null +++ b/gitbook/oidc-tokensh/options.md @@ -0,0 +1,34 @@ +## Detailed Information About All Options + +* [`-h, --help`](#help) +* [`--oidc |`](#oidc) +* [`--minsecs `](#minsecs) +* [`-o|--outfile `](#outfile) +* [`-v|--verbose`](#verbose) +* [`-- `](#command) + + +### `--oidc` + +This option is used to specify the `shortname` of an `oidc-agent` +configuration. If only one agent configuration is defined, this option may +be skipped. + +### `--minsecs` + +Specify the minimum number of seconds that the Access Token should still +be valid for. + +### `--outfile` + +Specify alternative file for storing the Access Token. + +### `--verbose` + +Show debug output + +### `-- ` + +Instead of the default shell, you can specify any other shell-like command +here. This is useful to specify your favourite shell. + diff --git a/src/oidc-tokensh/oidc-tokensh b/src/oidc-tokensh/oidc-tokensh new file mode 100755 index 00000000..c812874a --- /dev/null +++ b/src/oidc-tokensh/oidc-tokensh @@ -0,0 +1,242 @@ +#!/bin/bash +# +# Run oidc-agent and then start a shell command and keep the access +# token updated for as long as the command runs. +# +# Adapted by Marcus Hardt 2024, based on httokensh by Dave Dykstra + +usage() +{ + echo "This tool is based on httokensh by Dave Dykstra." + echo "" + echo "Usage: oidc-tokensh [-h] [oidc-token options] -- [command]" + echo + echo "Runs oidc-agent and oidc-token with given options, starts the " + echo "command, and runs oidc-token in the background as needed to " + echo "renew the token until the command exits." + echo "" + echo "Options:" + echo " -h, --help show this help message and exit" + echo " --oidc | name or url of the oidc-agent " + echo " configuration to use" + echo " --minsecs minimum lifetime the token should have" + echo " -o|--outfile specify alternative file for storing" + echo " the Access Token" + echo " -v|--verbose show debug output" + echo "" + echo "command defaults to \$SHELL" +} >&2 + +# if [ $# = 0 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then +# echo "[${LINENO}] You ran: $0 $*" +# # usage +# fi + +OIDC_TOKEN_ARGS="" +CMND_ARGS="" +ORIG_ARGS="" +GOTSEP=false +MINSECS=60 +GOTVERBOSE=false +GOTOUTFILE=false +START_RENEWER=false + +while [ $# -gt 0 ]; do + ORIG_ARGS="${ORIG_ARGS} ${1}" + if $GOTSEP; then + CMND_ARGS="${CMND_ARGS} ${1}" + else + case "$1" in + -h|--help) usage; exit 0 ;; + --) GOTSEP=true ;; + -v|--verbose) GOTVERBOSE=true ;; + --minsecs) MINSECS=$2; ORIG_ARGS="${ORIG_ARGS} $2"; shift ;; + -o|--outfile) GOTOUTFILE=true OUTFILE=$2; ORIG_ARGS="${ORIG_ARGS} $2"; shift ;; + --oidc) OIDC_ID=$2; ORIG_ARGS="${ORIG_ARGS} $2"; shift ;; + --renewer) START_RENEWER=true ;; + esac + fi + shift +done + +######################################################################### +get_bearer_token_file(){ + # Get BEARER_TOKEN_FILE according to WLCG Bearer Token Discovery (https://zenodo.org/records/3937438) + RETVAL="" + if [ -z "${BEARER_TOKEN_FILE}" ]; then + if [ -z "$XDG_RUNTIME_DIR" ]; then + RETVAL="/tmp/bt_u$(id -u)" + else + RETVAL="${XDG_RUNTIME_DIR}/bt_u$(id -u)" + fi + else + RETVAL="${BEARER_TOKEN_FILE}" + fi + echo "${RETVAL}" +} +get_bearer_token_file_orig(){ + if [ -z "$BEARER_TOKEN_FILE" ] && ! $GOTOUTFILE; then + if [ -n "$XDG_RUNTIME_DIR" ]; then + BTFILE="bt_u$(id -u).sh-$$" + BEARER_TOKEN_FILE=$XDG_RUNTIME_DIR/$BTFILE + else + BEARER_TOKEN_FILE=/tmp/$BTFILE + fi + export BEARER_TOKEN_FILE + fi + + if ${GOTOUTFILE}; then + export BEARER_TOKEN_FILE="${OUTFILE}" + fi + echo "[${LINENO}] ${BEARER_TOKEN_FILE}" +} + +decodejwt() { + echo "$1" | cut -d. -f 2 \ + | base64 -di 2>/dev/null \ + | jq --indent 4 2>/dev/null +} + +gettoken() { + MESSAGE="$1" + TIME_PARAM="$2" + [ "${GOTVERBOSE}" == "true" ] && [ "${START_RENEWER}" == "false" ] && { + echo "[${LINENO}] running oidc-add ${OIDC_TOKEN_ARGS}" + } + oidc-add ${OIDC_TOKEN_ARGS} >/dev/null + TOKEN=$(oidc-token ${OIDC_TOKEN_ARGS}) + # TOKEN=$(oidc-token egi) + RETVAL="$?" + if [ $RETVAL != 0 ]; then + echo "[${LINENO}] oidc-token failed, ${MESSAGE}" >&2 + exit $RETVAL + fi + echo "${TOKEN}" > "${BEARER_TOKEN_FILE}" + + TOKENJSON=$(decodejwt "${TOKEN}") + RETVAL="$?" + if [ $RETVAL != 0 ]; then + echo "[${LINENO}] decodejwt failed, ${MESSAGE}" >&2 + exit $RETVAL + fi + + EXP=$(echo "${TOKENJSON}"|jq .exp) + NOW=$(date +%s) + SLEEPSECS=$((EXP - MINSECS - NOW + TIME_PARAM)) + [ "${GOTVERBOSE}" == "true" ] && [ "${START_RENEWER}" == "false" ] && { + echo "[${LINENO}] SLEEPSECS: ${SLEEPSECS} -- TIME_PARAM: ${TIME_PARAM}" + } + if [ "${SLEEPSECS}" -lt "${TIME_PARAM}" ]; then + echo "[${LINENO}] Calculated renewal time of $SLEEPSECS seconds is less than ${TIME_PARAM}, ${MESSAGE}" + exit 1 + fi +} + +start_agent() { + [ "${GOTVERBOSE}" == "true" ] && [ "${START_RENEWER}" == "false" ] && { + echo "[${LINENO}] starting: >>oidc-agent -- /bin/bash -c "$0 --renewer ${ORIG_ARGS}"<<" + } + oidc-agent -- /bin/bash -c "$0 --renewer ${ORIG_ARGS}" +} + +start_token_renewer() { + # enable job control so background processes get their own process group + gettoken "Initial" 20 + set -m + { + exec 3>&1 1>>"$BEARER_TOKEN_FILE.log" + exec 4>&2 2>>"$BEARER_TOKEN_FILE.log" + trap cleanup 0 + # keep a copy of $PPID because it will change to 1 if parent dies + PARENTPID=$PPID + [ "${GOTVERBOSE}" == "true" ] && { + echo "[${LINENO}] oidc-token args are ${OIDC_TOKEN_ARGS}" + } + while true; do + echo "Renewal scheduled in $SLEEPSECS seconds" + sleep $SLEEPSECS + date + if kill -0 $PARENTPID 2>/dev/null; then + gettoken "Regular" 60 + else + echo "[${LINENO}] Parent process $PARENTPID not running, exiting" + echo "[${LINENO}] mypid: $$" + exit 0 + fi + done + } & + export BACKGROUND_PID=$! + [ "${GOTVERBOSE}" == "true" ] && [ "${START_RENEWER}" == "false" ] && { + echo "[${LINENO}] excuting: ${CMND_ARGS}" + } + # Start the actual shell + ${CMND_ARGS} + +} + +cleanup() +{ + [ -z "${BACKGROUND_PID}" ] || { + if kill -0 "$BACKGROUND_PID" 2>/dev/null; then + rm -f "${BEARER_TOKEN_FILE}" "${BEARER_TOKEN_FILE}.log" + else + echo -e "\n\nRenewal background process failed to renew Access Token, see $BEARER_TOKEN_FILE.log\n" + echo "Renewal background process failed, see $BEARER_TOKEN_FILE.log" >> "${BEARER_TOKEN_FILE}.log" + exit 2 + fi + } +} +######################################################################### + +[ -z "${OIDC_ID}" ] && { # the --oidc option was not defined. We try to find + # if there is only one configured. If so, we use + # that one. + echo "Trying to auto-detect oidc-agent configuration" + NUM_OIDC_ACCOUNTS=$(oidc-add -l | grep -cv "The following") + [ "$NUM_OIDC_ACCOUNTS" -eq 1 ] && { + OIDC_ID=$(oidc-add -l | grep -v "The following") + echo "Defaulting to $OIDC_ID" + } + [ "$NUM_OIDC_ACCOUNTS" -eq 1 ] || { + echo "Please specify the oidc-agent shortname that you wish to use" + exit 3 + } +} + +OIDC_TOKEN_ARGS="${OIDC_ID} -t ${MINSECS}" +[ "${GOTVERBOSE}" == "true" ] && [ "${START_RENEWER}" == "false" ] && { + echo "[${LINENO}] OIDC_TOKEN_ARGS: ${OIDC_TOKEN_ARGS}" +} + +if ! $GOTSEP; then + CMND_ARGS="${SHELL}" +fi + +BEARER_TOKEN_FILE=$(get_bearer_token_file) +export BEARER_TOKEN_FILE + + +[ "${GOTVERBOSE}" == "true" ] && [ "${START_RENEWER}" == "false" ] && { + echo "[${LINENO}] Bearer Token is at $BEARER_TOKEN_FILE" +} +[ "${START_RENEWER}" == "false" ] && { + echo "Renewal log is at $BEARER_TOKEN_FILE.log" +} + +[ "${START_RENEWER}" == "true" ] && { + [ "${GOTVERBOSE}" == "true" ] && [ "${START_RENEWER}" == "false" ] && { + echo "[${LINENO}] Starting renewer" + echo "[${LINENO}] OIDC_TOKEN_ARGS: ${OIDC_TOKEN_ARGS}" + } + trap cleanup 0 + start_token_renewer + [ "${GOTVERBOSE}" == "true" ] && [ "${START_RENEWER}" == "false" ] && { + echo "[${LINENO}] BACKGROUND_PID: ${BACKGROUND_PID}" + } +} + + +[ "${START_RENEWER}" == "false" ] && { + # trap cleanup 0 + start_agent +}