Use NetScaler credentials stored in a Vault server with NetScaler GSLB controller

Last published : Nov 02, 2026
The NetScaler GSLB controller logs in to the NetScaler of every GSLB site. Instead of a Kubernetes secret for each site, you can store the site credentials in a HashiCorp Vault server and have a Vault Agent sidecar deliver them to the GSLB controller as files. This works for both multi-site and single-site GSLB deployments.
The GSLB controller hot-reloads the credentials that it uses to log in to the sites. When the credential of a site rotates in Vault, the controller validates the new credential against the NetScaler of that site and switches to it without a pod restart. If a rotation delivers an invalid credential, the controller does not use it and keeps the last-good credential of that site. If you already changed the password on the NetScaler of that site, the last-good credential no longer works either, so the controller cannot log in to that site until you fix the value in Vault (see Step 6). Each site rotates independently. The controller checks for rotated credentials every 30 seconds by default. To change the interval, set the NS_CRED_RELOAD_INTERVAL environment variable.
This feature needs NetScaler GSLB controller 4.3.22 or later.
Vault pages for the other NetScaler controllers:
  • Use NetScaler credentials stored in a Vault server with NetScaler Ingress Controller
  • Use NetScaler credentials stored in a Vault server with NetScaler Kubernetes Gateway Controller
  • Use NetScaler credentials stored in a Vault server with NetScaler Node Controller

How it works

A Vault Agent sidecar in the GSLB controller pod logs in to Vault with the pod's service account token. It writes the credentials into an in-memory volume that the Helm chart mounts at vaultCredentials.mountPath (default /etc/citrix), and rewrites them whenever they change in Vault.
File Content
<mountPath>/auth/<siteName>/username The user name for the NetScaler of the site.
<mountPath>/auth/<siteName>/password The password of that user.
<mountPath>/auth/<siteName>/mep_password Optional. The password for secure site-to-site communication. It takes the place of the sitesyncpassword key of a site secret. The controller applies it to NetScaler only when it starts (see Step 6).
<mountPath>/username and <mountPath>/password Required when you set nsIP, for example in a single-site deployment. The credentials of that NetScaler.
<siteName> is the siteName value of the site in sitedata.
For each site, a file that has a value takes precedence over the Kubernetes secret of the site (secretName). If a file does not exist, is empty, or contains only white space, the controller skips it and uses the site secret, if the chart passes the secret to the controller. With the default vaultCredentials.suppressEnvCredentials: true, the chart does not pass the site secrets to the controller, so you do not need to create them. Set it to false to keep the secrets as a start-up fallback. Only the files are hot-reloaded.

Prerequisites

  • The prerequisites for deploying the GSLB controller. For more information, see Deploy NetScaler GSLB controller for multiple sites.
  • A NetScaler system user for each GSLB site. You store its user name and password in Vault.
  • A HashiCorp Vault server with the KV v2 secrets engine enabled at secret/, and vault CLI access to it.

Step 1: Set up Vault (one time)

If you have not yet set up Vault and its Kubernetes authentication for another NetScaler controller in this cluster, complete Steps 0, 1, and 2 of Use NetScaler credentials stored in a Vault server with NetScaler Ingress Controller. These steps install Vault, create the service account that Vault uses to check pod tokens, and enable Kubernetes authentication. Then return to this page and continue with Step 2.
Note:
GSLB controllers run in several clusters. Kubernetes authentication in Vault is set up for one cluster. For each additional cluster, enable Kubernetes authentication at its own path and configure it for that cluster. For example: vault auth enable -path=kubernetes-cluster2 kubernetes, and then vault write auth/kubernetes-cluster2/config ... with the API server address and CA certificate of that cluster. In that cluster, create the role in Step 2 under the same path (auth/kubernetes-cluster2/role/netscaler-gslb), and set mount_path = "auth/kubernetes-cluster2" in the Vault Agent configuration in Step 3.

Step 2: Store the site credentials and allow the GSLB controller to read them

