Files
git.stella-ops.org/docs/modules/attestor/intoto-link-guide.md
StellaOps Bot d486d41a48 save progress
2026-01-03 12:41:57 +02:00

10 KiB

in-toto Link Generation Guide

This guide explains how to use StellaOps to generate in-toto link attestations for supply chain provenance.

Overview

in-toto links record the materials (inputs), products (outputs), and command executed for each step in a supply chain. They are essential for:

  • SLSA compliance - SLSA levels require provenance attestations
  • Supply chain transparency - Prove what went into a build/scan
  • Audit trails - Forensic analysis of build processes
  • Policy enforcement - Verify required steps were executed by authorized functionaries

Quick Start

CLI Usage

Create an in-toto link for a scan operation:

stella attest link \
  --step scan \
  --material "oci://docker.io/library/nginx@sha256:abc123" \
  --product "file://sbom.cdx.json=sha256:def456" \
  --product "file://vulns.json=sha256:789xyz" \
  --command stella scan --image nginx:1.25 \
  --return-value 0 \
  --output scan-link.json

API Usage

curl -X POST http://localhost:5050/api/v1/attestor/links \
  -H "Content-Type: application/json" \
  -d '{
    "stepName": "scan",
    "materials": [
      {"uri": "oci://docker.io/library/nginx@sha256:abc123", "sha256": "abc123..."}
    ],
    "products": [
      {"uri": "file://sbom.cdx.json", "sha256": "def456..."}
    ],
    "command": ["stella", "scan", "--image", "nginx:1.25"],
    "returnValue": 0
  }'

Concepts

Materials

Materials are input artifacts consumed by a supply chain step. Examples:

  • Container images being scanned
  • Source code being built
  • Dependencies being fetched

Materials are specified as URIs with cryptographic digests:

  • oci://registry.example.com/app@sha256:... - Container image
  • git://github.com/org/repo@abc123 - Git commit
  • file://src/main.rs - Local file

Products

Products are output artifacts produced by a supply chain step. Examples:

  • SBOMs generated from scanning
  • Vulnerability reports
  • Built binaries
  • Signed releases

Products are also specified as URIs with digests:

  • file://sbom.cdx.json - SBOM file
  • file://vulns.json - Vulnerability report
  • oci://registry.example.com/app:v1.0 - Built image

Commands

The command captures what was executed during the step:

  • The executable and all arguments
  • Used for audit and reproducibility

By-Products

Additional metadata about step execution:

  • return-value - Exit code of the command
  • stdout - Captured standard output (optional)
  • stderr - Captured standard error (optional)

Environment

Environment variables captured during execution. Useful for:

  • Recording build tool versions
  • Capturing CI/CD context (commit SHA, build number)
  • Documenting configuration

Data Model

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "file://sbom.cdx.json",
      "digest": { "sha256": "..." }
    }
  ],
  "predicateType": "https://in-toto.io/Link/v1",
  "predicate": {
    "name": "scan",
    "command": ["stella", "scan", "--image", "nginx:1.25"],
    "materials": [
      {
        "uri": "oci://docker.io/library/nginx@sha256:...",
        "digest": { "sha256": "..." }
      }
    ],
    "products": [
      {
        "uri": "file://sbom.cdx.json",
        "digest": { "sha256": "..." }
      }
    ],
    "byproducts": {
      "return-value": 0
    },
    "environment": {
      "STELLAOPS_VERSION": "2026.01"
    }
  }
}

DSSE Envelope

Links are wrapped in Dead Simple Signing Envelopes (DSSE):

{
  "payloadType": "application/vnd.in-toto+json",
  "payload": "<base64-encoded-link>",
  "signatures": [
    {
      "keyid": "key-identifier",
      "sig": "<base64-encoded-signature>"
    }
  ]
}

Programmatic Usage

Using LinkBuilder (Fluent API)

using StellaOps.Attestor.Core.InToto;

var link = new LinkBuilder("scan")
    .AddMaterial("oci://nginx:1.25@sha256:abc123", 
        new ArtifactDigests { Sha256 = "abc123..." })
    .AddProduct("file://sbom.cdx.json", 
        new ArtifactDigests { Sha256 = "def456..." })
    .WithCommand("stella", "scan", "--image", "nginx:1.25")
    .WithReturnValue(0)
    .WithEnvironment("CI", "true")
    .Build();

// Serialize to JSON
var json = link.ToJson(indented: true);

Using LinkRecorder (Step Recording)

using StellaOps.Attestor.Core.InToto;

var recorder = serviceProvider.GetRequiredService<ILinkRecorder>();

// Record a step with automatic digest computation
var link = await recorder.RecordStepAsync(
    stepName: "build",
    action: async () =>
    {
        // Execute your build step
        await BuildAsync();
        return 0; // return value
    },
    materials: new[]
    {
        MaterialSpec.WithLocalPath("git://repo", "/path/to/source")
    },
    products: new[]
    {
        ProductSpec.File("/path/to/output/app.tar.gz")
    },
    cancellationToken);

