Getting Started with Chainguard Enforce

Chainguard Enforce User Onboarding

Chainguard Enforce is a supply chain security solution for containerized workloads. Chainguard Enforce enables you to build, manage, ensure continuous compliance, and enforce policies that protect organizations from supply chain threats. Using open source projects and standards that are trusted by the community — like Cosign and Fulcio from the Sigstore project — Chainguard Enforce offers a robust approach to securing your workloads.

This guide will walk you through using Chainguard Enforce on a Kubernetes cluster running on your laptop with kind. We will be using Chainguard Enforce to achieve the following:

  • Policy management — we will create a policy and show it being applied to the cluster, this will involve the use of signed containers and SBOMs (software bills of materials)
  • Observation and monitoring — we will use the chainctl command line tool to understand our images from a security standpoint
  • Enforce — we will verify that Chainguard Enforce stops the deployment of an unsigned image

In this guide, we will set up an example cluster, draft a policy and observe how it behaves, and finally enforce that policy. Ultimately, our goal is to improve our software security in deployment contexts by enforcing the use of signed containers and rejecting any containers that are unsigned.

Prerequisites

Before running Chainguard Enforce locally, you’ll need to ensure you have the following installed:

  • curl — to retrieve files from the web, follow the relevant curl download docs for your machine.
  • Docker — you’ll need Docker installed and running in order to step through this tutorial.
  • kind — to create a kind Kubernetes cluster on our laptop, you can download and install kind for your relevant operating system by following the kind install docs.
  • kubectl — to work with your kind cluster, you can install for your operating system by following the official Kubernetes kubectl documentation.
  • jq — to process JSON, visit the jq downloads page to set it up.
  • For macOS users, you’ll need to update to bash version 4 or higher, which is not preinstalled in the machine. Please follow our guide on how to update your version if you are getting version 3 or below when you run bash --version.

With these prerequisites in place, you’re ready to begin.

Step 1 — Install chainctl

Our command line interface (CLI) tool, chainctl, will help you interact with the account model that Chainguard Enforce provides, and enable you to make queries into the state of your clusters and policies registered with the platform. The tool uses the familiar <context> <noun> <verb> style of CLI interactions. For example, to list groups within the context of Chainguard Identity and Access Management (IAM), you can run chainctl iam groups list to receive relevant output.

Before we begin, let’s move into a directory that we can work in. For our example, we’ll create a new directory called enforce-demo.

mkdir ~/enforce-demo && cd $_

You have two options to install chainctl. For macOS and Linux, you can use the package manager Homebrew.

brew tap chainguard-dev/tap
brew install chainctl

A platform agnostic approach to installing chainctl is through using curl, which you can achieve with the following command.

curl -o chainctl "https://dl.enforce.dev/chainctl/latest/chainctl_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/aarch64/arm64/')"

Move chainctl into your /usr/local/bin directory and elevate its permissions so that it can execute as needed.

sudo install -o $UID -g $GID 0755 chainctl /usr/local/bin/chainctl

Finally, alias its path so that you can use chainctl on the command line.

alias chainctl=/usr/local/bin/chainctl

You can verify that everything was set up correctly by checking the chainctl version.

chainctl version

You should receive output similar to the following.

   ____   _   _      _      ___   _   _    ____   _____   _
  / ___| | | | |    / \    |_ _| | \ | |  / ___| |_   _| | |
 | |     | |_| |   / _ \    | |  |  \| | | |       | |   | |
 | |___  |  _  |  / ___ \   | |  | |\  | | |___    | |   | |___
  \____| |_| |_| /_/   \_\ |___| |_| \_|  \____|   |_|   |_____|
chainctl: Chainguard Control

GitVersion:    <semver version>
GitCommit:     <commit hash>
GitTreeState:  clean
BuildDate:     <date here>
GoVersion:     <compiler version>
Compiler:      gc
Platform:      <your platform>

With chainctl successfully installed, we can continue through the demo. For more details on chainctl installation, please review How to Install chainctl for Chainguard Enforce.

Step 2 — Log in and check IAM group

Chainguard provides a way to organize Policies and Clusters into a hierarchy of groups through its Identity and Access Management (IAM) model. Chainguard Enforce provides a rich IAM model similar to those available through AWS and GCP.

Each Chainguard Policy needs to be associated with a group, and will be effective for that group as well as all the groups descending from it. Each Cluster needs to be associated with a group and will be enforced based on that group’s policies.

