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.
Personal Access Tokens for CI/CD
Section titled “Personal Access Tokens for CI/CD”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.
Quick setup
Section titled “Quick setup”-
Create a token on your workstation (requires interactive login):
Terminal window ndcli auth token create --name "github-ci" --scope rw --expiry 90dCopy the displayed
ndpat_...value — it is shown only once. -
Add the token as a CI secret. In GitHub Actions: Settings > Secrets and variables > Actions > New repository secret. Name it
NDCLI_TOKEN. -
Reference the secret in your pipeline using
NDCLI_TOKEN: ${{ secrets.NDCLI_TOKEN }}(GitHub) or$NDCLI_TOKEN(GitLab). -
Run any
ndclicommand. The token is picked up automatically — no login prompt.
CI provider examples
Section titled “CI provider examples”name: Sync Firewall Configon: 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 --yesstages: - deploy
sync-config: stage: deploy image: ubuntu:latest before_script: - brew tap netdefense-io/tap - brew trust netdefense-io/tap - brew install ndcli script: - ndcli device list -f json - ndcli sync apply --org my-org --yes variables: NDCLI_TOKEN: $NDCLI_TOKEN # Set in GitLab CI/CD > Variables only: - main#!/usr/bin/env bash# Works in any CI system that supports environment variables.# Set NDCLI_TOKEN before running this script.
set -euo pipefail
if [ -z "${NDCLI_TOKEN:-}" ]; then echo "Error: NDCLI_TOKEN is not set." >&2 exit 1fi
ndcli device list -f jsonndcli sync apply --org my-org --yesToken rotation
Section titled “Token rotation”Tokens have a finite lifetime (90 days by default). To rotate without pipeline downtime:
-
Create the replacement token locally:
Terminal window ndcli auth token create --name "github-ci-v2" --scope rw --expiry 90d -
Update the CI secret with the new token value.
-
Verify the pipeline passes with the new token.
-
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"JSON output for scripting
Section titled “JSON output for scripting”Every ndcli command supports multiple output formats via the -f flag. Use -f json to get machine-readable output suitable for jq, Python scripts, or other downstream tools.
Output formats
Section titled “Output formats”| Format | Flag | Use case |
|---|---|---|
| Table | -f table (default) | Human-readable aligned columns for terminal use. |
| Simple | -f simple | Compact key-value output for quick inspection. |
| Detailed | -f detailed | Expanded view with all fields and relationships. |
| JSON | -f json | Machine-readable output for scripting and automation. |
JSON output example
Section titled “JSON output example”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}Scripting with jq
Section titled “Scripting with jq”# Get names of all devices not syncedndcli sync status -f json | jq -r '.items[] | select(.in_sync == false) | .device_name'
# Count devices per OUndcli device list -f json | jq -r '.devices[].organizational_units[]' | sort | uniq -c
# Get overlay IPs for all VPN membersndcli network member list corporate-vpn -f json | jq -r '.members[] | "\(.device): \(.overlay_ip)"'Best practices
Section titled “Best practices”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. 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.
if [ -z "${NDCLI_TOKEN:-}" ]; then echo "Error: NDCLI_TOKEN is not set." >&2 exit 1fi