Files
git.stella-ops.org/docs/ci/dsse-build-flow.md
master 7040984215 Add inline DSSE provenance documentation and Mongo schema
- Introduced a new document outlining the inline DSSE provenance for SBOM, VEX, scan, and derived events.
- Defined the Mongo schema for event patches, including key fields for provenance and trust verification.
- Documented the write path for ingesting provenance metadata and backfilling historical events.
- Created CI/CD snippets for uploading DSSE attestations and generating provenance metadata.
- Established Mongo indexes for efficient provenance queries and provided query recipes for various use cases.
- Outlined policy gates for managing VEX decisions based on provenance verification.
- Included UI nudges for displaying provenance information and implementation tasks for future enhancements.

---

Implement reachability lattice and scoring model

- Developed a comprehensive document detailing the reachability lattice and scoring model.
- Defined core types for reachability states, evidence, and mitigations with corresponding C# models.
- Established a scoring policy with base score contributions from various evidence classes.
- Mapped reachability states to VEX gates and provided a clear overview of evidence sources.
- Documented the event graph schema for persisting reachability data in MongoDB.
- Outlined the integration of runtime probes for evidence collection and defined a roadmap for future tasks.

---

Introduce uncertainty states and entropy scoring

- Created a draft document for tracking uncertainty states and their impact on risk scoring.
- Defined core uncertainty states with associated entropy values and evidence requirements.
- Established a schema for storing uncertainty states alongside findings.
- Documented the risk score calculation incorporating uncertainty and its effect on final risk assessments.
- Provided policy guidelines for handling uncertainty in decision-making processes.
- Outlined UI guidelines for displaying uncertainty information and suggested remediation actions.

---

Add Ruby package inventory management

- Implemented Ruby package inventory management with corresponding data models and storage mechanisms.
- Created C# records for Ruby package inventory, artifacts, provenance, and runtime details.
- Developed a repository for managing Ruby package inventory documents in MongoDB.
- Implemented a service for storing and retrieving Ruby package inventories.
- Added unit tests for the Ruby package inventory store to ensure functionality and data integrity.
2025-11-13 00:20:33 +02:00

9.1 KiB
Raw Blame History

Build-Time DSSE Attestation Walkthrough

Status: Draft — aligns with the November 2025 advisory “Embed in-toto attestations (DSSE-wrapped) into .NET 10/C# builds.”
Owners: Attestor Guild · DevOps Guild · Docs Guild.

This guide shows how to emit signed, in-toto compliant DSSE envelopes for every container build step (scan → package → push) using StellaOps Authority keys. The same primitives power our Signer/Attestor services, but this walkthrough targets developer pipelines (GitHub/GitLab, dotnet builds, container scanners).


1. Concepts refresher

Term Meaning
In-toto Statement JSON document describing what happened (predicate) to which artifact (subject).
DSSE Dead Simple Signing Envelope: wraps the statement, base64 payload, and signatures.
Authority Signer StellaOps client that signs data via file-based keys, HSM/KMS, or keyless Fulcio certs.
PAE Pre-Authentication Encoding: canonical “DSSEv1 ” byte layout that is signed.

Requirements:

  1. .NET 10 SDK (preview) for C# helper code.
  2. Authority key material (dev: file-based Ed25519; prod: Authority/KMS signer).
  3. Artifact digest (e.g., pkg:docker/registry/app@sha256:...) per step.

2. Helpers (drop-in library)

Create src/StellaOps.Attestation with:

public sealed record InTotoStatement(
    string _type,
    IReadOnlyList<Subject> subject,
    string predicateType,
    object predicate);

public sealed record Subject(string name, IReadOnlyDictionary<string,string> digest);

public sealed record DsseEnvelope(
    string payloadType,
    string payload,
    IReadOnlyList<Signature> signatures);

public sealed record Signature(string keyid, string sig);

public interface IAuthoritySigner
{
    Task<string> GetKeyIdAsync(CancellationToken ct = default);
    Task<byte[]> SignAsync(ReadOnlyMemory<byte> pae, CancellationToken ct = default);
}

DSSE helper:

public static class DsseHelper
{
    public static async Task<DsseEnvelope> WrapAsync(
        InTotoStatement statement,
        IAuthoritySigner signer,
        string payloadType = "application/vnd.in-toto+json",
        CancellationToken ct = default)
    {
        var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(statement,
            new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
        var pae = PreAuthEncode(payloadType, payloadBytes);

        var signature = await signer.SignAsync(pae, ct).ConfigureAwait(false);
        var keyId = await signer.GetKeyIdAsync(ct).ConfigureAwait(false);

        return new DsseEnvelope(
            payloadType,
            Convert.ToBase64String(payloadBytes),
            new[] { new Signature(keyId, Convert.ToBase64String(signature)) });
    }

    private static byte[] PreAuthEncode(string payloadType, byte[] payload)
    {
        static byte[] Cat(params byte[][] parts)
        {
            var len = parts.Sum(p => p.Length);
            var buf = new byte[len];
            var offset = 0;
            foreach (var part in parts)
            {
                Buffer.BlockCopy(part, 0, buf, offset, part.Length);
                offset += part.Length;
            }
            return buf;
        }

        var header = Encoding.UTF8.GetBytes("DSSEv1");
        var pt = Encoding.UTF8.GetBytes(payloadType);
        var lenPt = Encoding.UTF8.GetBytes(pt.Length.ToString(CultureInfo.InvariantCulture));
        var lenPayload = Encoding.UTF8.GetBytes(payload.Length.ToString(CultureInfo.InvariantCulture));
        var space = Encoding.UTF8.GetBytes(" ");

        return Cat(header, space, lenPt, space, pt, space, lenPayload, space, payload);
    }
}

Authority signer examples:

/dev (file Ed25519):

public sealed class FileEd25519Signer : IAuthoritySigner, IDisposable
{
    private readonly Ed25519 _ed;
    private readonly string _keyId;

