Use NetScaler credentials stored in a Vault server with NetScaler Node Controller

Last published : Nov 02, 2026
NetScaler Node Controller logs in to NetScaler to configure the routes and the VXLAN overlay. Instead of a Kubernetes secret, you can store the NetScaler credentials in a HashiCorp Vault server and have a Vault Agent sidecar deliver them to the node controller as files.
The node controller hot-reloads these file-based credentials. When the credential rotates in Vault, the node controller validates the new credential against the NetScaler and switches to it without a pod restart. If a rotation delivers an invalid credential, the node controller does not use it and keeps the last-good credential. If you already changed the password on NetScaler, the last-good credential no longer works either, so the node controller cannot configure NetScaler, for example to add routes for new nodes, until you fix the value in Vault (see Step 6). The node 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 Node Controller 3.2.13 or later, and the netscaler-node-controller Helm chart 3.2.13 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 GSLB controller

How it works

A Vault Agent sidecar in the node controller pod logs in to Vault with the pod's service account token. It writes the user name and password into an in-memory volume that the Helm chart mounts at vaultCredentials.mountPath (default /etc/citrix), and rewrites them whenever they change in Vault.
The node controller reads each field (user name and password) from the first of these sources that has a value:
  1. <mountPath>/auth/<NS_IP>/username and <mountPath>/auth/<NS_IP>/password
  2. <mountPath>/username and <mountPath>/password
  3. The NS_USER and NS_PASSWORD environment variables, from the adcCredentialSecret secret
With the default vaultCredentials.suppressEnvCredentials: true, the chart does not set NS_USER and NS_PASSWORD, so you do not need adcCredentialSecret. Set it to false to keep the secret as a start-up fallback. Only the files are hot-reloaded. When no credential file is present, the node controller uses the environment variables, and hot-reload is off. The node controller looks for the files only at start-up, and the Vault Agent sidecar usually writes them later, so if you set suppressEnvCredentials to false, also add the Vault Agent as an init container (the same as the sidecar, plus -exit-after-auth), or hot-reload stays off.

Prerequisites

  • The prerequisites for deploying NetScaler Node Controller, including the NetScaler system user for the node controller and its command policy. For more information, see Establish network between Kubernetes nodes and Ingress NetScaler using node controller. You store the user name and password of that user 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 Kubernetes Gateway 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.

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

Log in to the Vault server and run the following commands:
  1. Store the credentials of the node controller's NetScaler user:
    vault kv put secret/netscaler/nsnc username='nsnc' password='&lt;password>'
    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 this secret. KV v2 stores data under secret/data/... and metadata under secret/metadata/..., so grant read on both.
    netscaler-nsnc-kv-ro.hcl:
    path "secret/data/netscaler/nsnc" {
      capabilities = ["read"]
    }
    path "secret/metadata/netscaler/nsnc" {
      capabilities = ["read"]
    }
    Then load it:
    vault policy write netscaler-nsnc-kv-ro netscaler-nsnc-kv-ro.hcl
  3. Create a role that lets the node controller's service account use the policy. This example uses the service account nsnc-vault, which you set with serviceAccount.name in Step 4. Replace <nsnc-namespace> with the namespace of the node controller.
    vault write auth/kubernetes/role/netscaler-nsnc

    bound_service_account_names=nsnc-vault

    bound_service_account_namespaces=&lt;nsnc-namespace>

    policies=netscaler-nsnc-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 two files. Keep exit_after_auth = false, so that the agent keeps running and rewrites the files when the credential rotates. static_secret_render_interval makes the agent check Vault for changes every 30 seconds; the default is 5 minutes.
vault-agent-config.hcl:
exit_after_auth = false
pid_file        = "/home/vault/pidfile"

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

template_config {
  static_secret_render_interval = "30s"
}

template {
  destination = "/etc/citrix/username"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/nsnc\" }}{{ .Data.data.username }}{{ end }}"
}
template {
  destination = "/etc/citrix/password"
  perms       = "0444"
  contents    = "{{ with secret \"secret/data/netscaler/nsnc\" }}{{ .Data.data.password }}{{ end }}"
}
The templates write the files with mode 0444, because the Vault Agent runs as a different user than the node controller. Only the containers that mount the citrix-creds volume can read the files.
Create a ConfigMap from it in the namespace of the node controller:
kubectl create configmap nsnc-vault-agent-config --from-file=./vault-agent-config.hcl -n <nsnc-namespace>

Step 4: Deploy the node 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.
nsnc-vault-values.yaml:
serviceAccount:
  name: nsnc-vault             # the service account that the Vault role in Step 2 allows

vaultCredentials:
  enabled: true                # adcCredentialSecret is not needed

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: nsnc-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 node controller with its usual values, but without adcCredentialSecret:
helm install nsnc netscaler/netscaler-node-controller -f nsnc-vault-values.yaml -n <nsnc-namespace> --set license.accept=yes,nsIP=<NSIP>,vtepIP=<NetScaler SNIP>,vxlan.id=<VXLAN ID>,vxlan.port=<VXLAN PORT>,network=<IP-address-range-for-VTEP-overlay>,cniType=<CNI type>
The Vault-related values of the netscaler-node-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 set NS_USER and NS_PASSWORD from adcCredentialSecret. Set it to false to keep the secret as a start-up fallback.
vaultCredentials.credentialWaitSeconds 120 How long the node 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 node controller container.
extraEnv [] Extra environment variables for the node controller container, such as NS_CRED_RELOAD_INTERVAL.

Step 5: Verify

Check the node controller logs. Find the deployment name with kubectl get deploy -n <nsnc-namespace>, then run:
kubectl logs deploy/<deployment-name> -c netscaler-node-controller -n <nsnc-namespace> | grep -iE "hot-reload|credential"
Expected:
  • Starting credential hot-reload watcher for <NSIP>
  • The pod becomes Ready.

Step 6: Rotate the credentials

  1. On the NetScaler, change the password of the node controller's user: set system user nsnc <newpassword>.
  2. Right after that, update the Vault secret to the same value: vault kv put secret/netscaler/nsnc username='nsnc' password='<newpassword>'.
  3. Within 30 seconds, the Vault Agent picks up the change and rewrites the files. The node 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.
  4. If the new value is wrong, the node controller does not use it. It keeps the last-good credential and logs Credential rotation probe failed. Because you changed the password on NetScaler in item 1, the last-good credential no longer works either, so the node controller cannot configure NetScaler. Each request that it sends with the old password fails. The node controller keeps testing the new value, but after repeated failures it waits longer between tests, up to 10 minutes. Fix the value in Vault right away.
  5. After you fix the value, the node controller uses it after its next test, which can take up to about 10 minutes.