Skip to content
Open
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
296 changes: 64 additions & 232 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,243 +1,75 @@
# Enterprise Contract CLI - Agent Instructions
# Conforma CLI

## Project Overview
Go CLI for verifying software supply chain artifacts — validates container image signatures,
provenance, and evaluates OPA/Rego policies. Built with `CGO_ENABLED=0`.

The `ec` (Enterprise Contract) CLI is a command-line tool for verifying artifacts and evaluating software supply chain policies. It validates container image signatures, provenance, and enforces policies across various types of software artifacts using Open Policy Agent (OPA)/Rego rules.
## Build & Test

## Essential Commands

### Building
```bash
make build # Build ec binary for current platform (creates dist/ec)
make dist # Build for all supported architectures
make clean # Remove build artifacts
DEBUG_BUILD=1 make build # Build with debugging symbols for gdb/dlv
make debug-run # Run binary with delve debugger (requires debug build)
make build # Build for current platform dist/ec_<os>_<arch>
make test # Run unit + integration + generative tests
make lint # golangci-lint + addlicense + tekton-lint (0 warnings enforced)
make lint-fix # Auto-fix lint issues
make ci # Full CI: test + lint-fix + acceptance
```

### Testing
### Acceptance Tests

```bash
make test # Run all tests (unit, integration, generative)
make acceptance # Run acceptance tests (Cucumber/Gherkin, 20m timeout)
make scenario_<name> # Run single acceptance scenario (replace spaces with underscores)
make feature_<name> # Run all scenarios in a single feature file

# Running specific tests
go test -tags=unit ./internal/evaluator -run TestSpecificFunction
cd acceptance && go test -test.run 'TestFeatures/scenario_name'
make acceptance # Run all (Cucumber/Gherkin via Godog, 20m timeout)
make scenario_<name> # Single scenario (replace spaces with underscores)
make feature_<name> # All scenarios in a feature file
```

