Create an Assumable Identity for a GitLab CI/CD Pipeline
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.
Prerequisites
To complete this guide, you will need the following.
terraform
installed on your local machine. Terraform is an open-source Infrastructure as Code tool which this guide will use to create various cloud resources. Follow the official Terraform documentation for instructions on installing the tool.chainctl
— the Chainguard Enforce command line interface tool — installed on your local machine. Follow our guide on How to Installchainctl
to set this up.- A GitLab project and CI/CD pipeline you can use to test out the identity you’ll create. GitLab provides a quickstart tutorial on creating your first pipeline which can be useful for getting a testing pipeline up and running.
Creating Terraform Files
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.
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
.
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.
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.
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.
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.
The next section will output the new identity’s id
value. This is a unique value that represents the identity itself.
output "gitlab-identity" {
value = chainguard_identity.gitlab.id
}
The section after that looks up the viewer
role.
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
.
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.
Creating Your Resources
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.
. . .
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.
. . .
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.
You’re now ready to create a GitLab CI pipeline which you’ll use to test out this identity.
Testing the identity with a GitLab CI/CD Pipeline
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.
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.
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
.
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.
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.
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.
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.
Removing Sample Resources
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.
Learn more
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.
Last updated: 2023-09-22 08:48