    public FileEd25519Signer(byte[] privateKeySeed, string keyId)
    {
        _ed = new Ed25519(privateKeySeed);
        _keyId = keyId;
    }

    public Task<string> GetKeyIdAsync(CancellationToken ct) => Task.FromResult(_keyId);

    public Task<byte[]> SignAsync(ReadOnlyMemory<byte> pae, CancellationToken ct)
        => Task.FromResult(_ed.Sign(pae.Span.ToArray()));

    public void Dispose() => _ed.Dispose();
}

Prod (Authority KMS):

Reuse the existing StellaOps.Signer.KmsSigner adapter—wrap it behind IAuthoritySigner.


3. Emitting attestations per step

Subject helper:

static Subject ImageSubject(string imageDigest) => new(
    name: imageDigest,
    digest: new Dictionary<string,string>{{"sha256", imageDigest.Replace("sha256:", "", StringComparison.Ordinal)}});

3.1 Scan

var scanStmt = new InTotoStatement(
    _type: "https://in-toto.io/Statement/v1",
    subject: new[]{ ImageSubject(imageDigest) },
    predicateType: "https://stella.ops/predicates/scanner-evidence/v1",
    predicate: new {
        scanner = "StellaOps.Scanner 0.9.0",
        findingsSha256 = scanResultsHash,
        startedAt = startedIso,
        finishedAt = finishedIso,
        rulePack = "lattice:default@2025-11-01"
    });

var scanEnvelope = await DsseHelper.WrapAsync(scanStmt, signer);
await File.WriteAllTextAsync("artifacts/attest-scan.dsse.json", JsonSerializer.Serialize(scanEnvelope));

3.2 Package (SLSA provenance)

var pkgStmt = new InTotoStatement(
    "https://in-toto.io/Statement/v1",
    new[]{ ImageSubject(imageDigest) },
    "https://slsa.dev/provenance/v1",
    new {
        builder = new { id = "stella://builder/dockerfile" },
        buildType = "dockerfile/v1",
        invocation = new { configSource = repoUrl, entryPoint = dockerfilePath },
        materials = new[] { new { uri = repoUrl, digest = new { git = gitSha } } }
    });

var pkgEnvelope = await DsseHelper.WrapAsync(pkgStmt, signer);
await File.WriteAllTextAsync("artifacts/attest-package.dsse.json", JsonSerializer.Serialize(pkgEnvelope));

3.3 Push

var pushStmt = new InTotoStatement(
    "https://in-toto.io/Statement/v1",
    new[]{ ImageSubject(imageDigest) },
    "https://stella.ops/predicates/push/v1",
    new { registry = registryUrl, repository = repoName, tags, pushedAt = DateTimeOffset.UtcNow });

var pushEnvelope = await DsseHelper.WrapAsync(pushStmt, signer);
await File.WriteAllTextAsync("artifacts/attest-push.dsse.json", JsonSerializer.Serialize(pushEnvelope));

4. CI integration

4.1 GitLab example

.attest-template: &attest
  image: mcr.microsoft.com/dotnet/sdk:10.0-preview
  before_script:
    - dotnet build src/StellaOps.Attestation/StellaOps.Attestation.csproj
  variables:
    AUTHORITY_KEY_FILE: "$CI_PROJECT_DIR/secrets/ed25519.key"
    IMAGE_DIGEST: "$CI_REGISTRY_IMAGE@${CI_COMMIT_SHA}"

attest:scan:
  stage: scan
  script:
    - dotnet run --project tools/StellaOps.Attestor.Tool -- step scan --subject "$IMAGE_DIGEST" --out artifacts/attest-scan.dsse.json
  artifacts:
    paths: [artifacts/attest-scan.dsse.json]

attest:package:
  stage: package
  script:
    - dotnet run --project tools/StellaOps.Attestor.Tool -- step package --subject "$IMAGE_DIGEST" --out artifacts/attest-package.dsse.json

attest:push:
  stage: push
  script:
    - dotnet run --project tools/StellaOps.Attestor.Tool -- step push --subject "$IMAGE_DIGEST" --registry "$CI_REGISTRY" --tags "$CI_COMMIT_REF_NAME"

4.2 GitHub Actions snippet

jobs:
  attest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '10.0.x' }
      - name: Build attestor helpers
        run: dotnet build src/StellaOps.Attestation/StellaOps.Attestation.csproj
      - name: Emit scan attestation
        run: dotnet run --project tools/StellaOps.Attestor.Tool -- step scan --subject "${{ env.IMAGE_DIGEST }}" --out artifacts/attest-scan.dsse.json
        env:
          AUTHORITY_KEY_REF: ${{ secrets.AUTHORITY_KEY_REF }}

5. Verification

  • stella attest verify --file artifacts/attest-scan.dsse.json (CLI planned under DSSE-CLI-401-021).
  • Manual validation:
    1. Base64 decode payload → ensure _type = https://in-toto.io/Statement/v1, subject[].digest.sha256 matches artifact.
    2. Recompute PAE and verify signature with the Authority public key.
    3. Attach envelope to Rekor (optional) via existing Attestor API.

6. Storage conventions

Store DSSE files next to build outputs:

artifacts/
  attest-scan.dsse.json
  attest-package.dsse.json
  attest-push.dsse.json

Include the SHA-256 digest of each envelope in promotion manifests (docs/release/promotion-attestations.md) so downstream verifiers can trace chain of custody.


7. References

Keep this file updated alongside DSSE-LIB-401-020 and DSSE-CLI-401-021. When the bench repo publishes sample attestations, link them here.