Skip to content

License Key Provisioning ​

License keys replace the legacy shared API key with per-client, per-tier authentication for the NetVuln Tool collection portal.

What Changed ​

Previously, all NetVuln Tool deployments shared a single CONSULTATION_API_KEY (the NV_UPLOAD_API_KEY environment variable on the server). License keys provide:

  • Per-client isolation: each client gets their own key with a client_id
  • Tier-based permissions: feature access scales with the client's tier
  • Usage limits: configurable session and agent caps per key
  • Lifecycle management: keys can be expired, revoked, or deactivated
  • Audit trail: every key usage is timestamped

Tier Comparison ​

FeatureProfessionalBusinessMSP
Upload sessionsYYY
Download reportsYYY
View sessionsYYY
Update statusYYY
Benchmark comparisonYY
Remediation trackingYY
Webhook subscriptionsYY
Compliance mappingYY
C2 / Agent commandsYY
Agent group managementY
Database healthY
Audit log accessY
Fleet monitoringY

Default limits:

TierMax SessionsMax Agents
Professional1003
Business75015
MSP3,00040

Limits are configurable per key at creation time. No tier is unlimited; null (no limit) is an admin-only per-key override, never a tier default.

Key Format ​

nvt_{tier}_{64 hex characters}

The {tier} segment is one of pro, biz, or msp:

  • nvt_pro_<hex> (Professional)
  • nvt_biz_<hex> (Business)
  • nvt_msp_<hex> (MSP)

The key prefix (first 14 characters) is used for fast database lookup. The full key is SHA-256 hashed before storage; the plaintext is shown once at creation and cannot be recovered.

Admin Provisioning ​

Via Admin Dashboard ​

  1. Log in to the admin dashboard at https://portal.netvulntool.com/admin/
  2. Navigate to the Licenses tab
  3. Click Create License Key
  4. Fill in: tier, client ID, name, optional description, optional limits and expiry
  5. Copy the plaintext key shown (it will not be displayed again)
  6. Provide the key to the client securely

Via API ​

bash
# Acquire the admin JWT from the portal's OWN Identity service. Netlify
# Identity is per site, so the token has to be minted by the same host that
# will later accept it. This endpoint is not a portal function, so it is not
# intercepted at the edge.
TOKEN=$(curl -s -X POST "https://portal.netvulntool.com/.netlify/identity/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=password" \
  --data-urlencode "username=$ADMIN_EMAIL" \
  --data-urlencode "password=$ADMIN_PW" \
  | jq -r '.access_token')

# Create a professional-tier key. Everything under /api/ IS a portal function,
# so the token travels in X-Identity-Token. Sending it in Authorization gets
# the request terminated at the edge before the handler runs.
curl -s -X POST "https://portal.netvulntool.com/api/licenses" \
  -H "X-Identity-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tier": "professional",
    "client_id": "acme-corp",
    "name": "ACME Corp Production",
    "max_sessions": 100
  }' | jq '.license.plaintext_key'

Client Setup ​

New Installation ​

In your report_config.conf:

bash
CONSULTATION_API_URL="https://portal.netvulntool.com/api/upload"
NV_API_KEY="nvt_pro_<hex>"

You can also place the key in the file-based resolution channel, ~/.netvuln/license.key. An installer or manual setup writes this file (keep it mode 600); it is the lowest-precedence source and is handy when you want the key available to every config on a host without repeating it.

Migration from Legacy Key ​

If you are currently using CONSULTATION_API_KEY, you have two options:

  1. Replace the variable (recommended):

    bash
    # Old
    CONSULTATION_API_KEY="old-shared-key"
    # New
    NV_API_KEY="nvt_pro_<hex>"
  2. Keep both (NV_API_KEY takes precedence when both are set):

    bash
    CONSULTATION_API_KEY="old-shared-key"   # fallback
    NV_API_KEY="nvt_pro_<hex>"              # used when set

The legacy CONSULTATION_API_KEY variable is still supported for backward compatibility.

Add or change the key on an existing install ​

To add a key to an install that has none, or to change the key on one that has, run scripts/set_license_key.sh. Do not re-run install.sh for this: it rewrites your configs and leaves scanning turned off.

