Sigstore Policy Controller

repository·main·Indexed 41 days ago

https://github.com/sigstore/policy-controller

A Kubernetes admission controller that enforces supply-chain security by validating container image signatures and attestations using Sigstore/Cosign metadata. It supports verification of images signed via custom keys, keyless signing, AWS KMS, GCP KMS, and GitHub Actions, and provides a policy-tester CLI for validating ClusterImagePolicy resources.

Tokens
14.3K
Snippets
49
Records
76
Agent score
52%

What's inside Policy Controller

  1. What is Policy Controller

    main

    policy-controller is a Kubernetes admission controller used to enforce policies on a cluster based on verifiable supply-chain metadata from cosign.

    Key features include:

    • Automatic validation of signatures and attestations on container images.
    • Enforcement configured on a per-namespace basis.
    • Support for multiple keys.
    • Image tag resolution to ensure the image running is identical to the one admitted.
  2. What is HCL (HashiCorp Configuration Language)?

    main

    HCL is a structured configuration language designed to be both human-friendly and machine-friendly, specifically targeted at DevOps tools and servers.

    Key characteristics:

    • JSON Compatibility: HCL is fully JSON compatible. You can use JSON as valid input for any system expecting HCL, which allows machines to generate JSON as an interoperability layer while humans write HCL.
    • Human-Centric: Unlike JSON, HCL supports comments and is designed to be easily read and modified by people.
    • Structured: It supports primitives (strings, numbers, booleans), objects, and lists/arrays.
  3. Use go-retryablehttp for automatic retries and exponential backoff

    main

    The retryablehttp package is a wrapper around the standard net/http library that provides automatic retries and exponential backoff.

    Retry Behavior:

    • Retries are triggered if the client returns an error (e.g., connection errors).
    • Retries are triggered if a 500-range response code is received (except for 501).
    • For requests with a body (POST/PUT), the library supports "rewinding" the request body to allow full re-attempts if the initial request fails.

    Basic Usage: Using the library is nearly identical to using net/http. The returned response is a standard *http.Response.

    resp, err := retryablehttp.Get("/foo")
    if err != nil {
        panic(err)
    }
  4. What is cleanhttp and why use it?

    main

    In Go, http.DefaultClient and http.DefaultTransport are shared global values. While http.Client is safe for concurrent use, modifying the fields of the client struct itself is not thread-safe. If multiple libraries attempt to tweak the shared http.DefaultClient, it can lead to race conditions and unpredictable behavior.

    cleanhttp provides functions to obtain a "clean" http.Client. This client uses the same default values as the Go standard library but is a unique instance that does not share state with other clients, preventing side effects caused by library dependencies modifying global defaults.

  5. Understand the go-sockaddr type hierarchy

    main

    The library uses a union-style hierarchy to represent different socket address types, mapping closely to the C sockaddr structure.

    • SockAddr is the top-level interface.
    • SockAddrUnix represents UNIX domain sockets.
    • IPAddr represents IP-based addresses, which branches into:
      • IPv4Addr (mapping to sockaddr_in)
      • IPv6Addr (mapping to sockaddr_in6)
    +-------------------------------------------------------+
    |                                                       |
    |                        sockaddr                       |
    |                        SockAddr                       |
    |                                                       |
    | +--------------+ +----------------------------------+ |
    | | sockaddr_un  | |                                  | |
    | | SockAddrUnix | |           sockaddr_in{,6}        | |
    | +--------------+ |                IPAddr            | |
    |                  |                                  | |
    |                  | +-------------+ +--------------+ | |
    |                  | | sockaddr_in | | sockaddr_in6 | | |
    |                  | |   IPv4Addr  | |   IPv6Addr  | | |
    |                  | +-------------+ +--------------+ | |
    |                  |                                  | |
    |                  +----------------------------------+ |
    |                                                       |
    +-------------------------------------------------------+
  6. Define Authority rules for signature discovery and validation

    main

    The Authority block defines how signatures are discovered and cryptographically validated. You can use either key or keyless fields for validation. If multiple authorities are specified, any one of them can satisfy the requirement to admit an image.

    Supported validation methods:

    • key: Uses a KeyRef to validate the image.
    • keyless: Uses KeylessRef to verify against a Fulcio instance.
    • static: Applies a static policy without cryptographic validation.
    • ctlog: Verifies the authority against a Rekor instance.
    • rfc3161timestamp: Verifies the signature timestamp against an RFC3161 time-stamping instance.

    Other configuration:

    • name: A unique name for the authority (defaults to authority-<index> if not set). Used for referencing in CIP policies.
    • source: Configures where to consume signatures.
    • attestations: A list of individual attestations to verify once the authority's signature is validated.
    • signatureFormat: Specifies the format (legacy or bundle). Defaults to legacy.
  7. Configure Attestation validation in ClusterImagePolicy

    main

    An Attestation defines the type of attestation to validate and allows you to apply an optional Policy decision to it. The Authority block is used to verify the specified attestation types. If a Policy is provided, it is only applied after the attestation signature has been successfully verified.

    Key fields:

    • name: The name of the attestation, which can be referenced later in the ClusterImagePolicy (CIP) level policy.
    • predicateType: Defines which predicate type to verify (matches cosign verify-attestation options).
    • policy: An optional Policy object that defines matching signatures and attestations.
  8. Configure TLS with rootcerts

    main

    The rootcerts library provides utility functions to populate the RootCAs field of a tls.Config struct. This is useful for loading specific certificate files or paths, and it includes a workaround for a known bug in the Darwin (macOS) implementation of Go's standard library that prevents loading trusted certificates from System and Login keychains when RootCAs is nil.

    func httpClient() (*http.Client, error) {
    	tlsConfig := &tls.Config{}
    	err := rootcerts.ConfigureTLS(tlsConfig, &rootcerts.Config{
    		CAFile:      os.Getenv("MYAPP_CAFILE"),
    		CAPath:      os.Getenv("MYAPP_CAPATH"),
    		Certificate: os.Getenv("MYAPP_CERTIFICATE"),
    	})
    	if err != nil {
    		return nil, err
    	}
    	c := cleanhttp.DefaultClient()
    	t := cleanhttp.DefaultTransport()
    	t.TLSClientConfig = tlsConfig
    	c.Transport = t
    	return c, nil
    }
  9. Set up local development with local-dev

    main

    You can use the local-dev CLI tool to spin up a local Kind Kubernetes cluster for testing local changes.

    Prerequisites:

    • Docker
    • kind
    • ko
    • kubectl

    Setup Steps:

    1. Build the tool:
    make local-dev
    1. Run the setup:
    ./bin/local-dev setup

    Optional Flags:

    • --cluster-name: Specify the name of the cluster.
    • --k8s-version: Specify the Kubernetes version.
    • --registry-url: Specify a registry URL.

    Cleanup:

    ./bin/local-dev clean --cluster-name=<my cluster name>
    make local-dev
    ./bin/local-dev setup
  10. Use a local Kind registry for development

    main

    To use the local Kind registry instead of a live one during development, do not include the --registry-url flag when calling the local-dev CLI. It will default to the local registry.

    Requirement: You must add the following line to your /etc/hosts file before running the CLI: 127.0.0.1 registry.local

  11. Authenticate with Azure Container Registry (ACR) using Service Principals

    main

    For AKS clusters using Service Principals, you should provide the client ID and tenant ID as workload identity annotations during Helm installation.

    helm upgrade --install policy-controller sigstore/policy-controller --version 0.9.0 \
    --set-json webhook.serviceAccount.annotations="{\"azure.workload.identity/client-id\": \"${SERVICE_PRINCIPAL_CLIENT_ID}\", \"azure.workload.identity/tenant-id\": \"${TENANT_ID}\"}"
    helm upgrade --install policy-controller sigstore/policy-controller --version 0.9.0 \
    --set-json webhook.serviceAccount.annotations="{\"azure.workload.identity/client-id\": \"${SERVICE_PRINCIPAL_CLIENT_ID}\", \"azure.workload.identity/tenant-id\": \"${TENANT_ID}\"}"
  12. Wrap, check, and extract errors with errwrap

    main

    The errwrap package provides a standardized way to wrap errors and inspect them later without losing the original error structure. It provides helpers to check for error content (by string or type) and to extract specific underlying errors.

    Wrapping Errors

    Use errwrap.Wrapf to wrap an error with a formatted message. Use the {{err}} placeholder to include the original error in the message.

    Checking Errors

    • errwrap.Contains(err, string): Returns true if the error (or any wrapped error) contains the specified string.
    • errwrap.ContainsType(err, target): Returns true if the error (or any wrapped error) matches the type of the provided target.

    Extracting Errors

    • errwrap.GetType(err, target): Returns the underlying error if it matches the type of target, otherwise returns nil.
    // Wrapping an error
    func tryOpen() error {
    	_, err := os.Open("/i/dont/exist")
    	if err != nil {
    		return errwrap.Wrapf("Doesn't exist: {{err}}", err)
    	}
    	return nil
    }
    
    func main() {
    	err := tryOpen()
    
    	// Checking for string content
    	if errwrap.Contains(err, "does not exist") {
    		// Do something
    	}
    
    	// Checking for a specific type
    	if errwrap.ContainsType(err, new(os.PathError)) {
    		// Do something
    	}
    
    	// Extracting a specific error type
    	perr := errwrap.GetType(err, new(os.PathError))
    }