@@ -316,42 +316,104 @@ Next, create the Dockerfile.
316316 #! /bin/bash
317317 set -e
318318
319- if [ -z " ${AZP_URL} " ]; then
320- echo 1>&2 " error: missing AZP_URL environment variable"
321- exit 1
322- fi
319+ # Load a token either from the environment variable or by using the service principal credentials.
320+ load_azp_token () {
321+ # Always un-export AZP_CLIENTSECRET so it is never inherited by
322+ # child processes (e.g., agent job processes). It remains available
323+ # as a shell variable so that it can be used to generate a token.
324+ export -n AZP_CLIENTSECRET
325+
326+ if [ -n " $AZP_CLIENTID " ]; then
327+ if [ -z " $AZP_CLIENTSECRET " ]; then
328+ echo 1>&2 " error: AZP_CLIENTSECRET must be set when AZP_CLIENTID is used"
329+ exit 1
330+ fi
331+
332+ if [ -z " $AZP_TENANTID " ]; then
333+ echo 1>&2 " error: AZP_TENANTID must be set when AZP_CLIENTID is used"
334+ exit 1
335+ fi
336+
337+ echo " Using service principal credentials to get token"
338+ # Isolate Azure CLI state to a dedicated, restrictive directory to avoid
339+ # leaking cached credentials/tokens to subsequent pipeline job processes.
340+ AZP_TOKEN=" $(
341+ export AZURE_CONFIG_DIR=" $( mktemp -d) "
342+ chmod 700 " $AZURE_CONFIG_DIR "
343+ az_cli_cleanup () {
344+ # Log out and remove the isolated Azure CLI config directory to purge cached tokens.
345+ az logout --username " $AZP_CLIENTID " > /dev/null 2>&1 || true
346+ az account clear > /dev/null 2>&1 || true
347+ rm -rf " $AZURE_CONFIG_DIR "
348+ }
349+ trap az_cli_cleanup EXIT
350+ az login --allow-no-subscriptions --service-principal --username " $AZP_CLIENTID " --password " $AZP_CLIENTSECRET " --tenant " $AZP_TENANTID " > /dev/null
351+ # adapted from https://learn.microsoft.com/en-us/azure/databricks/dev-tools/user-aad-token
352+ az account get-access-token --query accessToken --output tsv
353+ ) "
354+ echo " Token retrieved"
355+ fi
323356
324- if [ -n " $AZP_CLIENTID " ]; then
325- echo " Using service principal credentials to get token"
326- az login --allow-no-subscriptions --service-principal --username " $AZP_CLIENTID " --password " $AZP_CLIENTSECRET " --tenant " $AZP_TENANTID "
327- # adapted from https://learn.microsoft.com/en-us/azure/databricks/dev-tools/user-aad-token
328- AZP_TOKEN=$( az account get-access-token --query accessToken --output tsv)
329- echo " Token retrieved"
330- fi
357+ if [ -z " ${AZP_TOKEN_FILE} " ]; then
358+ if [ -z " ${AZP_TOKEN} " ]; then
359+ echo 1>&2 " error: missing AZP_TOKEN environment variable"
360+ exit 1
361+ fi
331362
332- if [ -z " ${AZP_TOKEN_FILE} " ]; then
333- if [ -z " ${AZP_TOKEN} " ]; then
334- echo 1>&2 " error: missing AZP_TOKEN environment variable"
335- exit 1
363+ AZP_TOKEN_FILE=" $( dirname " $0 " ) /.token"
336364 fi
337365
338- AZP_TOKEN_FILE=" /azp/.token"
339- echo -n " ${AZP_TOKEN} " > " ${AZP_TOKEN_FILE} "
340- fi
341-
342- unset AZP_CLIENTSECRET
343- unset AZP_TOKEN
366+ if [ -n " ${AZP_TOKEN-} " ]; then
367+ if [ -d " ${AZP_TOKEN_FILE} " ]; then
368+ echo 1>&2 " error: AZP_TOKEN_FILE is a directory, expected a file path: ${AZP_TOKEN_FILE} "
369+ exit 1
370+ fi
371+ # If the file already exists, it must itself be writable.
372+ if [ -e " ${AZP_TOKEN_FILE} " ]; then
373+ if [ ! -w " ${AZP_TOKEN_FILE} " ]; then
374+ echo 1>&2 " error: existing AZP_TOKEN_FILE is not writable: ${AZP_TOKEN_FILE} "
375+ exit 1
376+ fi
377+ else
378+ if [ ! -w " $( dirname " ${AZP_TOKEN_FILE} " ) " ]; then
379+ echo 1>&2 " error: AZP_TOKEN_FILE parent directory is missing or not writable: $( dirname " ${AZP_TOKEN_FILE} " ) "
380+ exit 1
381+ fi
382+ fi
383+ (umask 177 && echo -n " ${AZP_TOKEN} " > " ${AZP_TOKEN_FILE} " )
384+ chmod 600 " ${AZP_TOKEN_FILE} "
385+ else
386+ if [ ! -r " ${AZP_TOKEN_FILE} " ]; then
387+ echo 1>&2 " error: AZP_TOKEN_FILE is not readable: ${AZP_TOKEN_FILE} "
388+ exit 1
389+ fi
390+ if [ ! -s " ${AZP_TOKEN_FILE} " ]; then
391+ echo 1>&2 " error: AZP_TOKEN_FILE is empty: ${AZP_TOKEN_FILE} "
392+ exit 1
393+ fi
394+ fi
344395
345- if [ -n " ${AZP_WORK} " ]; then
346- mkdir -p " ${AZP_WORK} "
347- fi
396+ # Unset the AZP_TOKEN environment variable to prevent it from being exposed.
397+ # At this point the token is only available in the file specified by AZP_TOKEN_FILE.
398+ unset AZP_TOKEN
399+ }
348400
401+ # Cleanup function to remove the agent configuration.
349402 cleanup () {
350403 trap " " EXIT
351404
352405 if [ -e ./config.sh ]; then
353406 print_header " Cleanup. Removing Azure Pipelines agent..."
354407
408+ # Try to get a new token if using service principal credentials, as the old one
409+ # might have expired between the time it was waiting to run a job and now.
410+ # If refresh fails, continue cleanup with the existing token file.
411+ if [ -n " $AZP_CLIENTID " ]; then
412+ if ! ( load_azp_token ); then
413+ echo " Warning: failed to refresh Azure Pipelines token during cleanup. Continuing with the existing token."
414+ fi
415+ fi
416+
355417 # If the agent has some running jobs, the configuration removal process will fail.
356418 # So, give it some time to finish the job.
357419 while true ; do
@@ -369,8 +431,20 @@ Next, create the Dockerfile.
369431 echo -e " \n${lightcyan} $1 ${nocolor} \n"
370432 }
371433
434+ if [ -z " ${AZP_URL} " ]; then
435+ echo 1>&2 " error: missing AZP_URL environment variable"
436+ exit 1
437+ fi
438+
439+ # Load the AZP token for initial setup.
440+ load_azp_token
441+
442+ if [ -n " ${AZP_WORK} " ]; then
443+ mkdir -p " ${AZP_WORK} "
444+ fi
445+
372446 # Let the agent ignore the token env variables
373- export VSO_AGENT_IGNORE=" AZP_TOKEN,AZP_TOKEN_FILE"
447+ export VSO_AGENT_IGNORE=" AZP_TOKEN,AZP_TOKEN_FILE,AZP_CLIENTSECRET "
374448
375449 print_header " 1. Determining matching Azure Pipelines agent..."
376450
0 commit comments