diff --git a/_vale/config/vocabularies/Docker/accept.txt b/_vale/config/vocabularies/Docker/accept.txt index 88eedf3531e4..ad6a182c3bc5 100644 --- a/_vale/config/vocabularies/Docker/accept.txt +++ b/_vale/config/vocabularies/Docker/accept.txt @@ -11,6 +11,7 @@ Artifactory armhf auditable autolock +[Aa]llowlist(ing)? Azure Azure AD bootup @@ -169,6 +170,7 @@ Qualcomm Quickview rebalance reimplement +Rego Rekor ROCm rollback @@ -180,6 +182,7 @@ scrollable SELinux Slack snapshotters? +Sigstore Snyk Solr SonarQube diff --git a/content/manuals/build/checks.md b/content/manuals/build/checks.md index 90cfda90735c..cb9c6bbde6ea 100644 --- a/content/manuals/build/checks.md +++ b/content/manuals/build/checks.md @@ -1,12 +1,7 @@ --- title: Checking your build configuration linkTitle: Build checks -params: - sidebar: - badge: - color: green - text: New -weight: 30 +weight: 20 description: Learn how to use build checks to validate your build configuration. keywords: build, buildx, buildkit, checks, validate, configuration, lint --- diff --git a/content/manuals/build/policies/_index.md b/content/manuals/build/policies/_index.md new file mode 100644 index 000000000000..3bf637934122 --- /dev/null +++ b/content/manuals/build/policies/_index.md @@ -0,0 +1,160 @@ +--- +title: Validating build inputs with policies +linkTitle: Validating builds +description: Secure your Docker builds by validating images, Git repositories, and dependencies with build policies +keywords: build policies, opa, rego, docker security, supply chain, attestations +weight: 70 +params: + sidebar: + badge: + color: green + text: New +--- + +Building with Docker often involves downloading remote resources of some kind. +These external dependencies are called **build inputs**: Docker images, Git +repositories, remote files, and other artifacts your build pulls in. + +For example: + +- Pulling images from a registry +- Cloning a source code repository +- Fetching files from a server over HTTPS + +When consuming build inputs, it's a good idea to verify the contents are what +you expect them to be. One way to do this is to use the `--checksum` option for +the `ADD` Dockerfile instruction. This lets you verify the SHA256 checksum of a +remote resource when pulling it into a build: + +```dockerfile +ADD --checksum=sha256:c0ff3312345… https://example.com/archive.tar.gz / +``` + +If the remote `archive.tar.gz` file does not match the checksum that the +Dockerfile expects, the build fails. + +Checksums verify that content matches what you expect, but only for the `ADD` +instruction. They don't tell you anything about where the content came from or +how it was produced. You can't use checksums to enforce constraints like +"images must be signed" or "dependencies must come from approved sources." + +## Prerequisites + +Build policies require: + +- **Buildx 0.31.0 or later** - Check your version: `docker buildx version` +- **BuildKit 0.26.0 or later** - Verify with: `docker buildx inspect +--bootstrap` + +If you're using Docker Desktop, ensure you're on a version that includes these +updates. + +## Build policies + +Buildx version 0.31.0 added support for **build policies**. Build policies are +rules for securing your Docker build supply chain, and help protect against +upstream compromises, malicious dependencies, and unauthorized modifications to +your build inputs. + +With build policies, you can perform extended verifications on inputs, such as: + +- Docker images must use digest references (not tags alone) +- Images must have provenance attestations and cosign signatures +- Git tags are signed by maintainers with a PGP public key +- All remote artifacts must use HTTPS and include a checksum for verification + +Build policies are defined in a declarative policy language, called Rego, +created for the [Open Policy Agent (OPA)](https://www.openpolicyagent.org/). +The following example shows a minimal build policy in Rego. + +```rego {title="Dockerfile.rego"} +package docker + +default allow := false + +# Allow any local inputs for this build +allow if input.local + +# Allow images, but only if they have provenance attestations +allow if { + input.image.hasProvenance +} + +decision := {"allow": allow} +``` + +If the Dockerfile associated with this policy references an image with no +provenance attestation in a `FROM` instruction, the policy would be violated +and the build would fail. + +## How policies work + +When you run `docker buildx build`, buildx: + +1. Resolves all build inputs (images, Git repos, HTTP downloads) +2. Looks for a policy file matching your Dockerfile name (e.g., + `Dockerfile.rego`) +3. Evaluates each input against the policy before the build starts +4. Allows the build to proceed only if all inputs pass the policy + +Policies are written in Rego (Open Policy Agent's policy language). You don't +need to be a Rego expert - the [Introduction](./intro.md) tutorial teaches you +everything needed. + +Policy files live alongside your Dockerfile: + +```text +project/ +├── Dockerfile +├── Dockerfile.rego +└── src/ +``` + +No additional configuration is needed - buildx automatically finds and loads +the policy when you build. + +## Use cases + +Build policies help you enforce security and compliance requirements on your +Docker builds. Common scenarios where policies provide value: + +### Enforce base image standards + +Require all production Dockerfiles to use specific, approved base images with +digest references. Prevent developers from using arbitrary images that haven't +been vetted by your security team. + +### Validate third-party dependencies + +When your build downloads files, libraries, or tools from the internet, verify +they come from trusted sources and match expected checksums or signatures. This +protects against supply chain attacks where an upstream dependency is +compromised. + +### Ensure signed releases + +Require that all dependencies - whether container images or downloaded files - +have valid signatures from trusted parties. Use GPG signatures, Sigstore +attestations, or GitHub attestations to verify authenticity. + +### Meet compliance requirements + +Some regulatory frameworks require evidence that you validate your build +inputs. Build policies give you an auditable, declarative way to demonstrate +you're checking dependencies against security standards. + +### Separate development and production rules + +Apply stricter validation for production builds while allowing more flexibility +during development. The same policy file can contain conditional rules based on +build context or target. + +## Get started + +Ready to start writing policies? The [Introduction](./intro.md) tutorial walks +you through creating your first policy and teaches the Rego basics you need. + +For practical usage guidance, see [Using build policies](./usage.md). + +For practical examples you can copy and adapt, see the [Example +policies](./examples.md) library. diff --git a/content/manuals/build/policies/built-ins.md b/content/manuals/build/policies/built-ins.md new file mode 100644 index 000000000000..625475dde397 --- /dev/null +++ b/content/manuals/build/policies/built-ins.md @@ -0,0 +1,201 @@ +--- +title: Built-in functions +linkTitle: Built-in functions +description: Buildx includes built-in helper functions to make writing policies easier +keywords: build policies, built-in functions, rego functions, signature verification, policy helpers +weight: 90 +--- + +Buildx provides built-in functions to extend Rego policies with Docker-specific +operations like loading local files, verifying Git signatures, and pinning +image digests. + +> [!NOTE] +> These are Buildx-specific functions, distinct from [Rego's standard built-in +> functions](https://www.openpolicyagent.org/docs/latest/policy-reference/#built-in-functions). +> Use both Buildx functions (documented here) and Rego built-ins (like +> `startswith`, `regex.match`) together in your policies. + +## Available built-in functions + +Buildx provides four custom built-in functions for policy development: + +### `print(...)` + +Outputs debug information during policy evaluation. + +**Parameters:** + +- Any number of values to print + +**Returns:** The values (pass-through) + +**Example:** + +```rego +allow if { + input.image.repo == "alpine" + print("Allowing alpine image:", input.image.tag) +} +``` + +Debug output appears when building with `--progress=plain`. + +--- + +### `load_json(filename)` + +Loads and parses JSON data from local files in the build context. + +**Parameters:** + +- `filename` (string) - Path to JSON file relative to policy directory + +**Returns:** Parsed JSON data as Rego value + +**Example:** + +```rego +# Load approved versions from external file +approved_versions = load_json("versions.json") + +allow if { + input.image.repo == "alpine" + some version in approved_versions.alpine + input.image.tag == version +} +``` + +**File structure:** + +```text +project/ +├── Dockerfile +├── Dockerfile.rego +└── versions.json +``` + +**versions.json:** + +```json +{ + "alpine": ["3.19", "3.20"], + "golang": ["1.21", "1.22"] +} +``` + +The JSON file must be in the same directory as the policy or in a +subdirectory accessible from the policy location. + +--- + +### `verify_git_signature(git_object, keyfile)` + +Verifies PGP signatures on Git commits or tags. + +**Parameters:** + +- `git_object` (object) - Either `input.git.commit` or `input.git.tag` +- `keyfile` (string) - Path to PGP public key file (relative to policy + directory) + +**Returns:** Boolean - `true` if signature is valid, `false` otherwise + +**Example:** + +```rego +# Require signed Git tags +allow if { + input.git.tagName != "" + verify_git_signature(input.git.tag, "maintainer.asc") +} + +# Require signed commits +allow if { + input.git.commit + verify_git_signature(input.git.commit, "keys/team.asc") +} +``` + +**Directory structure:** + +```text +project/ +├── Dockerfile.rego +└── maintainer.asc # PGP public key +``` + +Or with subdirectory: + +```text +project/ +├── Dockerfile.rego +└── keys/ + ├── maintainer.asc + └── team.asc +``` + +**Obtaining public keys:** + +```console +$ gpg --export --armor user@example.com > maintainer.asc +``` + +--- + +### `pin_image(image_object, digest)` + +Pins an image to a specific digest, overriding the tag-based reference. Use +this to force builds to use specific image versions. + +**Parameters:** + +- `image_object` (object) - Must be `input.image` (the current image being + evaluated) +- `digest` (string) - Target digest in format `sha256:...` + +**Returns:** Boolean - `true` if pinning succeeds + +**Example:** + +```rego +# Pin alpine 3.19 to specific digest +alpine_3_19_digest = "sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412" + +allow if { + input.image.repo == "alpine" + input.image.tag == "3.19" + pin_image(input.image, alpine_3_19_digest) +} +``` + +**Automatic digest replacement:** + +```rego +# Replace old digests with patched versions +replace_map = { + "3.22.0": "3.22.2", + "3.22.1": "3.22.2", +} + +alpine_digests = { + "3.22.0": "sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715", + "3.22.2": "sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412", +} + +allow if { + input.image.repo == "alpine" + some old_version, new_version in replace_map + input.image.checksum == alpine_digests[old_version] + print("Replacing", old_version, "with", new_version) + pin_image(input.image, alpine_digests[new_version]) +} +``` + +This pattern automatically upgrades old image versions to patched releases. + +## Next steps + +- Browse complete examples: [Example policies](./examples.md) +- Learn policy development workflow: [Using build policies](./usage.md) +- Reference input fields: [Input reference](./inputs.md) diff --git a/content/manuals/build/policies/debugging.md b/content/manuals/build/policies/debugging.md new file mode 100644 index 000000000000..a8c37a72f2cc --- /dev/null +++ b/content/manuals/build/policies/debugging.md @@ -0,0 +1,183 @@ +--- +title: Debugging build policies +linkTitle: Debugging +description: Debug policies during development with inspection and testing tools +keywords: build policies, debugging, policy troubleshooting, log-level, policy eval, rego debugging +weight: 70 +--- + +When policies don't work as expected, use the tools available to inspect policy +evaluation and understand what's happening. This guide covers the debugging +techniques and common gotchas. + +## Quick reference + +Essential debugging commands: + +```console +# See complete input data during builds (recommended) +$ docker buildx build --progress=plain --policy log-level=debug . + +# See policy checks and decisions +$ docker buildx build --progress=plain . + +# Explore input structure for different sources +$ docker buildx policy eval --print . +$ docker buildx policy eval --print https://github.com/org/repo.git +$ docker buildx policy eval --print docker-image://alpine:3.19 + +# Test if policy allows a source +$ docker buildx policy eval . +``` + +## Policy output with --progress=plain + +To see policy evaluation during builds, use `--progress=plain`: + +```console +$ docker buildx build --progress=plain . +``` + +This shows all policy checks, decisions, and `print()` output. Without +`--progress=plain`, policy evaluation is silent unless there's an error. + +```plaintext +#1 loading policies Dockerfile.rego +#1 0.010 checking policy for source docker-image://alpine:3.19 (linux/arm64) +#1 0.011 Dockerfile.rego:8: image: {"ref":"alpine:3.19","repo":"alpine","tag":"3.19"} +#1 0.012 policy decision for source docker-image://alpine:3.19: ALLOW +``` + +If a policy denies a source, you'll see: + +```text +#1 0.012 policy decision for source docker-image://nginx:latest: DENY +ERROR: source "docker-image://nginx:latest" not allowed by policy +``` + +## Debug logging + +For detailed debugging, add `--policy log-level=debug` to see the full input +JSON, unresolved fields, and policy responses: + +```console +$ docker buildx build --progress=plain --policy log-level=debug . +``` + +This shows significantly more information than the default level, including the +complete input structure for each source without needing `print()` statements +in your policy. + +Complete input JSON: + +```text +#1 0.007 policy input: { +#1 0.007 "env": { +#1 0.007 "filename": "." +#1 0.007 }, +#1 0.007 "image": { +#1 0.007 "ref": "docker.io/library/alpine:3.19", +#1 0.007 "host": "docker.io", +#1 0.007 "repo": "alpine", +#1 0.007 "fullRepo": "docker.io/library/alpine", +#1 0.007 "tag": "3.19", +#1 0.007 "platform": "linux/arm64", +#1 0.007 "os": "linux", +#1 0.007 "arch": "arm64" +#1 0.007 } +#1 0.007 } +``` + +Unresolved fields: + +```text +#1 0.007 unknowns for policy evaluation: [input.image.checksum input.image.labels input.image.user input.image.volumes input.image.workingDir input.image.env input.image.hasProvenance input.image.signatures] +``` + +Policy response: + +```text +#1 0.008 policy response: map[allow:true] +``` + +This detailed output is invaluable for understanding exactly what data your +policy receives and which fields are not yet resolved. Use debug logging when +developing policies to avoid needing extensive `print()` statements. + +## Conditional debugging with print() + +While `--policy log-level=debug` shows all input data automatically, the +`print()` function is useful for debugging specific rule logic and conditional +flows: + +```rego +allow if { + input.image + print("Checking image:", input.image.repo, "isCanonical:", input.image.isCanonical) + input.image.repo == "alpine" + input.image.isCanonical +} +``` + +Use `print()` to debug conditional logic within rules or track which rules are +evaluating. For general input inspection during development, use `--policy +log-level=debug` instead - it requires no policy modifications. + +> [!NOTE] +> Print statements only execute when their containing rule evaluates. A rule +> like `allow if { input.image; print(...) }` only prints for image inputs, +> not for Git repos, HTTP downloads, or local files. + +## Common issues + +### Full repository path vs repo name + +**Symptom:** Policy checking repository names doesn't match as expected. + +**Cause:** Docker Hub images use `input.image.repo` for the short name +(`"alpine"`) but `input.image.fullRepo` includes the full path +(`"docker.io/library/alpine"`). + +**Solution:** + +```rego +# Match just the repo name (works for Docker Hub and other registries) +allow if { + input.image + input.image.repo == "alpine" +} + +# Or match the full repository path +allow if { + input.image + input.image.fullRepo == "docker.io/library/alpine" +} +``` + +### Policy evaluation happens multiple times + +**Symptom:** Build output shows the same source evaluated multiple times. + +**Cause:** BuildKit may evaluate policies at different stages (reference +resolution, actual pull) or for different platforms. + +**This is normal behavior.** Policies should be idempotent (produce same result +each time for the same input). + +### Fields missing with policy eval + +**Symptom:** `docker buildx policy eval --print` doesn't show expected fields +like `hasProvenance`, `labels`, or `checksum`. + +**Cause:** `policy eval` doesn't fetch sources, so many fields remain +unresolved. + +**Solution:** Use actual builds with `--progress=plain` to see complete field +data. See [Using build policies](./usage.md#viewing-input-structure) for +details on field availability. + +## Next steps + +- See complete field reference: [Input reference](./inputs.md) +- Review example policies: [Examples](./examples.md) +- Learn policy usage patterns: [Using build policies](./usage.md) diff --git a/content/manuals/build/policies/examples.md b/content/manuals/build/policies/examples.md new file mode 100644 index 000000000000..3b673cca0478 --- /dev/null +++ b/content/manuals/build/policies/examples.md @@ -0,0 +1,585 @@ +--- +title: Example policies +linkTitle: Examples +description: Browse the example library of build policies +keywords: build policies, policy examples, rego examples, docker security, registry allowlist +weight: 50 +--- + +This page provides complete, working policy examples you can copy and adapt. +The examples are organized into two sections: getting started policies for +quick adoption, and production templates for comprehensive security. + +If you're new to policies, start with the tutorials: +[Introduction](./intro.md), [Image validation](./validate-images.md), and [Git +validation](./validate-git.md). Those pages teach individual techniques. This +page shows complete policies combining those techniques. + +## How to use these examples + +1. **Copy** the policy code into a `Dockerfile.rego` file next to your + Dockerfile +2. **Customize** any TODO comments with your specific values +3. **Test** by running `docker build .` and verifying the policy works as + expected +4. **Refine** based on your team's needs + +### Using examples with bake + +These policies work with both `docker buildx build` and `docker buildx bake`. +For bake, place the policy alongside your Dockerfile and it loads +automatically. To use additional policies: + +```hcl +target "default" { + dockerfile = "Dockerfile" + policy = ["extra.rego"] +} +``` + +See the [Usage guide](./usage.md) for complete bake integration details. + +## Getting started + +These policies work immediately with minimal or no customization. Use them to +adopt policies quickly and demonstrate value to your team. + +### Development-friendly baseline + +A permissive policy that allows typical development workflows while blocking +obvious security issues. + +```rego +package docker + +default allow := false + +allow if input.local +allow if input.git + +# Allow common public registries +allow if { + input.image.host == "docker.io" # Docker Hub +} + +allow if { + input.image.host == "ghcr.io" # GitHub Container Registry +} + +allow if { + input.image.host == "dhi.io" # Docker Hardened Images +} + +# Require HTTPS for all downloads +allow if { + input.http.schema == "https" +} + +decision := {"allow": allow} +``` + +This policy allows local and Git contexts, images from Docker Hub, GitHub +Container Registry, and [Docker Hardened Images](/dhi/), and `ADD` downloads +over HTTPS. It blocks HTTP downloads and non-standard registries. + +When to use: Starting point for teams new to policies. Provides basic security +without disrupting development workflows. + +### Registry allowlist + +Control which registries your builds can pull images from. + +```rego +package docker + +default allow := false + +allow if input.local + +# TODO: Add your internal registry hostname +allowed_registries := ["docker.io", "ghcr.io", "dhi.io", "registry.company.com"] + +allow if { + input.image.host in allowed_registries +} + +# Allow mirrored DHI images from Docker Hub (DHI Enterprise users) +# TODO: Replace with your organization namespace +allow if { + input.image.host == "docker.io" + startswith(input.image.repo, "myorg/dhi-") +} + +deny_msg contains msg if { + not allow + input.image + msg := sprintf("registry %s is not in the allowlist", [input.image.host]) +} + +decision := {"allow": allow, "deny_msg": deny_msg} +``` + +This policy restricts image pulls to approved registries. Customize and add +your internal registry to the list. If you have a DHI Enterprise subscription +and have mirrored Docker Hardened Images to Docker Hub, add a rule to allow +images from your organization's namespace. + +When to use: Enforce corporate policies about approved image sources. Prevents +developers from using arbitrary public registries. + +### Pin base images to digests + +Require digest references for reproducible builds. + +```rego +package docker + +default allow := false + +allow if input.local + +# Require digest references for all images +allow if { + input.image.isCanonical +} + +deny_msg contains msg if { + not allow + input.image + msg := sprintf("image %s must use digest reference (e.g., @sha256:...)", [input.image.ref]) +} + +decision := {"allow": allow, "deny_msg": deny_msg} +``` + +This policy requires images use digest references like +`alpine@sha256:abc123...` instead of tags like `alpine:3.19`. Digests are +immutable - the same digest always resolves to the same image content. + +When to use: Ensure build reproducibility. Prevents builds from breaking when +upstream tags are updated. Required for compliance in some environments. + +### Control external dependencies + +Pin specific versions of dependencies downloaded during builds. + +```rego +package docker + +default allow := false + +allow if input.local + +# Allow any image (add restrictions as needed) +allow if input.image + +# TODO: Add your allowed Git repositories and tags +allowed_repos := { + "https://github.com/moby/buildkit.git": ["v0.26.1", "v0.27.0"], +} +# Only allow Git input from allowed_repos +allow if { + some repo, versions in allowed_repos + input.git.remote == repo + input.git.tagName in versions +} + +# TODO: Add your allowed downloads +allow if { + input.http.url == "https://example.com/app-v1.0.tar.gz" +} + +decision := {"allow": allow} +``` + +This policy creates allowlists for external dependencies. Add your Git +repositories with approved version tags, and URLs. + +When to use: Control which external dependencies can be used in builds. +Prevents builds from pulling arbitrary versions or unverified downloads. + +## Production templates + +These templates demonstrate comprehensive security patterns. They require +customization but show best practices for production environments. + +### Image attestation and provenance + +Require images have provenance attestations from trusted builders. + +```rego +package docker + +default allow := false + +allow if input.local + +# TODO: Add your repository names +allowed_repos := ["myorg/backend", "myorg/frontend", "myorg/worker"] + +# Production images need full attestations +allow if { + some repo in allowed_repos + input.image.repo == repo + input.image.hasProvenance + some sig in input.image.signatures + trusted_github_builder(sig, repo) +} + +# Helper to validate GitHub Actions build from main branch +trusted_github_builder(sig, repo) if { + sig.signer.certificateIssuer == "CN=sigstore-intermediate,O=sigstore.dev" + sig.signer.issuer == "https://token.actions.githubusercontent.com" + startswith(sig.signer.buildSignerURI, sprintf("https://github.com/myorg/%s/.github/workflows/", [repo])) + sig.signer.sourceRepositoryRef == "refs/heads/main" + sig.signer.runnerEnvironment == "github-hosted" +} + +# Allow Docker Hardened Images with built-in attestations +allow if { + input.image.host == "dhi.io" + input.image.isCanonical + input.image.hasProvenance +} + +# Allow official base images with digests +allow if { + input.image.repo == "alpine" + input.image.host == "docker.io" + input.image.isCanonical +} + +decision := {"allow": allow} +``` + +This template validates that your application images have provenance +attestations, and were built by GitHub Actions from your main branch. Docker +Hardened Images are allowed when using digests since they include comprehensive +attestations by default. Other base images must use digests. + +Customize: + +- Replace `allowed_repos` with your image names +- Update the organization name in `trusted_github_builder()` +- Add rules for other base images you use + +When to use: Enforce supply chain security for production deployments. Ensures +images are built by trusted CI/CD pipelines with auditable provenance. + +### Signed Git releases + +Enforce signed tags from trusted maintainers for Git dependencies. + +```rego +package docker + +default allow := false + +allow if input.local + +allow if input.image + +# TODO: Replace with your repository URL +is_buildkit if { + input.git.remote == "https://github.com/moby/buildkit.git" +} + +is_version_tag if { + is_buildkit + regex.match(`^v[0-9]+\.[0-9]+\.[0-9]+$`, input.git.tagName) +} + +# Version tags must be signed +allow if { + is_version_tag + input.git.tagName != "" + verify_git_signature(input.git.tag, "maintainers.asc") +} + +# Allow unsigned refs for development +allow if { + is_buildkit + not is_version_tag +} + +decision := {"allow": allow} +``` + +This template requires production release tags to be signed by trusted +maintainers. Development branches and commits can be unsigned. + +Setup: + +1. Export maintainer PGP public keys to `maintainers.asc`: + ```console + $ gpg --export --armor user1@example.com user2@example.com > maintainers.asc + ``` +2. Place `maintainers.asc` in the same directory as your policy file + +Customize: + +- Replace the repository URL in `is_buildkit` +- Update the maintainers in the PGP keyring file +- Adjust the version tag regex pattern if needed + +When to use: Validate that production dependencies come from signed releases. +Protects against compromised releases or unauthorized updates. + +### Multi-registry policy + +Apply different validation rules for internal vs external registries. + +```rego +package docker + +default allow := false + +allow if input.local + +# TODO: Replace with your internal registry hostname +internal_registry := "registry.company.com" + +# Internal registry: basic validation +allow if { + input.image.host == internal_registry +} + +# External registries: strict validation +allow if { + input.image.host != internal_registry + input.image.host != "" + input.image.isCanonical + input.image.hasProvenance +} + +# Docker Hub: allowlist specific images +allow if { + input.image.host == "docker.io" + # TODO: Add your approved base images + input.image.repo in ["alpine", "golang", "node"] + input.image.isCanonical +} + +# Docker Hardened Images: trusted by default with built-in attestations +allow if { + input.image.host == "dhi.io" + input.image.isCanonical +} + +decision := {"allow": allow} +``` + +This template defines a trust boundary between internal and external image +sources. Internal images require minimal validation, while external images need +digests and provenance. Docker Hardened Images from `dhi.io` are treated as +trusted since they include comprehensive attestations and security guarantees. + +Customize: + +- Set your internal registry hostname +- Add your approved Docker Hub base images +- Adjust validation requirements based on your security policies + +When to use: Organizations with internal registries that need different rules +for internal vs external sources. Balances security with practical workflow +needs. + +### Multi-environment policy + +Apply different rules based on the build target or stage. For example, + +```rego +package docker + +default allow := false + +allow if input.local + +# TODO: Define your environment detection logic +is_production if { + input.env.target == "production" +} + +is_development if { + input.env.target == "development" +} + +# Production: strict rules - only digest images with provenance +allow if { + is_production + input.image.isCanonical + input.image.hasProvenance +} + +# Development: permissive rules - any image +allow if { + is_development + input.image +} + +# Staging inherits production rules (default target detection) +allow if { + not is_production + not is_development + input.image.isCanonical +} + +decision := {"allow": allow} +``` + +This template uses build targets to apply different validation levels. +Production requires attestations and digests, development is permissive, and +staging uses moderate rules. + +Customize: + +- Update environment detection logic (target names, build args, etc.) +- Adjust validation requirements for each environment +- Add more environments as needed + +When to use: Teams with separate build configurations for different deployment +stages. Allows flexibility in development while enforcing strict rules for +production. + +### Complete dependency pinning + +Pin all external dependencies to specific versions across all input types. + +```rego +package docker + +default allow := false + +allow if input.local + +# TODO: Add your pinned images with exact digests +# Docker Hub images use docker.io as host +allowed_dockerhub := { + "alpine": "sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412", + "golang": "sha256:abc123...", +} + +allow if { + input.image.host == "docker.io" + some repo, digest in allowed_dockerhub + input.image.repo == repo + input.image.checksum == digest +} + +# TODO: Add your pinned DHI images +allowed_dhi := { + "python": "sha256:def456...", + "node": "sha256:ghi789...", +} + +allow if { + input.image.host == "dhi.io" + some repo, digest in allowed_dhi + input.image.repo == repo + input.image.checksum == digest +} + +# TODO: Add your pinned Git dependencies +allowed_git := { + "https://github.com/moby/buildkit.git": { + "tag": "v0.26.1", + "commit": "abc123...", + }, +} + +allow if { + some url, version in allowed_git + input.git.remote == url + input.git.tagName == version.tag + input.git.commitChecksum == version.commit +} + +# TODO: Add your pinned HTTP downloads +allowed_downloads := { + "https://releases.example.com/app-v1.0.tar.gz": "sha256:def456...", +} + +allow if { + some url, checksum in allowed_downloads + input.http.url == url + input.http.checksum == checksum +} + +decision := {"allow": allow} +``` + +This template pins every external dependency to exact versions with cryptographic +verification. Images use digests, Git repos use commit SHAs, and downloads use +checksums. + +Customize: + +- Add all your dependencies with exact versions/checksums +- Maintain this file when updating dependencies +- Consider automating updates through CI/CD + +When to use: Maximum reproducibility and security. Ensures builds always use +exact versions of all dependencies. Required for high-security or regulated +environments. + +### Manual signature verification + +Verify image signatures by inspecting signature metadata fields. + +```rego +package docker + +default allow := false + +allow if input.local + +# Require valid GitHub Actions signatures +allow if { + input.image + input.image.hasProvenance + some sig in input.image.signatures + valid_github_signature(sig) +} + +# Helper function to validate GitHub Actions signature +valid_github_signature(sig) if { + # Sigstore keyless signing + sig.signer.certificateIssuer == "CN=sigstore-intermediate,O=sigstore.dev" + sig.signer.issuer == "https://token.actions.githubusercontent.com" + + # TODO: Replace with your organization + startswith(sig.signer.buildSignerURI, "https://github.com/myorg/.github/workflows/") + startswith(sig.signer.sourceRepositoryURI, "https://github.com/myorg/") + + # Verify GitHub hosted runner + sig.signer.runnerEnvironment == "github-hosted" + + # Require timestamp + count(sig.timestamps) > 0 +} + +decision := {"allow": allow} +``` + +This policy validates that images were built by GitHub Actions using Sigstore +keyless signing. + +Customize: + +- Replace `myorg` with your GitHub organization +- Adjust workflow path restrictions +- Add additional signature field checks as needed + +When to use: Enforce that images are built by CI/CD with verifiable signatures, +not manually pushed by developers. + +## Next steps + +- Write unit tests for your policies: [Test build policies](./testing.md) +- Review [Built-in functions](./built-ins.md) for signature verification and + attestation checking +- Check the [Input reference](./inputs.md) for all available fields you can + validate +- Read the tutorials for detailed explanations: + [Introduction](./intro.md), [Image validation](./validate-images.md), [Git + validation](./validate-git.md) diff --git a/content/manuals/build/policies/inputs.md b/content/manuals/build/policies/inputs.md new file mode 100644 index 000000000000..5b6f114c0997 --- /dev/null +++ b/content/manuals/build/policies/inputs.md @@ -0,0 +1,450 @@ +--- +title: Input reference +linkTitle: Input reference +description: Reference documentation for policy input fields +keywords: build policies, input reference, policy fields, image metadata, git metadata +weight: 80 +--- + +When buildx evaluates policies, it provides information about build inputs +through the `input` object. The structure of `input` depends on the type of +resource your Dockerfile references. + +## Input types + +Build inputs correspond to Dockerfile instructions: + +| Dockerfile instruction | Input type | Access pattern | +| --------------------------------------- | ---------- | -------------- | +| `FROM alpine:latest` | Image | `input.image` | +| `COPY --from=builder /app /app` | Image | `input.image` | +| `ADD https://example.com/file.tar.gz /` | HTTP | `input.http` | +| `ADD git@github.com:user/repo.git /src` | Git | `input.git` | +| Build context (`.`) | Local | `input.local` | + +Each input type has specific fields available for policy evaluation. + +## HTTP inputs + +HTTP inputs represent files downloaded over HTTP or HTTPS using the `ADD` +instruction. + +### Example Dockerfile + +```dockerfile +FROM alpine +ADD --checksum=sha256:abc123... https://example.com/app.tar.gz /app.tar.gz +``` + +### Available fields + +#### `input.http.url` + +The complete URL of the resource. + +```rego +allow if { + input.http.url == "https://example.com/app.tar.gz" +} +``` + +#### `input.http.schema` + +The URL scheme (`http` or `https`). + +```rego +# Require HTTPS for all downloads +allow if { + input.http.schema == "https" +} +``` + +#### `input.http.host` + +The hostname from the URL. + +```rego +# Allow downloads from approved domains +allow if { + input.http.host == "cdn.example.com" +} +``` + +#### `input.http.path` + +The path component of the URL. + +```rego +allow if { + startswith(input.http.path, "/releases/") +} +``` + +#### `input.http.checksum` + +The checksum specified with `ADD --checksum=...`, if present. Empty string if +no checksum was provided. + +```rego +# Require checksums for all downloads +allow if { + input.http.checksum != "" +} +``` + +#### `input.http.hasAuth` + +Boolean indicating if the request includes authentication (HTTP basic auth or +bearer token). + +```rego +# Require authentication for internal servers +allow if { + input.http.host == "internal.company.com" + input.http.hasAuth +} +``` + +## Image inputs + +Image inputs represent container images from `FROM` instructions or +`COPY --from` references. + +### Example Dockerfile + +```dockerfile +FROM alpine:3.19@sha256:abc123... +COPY --from=builder:latest /app /app +``` + +### Available fields + +#### `input.image.ref` + +The complete image reference as written in the Dockerfile. + +```rego +allow if { + input.image.ref == "alpine:3.19@sha256:abc123..." +} +``` + +#### `input.image.host` + +The registry hostname. Docker Hub images use `"docker.io"`. + +```rego +# Only allow Docker Hub images +allow if { + input.image.host == "docker.io" +} + +# Only allow images from GitHub Container Registry +allow if { + input.image.host == "ghcr.io" +} +``` + +#### `input.image.repo` + +The repository name without the registry host. + +```rego +allow if { + input.image.repo == "library/alpine" +} +``` + +#### `input.image.fullRepo` + +The full repository path including registry host. + +```rego +allow if { + input.image.fullRepo == "docker.io/library/alpine" +} +``` + +#### `input.image.tag` + +The tag portion of the reference. Empty if using a digest reference. + +```rego +# Allow only specific tags +allow if { + input.image.tag == "3.19" +} +``` + +#### `input.image.isCanonical` + +Boolean indicating if the reference uses a digest (`@sha256:...`). + +```rego +# Require digest references +allow if { + input.image.isCanonical +} +``` + +#### `input.image.checksum` + +The SHA256 digest of the image manifest. + +```rego +allow if { + input.image.checksum == "sha256:abc123..." +} +``` + +#### `input.image.platform` + +The target platform for multi-platform images. + +```rego +allow if { + input.image.platform == "linux/amd64" +} +``` + +#### `input.image.os` + +The operating system from the image configuration. + +```rego +allow if { + input.image.os == "linux" +} +``` + +#### `input.image.arch` + +The CPU architecture from the image configuration. + +```rego +allow if { + input.image.arch == "amd64" +} +``` + +#### `input.image.hasProvenance` + +Boolean indicating if the image has provenance attestations. + +```rego +# Require provenance for production images +allow if { + input.image.hasProvenance +} +``` + +#### `input.image.labels` + +A map of image labels from the image configuration. + +```rego +# Check for specific labels +allow if { + input.image.labels["org.opencontainers.image.vendor"] == "Example Corp" +} +``` + +#### `input.image.signatures` + +Array of attestation signatures. Each signature has fields: + +- `kind` - Signature type (e.g., `"sigstore"`) +- `timestamps` - Trusted timestamps from transparency logs + +```rego +# Require at least one signature +allow if { + count(input.image.signatures) > 0 +} +``` + +When using Sigstore signatures, additional fields are available under +`input.image.signature` (singular) with details about the signing workflow. + +## Git inputs + +Git inputs represent Git repositories referenced in `ADD` instructions or used +as build context. + +### Example Dockerfile + +```dockerfile +ADD git@github.com:moby/buildkit.git#v0.12.0 /src +``` + +### Available fields + +#### `input.git.host` + +The Git host (e.g., `github.com`, `gitlab.com`). + +```rego +allow if { + input.git.host == "github.com" +} +``` + +#### `input.git.remote` + +The complete Git URL. + +```rego +allow if { + input.git.remote == "https://github.com/moby/buildkit.git" +} +``` + +#### `input.git.ref` + +The Git reference (branch, tag, or commit SHA). + +```rego +allow if { + input.git.ref == "v0.12.0" +} +``` + +#### `input.git.tagName` + +The tag name if the reference is a tag. + +```rego +# Only allow version tags +allow if { + regex.match(`^v[0-9]+\.[0-9]+\.[0-9]+$`, input.git.tagName) +} +``` + +#### `input.git.branch` + +The branch name if the reference is a branch. + +```rego +allow if { + input.git.branch == "main" +} +``` + +#### `input.git.commitChecksum` + +The commit SHA256 checksum. + +```rego +allow if { + input.git.commitChecksum == "abc123..." +} +``` + +#### `input.git.commit` + +Object containing commit metadata: + +- `author` - Author name, email, when +- `committer` - Committer name, email, when +- `message` - Commit message +- `pgpSignature` - PGP signature details if signed +- `sshSignature` - SSH signature details if signed + +```rego +# Check commit author +allow if { + input.git.commit.author.email == "maintainer@example.com" +} +``` + +#### `input.git.tag` + +Object containing tag metadata for annotated tags: + +- `tagger` - Tagger name, email, when +- `message` - Tag message +- `pgpSignature` - PGP signature details if signed +- `sshSignature` - SSH signature details if signed + +```rego +# Require signed tags +allow if { + input.git.tag.pgpSignature != null +} +``` + +## Local inputs + +Local inputs represent the build context directory. + +### Available fields + +#### `input.local.name` + +The name or path of the local context. + +```rego +allow if { + input.local.name == "." +} +``` + +Local inputs are typically less restricted than remote inputs, but you can +still write policies to enforce context requirements. + +## Environment fields + +The `input.env` object provides build context information not specific to a +resource type. + +### Available fields + +#### `input.env.filename` + +The name of the Dockerfile being built. + +```rego +# Stricter rules for production Dockerfile +allow if { + input.env.filename == "Dockerfile" + input.image.isCanonical +} + +# Relaxed rules for development +allow if { + input.env.filename == "Dockerfile.dev" +} +``` + +#### `input.env.target` + +The build target from multi-stage builds. + +```rego +# Require signing only for release builds +allow if { + input.env.target == "release" + input.git.tagName != "" + verify_git_signature(input.git.tag, "maintainer.asc") +} +``` + +#### `input.env.args` + +Build arguments passed with `--build-arg`. Access specific arguments by key. + +```rego +# Check build argument values +allow if { + input.env.args.ENVIRONMENT == "production" + input.image.hasProvenance +} +``` + +## Next steps + +- See [Built-in functions](./built-ins.md) for built-in helper functions to + check and validate input properties +- Browse [Example policies](./examples.md) for common patterns +- Read about [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) + for advanced policy logic diff --git a/content/manuals/build/policies/intro.md b/content/manuals/build/policies/intro.md new file mode 100644 index 000000000000..f2691f08e279 --- /dev/null +++ b/content/manuals/build/policies/intro.md @@ -0,0 +1,328 @@ +--- +title: Introduction to build policies +linkTitle: Introduction +description: Get started with writing and evaluating build policies +keywords: build policies, opa, rego, policy tutorial, docker build, security +weight: 10 +--- + +Build policies let you validate the inputs to your Docker builds before they +run. This tutorial walks you through creating your first policy, teaching the +Rego basics you need along the way. + +## What you'll learn + +By the end of this tutorial, you'll understand: + +- How to create and organize policy files +- Basic Rego syntax and patterns +- How to write policies that validate URLs, checksums, and images +- How policies evaluate during builds + +## Prerequisites + +- Buildx version 0.31 or later +- Basic familiarity with Dockerfiles and building images + +## How policies work + +When you build an image, buildx resolves all the inputs your Dockerfile +references: base images from `FROM` instructions, files from `ADD` or `COPY`, +and Git repositories. Before running the build, buildx evaluates your policies +against these inputs. If any input violates a policy, the build fails before +any instructions execute. + +Policies are written in Rego, a declarative language designed for expressing +rules and constraints. You don't need to know Rego to get started - this +tutorial teaches you what you need. + +## Create your first policy + +Create a new directory for this tutorial and add a Dockerfile: + +```console +$ mkdir policy-tutorial +$ cd policy-tutorial +``` + +Create a `Dockerfile` that downloads a file with `ADD`: + +```dockerfile +FROM scratch +ADD https://example.com/index.html /index.html +``` + +Now create a policy file. Policies use the `.rego` extension and live alongside +your Dockerfile. Create `Dockerfile.rego`: + +```rego {title="Dockerfile.rego"} +package docker + +default allow := false + +allow if input.local +allow if { + input.http.host == "example.com" +} + +decision := {"allow": allow} +``` + +Save this file as `Dockerfile.rego` in the same directory as your Dockerfile. + +Let's break down what this policy does: + +- `package docker` - All build policies must start with this package declaration +- `default allow := false` - Start with deny-by-default for security +- `allow if input.local` - The first rule allows any local files (your build context) +- `allow if { input.http.host == "example.com" }` - The second rule allows HTTP downloads from `example.com` +- `decision := {"allow": allow}` - The final decision object tells buildx whether to allow or deny the input + +This policy says: "Only allow local files and HTTP downloads from +`example.com`". Any other inputs cause the build to fail. + +### About `input.local` + +You'll see `allow if input.local` in nearly every policy. This rule allows +local file access, which includes your build context (the `.` directory) and +importantly, the Dockerfile itself. Without this rule, buildx can't read your +Dockerfile to start the build. + +Even builds that don't reference any files from the build context need +`input.local` because the Dockerfile is a local file. The policy evaluates +before the build starts, and denying local access means denying access to the +Dockerfile. + +In rare cases, you might want stricter local file policies - for example, +checking `input.local.name` to restrict which directories can be used as build +context. But most policies simply allow all local access. + +## Automatic policy loading + +Buildx automatically loads policies that match your Dockerfile name. When you +build with `Dockerfile`, buildx looks for `Dockerfile.rego` in the same +directory. For a file named `app.Dockerfile`, it looks for +`app.Dockerfile.rego`. + +This automatic loading means you don't need any command-line flags in most +cases - create the policy file and build. + +The policy file must be in the same directory as the Dockerfile. If buildx +can't find a matching policy, the build proceeds without policy evaluation +(unless you use `--policy strict=true`). + +For more control over policy loading, see the [Usage guide](./usage.md). + +## Run a build with your policy + +Build the image with policy evaluation enabled: + +```console +$ docker build . +``` + +The build succeeds because the URL in your Dockerfile matches the policy. Now +try changing the URL in your Dockerfile to something else: + +```dockerfile +FROM scratch +ADD https://api.github.com/users/octocat /user.json +``` + +Build again: + +```console +$ docker build . +``` + +This time the build fails with a policy violation. The `api.github.com` +hostname doesn't match the rule in your policy, so buildx rejects it before +running any build steps. + +## Debugging policy failures + +If your build fails with a policy violation, use `--progress=plain` to see +exactly what went wrong: + +```console +$ docker buildx build --progress=plain . +``` + +This shows all policy checks, the input data for each source, and allow/deny +decisions. For complete debugging guidance, see [Debugging](./debugging.md). + +## Add helpful error messages + +When a policy denies an input, users see a generic error message. You can +provide custom messages that explain why the build was denied: + +```rego {title="Dockerfile.rego"} +package docker + +default allow := false + +allow if input.local +allow if { + input.http.host == "example.com" + input.http.schema == "https" +} + +deny_msg contains msg if { + not allow + input.http + msg := "only HTTPS downloads from example.com are allowed" +} + +decision := {"allow": allow, "deny_msg": deny_msg} +``` + +Now when a build is denied, users see your custom message explaining what went +wrong: + +```console +$ docker buildx build . +Policy: only HTTPS downloads from example.com are allowed +ERROR: failed to build: ... source not allowed by policy +``` + +The `deny_msg` rule uses `contains` to add messages to a set. You can add +multiple deny messages for different failure conditions to help users understand +exactly what needs to change. + +## Understand Rego rules + +Rego policies are built from rules. A rule defines when something is allowed. +The basic pattern is: + +```rego +allow if { + condition_one + condition_two + condition_three +} +``` + +All conditions must be true for the rule to match. Think of it as an AND +operation - the URL must match AND the checksum must match AND any other +conditions you specify. + +You can have multiple `allow` rules in one policy. If any rule matches, the +input is allowed: + +```rego +# Allow downloads from example.com +allow if { + input.http.host == "example.com" +} + +# Also allow downloads from api.github.com +allow if { + input.http.host == "api.github.com" +} +``` + +This works like OR - the input can match the first rule OR the second rule. + +## Access input fields + +The `input` object gives you access to information about build inputs. The +structure depends on the input type: + +- `input.http` - Files downloaded with `ADD https://...` +- `input.image` - Container images from `FROM` or `COPY --from` +- `input.git` - Git repositories from `ADD git://...` or build context +- `input.local` - Local file context + +For HTTP downloads, you can access: + +| Key | Description | Example | +| ------------------- | ---------------------------------- | -------------------------------- | +| `input.http.url` | The full URL | `https://example.com/index.html` | +| `input.http.schema` | The protocol (HTTP/HTTPS) | `https` | +| `input.http.host` | The hostname | `example.com` | +| `input.http.path` | The URL path, including parameters | `/index.html` | + +Update your policy to require HTTPS: + +```rego +package docker + +default allow := false + +allow if { + input.http.host == "example.com" + input.http.schema == "https" +} + +decision := {"allow": allow} +``` + +Now the policy requires both the hostname to be `example.com` and the protocol +to be HTTPS. HTTP URLs (without TLS) would fail the policy check. + +## Pattern matching and strings + +Rego provides [built-in functions] for pattern matching. Use `startswith()` to +match URL prefixes: + +[built-in functions]: https://www.openpolicyagent.org/docs/policy-language#built-in-functions + +```rego +allow if { + startswith(input.http.url, "https://example.com/") +} +``` + +This allows any URL that starts with `https://example.com/`. + +Use `regex.match()` for complex patterns: + +```rego +allow if { + regex.match(`^https://example\.com/.+\.json$`, input.http.url) +} +``` + +This matches URLs that: + +- Start with `https://example.com/` +- End with `.json` +- Have at least one character between the domain and extension + +## Policy file location + +Policy files live adjacent to the Dockerfile they validate, using the naming +pattern `.rego`: + +```text +project/ +├── Dockerfile # Main Dockerfile +├── Dockerfile.rego # Policy for Dockerfile +├── lint.Dockerfile # Linting Dockerfile +└── lint.Dockerfile.rego # Policy for lint.Dockerfile +``` + +When you build, buildx automatically loads the corresponding policy file: + +```console +$ docker buildx build -f Dockerfile . # Loads Dockerfile.rego +$ docker buildx build -f lint.Dockerfile . # Loads lint.Dockerfile.rego +``` + +## Next steps + +You now understand how to write basic build policies for HTTP resources. To +continue learning: + +- Apply and test policies: [Using build policies](./usage.md) +- Learn [Image validation](./validate-images.md) to validate container images + from `FROM` instructions +- Learn [Git validation](./validate-git.md) to validate Git repositories used + in builds +- See [Example policies](./examples.md) for copy-paste-ready policies covering + common scenarios +- Write unit tests for your policies: [Test build policies](./testing.md) +- Debug policy failures: [Debugging](./debugging.md) +- Read the [Input reference](./inputs.md) for all available input fields +- Check the [Built-in functions](./built-ins.md) for signature verification, + attestations, and other security checks diff --git a/content/manuals/build/policies/testing.md b/content/manuals/build/policies/testing.md new file mode 100644 index 000000000000..89effde6ef83 --- /dev/null +++ b/content/manuals/build/policies/testing.md @@ -0,0 +1,207 @@ +--- +title: Test build policies +linkTitle: Testing +description: Write and run unit tests for build policies using OPA's test framework +keywords: build policies, opa, rego, testing, unit tests, policy validation +weight: 60 +--- + +The [`docker buildx policy test`](/reference/cli/docker/buildx/policy/test/) +command runs unit tests for build policies using OPA's standard test framework. + +```console +$ docker buildx policy test +``` + +This validates policy logic with mocked inputs. For testing against real +sources (actual image metadata, Git repositories), use [`policy +eval`](/reference/cli/docker/buildx/policy/eval/) instead. + +## Basic example + +Start with a simple policy: + +```rego {title="Dockerfile.rego"} +package docker + +default allow = false + +allow if { + input.image.repo == "alpine" +} + +decision := {"allow": allow} +``` + +Create a test file with the `*_test.rego` suffix. Test functions must start +with `test_`: + +```rego {title="Dockerfile_test.rego"} +package docker + +test_alpine_allowed if { + decision.allow with input as {"image": {"repo": "alpine"}} +} + +test_ubuntu_denied if { + not decision.allow with input as {"image": {"repo": "ubuntu"}} +} +``` + +Run the tests: + +```console +$ docker buildx policy test . +test_alpine_allowed: PASS (allow=true) +test_ubuntu_denied: PASS (allow=false) +``` + +## Command options + +Filter tests by name with `--run`: + +```console +$ docker buildx policy test --run alpine . +test_alpine_allowed: PASS (allow=true) +``` + +Test policies with non-default filenames using `--filename`: + +```console +$ docker buildx policy test --filename app.Dockerfile . +``` + +This loads `app.Dockerfile.rego` and runs `*_test.rego` files against it. + +## Test output + +Passed tests show the allow status and any deny messages: + +```console +test_alpine_allowed: PASS (allow=true) +test_ubuntu_denied: PASS (allow=false, deny_msg=only alpine images are allowed) +``` + +Failed tests show input, decision output, and missing fields: + +```console +test_invalid: FAIL (allow=false) +input: + { + "image": {} + } +decision: + { + "allow": false, + "deny_msg": [ + "only alpine images are allowed" + ] + } +missing_input: input.image.repo +``` + +## Test deny messages + +To test custom error messages, capture the full decision result and assert on +the `deny_msg` field. + +For a policy with deny messages: + +```rego {title="Dockerfile.rego"} +package docker + +default allow = false + +allow if { + input.image.repo == "alpine" +} + +deny_msg contains msg if { + not allow + msg := "only alpine images are allowed" +} + +decision := {"allow": allow, "deny_msg": deny_msg} +``` + +Test the deny message: + +```rego {title="Dockerfile_test.rego"} +test_deny_message if { + result := decision with input as {"image": {"repo": "ubuntu"}} + not result.allow + "only alpine images are allowed" in result.deny_msg +} +``` + +## Test patterns + +**Test environment-specific rules:** + +```rego +test_production_requires_digest if { + decision.allow with input as { + "env": {"target": "production"}, + "image": {"isCanonical": true} + } +} + +test_development_allows_tags if { + decision.allow with input as { + "env": {"target": "development"}, + "image": {"isCanonical": false} + } +} +``` + +**Test multiple registries:** + +```rego +test_dockerhub_allowed if { + decision.allow with input as { + "image": { + "ref": "docker.io/library/alpine", + "host": "docker.io", + "repo": "alpine" + } + } +} + +test_ghcr_allowed if { + decision.allow with input as { + "image": { + "ref": "ghcr.io/myorg/myapp", + "host": "ghcr.io", + "repo": "myorg/myapp" + } + } +} +``` + +For available input fields, see the [Input reference](./inputs.md). + +## Organize test files + +The test runner discovers all `*_test.rego` files recursively: + +```plaintext +build-policies/ +├── Dockerfile.rego +├── Dockerfile_test.rego +└── tests/ + ├── registries_test.rego + ├── signatures_test.rego + └── environments_test.rego +``` + +Run all tests: + +```console +$ docker buildx policy test . +``` + +Or test specific files: + +```console +$ docker buildx policy test tests/registries_test.rego +``` diff --git a/content/manuals/build/policies/usage.md b/content/manuals/build/policies/usage.md new file mode 100644 index 000000000000..de1cdc0a8235 --- /dev/null +++ b/content/manuals/build/policies/usage.md @@ -0,0 +1,498 @@ +--- +title: Using build policies +linkTitle: Usage +description: Apply policies to builds and develop policies iteratively +keywords: build policies, policy eval, docker buildx, policy development, debugging +weight: 20 +--- + +Build policies validate inputs before builds execute. This guide covers how to +develop policies iteratively and apply them to real builds with `docker buildx +build` and `docker buildx bake`. + +## Prerequisites + +- **Buildx 0.31.0 or later** - Check your version: `docker buildx version` +- **BuildKit 0.26.0 or later** - Verify with: `docker buildx inspect + --bootstrap` + +If you're using Docker Desktop, ensure you're on a version that includes these +updates. + +## Policy development workflow + +Buildx automatically loads policies that match your Dockerfile name. When you +build with `Dockerfile`, buildx looks for `Dockerfile.rego` in the same +directory. For a file named `app.Dockerfile`, it looks for +`app.Dockerfile.rego`. See the [Advanced: Policy configuration](#advanced-policy-configuration) +section for configuration options and manual policy loading. + +Writing policies is an iterative process that includes inspecting the sources +you use, the input properties associated with those sources, and writing policy +checks to verify that the properties meet your expectations. + +1. Inspect your Dockerfile inputs with `--progress=plain` builds +2. Understand available fields with `policy eval --print` +3. Write initial policy rules based on what you found +4. Test specific sources with `policy eval` +5. Refine rules based on actual input data + +### Viewing inputs from your Dockerfile + +To see the inputs that your Dockerfile references (images, Git repos, HTTP +downloads), build with debug logging: + +```console +$ docker buildx build --progress=plain --policy log-level=debug . +``` + +Example output for an image source: + +```text +#1 0.010 checking policy for source docker-image://alpine:3.19 (linux/arm64) +#1 0.011 policy input: { +#1 0.011 "env": { +#1 0.011 "filename": "." +#1 0.011 }, +#1 0.011 "image": { +#1 0.011 "ref": "docker.io/library/alpine:3.19", +#1 0.011 "host": "docker.io", +#1 0.011 "repo": "alpine", +#1 0.011 "tag": "3.19", +#1 0.011 "platform": "linux/arm64" +#1 0.011 } +#1 0.011 } +#1 0.011 unknowns for policy evaluation: [input.image.checksum input.image.labels ...] +#1 0.012 policy decision for source docker-image://alpine:3.19: ALLOW +``` + +This shows the complete input structure, which fields are unresolved, and the +policy decision for each source. See [Input reference](./inputs.md) for all +available fields. + +### Testing policies with policy eval + +Use [`docker buildx policy eval`](/reference/cli/docker/buildx/policy/eval/) to +test whether your policy allows a specific source without running a full build. + +> [!IMPORTANT] +> `policy eval` doesn't fetch sources, so many fields remain unresolved. +> Policies that check fields like `hasProvenance`, `signatures`, or `labels` +> can't be fully tested with `eval`. Use an actual build with `--progress=plain` +> to test those policies. + +Note: `docker buildx policy eval` tests one source at a time. It doesn't parse +your Dockerfile to evaluate all inputs - for that, [build with +--progress=plain](#viewing-inputs-from-your-dockerfile). + +Test if your policy allows the local context: + +```console +$ docker buildx policy eval . +``` + +No output means the policy allowed the source. If denied, you see: + +```console +ERROR: policy denied +``` + +Test other sources: + +```console +$ docker buildx policy eval https://example.com # Test HTTP +$ docker buildx policy eval https://github.com/org/repo.git # Test Git +``` + +**Available fields during `policy eval` for images:** +- Reference info: `ref`, `host`, `repo`, `fullRepo`, `tag`, `isCanonical` +- Platform info: `platform`, `os`, `arch`, `variant` + +**Unresolved fields (only available during builds):** +- Metadata: `checksum`, `labels`, `user`, `volumes`, `workingDir`, `env`, `createdTime` +- Attestations: `hasProvenance`, `signatures` + +### Iterative development example + +Here's a practical workflow for developing policies: + +1. Start with basic deny-all policy: + + ```rego {title="Dockerfile.rego"} + package docker + + default allow := false + + allow if input.local + + decision := {"allow": allow} + ``` + +2. Build with debug logging to see what inputs your Dockerfile uses: + + ```console + $ docker buildx build --progress=plain --policy log-level=debug . + ``` + + The output shows the denied image and its input structure: + + ```text + #1 0.026 checking policy for source docker-image://docker.io/library/alpine:3.19 + #1 0.027 policy input: { + #1 0.027 "image": { + #1 0.027 "repo": "alpine", + #1 0.027 "tag": "3.19", + #1 0.027 ... + #1 0.027 } + #1 0.027 } + #1 0.028 policy decision for source docker-image://alpine:3.19: DENY + #1 ERROR: source "docker-image://alpine:3.19" not allowed by policy + ``` + +3. Add a rule allowing the alpine image: + + ```rego + allow if { + input.image.repo == "alpine" + } + ``` + +4. Build again to verify the policy works: + + ```console + $ docker buildx build . + ``` + +If it fails, see [Debugging](./debugging.md) for troubleshooting guidance. + +## Using policies with docker build + +Once you've developed and tested your policy, apply it to real builds. + +### Basic usage + +Create a policy alongside your Dockerfile: + +```dockerfile {title="Dockerfile"} +FROM alpine:3.19 +RUN echo "hello" +``` + +```rego {title="Dockerfile.rego"} +package docker + +default allow := false + +allow if input.local + +allow if { + input.image.repo == "alpine" +} + +decision := {"allow": allow} +``` + +Build normally: + +```console +$ docker buildx build . +``` + +Buildx loads the policy automatically and validates the `alpine:3.19` image +before building. + +### Build with different Dockerfile names + +Specify the Dockerfile with `-f`: + +```console +$ docker buildx build -f app.Dockerfile . +``` + +Buildx looks for `app.Dockerfile.rego` in the same directory. + +### Build with manual policy + +Add an extra policy to the automatic one: + +```console +$ docker buildx build --policy filename=extra-checks.rego . +``` + +Both `Dockerfile.rego` (automatic) and `extra-checks.rego` (manual) must pass. + +### Build without automatic policy + +Use only your specified policy: + +```console +$ docker buildx build --policy reset=true,filename=strict.rego . +``` + +## Using policies with bake + +[Bake](/build/bake/) supports automatic policy loading just like `docker buildx +build`. Place `Dockerfile.rego` alongside your Dockerfile and run: + +```console +$ docker buildx bake +``` + +### Manual policy in bake files + +Specify additional policies in your `docker-bake.hcl`: + +```hcl {title="docker-bake.hcl"} +target "default" { + dockerfile = "Dockerfile" + policy = ["extra.rego"] +} +``` + +The `policy` attribute takes a list of policy files. Bake loads these in +addition to the automatic `Dockerfile.rego` (if it exists). + +### Multiple policies in bake + +```hcl {title="docker-bake.hcl"} +target "webapp" { + dockerfile = "Dockerfile" + policy = [ + "shared/base-policy.rego", + "security/image-signing.rego" + ] +} +``` + +All policies must pass for the target to build successfully. + +### Different policies per target + +Apply different validation rules to different targets: + +```hcl {title="docker-bake.hcl"} +target "development" { + dockerfile = "dev.Dockerfile" + policy = ["policies/permissive.rego"] +} + +target "production" { + dockerfile = "prod.Dockerfile" + policy = ["policies/strict.rego", "policies/signing-required.rego"] +} +``` + +Build with the appropriate target: + +```console +$ docker buildx bake development # Uses permissive policy +$ docker buildx bake production # Uses strict policies +``` + +### Bake with policy options + +Currently, bake doesn't support policy options (reset, strict, disabled) in the +HCL file. Use command-line flags instead: + +```console +$ docker buildx bake --policy disabled=true production +``` + +## Testing in CI/CD + +Validate policies in continuous integration by running builds with the `--policy` flag. For unit testing policies before running builds, see [Test build policies](./testing.md). + +Test policies during CI builds: + +```yaml {title=".github/workflows/test-policies.yml"} +name: Test Build Policies +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - name: Test build with policy + run: docker buildx build --policy strict=true . +``` + +This ensures policy changes don't break builds and that new rules work as +intended. The `strict=true` flag fails the build if policies aren't loaded. + +## Advanced: Policy configuration + +This section covers advanced policy loading mechanisms and configuration +options. + +### Automatic policy loading + +Buildx automatically loads policies that match your Dockerfile name. When you +build with `Dockerfile`, buildx looks for `Dockerfile.rego` in the same +directory. For a file named `app.Dockerfile`, it looks for +`app.Dockerfile.rego`. + +```text +project/ +├── Dockerfile +├── Dockerfile.rego # Loaded automatically for Dockerfile +├── app.Dockerfile +├── app.Dockerfile.rego # Loaded automatically for app.Dockerfile +└── src/ +``` + +This automatic loading means you don't need command-line flags in most cases. +Create the policy file alongside your Dockerfile and build: + +```console +$ docker buildx build . +``` + +Buildx detects `Dockerfile.rego` and evaluates it before running the build. + +> [!NOTE] +> Policy files must be in the same directory as the Dockerfile they validate. +> Buildx doesn't search parent directories or subdirectories. + +### When policies don't load + +If buildx can't find a matching `.rego` file, the build proceeds without policy +evaluation. To require policies and fail if none are found, use strict mode: + +```console +$ docker buildx build --policy strict=true . +``` + +This fails the build if no policy loads or if the BuildKit daemon doesn't +support policies. + +### Manual policy configuration + +The `--policy` flag lets you specify additional policies, override automatic +loading, or control policy behavior. + +Basic syntax: + +```console +$ docker buildx build --policy filename=custom.rego . +``` + +This loads `custom.rego` in addition to the automatic `Dockerfile.rego` (if it +exists). + +Multiple policies: + +```console +$ docker buildx build --policy filename=policy1.rego --policy filename=policy2.rego . +``` + +All policies must pass for the build to succeed. Use this to enforce layered +requirements (base policy + project-specific rules). + +Available options: + +| Option | Description | Example | +| ------------------- | ------------------------------------------------------- | ----------------------------- | +| `filename=` | Load policy from specified file | `filename=custom.rego` | +| `reset=true` | Ignore automatic policies, use only specified ones | `reset=true` | +| `disabled=true` | Disable all policy evaluation | `disabled=true` | +| `strict=true` | Fail if BuildKit doesn't support policies | `strict=true` | +| `log-level=` | Control policy logging (error, warn, info, debug, none). Use `debug` to see complete input JSON and unresolved fields | `log-level=debug` | + +Combine options with commas: + +```console +$ docker buildx build --policy filename=extra.rego,strict=true . +``` + +### Exploring sources with policy eval + +The `docker buildx policy eval` command lets you quickly explore and test +sources without running a build. + +#### Inspect input structure with --print + +Use `--print` to see the input structure for any source without running policy +evaluation: + +```console +$ docker buildx policy eval --print https://github.com/moby/buildkit.git +``` + +```json +{ + "git": { + "schema": "https", + "host": "github.com", + "remote": "https://github.com/moby/buildkit.git" + } +} +``` + +Test different source types: + +```console +# HTTP downloads +$ docker buildx policy eval --print https://releases.hashicorp.com/terraform/1.5.0/terraform.zip + +# Images (requires docker-image:// prefix) +$ docker buildx policy eval --print docker-image://alpine:3.19 + +# Local context +$ docker buildx policy eval --print . +``` + +This shows only basic fields without fetching the source. Fields like image +metadata, Git commit details, and HTTP checksums remain unresolved. For +complete field data, use an actual build with `--policy log-level=debug`. + +#### Test with specific policy files + +The `--filename` flag specifies which policy file to load by providing the base +Dockerfile name (without the `.rego` extension). This is useful for testing +sources against policies associated with different Dockerfiles. + +For example, to test a source against the policy for `app.Dockerfile`: + +```console +$ docker buildx policy eval --filename app.Dockerfile . +``` + +This loads `app.Dockerfile.rego` and tests whether it allows the source `.` +(the local directory). The flag defaults to `Dockerfile` if not specified. + +Test different sources against your policy: + +```console +$ docker buildx policy eval --filename app.Dockerfile https://github.com/org/repo.git +$ docker buildx policy eval --filename app.Dockerfile docker-image://alpine:3.19 +``` + +### Reset automatic loading + +To use only your specified policies and ignore automatic `.rego` files: + +```console +$ docker buildx build --policy reset=true,filename=custom.rego . +``` + +This skips `Dockerfile.rego` and loads only `custom.rego`. + +### Disable policies temporarily + +Disable policy evaluation for testing or emergencies: + +```console +$ docker buildx build --policy disabled=true . +``` + +The build proceeds without any policy checks. Use this carefully - you're +bypassing security controls. + +## Next steps + +- Write unit tests for your policies: [Test build policies](./testing.md) +- Debug policy failures: [Debugging](./debugging.md) +- Browse working examples: [Example policies](./examples.md) +- Reference all input fields: [Input reference](./inputs.md) diff --git a/content/manuals/build/policies/validate-git.md b/content/manuals/build/policies/validate-git.md new file mode 100644 index 000000000000..7f60cde10add --- /dev/null +++ b/content/manuals/build/policies/validate-git.md @@ -0,0 +1,428 @@ +--- +title: Validating Git repositories +linkTitle: Git validation +description: Write policies to validate Git repositories used in your builds +keywords: build policies, git validation, git signatures, gpg, signed commits, signed tags +weight: 40 +--- + +Git repositories often appear in Docker builds as source code inputs. The `ADD` +instruction can clone repositories, and build contexts can reference Git URLs. +Validating these inputs ensures you're building from trusted sources with +verified versions. + +This guide teaches you to write policies that validate Git inputs, from basic +version pinning to verifying signed commits and tags. + +## Prerequisites + +You should understand the policy basics from the [Introduction](./intro.md): +creating policy files, basic Rego syntax, and how policies evaluate during +builds. + +## What are Git inputs? + +Git inputs come from `ADD` instructions that reference Git repositories: + +```dockerfile +# Clone a specific tag +ADD https://github.com/moby/buildkit.git#v0.26.1 /buildkit + +# Clone a branch +ADD https://github.com/user/repo.git#main /src + +# Clone a commit +ADD https://github.com/user/repo.git#abcde123 /src +``` + +The build context can also be a Git repository when you build with: + +```console +$ docker build https://github.com/user/repo.git#main +``` + +Each Git reference triggers a policy evaluation. Your policy can inspect +repository URLs, validate versions, check commit metadata, and verify +signatures. + +## Match specific repositories + +The simplest Git policy restricts which repositories can be used: + +```rego {title="Dockerfile.rego"} +package docker + +default allow := false + +allow if input.local + +allow if { + input.git.host == "github.com" + input.git.remote == "https://github.com/moby/buildkit.git" +} + +decision := {"allow": allow} +``` + +This policy: + +- Denies all inputs by default +- Allows local build context +- Allows only the BuildKit repository from GitHub + +The `host` field contains the Git server hostname, and `remote` contains the +full repository URL. Test it: + +```dockerfile {title="Dockerfile"} +FROM scratch +ADD https://github.com/moby/buildkit.git#v0.26.1 / +``` + +```console +$ docker build . +``` + +The build succeeds. Try a different repository and it fails. + +You can match multiple repositories with additional rules: + +```rego +allow if { + input.git.host == "github.com" + input.git.remote == "https://github.com/moby/buildkit.git" +} + +allow if { + input.git.host == "github.com" + input.git.remote == "https://github.com/docker/cli.git" +} + +decision := {"allow": allow} +``` + +## Pin to specific versions + +Tags and branches can change over time. Pin to specific versions to ensure +reproducible builds: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.git.remote == "https://github.com/moby/buildkit.git" + input.git.tagName == "v0.26.1" +} + +decision := {"allow": allow} +``` + +The `tagName` field contains the tag name when the Git reference points to a +tag. Use `branch` for branches: + +```rego +allow if { + input.git.remote == "https://github.com/user/repo.git" + input.git.branch == "main" +} +``` + +Or use `ref` for any type of reference (branch, tag, or commit SHA): + +```rego +allow if { + input.git.ref == "v0.26.1" +} +``` + +## Use version allowlists + +For repositories you trust but want to control versions, maintain an allowlist: + +```rego +package docker + +default allow := false + +allowed_versions = [ + {"tag": "v0.26.1", "annotated": true, "sha": "abc123"}, +] + +is_buildkit if { + input.git.remote == "https://github.com/moby/buildkit.git" +} + +allow if { + not is_buildkit +} + +allow if { + is_buildkit + some version in allowed_versions + version.tag == input.git.tagName +} + +decision := {"allow": allow} +``` + +This policy: + +- Defines an allowlist of approved versions with metadata +- Uses a helper rule (`is_buildkit`) for readability +- Allows all non-BuildKit inputs +- For BuildKit, checks the tag against the allowlist + +The helper rule makes complex policies more maintainable. You can expand the +allowlist as new versions are approved: + +```rego +allowed_versions = [ + {"tag": "v0.26.1", "annotated": true, "sha": "abc123"}, + {"tag": "v0.27.0", "annotated": true, "sha": "def456"}, +] +``` + +## Validate with regex patterns + +Use pattern matching for semantic versioning: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.git.remote == "https://github.com/moby/buildkit.git" + regex.match(`^v[0-9]+\.[0-9]+\.[0-9]+$`, input.git.tagName) +} + +decision := {"allow": allow} +``` + +This allows any BuildKit tag matching the pattern `vX.Y.Z` where X, Y, and Z +are numbers. The regex ensures you're using release versions, not pre-release +tags like `v0.26.0-rc1`. + +Match major versions: + +```rego +# Only allow v0.x releases +allow if { + input.git.remote == "https://github.com/moby/buildkit.git" + regex.match(`^v0\.[0-9]+\.[0-9]+$`, input.git.tagName) +} +``` + +## Inspect commit metadata + +The `commit` object provides detailed information about commits: + +```rego +package docker + +default allow := false + +allow if input.local + +# Check commit author +allow if { + input.git.remote == "https://github.com/user/repo.git" + input.git.commit.author.email == "trusted@example.com" +} + +decision := {"allow": allow} +``` + +The `commit` object includes: + +- `author.name` - Author's name +- `author.email` - Author's email +- `author.when` - When the commit was authored +- `committer.name` - Committer's name +- `committer.email` - Committer's email +- `committer.when` - When the commit was committed +- `message` - Commit message + +Validate commit messages: + +```rego +allow if { + input.git.commit + contains(input.git.commit.message, "Signed-off-by:") +} +``` + +Pin to specific commit SHA: + +```rego +allow if { + input.git.commitChecksum == "abc123def456..." +} +``` + +## Require signed commits + +GPG-signed commits prove authenticity. Check for commit signatures: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.git.remote == "https://github.com/moby/buildkit.git" + input.git.commit.pgpSignature != null +} + +decision := {"allow": allow} +``` + +The `pgpSignature` field is `null` for unsigned commits. For signed commits, it +contains signature details. + +SSH signatures work similarly: + +```rego +allow if { + input.git.commit.sshSignature != null +} +``` + +## Require signed tags + +Annotated tags can be signed, providing a cryptographic guarantee of the +release: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.git.remote == "https://github.com/moby/buildkit.git" + input.git.tag.pgpSignature != null +} + +decision := {"allow": allow} +``` + +The `tag` object is only available for annotated tags. It includes: + +- `tagger.name` - Who created the tag +- `tagger.email` - Tagger's email +- `tagger.when` - When the tag was created +- `message` - Tag message +- `pgpSignature` - GPP signature (if signed) +- `sshSignature` - SSH signature (if signed) + +Lightweight tags don't have a `tag` object, so this policy effectively requires +annotated, signed tags. + +## Verify signatures with public keys + +Use the `verify_git_signature()` function to cryptographically verify Git +signatures against trusted public keys: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.git.remote == "https://github.com/moby/buildkit.git" + input.git.tagName != "" + verify_git_signature(input.git.tag, "maintainers.asc") +} + +decision := {"allow": allow} +``` + +This verifies that Git tags are signed by keys in the `maintainers.asc` public +key file. To set this up: + +1. Export maintainer public keys: + ```console + $ gpg --export --armor user1@example.com user2@example.com > maintainers.asc + ``` +2. Place `maintainers.asc` alongside your policy file + +The function verifies PGP signatures on commits or tags. See [Built-in +functions](./built-ins.md) for more details. + +## Apply conditional rules + +Use different rules for different contexts. Allow unsigned refs during +development but require signing for production: + +```rego +package docker + +default allow := false + +allow if input.local + +is_buildkit if { + input.git.remote == "https://github.com/moby/buildkit.git" +} + +is_version_tag if { + is_buildkit + regex.match(`^v[0-9]+\.[0-9]+\.[0-9]+$`, input.git.tagName) +} + +# Version tags must be signed +allow if { + is_version_tag + input.git.tagName != "" + verify_git_signature(input.git.tag, "maintainers.asc") +} + +# Non-version refs allowed in development +allow if { + is_buildkit + not is_version_tag + input.env.target != "release" +} + +decision := {"allow": allow} +``` + +This policy: + +- Defines helper rules for readability +- Requires signed version tags from maintainers +- Allows unsigned refs (branches, commits) unless building the release target +- Uses `input.env.target` to detect the build target + +Build a development target without signatures: + +```console +$ docker buildx build --target=dev . +``` + +Build the release target, and signing is enforced: + +```console +$ docker buildx build --target=release . +``` + +## Next steps + +You now understand how to validate Git repositories in build policies. To +continue learning: + +- Browse [Example policies](./examples.md) for complete policy patterns +- Read [Built-in functions](./built-ins.md) for Git signature verification + functions +- Check the [Input reference](./inputs.md) for all available Git fields diff --git a/content/manuals/build/policies/validate-images.md b/content/manuals/build/policies/validate-images.md new file mode 100644 index 000000000000..b4ab88a8a5e0 --- /dev/null +++ b/content/manuals/build/policies/validate-images.md @@ -0,0 +1,413 @@ +--- +title: Validating image inputs +linkTitle: Image validation +description: Write policies to validate container images used in your builds +keywords: build policies, image validation, docker images, provenance, attestations, signatures +weight: 30 +--- + +Container images are the most common build inputs. Every `FROM` instruction +pulls an image, and `COPY --from` references pull additional images. Validating +these images protects your build supply chain from compromised registries, +unexpected updates, and unauthorized base images. + +This guide teaches you to write policies that validate image inputs, +progressing from basic allowlisting to advanced attestation checks. + +## Prerequisites + +You should understand the policy basics from the [Introduction](./intro.md): +creating policy files, basic Rego syntax, and how policies evaluate during +builds. + +## What are image inputs? + +Image inputs come from two Dockerfile instructions: + +```dockerfile +# FROM instructions +FROM alpine:3.22 +FROM golang:1.25-alpine AS builder + +# COPY --from references +COPY --from=builder /app /app +COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf +``` + +Each of these references triggers a policy evaluation. Your policy can inspect +image metadata, verify attestations, and enforce constraints before the build +proceeds. + +## Allowlist specific repositories + +The simplest image policy restricts which repositories can be used. This +prevents developers from using arbitrary images that haven't been vetted. + +Create a policy that only allows Alpine: + +```rego {title="Dockerfile.rego"} +package docker + +default allow := false + +allow if input.local + +allow if { + input.image.repo == "alpine" +} + +decision := {"allow": allow} +``` + +This policy: + +- Denies all inputs by default +- Allows local build context +- Allows any image from the `alpine` repository (any tag or digest) + +Test it with a Dockerfile: + +```dockerfile {title="Dockerfile"} +FROM alpine +RUN echo "hello" +``` + +```console +$ docker build . +``` + +The build succeeds. Try changing to `FROM ubuntu`: + +```console +$ docker build . +``` + +The build fails because `ubuntu` doesn't match the allowed repository. + +## Compare semantic versions + +Restrict images to specific version ranges using Rego's `semver` functions: + +```rego +package docker + +default allow := false + +allow if input.local + +# Allow Go 1.21 or newer +allow if { + input.image.repo == "golang" + semver.is_valid(input.image.tag) + semver.compare(input.image.tag, "1.21.0") >= 0 +} + +decision := {"allow": allow} +``` + +The `semver.compare(a, b)` function compares semantic versions and returns: + +- `-1` if version `a` is less than `b` +- `0` if versions are equal +- `1` if version `a` is greater than `b` + +Use `semver.is_valid()` to check if a tag is a valid semantic version before +comparing. + +Restrict to specific version ranges: + +```rego +allow if { + input.image.repo == "node" + version := input.image.tag + semver.is_valid(version) + semver.compare(version, "20.0.0") >= 0 # 20.0.0 or newer + semver.compare(version, "21.0.0") < 0 # older than 21.0.0 +} +``` + +This allows only Node.js 20.x versions. The pattern works for any image using +semantic versioning. + +These `semver` functions are standard Rego built-ins documented in the [OPA +policy +reference](https://www.openpolicyagent.org/docs/latest/policy-reference/#semver). + +## Require digest references + +Tags like `alpine:3.22` can change - someone could push a new image with the +same tag. Digests like `alpine@sha256:abc123...` are immutable. Requiring +digests ensures builds are reproducible. + +Update your policy to require digests: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.image.isCanonical +} + +decision := {"allow": allow} +``` + +The `isCanonical` field is `true` when the image reference includes a digest. +Now update your Dockerfile: + +```dockerfile +FROM alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 +RUN echo "hello" +``` + +The build succeeds with a digest reference. Without the digest, it fails. + +You can also validate specific digests: + +```rego +allow if { + input.image.repo == "alpine" + input.image.checksum == "sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412" +} + +decision := {"allow": allow} +``` + +This pins the exact image content, useful for critical base images. + +## Restrict registries + +Control which registries your builds can pull from. This helps enforce +corporate policies or restrict to trusted sources. + +```rego +package docker + +default allow := false + +allow if input.local + +# Allow Docker Hub images +allow if { + input.image.host == "docker.io" # Docker Hub + input.image.repo == "alpine" +} + +# Allow images from internal registry +allow if { + input.image.host == "registry.company.com" +} + +decision := {"allow": allow} +``` + +The `host` field contains the registry hostname. Docker Hub images use +`"docker.io"` as the host value. Test with: + +```dockerfile +FROM alpine # Allowed (Docker Hub) +FROM registry.company.com/myapp:latest # Allowed (company registry) +FROM ghcr.io/someorg/image:latest # Denied (wrong registry) +``` + +Use `fullRepo` when you need the complete path including registry: + +```rego +allow if { + input.image.fullRepo == "docker.io/library/alpine" +} +``` + +## Validate platform constraints + +Multi-architecture images support different operating systems and CPU +architectures. You can restrict builds to specific platforms: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.image.os == "linux" + input.image.arch in ["amd64", "arm64"] +} + +decision := {"allow": allow} +``` + +This policy: + +- Defines supported architectures in a list +- Checks `input.image.os` matches Linux +- Verifies `input.image.arch` is in the supported list + +The `os` and `arch` fields come from the image manifest, reflecting the actual +image platform. This works with Docker's automatic platform selection - +policies validate what buildx resolves, not what you specify. + +## Inspect image metadata + +Images contain metadata like environment variables, labels, and working +directories. You can validate these to ensure images meet requirements. + +Check for specific environment variables: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.image.repo == "golang" + input.image.workingDir == "/go" + some ver in input.image.env + startswith(ver, "GOLANG_VERSION=") + some toolchain in input.image.env + toolchain == "GOTOOLCHAIN=local" +} + +decision := {"allow": allow} +``` + +This policy validates the official Go image by checking: + +- The working directory is `/go` +- The environment has `GOLANG_VERSION` set +- The environment includes `GOTOOLCHAIN=local` + +The `input.image.env` field is an array of strings in `KEY=VALUE` format. +Use Rego's `some` iteration to search the array. + +Check image labels: + +```rego +allow if { + input.image.labels["org.opencontainers.image.vendor"] == "Example Corp" + input.image.labels["org.opencontainers.image.version"] != "" +} +``` + +The `labels` field is a map, so you access values with bracket notation. + +## Require attestations and provenance + +Modern images include [attestations](/build/metadata/attestations/): +machine-readable metadata about how the image was built. +[Provenance](/build/metadata/attestations/slsa-provenance/) attestations +describe the build process, and [SBOMs](/build/metadata/attestations/sbom/) +list the software inside. + +Require provenance: + +```rego +package docker + +default allow := false + +allow if input.local + +allow if { + input.image.hasProvenance +} + +decision := {"allow": allow} +``` + +The `hasProvenance` field is `true` when the image has provenance or SBOM +[attestations](../metadata/attestations/_index.md). + +## Verify GitHub Actions signatures + +For images built with GitHub Actions, verify they came from trusted workflows by +inspecting signature metadata: + +```rego +allow if { + input.image.repo == "myapp" + input.image.hasProvenance + some sig in input.image.signatures + valid_github_signature(sig) +} + +# Helper to validate GitHub Actions signature +valid_github_signature(sig) if { + sig.signer.certificateIssuer == "CN=sigstore-intermediate,O=sigstore.dev" + sig.signer.issuer == "https://token.actions.githubusercontent.com" + startswith(sig.signer.buildSignerURI, "https://github.com/myorg/") + sig.signer.runnerEnvironment == "github-hosted" +} + +decision := {"allow": allow} +``` + +This pattern works with any GitHub Actions workflow using Sigstore keyless +signing. The signature metadata provides cryptographic proof of the build's +origin. For complete signature verification examples, see [Example +policies](./examples.md). + +## Combine multiple checks + +Real policies often combine several checks. Multiple conditions in one `allow` +rule means AND - all must be true: + +```rego +package docker + +default allow := false + +allow if input.local + +# Production images need everything +allow if { + input.image.repo == "alpine" + input.image.isCanonical + input.image.hasProvenance +} + +decision := {"allow": allow} +``` + +Multiple `allow` rules means OR - any rule can match: + +```rego +package docker + +default allow := false + +allow if input.local + +# Allow Alpine with strict checks +allow if { + input.image.repo == "alpine" + input.image.isCanonical +} + +# Allow Go with different checks +allow if { + input.image.repo == "golang" + input.image.workingDir == "/go" +} + +decision := {"allow": allow} +``` + +Use this pattern to apply different requirements to different base images. + +## Next steps + +You now understand how to validate container images in build policies. To +continue learning: + +- Learn [Git repository validation](./validate-git.md) for source code inputs +- Browse [Example policies](./examples.md) for complete policy patterns +- Read [Built-in functions](./built-ins.md) for signature verification and + attestation checking +- Check the [Input reference](./inputs.md) for all available image fields diff --git a/content/reference/cli/docker/buildx/policy/_index.md b/content/reference/cli/docker/buildx/policy/_index.md new file mode 100644 index 000000000000..3208684b3544 --- /dev/null +++ b/content/reference/cli/docker/buildx/policy/_index.md @@ -0,0 +1,14 @@ +--- +datafolder: buildx +datafile: docker_buildx_policy +title: docker buildx policy +layout: cli +--- + + diff --git a/content/reference/cli/docker/buildx/policy/eval.md b/content/reference/cli/docker/buildx/policy/eval.md new file mode 100644 index 000000000000..4f3c5b7ea333 --- /dev/null +++ b/content/reference/cli/docker/buildx/policy/eval.md @@ -0,0 +1,14 @@ +--- +datafolder: buildx +datafile: docker_buildx_policy_eval +title: docker buildx policy eval +layout: cli +--- + + diff --git a/content/reference/cli/docker/buildx/policy/test.md b/content/reference/cli/docker/buildx/policy/test.md new file mode 100644 index 000000000000..cbc7672bf922 --- /dev/null +++ b/content/reference/cli/docker/buildx/policy/test.md @@ -0,0 +1,14 @@ +--- +datafolder: buildx +datafile: docker_buildx_policy_test +title: docker buildx policy test +layout: cli +--- + + diff --git a/data/buildx/docker_buildx.yaml b/data/buildx/docker_buildx.yaml index 78b279c5a9e6..0f8cd0c38346 100644 --- a/data/buildx/docker_buildx.yaml +++ b/data/buildx/docker_buildx.yaml @@ -16,6 +16,7 @@ cname: - docker buildx imagetools - docker buildx inspect - docker buildx ls + - docker buildx policy - docker buildx prune - docker buildx rm - docker buildx stop @@ -33,6 +34,7 @@ clink: - docker_buildx_imagetools.yaml - docker_buildx_inspect.yaml - docker_buildx_ls.yaml + - docker_buildx_policy.yaml - docker_buildx_prune.yaml - docker_buildx_rm.yaml - docker_buildx_stop.yaml diff --git a/data/buildx/docker_buildx_bake.yaml b/data/buildx/docker_buildx_bake.yaml index d4460e9c9a21..68fe69e008cf 100644 --- a/data/buildx/docker_buildx_bake.yaml +++ b/data/buildx/docker_buildx_bake.yaml @@ -198,6 +198,16 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: var + value_type: stringArray + default_value: '[]' + description: Set a variable value (e.g., `name=value`) + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false inherited_options: - option: builder value_type: string @@ -578,6 +588,7 @@ examples: |- $ docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets $ docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with 'foo' $ docker buildx bake --set target.platform+=linux/arm64 # appends 'linux/arm64' to the platform list + $ docker buildx bake --set target.contexts.bar=../bar # overrides 'bar' named context ``` > [!NOTE] @@ -596,6 +607,7 @@ examples: |- * `cache-to` * `call` * `context` + * `contexts` * `dockerfile` * `entitlements` * `extra-hosts` diff --git a/data/buildx/docker_buildx_build.yaml b/data/buildx/docker_buildx_build.yaml index aee91492c4d6..6dd7b8b616fc 100644 --- a/data/buildx/docker_buildx_build.yaml +++ b/data/buildx/docker_buildx_build.yaml @@ -332,6 +332,17 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: policy + value_type: stringArray + default_value: '[]' + description: | + Policy configuration (format: `filename=path[,filename=path][,reset=true|false][,disabled=true|false][,strict=true|false][,log-level=level]`) + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: print value_type: string description: Print result of information request (e.g., outline, targets) @@ -376,7 +387,7 @@ options: - option: push value_type: bool default_value: "false" - description: Shorthand for `--output=type=registry` + description: Shorthand for `--output=type=registry,unpack=false` details_url: '#push' deprecated: false hidden: false @@ -704,8 +715,13 @@ examples: |- --build-context=name=VALUE ``` - Define additional build context with specified contents. In Dockerfile the context can be accessed when `FROM name` or `--from=name` is used. - When Dockerfile defines a stage with the same name it is overwritten. + Define additional build context with specified contents. + + In a Dockerfile: + + - the context can be accessed when `FROM name` or `--from=name` is used + - the context overrides a stage called `name` when used as `FROM ... AS name` + - the context overrides a `#syntax` directive when used as `#syntax=name` The value can be a: diff --git a/data/buildx/docker_buildx_dap_build.yaml b/data/buildx/docker_buildx_dap_build.yaml index b7c221c7153f..43cc6a99a711 100644 --- a/data/buildx/docker_buildx_dap_build.yaml +++ b/data/buildx/docker_buildx_dap_build.yaml @@ -321,6 +321,17 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: policy + value_type: stringArray + default_value: '[]' + description: | + Policy configuration (format: `filename=path[,filename=path][,reset=true|false][,disabled=true|false][,strict=true|false][,log-level=level]`) + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: print value_type: string description: Print result of information request (e.g., outline, targets) @@ -363,7 +374,7 @@ options: - option: push value_type: bool default_value: "false" - description: Shorthand for `--output=type=registry` + description: Shorthand for `--output=type=registry,unpack=false` deprecated: false hidden: false experimental: false diff --git a/data/buildx/docker_buildx_debug_build.yaml b/data/buildx/docker_buildx_debug_build.yaml index 4ba3d136fdc3..e3e403930c92 100644 --- a/data/buildx/docker_buildx_debug_build.yaml +++ b/data/buildx/docker_buildx_debug_build.yaml @@ -314,6 +314,17 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: policy + value_type: stringArray + default_value: '[]' + description: | + Policy configuration (format: `filename=path[,filename=path][,reset=true|false][,disabled=true|false][,strict=true|false][,log-level=level]`) + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: print value_type: string description: Print result of information request (e.g., outline, targets) @@ -356,7 +367,7 @@ options: - option: push value_type: bool default_value: "false" - description: Shorthand for `--output=type=registry` + description: Shorthand for `--output=type=registry,unpack=false` deprecated: false hidden: false experimental: false diff --git a/data/buildx/docker_buildx_policy.yaml b/data/buildx/docker_buildx_policy.yaml new file mode 100644 index 000000000000..a905cbc376a5 --- /dev/null +++ b/data/buildx/docker_buildx_policy.yaml @@ -0,0 +1,39 @@ +command: docker buildx policy +short: Commands for working with build policies +long: Commands for working with build policies +pname: docker buildx +plink: docker_buildx.yaml +cname: + - docker buildx policy eval + - docker buildx policy test +clink: + - docker_buildx_policy_eval.yaml + - docker_buildx_policy_test.yaml +inherited_options: + - option: builder + value_type: string + description: Override the configured builder instance + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: debug + shorthand: D + value_type: bool + default_value: "false" + description: Enable debug logging + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false +deprecated: false +hidden: false +experimental: false +experimentalcli: false +kubernetes: false +swarm: false + diff --git a/data/buildx/docker_buildx_policy_eval.yaml b/data/buildx/docker_buildx_policy_eval.yaml new file mode 100644 index 000000000000..ba43380f6c87 --- /dev/null +++ b/data/buildx/docker_buildx_policy_eval.yaml @@ -0,0 +1,65 @@ +command: docker buildx policy eval +short: Evaluate policy for a source +long: Evaluate policy for a source +usage: docker buildx policy eval [OPTIONS] source +pname: docker buildx policy +plink: docker_buildx_policy.yaml +options: + - option: fields + value_type: stringSlice + default_value: '[]' + description: Fields to evaluate + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: filename + value_type: string + default_value: Dockerfile + description: Policy filename to evaluate + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: print + value_type: bool + default_value: "false" + description: Print policy output + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false +inherited_options: + - option: builder + value_type: string + description: Override the configured builder instance + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: debug + shorthand: D + value_type: bool + default_value: "false" + description: Enable debug logging + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false +deprecated: false +hidden: false +experimental: false +experimentalcli: false +kubernetes: false +swarm: false + diff --git a/data/buildx/docker_buildx_policy_test.yaml b/data/buildx/docker_buildx_policy_test.yaml new file mode 100644 index 000000000000..7a18fbc86537 --- /dev/null +++ b/data/buildx/docker_buildx_policy_test.yaml @@ -0,0 +1,54 @@ +command: docker buildx policy test +short: Run policy tests +long: Run policy tests +usage: docker buildx policy test +pname: docker buildx policy +plink: docker_buildx_policy.yaml +options: + - option: filename + value_type: string + default_value: Dockerfile + description: Name of the Dockerfile to validate + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: run + value_type: string + description: Run only tests with name containing this substring + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false +inherited_options: + - option: builder + value_type: string + description: Override the configured builder instance + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false + - option: debug + shorthand: D + value_type: bool + default_value: "false" + description: Enable debug logging + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false +deprecated: false +hidden: false +experimental: false +experimentalcli: false +kubernetes: false +swarm: false +