Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -533,15 +533,41 @@ Once enabled, all packages carry an [attestation bundle](https://github.com/in-t
When `provenance.slsa: true` is set, Leeway automatically enables all SLSA L3 runtime features to ensure build integrity and artifact distinguishability:

- ✅ **Cache verification**: Downloads are verified against Sigstore attestations
- ✅ **Require attestation**: Missing/invalid attestations trigger local rebuilds (strict mode)
- ✅ **In-flight checksums**: Build artifacts are checksummed during the build to prevent tampering
- ✅ **Docker export mode**: Docker images go through the cache and signing flow (workspace default)

These features are automatically enabled by setting environment variables:
- `LEEWAY_SLSA_CACHE_VERIFICATION=true`
- `LEEWAY_SLSA_REQUIRE_ATTESTATION=true`
- `LEEWAY_ENABLE_IN_FLIGHT_CHECKSUMS=true`
- `LEEWAY_DOCKER_EXPORT_TO_CACHE=true`
- `LEEWAY_SLSA_SOURCE_URI` (set from Git origin)

### SLSA Cache Verification Modes

When cache verification is enabled, Leeway can operate in two modes:

**Permissive Mode** (`LEEWAY_SLSA_REQUIRE_ATTESTATION=false`, default when manually enabling):
- Missing/invalid attestation → Download artifact without verification (with warning)
- Provides graceful degradation and backward compatibility
- Useful during migration or when some artifacts lack attestations

**Strict Mode** (`LEEWAY_SLSA_REQUIRE_ATTESTATION=true`, auto-enabled with `provenance.slsa: true`):
- Missing/invalid attestation → Skip download, build locally with correct attestation
- Enforces strict security and enables self-healing (e.g., cross-PR attestation mismatches)
- Recommended for production environments requiring SLSA L3 compliance

You can override the mode using:
```bash
# Disable strict mode temporarily
leeway build :app --slsa-require-attestation=false

# Or via environment variable
export LEEWAY_SLSA_REQUIRE_ATTESTATION=false
leeway build :app
```

### Configuration Precedence

The Docker export mode follows a clear precedence hierarchy (highest to lowest):
Expand Down
9 changes: 8 additions & 1 deletion cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func addBuildFlags(cmd *cobra.Command) {
cmd.Flags().StringToString("docker-build-options", nil, "Options passed to all 'docker build' commands")
cmd.Flags().Bool("slsa-cache-verification", false, "Enable SLSA verification for cached artifacts")
cmd.Flags().String("slsa-source-uri", "", "Expected source URI for SLSA verification (required when verification enabled)")
cmd.Flags().Bool("slsa-require-attestation", false, "Require SLSA attestations (missing/invalid → build locally)")
cmd.Flags().Bool("in-flight-checksums", false, "Enable checksumming of cache artifacts to prevent TOCTU attacks")
cmd.Flags().String("report", "", "Generate a HTML report after the build has finished. (e.g. --report myreport.html)")
cmd.Flags().String("report-segment", os.Getenv(EnvvarSegmentKey), "Report build events to segment using the segment key (defaults to $LEEWAY_SEGMENT_KEY)")
Expand Down Expand Up @@ -442,6 +443,7 @@ func parseSLSAConfig(cmd *cobra.Command) (*cache.SLSAConfig, error) {
// Get SLSA verification settings from environment variables (defaults)
slsaVerificationEnabled := os.Getenv(EnvvarSLSACacheVerification) == "true"
slsaSourceURI := os.Getenv(EnvvarSLSASourceURI)
requireAttestation := os.Getenv(EnvvarSLSARequireAttestation) == "true"

// CLI flags override environment variables (if cmd is provided)
if cmd != nil {
Expand All @@ -455,6 +457,11 @@ func parseSLSAConfig(cmd *cobra.Command) (*cache.SLSAConfig, error) {
slsaSourceURI = flagValue
}
}
if cmd.Flags().Changed("slsa-require-attestation") {
if flagValue, err := cmd.Flags().GetBool("slsa-require-attestation"); err == nil {
requireAttestation = flagValue
}
}
}

// If verification is disabled, return nil
Expand All @@ -471,7 +478,7 @@ func parseSLSAConfig(cmd *cobra.Command) (*cache.SLSAConfig, error) {
Verification: true,
SourceURI: slsaSourceURI,
TrustedRoots: []string{"https://fulcio.sigstore.dev"},
RequireAttestation: false, // Default: missing attestation → download without verification
RequireAttestation: requireAttestation,
}, nil
}

Expand Down
152 changes: 152 additions & 0 deletions cmd/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ func TestBuildCommandFlags(t *testing.T) {
wantFlag: "in-flight-checksums",
wantVal: false,
},
{
name: "slsa-require-attestation flag default",
args: []string{},
wantFlag: "slsa-require-attestation",
wantVal: false,
},
{
name: "slsa-require-attestation flag enabled",
args: []string{"--slsa-require-attestation"},
wantFlag: "slsa-require-attestation",
wantVal: true,
},
{
name: "slsa-require-attestation flag explicitly disabled",
args: []string{"--slsa-require-attestation=false"},
wantFlag: "slsa-require-attestation",
wantVal: false,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -240,3 +258,137 @@ func TestGetBuildOptsWithInFlightChecksums(t *testing.T) {
})
}
}