### Code Quality
```bash
make lint # Run all linters (golangci-lint, addlicense, tekton-lint)
make lint-fix # Auto-fix linting issues
make ci # Run full CI suite (test + lint-fix + acceptance)
Flags: `-persist` keeps test env for debugging, `-restore` reruns against persisted env,
`-tags=@focus` runs tagged scenarios. Update snapshots: `UPDATE_SNAPS=true make acceptance`.

See `acceptance/README.md` for Testcontainers setup, WireMock stubbing, and snapshot testing details.

**macOS:** Acceptance tests require a Podman machine. Run `./hack/macos/setup-podman-machine.sh`
once for automated setup (creates machine with 4 CPUs, 8GB RAM, configures DNS and networks),
then `./hack/macos/run-acceptance-tests.sh` to run tests. See `hack/macos/README.md` for options
and `hack/macos/TROUBLESHOOTING.md` for detailed debugging.

### Test Tags

Tests use build tags with different timeouts:
- `unit` (10s), `integration` (15s), `generative` (30s), `acceptance` (20m)
- Run specific: `go test -tags=unit ./internal/evaluator -run TestName`

## Key Conventions

- **Multi-module project:** root, `acceptance/`, `tools/` each have their own go.mod.
Run `go mod tidy` in the right module.
- **Debug mode:** `--debug` or `EC_DEBUG=1` preserves `ec-work-*` temp directories for inspection.
- Conventional commits with Jira key encouraged (e.g., `feat(EC-1234): description`).

## CGO and DNS Resolution

Binaries are built with `CGO_ENABLED=0` for portability. This uses Go's native DNS resolver,
which **cannot resolve second-level localhost domains** (e.g., `apiserver.localhost`).
Acceptance tests require `/etc/hosts` entries:

```
127.0.0.1 apiserver.localhost
127.0.0.1 rekor.localhost
```

## Design Documents

Read these before modifying the corresponding areas:

- `internal/evaluator/DESIGN.md` — rule filtering: why two resolvers, two-pass design, scoring precedence, adding filters
- `internal/validate/vsa/DESIGN.md` — VSA: storage backends, DSSE signing rationale, expiration model
- `acceptance/README.md` — acceptance test framework, Testcontainers, WireMock, snapshot testing

## Troubleshooting

System-level issues that surface in acceptance tests:

## Architecture

### Command Structure
Main commands in `cmd/`:
- **validate** - Validate container images, attestations, and policies
- **test** - Test policies against data (similar to conftest)
- **fetch** - Download and inspect attestations
- **inspect** - Examine policy bundles and data
- **track** - Track compliance status
- **sigstore** - Sigstore-related operations
- **initialize** - Initialize policy configurations

### Core Components

#### Policy Evaluation (`internal/evaluator/`)
- **Conftest Evaluator**: Main evaluation engine using OPA/Rego
- **Pluggable Rule Filtering**: Extensible system for filtering which rules run based on:
- Pipeline intentions (build vs release vs production)
- Include/exclude lists (collections, packages, specific rules)
- Custom metadata criteria
- **Result Processing**: Complex rule result filtering with scoring, severity promotion/demotion, and effective time handling

**Key Implementation Details:**
- PolicyResolver interface provides comprehensive policy resolution for pre and post-evaluation filtering
- UnifiedPostEvaluationFilter implements unified filtering logic
- Sophisticated scoring system for include/exclude decisions (collections: 10pts, packages: 10pts per level, rules: +100pts, terms: +100pts)
- Term-based filtering allows fine-grained control (e.g., `tasks.required_untrusted_task_found:clamav-scan`)
- See `.cursor/rules/rule_filtering_process.mdc` and `.cursor/rules/package_filtering_process.mdc` for detailed documentation

#### Attestation Handling (`internal/attestation/`)
- Parsing and validation of in-toto attestations
- SLSA provenance processing (supports both v0.2 and v1.0)
- Integration with Sigstore for signature verification

#### VSA (Verification Summary Attestation) (`internal/validate/vsa/`)
VSA creates cryptographically signed attestations containing validation metadata and policy information after successful image validation.

**Layered Architecture:**
1. Core Interfaces (`interfaces.go`) - Fundamental VSA interfaces
2. Service Layer (`service.go`) - High-level VSA processing orchestration
3. Core Logic (`vsa.go`) - VSA data structures and predicate generation
4. Attestation (`attest.go`) - DSSE envelope creation and signing
5. Storage (`storage*.go`) - Abstract storage backends (local, Rekor)
6. Retrieval (`*_retriever.go`) - VSA retrieval mechanisms
7. Orchestration (`orchestrator.go`) - Complex VSA processing workflows
8. Validation (`validator.go`) - VSA validation with policy comparison
9. Command Interface (`cmd/validate/vsa.go`) - CLI for VSA validation

**Key Features:**
- Policy comparison and equivalence checking
- DSSE envelope signature verification (enabled by default)
- Multiple storage backends (local filesystem, Rekor transparency log)
- VSA expiration checking with configurable thresholds
- Batch validation from application snapshots with parallel processing

See `.cursor/rules/vsa_functionality.mdc` for comprehensive documentation.

#### Input Processing (`internal/input/`)
- Multiple input sources: container images, files, Kubernetes resources
- Automatic detection and parsing of different artifact types

#### Policy Management (`internal/policy/`)
- OCI-based policy bundle loading
- Git repository policy fetching
- Policy metadata extraction and rule discovery

### Key Internal Packages
- `internal/signature/` - Container image signature verification
- `internal/image/` - Container image operations and metadata
- `internal/kubernetes/` - Kubernetes resource processing
- `internal/utils/` - Common utilities and helpers
- `internal/rego/` - Rego policy compilation and execution
- `internal/format/` - Output formatting (JSON, YAML, etc.)

## Module Structure

The project uses multiple Go modules:
- **Root module** - Main CLI application
- **acceptance/** - Acceptance test module with Cucumber integration
- **tools/** - Development tools and utilities

## Testing Strategy

### Test Types
- **Unit tests** (`-tags=unit`, 10s timeout) - Fast isolated tests
- **Integration tests** (`-tags=integration`, 15s timeout) - Component integration
- **Generative tests** (`-tags=generative`, 30s timeout) - Property-based testing
- **Acceptance tests** (20m timeout) - End-to-end Cucumber scenarios with real artifacts
- Use `-persist` flag to keep test environment after execution for debugging
- Use `-restore` to run tests against persisted environment
- Use `-tags=@focus` to run specific scenarios

### Acceptance Test Framework
- Uses Cucumber/Gherkin syntax for feature definitions in `features/` directory
- Steps implemented in Go using Godog framework
- Self-contained test environment using Testcontainers
- WireMock for stubbing HTTP APIs (Kubernetes apiserver, Rekor)
- Snapshots stored in `features/__snapshots__/` (update with `UPDATE_SNAPS=true`)

## Development Environment

### Required Tools
- Go 1.24.4+
- Make
- Podman/Docker for container operations
- Node.js for tekton-lint

### Troubleshooting Common Issues

1. **Go checksum mismatch**
```bash
go env -w GOPROXY='https://proxy.golang.org,direct'
```

2. **Container failures** - Ensure podman runs as user service, not system service
```bash
systemctl status podman.socket podman.service
systemctl disable --now podman.socket podman.service
systemctl enable --user --now podman.socket podman.service
```

3. **Too many containers** - Increase inotify watches
```bash
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
```

4. **Key limits** - Increase max keys
```bash
echo kernel.keys.maxkeys=1000 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
```

5. **Host resolution** - Add to `/etc/hosts`:
```
127.0.0.1 apiserver.localhost
127.0.0.1 rekor.localhost
```

## Key Configuration

### Policy Sources
Policies can be loaded from:
- OCI registries: `oci::quay.io/repo/policy:tag`
- Git repositories: `git::https://github.com/repo//path`
- Local files/directories