Log in to the Vault server and run the following commands. The examples use two sites, site1 and site2.
  1. Store the credentials of each site. Include sitesyncpassword only if you use a password for secure site-to-site communication:
    vault kv put secret/netscaler/site1 username='&lt;user for site1>' password='&lt;password for site1>' sitesyncpassword='&lt;site-to-site password for site1>'
    vault kv put secret/netscaler/site2 username='&lt;user for site2>' password='&lt;password for site2>' sitesyncpassword='&lt;site-to-site password for site2>'
    Note: A password that you type in a command stays in your shell history. To avoid that, first read the password into a variable without showing it (for example, read -rs PASSWORD in bash), and then use password="$PASSWORD" in the command.
  2. Create a read-only policy for the site secrets. KV v2 stores data under secret/data/... and metadata under secret/metadata/..., so grant read on both. If you set nsIP to a NetScaler that is not one of the sites, also store its credentials in a secret of their own and add that path.
    netscaler-gslb-kv-ro.hcl:
    path "secret/data/netscaler/site1" {
      capabilities = ["read"]
    }
    path "secret/metadata/netscaler/site1" {
      capabilities = ["read"]
    }
    path "secret/data/netscaler/site2" {
      capabilities = ["read"]
    }
    path "secret/metadata/netscaler/site2" {
      capabilities = ["read"]
    }
    Then load it:
    vault policy write netscaler-gslb-kv-ro netscaler-gslb-kv-ro.hcl
  3. Create a role that lets the GSLB controller's service account use the policy. This example uses the service account gslb-vault, which you set with serviceAccount.name in Step 4. Replace <gslb-namespace> with the namespace of the GSLB controller.
    vault write auth/kubernetes/role/netscaler-gslb

    bound_service_account_names=gslb-vault

    bound_service_account_namespaces=&lt;gslb-namespace>

    policies=netscaler-gslb-kv-ro

    ttl=24h
    Use a role name that no other controller uses. Writing a role with the same name replaces it, so another controller that uses that role can no longer log in to Vault.

Step 3: Create the Vault Agent configuration

Create a Vault Agent configuration that writes the files of every site. Keep exit_after_auth = false, so that the agent keeps running and rewrites the files when a credential rotates. static_secret_render_interval makes the agent check Vault for changes every 30 seconds; the default is 5 minutes. Leave out the mep_password stanzas if you do not use a site-to-site password.
vault-agent-config.hcl:
exit_after_auth = false
pid_file        = "/home/vault/pidfile"

auto_auth {
  method "kubernetes" {
    mount_path = "auth/kubernetes"
    config = { role = "netscaler-gslb" }
  }
  sink "file" { config = { path = "/home/vault/.vault-token" } }
}

template_config {
  static_secret_render_interval = "30s"
}

template {
  destination = "/etc/citrix/auth/site1/username"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/site1\" }}{{ .Data.data.username }}{{ end }}"
}
template {
  destination = "/etc/citrix/auth/site1/password"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/site1\" }}{{ .Data.data.password }}{{ end }}"
}
template {
  destination = "/etc/citrix/auth/site1/mep_password"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/site1\" }}{{ .Data.data.sitesyncpassword }}{{ end }}"
}
template {
  destination = "/etc/citrix/auth/site2/username"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/site2\" }}{{ .Data.data.username }}{{ end }}"
}
template {
  destination = "/etc/citrix/auth/site2/password"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/site2\" }}{{ .Data.data.password }}{{ end }}"
}
template {
  destination = "/etc/citrix/auth/site2/mep_password"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/site2\" }}{{ .Data.data.sitesyncpassword }}{{ end }}"
}
If you set nsIP, also add templates for the files /etc/citrix/username and /etc/citrix/password. Without them, the controller cannot log in to that NetScaler, and it stops after credentialWaitSeconds. In a single-site deployment, nsIP is the NetScaler of site1, so these templates read the site1 secret:
template {
  destination = "/etc/citrix/username"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/site1\" }}{{ .Data.data.username }}{{ end }}"
}
template {
  destination = "/etc/citrix/password"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/site1\" }}{{ .Data.data.password }}{{ end }}"
}
The templates write the files with mode 0444, because the Vault Agent runs as a different user than the GSLB controller. Only the containers that mount the citrix-creds volume can read the files.
Create a ConfigMap from it in the namespace of the GSLB controller:
kubectl create configmap gslb-vault-agent-config --from-file=./vault-agent-config.hcl -n <gslb-namespace>

Step 4: Deploy the GSLB controller with Vault credentials

Set vaultCredentials.enabled to true and add the Vault Agent as a sidecar with extraContainers. The chart creates the shared citrix-creds in-memory volume. Add the agent's volumes with extraVolumes, including a projected service account token that the agent uses to log in to Vault.
The following values.yaml file is based on the example in Deploy NetScaler GSLB controller for multiple sites. Keep your other values, such as siteMask. The sites do not need secretName, because the credentials come from Vault:
license:
  accept: yes
localRegion: "east"
localCluster: "cluster1"
openshift: false # set to true for OpenShift deployments
entityPrefix: "k8s"

sitedata:
- siteName: "site1"
  siteIp: "x.x.x.x"
  siteRegion: "east"
- siteName: "site2"
  siteIp: "x.x.x.x"
  siteRegion: "west"