func TestParseSLSAConfig(t *testing.T) {
tests := []struct {
name string
envVerification string
envSourceURI string
envRequireAttestation string
flagVerification *bool
flagSourceURI *string
flagRequireAttestation *bool
wantConfig bool
wantRequireAttestation bool
wantError bool
}{
{
name: "verification disabled",
wantConfig: false,
},
{
name: "verification enabled via env, no source URI",
envVerification: "true",
wantError: true,
},
{
name: "verification enabled via env with source URI",
envVerification: "true",
envSourceURI: "https://github.com/gitpod-io/leeway",
wantConfig: true,
},
{
name: "require attestation via env",
envVerification: "true",
envSourceURI: "https://github.com/gitpod-io/leeway",
envRequireAttestation: "true",
wantConfig: true,
wantRequireAttestation: true,
},
{
name: "require attestation via flag overrides env",
envVerification: "true",
envSourceURI: "https://github.com/gitpod-io/leeway",
envRequireAttestation: "false",
flagRequireAttestation: boolPtr(true),
wantConfig: true,
wantRequireAttestation: true,
},
{
name: "flag disables require attestation",
envVerification: "true",
envSourceURI: "https://github.com/gitpod-io/leeway",
envRequireAttestation: "true",
flagRequireAttestation: boolPtr(false),
wantConfig: true,
wantRequireAttestation: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set environment variables
if tt.envVerification != "" {
t.Setenv(EnvvarSLSACacheVerification, tt.envVerification)
}
if tt.envSourceURI != "" {
t.Setenv(EnvvarSLSASourceURI, tt.envSourceURI)
}
if tt.envRequireAttestation != "" {
t.Setenv(EnvvarSLSARequireAttestation, tt.envRequireAttestation)
}

// Create test command
cmd := &cobra.Command{
Use: "build",
Run: func(cmd *cobra.Command, args []string) {},
}
addBuildFlags(cmd)

// Set flags if specified
if tt.flagVerification != nil {
if err := cmd.Flags().Set("slsa-cache-verification", boolToString(*tt.flagVerification)); err != nil {
t.Fatalf("failed to set verification flag: %v", err)
}
}
if tt.flagSourceURI != nil {
if err := cmd.Flags().Set("slsa-source-uri", *tt.flagSourceURI); err != nil {
t.Fatalf("failed to set source URI flag: %v", err)
}
}
if tt.flagRequireAttestation != nil {
if err := cmd.Flags().Set("slsa-require-attestation", boolToString(*tt.flagRequireAttestation)); err != nil {
t.Fatalf("failed to set require attestation flag: %v", err)
}
}

// Test parseSLSAConfig
config, err := parseSLSAConfig(cmd)

if tt.wantError {
if err == nil {
t.Error("expected error but got none")
}
return
}

if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if tt.wantConfig {
if config == nil {
t.Fatal("expected config but got nil")
}
if config.RequireAttestation != tt.wantRequireAttestation {
t.Errorf("expected RequireAttestation=%v, got %v", tt.wantRequireAttestation, config.RequireAttestation)
}
} else {
if config != nil {
t.Errorf("expected nil config but got %+v", config)
}
}
})
}
}

func boolPtr(b bool) *bool {
return &b
}

