Product Docs
Open Source
Education
In Chainguard Enforce, assumable identities are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human.
This procedural tutorial outlines how to create an identity using Terraform, and then create a GitLab CI/CD pipeline that will assume the identity to interact with Chainguard resources.
To complete this guide, you will need the following.
terraform
chainctl
We will be using Terraform to create an identity for a GitLab pipeline to assume. This step outlines how to create three Terraform configuration files that, together, will produce such an identity.
To help explain each configuration file’s purpose, we will go over what they do and how to create each file one by one. First, though, create a directory to hold the Terraform configuration and navigate into it.
mkdir ~/enforce-gitlab && cd $_
This will help make it easier to clean up your system at the end of this guide.
main.tf
The first file, which we will call main.tf, will serve as the scaffolding for our Terraform infrastructure.
The file will consist of the following content.
terraform { required_providers { chainguard = { source = "chainguard/chainguard" } } } provider "chainguard" {}
This is a fairly barebones Terraform configuration file, but we will define the rest of the resources in the other two files. In main.tf, we declare and initialize the Chainguard Terraform provider.
To create the main.tf file, run the following command.
cat > main.tf <<EOF terraform { required_providers { chainguard = { source = "chainguard/chainguard" } } } provider "chainguard" {} EOF
Next, you can create the sample.tf file.
sample.tf
sample.tf will create a couple of structures that will help us test out the identity in a workflow.
This Terraform configuration consists of two main parts. The first part of the file will contain the following lines.
resource "chainguard_group" "user-group" { name = "example-group" description = <<EOF This group simulates an end-user group, which the GitLab CI pipeline identity can interact with via the identity in gitlab.tf. EOF }
This section creates a Chainguard Enforce IAM group named example-group, as well as a description of the group. This will serve as some data for the identity — which will be created by the gitlab.tf file — to access when we test it out later on.
example-group
gitlab.tf
The next section contains these lines, which create a sample policy and apply it to the example-group group created in the previous section.
resource "chainguard_policy" "cgr-trusted" { parent_id = chainguard_group.user-group.id document = jsonencode({ apiVersion = "policy.sigstore.dev/v1beta1" kind = "ClusterImagePolicy" metadata = { name = "trust-any-cgr" } spec = { images = [{ glob = "cgr.dev/**" }] authorities = [{ static = { action = "pass" } }] } }) }
This policy trusts everything coming from the Chainguard Registry. Because this policy is broadly permissive, it wouldn’t be practical or secure to use in a real-world scenario. Like the example group, this policy serves as some data for the GitLab pipeline to inspect after it assumes the Chainguard identity.
Create the sample.tf file with the following command.
cat > sample.tf <<EOF resource "chainguard_group" "user-group" { name = "example-group" description = <<EOF This group simulates an end-user group, which the GitLab CI pipeline identity can interact with via the identity in gitlab.tf. EOF } resource "chainguard_policy" "cgr-trusted" { parent_id = chainguard_group.user-group.id document = jsonencode({ apiVersion = "policy.sigstore.dev/v1beta1" kind = "ClusterImagePolicy" metadata = { name = "trust-any-cgr" } spec = { images = [{ glob = "cgr.dev/**" }] authorities = [{ static = { action = "pass" } }] } }) } EOF
Now you can move on to creating the last of our Terraform configuration files, gitlab.tf.
The gitlab.tf file is what will actually create the identity for your GitLab CI pipeline workflow to assume. The file will consist of four sections, which we’ll go over one by one.
The first section creates the identity itself.
resource "chainguard_identity" "gitlab" { parent_id = chainguard_group.user-group.id name = "gitlab-ci" description = <<EOF This is an identity that authorizes Gitlab CI in this repository to assume to interact with chainctl. EOF claim_match { issuer = "https://gitlab.com" subject = "project_path:<group_name>/<project_name>:ref_type:branch:ref:main" audience = "https://gitlab.com" } }
First this section creates a Chainguard Identity tied to the chainguard_group created by the sample.tf file; namely, the example-group group. The identity is named gitlab-ci and has a brief description.
chainguard_group
gitlab-ci
The most important part of this section is the claim_match. When the GitLab pipeline tries to assume this identity later on, it must present a token matching the issuer, subject, and audience specified here in order to do so. The issuer is the entity that creates the token, the subject is the entity that the token represents (here, the GitLab pipeline), and the audience is the intended recipient of the token.
claim_match
issuer
subject
audience
In this case, the issuer field points to https://gitlab.com, the issuer of JWT tokens for GitLab pipelines. Likewise, the audience field also points to https://gitlab.com. This will work for demonstration purposes, but if you’re taking advantage of GitLab’s support for custom audiences then be sure to change this to the appropriate audience.
https://gitlab.com
The GitLab documentation provides several examples of subject claims which you can refer to if you want to construct a subject claim specific to your needs. For the purposes of this guide, though, you will need to replace <group_name> and <project_name> with the name of your GitLab group and project names, respectively.
<group_name>
<project_name>
The next section will output the new identity’s id value. This is a unique value that represents the identity itself.
id
output "gitlab-identity" { value = chainguard_identity.gitlab.id }
The section after that looks up the viewer role.
viewer
data "chainguard_roles" "viewer" { name = "viewer" }
The final section grants this role to the identity on the example-group.
resource "chainguard_rolebinding" "view-stuff" { identity = chainguard_identity.gitlab.id group = chainguard_group.user-group.id role = data.chainguard_roles.viewer.items[0].id }
Run the following command to create this file with each of these sections. Be sure to change the subject value to align with your own GitLab project. For example, if your GitLab repository is located at gitlab.com/OrgName/repo-name.git you would set the subject value to "project_path:OrgName/repo-name:ref_type:branch:ref:main.
gitlab.com/OrgName/repo-name.git
"project_path:OrgName/repo-name:ref_type:branch:ref:main
cat > gitlab.tf <<EOF resource "chainguard_identity" "gitlab" { parent_id = chainguard_group.user-group.id name = "gitlab-ci" description = <<EOF This is an identity that authorizes GitLab CI in this repository to assume to interact with chainctl. EOF claim_match { issuer = "https://gitlab.com" subject = "project_path:<group_name>/<project_name>:ref_type:branch:ref:main" audience = "https://gitlab.com" } } output "gitlab-identity" { value = chainguard_identity.gitlab.id } data "chainguard_roles" "viewer" { name = "viewer" } resource "chainguard_rolebinding" "view-stuff" { identity = chainguard_identity.gitlab.id group = chainguard_group.user-group.id role = data.chainguard_roles.viewer.items[0].id } EOF
Following that, your Terraform configuration will be ready. Now you can run a few terraform commands to create the resources defined in your .tf files.
.tf
First, run terraform init to initialize Terraform’s working directory.
terraform init
Then run terraform plan. This will produce a speculative execution plan that outlines what steps Terraform will take to create the resources defined in the files you set up in the last section.
terraform plan
Then apply the configuration.
terraform apply
Before going through with applying the Terraform configuration, this command will prompt you to confirm that you want it to do so. Enter yes to apply the configuration.
yes
. . . Plan: 4 to add, 0 to change, 0 to destroy. Changes to Outputs: + gitlab-identity = (known after apply) Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value:
After pressing ENTER, the command will complete and will output an gitlab-ci value.
ENTER
. . . pply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: gitlab-identity = "<your actions identity>"
This is the identity’s UIDP (unique identity path), which you configured the gitlab.tf file to emit in the previous section. Note this value down, as you’ll need it to set up the GitLab CI pipeline you’ll use to test the identity. If you need to retrieve this UIDP later on, though, you can always run the following chainctl command to obtain a list of the UIDPs of all your existing identities.
chainctl iam identities ls
Note that you may receive a PermissionDenied error part way through the apply step. If so, run chainctl auth login once more, and then terraform apply again to resume creating the identity and resources.
PermissionDenied
chainctl auth login
You’re now ready to create a GitLab CI pipeline which you’ll use to test out this identity.
From the GitLab Dashboard, select Projects in the left-hand sidebar menu. From there, click on the project you specified in the subject claim to be taken to the project overview. There, in the list of the repository’s contents, there will be a file named .gitlab-ci.yml. This is a special file that’s required when using GitLab CI/CD, as it contains the CI/CD configuration.
.gitlab-ci.yml
Click on the .gitlab-ci.yml file, then click the Edit button and select an option for editing the file. For the purpose of this guide, delete whatever content is in this file to start and replace it with the following.
image: cgr.dev/chainguard/wolfi-base stages: - assume-and-explore assume-and-explore: id_tokens: ID_TOKEN_1: aud: https://gitlab.com stage: assume-and-explore script: - | # Install chainctl wget -O chainctl "https://dl.enforce.dev/chainctl/latest/chainctl_linux_$(uname -m)" chmod +x chainctl mv chainctl /usr/bin # Assume chainctl auth login \ --identity-token $ID_TOKEN_1 \ --identity <your gitlab identity> # Explore chainctl policy ls
Let’s go over what this configuration does.
First, GitLab requires that pipelines have a shell. To this end, this configuration uses the cgr.dev/chainguard/wolfi-base image since it includes the sh shell.
cgr.dev/chainguard/wolfi-base
sh
Next, this configuration creates a JSON Web Token (JWT) with an id_tokens block that will allow the job to be able to fetch an OIDC token and authenticate with Chainguard Enforce. GitLab requires that any JWTs created in this manner must include an aud keyword. In this case, it should align with the audience associated with the Chainguard identity created in the gitlab.tf file: https://gitlab.com.
id_tokens
aud
Following that, the job runs a few commands to download and install chainctl. It then uses chainctl, the JWT, and the Chainguard identity’s id value to log in to Chainguard Enforce under the assumed identity. Be sure to replace <your gitlab identity> with the identity UIDP you noted down in the previous section.
<your gitlab identity>
After logging in, the pipeline is able to run any chainctl command under the assumed identity. To test out this ability, this configuration runs the chainctl policies ls command to list all the policies associated with the example-group group.
chainctl policies ls
After updating the configuration, commit the changes and the pipeline will run automatically. A status box in the dashboard will let you know whether the pipeline runs successfully.
Click the View Pipeline button, and then click the assume-and-explore job button to open the job’s output from the last run. The following lines should appear near the bottom of this output.
. . . ID | NAME | DESCRIPTION | MODE ------------------------------------------------------------+---------------+-------------+----------- 4094ca0d1d52b57c89a8eee9f9c3631db0575b3b/aa02f5371fc4075c | trust-any-cgr | | ENFORCED . . .
This indicates that the GitLab CI/CD pipeline did indeed assume the identity and run the chainctl policy ls command, returning the policy you created in the sample.tf file.
chainctl policy ls
If you’d like to experiment further with this identity and what the workflow can do with it, there are a few parts of this setup that you can tweak. For instance, if you’d like to give this identity different permissions you can change the role data source to the role you would like to grant.
data "chainguard_roles" "editor" { name = "editor" }
To retrieve a list of all the roles available to your Chainguard Enforce installation — including any custom roles — you can run the following command.
chainctl iam roles list
You can also edit the pipeline itself to change its behavior. For example, instead of inspecting the policies the identity has access to, you could have the workflow inspect the groups like in the following exmaple.
# Explore chainctl iam groups ls
Of course, the GitLab pipeline will only be able to perform certain actions on certain resources, depending on what kind of access you grant it.
To remove the resources Terraform created, you can run the terraform destroy command.
terraform destroy
This will destroy the sample policy, the role-binding, and the identity created in this guide. However, you’ll need to destroy the example-group group yourself with chainctl.
chainctl iam groups rm example-group
You can then remove the working directory to clean up your system.
rm -r ~/enforce-glitlab/
Following that, all of the example resources created in this guide will be removed from your system.
For more information about how assumable identities work in Chainguard Enforce, check out our conceptual overview of assumable identities. Additionally, the Terraform documentation includes a section on recommended best practices which you can refer to if you’d like to build on this Terraform configuration for a production environment. Likewise, for more information on using GitLab CI/CD pipelines, we encourage you to check out the official documentation on the subject.