CONNECTED Conference 2023 - Aufzeichnungen jetzt hier verfügbar +++                     


über alle News und Events


Alle News


Rules, Rules, RULES!! Dan Toomey, The evolution of...


Keynote von Slava Koltovich, Feature: E2E - AIS...


Inspirierende Messeerfahrungen auf der 'Zukunft...


In diesem Artikel wird beschrieben, wie ihr eure...


Messaging mit dem Service Bus ermöglicht die...


Sebastian Meyer, Microsoft & SAP...


Für Entwickler, Architekten, Projektleiter und...


In der Welt der Softwareentwicklung ist die...


QUIBIQ spendet für den guten Zweck – und für...


Eine bestimmte Antwort auf einen HTTP Request zu...


How-to: Azure API Management Self-Hosted Gateway - Token Cycling

The self-hosted gateway is a feature of the API Management to support the management of hybrid and multi-cloud environments. More clearly, this feature enables organizations to manage their APIs hosted either on Azure cloud, on-premises, or on any other cloud, from a single API Management service in Azure. This is done through deploying a containerized version of the API Management Gateway to the same environment where the APIs are hosted. This containerized version of the gateway can be deployed in Docker, or in a Kubernetes cluster.

Connectivity to Azure:

The Self-Hosted Gateway requires a connection to Azure in order to:

  • Report its status through sending a heartbeat every minute.
  • Check regularly for configuration updates and apply them.

However, in order for the self-hosted gateway to be able to download the configuration data from the API Management endpoint; it needs a valid access token.

Token Cycling / Token Rotation:

The access token given for the self-hosted gateway is only valid for 30 days. Therefore, this token needs to be regenerated and assigned to the self-hosted gateway either manually or through automation before it expires in order for the self-hosted gateway to keep accessing and getting the configuration updates.

In order to automatically rotate API management self-hosted gateway tokens, we can have Kubernetes CronJob that runs on a regular basis and regenerate the token and assign it to the self-hosted gateway.

