IssuerDirectory
IssuerDirectory is the Stella Ops trust registry for CSAF publishers and VEX statement issuers — it stores issuer metadata, signing keys, and per-tenant trust weights that Concelier/Excititor and VexLens consult when verifying signed advisory and VEX content.
Status: Implemented (source relocated by Sprint 216) Source: src/Authority/StellaOps.IssuerDirectory/ (previously src/IssuerDirectory/) Owner: Authority domain (Identity & Trust)
Latest updates (2026-04-16)
- IssuerDirectory web runtime no longer silently falls back to in-memory persistence outside
Testing; non-testing hosts now require PostgreSQL wiring (IssuerDirectory:Persistence:Provider=Postgres). Startup throwsIssuerDirectory requires Persistence:Provider=Postgres outside Testing.otherwise. - Canonical configuration now lives under
IssuerDirectory:Persistence:*, while legacyIssuerDirectory:Postgres:*settings remain supported for compatibility (LegacyPostgresOptions; the effective value is resolved byGetEffectivePostgres*helpers, with the canonical keys winning). - Anonymous mode (
IssuerDirectory:Authority:Enabled=false) is a local/test-only path.IssuerDirectoryAnonymousModeGuard.Enforceblocks anonymous startup outsideTesting. - Focused runtime coverage lives in
StellaOps.IssuerDirectory.WebService.Tests.
Purpose
IssuerDirectory maintains a trust registry of CSAF publishers and VEX statement issuers. It stores issuer metadata, issuer signing keys (with rotation/revocation lifecycle), and per-tenant trust overrides (weights) for upstream vulnerability advisories and VEX statements. Consumers (notably Concelier/Excititor and VexLens) read issuer keys and trust weights when verifying signed advisory/VEX content.
Domain ownership
As of Sprint 216, IssuerDirectory source is owned by the Authority domain. The runtime service identity, container, and database schema remain independent. Schema isolation from AuthorityDbContext is a deliberate security feature (blast-radius containment between issuer-metadata handling and authentication internals).
See ../authority/architecture.md(sections 21.1–21.4) for schema ownership and the no-merge rationale.
Components
The module is split across the Authority tree:
Service host (src/Authority/StellaOps.IssuerDirectory/):
StellaOps.IssuerDirectory.WebService- Minimal-API host (Program.cs), endpoint mapping, options, security policies, tenant/actor resolution, Router integration, health endpoints (/healthz,/readyz), OpenAPI.StellaOps.IssuerDirectory.Core- Domain model (IssuerRecord,IssuerKeyRecord,IssuerTrustOverrideRecord), services (IssuerDirectoryService,IssuerKeyService,IssuerTrustService), key validation, and metrics (IssuerDirectoryMetrics).StellaOps.IssuerDirectory.Infrastructure- In-memory repositories (Testing only) and the CSAF publisher seed loader.
Libraries (src/Authority/__Libraries/):
StellaOps.IssuerDirectory.Persistence- PostgreSQL repositories,IssuerDirectoryDbContext, the startup migration (Migrations/001_initial_schema.sql), and DI registration (AddIssuerDirectoryPersistence).StellaOps.IssuerDirectory.Client- Typed HTTP client (IIssuerDirectoryClient/IssuerDirectoryClient) with caching, consumed by other services for issuer keys and trust lookups.
HTTP API
All routes are under the /issuer-directory/issuers group and require an Authority token carrying the policy scope below (X-StellaOps-TenantId header selects the tenant; an optional X-StellaOps-Reason header is recorded in the audit trail). Most read routes accept ?includeGlobal=true (default true on issuer reads) to fold in global/system-seed issuers.
| Method & path | Operation | Required policy / scope |
|---|---|---|
GET /issuer-directory/issuers | List issuers | Reader / issuer-directory:read |
GET /issuer-directory/issuers/{id} | Get issuer | Reader / issuer-directory:read |
POST /issuer-directory/issuers | Create issuer | Writer / issuer-directory:write |
PUT /issuer-directory/issuers/{id} | Update issuer | Writer / issuer-directory:write |
DELETE /issuer-directory/issuers/{id} | Delete issuer | Admin / issuer-directory:admin |
GET /issuer-directory/issuers/{issuerId}/keys | List issuer keys | Reader / issuer-directory:read |
POST /issuer-directory/issuers/{issuerId}/keys | Add key | Writer / issuer-directory:write |
POST /issuer-directory/issuers/{issuerId}/keys/{keyId}/rotate | Rotate key | Writer / issuer-directory:write |
DELETE /issuer-directory/issuers/{issuerId}/keys/{keyId} | Revoke key | Admin / issuer-directory:admin |
GET /issuer-directory/issuers/{issuerId}/trust | Get effective trust | Reader / issuer-directory:read |
PUT /issuer-directory/issuers/{issuerId}/trust | Set tenant trust override | Writer / issuer-directory:write |
DELETE /issuer-directory/issuers/{issuerId}/trust | Delete trust override | Admin / issuer-directory:admin |
Health/diagnostics: GET /healthz (liveness), GET /readyz (readiness), and a build-info endpoint are mapped anonymously. OpenAPI is exposed via MapOpenApi.
Authorization
Endpoints bind to three named policies (IssuerDirectoryPolicies): IssuerDirectory.Reader, IssuerDirectory.Writer, IssuerDirectory.Admin. When IssuerDirectory:Authority:Enabled=true (default), each policy is registered via AddStellaOpsScopePolicy against the configured ReadScope / WriteScope / AdminScope (defaults issuer-directory:read, issuer-directory:write, issuer-directory:admin — see StellaOpsScopes.IssuerDirectoryRead/Write/Admin). The framework handler honours configured BypassNetworks (e.g. ISSUERDIRECTORY__AUTHORITY__BYPASSNETWORKS__0). When Authority is disabled (local/test), all three policies allow anonymous access.
Key types
Issuer key requests carry a type string parsed into the IssuerKeyType enum:
Ed25519PublicKeyX509CertificateDssePublicKey
(The startup-migration SQL key_type CHECK constraint lists a broader value set — ed25519, x509, dsse, kms, hsm, fido2 — but only the three enum members above are accepted by the WebService today.) Trust override weights are constrained to the range [-10, 10].
Configuration
See etc/issuer-directory.yaml.sample for the full sample. The binding section is IssuerDirectory and the environment prefix is ISSUERDIRECTORY_ (double-underscore for nesting, e.g. ISSUERDIRECTORY__PERSISTENCE__POSTGRESCONNECTIONSTRING).
Key settings:
IssuerDirectory:Persistence:Provider(Postgresdefault;InMemoryonly honoured in theTestingenvironment)IssuerDirectory:Persistence:PostgresConnectionStringIssuerDirectory:Persistence:SchemaName(defaults toissuerviaIssuerDirectoryDataSource.DefaultSchemaName)IssuerDirectory:Postgres:*— legacy compatibility keys (ConnectionString,Schema,CommandTimeoutSeconds)IssuerDirectory:Authority:Enabled/Issuer/Audiences/RequireHttpsMetadataIssuerDirectory:Authority:ReadScope/WriteScope/AdminScope/BypassNetworksIssuerDirectory:TenantHeader(defaults toX-StellaOps-TenantId)IssuerDirectory:SeedCsafPublishers(defaults totrue) andIssuerDirectory:CsafSeedPath(defaults tocsaf-publishers.json) — bootstraps system-seed CSAF publishers on startup viaCsafPublisherSeedLoaderIssuerDirectory:Telemetry:MinimumLogLevel
Persistence
PostgreSQL schema (default issuer), auto-migrated on startup via AddStartupMigrations (module IssuerDirectory.Persistence, embedded Migrations/001_initial_schema.sql). Tables:
issuers- issuer metadata (tenant-scoped or global), endpoints/contact/metadata JSONB, tags, status,is_system_seed;UNIQUE (tenant_id, name)issuer_keys- signing keys with lifecycle (active/retired/revoked), fingerprint dedupe, rotation chain (replaces_key_id)trust_overrides- per-consumer-tenant weight overrides (NUMERIC(5,2), range[-10,10]);UNIQUE (issuer_id, tenant_id)audit- issuer-domain audit log (actions:create_issuer,update_issuer,delete_issuer,add_key,rotate_key,revoke_key,set_trust,delete_trust,seed_csaf)schema_migrations- migration tracking
In-memory repositories exist for the Testing environment only. Unified audit events are also emitted to Timeline via AddAuditEmission.
Observability
Meter StellaOps.IssuerDirectory exposes:
issuer_directory_changes_total- issuer create/update/delete eventsissuer_directory_key_operations_total- key create/rotate/revoke operationsissuer_directory_key_validation_failures_total- key validation/verification failures
Dependencies
- PostgreSQL (default schema:
issuer; configurable viaIssuerDirectory:Persistence:SchemaName) - Authority (token authentication; resource-server validation)
- Stella Router (service registration / messaging transport)
- Timeline (unified audit emission)
Consumers:
- Concelier / Excititor - verify signed VEX/advisory content using issuer keys (
StellaOps.IssuerDirectory.Clientis referenced byStellaOps.Excititor.WebServiceandStellaOps.Excititor.Worker). - VexLens - trust-scoring/signature verification via its own
IIssuerDirectoryabstraction andAuthorityIssuerDirectoryAdapterover the directory client.
Related Documentation
- API reference — endpoint and DTO detail.
- Architecture:
../authority/architecture.md(sections 21.1–21.4). - Archived original:
docs-archive/modules/issuer-directory/. - Concelier — consumes issuer keys to verify signed advisories/VEX.
- VexLens — trust scoring and signature verification.
