Chainguard Terraform Provider is now available
What is a Terraform Provider?
If you are not familiar with Terraform, it is an infrastructure as code tool that allows users to declaratively configure resources in cloud providers like AWS and GCP, SaaS platforms, and many other API driven environments. The Terraform engine takes care of tracking the current state of declared resources and creates, updates, or destroys them as needed to reconcile the desired and actual states of the world.
Terraform providers are written by third party developers to allow Terraform to manage resources in their environment. There are thousands of providers available on the Terraform Registry. Chances are if you are using any service with an API, it has a Terraform provider available.
At Chainguard, we use Terraform extensively; not only to manage our infrastructure (i.e. Infrastructure as Code, or IaC), but also to declaratively build and maintain Chainguard Images. See our previous blog post on Images as Code for a history and an in-depth description of the tools we use.
How to use the Chainguard Terraform Provider
Configuring the provider
The Chainguard Terraform Provider enables users to manage resources on the Chainguard Platform, such as identities, role-bindings, custom roles, and more. To get started, include Chainguard in the block of required providers.
terraform {
required_providers {
chainguard = {source = "chainguard-dev/chainguard"}
}
}
If you don’t have an active Chainguard token, the provider will automatically launch a browser to complete the Oauth2 flow with one of the default identity providers (GitHub, GitLab, Google). This means the Terraform provider does not require chainctl
to be installed to manage authentication with Chainguard.
While many users will likely authenticate as their own identity, Chainguard also supports the common notion of account impersonation (what we call assumable identities) that allow a user, or more often an automated system, to temporarily assume capabilities over a defined set of resources, given that their identity token matches the configured criteria. See the Chainguard Academy article for more details about the use cases and configuration of assumable identities. And of course, you use the Terraform provider to declaratively configure these identities (more on that below).
You can customize the behavior of the authentication flow of the Chainguard Terraform provider in a number of ways, including specifying an identity to assume or a verified organization name to use a previously configured custom identity provider.
provider "chainguard" {
login_options {
organization_name = "my-org.com"
# identity_id is the exact ID of an assumable identity.
# Get this ID with chainctl iam identities list
identity_id = "1f127a7c0609329f04b43d845cf80eea4247a07c/d6305475446bbef6"
}
}
The provider can also be configured to use an OIDC token, either directly supplied or from a file, with the automatic browser flow disabled for use in CI workflows.
provider "chainguard" {
login_options {
# Disable the automatic browser authentication flow.
disabled = true
identity_token = "/path/to/oidc/token"
}
}
More information about authenticating with the Chainguard Terraform provider can be found in the documentation and examples.
The root of all resources
At the heart of Terraform is the concept of declaratively managing resources on a system. What this means in practice is defining how you want the state of the world to be, and letting Terraform and the provider handle the details of making that a reality.
Resources on the Chainguard platform are organized in a hierarchical structure of folder-like groups, with an organization’s group at the root, though most users will only need to work at the organization level.
All user-managed resources are defined in relation to some parent group. In terms of using the Terraform provider, this means developers need a reference to their organization’s group to sprinkle throughout. There are a few ways to define this reference.
Define a local variable
If you are familiar with chainctl
, you can determine your organization’s ID with:
chainctl iam groups list --output=table.
Copy the UIDP of your root or organization group (it is a 40-digit long hex string) and create a local variable in Terraform.
locals {
org_id = "[organization id]"
}
Throughout your Terraform code, you can refer to this value as local.org_id
. If you are setting up a reusable Terraform module, you may consider using an input variable instead. See the Terraform documentation for more details.
Use a data resource
If you know the exact name of your organization’s group, you can use a data resource to query the API for it:
data "chainguard_group" "org" {
# This indicates the group is an organization.
parent_id = "/"
name = "[organization group name]"
}
To refer to the organization’s ID later, use the reference data.chainguard_group.org.id
. The rest of the examples in this post will use this for referring to the organization’s ID.
There are also data sources for user identities and roles that will be used later in this post.
Now that we have an organization to nest everything under, let’s manage some resources!
Managing users
The Chainguard Terraform provider is an excellent tool to help configure your organization’s users and roles. We offer a variety of ways to grant users access to your organization on Chainguard; organizations can choose the approach that best fits their use case.
When authenticating with the Chainguard Platform, you have the option of using the default OIDC providers (GitHub, GitLab, Google) or bringing your own identity provider, as long as it is OIDC compliant.
To configure a new identity provider for your organization, use the chainguard_identity_provider
resource. You must provide a parent_id
(your organization’s ID), a name
(the identity provider service is a good choice), a default_role
that new users will be bound to on first login, and the oidc
configuration. An optional description
is also accepted.
# The default role can be either a built-in role, or a custom role.
# To see the list of available built-in roles
# use chainctl iam roles list --managed
data "chainguard_role" "default_role" {
name = "registry.pull_token_creator"
}
resource "chainguard_identity_provider" "idp" {
parent_id = data.chainguard_group.org.id
name = "[identity provider service]"
Description = “My org’s identity provider”
# Role data sources return list of matched roles.
# Don't use data.chainguard_role.default_role.id here, as that
# is not the ID of the returned role.
default_role = data.chainguard_role.default_role.items.0.id
oidc {
issuer = "[URL of identity provider issuer]"
client_id = "[identity provider client ID]"
client_secret = "[identity provider client secret]"
# openid scope is always requested, add additional scopes
# your identity provider provides (e.g. email, profile)
additional_scopes = ["email"]
}
}
output "idp_id" {
value = chainguard_identity_provider.idp.id
}
To authenticate with this identity provider, provide either the final ID from above or your organization’s name to chainctl
:
# Provide the identity provider ID
chainctl auth login --identity-provider=[ID from Terraform output]
# Or provide the organization name
chainctl auth login --org-name=my-org.com
You can also pass these to the Terraform provider in the login_options
block of the provider configuration:
provider "chainguard" {
login_options {
# Authenticate by identity provider ID
identity_provider = "[identity provider ID]"
# OR by organization name
organization_name = "my-org.com"
}
}
If you’re not bringing your own identity provider, but rather relying on one of the default OIDC providers, you can still pre-bind users to roles within your organization so they can log in and access your organization’s resources right away.
For example, let’s say you want your users to log in with their GitHub account. To pre-bind users to a role within your organization, you will need to know their GitHub ID.
# Create a custom role in this example for first time users.
resource "chainguard_role" "default_role" {
parent_id = data.chainguard_group.org.id
name = "org-default-role"
description = "The role new users are bound to on first login."
# A full list of all capabilities you can assign to a role
# are available with chainctl iam roles capabilities list
capabilities = [
"groups.list",
"repo.list",
"tag.list",
...
]
}
# Gather a list of user identities
data "chainguard_identity" "users" {
# Assumes there exists a github_ids set variable defined
# with the GitHub IDs of your users
for_each = var.github_ids
# The issuer is always the same when using the default
# OIDC providers.
issuer = "https://auth.chainguard.dev/"
# Subjects are prepended with the name of the OIDC provider
# when using the default provider: github, gitlab, or google-oauth2
subject = "github|${each.key}”
}
# Bind your users to a default role
resource "chainguard_rolebinding" "default_bindings" {
for_each = var.github_ids
group = data.chainguard_group.org.id
identity = data.chainguard_identity.users[each.key].id
role = chainguard_role.default_role.id
}
After applying this Terraform, any user whose GitHub ID was included can log in and already be bound to the default role in your organization.
As mentioned above, Chainguard supports impersonation through the use of assumable identities. We have an entire collection of articles on Chainguard Academy to walk you through configuring assumable identities for a variety of continuous integration systems, including the Terraform to configure these identities. In this post, let’s consider a simpler example of configuring an identity anyone in your organization could assume to pull images.
# Create an assumable identity employees or your organization can impersonate.
resource "chainguard_identity" "employees" {
parent_id = data.chainguard_group.org.id
name = "all-employees"
description = "An identity all employees at my-org can assume."
claim_match {
issuer = "[URL of identity provider issuer]"
# The subject of an OIDC token is often a unique identifier associated with
# the user authenticating with that provider. See your identity provider
# documentation to generate a matching regular expression.
subject_pattern = "[Regular expression of the expected subject]"
# Optional: audience or audience_pattern claim to further refine access.
claim_patterns = {
"email" : ".*@my-org\\.com$"
# Other arbitrary claims included by your identity provider to match on.
}
}
}
# Look up the registry image puller role
data "chainguard_role" "puller" {
name = "registry.pull"
}
# Grant the identity the permission to pull images
resource "chainguard_rolebinding" "employees-pull-access" {
identity = chainguard_identity.employees.id
group = data.chainguard_group.org.id
role = data.chainguard_role.items[0].id
}
output "employees_identity" {
value = chainguard_identity.employees.id
}
Users can pass the identity ID from the output when authenticating with chainctl
by using the --identity
flag, or by setting the identity_id
attribute in the provider configuration login_options
block.
Using an assumable identity allows an organization to bypass granting access to all users individually. Instead, all employees whose identity matches the claims may impersonate a common identity, removing the need to deactivate user accounts in the event of offboarding.
More to explore
The Chainguard Terraform provider offers more resources to interact with the Chainguard platform, such as subscribing to events emitted by Chainguard (visit our platform examples repository for guidance configuring subscriptions). See the full documentation for examples of all available resources.
Ready to Lock Down Your Supply Chain?
Talk to our customer obsessed, community-driven team.