Using IInTotoLinkSigningService

using StellaOps.Attestor.Core.InToto;

var signingService = serviceProvider.GetRequiredService<IInTotoLinkSigningService>();

// Sign a link
var result = await signingService.SignLinkAsync(
    link,
    new InTotoLinkSigningOptions
    {
        KeyId = "my-signing-key",
        SubmitToRekor = true,
        CallerSubject = "build-agent@example.com",
        CallerAudience = "stellaops",
        CallerClientId = "build-system"
    },
    cancellationToken);

// result.Envelope contains the signed DSSE envelope
// result.RekorEntry contains the transparency log entry (if submitted)

Layout Verification

Layouts define the expected steps, their order, and required functionaries (signers).

Defining a Layout

{
  "steps": [
    {
      "name": "build",
      "expectedMaterials": ["git://*"],
      "expectedProducts": ["file://dist/*"],
      "threshold": 1
    },
    {
      "name": "scan",
      "expectedMaterials": ["oci://*"],
      "expectedProducts": ["file://sbom.*", "file://vulns.*"],
      "threshold": 1
    },
    {
      "name": "sign",
      "expectedMaterials": ["file://dist/*"],
      "expectedProducts": ["file://dist/*.sig"],
      "threshold": 2
    }
  ],
  "keys": {
    "builder-key-1": { "allowedSteps": ["build"] },
    "scanner-key-1": { "allowedSteps": ["scan"] },
    "signer-key-1": { "allowedSteps": ["sign"] },
    "signer-key-2": { "allowedSteps": ["sign"] }
  }
}
using StellaOps.Attestor.Core.InToto.Layout;

var verifier = serviceProvider.GetRequiredService<ILayoutVerifier>();

var result = verifier.Verify(
    layout,
    signedLinks,
    trustedKeys);

if (!result.Success)
{
    foreach (var violation in result.Violations)
    {
        Console.WriteLine($"Violation in {violation.StepName}: {violation.Message}");
    }
}

Integration Examples

Scanner Integration

When using the scanner, links can be automatically emitted:

public class ScanService : IInTotoLinkEmitter
{
    public async Task<ScanResult> ScanAsync(string imageRef, CancellationToken ct)
    {
        // Use extension methods to create specs
        var material = imageRef.ToMaterialSpec();
        
        // Perform scan...
        
        // Create products
        var sbomProduct = ProductSpec.File(sbomPath, "sbom.cdx.json");
        
        // Record and sign the link
        var signedLink = await _linkSigningService.RecordAndSignStepAsync(
            stepName: "scan",
            command: new[] { "stella", "scan", "--image", imageRef },
            materials: new[] { material },
            products: new[] { sbomProduct },
            options: new InTotoLinkSigningOptions { KeyId = _keyId },
            cancellationToken: ct);
            
        return new ScanResult { Link = signedLink };
    }
}

CI/CD Pipeline Integration

Example GitHub Actions workflow:

jobs:
  build:
    steps:
      - uses: actions/checkout@v4
      
      - name: Build
        run: make release
        
      - name: Create in-toto link
        run: |
          stella attest link \
            --step build \
            --material "git://${{ github.repository }}@${{ github.sha }}" \
            --product "file://dist/app.tar.gz=$(sha256sum dist/app.tar.gz | cut -d' ' -f1)" \
            --command make release \
            --return-value $? \
            --env GITHUB_SHA --env GITHUB_RUN_ID \
            --key ${{ secrets.SIGNING_KEY }} \
            --rekor \
            --output build-link.json
            
      - name: Upload link
        uses: actions/upload-artifact@v4
        with:
          name: provenance
          path: build-link.json

Best Practices

  1. Always include digests - Materials and products should have cryptographic digests for integrity verification.

  2. Capture relevant environment - Include CI/CD context, tool versions, and configuration that affects reproducibility.

  3. Use meaningful step names - Step names should be consistent across your pipeline for layout verification.

  4. Sign with unique keys per role - Different functionaries (builders, scanners, signers) should use different keys.

  5. Submit to transparency log - Use --rekor to record links in the Sigstore transparency log for tamper evidence.

  6. Store links with artifacts - Keep signed links alongside the artifacts they describe for audit purposes.

Troubleshooting

Error: Product 'file://output.tar' has no digest

Ensure all products have digests computed. Use ProductSpec.WithLocalPath for automatic computation.

Layout Verification Fails

Violation in sign: Threshold not met (1 of 2 required signatures)

The layout requires multiple signatures for the step. Collect more signed links from authorized functionaries.

Signature Verification Fails

Error: Signature verification failed for key 'unknown-key'

Ensure the signing key is in the trusted keys list for layout verification.

References