When you are invited to Chainguard Enforce, your account will already be associated with a group.

First, authenticate and log in to Enforce with chainctl.

chainctl auth login

You’ll be taken through a workflow with an OIDC provider. You can read more about the various authentication and sign on options in our login documentation.

Alternatively, if you’re trying to access someone else’s Chainguard Enforce installation (like that of a team member), they will need to generate an invite code and share it with you. You can then use that invite code in the following command — replacing $INVITE_CODE — to log in.

chainctl auth login --invite-code $INVITE_CODE

Once you’re logged in, you can check which group or groups you belong to, and grab the ID of the group with chainctl.

chainctl iam groups ls -o table

You may be asked to authenticate to the Chainguard Enforce platform through an OIDC flow within a browser window. Go through the authentication by selecting the relevant accounts.

You’ll receive output in the form of a table of your current group (or groups), similar to the following.

                     ID                    |       NAME          | DESCRIPTION
-------------------------------------------+---------------------+--------------
  b9adda06841c1d34dfa73d5902ed44b5448b7958 | enforce-demo-group  |

Note: If you don’t receive output like the previous example, you can create a new group by running chainctl iam groups create --no-parent to create a new group. The --no-parent flag will ensure that the new group will be a new root group; this means it won’t have any connections to your existing Chainguard resources, making it safe for experimentation. After group creation, you can run chainctl iam groups ls -o table again to retrieve the new group’s ID.

Let’s create a variable that stores that ID for later steps in the tutorial. Replace $GROUP_ID below with the relevant ID; for exmaple, in the case of enforce-demo-group above, you would enter b9adda06841c1d34dfa73d5902ed44b5448b7958 instead of $GROUP_ID in the next command.

export GROUP=$GROUP_ID

You can learn more about our IAM model by reading our Overview of Chainguard Enforce IAM article. This document will provide you with guidance on creating a group hierarchy that enables policies to be inherited from parent groups, and discrete policies for different groups depending on your needs.

Step 3 — Prepare Kubernetes cluster

In order to put Chainguard Enforce into action within a cluster, we’ll now create a Kubernetes cluster using kind. For demonstration purposes, we will name our cluster enforce-demo by passing that to the --name flag, but you may opt to use an alternate name.

kind create cluster --name enforce-demo

You can now install the Chainguard Enforce Agent in your cluster. We are passing the option so that every namespace has policy enforcement by default. You can read more about options at installion in the Configuring Enforcer Options guide

chainctl cluster install --group=$GROUP --private --context kind-enforce-demo --opt=namespace_enforcement_mode=opt-out

Once everything is set up, your terminal output will indicate that the cluster was successfully configured. We now have a Kubernetes cluster setup that’s running an application.

Step 4 — Create a security policy

At this point, we want to roll out a policy ensuring that our development teams only deploy containers that have signatures and SBOMs without disruption.

To achieve this, we will create a new policy to require that only signed containers with SBOMs are deployed. We’ll associate a sample-policy.yaml file with the demo group in our IAM.

cat > sample-policy.yaml <<EOF
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: sample-policy
spec:
  images:
  - glob: "ghcr.io/chainguard-dev/*/*"
  - glob: "ghcr.io/chainguard-dev/*"
  - glob: "index.docker.io/*"
  - glob: "index.docker.io/*/*"
  - glob: "cgr.dev/chainguard/**"
  authorities:
  - keyless:
      url: https://fulcio.sigstore.dev
      identities:
        - issuerRegExp: ".*"
          subjectRegExp: ".*"
EOF

This policy creates a cluster image policy with the Sigstore beta API, and with Fulcio as a keyless authority. Here, we are requiring not only that Chainguard demo images be signed, but that all images from Docker be signed as well. To learn more about how to admit images through the cluster image policy, review the Policy Controller Admission of images documentation.

We have already set up the GROUP variable in with the group we created above in Step 2. Let’s now associate this new policy with that group.

chainctl policies apply -f sample-policy.yaml --group=$GROUP

You should get feedback about the group selected and that the policy was applied, similar to the following.

                             ID                             |     NAME      | DESCRIPTION
------------------------------------------------------------+---------------+--------------
  a4de00fd08b377db719e52b0b19f58bc7ac5b45e/f265297c59250570 | sample-policy |