serviceAccount:
  name: gslb-vault             # the service account that the Vault role in Step 2 allows

vaultCredentials:
  enabled: true

extraContainers:
  - name: vault-agent
    image: hashicorp/vault:1.21
    args: ["agent", "-config=/etc/vault/vault-agent-config.hcl", "-log-level=info"]
    env:
      - name: VAULT_ADDR
        value: "https://<VAULT URL>:8200"
      - name: SKIP_SETCAP      # needed when the container does not run as root, for example on OpenShift
        value: "true"
    volumeMounts:
      - { name: vault-config, mountPath: /etc/vault }
      - { name: vault-token,  mountPath: /home/vault }
      - { name: citrix-creds, mountPath: /etc/citrix }
      - { name: sa-token,     mountPath: /var/run/secrets/kubernetes.io/serviceaccount, readOnly: true }

extraVolumes:
  - name: vault-config
    configMap: { name: gslb-vault-agent-config }
  - name: vault-token
    emptyDir: { medium: Memory }
  - name: sa-token
    projected:
      sources:
        - serviceAccountToken: { path: token, expirationSeconds: 3600, audience: vault }
        - configMap: { name: kube-root-ca.crt, items: [{ key: ca.crt, path: ca.crt }] }
        - downwardAPI: { items: [{ path: namespace, fieldRef: { fieldPath: metadata.namespace } }] }
The projected token has the audience vault, so it works for logging in to Vault but not against the Kubernetes API.
Install the GSLB controller:
helm install gslb-release netscaler/netscaler-gslb-controller -f values.yaml -n <gslb-namespace>
You store the site credentials and the policy in Vault only once. In every cluster where you deploy a GSLB controller, create the role (Step 2, item 3), create the ConfigMap (Step 3), and install the controller (Step 4). Keep the same order of sites in sitedata in all clusters.
The Vault-related values of the netscaler-gslb-controller chart:
Value Default Description
vaultCredentials.enabled false Read the NetScaler credentials from files and hot-reload them.
vaultCredentials.mountPath /etc/citrix The path where the Vault Agent writes the files.
vaultCredentials.suppressEnvCredentials true Do not pass adcCredentialSecret and the site secrets to the controller. Set it to false to keep them as a start-up fallback.
vaultCredentials.credentialWaitSeconds 120 How long the controller waits at start-up for the files before it fails.
extraContainers [] Extra containers in the pod, such as the Vault Agent sidecar.
extraVolumes / extraVolumeMounts [] Extra pod volumes, and extra volume mounts for the controller container.
extraEnv [] Extra environment variables for the controller container, such as NS_CRED_RELOAD_INTERVAL.

Step 5: Verify

Check the controller logs. Find the deployment name with kubectl get deploy -n <gslb-namespace>, then run:
kubectl logs deploy/<deployment-name> -c nsgslbc -n <gslb-namespace> | grep -iE "hot-reload|CredentialWatcher|credentials"
Expected:
  • Credential hot-reload ENABLED
  • Starting GSLBCredentialWatcher ... for <N> site(s)
  • If you set nsIP: Starting CredentialWatcher ... for <NSIP>
  • The pod becomes Ready.

Step 6: Rotate the credentials of a site

  1. On the NetScaler of the site, change the password of the user: set system user <user> <newpassword>.
  2. Right after that, update the password in Vault. Use vault kv patch, which keeps the other keys of the secret, such as sitesyncpassword:
    vault kv patch secret/netscaler/site1 password='&lt;newpassword>'
  3. Within 30 seconds, the Vault Agent picks up the change and rewrites the files of that site. The controller validates the new credential and switches to it at its next check (NS_CRED_RELOAD_INTERVAL, default 30 seconds), so the switch takes about a minute. The other sites are not affected.
  4. If the new value is wrong, the controller does not use it. It keeps the last-good credential of the site and logs GSLB credential hot-reload: probe FAILED for site '<siteName>'. Because you changed the password on the NetScaler of the site in item 1, the last-good credential no longer works either, so the controller cannot log in to that site. The controller keeps testing the new value, but after repeated failures it waits longer between tests, up to 10 minutes. The GSLB controller in every other cluster does the same. Fix the value in Vault right away.
  5. After you fix the value, the controller switches to it at its next test, which can take 10 minutes or more.
Important: The controller does not hot-reload the site-to-site password. When nsIP is set, the controller applies that password to NetScaler only when it starts. It uses mep_password, or the site's login password when mep_password is missing. After you change sitesyncpassword, or the password of a site that has no sitesyncpassword, restart the GSLB controller in every cluster that sets nsIP.