### Debug Mode
- Use `--debug` flag or `EC_DEBUG=1` environment variable
- Debug mode preserves temporary `ec-work-*` directories for inspection

## Special Considerations

### CGO and DNS Resolution
Binaries are built with `CGO_ENABLED=0` for OS compatibility, which affects DNS resolution. The Go native resolver cannot resolve second-level localhost domains like `apiserver.localhost`, requiring manual `/etc/hosts` entries for acceptance tests.

### Multi-Architecture Support
The build system supports all major platforms and architectures. Use `make dist` to build for all supported targets or `make dist/ec_<os>_<arch>` for specific platforms.

### Policy Rule Filtering System
The evaluation system includes sophisticated rule filtering that operates at multiple levels:

#### Pre-Evaluation Filtering (Package Level)
1. **Pipeline Intention Filtering** (ECPolicyResolver only)
- When `pipeline_intention` is set: only include packages with matching metadata
- When not set: only include general-purpose rules (no pipeline_intention metadata)

2. **Rule-by-Rule Evaluation**
- Each rule is scored against include/exclude criteria
- Scoring system: collections (10pts), packages (10pts/level), rules (+100pts), terms (+100pts)
- Higher score determines inclusion/exclusion

3. **Package-Level Determination**
- If ANY rule in package is included → Package is included
- Package runs through conftest evaluation

#### Post-Evaluation Filtering (Result Level)
- UnifiedPostEvaluationFilter processes all results using same PolicyResolver
- Filters warnings, failures, exceptions, skipped results
- Applies severity logic (promotion/demotion based on metadata)
- Handles effective time filtering (future-effective failures → warnings)

#### Term-Based Filtering
Terms provide fine-grained control over specific rule instances:
- Example: `tasks.required_untrusted_task_found:clamav-scan` (scores 210pts)
- Can override general patterns like `tasks.*` (10pts)
- Terms are extracted from result metadata during filtering

### Working with Rule Filtering Code
When modifying policy evaluation or filtering logic:
1. Read `.cursor/rules/package_filtering_process.mdc` for architecture overview
2. Read `.cursor/rules/rule_filtering_process.mdc` for detailed filtering flow
3. Main filtering code is in `internal/evaluator/filters.go`
4. Integration point is in `internal/evaluator/conftest_evaluator.go`