The following section describes a project called “apim-shg-rotate” which is a Docker container image that can be deployed in a Kubernetes CronJob to automatically rotate API management self-hosted gateway tokens regularly. ]1[

1. Requirements:

  • A Kubernetes cluster, and a self-hosted gateway into that cluster.
  • A service principal with one of the following roles:
    • API Management Service Contributor (built-in role). This role gives access to far more than what we actually need here.
    • API Management Self-Hosted Gateway Token Operator (custom role). This role gives only two permissions, to list the self-hosted gateways and to generate new tokens. ]2[ There are specific steps that should be followed in order to create a custom role.

2. Deployment in the cluster:

The script should be deployed in the same namespace as the API management self-hosted gateway.

The following is required for the deployment:

  • Kubernetes service principal secret, client ID, client secret, and tenant ID.
  • RBAC which is a .yaml file for Role, Role Binding, and Service Account.
  • CronJob


The following steps describe in detail the deployment process:

  1. Create the following “” script file.
    PS: Double click the below text snippet to open the full script file.

#! /bin/bash
set -eo pipefail

# A shell script to use the Azure CLI and kubectl in order to rotate a token on a
# self-hosted application gateway. Designed to be run in a container using a Kubernetes
# CronJob.
# Based on an idea from
# but rewritten.

 function usage {
    cat <<-EOF

     Usage: $(basename $0)
      -s|--subscription-id <SUBSCRIPTION_ID>
      -r|--resource-group <RESOURCE_GROUP>
      -a|--apim-instance <APIM_INSTANCE>
      -g|--apim-gateway <APIM_GATEWAY>
      -n|--namespace <NAMESPACE>
      -t|--token-secret <TOKEN_SECRET>
      -k|--token-key <TOKEN_KEY: last, rotate, primary, secondary>
      -o|--k8s-object <K8S_OBJECT>
      -c|--client-id <SP_ID>
      -p|--client-secret <SP_SECRET>

     Generates a new token for an APIM self-hosted gateway, updates the Kubernetes secret, then performs a rolling restart.
    Arguments may be specified as parameters or as environment variables.     


      -s, --subscription-id, SUBSCRIPTION_ID environment variable
            The GUID of the scription containing the APIM instance. 

      -r, --resource-group, RESOURCE_GROUP environment variable
            The name of the resource group containing the APIM instance. 

      -a, --apim-instance, APIM_INSTANCE environment variable
            The name of the APIM instance 

      -g, --apim-gateway, APIM_GATEWAY environment variable
            The name of the self-hosted gateway 

      -n, --namespace, NAMESPACE environment variable
            The namespace containing the token to rotate

       -t, --token-secret, TOKEN_SECRET environment variable
            The name of the Kubernetes secret object containing the token to rotate 

      -k, --token-key, TOKEN_KEY
            Switch the key used to generate the token. Can be set to one of the following values:
              "last" - default, the value from the last-key-used annotation on the token will be used

                      if no annotation is present, defaults to primary
              "rotate" - rotate from the last used key to the other key: primary -> secondary or secondary -> primary
              "primary" - generate the new token from the primary key
              "secondary" - generate the new token from the secondary key

      -o, --k8s-object, K8S_OBJECT
            The name of the Kubernetes object to restart after updating the secret, using kubectl rollout restart.           Should be specified in the form "type/name", e.g. "deployment/apim-gateway" or "statefulset/apim-gateway"

      --client-id, SP_ID
            The client ID of the service principal to use, if the login method is "sp"       

      --client-secret, SP_SECRET
            The client secret of the service principal to use, if the login method is "sp". This can be either a secret or the path to a certificate.

      --tenant, TENANT
            The tenant ID of the service principal to use, if the login method is "sp".       

            Enable debug logging (set -x) 

    exit 1


# For SP login, we make a temporary directory. Clean it up on exit.
function cleanup {
  [[ ! -z "${MY_AZURE_CONFIG_DIR}" ]] && {
    rm -rf "${MY_AZURE_CONFIG_DIR}"

trap cleanup EXIT

PARSED_ARGUMENTS=$(getopt -a -n "$(basename $0)" -o s:r:a:g:n:t:k:o:l:h --long subscription-id:,resource-group:,apim-instance:,apim-gateway:,namespace:,token-secret:,token-key:,k8s-object:,client-id:,client-secret:,tenant:,debug,help -- "$@")
if "$VALID_ARGUMENTS" != "0" ]; then


eval set -- "$PARSED_ARGUMENTS"
while :

    case "$1" in
      --debug) set -x; shift;;
      -s | --subscription-id) SUBSCRIPTION_ID="${2}"shift 2;;
      -r | --resource-group) RESOURCE_GROUP="${2}"shift 2;;
      -a | --apim-instance) APIM_INSTANCE="${2}"shift 2;;
      -g | --apim-gateway) APIM_GATEWAY="${2}"shift 2;;
      -n | --namespace) NAMESPACE="${2}"shift 2;;
      -t | --token-secret) TOKEN_SECRET="${2}"shift 2;;
      -k | --token-key) TOKEN_KEY="${2}"shift 2;;
      -o | --k8s-object) K8S_OBJECT="${2}"shift 2;;
    --client-id) SP_ID="${2}"shift 2;;
    --client-secret) SP_SECRET="${2}"shift 2;;
    --tenant) TENANT="${2}"shift 2;;
      -h | --help) usage;;
      --) shiftbreak ;;
      *) echo "ERROR: didn't parse an argument properly: ${1} ${2}"; usage;;


# Set defaults

# Error out if variables aren't set
    if [[ -z "${!var}" ]]; then
      echo -e "ERROR: Parameter ${var} is undefined. Please specify the parameter as either an environment variable or a command argument."

if [[ "${FAIL:-0}" == "1" ]]; then
  echo -e "\nRun $(basename $0) --help for usage information."
  exit 1


cat << EOF

APIM Self-hosted Gateway Secret Rotation
Date: $(date)

Subscription ID:  $SUBSCRIPTION_ID
Resource group:   $RESOURCE_GROUP
K8s Namespace:    $NAMESPACE
K8s Token Secret: $TOKEN_SECRET
APIM Key Source:  $TOKEN_KEY
K8S Object:       ${K8S_OBJECT:-not provided}
Client ID:        ${SP_ID:-not provided}
Client Secret:    ${MASKED_SP_SECRET:-not provided}
Client Tenant:    ${TENANT:-not provided}



for var in SP_ID SP_SECRET TENANT; do
    if [[ -z "${!var}" ]]; then
        echo -e "ERROR: When using service principal login type, ${var} must be defined!"