We have confirmed that we’ve created the sample-policy based on the sample-policy.yaml file, and we are applying it to the demo group that we have set up in our environment. We can ensure that everything is as expected by listing the policies with chainctl. Note that you can pass policy or policies to the command.

chainctl policies ls

Here, you’ll get output on the policy you created. If you were to have another policy applied to this cluster, you would receive that output as well.

You should now be able to review the policy that you set up. With this policy described and connected to our group, we are ready to install Chainguard Enforce into our cluster to gain insight into where our cluster currently stands from a security perspective.

Step 5 — Inspect compliance of containers

Under the hood, Chainguard Enforce leverages upstream Sigstore components like ClusterImagePolicy, a Kubernetes CustomResourceDefinition (CRD). Verify that the sample-policy was distributed to the cluster by using kubectl.

kubectl get clusterimagepolicies

We’ll get feedback that the sample-policy was distributed and how long ago.

NAME              AGE
sample-policy     68s

Next, verify the compliance records of containers via the CLI.

First, obtain the cluster ID and load it into a variable for usage. We are using kubectl to get an UUID (universally unique identifier) that Chainguard Enforce uses to identify the Agent running on your cluster.

export CLUSTER_ID=$(chainctl cluster list -o json | jq -r '.items[0].name')

With this set up, we can run the following command to list the records of the scanned images.

chainctl cluster records list $CLUSTER_ID

If you didn’t specify the $CLUSTER_ID, the CLI will ask you to select from a menu instead.

Your output may be wide, and may have some extra lines. From this output, you should be able to determine the different categories of containers on your cluster, including containers from the vendor (such as GKE or EKS), the Chainguard agent, and the application image itself.

Let’s step through adding new images to the cluster to see how the policy is working. We’ll begin by deploying a generic NGINX image.

kubectl create deployment nginx --image=nginx

Give this some time (up to a few minutes) to populate and then check what’s running now that we have a new image in the cluster.

chainctl cluster records list $CLUSTER_ID

You should get feedback that this fails the policy. That is because this generic NGINX image has neither a signature nor an SBOM.

                              IMAGE                             |        POLICIES        |   WORKLOADS   | LAST SEEN
----------------------------------------------------------------+------------------------+---------------+------------
  index.docker.io/library/nginx@sha256:0b9700…                  | sample-policy:fail:11s | Pod:1         | 82s

Next, let’s pull in an image that has an SBOM and signature. This is an NGINX image from Chainguard.

kubectl create deployment good-nginx --image=ghcr.io/chainguard-dev/nginx-image-demo

Again, check the output with chainctl cluster records list $CLUSTER_ID.

                              IMAGE                             |        POLICIES        |   WORKLOADS   | PACKAGES | LAST SEEN | LAST REFRESHED
----------------------------------------------------------------+------------------------+---------------+----------+-----------+-----------------
  ghcr.io/chainguard-dev/nginx-image-demo@sha256:4b3990…        | sample-policy:pass:2s  | Pod:1         | apk:45   | 47s       | sbom:1s
                                                                |                        |               |          |           | sig:2s

This image passes the policy we set up above because it has both an SBOM and a signature.

Step 6 – Enforce policy

We have improved our compliance by introducing and requiring a signing and SBOM policy. We can check that our policy is enforced by trying to run an unsigned image. We’ll use an unsigned Ubuntu image as an example.

kubectl run not-signed --image=ubuntu

You’ll receive output that this attempt at running an unsigned image has been rejected.

Error from server (BadRequest): admission webhook "enforcer.chainguard.dev" denied the request: validation failed: failed policy: sample-policy

Congratulations! You now have a policy in place to install Cosign, sign container images, and enforce that only signed images are deployed.

Clean up and next steps

If you would like, you can now clean up your work by uninstalling chainctl from the cluster.

chainctl cluster uninstall

You may also want to delete the kind cluster you created.

kind delete cluster --name enforce-demo

To learn more visit our Chainguard Enforce for Kubernetes documentation page. For additional sample policies that you can try with Chainguard Enforce, review our policy catalogue in the Enforce Console.

To understand how Chainguard Enforce offers continuous verification of your workloads, review our page on how continuous verification works and learn how to finetune the timing of continuous verification

If you would like to learn about how you can use this product alongside signing Git commits, check out Chainguard Enforce for Git.

You can read all about software supply chain security on Chainguard Academy.

Last updated: 2023-03-18 15:22