News

Chainguard Terraform Provider is now available

Colin Douglas, Senior Software Engineer
January 31, 2024
copied

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.

-- CODE language-bash -- 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.

-- CODE language-bash -- 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.

-- CODE language-bash -- 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.

A chart showing how resources on the Chainguard Terraform platform are organized.

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:

-- CODE language-bash -- 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.

-- CODE language-bash -- 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:

-- CODE language-bash -- 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.

-- CODE language-bash -- # 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:

-- CODE language-bash -- # 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:

-- CODE language-bash -- 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.

-- CODE language-bash -- # 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.

-- CODE language-bash -- # 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.

Related articles

Ready to lock down your supply chain?

Talk to our customer obsessed, community-driven team.