### Working with VSA Code
When modifying VSA functionality:
1. Read `.cursor/rules/vsa_functionality.mdc` for complete documentation
2. Understand the layered architecture (9 layers from interfaces to CLI)
3. VSA code is in `internal/validate/vsa/` directory
4. CLI implementation in `cmd/validate/vsa.go`
5. Signature verification is enabled by default and implemented via DSSE envelopes

## Additional Documentation

For detailed implementation guides, see:
- `.cursor/rules/package_filtering_process.mdc` - Pluggable rule filtering system
- `.cursor/rules/rule_filtering_process.mdc` - Complete rule filtering process
- `.cursor/rules/vsa_functionality.mdc` - VSA architecture and workflows
| Problem | Fix |
|---------|-----|
| Go checksum mismatch | `go env -w GOPROXY='https://proxy.golang.org,direct'` |
| Podman container failures | Use user service: `systemctl enable --user --now podman.socket` |
| Too many containers (inotify) | `echo fs.inotify.max_user_watches=524288 \| sudo tee -a /etc/sysctl.conf` |
| Key limit errors | `echo kernel.keys.maxkeys=1000 \| sudo tee -a /etc/sysctl.conf` |
35 changes: 35 additions & 0 deletions internal/evaluator/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Rule Filtering Design

## Why Two PolicyResolvers

`ECPolicyResolver` handles the full Conforma evaluation path including pipeline intention
filtering — rules declare what pipeline type they apply to (build, release, production) via
metadata, and the resolver matches against the configured intention. This is the default for
`ec validate`.

`IncludeExcludePolicyResolver` skips pipeline intention filtering entirely. It exists for
`ec test` (conftest-compatible mode) where pipeline intentions don't apply. Use this resolver
when evaluating policies outside the Conforma pipeline context.

## Filtering Happens Twice

Pre-evaluation filtering selects which *packages* to load into OPA. Post-evaluation filtering
decides which *results* to keep. Both use the same `PolicyResolver` instance so decisions are
consistent. This two-pass design exists because OPA evaluates all rules in a loaded package —
you can't selectively run individual rules within a package, only control which packages load.

## Scoring Determines Precedence

When include and exclude criteria conflict, the more-specific pattern wins via scoring.
The scoring rules are documented in the `LegacyScore` function docstring in `filters.go`.
The key insight: terms (+100pts) and specific rules (+100pts) always override collection-level
(10pts) or wildcard (1pt) patterns. This lets operators exclude a broad category while
including specific exceptions, or vice versa.

## Adding a New Filter

Follow the pattern in `IncludeExcludePolicyResolver`: embed `basePolicyResolver`, implement
`processPackage` with your filtering logic, and delegate to `baseResolvePolicy`. The
`basePolicyResolver` provides the shared scoring and package inclusion logic.

Key files: `filters.go` (all filtering logic), `conftest_evaluator.go` (integration point).
28 changes: 28 additions & 0 deletions internal/validate/vsa/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# VSA Design

## Purpose

A VSA (Verification Summary Attestation) is a cryptographically signed record that a specific
image was validated against a specific policy at a specific time. It enables skipping
re-validation when the same image+policy combination has already been verified and the VSA
hasn't expired.

## Why Multiple Storage Backends

VSAs can be stored locally (filesystem) or in Rekor (transparency log). Local storage is for
development and testing. Rekor storage provides tamper-evident public auditability — once a
VSA is logged, it can't be silently modified or deleted. The backend is selected at the CLI
level; the core logic is backend-agnostic via the storage interfaces in `storage.go`.

## DSSE Signing

VSAs are wrapped in DSSE (Dead Simple Signing Envelope) with signature verification enabled
by default. This was a deliberate security decision — unsigned VSAs would allow an attacker
to forge validation results and skip policy enforcement. The signing key is the same key used
for the original image validation.

## Expiration

VSAs have configurable expiration. This ensures that policy changes eventually take effect
even for previously-validated images. When a VSA is expired, the image must be re-validated
against the current policy. The threshold is set by the caller (typically 24h in production).
Loading