Skip to main content

Sigstore Workload Attestation

By using Teleport's integration with Sigstore, you can lock down access to a workload identity and the resources it protects to only signed container images, reducing the scope for supply chain attacks.

Teleport Enterprise Required

A valid Teleport Enterprise license is required to use the Sigstore attestation feature of Teleport Workload Identity.

How it works

Signing

Artifact signatures and attestations are generated using cosign or another Sigstore-compatible tool such as GitHub's attest-build-provenance action.

In keyless mode, these signatures are signed with a single-use X.509 certificate issued by Fulcio, which encodes the signer's OIDC identity, for example: a user's Google account or a GitHub Actions token.

Users can alternatively opt to sign with a traditional long-lived private/public keypair and distribute the public key to consumers themselves.

Signatures and their associated metadata are recorded in the Rekor transparency log for auditability, non-repudiation, and to timestamp the signature because, due to the short-lived nature of keyless signing certificates, the certificate's validity period (not before, not after) cannot be used.

Users may opt to use an RFC 3161 timestamp authority as well as Rekor, or instead of it if they do not need the other features of a transparency log.

The signature, certificate, proof of transparency log inclusion, and RFC 3161 timestamps are uploaded to the container image registry.

Verification

When a container-based workload connects to tbot's SPIFFE Workload API, tbot will discover which container image the workload is running using the platform specific attestor (i.e. Kubernetes, Docker, or Podman). It will then query the relevant container registry for signatures relating to that specific image digest. These signatures will be sent to the Teleport auth server when requesting a SVID.

If your WorkloadIdentity rules contain a call to sigstore.policy_satisfied, the auth server will load and evaluate the named SigstorePolicy objects.

These policies will be used to determine the "trusted roots" that will be used to verify the signature's authenticity. For keyless signing, this will include the Fulcio and Rekor certificate chains. For traditional key-based signing this will be the signer's public key.

Policies also include a set of requirements about the signatures, for example: which in-toto attestation predicate types are required or whether a cosign simple signing-based artifact signature is required.

SigstorePolicy Resource

kind: sigstore_policy
version: v1
metadata:
  # The name of the SigstorePolicy resource, used as a parameter to the
  # `sigstore.policy_satisfied` rule expression function.
  name: github-provenance
