Skip to content

Automation & CI/CD

NetDefense supports two complementary automation patterns: static token authentication for CI/CD pipelines that need non-interactive access, and structured output for scripting and downstream tool integration.

The interactive browser login (ndcli auth login) does not work in headless environments like GitHub Actions or GitLab CI — there is no browser to redirect to. Personal Access Tokens (PATs) solve this: you create a token once on your workstation, store it as a CI secret, and every pipeline run uses it automatically via the NDCLI_TOKEN environment variable.

See the Personal Access Tokens guide for a full overview of token scopes, expiration, and the permission model.

  1. Create a token on your workstation (requires interactive login):

    Terminal window
    ndcli auth token create --name "github-ci" --scope rw --expiry 90d

    Copy the displayed ndpat_... value — it is shown only once.

  2. Add the token as a CI secret. In GitHub Actions: Settings > Secrets and variables > Actions > New repository secret. Name it NDCLI_TOKEN.

  3. Reference the secret in your pipeline using NDCLI_TOKEN: ${{ secrets.NDCLI_TOKEN }} (GitHub) or $NDCLI_TOKEN (GitLab).

  4. Run any ndcli command. The token is picked up automatically — no login prompt.

name: Sync Firewall Config
on:
push:
branches: [main]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Install ndcli
run: |
brew tap netdefense-io/tap
brew trust netdefense-io/tap
brew install ndcli
- name: List devices
env:
NDCLI_TOKEN: ${{ secrets.NDCLI_TOKEN }}
run: |
ndcli device list -f json
- name: Sync configuration
env:
NDCLI_TOKEN: ${{ secrets.NDCLI_TOKEN }}
run: |
ndcli sync apply --org my-org --yes

Tokens have a finite lifetime (90 days by default). To rotate without pipeline downtime:

  1. Create the replacement token locally:

    Terminal window
    ndcli auth token create --name "github-ci-v2" --scope rw --expiry 90d
  2. Update the CI secret with the new token value.

  3. Verify the pipeline passes with the new token.

  4. Revoke the old token:

    Terminal window
    ndcli auth token revoke github-ci --yes

For automated rotation in scripts (e.g. from a privileged rotation system), you can script steps 1 and 4 using JSON output:

#!/usr/bin/env bash
# Requires interactive login — run with a user session, not NDCLI_TOKEN.
set -euo pipefail
OLD_NAME="${1:?Usage: $0 <old-token-name> <new-token-name>}"
NEW_NAME="${2:?Usage: $0 <old-token-name> <new-token-name>}"
# Create the new token (capture the token value from JSON output)
NEW_TOKEN=$(ndcli auth token create --name "$NEW_NAME" --scope rw --expiry 90d -f json | jq -r '.token')
echo "New token created: $NEW_NAME"
echo "Token value: $NEW_TOKEN"
echo ""
echo "Update your CI secret to: $NEW_TOKEN"
echo "Then run: ndcli auth token revoke $OLD_NAME --yes"

The list, describe and status commands support multiple output formats via the -f flag. Use -f json to get machine-readable output suitable for jq, Python scripts, or other downstream tools. Commands that change something may print a confirmation line instead of JSON, so rely on their exit status, and see Errors in scripts for failures and partial failures.

FormatFlagUse case
Table-f table (default)Human-readable aligned columns for terminal use.
Simple-f simpleCompact key-value output for quick inspection.
Detailed-f detailedExpanded view with all fields and relationships.
JSON-f jsonMachine-readable output for scripting and automation.
Terminal window
ndcli device list -f json
{
"devices": [
{
"uuid": "e2eb98b8-0ed3-11f1-8792-66a021937fa2",
"name": "fw-hq-primary",
"status": "ENABLED",
"organization": "example-org",
"organizational_units": ["production"],
"version": "2.4.1",
"heartbeat": "2026-02-21T03:17:31Z",
"synced_at": "2026-02-21T03:16:43Z"
}
],
"total": 8
}
Terminal window
# Get names of all devices not synced
ndcli sync status -f json | jq -r '.items[] | select(.in_sync == false) | .device_name'
# Count devices per OU
ndcli device list -f json | jq -r '.devices[].organizational_units[]' | sort | uniq -c
# Get overlay IPs for all VPN members
ndcli network member list corporate-vpn -f json | jq -r '.members[] | "\(.device): \(.overlay_ip)"'

A failed command exits non-zero and prints its error on standard error. With -f json the error is a document of its own, {"error": {"code": "...", "message": "..."}}, and code is there when the server gave one. Branch on the exit status, and on code to tell failures apart:

Terminal window
if ! ndcli snippet create web-servers --type ALIAS --file ./web-servers.json -f json 2> err.json; then
jq -r '.error.code // "no code"' err.json
fi

See Errors for the text format.

A partial failure is not a failed command. sync apply exits 0 when only some devices fail, so read errors in its JSON; device approve-all exits 0 whatever fails, and ends with an Approved: N, Failed: M line.


Use org-scoped RO tokens for read-only CI jobs. If a pipeline only reads device lists or sync status, restrict the token: --scope ro --org <your-org>. A compromised RO token cannot modify any configuration.

Use separate tokens per CI system and environment. Create one token per pipeline (e.g. github-ci, gitlab-staging, gitlab-prod). This limits the blast radius of a leak and makes it easy to identify unauthorized use via last_used_at.

Align token expiry with your rotation policy. If you rotate every 90 days, set --expiry 90d. This ensures tokens expire even if rotation is missed.

Monitor last_used_at to find stale tokens. Run ndcli auth token list periodically, with an interactive login so that it lists every token on the account (a token restricted to one organization lists only the tokens restricted to that same organization). Tokens that have never been used or show a very old last_used_at are candidates for revocation.

Fail the pipeline explicitly if the token is missing. At the top of every automation script, check that NDCLI_TOKEN is set and fail loudly if it is not — otherwise the script may fall back to prompting for interactive login and hang.

Terminal window
if [ -z "${NDCLI_TOKEN:-}" ]; then
echo "Error: NDCLI_TOKEN is not set." >&2
exit 1
fi