if [[ "${FAIL:-0}" == "1" ]]; then
    echo -e "\nRun $(basename $0) --help for usage information."
    exit 1

MY_AZURE_CONFIG_DIR=$(mktemp -d)
echo -n "Checking for service principal access..."
# Try a service principal
az login --service-principal -u "$SP_ID" -p "$SP_SECRET" --tenant $TENANT >/dev/null
if [[ $(az account list --refresh --query "length([?id=='$SUBSCRIPTION_ID'])" 2>/dev/null) == 1 ]]; then
    echo "done (logged in via service principal)."

    echo -e "\n\nERROR: Failed to access the subscription via az cli, either via already logged in credentials or identity."
    exit 1


echo -n "Validating APIM instance is present and correct..."
APIM_RESOURCE_ID=$(az apim show --subscription $SUBSCRIPTION_ID --resource-group $RESOURCE_GROUP --name $APIM_INSTANCE --query "id" --only-show-errors -o tsv 2>&1) || {
    echo -e "\n\nERROR: Unable to find $APIM_INSTANCE in resource group $RESOURCE_GROUP in subscription $SUBSCRIPTION_ID."
    echo -e "\nCommand output:\n${APIM_RESOURCE_ID}"
    exit 1

echo "done." 

echo -n "Validating APIM gateway instance is present and correct..."
OUTPUT=$(az rest --method GET --uri "${GATEWAY_RESOURCE_ID}" --uri-parameters "api-version=2021-08-01" 2>&1) || {
    echo -e "\n\nERROR: Unable to query APIM self-hosted gateway instance properties."
    echo -e "\nAPI output:\n${OUTPUT}"
    exit 1

echo "done."

echo -n "Validating Kubernetes secret is present and correct..."
LAST_USED_KEY_ANNOTATION=$(kubectl --namespace $NAMESPACE get secret $TOKEN_SECRET -o jsonpath='{.metadata.annotations.last-used-key}' 2>&1) || {
    echo -e "\n\nERROR: unable to retrieve Kubernetes secret ${TOKEN_SECRET}."
    echo -e "\nkubectl output:\n${LAST_USED_KEY_ANNOTATION}"
    exit 1

echo "done (last used key: \"${LAST_USED_KEY_ANNOTATION:-unset}\")."

echo -n "Determining which key to use to generate the token..."
case "${TOKEN_KEY}" in
      case "${LAST_USED_KEY_ANNOTATION:-secondary}" in
        primary) TOKEN_KEY="secondary";;
        secondary) TOKEN_KEY="primary";;
      echo -e "\n\nERROR: Unrecognized argument to -k/--token-key/TOKEN_KEY."

echo "done - token will be generated from the ${TOKEN_KEY} key."

echo -n "Generating new token for gateway..."
TOKEN_EXPIRATION_DATE="$(date -Iseconds -d"@$(($(date +%s)+1592000))")" # Date +30 days; have to use this format for busybox date
TOKEN=$(az rest --method POST --uri $GENERATE_TOKEN_URL --body "{ \"expiry\": \"${TOKEN_EXPIRATION_DATE}\", \"keyType\": \"${TOKEN_KEY}\" }" --query value -o tsv) || {
    echo -e "\n\nERROR: unable to generate new token for gateway."
    echo -e "\nAPI call output:\n${TOKEN}"
    exit 1

echo "done." 

echo -n "Updating Kubernetes secret..."
OUTPUT=$(kubectl --namespace $NAMESPACE create secret generic ${TOKEN_SECRET} --from-literal value="GatewayKey ${TOKEN}" --dry-run=client -o yaml 2>&1 | kubectl --namespace $NAMESPACE apply -f - 2>&1) || {
    echo -e "\n\nERROR: Unable to update token secret."
    echo -e "\nkubectl output:\n${OUTPUT}"
    exit 1

echo "${OUTPUT}"


echo -n "Annotating token secret with \"last-used-key: ${TOKEN_KEY}\"..."
OUTPUT=$(kubectl --namespace $NAMESPACE annotate --overwrite=true secret $TOKEN_SECRET last-used-key="${TOKEN_KEY}" 2>&1) || {
    echo -e "\n\nERROR: Failed to annotate token secret."
    echo -e "\nkubectl output:\n${OUTPUT}"
    exit 1

echo "${OUTPUT}"


if [[ ! -z "${K8S_OBJECT}" ]]; then
    echo -n "Performing a rolling restart of the self-hosted gateway..."
    OUTPUT=$(kubectl --namespace $NAMESPACE rollout restart $K8S_OBJECT 2>&1) || {
      echo -e "\n\nERROR: Failed to restart $K8S_OBJECT."
      echo -e "\nkubectl output:\n${OUTPUT}"
      exit 1
    echo "${OUTPUT}"



echo "Token rotation complete."
echo "A new token was generated based on the ${TOKEN_KEY} APIM key and will expire on ${TOKEN_EXPIRATION_DATE}."

2. Create a Docker image.
PS: Use Dockerfile to create the image as follow:


RUN /usr/local/bin/az aks install-cli

COPY /root


3. Push the image into a Docker Hub Repository.

4. Create the rbac.yaml file as follows:
apiVersion: v1
kind: ServiceAccount
  name: apim-shg-rotate

kind: Role
  name: apim-shg-rotate

  - apiGroups: ["*"]
    resources: ["deployments"]
    verbs: ["get", "patch"]
  - apiGroups: ["*"]
    resources: ["secrets"]
    verbs: ["get", "update", "patch"]
  - apiGroups: ["*"]
    resources: ["statefulsets"]
    verbs: ["get", "patch"]

kind: RoleBinding
  name: apim-shg-rotate

  - kind: ServiceAccount
    name: apim-shg-rotate
  kind: Role
  name: apim-shg-rotate


5. Create the cronjob.yaml file as follows:
PS: Adapt the variables inside this cronjob.yaml file according to your information. Variables that need to be adapted are marked with #adapt.
apiVersion: batch/v1
kind: CronJob
  name: apim-shg-rotate

  concurrencyPolicy: Forbid
  schedule: "0 9 1,15 * *"           #adapt
      activeDeadlineSeconds: 600
      completions: 1
      parallelism: 1
            aadpodidbinding: apim-shg-rotate
            - name: update-token
              image: qbqalex/rotate4shg:latest     #adapt
              imagePullPolicy: Always
                - name: SUBSCRIPTION_ID
                  value: SUBSCRIPTION_ID_Value     #adapt
                - name: RESOURCE_GROUP
                  value: Resource_Group_Name       #adapt
                - name: APIM_INSTANCE
                  value: APIM_INSTANCE_Name   #adapt
                - name: APIM_GATEWAY                            
                  value: APIM_GATEWAY_Name         #adapt
                - name: NAMESPACE
                  value: APIM_SHG_NAMESPACE        #adapt
                - name: TOKEN_SECRET
                  value: APIM_TOKEN_Secret         #adapt
                - name: TOKEN_KEY
                  value: APIM_TOKEN_KEY            #adapt
                - name: K8S_OBJECT
                  value: APIM_SHG_OBJECT           #adapt
                - name: SP_ID
                  value: Service_Principal_ID      #adapt
                - name: SP_SECRET
                  value: Service_Principal_Secret  #adapt
                - name: TENANT

                  value: Tenant_ID                #adapt

          restartPolicy: Never

          serviceAccountName: apim-shg-rotate

          automountServiceAccountToken: true

6. Finally, apply the two .yaml files created in the previous two steps as follows:

             kubectl apply -f rbac.yalm
             kubectl apply -f cronjob.yalm

Now the CronJob will run on a regular basis, depending on the “schedule” chosen in the cronjob.yaml file.

For example, if the schedule was chosen as follow 0 0 */25 * * : this means that CronJob will regenerate the token at 00:00 on every 25th day of the month, i.e., the token will be regenerated one time each month before it expires, and it will be re-assigned to the self-hosted gateway.

Ihre Kontaktmöglichkeiten

Sie haben eine konkrete Frage an uns


Bleiben Sie immer auf dem Laufenden


Mit meinem "Ja" erkläre ich mich mit der Verarbeitung meiner Daten zur Zusendung von Informationen einverstanden. Ich weiß, dass ich diese Erklärung jederzeit durch einfache Mitteilung widerrufen kann. Bei einem Nein an dieser Stelle erhalte ich zukünftig keine Informationen mehr.

© QUIBIQ GmbH · Impressum · Datenschutz