func boolToString(b bool) string {
if b {
return "true"
}
return "false"
}
4 changes: 4 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ const (
// EnvvarSLSASourceURI configures the expected source URI for SLSA verification
EnvvarSLSASourceURI = "LEEWAY_SLSA_SOURCE_URI"

// EnvvarSLSARequireAttestation requires SLSA attestations (missing/invalid → build locally)
EnvvarSLSARequireAttestation = "LEEWAY_SLSA_REQUIRE_ATTESTATION"

// EnvvarEnableInFlightChecksums enables in-flight checksumming of cache artifacts
EnvvarEnableInFlightChecksums = "LEEWAY_ENABLE_IN_FLIGHT_CHECKSUMS"

Expand Down Expand Up @@ -120,6 +123,7 @@ variables have an effect on leeway:
<light_blue>LEEWAY_DEFAULT_CACHE_LEVEL</> Sets the default cache level for builds. Defaults to "remote".
<light_blue>LEEWAY_SLSA_CACHE_VERIFICATION</> Enables SLSA verification for cached artifacts (true/false).
<light_blue>LEEWAY_SLSA_SOURCE_URI</> Expected source URI for SLSA verification (github.com/owner/repo).
<light_blue>LEEWAY_SLSA_REQUIRE_ATTESTATION</> Require valid attestations; missing/invalid → build locally (true/false).
<light_blue>LEEWAY_ENABLE_IN_FLIGHT_CHECKSUMS</> Enable checksumming of cache artifacts (true/false).
<light_blue>LEEWAY_EXPERIMENTAL</> Enables experimental leeway features and commands.
`),
Expand Down
6 changes: 5 additions & 1 deletion pkg/leeway/cache/remote/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,11 @@ func (s *S3Cache) downloadOriginal(ctx context.Context, p cache.Package, version
// This function tries multiple extensions (.tar.gz, .tar) and their corresponding attestations.
// Returns nil (not an error) when no suitable artifacts are found to allow graceful fallback to local builds.
//
// Future CLI flag consideration: --slsa-require-attestation could set RequireAttestation=true
// Configuration:
// RequireAttestation can be set via:
// - Environment variable: LEEWAY_SLSA_REQUIRE_ATTESTATION=true
// - CLI flag: --slsa-require-attestation
// - Workspace config: Automatically enabled when provenance.slsa=true in WORKSPACE.yaml
func (s *S3Cache) downloadWithSLSAVerification(ctx context.Context, p cache.Package, version, localPath string) error {
log.WithFields(log.Fields{
"package": p.FullName(),
Expand Down
18 changes: 11 additions & 7 deletions pkg/leeway/cache/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@
// The cache system supports SLSA (Supply-chain Levels for Software Artifacts) verification
// for enhanced security. The behavior is controlled by the SLSAConfig.RequireAttestation field:
//
// - RequireAttestation=false (default): Missing attestation → download without verification
// This provides graceful degradation and backward compatibility.
// - RequireAttestation=false (default): Missing/invalid attestation → download without verification
// This provides graceful degradation and backward compatibility. The artifact is downloaded
// and used, but a warning is logged about the missing or invalid attestation.
//
// - RequireAttestation=true: Missing attestation → skip download, allow local build fallback
// This enforces strict security but may impact build performance.
// - RequireAttestation=true: Missing/invalid attestation → skip download, allow local build fallback
// This enforces strict security but may impact build performance. When verification fails,
// the artifact is not downloaded, forcing a local rebuild with proper attestation.
//
// The cache system is designed to never fail builds due to cache issues. When artifacts
// cannot be downloaded (missing, verification failed, network issues), the system gracefully
// falls back to local builds.
//
// Future Evolution:
// A CLI flag like --slsa-require-attestation could be added to set RequireAttestation=true
// for environments that require strict SLSA compliance.
// Configuration:
// RequireAttestation can be controlled via:
// - Environment variable: LEEWAY_SLSA_REQUIRE_ATTESTATION=true
// - CLI flag: --slsa-require-attestation
// - Workspace SLSA config: Automatically enabled when provenance.slsa=true in WORKSPACE.yaml
package cache

import (
Expand Down
9 changes: 9 additions & 0 deletions pkg/leeway/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const (
// EnvvarSLSASourceURI configures the expected source URI for SLSA verification
EnvvarSLSASourceURI = "LEEWAY_SLSA_SOURCE_URI"

// EnvvarSLSARequireAttestation requires SLSA attestations (missing/invalid → build locally)
EnvvarSLSARequireAttestation = "LEEWAY_SLSA_REQUIRE_ATTESTATION"

// EnvvarEnableInFlightChecksums enables in-flight checksumming of cache artifacts
EnvvarEnableInFlightChecksums = "LEEWAY_ENABLE_IN_FLIGHT_CHECKSUMS"
)
Expand Down Expand Up @@ -75,6 +78,7 @@ type WorkspaceProvenance struct {
//
// Sets environment variables as defaults (only if not already set):
// - LEEWAY_SLSA_CACHE_VERIFICATION
// - LEEWAY_SLSA_REQUIRE_ATTESTATION
// - LEEWAY_ENABLE_IN_FLIGHT_CHECKSUMS
// - LEEWAY_DOCKER_EXPORT_TO_CACHE
// - LEEWAY_SLSA_SOURCE_URI (from Git origin)
Expand All @@ -92,6 +96,11 @@ func (w *Workspace) ApplySLSADefaults() {
log.Debug("Auto-enabled: LEEWAY_SLSA_CACHE_VERIFICATION=true")
}

// Auto-enable require attestation for strict SLSA compliance (global feature)
if setEnvDefault(EnvvarSLSARequireAttestation, "true") {
log.Debug("Auto-enabled: LEEWAY_SLSA_REQUIRE_ATTESTATION=true")
}

// Auto-enable in-flight checksumming (global feature)
if setEnvDefault(EnvvarEnableInFlightChecksums, "true") {
log.Debug("Auto-enabled: LEEWAY_ENABLE_IN_FLIGHT_CHECKSUMS=true")
Expand Down
Loading