Playbook: NEXCON_API_KEY Rotation
Service: Nexcon — SIM card provider for GPS trackers
Secret: NEXCON_API_KEY
Rotation frequency: 90 days (or immediately on suspected exposure)
Last rotated: 2026-06-27
Next due: 2026-09-27
What uses this credential
| Consumer | How | Effect if missing |
|---|---|---|
nexcon-exporter container (vps-i1) | REST API calls to Nexcon portal — SIM status, data usage metrics | GPS tracker SIM metrics missing from Prometheus / Grafana |
scripts/nexcon-*.py (if any) | Direct API calls | Script failures |
| Alertmanager rules for SIM health | Downstream — alert suppressed if exporter is down | No alerts on SIM disconnections |
Where stored
| Location | Key name | How to update |
|---|---|---|
secrets/monitoring.env.sops | NEXCON_API_KEY | SOPS write pattern (see below) |
.env.local on dev workstation | NEXCON_API_KEY | Edit line directly (emergency fallback only) |
| vps-i1 deployed env | /opt/p24-infra/monitoring/.env | Auto-synced by secrets-sync.yml on merge to dev/main |
Nexcon portal URL
The Nexcon customer portal base URL is stored in .env.local on the dev workstation (key: nexcon_api_url). Check that file for the current portal address before attempting browser navigation. Do not display the URL value — confirm it exists with:
(Get-Content "d:\code_2026\p24-infra\.env.local" | Select-String "^nexcon_api_url=") -ne $null
# Returns: True/FalseAutomation status
Playwright-automatable — Claude can rotate this via the Nexcon customer portal. The portal provides an API management section where keys can be regenerated. Spawn a Playwright agent with the prompt below.
Note: If the Nexcon portal UI has changed or the API key regeneration flow is unclear, fall back to Option B (manual) or contact Nexcon support.
Rotation steps
Option A — Playwright agent (preferred)
Spawn a background agent with this prompt:
You are rotating NEXCON_API_KEY for the Nexcon SIM card provider portal.
IMPORTANT: Never display any secret value in your response. Reference key names only.
1. Read the Nexcon portal URL from .env.local silently:
$env:NEXCON_URL = (Get-Content "d:\code_2026\p24-infra\.env.local" |
Select-String "^nexcon_api_url=").ToString().Split('=',2)[1].Trim()
(Never print $env:NEXCON_URL)
2. Read any existing Nexcon login credentials from .env.local (keys: nexcon_username, nexcon_password)
Store silently in $env:NEXCON_USER and $env:NEXCON_PASS — never print values.
3. Use Playwright to navigate to $env:NEXCON_URL
- Log in with the credentials from step 2
- Navigate to the API management / Developer / API Keys section
- Find the existing API key — note the key name shown (do not log the value)
- Click "Regenerate" or "Delete + Create New" as the UI allows
- Copy the new key value into $env:NEW_KEY (never print it)
4. Update secrets/monitoring.env.sops:
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
Follow the SOPS write pattern: decrypt → update NEXCON_API_KEY line → re-encrypt → canary.
Temp file: secrets/monitoring-edit.env.sops
Use [System.IO.File]::WriteAllText with UTF8Encoding($false).
5. Verify the new key works (replace <NEXCON_API_PATH> with the known API endpoint):
$headers = @{ "Authorization" = "Bearer $env:NEW_KEY" }
$r = Invoke-RestMethod "$env:NEXCON_URL/api/v1/sims" -Headers $headers
Write-Host "SIMs accessible: $($r.Count)"
$env:NEW_KEY = ""; $env:NEXCON_URL = ""; $env:NEXCON_USER = ""; $env:NEXCON_PASS = ""
6. Commit and open PR targeting main:
git add secrets/monitoring.env.sops
git commit -m "chore: rotate NEXCON_API_KEY (scheduled 90d)"
Push and open PR via gh pr create.
7. After merge: restart nexcon-exporter on vps-i1:
ssh root@217.154.82.162 "cd /opt/p24-infra/monitoring && docker compose restart nexcon-exporter"
8. Append to docs/secrets-rotation-log.md:
| <YYYY-MM-DD> | NEXCON_API_KEY | scheduled 90d rotation | AI-agent | SOPS (monitoring) |
Option B — Manual (fallback)
Step 1 — Locate the Nexcon portal URL
URL: stored in .env.local key nexcon_api_url (never display value)
Read it: (Get-Content "d:\code_2026\p24-infra\.env.local" | Select-String "^nexcon_api_url=")
Navigate to that URL in your browser.
Step 2 — Log into Nexcon portal
Use credentials from .env.local (nexcon_username, nexcon_password)
Step 3 — Regenerate API key
→ Navigate to API / Developer / API Management section
→ Click "Regenerate API Key" or delete existing + create new
→ Copy the new key value immediately
Step 4 — Update secrets/monitoring.env.sops
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$plain = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops
$tempPath = "$PWD\secrets\monitoring-edit.env.sops"
$env:NEW_KEY = "<paste-from-step-3>"
$updated = $plain -replace "^NEXCON_API_KEY=.*", "NEXCON_API_KEY=$env:NEW_KEY"
[System.IO.File]::WriteAllText($tempPath, ($updated -join "`n") + "`n",
[System.Text.UTF8Encoding]::new($false))
$enc = sops --encrypt --input-type dotenv --output-type dotenv $tempPath
[System.IO.File]::WriteAllText("$PWD\secrets\monitoring.env.sops",
($enc -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false))
# Canary
sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops | Out-Null
if ($LASTEXITCODE -ne 0) { throw "SOPS corrupt — do NOT commit" }
[System.IO.File]::Delete($tempPath)
$env:NEW_KEY = ""
Step 5 — Commit and push
git add secrets/monitoring.env.sops
git commit -m "chore: rotate NEXCON_API_KEY (scheduled 90d)"
git push
gh pr create --base main --title "chore: rotate NEXCON_API_KEY"
Step 6 — After merge: restart nexcon-exporter on vps-i1
ssh root@217.154.82.162 "cd /opt/p24-infra/monitoring && docker compose restart nexcon-exporter"
Step 7 — Append to docs/secrets-rotation-log.md
| <YYYY-MM-DD> | NEXCON_API_KEY | scheduled 90d rotation | radieu | SOPS (monitoring) |
Verification
# 1. Confirm SOPS updated (key present, expected length 32)
$env:SOPS_AGE_KEY_FILE = "C:\Users\konar\.age\p24-infra-keys.txt"
$line = sops --decrypt --input-type dotenv --output-type dotenv secrets\monitoring.env.sops |
Select-String "^NEXCON_API_KEY="
$val = $line.ToString().Split('=', 2)[1]
Write-Host "NEXCON_API_KEY length: $($val.Length)" # expected: 32
# 2. Confirm nexcon-exporter is running and returning metrics on vps-i1
ssh root@217.154.82.162 "curl -s http://localhost:9XXX/metrics | head -5"
# Replace 9XXX with the nexcon-exporter port — check monitoring/docker-compose.yml
# 3. Check Prometheus scrape health for nexcon-exporter target
# https://prometheus.vps-i1.infra.zintegrowana.online/targets
# Status should be UPEscalation
| Symptom | Action |
|---|---|
| Nexcon portal UI has changed | Try navigating to /api, /developer, /settings/api paths; contact Nexcon support if not found |
| Login credentials rejected | Check nexcon_username / nexcon_password in .env.local; reset via Nexcon support |
| API key regeneration not available in UI | Contact Nexcon support to request key rotation — may need to open a support ticket |
| nexcon-exporter still returns 401 after rotation | Check secrets-sync.yml has run on vps-i1; force restart: docker compose restart nexcon-exporter |
| nexcon-exporter port/path unknown | Check monitoring/docker-compose.yml for the nexcon-exporter service definition |
Prevention
- The
credential-rotation.ymlGH Actions workflow (Monday 06:00 UTC) opens ahuman-actionissue whennext_duepasses. - Nexcon does not send expiry warnings — only the GH Actions check triggers rotation.
- After rotation, update this playbook’s Last rotated and Next due dates.
Audit Log — Log to infra_operations
After this operation completes, log it to the infra_operations audit table.
Python (Linux server — bms-4, vps-i1, vps-h1, or similar):
import sys
sys.path.insert(0, '/opt/p24-infra')
from scripts.lib.log_op import log_op
log_op(
actor="claude", # "radieu" for manual human ops, "claude" for agent
op_type="credential_rotation",
resource="NEXCON_API_KEY",
result="success", # "success" | "failed" | "skipped"
detail="Scheduled rotation — Nexcon API key regenerated and SOPS monitoring updated",
env="vps-i1",
gh_issue=2730,
)PowerShell (Windows dev machine):
$env:SUPABASE_URL = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_URL=").ToString().Split("=",2)[1].Trim()
$env:SUPABASE_SERVICE_KEY = (Get-Content "C:\code_2026\p24-infra\.env.local" | Select-String "^SUPABASE_SERVICE_KEY=").ToString().Split("=",2)[1].Trim()
python -c "
import os, sys
sys.path.insert(0, 'C:/code_2026/p24-infra')
from scripts.lib.log_op import log_op
log_op('claude', 'credential_rotation', 'NEXCON_API_KEY', 'success', 'Scheduled rotation — Nexcon API key regenerated and SOPS monitoring updated', 'vps-i1')
"
$env:SUPABASE_URL = ''; $env:SUPABASE_SERVICE_KEY = ''