diff --git a/_vale/config/vocabularies/Docker/accept.txt b/_vale/config/vocabularies/Docker/accept.txt index 0370dcd7c16e..9c3e581a483f 100644 --- a/_vale/config/vocabularies/Docker/accept.txt +++ b/_vale/config/vocabularies/Docker/accept.txt @@ -169,6 +169,7 @@ Qualcomm Quickview rebalance reimplement +Rego Rekor rollback rootful @@ -179,6 +180,7 @@ scrollable SELinux Slack snapshotters? +Sigstore Snyk Solr SonarQube @@ -290,4 +292,3 @@ Zsh [Vv]irtiofs [Vv]irtualize [Ww]alkthrough - 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..0c32157f558a --- /dev/null +++ b/content/manuals/build/policies/_index.md @@ -0,0 +1,122 @@ +--- +title: Validating build inputs with policies +linkTitle: Validating builds +description: "" +weight: 10 +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." + +## 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. + +## 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 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..395f62956df8 --- /dev/null +++ b/content/manuals/build/policies/built-ins.md @@ -0,0 +1,362 @@ +--- +title: Built-in functions +description: Buildx includes built-in helper functions to make writing policies easier +weight: 60 +--- + + + +Buildx provides built-in functions for common security checks like signature +verification, attestation validation, and version comparison. These functions +simplify policy writing by handling complex cryptographic operations. + +## Image verification functions + +### `trusted_github_builder(repo)` + +Verifies that an image was built by GitHub Actions using GitHub's trusted +builder infrastructure. + +**Parameters:** + +- `repo` (string) - GitHub repository in `owner/repo` format + +**Returns:** Boolean - `true` if the image has valid GitHub Actions build +attestations + +**Example:** + +```rego +# Require images are built by trusted GitHub Actions +allow if { + input.image.repo == "tonistiigi/xx" + trusted_github_builder("tonistiigi/xx") +} +``` + +This function checks that: + +- The image has provenance attestations +- The attestations were created by GitHub Actions +- The build came from the specified repository's workflows + +Use this to ensure images come from official GitHub Actions builds rather than +being pushed manually or built elsewhere. + +### `trusted_github_builder_ref(repo, ref)` + +Like `trusted_github_builder()`, but also validates the image was built from a +specific Git reference. + +**Parameters:** + +- `repo` (string) - GitHub repository in `owner/repo` format +- `ref` (string) - Git reference (branch, tag, or SHA) + +**Returns:** Boolean - `true` if the image was built by GitHub Actions from the +specified ref + +**Example:** + +```rego +# Only allow images built from the main branch +allow if { + input.image.repo == "myorg/myapp" + trusted_github_builder_ref("myorg/myapp", "refs/heads/main") +} +``` + +Use this to restrict images to specific branches or tags, ensuring production +builds only come from release branches. + +### `sigstore_self_signed(image, repo)` + +Verifies an image has a valid Sigstore keyless signature. + +**Parameters:** + +- `image` (object) - The image input object (`input.image`) +- `repo` (string) - Expected repository name + +**Returns:** Boolean - `true` if the image has a valid Sigstore signature + +**Example:** + +```rego +# Require Sigstore signatures +allow if { + input.image.repo == "crazymax/diun" + sigstore_self_signed(input.image, "crazymax/diun") +} +``` + +Sigstore provides keyless signing using OIDC identity. This function validates +the signature without requiring you to manage public keys. + +You can inspect additional signature metadata in the policy: + +```rego +allow if { + input.image.repo == "myorg/myapp" + sigstore_self_signed(input.image, "myorg/myapp") + # Verify the build workflow + input.image.signature.buildSignerURI == "https://github.com/myorg/ci/.github/workflows/build.yml@refs/heads/main" + # Verify it ran on GitHub's infrastructure + input.image.signature.runnerEnvironment == "github_hosted" +} +``` + +### `docker_hardened_image(image, name)` + +Verifies an image is from Docker's hardened image program. + +**Parameters:** + +- `image` (object) - The image input object (`input.image`) +- `name` (string) - Expected hardened image name + +**Returns:** Boolean - `true` if the image is a valid Docker hardened image + +**Example:** + +```rego +# Require hardened Go images +allow if { + input.image.repo == "docker/dhi-golang" + docker_hardened_image(input.image, "docker/dhi-golang") +} +``` + +Docker hardened images receive additional security testing, vulnerability +scanning, and timely security updates. Use this function to enforce hardened +base images for production builds. + +## Git verification functions + +### `git_signed(git, pubkey)` + +Verifies that a Git commit or tag is signed with a specific PGP public key. + +**Parameters:** + +- `git` (object) - The Git input object (`input.git`) +- `pubkey` (string) - Path to PGP public key file (relative to policy + directory) + +**Returns:** Boolean - `true` if the Git ref is signed with the specified key + +**Example:** + +```rego +# Require signed tags from maintainer key +allow if { + input.git.host == "github.com" + input.git.remote == "myorg/myapp" + input.git.tagName != "" + git_signed(input.git, "keys/maintainer.asc") +} +``` + +Place the public key file in your policy directory: + +```text +policy/ +├── git.rego +└── keys/ + └── maintainer.asc +``` + +### `git_signed_github_user(git, user)` + +Verifies that a Git commit or tag is signed by a specific GitHub user's +verified signing key. + +**Parameters:** + +- `git` (object) - The Git input object (`input.git`) +- `user` (string) - GitHub username + +**Returns:** Boolean - `true` if the Git ref is signed by the user's GitHub key + +**Example:** + +```rego +# Require signatures from trusted maintainers +maintainers := ["tonistiigi", "crazy-max", "jsternberg"] + +allow if { + input.git.host == "github.com" + input.git.remote == "moby/buildkit" + some i + git_signed_github_user(input.git, maintainers[i]) +} +``` + +This function queries GitHub's API to verify the signature matches a key +associated with the user's account. It works for both PGP and SSH signatures. + +Use this to validate releases are signed by known maintainers without manually +managing public keys. + +## HTTP verification functions + +### `gpg_signed(http, pubkey)` + +Verifies that an HTTP download has a detached GPG signature file. + +**Parameters:** + +- `http` (object) - The HTTP input object (`input.http`) +- `pubkey` (string) - Path to PGP public key file (relative to policy + directory) + +**Returns:** Boolean - `true` if a valid `.sig` or `.asc` signature file exists + +**Example:** + +```rego +# Require GPG signatures for downloads +allow if { + input.http.url + startswith(input.http.url, "https://nginx.org/download/") + gpg_signed(input.http, "keys/nginx-signing-key.asc") +} +``` + +This function looks for a signature file with the same name as the download but +with a `.sig` or `.asc` extension. For example, if downloading +`nginx-1.24.0.tar.gz`, it looks for `nginx-1.24.0.tar.gz.sig` and verifies the +signature against the provided public key. + +Place public keys in your policy directory: + +```text +policy/ +├── downloads.rego +└── keys/ + ├── nginx-signing-key.asc + └── vendor.asc +``` + +### `github_attested(repo)` + +Verifies that a GitHub release artifact has GitHub's official build +attestations. + +**Parameters:** + +- `repo` (string) - GitHub repository in `owner/repo` format + +**Returns:** Boolean - `true` if the artifact has valid GitHub attestations + +**Example:** + +```rego +# Require attestations for GitHub releases +allow if { + input.http.url + startswith(input.http.url, "https://github.com/cli/cli/releases/download/") + github_attested("cli/cli") +} +``` + +GitHub generates attestations for release artifacts uploaded through GitHub +Actions. This function verifies the artifact was built and published by the +repository's official workflows, not manually uploaded by someone with write +access. + +Use this to ensure you're downloading official releases rather than potentially +compromised files. + +## Version comparison functions + +### `version_gt(str)` + +Compares version strings and returns true if the input version is strictly +greater than the specified minimum. + +**Parameters:** + +- `str` (string) - Minimum version to compare against + +**Returns:** Boolean - `true` if input version is greater than the minimum + +**Example:** + +```rego +# Require golangci-lint version greater than v2.0.4 +allow if { + input.image.repo == "golangci/golangci-lint" + version_gt("v2.0.4") +} +``` + +This function uses semantic version comparison, understanding version formats +like `v1.2.3`, `1.2.3`, `v1.2.3-beta`, etc. + +### `version_gte(str)` + +Compares version strings and returns true if the input version is greater than +or equal to the specified minimum. + +**Parameters:** + +- `str` (string) - Minimum version to compare against + +**Returns:** Boolean - `true` if input version is >= the minimum + +**Example:** + +```rego +# Require at least Python 3.11 +allow if { + input.image.repo == "library/python" + startswith(input.image.tag, "3.") + version_gte("3.11") +} +``` + +Use version comparison functions to enforce minimum versions for tools, base +images, or dependencies with known security vulnerabilities in older releases. + +## Using built-in functions + +Built-in functions integrate with standard Rego syntax. Combine them with other +conditions to build comprehensive policies: + +```rego +package docker + +default allow := false + +# Production images need multiple checks +allow if { + input.image.repo == "myorg/backend" + input.image.isCanonical + trusted_github_builder_ref("myorg/backend", "refs/heads/main") + input.image.hasProvenance + input.image.hasSBOM +} + +# Dependencies need signatures +allow if { + input.http.url + startswith(input.http.url, "https://releases.myvendor.com/") + gpg_signed(input.http, "keys/vendor.asc") + input.http.checksum != "" +} + +decision := {"allow": allow} +``` + +All built-in functions return boolean values, so they work naturally in `allow` +rules alongside other conditions. + +## Next steps + +- Browse [Example policies](./examples.md) to see built-in functions in context +- Read the [Input reference](./inputs.md) for available input fields +- Learn about [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) + for advanced policy patterns diff --git a/content/manuals/build/policies/examples.md b/content/manuals/build/policies/examples.md new file mode 100644 index 000000000000..4b9c844875b9 --- /dev/null +++ b/content/manuals/build/policies/examples.md @@ -0,0 +1,447 @@ +--- +title: Example policies +linkTitle: Examples +description: Browse the example library of build policies +weight: 40 +--- + +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 + +## 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 +} + +# 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 and GitHub +Container Registry, 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", "registry.company.com"] + +allow if { + input.image.host in allowed_registries +} + +decision := {"allow": allow} +``` + +This policy restricts image pulls to approved registries. Customize and add +your internal registry to the list. + +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 +} + +decision := {"allow": allow} +``` + +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.fullURL == 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 + trusted_github_builder_ref(repo, "refs/heads/main") +} + +# Allow official base images with digests +allow if { + input.image.repo == "alpine" + 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. Base +images must use digests. + +Customize: + +- Replace `allowed_repos` with your image names +- Update the GitHub repository in `trusted_github_builder_ref()` +- 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.fullURL == "https://github.com/moby/buildkit.git" +} + +is_version_tag if { + is_buildkit + regex.match(`^v[0-9]+\.[0-9]+\.[0-9]+$`, input.git.tagName) +} + +# TODO: Add maintainer GitHub usernames +maintainers := ["tonistiigi", "crazy-max", "jsternberg"] + +# Version tags must be signed by maintainers +allow if { + is_version_tag + some maintainer in maintainers + git_signed_github_user(input.git, maintainer) +} + +# 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. + +Customize: + +- Replace the repository URL in `is_buildkit` +- Update the `maintainers` list with GitHub usernames +- 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 == "" + # TODO: Add your approved base images + input.image.repo in ["alpine", "golang", "node"] + 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. + +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 +allowed_images := { + "alpine": "sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412", + "golang": "sha256:abc123...", +} + +allow if { + input.image + some repo, digest in allowed_images + 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.fullURL == 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. + +## Next steps + +- 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..8913d90fdb4c --- /dev/null +++ b/content/manuals/build/policies/inputs.md @@ -0,0 +1,464 @@ +--- +title: Input reference +description: Reference documentation for policy input fields +weight: 50 +--- + +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. Empty for Docker Hub images. + +```rego +# Only allow images from specific registries +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.hasSBOM` + +Boolean indicating if the image has an SBOM (Software Bill of Materials) +attestation. + +```rego +# Require SBOM attestations +allow if { + input.image.hasSBOM +} +``` + +#### `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 repository path without the host. + +```rego +allow if { + input.git.remote == "moby/buildkit" +} +``` + +#### `input.git.fullURL` + +The complete Git URL. + +```rego +allow if { + startswith(input.git.fullURL, "https://github.com/") +} +``` + +#### `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, timestamp +- `committer` - Committer name, email, timestamp +- `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, timestamp +- `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" + git_signed_github_user(input.git, "maintainer") +} +``` + +#### `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 signature verification and + attestation checking +- 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..fbe7eec0d9f2 --- /dev/null +++ b/content/manuals/build/policies/intro.md @@ -0,0 +1,258 @@ +--- +title: Introduction to build policies +linkTitle: Introduction +description: Get started with writing and evaluating build policies +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.xy 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. + +## 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. + +## 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: + +- 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 +- 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/validate-git.md b/content/manuals/build/policies/validate-git.md new file mode 100644 index 000000000000..90307122b870 --- /dev/null +++ b/content/manuals/build/policies/validate-git.md @@ -0,0 +1,423 @@ +--- +title: Validating Git repositories +linkTitle: Git validation +description: Write policies to validate Git repositories used in your builds +weight: 30 +--- + +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.timestamp` - When the commit was authored +- `committer.name` - Committer's name +- `committer.email` - Committer's email +- `committer.timestamp` - 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.timestamp` - When the tag was created +- `message` - Tag message +- `PGPSignature` - GPG 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 GitHub signatures + +For GitHub repositories, use the `git_signed_github_user()` function to verify +tags are signed by trusted maintainers: + +```rego +package docker + +default allow = false + +allow if input.local + +maintainers = ["tonistiigi", "crazy-max", "jsternberg"] + +allow if { + input.git.remote == "https://github.com/moby/buildkit.git" + some maintainer in maintainers + git_signed_github_user(input.git, maintainer) +} + +decision := {"allow": allow} +``` + +This function verifies the Git tag or commit is signed with a key that GitHub +associates with the specified user. 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) +} + +maintainers = ["tonistiigi", "crazy-max"] + +# Version tags must be signed +allow if { + is_version_tag + some maintainer in maintainers + git_signed_github_user(input.git, maintainer) +} + +# 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..533dd7ce2403 --- /dev/null +++ b/content/manuals/build/policies/validate-images.md @@ -0,0 +1,349 @@ +--- +title: Validating image inputs +linkTitle: Image validation +description: Write policies to validate container images used in your builds +weight: 20 +--- + +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. + +## 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 have an +empty `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: machine-readable metadata about how the +image was built. Provenance attestations describe the build process, and SBOMs +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). + +For images built with GitHub Actions, verify they came from trusted workflows: + +```rego +allow if { + input.image.repo == "tonistiigi/xx" + trusted_github_builder("tonistiigi/xx") +} + +decision := {"allow": allow} +``` + +The `trusted_github_builder()` function verifies the image was by Docker's +trusted GitHub Actions builder, using the [reusable GitHub Actions +workflow](https://github.com/docker/github-builder-experimental). For more +verification functions, including Sigstore signature verification, refer to +[Built-in functions](./built-ins.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