394 lines
10 KiB
Markdown
394 lines
10 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
### InTotoLink Structure
|
|
|
|
```json
|
|
{
|
|
"_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):
|
|
|
|
```json
|
|
{
|
|
"payloadType": "application/vnd.in-toto+json",
|
|
"payload": "<base64-encoded-link>",
|
|
"signatures": [
|
|
{
|
|
"keyid": "key-identifier",
|
|
"sig": "<base64-encoded-signature>"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Programmatic Usage
|
|
|
|
### Using LinkBuilder (Fluent API)
|
|
|
|
```csharp
|
|
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)
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```yaml
|
|
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
|
|
|
|
### 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.
|
|
|
|
## References
|
|
|
|
- [in-toto Specification](https://github.com/in-toto/attestation)
|
|
- [in-toto Link Predicate](https://github.com/in-toto/attestation/blob/main/spec/predicates/link.md)
|
|
- [SLSA Provenance](https://slsa.dev/provenance/v1)
|
|
- [DSSE Specification](https://github.com/secure-systems-lab/dsse)
|