Read the key silently, then run the script:

bash
read -rs NV_LICENSE_KEY && export NV_LICENSE_KEY
bash scripts/set_license_key.sh

Leading and trailing whitespace in NV_LICENSE_KEY is trimmed, and the script removes the variable from its own environment so no child process inherits it. If no key is set in the environment, the script prompts for it on a terminal without echoing it. The key is never accepted as a command-line argument (--key and --license-key exit 2) and never appears on any command line, in output or in a log.

The script validates the key first. A malformed nvt_ key is refused, and a legacy key is accepted only if it uses letters, digits, ., _ and - and is at most 256 characters. Then it changes exactly two things:

  • the license file, license.key in the .netvuln directory (mode 600, directory mode 700). On a current-user install the file is replaced atomically (written mode 600 from the start, then renamed into place), and a directory or symlink at license.key is refused. On the nvt install the key is installed through sudo as owner nvt, mode 600, inside the 700 directory.
  • NV_API_KEY in every *.conf file in the configs directory. An existing uncommented NV_API_KEY= line is replaced in place, keeping any export prefix and indentation. A config with no such line gets NV_API_KEY="..." appended. Dot-files such as .c2_overrides.conf and symlinks are skipped. Each edited config ends at mode 600 (owned by nvt on the nvt install). Every other line is left exactly as it was. A config the script cannot read is never overwritten, and a configs directory that is unreadable (or, on a current-user install, not writable) is refused before anything is written.

It picks the install from where the script lives. On the service-account install under /opt/netvuln-tool it writes as the nvt account through sudo, using /home/nvt/.netvuln/license.key and /opt/netvuln-tool/configs. On a current-user install it writes ~/.netvuln/license.key and the configs in ${NVD_BASE_DIR:-$HOME/netvuln-tool}/configs, the same directory the daemon reads. It refuses to run from a tree other than the one the daemon runs from, or through sudo from a tree owned by someone else.

Afterwards it reloads the daemon so the new key takes effect: through systemd when the unit is active, or through scripts/netvuln_daemon_ctl.sh reload on a current-user install whose daemon is running. Otherwise the key takes effect at the next start. Pass --no-reload to skip the reload, or --help for the usage text.

Exit codeMeaning
0Key written (and the daemon reloaded, unless --no-reload)
1Runtime failure: a write, the reload, or an install guard. A guard refuses before anything is written; after a partial write the message names the files already updated
2Usage error, missing key, or invalid key

Client-Side Validation (v4.1.0) ​

Canonical Key and Resolution Precedence ​

NV_API_KEY is the canonical license key. The scanner checks these names in order (the same order in lib/license.sh and netvuln/license.py):

  1. NV_LICENSE_KEY environment variable (never shadowed by a config file; env-first)
  2. NV_API_KEY config value, then environment variable
  3. CONSULTATION_API_KEY config value, then environment variable (legacy alias)
  4. ~/.netvuln/license.key file

For NV_API_KEY and CONSULTATION_API_KEY, a config-file value wins over the environment: sourcing the config overwrites those variables in bash, and the Python resolver matches that behavior.

The resolved key is parsed against nvt_{pro|biz|msp}_{64 hex} and the result is exported as NV_LICENSED (true/false), NV_LICENSE_TIER (professional/business/msp/legacy, empty for community), and NV_LICENSE_SOURCE (which channel the key came from). The plaintext key is never exported or logged.

Tier to Feature Map ​

FeatureMinimum tierGate
Core scanning, reporting, uploadnone (community)never gated
Compliance mappingbusinesscompliance_mapper.sh
Benchmark comparisonbusinessbenchmark_compare.sh
Remediation updatebusinessreserved
Webhooksbusinessreserved
C2 / agent commandsbusinesslib/command_handler.sh
Unknown / future featuresmspfail conservative

Gates are soft: a feature below your tier prints a warning and is skipped; scans never fail because of licensing. The portal still enforces tiers server-side with HTTP 403.

