Concelier connector credentials — operator acquisition guide

Audience: operators wiring Concelier to vendor-gated advisory feeds (cisco, microsoft, cve) and optional GHSA rate-limit tokens. Companion docs:

  • credentials-matrix.md— full per-source mechanism inventory (PAT / OAuth / API key / public).
  • RFC-0001-connector-credentials-storage.md — Console UI + Platform storage design that this guide complements.
  • SPRINT_20260505_037 — current skip-with-warning behavior when credentials are absent.
  • SPRINT_20260505_026 — operator-decision workflow.

UI path (Console)

Operators can now provision connector credentials from the Console at /admin/connectors/credentials(redirected internally to /console-admin/connector-credentials). The page is gated by two Authority permissions added in SPRINT_20260505_045:

PermissionGrants
connector:credentials:readList + detail view; never returns plaintext or authref:// URIs (RFC-0001 §6.4).
connector:credentials:writeCreate, rotate, revoke. Platform-admin role inherits both by default.

Behaviour notes:

REST surface (mounted at /api/v1/connector-credentials inside the Platform service, exposed to Console as /platform/api/v1/connector-credentials):

VerbPathRequired scopePurpose
GET/connector:credentials:readList credentials (metadata only).
GET/{id}connector:credentials:readDetail view (metadata only).
POST/connector:credentials:writeCreate. Send-only.
POST/{id}/rotateconnector:credentials:writeRotate with optional grace override.
DELETE/{id}connector:credentials:writeRevoke immediately (row retained for audit).
POST/{id}/verifyconnector:credentials:readVerify button (SPRINT_20260512_021 / RFC §6.2 step 4). Confirms the credential decrypts and is in a resolvable state. Returns { ok, reason, state, checkedAt } — never plaintext.
POST/resolveconnector:credentials:readConnector runtime resolution. Returns the decrypted plaintext base64-encoded for the calling tenant + named connector. Concelier / Excititor connectors call this internally through IRemoteCredentialResolver; operators do not invoke it directly. Emits the minute-batched audit row per RFC §7.

Verify button workflow

The Console list view exposes a Verify button on every row that is in active or rotating state. The button now picks between two probes:

  1. Vendor dry-run (SPRINT_20260512_032, default when supported) — Concelier runs the connector’s real auth path against the upstream vendor.
  2. Platform metadata (RFC-0001 §6.2 step 4, fallback) — Platform decrypts the row and runs the state-machine check. No vendor I/O.

Vendor dry-run (active rows on supported connectors)

POST /concelier/api/v1/connectors/{connectorId}/dry-run-credential
Authorization: Bearer <token with concelier:credentials:dry-run>
X-StellaOps-TenantId: <tenant>
Content-Type: application/json

{ "credentialId": "<guid>" }

Response shape:

{
  "ok": true,
  "connectorId": "concelier:ghsa",
  "credentialId": "fbe1d6c4-...",
  "vendorReachable": true,
  "authAccepted": true,
  "latencyMs": 842,
  "reason": null,
  "evidence": "GitHub GraphQL viewer.login=octocat."
}

Binding contract (enforced by the endpoint, see docs/architecture/proposal-concelier-credential-dryrun.md):

