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 imagegit://github.com/org/repo@abc123- Git commitfile://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 filefile://vulns.json- Vulnerability reportoci://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 commandstdout- 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
InTotoLink Structure
{
"_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"] }
}
}
Verifying Links Against a Layout
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
-
Always include digests - Materials and products should have cryptographic digests for integrity verification.
-
Capture relevant environment - Include CI/CD context, tool versions, and configuration that affects reproducibility.
-
Use meaningful step names - Step names should be consistent across your pipeline for layout verification.
-
Sign with unique keys per role - Different functionaries (builders, scanners, signers) should use different keys.
-
Submit to transparency log - Use
--rekorto record links in the Sigstore transparency log for tamper evidence. -
Store links with artifacts - Keep signed links alongside the artifacts they describe for audit purposes.
Troubleshooting
Link Validation Fails
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.