consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -1,101 +1,19 @@
|
||||
# StellaOps Signer
|
||||
# StellaOps Signer (Relocated)
|
||||
|
||||
Signer validates callers, enforces Proof-of-Entitlement, and produces signed DSSE bundles for SBOMs, reports, and exports.
|
||||
> **Sprint 204 (2026-03-04):** The Signer module source has been consolidated under the Attestor trust domain.
|
||||
> Source code is now at `src/Attestor/StellaOps.Signer/`.
|
||||
> Architecture documentation is now in the [Attestor architecture dossier](../attestor/architecture.md#trust-domain-model-sprint-204----2026-03-04).
|
||||
> Archived standalone docs are in `docs-archived/modules/signer/`.
|
||||
|
||||
## Latest updates (Sprint 0186/0401 · 2025-11-26)
|
||||
- **CryptoDsseSigner** implemented with ICryptoProviderRegistry integration (SIGN-CORE-186-004), enabling keyless + KMS signing modes with cosign-compatible DSSE output.
|
||||
- **SignerStatementBuilder** refactored to support StellaOps predicate types (`stella.ops/promotion@v1`, `stella.ops/sbom@v1`, `stella.ops/vex@v1`, etc.) with CanonicalJson canonicalization (SIGN-CORE-186-005).
|
||||
- **PredicateTypes catalog** extended with `stella.ops/vexDecision@v1` and `stella.ops/graph@v1` for reachability evidence chain (SIGN-VEX-401-018).
|
||||
- **Helper methods** added: `IsVexRelatedType`, `IsReachabilityRelatedType`, `GetAllowedPredicateTypes`, `IsAllowedPredicateType` for predicate type validation.
|
||||
- **Integration tests** upgraded with real crypto abstraction, fixture predicates (promotion, SBOM, VEX, replay, policy, evidence, graph), and deterministic test data (SIGN-TEST-186-006). All 102 Signer tests passing.
|
||||
## Runtime Identity (unchanged)
|
||||
|
||||
## Previous updates (Sprint 11 · 2025-10-21)
|
||||
- `/sign/dsse` pipeline landed with Authority OpTok + PoE enforcement, Fulcio/KMS signing modes, and deterministic DSSE bundles ready for Attestor logging.
|
||||
- `/verify/referrers` endpoint exposes release-integrity checks against scanner OCI referrers so callers can confirm digests before requesting signatures.
|
||||
- Plan quota enforcement (QPS/concurrency/artifact size) and audit/metrics wiring now align with the Sprint 11 signing-chain release.
|
||||
- Docker image: `stellaops/signer:dev`
|
||||
- API base path: `/api/v1/signer/`
|
||||
- DSSE signing endpoint: `POST /api/v1/signer/sign/dsse`
|
||||
- Database schemas: `signer`, `key_management` (isolated from Attestor schema by design)
|
||||
|
||||
## Responsibilities
|
||||
- Enforce Proof-of-Entitlement and plan quotas before signing artifacts.
|
||||
- Support keyless (Fulcio) and keyful (KMS/HSM) signing backends.
|
||||
- Verify scanner release integrity via OCI referrers prior to issuing signatures.
|
||||
- Emit DSSE payloads consumed by Attestor/Export Center and maintain comprehensive audit trails.
|
||||
## Why the move
|
||||
|
||||
## Key components
|
||||
- `StellaOps.Signer` service host with `SignerPipeline` orchestrating the signing flow.
|
||||
- `CryptoDsseSigner` for ES256 signature generation via `ICryptoProviderRegistry`.
|
||||
- `SignerStatementBuilder` for in-toto statement creation with `PredicateTypes` catalog.
|
||||
- `DefaultSigningKeyResolver` for tenant-aware key resolution (keyless/KMS modes).
|
||||
- Crypto providers under `StellaOps.Cryptography.*`.
|
||||
Signer, Attestor, and Provenance form the trust domain -- the set of services responsible for cryptographic evidence production, transparency logging, and verification. Consolidating source ownership under `src/Attestor/` makes trust-boundary responsibilities explicit while preserving runtime isolation and database schema separation.
|
||||
|
||||
## Integrations & dependencies
|
||||
- Authority for OpTok + PoE validation.
|
||||
- Licensing Service for entitlement introspection.
|
||||
- OCI registries (Referrers API) for scanner release verification.
|
||||
- Attestor for transparency logging and Rekor ingestion.
|
||||
- Export Center and CLI for artifact signing flows.
|
||||
|
||||
## API quick reference
|
||||
- `POST /api/v1/signer/sign/dsse` — validate OpTok/PoE, enforce quotas, return DSSE bundle with signing identity metadata.
|
||||
- `GET /api/v1/signer/verify/referrers` — report scanner release signer and trust verdict for a supplied image digest.
|
||||
|
||||
## Operational notes
|
||||
- Key management via Authority/DevOps runbooks.
|
||||
- Metrics for signing latency/throttle states.
|
||||
- Offline kit integration for signature verification.
|
||||
|
||||
## Backlog references
|
||||
- Sprint 0186: `docs/implplan/SPRINT_0186_0001_0001_record_deterministic_execution.md` (SIGN-CORE-186-004, SIGN-CORE-186-005, SIGN-TEST-186-006 DONE; SIGN-REPLAY-186-003 blocked on upstream).
|
||||
- Sprint 0401: `docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md` (SIGN-VEX-401-018 DONE; AUTH-REACH-401-005 TODO).
|
||||
- SIG docs/tasks in ../../TASKS.md (e.g., DOCS-SIG-26-006).
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### Phase 1 – Core service & PoE (Complete)
|
||||
- OpTok validation with Authority DPoP/mTLS tokens and signer.sign scope
|
||||
- Proof-of-Entitlement (PoE) introspection with cloud licensing integration
|
||||
- Scanner release verification via OCI referrers
|
||||
- DSSE signing pipeline: keyless (Fulcio) and keyful (KMS/HSM/FIDO2)
|
||||
- KMS key management foundations (KMSI-73-001, KMSI-73-002)
|
||||
- DSSE/SLSA BuildDefinition models with canonical JSON (PROV-OBS-53-001/002)
|
||||
|
||||
### Phase 2 – Export Center integration (In Progress)
|
||||
- CryptoDsseSigner with ICryptoProviderRegistry (keyless + KMS modes)
|
||||
- SignerStatementBuilder refactored for StellaOps predicate types
|
||||
- PromotionAttestationBuilder with canonicalized payloads (PROV-OBS-53-003)
|
||||
- Cosign-compatible DSSE output with provenance manifests
|
||||
- Blocking: SIGN-CORE-186-004/005 crypto provider refactoring, replay manifest support
|
||||
|
||||
### Phase 3 – Attestor alignment (Not Started)
|
||||
- DSSE envelope metadata for Attestor ingestion
|
||||
- Extended predicate catalog: stella.ops/vexDecision@v1, stella.ops/graph@v1 (SIGN-VEX-401-018 complete)
|
||||
- Helper methods: IsVexRelatedType, IsReachabilityRelatedType, predicate validation
|
||||
- Blocking: AUTH-REACH-401-005 predicate definitions, verification library (PROV-OBS-54-001/002)
|
||||
|
||||
### Phase 4 – Observability & resilience (Not Started)
|
||||
- Metrics: signing latency, PoE failures, quota hits, key usage distribution
|
||||
- Structured logs with trace IDs, subject digests, issuer mode, decision outcomes
|
||||
- Alerts for PoE outages, key exhaustion, quota breaches, failure spikes
|
||||
- CLI commands: stella promotion attest/verify, stella forensic attest show
|
||||
|
||||
### Key Acceptance Criteria
|
||||
- Signs only requests satisfying OpTok, PoE, quota, scanner provenance checks
|
||||
- DSSE outputs verify with standard cosign tooling
|
||||
- Export Center receives signed bundles with provenance manifests
|
||||
- Audit logs capture every request with tenant, issuer, subject digest, PoE state
|
||||
- CLI/Offline workflows verify signatures using Offline Kit trust roots
|
||||
|
||||
### Technical Decisions & Risks
|
||||
- PoE/entitlement outages: cache last-known entitlement within TTL, emergency bypass with audit
|
||||
- Key compromise: hardware-backed keys, rotation cadence, immediate revocation, incident runbook
|
||||
- Release verification failures: allowlist for trusted scanner digests, manual approval fallback
|
||||
- Determinism: canonicalize JSON, lock timestamp sources, regression tests for DSSE hashing
|
||||
|
||||
### Recent Updates (Sprint 0186/0401 · 2025-11-26)
|
||||
- CryptoDsseSigner with ES256 signature generation via ICryptoProviderRegistry
|
||||
- PredicateTypes catalog extended with VEX/graph predicates
|
||||
- Integration tests upgraded with real crypto, fixture predicates (102 tests passing)
|
||||
- CryptoPro signer plugin in progress (SEC-CRYPTO-90-020)
|
||||
|
||||
## Epic alignment
|
||||
- **Epic 10 – Export Center:** provide signing pipelines, cosign interoperability, and provenance manifests for bundle promotion.
|
||||
- **Epic 19 – Attestor Console:** supply DSSE payloads and Proof-of-Entitlement enforcement feeding attestation workflows described in `docs/modules/attestor/`.
|
||||
See the [Trust Domain Model ADR](../attestor/architecture.md#security-boundary-no-merge-decision-adr) for the no-merge rationale.
|
||||
|
||||
@@ -1,450 +0,0 @@
|
||||
# component_architecture_signer.md — **Stella Ops Signer** (2025Q4)
|
||||
|
||||
> Supports deliverables from Epic 10 – Export Center and Epic 19 – Attestor Console.
|
||||
|
||||
> **Scope.** Implementation‑ready architecture for the **Signer**: the *only* service allowed to produce **Stella Ops‑verified** signatures over SBOMs and reports. It enforces **entitlement** (PoE), **release integrity** (scanner provenance), **sender‑constrained auth** (DPoP/mTLS), and emits **in‑toto/DSSE** bundles suitable for **Rekor v2** logging by the Attestor. Includes APIs, data flow, storage, quotas, security, and test matrices.
|
||||
|
||||
---
|
||||
|
||||
## 0) Mission & boundaries
|
||||
|
||||
**Mission.** Convert authenticated signing requests from trusted Stella Ops services into **verifiable** DSSE bundles while enforcing **license policy** and **supply‑chain integrity**.
|
||||
|
||||
**Boundaries.**
|
||||
|
||||
* **Signer does not push to Rekor** — it returns DSSE to the caller; **Attestor** logs to **Rekor v2**.
|
||||
* **Signer does not compute PASS/FAIL** — it signs SBOMs/reports produced by Scanner/WebService after backend evaluation.
|
||||
* **Signer is stateless for hot path** — long‑term storage is limited to audit events; all secrets/keys live in KMS/HSM or are ephemeral (keyless).
|
||||
|
||||
---
|
||||
|
||||
## 1) Responsibilities (contract)
|
||||
|
||||
1. **Authenticate** caller with **OpTok** (Authority OIDC, DPoP or mTLS‑bound).
|
||||
2. **Authorize** scopes (`signer.sign`) + audience (`aud=signer`) + tenant/installation.
|
||||
3. **Validate entitlement** via **PoE** (Proof‑of‑Entitlement) against Cloud Licensing `/license/introspect`.
|
||||
4. **Verify release integrity** of the **scanner** image digest presented in the request: must be **cosign‑signed** by Stella Ops release key, discoverable via **OCI Referrers API**.
|
||||
5. **Enforce plan & quotas** (concurrency/QPS/artifact size/rate caps).
|
||||
6. **Mint signing identity**:
|
||||
|
||||
* **Keyless** (default): get a short‑lived X.509 cert from **Fulcio** using the Signer’s OIDC identity and sign the DSSE.
|
||||
* **Keyful** (optional): sign with an HSM/KMS key.
|
||||
7. **Return DSSE bundle** (subject digests + predicate + cert chain or KMS key id).
|
||||
8. **Audit** every decision; expose metrics.
|
||||
|
||||
---
|
||||
|
||||
## 2) External dependencies
|
||||
|
||||
* **Authority** (on‑prem OIDC): validates OpToks (JWKS/introspection) and DPoP/mTLS.
|
||||
* **Licensing Service (cloud)**: `/license/introspect` to verify PoE (active, claims, expiry, revocation).
|
||||
* **Fulcio** (Sigstore) *or* **KMS/HSM**: to obtain certs or perform signatures.
|
||||
* **OCI Registry (Referrers API)**: to verify **scanner** image release signature.
|
||||
* **Attestor**: downstream service that writes DSSE bundles to **Rekor v2**.
|
||||
* **Config/state stores**: Valkey (caches, rate buckets), PostgreSQL (audit log).
|
||||
|
||||
---
|
||||
|
||||
## 3) API surface (mTLS; DPoP supported)
|
||||
|
||||
Base path: `/api/v1/signer`. **All endpoints require**:
|
||||
|
||||
* Access token (JWT) from **Authority** with `aud=signer`, `scope=signer.sign`.
|
||||
* **Sender constraint**: DPoP proof per request or mTLS client cert.
|
||||
* **PoE** presented as either:
|
||||
|
||||
* **Client TLS cert** (if PoE is mTLS‑style) chained to Licensing CA, *or*
|
||||
* **PoE JWT** (DPoP/mTLS‑bound) in `X-PoE` header or request body.
|
||||
|
||||
### 3.1 `POST /sign/dsse`
|
||||
|
||||
Request (JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": [
|
||||
{ "name": "s3://stellaops/images/sha256:.../inventory.cdx.pb",
|
||||
"digest": { "sha256": "..." } }
|
||||
],
|
||||
"predicateType": "https://stella-ops.org/attestations/sbom/1",
|
||||
"predicate": {
|
||||
"image_digest": "sha256:...",
|
||||
"stellaops_version": "2.3.1 (2027.04)",
|
||||
"license_id": "LIC-9F2A...",
|
||||
"customer_id": "CUST-ACME",
|
||||
"plan": "pro",
|
||||
"policy_digest": "sha256:...", // optional for final reports
|
||||
"views": ["inventory", "usage"],
|
||||
"created": "2025-10-17T12:34:56Z"
|
||||
},
|
||||
"scannerImageDigest": "sha256:sc-web-or-worker-digest",
|
||||
"poe": {
|
||||
"format": "jwt", // or "mtls"
|
||||
"value": "eyJhbGciOi..." // PoE JWT when not using mTLS PoE
|
||||
},
|
||||
"options": {
|
||||
"signingMode": "keyless", // "keyless" | "kms"
|
||||
"expirySeconds": 600, // cert lifetime hint (keyless)
|
||||
"returnBundle": "dsse+cert" // dsse (default) | dsse+cert
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response 200:
|
||||
|
||||
```json
|
||||
{
|
||||
"bundle": {
|
||||
"dsse": { "payloadType": "application/vnd.in-toto+json", "payload": "<base64>", "signatures": [ ... ] },
|
||||
"certificateChain": [ "-----BEGIN CERTIFICATE-----...", "... root ..." ],
|
||||
"mode": "keyless",
|
||||
"signingIdentity": { "issuer": "https://fulcio.internal", "san": "urn:stellaops:signer", "certExpiry": "2025-10-17T12:44:56Z" }
|
||||
},
|
||||
"policy": { "plan": "pro", "maxArtifactBytes": 104857600, "qpsRemaining": 97 },
|
||||
"auditId": "a7c9e3f2-1b7a-4e87-8c3a-90d7d2c3ad12"
|
||||
}
|
||||
```
|
||||
|
||||
Errors (RFC 7807):
|
||||
|
||||
* `401 invalid_token` (JWT/DPoP/mTLS failure)
|
||||
* `403 entitlement_denied` (PoE invalid/revoked/expired; release year mismatch)
|
||||
* `403 release_untrusted` (scanner image not Stella‑signed)
|
||||
* `429 plan_throttled` (license plan caps)
|
||||
* `413 artifact_too_large` (size cap)
|
||||
* `400 invalid_request` (schema/predicate/type invalid)
|
||||
* `500 signing_unavailable` (Fulcio/KMS outage)
|
||||
|
||||
### 3.2 `GET /verify/referrers?imageDigest=<sha256>`
|
||||
|
||||
Checks whether the **image** at digest is signed by **Stella Ops release key**.
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{ "trusted": true, "signatures": [ { "type": "cosign", "digest": "sha256:...", "signedBy": "StellaOps Release 2027 Q2" } ] }
|
||||
```
|
||||
|
||||
> **Note:** This endpoint is also used internally by Signer before issuing signatures.
|
||||
|
||||
### 3.3 Predicate catalog (Sprint 401 update)
|
||||
|
||||
Signer now enforces an allowlist of predicate identifiers:
|
||||
|
||||
| Predicate | Description | Producer |
|
||||
|-----------|-------------|----------|
|
||||
| `stella.ops/sbom@v1` | SBOM/report attestation (existing). | Scanner WebService. |
|
||||
| `stella.ops/promotion@v1` | Promotion evidence (see `docs/release/promotion-attestations.md`). | DevOps/Export Center. |
|
||||
| `stella.ops/vexDecision@v1` | OpenVEX decision for a single `(cve, product)` pair, including reachability evidence references. | Policy Engine / VEXer. |
|
||||
|
||||
Requests with unknown predicates receive `400 predicate_not_allowed`. Policy Engine must supply the OpenVEX JSON as the `predicate` body; Signer preserves payload bytes verbatim so DSSE digest = OpenVEX digest.
|
||||
|
||||
---
|
||||
|
||||
### KMS drivers (keyful mode)
|
||||
|
||||
Signer now ships five deterministic KMS adapters alongside the default keyless flow:
|
||||
|
||||
- `services.AddFileKms(...)` – stores encrypted ECDSA material on disk for air-gapped or lab installs.
|
||||
- `services.AddAwsKms(options => { options.Region = "us-east-1"; /* optional: options.Endpoint, UseFipsEndpoint */ });` – delegates signing to AWS KMS, caches metadata/public keys offline, and never exports the private scalar. Rotation/revocation still run through AWS tooling (this library intentionally throws for those APIs so we do not paper over operator approvals).
|
||||
- `services.AddGcpKms(options => { options.Endpoint = "kms.googleapis.com"; });` – integrates with Google Cloud KMS asymmetric keys, auto-resolves the primary key version when callers omit a version, and verifies signatures locally with exported PEM material.
|
||||
- `services.AddPkcs11Kms(options => { options.LibraryPath = "/opt/hsm/libpkcs11.so"; options.PrivateKeyLabel = "stella-attestor"; });` – loads a PKCS#11 module, opens read-only sessions, signs digests via HSM mechanisms, and never hoists the private scalar into process memory.
|
||||
- `services.AddFido2Kms(options => { options.CredentialId = "<base64url>"; options.PublicKeyPem = "-----BEGIN PUBLIC KEY-----..."; options.AuthenticatorFactory = sp => new WebAuthnAuthenticator(); });` – routes signing to a WebAuthn/FIDO2 authenticator for dual-control or air-gap scenarios. The authenticator must supply the CTAP/WebAuthn plumbing; the library handles digesting, key material caching, and verification.
|
||||
|
||||
Cloud & hardware-backed drivers share a few invariants:
|
||||
|
||||
1. Hash payloads server-side (SHA-256) before invoking provider APIs – signatures remain reproducible and digest inputs are observable in structured audit logs.
|
||||
2. Cache metadata for the configurable window (default 5 min) and subject-public-key-info blobs for 10 min; tune these per sovereignty policy when running in sealed/offline environments.
|
||||
3. Only expose public coordinates (`Qx`, `Qy`) to the host ― `KmsKeyMaterial.D` is blank for non-exportable keys so downstream code cannot accidentally persist secrets.
|
||||
|
||||
> **Security review checkpoint:** rotate/destroy remains an administrative action in the provider. Document those runbooks per tenant, and gate AWS/GCP traffic in sealed-mode via the existing egress allowlist. PKCS#11 loads native code, so keep library paths on the allowlist and validate HSM policies separately. FIDO2 authenticators expect an operator in the loop; plan for session timeouts and explicit audit fields when enabling interactive signing.
|
||||
|
||||
## 4) Validation pipeline (hot path)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant Client as Scanner.WebService
|
||||
participant Auth as Authority (OIDC)
|
||||
participant Sign as Signer
|
||||
participant Lic as Licensing Service (cloud)
|
||||
participant Reg as OCI Registry (Referrers)
|
||||
participant Ful as Fulcio/KMS
|
||||
|
||||
Client->>Sign: POST /sign/dsse (OpTok + DPoP/mTLS, PoE, request)
|
||||
Note over Sign: 1) Validate OpTok, audience, scope, DPoP/mTLS binding
|
||||
Sign->>Lic: /license/introspect(PoE)
|
||||
Lic-->>Sign: { active, claims: {license_id, plan, valid_release_year, max_version}, exp }
|
||||
Note over Sign: 2) Enforce plan/version window and revocation
|
||||
|
||||
Sign->>Reg: Verify scannerImageDigest signed (Referrers + cosign)
|
||||
Reg-->>Sign: OK with signer identity
|
||||
Note over Sign: 3) Enforce release integrity
|
||||
|
||||
Note over Sign: 4) Enforce quotas (QPS/concurrency/size)
|
||||
Sign->>Ful: Mint cert (keyless) or sign via KMS
|
||||
Ful-->>Sign: Cert or signature
|
||||
|
||||
Sign-->>Client: DSSE bundle (+cert chain), policy counters, auditId
|
||||
```
|
||||
|
||||
**DPoP nonce dance (when enabled for high‑value ops):**
|
||||
|
||||
* If DPoP proof lacks a valid nonce, Signer replies `401` with `WWW-Authenticate: DPoP error="use_dpop_nonce", dpop_nonce="<nonce>"`.
|
||||
* Client retries with new proof including the nonce; Signer validates nonce and `jti` uniqueness (Valkey TTL cache).
|
||||
|
||||
---
|
||||
|
||||
## 5) Entitlement enforcement (PoE)
|
||||
|
||||
* **Accepted forms**:
|
||||
|
||||
* **mTLS PoE**: client presents a **PoE client cert** at TLS handshake; Signer validates chain to **Licensing CA** (CA bundle configured) and calls `/license/introspect` with cert thumbprint + serial.
|
||||
* **JWT PoE**: `X-PoE` bearer token (DPoP/mTLS‑bound) is validated (sig + `cnf`) locally (Licensing JWKS) and then **introspected** for status and claims.
|
||||
|
||||
* **Claims required**:
|
||||
|
||||
* `license_id`, `plan` (free|pro|enterprise|gov), `valid_release_year`, `max_version`, `exp`.
|
||||
* Optional: `tenant_id`, `customer_id`, `entitlements[]`.
|
||||
|
||||
* **Enforcements**:
|
||||
|
||||
* Reject if **revoked**, **expired**, **plan mismatch** or **release outside window** (`stellaops_version` in predicate exceeds `max_version` or release date beyond `valid_release_year`).
|
||||
* Apply plan **throttles** (QPS/concurrency/artifact bytes) via token‑bucket in Valkey keyed by `license_id`.
|
||||
|
||||
---
|
||||
|
||||
## 6) Release integrity (scanner provenance)
|
||||
|
||||
* **Input**: `scannerImageDigest` representing the actual Scanner component that produced the artifact.
|
||||
|
||||
* **Check**:
|
||||
|
||||
1. Use **OCI Referrers API** to enumerate signatures of that digest.
|
||||
2. Verify **cosign** signatures against the configured **Stella Ops Release** keyring (keyless Fulcio roots *or* keyful public keys).
|
||||
3. Optionally require Rekor inclusion for those signatures.
|
||||
|
||||
* **Policy**:
|
||||
|
||||
* If not signed by an authorized **Stella Ops Release** identity → **deny**.
|
||||
* If signed but **release year** > PoE `valid_release_year` → **deny**.
|
||||
|
||||
* **Cache**: LRU of digest → verification result (TTL 10–30 min) to avoid registry thrash.
|
||||
|
||||
---
|
||||
|
||||
## 7) Signing modes
|
||||
|
||||
### 7.1 Keyless (default; Sigstore Fulcio)
|
||||
|
||||
* Signer authenticates to **Fulcio** using its on‑prem OIDC identity (client credentials) and requests a **short‑lived cert** (5–10 min).
|
||||
* Generates **ephemeral keypair**, gets cert for the public key, signs DSSE with the **private key**.
|
||||
* DSSE **bundle** includes **certificate chain**; verifiers validate to Fulcio root.
|
||||
|
||||
### 7.2 Keyful (optional; KMS/HSM)
|
||||
|
||||
* Signer uses a configured **KMS** key (AWS KMS, GCP KMS, Azure Key Vault, Vault Transit, or HSM).
|
||||
* DSSE bundle includes **key metadata** (kid, cert chain if x509).
|
||||
* Recommended for FIPS/sovereign environments.
|
||||
|
||||
---
|
||||
|
||||
## 8) Predicates & schema
|
||||
|
||||
Supported **predicate types** (extensible):
|
||||
|
||||
* `https://stella-ops.org/attestations/sbom/1` (SBOM emissions)
|
||||
* `https://stella-ops.org/attestations/report/1` (final PASS/FAIL reports)
|
||||
* `https://stella-ops.org/attestations/vex-export/1` (Excititor exports; optional)
|
||||
|
||||
**Validation**:
|
||||
|
||||
* JSON‑Schema per predicate type; **canonical property order**.
|
||||
* `subject[*].digest` must include `sha256`.
|
||||
* `predicate.stellaops_version` must parse and match policy windows.
|
||||
|
||||
---
|
||||
|
||||
## 9) Quotas & throttling
|
||||
|
||||
Per `license_id` (from PoE):
|
||||
|
||||
* **QPS** (token bucket), **concurrency** (semaphore), **artifact bytes** (sliding window).
|
||||
* On exceed → `429 plan_throttled` with `Retry-After`.
|
||||
* Free/community plan may also receive **randomized delay** to disincentivize farmed signing.
|
||||
|
||||
---
|
||||
|
||||
## 10) Storage & caches
|
||||
|
||||
* **Valkey**:
|
||||
|
||||
* DPoP nonce & `jti` replay cache (TTL ≤ 10 min).
|
||||
* PoE introspection cache (short TTL, e.g., 60–120 s).
|
||||
* Release‑verify cache (`scannerImageDigest` → { trusted, ts }).
|
||||
|
||||
* **Audit store** (PostgreSQL): `signer.audit_events`
|
||||
|
||||
```
|
||||
{ _id, ts, tenantId, installationId, licenseId, customerId,
|
||||
plan, actor{sub,cnf}, request{predicateType, subjectSha256[], imageDigest},
|
||||
poe{type, thumbprint|jwtKid, exp, introspectSnapshot},
|
||||
release{digest, signerId, policy},
|
||||
mode: "keyless"|"kms",
|
||||
result: "success"|"deny:<reason>"|"error:<reason>",
|
||||
bundleSha256? }
|
||||
```
|
||||
|
||||
* **Config**: Stella Ops release signing keyring, Fulcio roots, Licensing CA bundle.
|
||||
|
||||
---
|
||||
|
||||
## 11) Security & privacy
|
||||
|
||||
* **mTLS** on all Signer endpoints.
|
||||
* **No bearer fallbacks** — DPoP/mTLS enforced for `aud=signer`.
|
||||
* **PoE** is never persisted beyond audit snapshots (minimized fields).
|
||||
* **Secrets**: no long‑lived private keys on disk (keyless) or handled via KMS APIs.
|
||||
* **Input hardening**: schema‑validate predicates; cap payload sizes; zstd/gzip decompression bombs guarded.
|
||||
* **Logging**: redact PoE JWTs, access tokens, DPoP proofs; log only hashes and identifiers.
|
||||
|
||||
---
|
||||
|
||||
## 12) Metrics & observability
|
||||
|
||||
* `signer.requests_total{result}`
|
||||
* `signer.latency_seconds{stage=auth|introspect|release_verify|sign}`
|
||||
* `signer.poe_failures_total{reason}`
|
||||
* `signer.release_verify_failures_total{reason}`
|
||||
* `signer.plan_throttle_total{license_id}`
|
||||
* `signer.bundle_bytes_total`
|
||||
* `signer.keyless_certs_issued_total` / `signer.kms_sign_total`
|
||||
* OTEL traces across stages; correlation id (`auditId`) returned to client.
|
||||
|
||||
---
|
||||
|
||||
## 13) Configuration (YAML)
|
||||
|
||||
```yaml
|
||||
signer:
|
||||
listen: "https://0.0.0.0:8443"
|
||||
authority:
|
||||
issuer: "https://authority.internal"
|
||||
jwksUrl: "https://authority.internal/jwks"
|
||||
require: "dpop" # "dpop" | "mtls"
|
||||
poe:
|
||||
mode: "both" # "jwt" | "mtls" | "both"
|
||||
licensing:
|
||||
introspectUrl: "https://www.stella-ops.org/api/v1/license/introspect"
|
||||
caBundle: "/etc/ssl/licensing-ca.pem"
|
||||
cacheTtlSeconds: 90
|
||||
release:
|
||||
referrers:
|
||||
allowRekorVerified: true
|
||||
keyrings:
|
||||
- type: "cosign-keyless"
|
||||
fulcioRoots: ["/etc/fulcio/root.pem"]
|
||||
identities:
|
||||
- san: "mailto:release@stella-ops.org"
|
||||
- san: "https://sigstore.dev/oidc/stellaops"
|
||||
signing:
|
||||
mode: "keyless" # "keyless" | "kms"
|
||||
fulcio:
|
||||
issuer: "https://fulcio.internal"
|
||||
oidcClientId: "signer"
|
||||
oidcClientSecretRef: "env:FULCIO_CLIENT_SECRET"
|
||||
certTtlSeconds: 600
|
||||
kms:
|
||||
provider: "aws-kms"
|
||||
keyId: "arn:aws:kms:...:key/..."
|
||||
quotas:
|
||||
default:
|
||||
qps: 100
|
||||
concurrency: 20
|
||||
maxArtifactBytes: 104857600
|
||||
free:
|
||||
qps: 5
|
||||
concurrency: 1
|
||||
maxArtifactBytes: 1048576
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14) Testing matrix
|
||||
|
||||
* **Auth & DPoP**: bad `aud`, wrong `jkt`, replayed `jti`, missing nonce, mTLS mismatch.
|
||||
* **PoE**: expired, revoked, plan mismatch, release year gate, max_version gate.
|
||||
* **Release verify**: unsigned digest, wrong signer, Rekor‑absent (when required), referrers unreachable.
|
||||
* **Signing**: Fulcio outage; KMS timeouts; bundle correctness (verifier harness).
|
||||
* **Quotas**: burst above QPS, artifact over size, concurrency overflow.
|
||||
* **Schema**: invalid predicate types/required fields.
|
||||
* **Determinism**: same request → identical DSSE (aside from cert validity period).
|
||||
* **Perf**: P95 end‑to‑end under 120 ms with caches warm (excluding network to Fulcio).
|
||||
|
||||
---
|
||||
|
||||
## 15) Failure modes & responses
|
||||
|
||||
| Failure | HTTP | Problem type | Notes |
|
||||
| ----------------------- | ---- | --------------------- | -------------------------------------------- |
|
||||
| Invalid OpTok / DPoP | 401 | `invalid_token` | `WWW-Authenticate` with DPoP nonce if needed |
|
||||
| PoE invalid/revoked | 403 | `entitlement_denied` | Include `license_id` (hashed) and reason |
|
||||
| Scanner image untrusted | 403 | `release_untrusted` | Include digest and required identity |
|
||||
| Plan throttle | 429 | `plan_throttled` | Include limits and `Retry-After` |
|
||||
| Artifact too large | 413 | `artifact_too_large` | Include cap |
|
||||
| Fulcio/KMS down | 503 | `signing_unavailable` | Retry‑After with jitter |
|
||||
|
||||
---
|
||||
|
||||
## 16) Deployment & HA
|
||||
|
||||
* Run ≥ 2 replicas; front with L7 LB; **sticky** not required.
|
||||
* Valkey for replay/quota caches (HA).
|
||||
* Audit sink (PostgreSQL) in primary region; asynchronous write with local fallback buffer.
|
||||
* Fulcio/KMS clients configured with retries/backoff; circuit breakers.
|
||||
|
||||
---
|
||||
|
||||
## 17) Implementation notes
|
||||
|
||||
* **.NET 10** minimal API + Kestrel mTLS; custom DPoP middleware; JWT/JWKS cache.
|
||||
* **Cosign verification** via sigstore libraries; Referrers queries over registry API with retries.
|
||||
* **DSSE** via in‑toto libs; canonical JSON writer for predicates.
|
||||
* **Backpressure** paths: refuse at auth/quota stages before any expensive network calls.
|
||||
|
||||
---
|
||||
|
||||
## 18) Examples (wire)
|
||||
|
||||
**Request (free plan; expect throttle if burst):**
|
||||
|
||||
```http
|
||||
POST /api/v1/signer/sign/dsse HTTP/1.1
|
||||
Authorization: DPoP <JWT>
|
||||
DPoP: <proof>
|
||||
Content-Type: application/json
|
||||
|
||||
```
|
||||
|
||||
**Error (release untrusted):**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "https://stella-ops.org/problems/release_untrusted",
|
||||
"title": "Scanner image not signed by StellaOps",
|
||||
"status": 403,
|
||||
"detail": "sha256:abcd... not in trusted keyring",
|
||||
"instance": "urn:audit:a7c9e3f2-..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 19) Roadmap
|
||||
|
||||
* **Key Transparency**: optional publication of Signer’s *own* certs to a KT log.
|
||||
* **Attested Build**: SLSA‑style provenance for Signer container itself, checked at startup.
|
||||
* **FIPS mode**: enforce `ES256` + KMS/HSM only; disallow Ed25519.
|
||||
* **Dual attestation**: optional immediate push to **Attestor** (sync mode) with timeout budget, returning Rekor UUID inline.
|
||||
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
# Keyless Signing Quick Start
|
||||
|
||||
Get keyless signing working in your CI/CD pipeline in under 5 minutes.
|
||||
|
||||
## Overview
|
||||
|
||||
Keyless signing uses your CI platform's OIDC identity to sign artifacts without managing private keys. The signature is bound to your repository, branch, and workflow identity.
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────┐ ┌───────────────┐
|
||||
│ CI Platform │────▶│ Fulcio │────▶│ Signed Artifact│
|
||||
│ OIDC Token │ │ Sigstore│ │ + Rekor Entry │
|
||||
└─────────────┘ └─────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
## GitHub Actions (Fastest)
|
||||
|
||||
### Step 1: Add the workflow
|
||||
|
||||
Create `.github/workflows/sign.yml`:
|
||||
|
||||
```yaml
|
||||
name: Build and Sign
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-and-sign:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build container
|
||||
run: |
|
||||
docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
|
||||
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
|
||||
|
||||
- name: Install StellaOps CLI
|
||||
run: curl -sL https://get.stella-ops.org/cli | sh
|
||||
|
||||
- name: Get OIDC Token
|
||||
id: oidc
|
||||
run: |
|
||||
TOKEN=$(curl -sLS "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sigstore" \
|
||||
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
|
||||
| jq -r '.value')
|
||||
echo "::add-mask::${TOKEN}"
|
||||
echo "token=${TOKEN}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Sign container
|
||||
env:
|
||||
STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
|
||||
run: |
|
||||
DIGEST=$(docker inspect ghcr.io/${{ github.repository }}:${{ github.sha }} \
|
||||
--format='{{index .RepoDigests 0}}' | cut -d@ -f2)
|
||||
stella attest sign --keyless --artifact "$DIGEST"
|
||||
```
|
||||
|
||||
### Step 2: Push and verify
|
||||
|
||||
```bash
|
||||
git add .github/workflows/sign.yml
|
||||
git commit -m "Add keyless signing"
|
||||
git push
|
||||
```
|
||||
|
||||
Check Actions tab - your container is now signed!
|
||||
|
||||
---
|
||||
|
||||
## GitLab CI (5 minutes)
|
||||
|
||||
### Step 1: Update `.gitlab-ci.yml`
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build
|
||||
- sign
|
||||
|
||||
build:
|
||||
stage: build
|
||||
image: docker:24
|
||||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
- echo "ARTIFACT_DIGEST=$(docker inspect $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --format='{{index .RepoDigests 0}}' | cut -d@ -f2)" >> build.env
|
||||
artifacts:
|
||||
reports:
|
||||
dotenv: build.env
|
||||
|
||||
sign:
|
||||
stage: sign
|
||||
image: stella-ops/cli:latest
|
||||
id_tokens:
|
||||
STELLAOPS_OIDC_TOKEN:
|
||||
aud: sigstore
|
||||
needs:
|
||||
- build
|
||||
script:
|
||||
- stella attest sign --keyless --artifact "$ARTIFACT_DIGEST"
|
||||
only:
|
||||
- main
|
||||
```
|
||||
|
||||
### Step 2: Push
|
||||
|
||||
```bash
|
||||
git add .gitlab-ci.yml
|
||||
git commit -m "Add keyless signing"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Gate
|
||||
|
||||
Add verification before deployment:
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
needs: [build-and-sign]
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
steps:
|
||||
- name: Verify before deploy
|
||||
run: |
|
||||
stella attest verify \
|
||||
--artifact "${{ needs.build-and-sign.outputs.digest }}" \
|
||||
--certificate-identity "repo:${{ github.repository }}:ref:refs/heads/main" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
--require-rekor
|
||||
|
||||
- name: Deploy
|
||||
run: kubectl set image deployment/app app=$IMAGE
|
||||
```
|
||||
|
||||
### GitLab CI
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
stage: deploy
|
||||
environment: production
|
||||
needs:
|
||||
- sign
|
||||
script:
|
||||
- |
|
||||
stella attest verify \
|
||||
--artifact "$ARTIFACT_DIGEST" \
|
||||
--certificate-identity "project_path:$CI_PROJECT_PATH:ref_type:branch:ref:main" \
|
||||
--certificate-oidc-issuer "https://gitlab.com" \
|
||||
--require-rekor
|
||||
- kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
only:
|
||||
- main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Identity Patterns Cheat Sheet
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
| Pattern | Example |
|
||||
|---------|---------|
|
||||
| Any branch | `repo:org/repo:.*` |
|
||||
| Main only | `repo:org/repo:ref:refs/heads/main` |
|
||||
| Tags only | `repo:org/repo:ref:refs/tags/v.*` |
|
||||
| Environment | `repo:org/repo:environment:production` |
|
||||
|
||||
**OIDC Issuer:** `https://token.actions.githubusercontent.com`
|
||||
|
||||
### GitLab CI
|
||||
|
||||
| Pattern | Example |
|
||||
|---------|---------|
|
||||
| Any ref | `project_path:group/project:.*` |
|
||||
| Main only | `project_path:group/project:ref_type:branch:ref:main` |
|
||||
| Tags only | `project_path:group/project:ref_type:tag:.*` |
|
||||
| Protected | `project_path:group/project:ref_protected:true` |
|
||||
|
||||
**OIDC Issuer:** `https://gitlab.com` (or self-hosted URL)
|
||||
|
||||
---
|
||||
|
||||
## Using Reusable Workflows
|
||||
|
||||
For cleaner pipelines, use StellaOps reusable workflows:
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
sign:
|
||||
uses: stella-ops/workflows/.github/workflows/stellaops-sign.yml@v1
|
||||
with:
|
||||
artifact-digest: sha256:abc123...
|
||||
artifact-type: image
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
verify:
|
||||
needs: [sign]
|
||||
uses: stella-ops/workflows/.github/workflows/stellaops-verify.yml@v1
|
||||
with:
|
||||
artifact-digest: sha256:abc123...
|
||||
certificate-identity: "repo:${{ github.repository }}:ref:refs/heads/main"
|
||||
certificate-oidc-issuer: "https://token.actions.githubusercontent.com"
|
||||
```
|
||||
|
||||
### GitLab CI
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- project: 'stella-ops/templates'
|
||||
file: '.gitlab-ci-stellaops.yml'
|
||||
|
||||
sign-container:
|
||||
extends: .stellaops-sign
|
||||
variables:
|
||||
ARTIFACT_DIGEST: sha256:abc123...
|
||||
ARTIFACT_TYPE: image
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What's Next?
|
||||
|
||||
- [Identity Constraints Guide](./identity-constraints.md) - Secure verification patterns
|
||||
- [Troubleshooting Guide](./keyless-signing-troubleshooting.md) - Common issues and fixes
|
||||
- [Offline Verification](../airgap/offline-verification.md) - Air-gapped environments
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Documentation: https://docs.stella-ops.org/
|
||||
- Issues: https://github.com/stella-ops/stellaops/issues
|
||||
- Slack: https://stellaops.slack.com/
|
||||
@@ -1,399 +0,0 @@
|
||||
# Keyless Signing Troubleshooting Guide
|
||||
|
||||
This guide covers common issues when integrating StellaOps keyless signing into CI/CD pipelines.
|
||||
|
||||
## Common Errors
|
||||
|
||||
### OIDC Token Acquisition Failures
|
||||
|
||||
#### Error: "Unable to get OIDC token"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: Unable to get ACTIONS_ID_TOKEN_REQUEST_URL
|
||||
```
|
||||
|
||||
**Cause:** The workflow doesn't have `id-token: write` permission.
|
||||
|
||||
**Solution:**
|
||||
```yaml
|
||||
# GitHub Actions
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
# GitLab CI
|
||||
job:
|
||||
id_tokens:
|
||||
STELLAOPS_OIDC_TOKEN:
|
||||
aud: sigstore
|
||||
```
|
||||
|
||||
#### Error: "Token audience mismatch"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: Token audience 'api://default' does not match expected 'sigstore'
|
||||
```
|
||||
|
||||
**Cause:** OIDC token was requested with wrong audience.
|
||||
|
||||
**Solution:**
|
||||
```yaml
|
||||
# GitHub Actions
|
||||
OIDC_TOKEN=$(curl -sLS "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sigstore" \
|
||||
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}")
|
||||
|
||||
# GitLab CI
|
||||
id_tokens:
|
||||
STELLAOPS_OIDC_TOKEN:
|
||||
aud: sigstore # Must be 'sigstore' for Fulcio
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fulcio Certificate Errors
|
||||
|
||||
#### Error: "Failed to get certificate from Fulcio"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: error getting certificate from Fulcio: 401 Unauthorized
|
||||
```
|
||||
|
||||
**Causes:**
|
||||
1. OIDC token expired (tokens are short-lived, typically 5-10 minutes)
|
||||
2. Fulcio doesn't recognize the OIDC issuer
|
||||
3. Network connectivity issues to Fulcio
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Token expiry:** Request token immediately before signing:
|
||||
```yaml
|
||||
- name: Get OIDC Token
|
||||
id: oidc
|
||||
run: |
|
||||
# Get fresh token right before signing
|
||||
OIDC_TOKEN=$(curl -sLS "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sigstore" \
|
||||
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
|
||||
| jq -r '.value')
|
||||
echo "token=${OIDC_TOKEN}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Sign (immediately after)
|
||||
env:
|
||||
STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
|
||||
run: stella attest sign --keyless --artifact "$DIGEST"
|
||||
```
|
||||
|
||||
2. **Unknown issuer:** Ensure your CI platform is supported:
|
||||
- GitHub Actions: `https://token.actions.githubusercontent.com`
|
||||
- GitLab.com: `https://gitlab.com`
|
||||
- Self-hosted GitLab: Must be configured in Fulcio
|
||||
|
||||
3. **Network issues:** Check connectivity:
|
||||
```bash
|
||||
curl -v https://fulcio.sigstore.dev/api/v2/signingCert
|
||||
```
|
||||
|
||||
#### Error: "Certificate identity not found in token"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: no matching subject or SAN found in OIDC token
|
||||
```
|
||||
|
||||
**Cause:** Token claims don't include expected identity fields.
|
||||
|
||||
**Solution:** Verify token contents:
|
||||
```bash
|
||||
# Decode and inspect token (don't do this in production logs)
|
||||
echo $OIDC_TOKEN | cut -d. -f2 | base64 -d | jq .
|
||||
```
|
||||
|
||||
Expected claims for GitHub Actions:
|
||||
```json
|
||||
{
|
||||
"sub": "repo:org/repo:ref:refs/heads/main",
|
||||
"iss": "https://token.actions.githubusercontent.com",
|
||||
"repository": "org/repo",
|
||||
"ref": "refs/heads/main"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Rekor Transparency Log Errors
|
||||
|
||||
#### Error: "Failed to upload to Rekor"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: error uploading entry to Rekor: 500 Internal Server Error
|
||||
```
|
||||
|
||||
**Causes:**
|
||||
1. Rekor service temporarily unavailable
|
||||
2. Entry too large
|
||||
3. Network issues
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Retry with backoff:**
|
||||
```yaml
|
||||
- name: Sign with retry
|
||||
run: |
|
||||
for i in 1 2 3; do
|
||||
stella attest sign --keyless --artifact "$DIGEST" && break
|
||||
echo "Attempt $i failed, retrying in 30s..."
|
||||
sleep 30
|
||||
done
|
||||
```
|
||||
|
||||
2. **Check Rekor status:** https://status.sigstore.dev/
|
||||
|
||||
3. **Use offline bundle (air-gapped):**
|
||||
```bash
|
||||
stella attest sign --keyless --artifact "$DIGEST" --offline-bundle
|
||||
```
|
||||
|
||||
#### Error: "Rekor entry not found"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: entry not found in transparency log
|
||||
```
|
||||
|
||||
**Cause:** Verification requiring Rekor but entry wasn't logged (offline signing).
|
||||
|
||||
**Solution:** Either:
|
||||
- Sign with Rekor enabled (default)
|
||||
- Verify without Rekor requirement:
|
||||
```bash
|
||||
stella attest verify --artifact "$DIGEST" --skip-rekor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Verification Failures
|
||||
|
||||
#### Error: "Certificate identity mismatch"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: certificate identity 'repo:org/repo:ref:refs/heads/feature'
|
||||
does not match expected 'repo:org/repo:ref:refs/heads/main'
|
||||
```
|
||||
|
||||
**Cause:** Artifact was signed from a different branch/ref than expected.
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Use regex for flexibility:**
|
||||
```bash
|
||||
stella attest verify \
|
||||
--artifact "$DIGEST" \
|
||||
--certificate-identity "repo:org/repo:.*" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
|
||||
```
|
||||
|
||||
2. **Verify expected signing context:**
|
||||
```bash
|
||||
# Check what identity was actually used
|
||||
stella attest inspect --artifact "$DIGEST" --show-identity
|
||||
```
|
||||
|
||||
#### Error: "Certificate OIDC issuer mismatch"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: certificate issuer 'https://gitlab.com'
|
||||
does not match expected 'https://token.actions.githubusercontent.com'
|
||||
```
|
||||
|
||||
**Cause:** Artifact was signed by a different CI platform.
|
||||
|
||||
**Solution:** Update verification to accept correct issuer:
|
||||
```bash
|
||||
# For GitLab-signed artifacts
|
||||
stella attest verify \
|
||||
--artifact "$DIGEST" \
|
||||
--certificate-identity "project_path:org/repo:.*" \
|
||||
--certificate-oidc-issuer "https://gitlab.com"
|
||||
```
|
||||
|
||||
#### Error: "Signature expired"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: certificate validity period has expired
|
||||
```
|
||||
|
||||
**Cause:** Fulcio certificates are short-lived (10 minutes). Verification after expiry requires Rekor proof.
|
||||
|
||||
**Solution:** Ensure Rekor verification is enabled:
|
||||
```bash
|
||||
stella attest verify \
|
||||
--artifact "$DIGEST" \
|
||||
--require-rekor \
|
||||
--certificate-identity "..." \
|
||||
--certificate-oidc-issuer "..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Platform-Specific Issues
|
||||
|
||||
#### GitHub Actions: "Resource not accessible by integration"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: Resource not accessible by integration
|
||||
```
|
||||
|
||||
**Cause:** GitHub App or token lacks required permissions.
|
||||
|
||||
**Solution:** Ensure workflow has correct permissions:
|
||||
```yaml
|
||||
permissions:
|
||||
id-token: write # For OIDC token
|
||||
contents: read # For checkout
|
||||
packages: write # If pushing to GHCR
|
||||
attestations: write # For GitHub attestations
|
||||
```
|
||||
|
||||
#### GitLab CI: "id_tokens not available"
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: STELLAOPS_OIDC_TOKEN variable not set
|
||||
```
|
||||
|
||||
**Cause:** GitLab version doesn't support `id_tokens` or feature is disabled.
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. Check GitLab version (requires 15.7+)
|
||||
2. Enable CI/CD OIDC in project settings:
|
||||
- Settings > CI/CD > Token Access
|
||||
- Enable "Allow CI job tokens from the following projects"
|
||||
|
||||
3. Use service account as fallback:
|
||||
```yaml
|
||||
sign:
|
||||
script:
|
||||
- |
|
||||
if [ -z "$STELLAOPS_OIDC_TOKEN" ]; then
|
||||
# Fallback to service account
|
||||
stella attest sign --key "$SIGNING_KEY" --artifact "$DIGEST"
|
||||
else
|
||||
stella attest sign --keyless --artifact "$DIGEST"
|
||||
fi
|
||||
```
|
||||
|
||||
#### Gitea: OIDC Token Format
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: Invalid OIDC token format
|
||||
```
|
||||
|
||||
**Cause:** Gitea Actions uses different token acquisition method.
|
||||
|
||||
**Solution:**
|
||||
```yaml
|
||||
- name: Get OIDC Token
|
||||
run: |
|
||||
# Gitea provides token directly in environment
|
||||
if [ -n "$ACTIONS_ID_TOKEN" ]; then
|
||||
echo "token=$ACTIONS_ID_TOKEN" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "::error::OIDC token not available"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Network and Connectivity
|
||||
|
||||
#### Error: "Connection refused" to Sigstore services
|
||||
|
||||
**Symptoms:**
|
||||
```
|
||||
Error: dial tcp: connection refused
|
||||
```
|
||||
|
||||
**Cause:** Firewall blocking outbound connections.
|
||||
|
||||
**Required endpoints:**
|
||||
| Service | URL | Purpose |
|
||||
|---------|-----|---------|
|
||||
| Fulcio | `https://fulcio.sigstore.dev` | Certificate issuance |
|
||||
| Rekor | `https://rekor.sigstore.dev` | Transparency log |
|
||||
| TUF | `https://tuf-repo-cdn.sigstore.dev` | Trust root |
|
||||
| OIDC | CI platform URL | Token validation |
|
||||
|
||||
**Solution:** Allow outbound HTTPS to these endpoints, or use self-hosted Sigstore.
|
||||
|
||||
#### Proxy Configuration
|
||||
|
||||
```yaml
|
||||
- name: Sign with proxy
|
||||
env:
|
||||
HTTPS_PROXY: http://proxy.internal:8080
|
||||
NO_PROXY: internal.corp.com
|
||||
STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
|
||||
run: stella attest sign --keyless --artifact "$DIGEST"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging Commands
|
||||
|
||||
### Inspect OIDC Token
|
||||
```bash
|
||||
# Decode token payload (never log in production)
|
||||
echo $OIDC_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq .
|
||||
```
|
||||
|
||||
### Verify Fulcio Connectivity
|
||||
```bash
|
||||
curl -v https://fulcio.sigstore.dev/api/v2/configuration
|
||||
```
|
||||
|
||||
### Check Rekor Entry
|
||||
```bash
|
||||
# Search by artifact hash
|
||||
rekor-cli search --sha "sha256:abc123..."
|
||||
|
||||
# Get entry details
|
||||
rekor-cli get --uuid "24296fb24b8ad77a..."
|
||||
```
|
||||
|
||||
### Inspect Attestation
|
||||
```bash
|
||||
stella attest inspect \
|
||||
--artifact "$DIGEST" \
|
||||
--show-certificate \
|
||||
--show-rekor-entry
|
||||
```
|
||||
|
||||
### Verbose Signing
|
||||
```bash
|
||||
STELLAOPS_LOG_LEVEL=debug stella attest sign --keyless --artifact "$DIGEST"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
1. **Check service status:** https://status.sigstore.dev/
|
||||
2. **StellaOps documentation:** https://docs.stella-ops.org/
|
||||
3. **Sigstore documentation:** https://docs.sigstore.dev/
|
||||
4. **File an issue:** https://github.com/stella-ops/stellaops/issues
|
||||
|
||||
When reporting issues, include:
|
||||
- CI platform and version
|
||||
- StellaOps CLI version (`stella --version`)
|
||||
- Sanitized error output (remove tokens/secrets)
|
||||
- Relevant workflow configuration
|
||||
@@ -1,230 +0,0 @@
|
||||
# Keyless Signing Guide
|
||||
|
||||
This guide explains how to configure and use keyless signing with Sigstore Fulcio for CI/CD pipelines.
|
||||
|
||||
## Overview
|
||||
|
||||
Keyless signing eliminates the need to manage long-lived signing keys by using short-lived X.509 certificates (~10 minute TTL) issued by Fulcio based on OIDC identity tokens. This approach:
|
||||
|
||||
- **Zero key management**: No secrets to rotate or protect
|
||||
- **Identity-bound signatures**: Signatures are cryptographically tied to the CI/CD identity
|
||||
- **Non-repudiation**: Audit trail via Rekor transparency log
|
||||
- **Industry standard**: Compatible with Sigstore ecosystem (cosign, gitsign, etc.)
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ CI Runner │────▶│ OIDC Token │────▶│ Fulcio │────▶│ Ephemeral │
|
||||
│ (GitHub/GL) │ │ Provider │ │ CA │ │ Cert │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Sign DSSE │
|
||||
│ Envelope │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
1. **CI runner provides OIDC token** - GitHub Actions, GitLab CI, etc. provide ambient identity tokens
|
||||
2. **Token exchanged for certificate** - Fulcio validates the OIDC token and issues a short-lived certificate
|
||||
3. **Ephemeral key generation** - A new ECDSA P-256 or Ed25519 key is generated per signing operation
|
||||
4. **DSSE signing** - The payload is signed using the ephemeral key
|
||||
5. **Certificate attached** - The Fulcio certificate is included in the signed bundle for verification
|
||||
|
||||
## Configuration
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```yaml
|
||||
# etc/signer.yaml
|
||||
signer:
|
||||
signing:
|
||||
mode: "keyless"
|
||||
keyless:
|
||||
enabled: true
|
||||
fulcio:
|
||||
url: "https://fulcio.sigstore.dev"
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
oidc:
|
||||
useAmbientToken: true
|
||||
```
|
||||
|
||||
### Private Fulcio Instance
|
||||
|
||||
For air-gapped or private deployments:
|
||||
|
||||
```yaml
|
||||
signer:
|
||||
signing:
|
||||
keyless:
|
||||
fulcio:
|
||||
url: "https://fulcio.internal.example.com"
|
||||
certificate:
|
||||
rootBundlePath: "/etc/stellaops/fulcio-roots.pem"
|
||||
additionalRoots:
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBjzCCATSgAwIBAgIRANZl...
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
### Identity Constraints
|
||||
|
||||
Restrict which identities are allowed to sign:
|
||||
|
||||
```yaml
|
||||
signer:
|
||||
signing:
|
||||
keyless:
|
||||
identity:
|
||||
expectedIssuers:
|
||||
- "https://token.actions.githubusercontent.com"
|
||||
- "https://gitlab.com"
|
||||
expectedSubjectPatterns:
|
||||
- "^https://github\.com/myorg/.*$"
|
||||
- "^project_path:mygroup/myproject:.*$"
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: Sign Artifacts
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
sign:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # Required for OIDC token
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install StellaOps CLI
|
||||
run: |
|
||||
curl -sSL https://get.stella-ops.io | bash
|
||||
|
||||
- name: Sign with keyless mode
|
||||
run: |
|
||||
stella sign --mode keyless \
|
||||
--image ghcr.io/${{ github.repository }}:${{ github.sha }}
|
||||
```
|
||||
|
||||
### GitLab CI
|
||||
|
||||
```yaml
|
||||
sign:
|
||||
image: registry.stella-ops.io/cli:latest
|
||||
id_tokens:
|
||||
SIGSTORE_ID_TOKEN:
|
||||
aud: sigstore
|
||||
script:
|
||||
- stella sign --mode keyless --image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
```
|
||||
|
||||
## Algorithm Support
|
||||
|
||||
| Algorithm | Status | Use Case |
|
||||
|-----------|--------|----------|
|
||||
| ECDSA P-256 | Preferred | Default, widest compatibility |
|
||||
| Ed25519 | Supported | Better performance, growing adoption |
|
||||
|
||||
Configure preferred algorithm:
|
||||
|
||||
```yaml
|
||||
signer:
|
||||
signing:
|
||||
keyless:
|
||||
algorithms:
|
||||
preferred: "ECDSA_P256"
|
||||
allowed: ["ECDSA_P256", "Ed25519"]
|
||||
```
|
||||
|
||||
## Signed Bundle Format
|
||||
|
||||
The keyless signing produces a DSSE envelope with embedded certificate:
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEi...",
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "",
|
||||
"sig": "MEUCIQD..."
|
||||
}
|
||||
],
|
||||
"certificateChain": [
|
||||
"-----BEGIN CERTIFICATE-----\nMIIC...",
|
||||
"-----BEGIN CERTIFICATE-----\nMIIB..."
|
||||
],
|
||||
"signingMode": "keyless",
|
||||
"signingIdentity": {
|
||||
"issuer": "https://token.actions.githubusercontent.com",
|
||||
"subject": "https://github.com/org/repo/.github/workflows/ci.yml@refs/heads/main"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
Bundles signed with keyless mode can be verified using:
|
||||
|
||||
```bash
|
||||
# Verify a signed bundle
|
||||
stella verify --bundle verdict.json \
|
||||
--expected-issuer "https://token.actions.githubusercontent.com" \
|
||||
--expected-subject "https://github.com/myorg/myrepo/*"
|
||||
```
|
||||
|
||||
The verification process:
|
||||
1. Validates the certificate chain to Fulcio roots
|
||||
2. Verifies the signature using the certificate's public key
|
||||
3. Checks identity claims match expectations
|
||||
4. Optionally validates SCT (Signed Certificate Timestamp)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**OIDC token not available**
|
||||
- Ensure id-token: write permission in GitHub Actions
|
||||
- Ensure id_tokens is configured in GitLab CI
|
||||
- Check ACTIONS_ID_TOKEN_REQUEST_URL environment variable
|
||||
|
||||
**Fulcio returns 401**
|
||||
- OIDC token may have expired (default 5-10 min validity)
|
||||
- Audience mismatch - ensure token is for sigstore
|
||||
- Issuer not trusted by Fulcio instance
|
||||
|
||||
**Certificate chain validation failed**
|
||||
- Root certificate bundle may be outdated
|
||||
- Private Fulcio instance roots not configured
|
||||
- Certificate expired (Fulcio certs are ~10 min TTL)
|
||||
|
||||
### Debug Logging
|
||||
|
||||
Enable verbose logging:
|
||||
|
||||
```bash
|
||||
STELLAOPS_LOG_LEVEL=debug stella sign --mode keyless ...
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Ephemeral keys never persist** - Keys exist only in memory during signing
|
||||
2. **Short-lived certificates** - ~10 minute validity limits exposure window
|
||||
3. **Identity verification** - Always configure expectedIssuers and expectedSubjectPatterns in production
|
||||
4. **SCT validation** - Enable requireSct: true for public Fulcio instances
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Signer Architecture](../architecture.md)
|
||||
- [DSSE Envelope Format](../dsse-format.md)
|
||||
- [CI/CD Gate Integration](../../policy/guides/cicd-gates.md)
|
||||
- [Sigstore Documentation](https://docs.sigstore.dev/)
|
||||
@@ -1,30 +0,0 @@
|
||||
# Signer Implementation Plan
|
||||
|
||||
## Purpose
|
||||
Define a concise, living plan for Signer DSSE signing, predicate registry, and attestor alignment.
|
||||
|
||||
## Active work
|
||||
- `docs/implplan/SPRINT_20260112_015_SIGNER_path_witness_predicate.md`
|
||||
|
||||
## Near-term deliverables
|
||||
- Register canonical path-witness predicate `https://stella.ops/predicates/path-witness/v1` with alias support.
|
||||
- Update predicate classification helpers and allowlists for reachability types.
|
||||
- Expand predicate allowlist tests and integration coverage for DSSE signing.
|
||||
- Maintain cosign-compatible DSSE outputs with deterministic canonical JSON.
|
||||
|
||||
## Dependencies
|
||||
- Authority for OpTok and Proof-of-Entitlement checks.
|
||||
- Crypto provider registry and keyless or KMS backends.
|
||||
- Attestor and Policy verification rules for accepted predicate types.
|
||||
- Path witness contract updates in `docs/contracts/witness-v1.md`.
|
||||
|
||||
## Evidence of completion
|
||||
- Predicate catalog updates in `src/Signer/StellaOps.Signer/StellaOps.Signer.Core/PredicateTypes.cs`.
|
||||
- Tests updated under `src/Signer/__Tests`.
|
||||
- DSSE bundles for path witness validate under Signer allowlist rules.
|
||||
|
||||
## Reference docs
|
||||
- `docs/modules/signer/README.md`
|
||||
- `docs/modules/signer/architecture.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
- `docs/contracts/witness-v1.md`
|
||||
Reference in New Issue
Block a user