ConnectorProbe shape (Sprint 032)
concelier:ghsaGraphQL query { viewer { login } } against /graphql (auth-only).
concelier:ciscoOAuth2 client_credentials grant against the configured TokenEndpoint.
concelier:msrcAzure AD client_credentials grant against https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token (same path MsrcTokenProvider uses on every cron tick). 400/401 bodies surface AADSTSxxxxx error codes verbatim in reason.
concelier:cveAuthenticated GET /cve?resultsPerPage=1 against the configured BaseEndpoint with the CVE-API-ORG / CVE-API-USER / CVE-API-KEY triplet (same headers used by the production fetch).
concelier:redhatAuthenticated GET https://api.access.redhat.com/management/v1/subscriptions/?limit=1 with Authorization: Bearer <PAT>. The RHSM subscription endpoint is genuinely PAT-gated, unlike the anonymous-accessible Hydra security-data path.
excititor:redhat-csafAuthenticated GET <MetadataUri> against the configured CSAF provider-metadata document (default: https://security.access.redhat.com/data/csaf/v2/provider-metadata.json) with Authorization: Bearer <PAT>. The metadata endpoint is anonymous-accessible today; the probe still exercises the CSAF directory host and rejects malformed PATs (401).

Platform metadata verify (fallback for rotating)

POST /platform/api/v1/connector-credentials/{id}/verify
Authorization: Bearer <token with connector:credentials:read>

Returns { ok, reason, state, checkedAt }. This was the only path prior to Sprint 032; it stays as the fallback for rows the dry-run endpoint declines (rotating / expired) and for connectors that have not yet shipped an IConnectorDryRun. Plaintext never leaves the Platform process.

Why the split

The pre-Sprint-032 button did not call the upstream vendor — it only proved the row decrypted. Operators have asked for a true probe (“given this stored credential, does the connector authenticate against the upstream vendor today?”) which is what the new endpoint delivers. The Console picks automatically, so operators do not have to know which path is in play; the result banner spells out whether the probe was a vendor call or metadata-only.

Runtime resolution chain (RFC §8.2)

Connectors resolve their credential in this order on every overlay run:

  1. Platform DBIRemoteCredentialResolver returns the decrypted value for (connectorId, currentTenant). 30 s in-process cache with eager Valkey connector-credentials-changed invalidation (SPRINT_20260512_021).
  2. Env var / DB source-settings — the existing AdvisorySourceRuntimeOverlay<T> dictionary path (CONCELIER_concelier__sources__ghsa__ApiToken, etc.).
  3. Skip-with-WARN — Sprint 037 contract. GHSA is the exception: anonymous mode is a legitimate operating point.

Un-tenanted ticks (no IStellaOpsTenantAccessor.TenantId) skip step 1 and fall straight through to env var per RFC §4.

Behavior with no credentials: credential-gated connectors below log a single WARN on the first cron tick after startup and then no-ops on every subsequent tick. vuln.source_states.error_count does not climb. You can leave them un-wired indefinitely without log noise or false-positive health-check failures. GHSA is different: public global advisories run anonymously and ApiToken is optional.

Verification command (after wiring credentials, recreate concelier and run):

bash devops/compose/scripts/stella-connectors.sh pipeline <source>

A wired credential produces non-zero pre/post row deltas in the output. An un-wired credential keeps the existing skip-with-WARN log.


How env vars map to config

Concelier services bind through AddStellaOpsDefaults with EnvironmentPrefix = "CONCELIER_". A config key concelier:sources:cisco:ClientId resolves from the environment variable CONCELIER_concelier__sources__cisco__ClientId (env prefix CONCELIER_, section path with : replaced by __). Operators set these in devops/compose/env/stellaops.env (or .env) and recreate the concelier container.

Example for all four below:

# devops/compose/env/stellaops.env (or .env)

# --- GHSA ---
CONCELIER_concelier__sources__ghsa__ApiToken=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# --- Microsoft (MSRC) ---
CONCELIER_concelier__sources__microsoft__TenantId=00000000-0000-0000-0000-000000000000
CONCELIER_concelier__sources__microsoft__ClientId=00000000-0000-0000-0000-000000000000
CONCELIER_concelier__sources__microsoft__ClientSecret=...

# --- Cisco ---
CONCELIER_concelier__sources__cisco__ClientId=...
CONCELIER_concelier__sources__cisco__ClientSecret=...

# --- CVE.org (MITRE CVE Services) ---
CONCELIER_concelier__sources__cve__ApiOrg=YourOrgShortName
CONCELIER_concelier__sources__cve__ApiUser=your.user@example.com
CONCELIER_concelier__sources__cve__ApiKey=...

Apply with:

docker compose -f devops/compose/docker-compose.stella-ops.yml up -d --no-deps --force-recreate concelier

1. GHSA (GitHub Security Advisories)

FieldValue
Source keyghsa
Connector pathsrc/Concelier/__Libraries/StellaOps.Concelier.Connector.Ghsa/
Auth mechanismOptional GitHub Personal Access Token (PAT) or GitHub App token
CostFree
Rate limitAnonymous REST quota for public global advisories; authenticated REST quota when ApiToken is configured; connector warns when remaining drops below 500

1.1 Where to register

  1. Sign in to https://github.com.
  2. Open Settings → Developer settings → Personal access tokensTokens (classic) or Fine-grained tokens.
  3. New token with the scopes below.

1.2 Required scopes / permissions

1.3 Optional env vars to set

CONCELIER_concelier__sources__ghsa__ApiToken=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Other optional settings (defaults usually sufficient):

CONCELIER_concelier__sources__ghsa__BaseEndpoint=https://api.github.com/
CONCELIER_concelier__sources__ghsa__PageSize=10
CONCELIER_concelier__sources__ghsa__MaxPagesPerFetch=1

1.4 Verification

bash devops/compose/scripts/stella-connectors.sh pipeline ghsa

Expected on success: pre=0 (or prior count), post>pre after fetch -> parse -> map. Expected without creds: the connector still runs against public global advisories; failures should be treated as network, rate-limit, endpoint, or parser issues rather than a missing-token skip.


2. Microsoft Security Update Guide (MSRC)

FieldValue
Source keymicrosoft
Connector pathsrc/Concelier/__Libraries/StellaOps.Concelier.Connector.Vndr.Msrc/
Auth mechanismOAuth2 client_credentials against Azure AD (Entra ID)
CostFree (requires an Azure AD tenant; the API itself has no charge)
Rate limitConnector enforces RequestDelay=250ms, PageSize<=500, FailureBackoff=5min

2.1 Where to register

  1. Sign in to https://portal.azure.com with an account that can register apps in your directory.
  2. Open Microsoft Entra ID → App registrations → New registration.
  3. Give the app a name (e.g. stellaops-concelier-msrc). Account types: Accounts in this organizational directory only is sufficient; no redirect URI is needed (client_credentials flow).
  4. After creation, open the app:
    • Certificates & secretsNew client secret → copy the Value (this is the ClientSecret; it is only shown once).
    • API permissionsAdd a permissionAPIs my organization uses → search Microsoft Security Update Guide → select the Application permission for the SUG API → Grant admin consent.
    • Overview → copy Application (client) ID (the ClientId) and Directory (tenant) ID (the TenantId).
  5. MSRC SUG API onboarding reference: https://api.msrc.microsoft.com/sug/v2.0/swagger and Microsoft’s “Security Update Guide REST API” docs.

2.2 Required scopes / permissions

2.3 Env vars to set

CONCELIER_concelier__sources__microsoft__TenantId=00000000-0000-0000-0000-000000000000
CONCELIER_concelier__sources__microsoft__ClientId=00000000-0000-0000-0000-000000000000
CONCELIER_concelier__sources__microsoft__ClientSecret=...

Optional (default usually sufficient):

CONCELIER_concelier__sources__microsoft__Scope=api://api.msrc.microsoft.com/.default

2.4 Verification

bash devops/compose/scripts/stella-connectors.sh pipeline microsoft

Expected without creds: MSRC fetch skipped: missing TenantId/ClientId/ClientSecret.


3. Cisco PSIRT (OpenVuln API)

FieldValue
Source keycisco
Connector pathsrc/Concelier/__Libraries/StellaOps.Concelier.Connector.Vndr.Cisco/
Auth mechanismOAuth2 client_credentials against https://id.cisco.com/oauth2/default/v1/token
CostFree (requires Cisco.com account + entitlement approval — typically same-day for organizations with active Cisco contracts)
Rate limitConnector enforces PageSize<=100, RequestDelay=250ms, RequestTimeout=30s, retry attempts=5

3.1 Where to register

  1. Create or sign in with a Cisco.com account at https://id.cisco.com/.
  2. Open the Cisco API Console at https://apiconsole.cisco.com/.
  3. My Apps & Keys → Register a New App:
    • Name: stellaops-concelier-cisco
    • OAuth2.0 Credentials Grant Type: Client Credentials
    • Select APIs: tick OpenVuln API
  4. Submit. Approval is required from Cisco before the entitlement becomes active. After approval the app page exposes the Client ID and Client Secret.

3.2 Required scopes / permissions

3.3 Env vars to set

CONCELIER_concelier__sources__cisco__ClientId=...
CONCELIER_concelier__sources__cisco__ClientSecret=...

Optional (defaults from CiscoOptions):

CONCELIER_concelier__sources__cisco__TokenEndpoint=https://id.cisco.com/oauth2/default/v1/token
CONCELIER_concelier__sources__cisco__BaseUri=https://apix.cisco.com/advisories/v2/

3.4 Verification

bash devops/compose/scripts/stella-connectors.sh pipeline cisco

Expected without creds: Cisco fetch skipped: missing ClientId/ClientSecret.


4. CVE.org (MITRE CVE Services)

FieldValue
Source keycve
Connector pathsrc/Concelier/__Libraries/StellaOps.Concelier.Connector.Cve/
Auth mechanismNo credential for public cvelistV5; optional API key for CVE Services; offline SeedDirectory fallback
CostFree (CVE Program is publicly funded; key issuance is gated to organizations participating in CVE Numbering Authorities (CNAs) or approved CVE consumers)
Rate limitSelf-throttled by RequestDelay=250ms, PageSize<=500, FailureBackoff=10min

4.1 Public no-credential default

The default Stella Ops Mirror path reads the official public CVE List v5 raw JSON repository at https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/. The fetch phase reads deltaLog.json, then downloads changed CVE-*.json records. No CVE Services credentials are required for this mode.

4.2 Where to register for optional CVE Services API use

  1. Read the CVE Services overview at https://www.cve.org/AllResources/CveServices.
  2. Apply for an account with the CVE Program through your CNA or via the contact form at https://www.cve.org/ResourcesSupport/Contact.
  3. Once approved, your CNA admin issues:
    • ApiOrg — your organisation short name
    • ApiUser — your account email/handle
    • ApiKey — long opaque string (rotate via the CVE Services UI)
  4. Test endpoint: https://cveawg.mitre.org/api/cve/CVE-2024-0001 should return JSON when called with the CVE-API-ORG, CVE-API-USER, CVE-API-KEY headers.

4.2.1 Offline fallback (no API key needed)

For air-gap deployments or operators who consume the CVE List via git clone https://github.com/CVEProject/cvelistV5, set SeedDirectory to the local path containing the year-partitioned JSON. The connector then ingests from disk without any HTTP call. This is the recommended path for offline/air-gap operators.

4.3 Required scopes / permissions

CVE Services uses the org/user/key triple as both authentication and authorization; no separate scope string. CNA write permissions are not required for read ingestion.

4.4 Env vars to set

For authenticated CVE Services operation:

CONCELIER_concelier__sources__cve__ApiOrg=YourOrgShortName
CONCELIER_concelier__sources__cve__ApiUser=your.user@example.com
CONCELIER_concelier__sources__cve__ApiKey=...

For offline operation (pick this path for air-gap):

CONCELIER_concelier__sources__cve__SeedDirectory=/var/lib/concelier/cvelistV5

Optional (defaults from CveOptions):

CONCELIER_concelier__sources__cve__CveListV5Endpoint=https://raw.githubusercontent.com/CVEProject/cvelistV5/main/cves/
CONCELIER_concelier__sources__cve__BaseEndpoint=https://cveawg.mitre.org/api/
CONCELIER_concelier__sources__cve__PageSize=500
CONCELIER_concelier__sources__cve__InitialBackfill=30.00:00:00

4.5 Verification

bash devops/compose/scripts/stella-connectors.sh pipeline cve

Expected without creds: the connector attempts public cvelistV5 first. It only falls back to SeedDirectory when the public delta source is unavailable or yields no records.


Operator follow-ups (out of scope here)

Audit notes

This document was authored from local sources only:

No external URLs were fetched during authoring (offline-first posture). The vendor URLs above are documented in connector source comments and / or credentials-matrix.md; operators should verify the URLs are current at credential-acquisition time.