spec:
  # Configuration for Sigstore's keyless mode.
  #
  # Mutually exclusive with `key`.
  keyless:
    # Trusted signing identities. If multiple identities are given, at least one
    # must match in order for the policy to be satisfied.
    identities:
    -
      # Matches the OIDC issuer exactly.
      issuer: https://accounts.google.com
      # Matches the OIDC identity exactly.
      subject: security@example.com
    -
      # Matches the OIDC issuer using a regular expression.
      issuer_regex: ^https://token.actions.githubusercontent.com(/asteroid-earth)?$
      # Matches the OIDC identity using a regular expression.
      subject_regex: ^https://github.com/asteroid-earth/(.*)/\.github/workflows/(.*)@refs/heads/main$

  # List of custom trusted roots that can be supplied when using private Sigstore
  # infrastructure rather than the "Public Good" instance. Roots will be combined
  # such that certificates issued by any of the certificate authorities are trusted.
  #
  # Formatted as `application/vnd.dev.sigstore.trustedroot+json;version=0.1`
  # JSON strings.
  trusted_roots:
    - |
      {
        "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1",
        "certificateAuthorities": [
        ...

  # Configuration for traditional key-based signature verification.
  #
  # Mutually exclusive with `keyless`.
  key:
    # Public key in PEM-encoded DER format. Must be an RSA, ECDSA or Ed25519 key.
    public: |
      -----BEGIN PUBLIC KEY-----
      MCowBQYDK2VwAyEAA6h8OgaAX0htOLNP5hwaENfcivylMa8yBuOqD7k6kYE=
      -----END PUBLIC KEY-----

  # Which signatures and attestations must be present in order for the policy to
  # be satisfied.
  requirements:
    # Whether there must be a cosign simple-signing based artifact signature.
    #
    # Mutually exclusive with `attestations`.
    artifact_signature: false

    # Which attestations must (all) be present.
    #
    # Mutually exclusive with `artifact_signature`.
    attestations:
    -
      # The in-toto predicate type of the required attestation.
      predicate_type: https://slsa.dev/provenance/v1
RSA Padding

When verifying signatures signed with a static RSA keypair, Teleport currently only supports the PKCS#v1.5 padding scheme (used by cosign).

RSA-PSS is currently unsupported.

Example: GitHub Artifact Attestation

When using the attest-build-provenance action from a public repository, GitHub will use Sigstore's Public Good instance of Fulcio and Rekor, so all that must be configured are the trusted identity and required attestations.

kind: sigstore_policy
version: v1
metadata:
  name: github-provenance
spec:
  keyless:
    identities:
      - issuer: https://token.actions.githubusercontent.com
        subject_regex: ^https://github.com/asteroid-earth/(.*)/\.github/workflows/(.*)@refs/heads/main$
  requirements:
    attestations:
      - predicate_type: https://slsa.dev/provenance/v1

GitHub Enterprise customers can use artifact attestation from their private repositories which uses GitHub's internal instance of Fulcio and GitHub's own timestamp authority in place of Rekor.

To support signatures from private repositories, export the trusted roots using the GitHub CLI:

$ gh attestation trusted-root

This will return a newline-delimited list of JSON trusted root objects, which you can use in the keyless section of your policy.

When using GitHub's internal instance of Fulcio, the OIDC issuer in signing certificates will be suffixed with your organization name.

kind: sigstore_policy
version: v1
metadata:
  name: github-provenance
spec:
  keyless:
    identities:
      - issuer_regex: ^https://token.actions.githubusercontent.com(/asteroid-earth)?$
        subject_regex: ^https://github.com/asteroid-earth/(.*)/\.github/workflows/(.*)@refs/heads/main$
    trusted_roots:
      -  |
        {"mediaType":"application/vnd.dev.sigstore.trustedroot+json;version=0.1" ...
      -  |
        {"mediaType":"application/vnd.dev.sigstore.trustedroot+json;version=0.1" ...
  requirements:
    attestations:
      - predicate_type: https://slsa.dev/provenance/v1

Timestamp and transparency log requirements

When using keyless mode, either proof of inclusion in a transparency log or an RFC 3161 timestamp is required in order to trust the keyless certificate.

Therefore, when specifying custom trusted_roots, there must be at least one entry in either the tlogs or timestamp_authorities list. If there are many entries, signatures only need to match one of them, for example: if there are multiple transparency logs configured, a signature only needs to be included in one of them to be validated.

WorkloadIdentity Rules

In order to apply a policy when Teleport is deciding whether to issue a SVID, call the sigstore.policy_satisfied function from your WorkloadIdentity rule expressions.

This function accepts multiple policy names and returns a boolean value indicating whether all of the given policies were satisfied by the signatures presented by tbot.

kind: workload_identity
version: v1
metadata:
  labels:
    application: k8s-service
  name: k8s-workload-identity
spec:
  spiffe:
    id: /k8s/{{ workload.kubernetes.labels["app"] }}
  rules:
    allow:
      - expression: |
          sigstore.policy_satisfied("github-provenance", "security-scan") || sigstore.policy_satisfied("security-team-signoff")

tbot configuration

services:
  - type: workload-identity-api
    listen: unix:///run/tbot/sockets/workload.sock
    selector:
      name: k8s-workload-identity
    attestors:
      sigstore:
        enabled: true
        additional_registries:
          - host: ghcr.io
        credentials_path: /path/to/docker/config.json
        allowed_private_network_prefixes:
          - "192.168.1.42/32"
          - "fd12:3456:789a:1::1/128"

In order for tbot to discover signatures for a workload's container image, you must enable the Sigstore attestor as well as the attestor for your container platform (i.e. Kubernetes, Docker, or Podman).

By default, the Sigstore attestor will look for signatures in the image's source registry, but if you're distributing signatures through another registry you can set additional_registries.

Private registries

If your registry requires authentication, you can use credentials_path to provide a Docker or Podman configuration file where the auths section contains per-registry credentials.

If you don't specify credentials_path, tbot will look in the following places by default:

  • $HOME/.docker/config.json
  • $DOCKER_CONFIG
  • $REGISTRY_AUTH_FILE
  • $XDG_RUNTIME_DIR/containers/auth.json

Because tbot uses Docker's cli package, it also supports credential helpers such as docker-credential-gcr.

Often, the most straightforward way to create a credentials file for tbot is to use docker login and copy the auths and credHelpers sections out of $HOME/.docker/config.json.

By default, tbot will refuse to connect to registries hosted at private network addresses (e.g. in IP ranges designated for private use by RFC 1918) to reduce the surface area for SSRF attacks. If your registry is on a private network, you can add its IP to the allowed_private_network_prefixes list using CIDR notation.

Using Kubernetes registry credentials

If you're running tbot in Kubernetes, you likely already have credentials for your registry configured in order for the kubelet to pull images.

$ kubectl create secret docker-registry docker-credentials \
  --docker-server=<your-registry-server> \
  --docker-username=<your-name> \
  --docker-password=<your-password> \
  --docker-email=<your-email>

To configure the Sigstore attestor to use these credentials, you can mount the secret as a volume in your tbot DaemonSet, and point credentials_path or $DOCKER_CONFIG to it:

spec:
  template:
    spec:
      containers:
        - name: tbot
          volumeMounts:
            - mountPath: /var/run/secrets/docker
              name: docker-credentials
          env:
            - name: DOCKER_CONFIG
              value: /var/run/secrets/docker/.dockerconfigjson
      volumes:
        - name: docker-credentials
          secret:
            secretName: docker-credentials