Gatekeeper Policies with Shipwright

Restricting Shipwright to trusted sources using Gatekeeper

Gatekeeper is a customizable admission webhook for Kubernetes, which allows you to configure policy over what resources can be created in the cluster. In particular, we can use Gatekeeper to add policy to Shipwright Builds. In this example, you can see how you can use a policy to control what source repositories Shipwright is allowed to build, so that you can have more control over what code executes inside your cluster.

Gatekeeper

Only allow builds of allowed sources

Unless you are able to build images without root access in the build-process, there are risks to building arbitrary Open Container Initiative (OCI) images within your cluster. Because of these risks, an organization may want to limit builds to trusted sources, such as specific github organizations or an internally hosted git server. This is an example of how that can be configured using Gatekeeper.

Step 0. Install Gatekeeper

If you do not have Gatekeeper installed already, see install instructions here. These examples were tested with version 3.4.

At the time of writing 3.5 has an open issue with parameters, but there is an open PR to fix it.

Step 1. Configure Gatekeeper to watch for Build resources

Either Create or append to your gatekeeper-system config. This configmap specifies all resources that Gatekeeper will monitor.

apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
  name: config
  namespace: "gatekeeper-system"
spec:
  sync:
    syncOnly:
      - group: "shipwright.io"
        version: "v1alpha1"
        kind: "Build"

Step 2. Create the ShipwrightAllowlist Constraint Template

This constraint template will let us create our ShipwrightAllowlist; in the parameters to the ShipwrightAllowlist we will specify the allowed sources, and this constraint template will apply the logic.

This constraint template is based on this example in the Gatekeeper docs.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: shipwrightallowlist
spec:
  crd:
    spec:
      names:
        kind: ShipwrightAllowlist
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package shipwrightallowlist

        violation[{"msg": msg}] {
          input.review.object.kind == "Build"
          repo_url := input.review.object.spec.source.url
          # Remove the protocol from the url
          repo = strings.replace_n({
            "https://": "",
            "http://": "",
            "git://": "",
            "ssh://": "",
          }, repo_url)

          # is the repo in the allowlist?
          allowlist := [
            good | source = input.parameters.allowedsources[_];
            good = startswith(repo, source)
          ]
          not any(allowlist)

          msg := sprintf("The Build repo has not been pre-approved: %v. Allowed sources are: %v", [repo, input.parameters.allowedsources])
        }        

Step 3. Create your ShipwrightAllowlist constraint

The ConstraintTemplate we created above creates a new Custom Resource Definition (CRD) called ShipwrightAllowlist. With this CRD created, we can define a list of allowed sources to build from!

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: ShipwrightAllowlist
metadata:
  name: shipwrightallowlist
spec:
  match:
    kinds:
      - apiGroups: ["shipwright.io"]
        kinds: ["Build"]
  parameters:
    # Remember to terminate the sources with a `/` at the end.
    # Don't include the protocol. I.e.
    # GOOD: "github.com/shipwright-io/"
    # BAD:  "https://github.com/shipwright-io"
    allowedsources:
      - "github.com/shipwright-io/"

Step 4. Test it out!

An allowed Build

This Build below will be created, as is under the github.com/shipwright-io/ organization, so it is allowed by our policy.

# sample-go-build.yaml
apiVersion: shipwright.io/v1alpha1
kind: Build
metadata:
  name: buildah-golang-build
spec:
  source:
    # We trust github.com/shipwright-io/
    url: https://github.com/shipwright-io/sample-go
    contextDir: docker-build
  strategy:
    name: buildah
    kind: ClusterBuildStrategy
  dockerfile: Dockerfile
  output:
    image: image-registry.openshift-image-registry.svc:5000/build-examples/taxi-app

Create it

$ kubectl apply -f sample-go-build.yaml
build.shipwright.io/buildah-golang-build created

A disallowed Build

However the build below will not be created, as it belongs to github.com/docker-library/

# hello-world-build.yaml
apiVersion: shipwright.io/v1alpha1
kind: Build
metadata:
  name: kaniko-hello-world-build
  annotations:
    build.shipwright.io/build-run-deletion: "true"
spec:
  source:
    # Not an approved source!!!
    url: https://github.com/docker-library/hello-world
    contextDir: .
  strategy:
    name: kaniko
    kind: ClusterBuildStrategy
  dockerfile: Dockerfile.build
  output:
    image: image-registry.openshift-image-registry.svc:5000/build-examples/hello-world

Attempting to create this will yield an error.

$ kubectl apply -f hello-world-build.yaml
Error from server ([shipwrightallowlist] The Build repo has not been pre-approved: github.com/docker-library/hello-world. Allowed sources are: ["github.com/shipwright-io/"]): error when creating "hello-world-build.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [shipwrightallowlist] The Build repo has not been pre-approved: github.com/docker-library/hello-world. Allowed sources are: ["github.com/shipwright-io/"]

That’s it!

With just a few yaml files, we can add layers of protection to the cluster. You can checkout Gatekeeper to learn more about the Kubernetes admission webhook, and also checkout Open Policy Agent which powers Gatekeeper and provides the policy language.

Last modified August 14, 2023: Fix broken build links (006a431)