Legacy and Malformed Keys ​

  • A non-empty key that is not nvt_-prefixed is treated as a legacy key: tier legacy, all features enabled, one-time deprecation warning.
  • An nvt_-prefixed key that fails the format check (for example, a truncated paste) warns loudly and also resolves to tier legacy. The client never locks itself out; the portal remains authoritative.

Online Validation Cache and Grace Window ​

When a portal URL and key are configured, the scanner validates the key against GET /api/license once per pipeline run and once per daemon heartbeat cycle (lib/license_validate.sh, netvuln/license.py).

  • Cache file: ~/.netvuln/license_cache.json, mode 600. It stores a SHA-256 fingerprint prefix of the key, never the plaintext.
  • TTL: 24 hours (NV_LICENSE_TTL_SECONDS, default 86400). A fresh cache skips the network entirely.
  • Grace: a further 72 hours (NV_LICENSE_GRACE_SECONDS, default 259200). If the portal is unreachable, the cached tier keeps working through the grace window, then the scanner downgrades to community features.
  • A revoked, expired, or inactive response downgrades immediately with a warning naming the reason. The cached negative result stays authoritative even when the portal later becomes unreachable; only a fresh valid:true answer restores the tier.
  • First run offline (no cache yet for the key) keeps the tier derived from the key format.
  • Hosts without jq skip online validation and rely on the format gate.

/api/license Troubleshooting ​

SymptomCauseResolution
License invalid (reason: revoked)Key was revokedRequest a new key from your administrator
License invalid (reason: expired)Past expires_atRequest a new key or an extension
License invalid (reason: inactive)Key deactivatedAsk your administrator to reactivate
License invalid (reason: not_found)Key unknown to the portalVerify the key was copied in full
using cached license (offline)Portal unreachable, within graceRestore connectivity before the grace window ends
beyond the grace windowOffline for more than TTL + graceReconnect; the next successful check restores the tier
HTTP 401 from /api/licenseMissing x-api-key headerCheck the config plumbing; the key did not reach the request

Key Lifecycle ​

StateDescriptionCan Upload?
ActiveNormal operating stateYes
InactiveAdministratively disabled (is_active: false)No
ExpiredPast expires_at dateNo
RevokedPermanently revoked by adminNo
Limit reachedSession or agent count at maximumNo

Revoking a Key ​

Revocation is permanent. Once revoked, the key cannot be reactivated, and a new key must be created.

bash
curl -s -X PATCH "https://portal.netvulntool.com/api/licenses?id=lic_abc123" \
  -H "X-Identity-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"action": "revoke"}'

Deleting a Key ​

Keys must be revoked before deletion (two-step safety):

bash
curl -s -X DELETE "https://portal.netvulntool.com/api/licenses?id=lic_abc123" \
  -H "X-Identity-Token: $TOKEN"

Troubleshooting ​

Error MessageCauseResolution
Invalid API keyKey not found in database or hash mismatchVerify the key was copied correctly; check for trailing whitespace
License key is inactiveKey was deactivated by adminContact your administrator to reactivate
License key has been revokedKey was permanently revokedRequest a new key from your administrator
License key has expiredKey's expires_at date has passedRequest a new key or ask admin to extend
Session limit reached (N)Client has N sessions, at the key's maximumArchive old sessions or request a limit increase
Agent limit reached (N)Client has N registered agents, at maximumDecommission unused agents or request a limit increase
Insufficient permissions for XKey's tier does not include the requested featureUpgrade to a higher tier

E2E Testing ​

A license lifecycle test suite lives at tests/e2e/test_license_lifecycle.sh. It validates all key states and tier boundaries against a live portal deployment, configured via tests/e2e/e2e.conf (copy the tracked tests/e2e/e2e.conf.example template):

bash
cp tests/e2e/e2e.conf.example tests/e2e/e2e.conf
# Edit e2e.conf with your dev portal admin credentials
./tests/e2e/test_license_lifecycle.sh -c tests/e2e/e2e.conf
  • API Contract: the GET /api/license response schema, tier permission matrix, and license validation deployment order.
  • Secrets Management: where license keys live, rotation procedures, and the two-key rule.

Apache-2.0 licensed (appliance subtree proprietary)