{"id":8531,"date":"2024-01-19T08:52:13","date_gmt":"2024-01-19T08:52:13","guid":{"rendered":"https:\/\/www.satup.xyz\/index.php\/2024\/01\/19\/fine-grained-rbac-for-github-action-workflows-with-github-oidc-and-hashicorp-vault\/"},"modified":"2024-01-19T08:52:13","modified_gmt":"2024-01-19T08:52:13","slug":"fine-grained-rbac-for-github-action-workflows-with-github-oidc-and-hashicorp-vault","status":"publish","type":"post","link":"https:\/\/www.satup.xyz\/index.php\/2024\/01\/19\/fine-grained-rbac-for-github-action-workflows-with-github-oidc-and-hashicorp-vault\/","title":{"rendered":"Fine-Grained RBAC For GitHub Action Workflows With GitHub OIDC and HashiCorp Vault"},"content":{"rendered":"<p><br \/>\n<\/p>\n<div>\n<p>In our <a href=\"https:\/\/www.digitalocean.com\/blog\/enabling-engineering-teams-developer-first-secrets-management\">last article<\/a>, we discussed how a developer-first, contextual approach to secrets management enables a security program to meet the speed and scale of modern businesses. In this article, we explain how we accomplished this by leveraging GitHub\u2019s OpenID Connect (OIDC) support for authentication to fine-grained Hashicorp Vault roles, resulting in a \u201ccredentials-free\u201d experience for development teams in our deployment pipelines. For organizations running CI\/CD through GitHub Actions and managing their secrets with HashiCorp Vault, we\u2019ve found a process that offers a streamlined experience for developers while enabling simplified orchestration and management for security.<\/p>\n<p>In this article, we step through the technical implementation we employed between GitHub and Vault to support this OIDC flow for secrets consumption. We cover both the programmatic components of this secrets management pattern and the engineering details other organizations may wish to adapt to create their own versions of this program. At the end of the article, we share a Terraform module we have open sourced to help any organization to get up and running with a similar initiative.<\/p>\n<p>A common concern with many secrets management efforts is the \u201csecret zero\u201d problem. An organization stores all of its secrets in some kind of protected enclave, such as HashiCorp Vault or 1Password. The organization needs to restrict access to the secrets and to segment which secrets each group can access. Therefore, roles are created and authentication to those roles are distributed to appropriate users, teams, and systems.<\/p>\n<p>But where can <em>those<\/em> authentication credentials be safely stored? Not in the secret store, as these credentials are a precondition to getting access. Could they be stored in a separate protected enclave? But how is access to <em>that<\/em> system protected? It\u2019s <a href=\"https:\/\/en.wikipedia.org\/wiki\/Turtles_all_the_way_down\">turtles all the way down<\/a>. This first set of login credentials used to gain access to the secrets store is often denoted \u201csecret zero.\u201d<\/p>\n<h3 id=\"current-alternatives-require-complex-management\"><a href=\"#current-alternatives-require-complex-management\" onclick=\"navigator.clipboard.writeText(this.href);\">Current Alternatives Require Complex Management<\/a><a class=\"hash-anchor\" href=\"#current-alternatives-require-complex-management\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p>One common solution to the \u201csecret zero\u201d problem is to introduce <em>another<\/em> secret enclave that contains an already trusted entity at the point where access to the secrets store is needed. For example, if a company\u2019s secrets are stored in HashiCorp Vault, static long-lived credentials to Vault (e.g. userpass, AppRole) can be generated and stored in GitHub as <a href=\"https:\/\/docs.github.com\/en\/actions\/security-guides\/encrypted-secrets#creating-encrypted-secrets-for-a-repository\">encrypted secrets<\/a>. A repository is granted access to these secrets as a property of the access control settings set on the repository or organization available in GitHub. Depending on the operating environment and the company in question, this may be sufficient to allow a GitHub Action workflow secure access to secrets in Vault.<\/p>\n<p>For many organizations, however, this approach necessitates implementing complex management procedures. An organization should be able to produce the following information about their secrets management program:<\/p>\n<ul>\n<li>\n<p>A mapping of which authentication roles are used by which repositories<\/p>\n<\/li>\n<li>\n<p>The secrets accessible to each team\u2019s projects<\/p>\n<\/li>\n<li>\n<p>Whether a team\u2019s access is too broad or too restrictive<\/p>\n<\/li>\n<li>\n<p>Demonstrate adherence to specific compliance requirements<\/p>\n<\/li>\n<\/ul>\n<p>GitHub secrets do not currently provide capabilities to enable a company to produce this information. A Vault role with static credentials may be created for a particular use case, but an organization cannot natively confirm it has not been stored in a second repository\u2019s secrets and leveraged for an unintended use case.<\/p>\n<p>Moreover, these credentials are all static and long-lived, posing a risk to the organization if the deployment workflow is compromised. Stolen credentials continue to be a significant factor in breach incidents. All of a sudden an organization must build a sprawling asset management system for its secret store login credentials that is likely perpetually out of date, build a homegrown credential rotation and auditing lifecycle, or entirely give up on understanding these relationships and accept the risk this lack of visibility poses. This approach is certainly better than plaintext exposure! But GitHub OIDC offers a better solution.<\/p>\n<p>GitHub <a href=\"https:\/\/github.blog\/2023-01-11-passwordless-deployments-to-the-cloud\/\">launched OIDC support<\/a> within GitHub Actions in <a href=\"https:\/\/github.blog\/changelog\/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect\/\">October 2021<\/a> to enable cloud deployment workflows to authenticate to their services without needing to handle credentials inside the GitHub repo. From their <a href=\"https:\/\/github.com\/github\/roadmap\/issues\/249\">roadmap issue<\/a>: \u201cOpenID token exchange eliminates the need for storing any long-lived cloud secrets in GitHub.\u201d By using OIDC authentication to Vault, we remove the need for engineers to manage a root credential pair and solve the \u201csecret zero\u201d problem for these workloads!<\/p>\n<p>Discussing OAuth2 and OpenID Connect are outside the scope of this article, but <a href=\"https:\/\/developer.okta.com\/blog\/2019\/10\/21\/illustrated-guide-to-oauth-and-oidc\">this introduction to OAuth and OIDC<\/a> from Okta serves as a helpful visual explainer. At a high level, OIDC is a way to authenticate a user or service to a third party identity provider (IdP) using a JSON Web Token (JWT). Instead of managing login credentials, the token exposes parameters (<a href=\"https:\/\/auth0.com\/docs\/secure\/tokens\/json-web-tokens\/json-web-token-claims\">known as claims<\/a>) which we can bind a Vault role against. When GitHub presents a token containing the necessary combination of claims, Vault will return an auth token for a given Vault role.<\/p>\n<h3 id=\"fine-grained-ci-cd-roles\"><a href=\"#fine-grained-ci-cd-roles\" onclick=\"navigator.clipboard.writeText(this.href);\">Fine-Grained CI\/CD Roles<\/a><a class=\"hash-anchor\" href=\"#fine-grained-ci-cd-roles\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p>Beyond solving the \u201csecret zero\u201d problem, using GitHub OIDC for authentication provides greater flexibility to fine-tune least-privilege access to roles. For example, beyond simply delineating between repositories inside an organization, GitHub OIDC auth allows us to bind specific workflows inside a repository to <em>different<\/em> Vault roles in an auditable, consistent manner. Suddenly, we can not only definitively answer the question \u201cWhat Vault roles are used by which repositories?\u201d through native properties of our authentication configuration, but we are capable of asking &#8211; and answering &#8211; the more granular \u201cin what <em>scenarios<\/em> can a repository access a Vault role?\u201d<\/p>\n<p>Beyond the question, \u201cwhat secrets can team X\u2019s project access?\u201d we can <em>enforce<\/em> what different sets of secrets team X\u2019s project can access during deployments, CI testing, and other use cases as a native property of our authentication scheme. And we can accomplish all of this without requiring developers to handle credentials to Vault themselves, without having to deal with static credential rotation lifecycles or exposure, with credential TTLs in the seconds or minutes, and with complete auditability designed into the configuration-as-code approach.<\/p>\n<p>As we discussed in our <a href=\"https:\/\/www.digitalocean.com\/blog\/enabling-engineering-teams-developer-first-secrets-management#developer-first-security\">previous post on developer-first security<\/a>, a developer-first security approach integrates into the organization\u2019s existing development workflows. The first step is to document the workflows used by development teams. This is unique for every organization, but there are general patterns we can discuss. For DigitalOcean, we began with the following five use cases:<\/p>\n<ol>\n<li>\n<p><strong>Testing pull requests<\/strong> &#8211; A continuous integration (CI) workflow testing pull requests in a repository needs to access nonproduction secrets.<\/p>\n<\/li>\n<li>\n<p><strong>Continuous deployment (CD) triggers<\/strong> &#8211;  Pushes to the main branch trigger a continuous deployment workflow that builds a new version of the application and deploys it to production. This workflow needs access to production secrets.<\/p>\n<\/li>\n<li>\n<p><strong>Complex, multi-environment workflows<\/strong> &#8211; A single workflow that deploys first to a staging environment, verifies correct functionality, and then deploys the application to production should have access to staging and production secrets at each respective point inside the workflow, but should not be able to access both staging and production secrets at the same time.<\/p>\n<\/li>\n<li>\n<p><strong>Supports monorepos<\/strong> &#8211; Multiple teams contributing to a monorepo can define individual <code>.github\/workflow\/<\/code> files inside the same repository and get access to their unique credentials that other teams and workflows inside the monorepo cannot access.<\/p>\n<\/li>\n<li>\n<p><strong>Reusable &amp; shareable workflows<\/strong> &#8211; An internal reusable workflow, such as a set of tasks encapsulating publishing artifacts to Artifactory, can access its needed secrets when called from any repository across multiple GitHub organizations. Consumers invoking the workflow do not need to configure anything unique for access to secrets.<\/p>\n<\/li>\n<\/ol>\n<p>The following security considerations apply to each developer use case:<\/p>\n<ol>\n<li>\n<p>Credentials must be short-lived. Compromise of any workflow must present an extremely minimal window of opportunity for a malicious entity to exploit these credentials.<\/p>\n<\/li>\n<li>\n<p>Secrets consumption must be fully auditable &#8211; we must be able to determine what repository accessed what Vault role (and therefore consumed what secrets) at some specific time. We must also be able to determine what secrets <em>could<\/em> be consumed by a repository or workflow at any given time.<\/p>\n<\/li>\n<\/ol>\n<p>Let\u2019s step through how the OIDC configuration can be bound to Vault and how to provide the fine-grained customizability to match these developer and security use cases. These code examples will use Terraform.<\/p>\n<p>Enabling a GitHub OIDC configuration on Vault\u2019s end requires creating a new <a href=\"https:\/\/developer.hashicorp.com\/vault\/docs\/auth\/jwt\">JWT auth backend<\/a> pointing to <a href=\"http:\/\/GitHub.com\">GitHub.com<\/a> or to a GitHub Enterprise Server instance. GitHub has documentation on <a href=\"https:\/\/docs.github.com\/en\/enterprise-server@latest\/actions\/deployment\/security-hardening-your-deployments\/configuring-openid-connect-in-hashicorp-vault#adding-the-identity-provider-to-hashicorp-vault\">how to construct the URL<\/a> for a GitHub Enterprise Server.<\/p>\n<pre class=\"language-hcl\"><code><span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend\"<\/span><\/span> <span class=\"token string\">\"github_oidc\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">description<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"Accept OIDC authentication from GitHub Action workflows\"<\/span>\n\n  <span class=\"token property\">path<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"gha\"<\/span>\n\n  <span class=\"token property\">oidc_discovery_url<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"https:\/\/token.actions.githubusercontent.com\"<\/span>\n\n  <span class=\"token property\">bound_issuer<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"https:\/\/token.actions.githubusercontent.com\"<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<p>At this point, Vault and GitHub are configured to talk to each other. What\u2019s left is defining each use case as its own Vault role configuration on this authentication backend. This is the meat of the configuration and what to do depends on the needs of the developers in your organization.<\/p>\n<p>Organizations configuring OIDC authentication from <a href=\"http:\/\/github.com\">github.com<\/a> should take an additional configuration step: <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/deployment\/security-hardening-your-deployments\/about-security-hardening-with-openid-connect#switching-to-a-unique-token-url\">switch to a unique token URL<\/a>. Setting the <code>bound_issuer<\/code> and <code>oidc_discovery_url<\/code> to <code>https:\/\/token.actions.githubusercontent.com<\/code> grants the entirety of public GitHub the <em>possibility<\/em> of authenticating to your Vault server. If you accidentally misconfigure the bound claims that we describe below, you could be exposing your Vault server to other users on <a href=\"http:\/\/github.com\">github.com<\/a>.<\/p>\n<p>To prevent this, GitHub has recently added an <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/rest\/actions\/oidc?apiVersion=2022-11-28#set-the-github-actions-oidc-custom-issuer-policy-for-an-enterprise\">API-only configuration<\/a> for organizations to customize your enterprise\u2019s token URL to <code>https:\/\/token.actions.githubusercontent.com\/&lt;enterpriseSlug&gt;<\/code>, where <code>enterpriseSlug<\/code> refers to <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/admin\/overview\/creating-an-enterprise-account\">the value that was set<\/a> when your enterprise cloud account was created. We strongly recommend any enterprise cloud organizations using GitHub OIDC enable this setting. This way, no matter how the bound claims are configured below, it is not possible for other users or enterprises on <a href=\"http:\/\/github.com\">github.com<\/a> to get a valid OIDC token to your Vault server. Both the <code>oidc_discovery_url<\/code> and <code>bound_issuer<\/code> should use this new token URL.<\/p>\n<pre class=\"language-hcl\"><code><span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend\"<\/span><\/span> <span class=\"token string\">\"github_oidc\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">description<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"Accept OIDC authentication from GitHub Action workflows\"<\/span>\n\n  <span class=\"token property\">path<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"gha\"<\/span>\n\n  <span class=\"token property\">oidc_discovery_url<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"https:\/\/token.actions.githubusercontent.com\/mycompany\"<\/span>\n\n  <span class=\"token property\">bound_issuer<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"https:\/\/token.actions.githubusercontent.com\/mycompany\"<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<p>This does not apply to GitHub Enterprise Server accounts, as the self-hosted instance is already unique to your enterprise.<\/p>\n<p><a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/deployment\/security-hardening-your-deployments\/about-security-hardening-with-openid-connect#understanding-the-oidc-token\">The claims provided<\/a> in GitHub\u2019s JWT define our authentication configuration capabilities. We can bind any combination of these key-value pairs to a Vault role, thereby requiring all of that data to exist in a GitHub workflow\u2019s JWT before granting access to a Vault role and its underlying policies. The following is an example GitHub JWT displaying the claims contained in a token:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/doimages.nyc3.cdn.digitaloceanspaces.com\/002Blog\/fine-grained-security-blog-image1.jpg\" alt=\"security image1\"\/><\/p>\n<p>The primary property we use at DigitalOcean is the bound subject <code>(sub)<\/code> claim, although simple use cases can use alternative JWT properties. For example, to allow one repository to access a certain Vault role while preventing other repositories from authenticating, we can bind the <code>repository<\/code> claim to a Vault role instead.<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"github_oidc_role\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-myrole\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">repository<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"digitalocean\/myrepo\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n  \n\n  <span class=\"token property\">token_policies<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"default\"<\/span>, <span class=\"token string\">\"mypolicy\"<\/span><span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token property\">bound_audiences<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"https:\/\/github.com\/digitalocean\"<\/span><span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token property\">role_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"jwt\"<\/span>\n\n  <span class=\"token property\">backend<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"gha\"<\/span>\n\n  <span class=\"token property\">user_claim<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"actor\"<\/span>\n\n  <span class=\"token property\">token_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"batch\"<\/span>\n\n  <span class=\"token property\">token_ttl<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token number\">300<\/span>  \n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<p>More commonly, however, we want finer-grained delineation, such as separating pull request workflows from a deployment workflow triggered from the main branch. For this, we can create two separate roles on Vault, each granting access to a respective development or production policy. The subject claim enables us to enforce these separate use cases:<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"only_prs\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-prs\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:pull_request\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n  \n\n<span class=\"token punctuation\">}<\/span>\n\n\n\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"only_main_branch\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role-name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-main\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:ref:refs\/heads\/main\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n  \n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<p>Workflows invoked inside of a pull request that attempt to receive an authentication token for the \u201cmyrepo-main\u201d Vault role will fail, as the OIDC properties in the JWT will not match the preconfigured expectation in the <code>bound_claims<\/code>. Workflows from any <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/using-workflows\/events-that-trigger-workflows\">event trigger<\/a> that is <em>not<\/em> a <code>pull_request<\/code>, such as a <code>push<\/code>, will fail to authenticate to the \u201cmyrepo-prs\u201d Vault role.<\/p>\n<p>There are a <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/deployment\/security-hardening-your-deployments\/about-security-hardening-with-openid-connect#example-subject-claims\">number of ways<\/a> to filter the subject claim. The options boil down to:<\/p>\n<ol>\n<li>\n<p><code>pull_request<\/code> (but no other) workflow triggers<\/p>\n<\/li>\n<li>\n<p>some specific branch on the repository<\/p>\n<\/li>\n<li>\n<p>some specific tag on the repository<\/p>\n<\/li>\n<li>\n<p>some wildcard pattern for multiple branches or multiple tags (e.g. <code>ref:refs\/tags\/*<\/code>)<\/p>\n<\/li>\n<li>\n<p>some <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/deployment\/targeting-different-environments\/using-environments-for-deployment\">GitHub Environment<\/a> (or a wildcard pattern for multiple GitHub Environments, although we have not encountered a use case for this)<\/p>\n<\/li>\n<\/ol>\n<p>These five configurations give us almost all of the tools we need to solve the developer use cases we previously identified in this article. Combining other claims in the JWT with the bound subject (<code>sub<\/code>) give us everything we need. Crucially, the method developers use to consume secrets remains consistent across all of these use cases. HashiCorp maintains a <a href=\"https:\/\/github.com\/hashicorp\/vault-action\">GitHub Action<\/a> for consumption of secrets in Action workflows. A developer includes the name of their desired role and what secrets they wish to access:<\/p>\n<pre class=\"language-yaml\"><code><span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">uses<\/span><span class=\"token punctuation\">:<\/span> hashicorp\/vault<span class=\"token punctuation\">-<\/span>action@v2\n\n  <span class=\"token key atrule\">with<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">role<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"myrepo-prs\"<\/span>\n\n    <span class=\"token key atrule\">secrets<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">|<\/span>\n\n      secrets\/data\/their\/chosen\/secrets mysecret <span class=\"token punctuation\">|<\/span> MY_SECRET ;\n\n    \n\n    <span class=\"token key atrule\">url<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"https:\/\/my-vault.company.com:8200\"<\/span>\n\n    <span class=\"token key atrule\">caCertificate<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"optional yet likely for an enterprise vault configuration\"<\/span>\n\n    <span class=\"token key atrule\">method<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"jwt\"<\/span>\n\n    <span class=\"token key atrule\">path<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"gha\"<\/span>\n<\/code><\/pre>\n<p>If the expected bound claims match a user\u2019s workflow for the requested Vault role, they will be granted a short-lived token. Because we set the <code>token_ttl<\/code> on the Vault role configuration for 5 minutes, the Vault token granted to each workflow will expire after that time. This gives a malicious entity an extremely small window of time to exploit a valid auth token while providing plenty of time for a legitimate developer to retrieve the secrets their workflow requires. In 80% of cases we\u2019ve found that a 60 second TTL is plenty of time. We recently bumped our default TTL from 60 seconds to 5 minutes to account for those other edge cases inside our organization. We will grant certain workflows up to a 30 minute TTL, but we have yet to find a use case that requires a Vault token for longer.<\/p>\n<p>Let\u2019s see how an OIDC configuration can enable each of the five developer use cases we listed above.<\/p>\n<h3 id=\"testing-pull-requests\"><a href=\"#testing-pull-requests\" onclick=\"navigator.clipboard.writeText(this.href);\">Testing Pull Requests<\/a><a class=\"hash-anchor\" href=\"#testing-pull-requests\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p><em>Example: A continuous integration (CI) workflow testing pull requests in a repository needs to access nonproduction secrets.<\/em><\/p>\n<p>This can be enforced via the <code>pull_request<\/code> bound subject mentioned previously. A complete example is:<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"myrepo-nonprod-prs\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-nonprod-prs\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:pull_request\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n  \n\n  <span class=\"token property\">token_policies<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"default\"<\/span>, vault_policy.myrepo-nonprod-prs.name<span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token property\">bound_audiences<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"https:\/\/github.com\/digitalocean\"<\/span><span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token property\">role_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"jwt\"<\/span>\n\n  <span class=\"token property\">backend<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"gha\"<\/span>\n\n  <span class=\"token property\">user_claim<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"actor\"<\/span>\n\n  <span class=\"token property\">token_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"batch\"<\/span>\n\n  <span class=\"token property\">token_ttl<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token number\">300<\/span>  \n\n\n\n<span class=\"token punctuation\">}<\/span>\n\n\n\n<span class=\"token keyword\">data <span class=\"token type variable\">\"vault_policy_document\"<\/span><\/span> <span class=\"token string\">\"myrepo-nonprod-pr\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token keyword\">rule<\/span> <span class=\"token punctuation\">{<\/span>\n\n    <span class=\"token property\">path<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"secret\/data\/myteam\/myproject\/development\"<\/span>\n\n    <span class=\"token property\">capabilities<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"read\"<\/span><span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token punctuation\">}<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n\n\n\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_policy\"<\/span><\/span> <span class=\"token string\">\"myrepo-nonprod-prs\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-nonprod-prs-policy\"<\/span>\n\n  <span class=\"token property\">policy<\/span> <span class=\"token punctuation\">=<\/span> data.vault_policy_document.myrepo-nonprod-prs.hcl\n\n<span class=\"token punctuation\">}<\/span>\n\n<\/code><\/pre>\n<h3 id=\"continuous-deployment-cd-triggers\"><a href=\"#continuous-deployment-cd-triggers\" onclick=\"navigator.clipboard.writeText(this.href);\">Continuous deployment (CD) triggers<\/a><a class=\"hash-anchor\" href=\"#continuous-deployment-cd-triggers\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p><em>Example: Pushes to the main branch trigger a continuous deployment workflow that builds a new version of the application and deploys it to production. This workflow needs access to production secrets.<\/em><\/p>\n<p>Similarly, we can use the main branch bound subject construction provided earlier. A complete example of this configuration follows. Note the only material changes are to the <code>role_name<\/code>, <code>bound_claims<\/code>, and the contents of the policy this Vault role should be granted. The rest of the examples will focus on those values.<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"myrepo-prod-branch-main\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-prod-branch-main\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:ref:refs\/heads\/main\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n  \n\n  <span class=\"token property\">token_policies<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"default\"<\/span>, vault_policy.myrepo-prod-branch-main.name<span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token property\">bound_audiences<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"https:\/\/github.com\/digitalocean\"<\/span><span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token property\">role_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"jwt\"<\/span>\n\n  <span class=\"token property\">backend<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"gha\"<\/span>\n\n  <span class=\"token property\">user_claim<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"actor\"<\/span>\n\n  <span class=\"token property\">token_ttl<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token number\">300<\/span>  \n\n  <span class=\"token property\">token_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"batch\"<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n\n\n\ndata <span class=\"token string\">\"vault_policy_document \"<\/span>myrepo-prod-branch-main<span class=\"token string\">\" {\n\n  rule {\n\n    path = \"<\/span>secret\/data\/myteam\/myproject\/production<span class=\"token string\">\"\n\n    capabilities = [\"<\/span>read<span class=\"token string\">\"]\n\n  }\n\n}\n\n\n\nresource \"<\/span>vault_policy<span class=\"token string\">\" \"<\/span>myrepo-prod-branch-main<span class=\"token string\">\" {\n\n  name = \"<\/span>myrepo-prod-branch-main-policy\"\n\n  <span class=\"token property\">policy<\/span> <span class=\"token punctuation\">=<\/span> data.vault_policy_document.myrepo-prod-branch-main.hcl\n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<h3 id=\"complex-multi-environment-workflows\"><a href=\"#complex-multi-environment-workflows\" onclick=\"navigator.clipboard.writeText(this.href);\">Complex, multi-environment workflows<\/a><a class=\"hash-anchor\" href=\"#complex-multi-environment-workflows\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p><em>Example: A single workflow that deploys first to a staging environment, verifies correct functionality, and then deploys the application to production should have access to staging and production secrets at each respective point inside the workflow, but should not be able to access both staging and production secrets at the same time.<\/em><\/p>\n<p>This is a more complicated real-world use case. While there\u2019s a bit more to configure on the GitHub side, the authentication to Vault remains consistent. As there are two sets of secrets involved here &#8211; staging secrets and production secrets &#8211; we want to create two corresponding Vault roles. But, the same workflow file will need both Vault roles. We need to enforce that at no point can an arbitrary task inside the workflow access both sets of secrets.<\/p>\n<p>To accomplish this, we will use one of the other bound subject filtering options: <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/deployment\/targeting-different-environments\/using-environments-for-deployment\">GitHub Environments<\/a>. Environments are an access control feature on GitHub repositories. For this use case, we don\u2019t need to configure the environments in any way aside from ensuring they exist on our developer\u2019s repository.<\/p>\n<p>First, we need to create <code>staging<\/code> and <code>production<\/code> environments, leaving all of the other settings blank.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/doimages.nyc3.cdn.digitaloceanspaces.com\/002Blog\/fine-grained-security-blog2.jpg\" alt=\"security image2\"\/><\/p>\n<p>Second, we need to configure our two Vault roles, using an <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/deployment\/security-hardening-your-deployments\/about-security-hardening-with-openid-connect#filtering-for-a-specific-environment\">environment filter<\/a> on the subject claim.<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"myrepo-env-staging\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-env-staging\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:environment:staging\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n  \n\n<span class=\"token punctuation\">}<\/span>\n\n\n\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"myrepo-env-production\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-env-production\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:environment:production\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n  \n\n<span class=\"token punctuation\">}<\/span>\n\n<\/code><\/pre>\n<p>This configuration means that a workflow job invoked under the <code>staging<\/code> GitHub Environment can retrieve an auth token for the <code>myrepo-env-staging <\/code>Vault role, while the <code>production<\/code> GitHub Environment can retrieve an auth token for the <code>myrepo-env-production<\/code> Vault role. Workflows not invoked under those environments will fail to authenticate to Vault, and since only one environment can be applied to a workflow job, neither environment can access the other environment\u2019s secrets.<\/p>\n<p>Third, we build our GitHub Actions workflow. To accomplish this use case of a continuous deployment pushing to staging, running some tests, then deploying to production, we can create two workflow jobs in which one job requires the other to have successfully completed. Each job <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/using-workflows\/workflow-syntax-for-github-actions#jobsjob_idenvironment\">is assigned<\/a> its respective environment.<\/p>\n<pre class=\"language-yaml\"><code>\n<span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Continuous Deployment\n\n<span class=\"token key atrule\">on<\/span><span class=\"token punctuation\">:<\/span>\n\n  <span class=\"token key atrule\">push<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">branches<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> main\n\n\n\n<span class=\"token key atrule\">jobs<\/span><span class=\"token punctuation\">:<\/span>\n\n  <span class=\"token key atrule\">deploy-staging<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Deploy and Test App on Staging\n\n    <span class=\"token key atrule\">environment<\/span><span class=\"token punctuation\">:<\/span> staging\n\n    <span class=\"token key atrule\">runs-on<\/span><span class=\"token punctuation\">:<\/span> ubuntu<span class=\"token punctuation\">-<\/span>latest\n\n    \n\n    \n\n    <span class=\"token key atrule\">permissions<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token key atrule\">contents<\/span><span class=\"token punctuation\">:<\/span> read\n\n      <span class=\"token key atrule\">id-token<\/span><span class=\"token punctuation\">:<\/span> write\n\n    <span class=\"token key atrule\">steps<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">uses<\/span><span class=\"token punctuation\">:<\/span> actions\/checkout@v3\n\n\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Import Secrets\n\n        <span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> secrets\n\n        <span class=\"token key atrule\">uses<\/span><span class=\"token punctuation\">:<\/span> hashicorp\/vault<span class=\"token punctuation\">-<\/span>action@v2\n\n        <span class=\"token key atrule\">with<\/span><span class=\"token punctuation\">:<\/span>\n\n          <span class=\"token key atrule\">role<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"myrepo-env-staging\"<\/span>\n\n          <span class=\"token key atrule\">secrets<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">|<\/span>\n\n            secrets\/data\/myteam\/myproject\/staging mysecret <span class=\"token punctuation\">|<\/span> MY_SECRET ;\n\n          \n\n          <span class=\"token key atrule\">url<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"https:\/\/my-vault.company.com:8200\"<\/span>\n\n          <span class=\"token key atrule\">caCertificate<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"optional yet likely for an enterprise vault configuration\"<\/span>\n\n          <span class=\"token key atrule\">method<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"jwt\"<\/span>\n\n          <span class=\"token key atrule\">path<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"gha\"<\/span>\n\n          <span class=\"token key atrule\">exportEnv<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token boolean important\">false<\/span>\n\n\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Deploy something\n\n        <span class=\"token key atrule\">run<\/span><span class=\"token punctuation\">:<\/span> \n\n        <span class=\"token key atrule\">env<\/span><span class=\"token punctuation\">:<\/span>\n\n          <span class=\"token key atrule\">my_env_var<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"${{ steps.secrets.outputs.MY_SECRET }}\"<\/span>\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Test something\n\n        <span class=\"token key atrule\">run<\/span><span class=\"token punctuation\">:<\/span> \n\n\n\n  <span class=\"token key atrule\">deploy-production<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Deploy to Production\n\n    <span class=\"token key atrule\">environment<\/span><span class=\"token punctuation\">:<\/span> production\n\n    \n\n    <span class=\"token key atrule\">needs<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> deploy<span class=\"token punctuation\">-<\/span>staging\n\n    <span class=\"token key atrule\">runs-on<\/span><span class=\"token punctuation\">:<\/span> ubuntu<span class=\"token punctuation\">-<\/span>latest\n\n    <span class=\"token key atrule\">permissions<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token key atrule\">contents<\/span><span class=\"token punctuation\">:<\/span> read\n\n      <span class=\"token key atrule\">id-token<\/span><span class=\"token punctuation\">:<\/span> write\n\n    <span class=\"token key atrule\">steps<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">uses<\/span><span class=\"token punctuation\">:<\/span> actions\/checkout@v3\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Import Secrets\n\n        <span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> secrets\n\n        <span class=\"token key atrule\">uses<\/span><span class=\"token punctuation\">:<\/span> hashicorp\/vault<span class=\"token punctuation\">-<\/span>action@v2\n\n        <span class=\"token key atrule\">with<\/span><span class=\"token punctuation\">:<\/span>\n\n          <span class=\"token key atrule\">role<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"myrepo-env-production\"<\/span>\n\n          <span class=\"token key atrule\">secrets<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">|<\/span>\n\n            secrets\/data\/myteam\/myproject\/production mysecret <span class=\"token punctuation\">|<\/span> PROD_SECRET ;\n\n          \n\n          <span class=\"token key atrule\">url<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"https:\/\/my-vault.company.com:8200\"<\/span>\n\n          <span class=\"token key atrule\">caCertificate<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"optional yet likely for an enterprise vault configuration\"<\/span>\n\n          <span class=\"token key atrule\">method<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"jwt\"<\/span>\n\n          <span class=\"token key atrule\">path<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"gha\"<\/span>\n\n          <span class=\"token key atrule\">exportEnv<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token boolean important\">false<\/span>\n\n\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Deploy something\n\n        <span class=\"token key atrule\">run<\/span><span class=\"token punctuation\">:<\/span> \n\n        <span class=\"token key atrule\">env<\/span><span class=\"token punctuation\">:<\/span>\n\n          <span class=\"token key atrule\">my_env_var<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"${{ steps.secrets.outputs.PROD_SECRET }}\"<\/span>\n\n<\/code><\/pre>\n<p>We can additionally enforce that these environments can only authenticate from a specific workflow file  using the technique for our next developer use case. That is, someone cannot create a new file in the repo, add the <code>environment: production<\/code> line, and access the production environment secrets from that other workflow.<\/p>\n<h3 id=\"supports-monorepos\"><a href=\"#supports-monorepos\" onclick=\"navigator.clipboard.writeText(this.href);\">Supports Monorepos<\/a><a class=\"hash-anchor\" href=\"#supports-monorepos\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p><em>Example: Multiple teams contributing to a monorepo can define individual <code>.github\/workflow\/<\/code>files inside the same repository and get access to their unique credentials that other teams and workflows inside the monorepo cannot access.<\/em><\/p>\n<p>To accomplish this, we combine two attributes for the <code>bound_claims<\/code> of this Vault role: <code>sub<\/code> and <code>job_workflow_ref<\/code>. As a reminder, we can combine any number of the GitHub JWT claims to a Vault role!<\/p>\n<p>The <code>job_workflow_ref<\/code> is one of the other <a href=\"https:\/\/docs.github.com\/en\/enterprise-server@latest\/actions\/deployment\/security-hardening-your-deployments\/about-security-hardening-with-openid-connect#understanding-the-oidc-token\">supported claims<\/a> in GitHub\u2019s JWT. Its format is <code>organization\/repo\/&lt;path to workflow file&gt;@&lt;repo ref&gt;<\/code>.<\/p>\n<pre class=\"language-hcl\"><code><span class=\"token property\">\"job_workflow_ref\"<\/span>: <span class=\"token string\">\"octo-org\/octo-automation\/.github\/workflows\/oidc.yml@refs\/heads\/main\"<\/span>\n<\/code><\/pre>\n<p>The ref at the end of the string signifies the version of the workflow file that is being bound to this configuration and has to match where a workflow run is invoked. For example, if we were constructing a Vault role intended to be used in a production deployment from the main branch of a monorepo, setting <code>@refs\/heads\/main<\/code> on the <code>job_workflow_ref<\/code> means that the specified workflow triggered from the main branch &#8211; via most workflow triggers &#8211; will succeed, while workflows triggered from something like a pull request will fail, as the <code>job_workflow_ref<\/code> will end with something like <code>@refs\/pull\/12345\/merge<\/code>.<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"mymonorepo-myteam-myworkflow\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"mymonorepo-myteam-myworkflow\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span>\n\n    <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:ref:refs\/heads\/main\"<\/span>\n\n    \n\n    <span class=\"token property\">job_workflow_ref<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"digitalocean\/myrepo\/.github\/workflows\/myteam-deployment.yml@refs\/heads\/main\"<\/span>\n\n  <span class=\"token punctuation\">}<\/span>\n\n  \n\n<span class=\"token punctuation\">}<\/span>\n\n<\/code><\/pre>\n<p>For a workflow in a monorepo that should run from any pull request &#8211; but should not expose secrets to any other workflow file &#8211; we can use a wildcard in our <code>job_workflow_ref<\/code>.<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"mymonorepo-myteam-myworkflow\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"mymonorepo-myteam-myworkflow\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span>\n\n    <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:pull_request\"<\/span>\n\n    <span class=\"token property\">job_workflow_ref<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"digitalocean\/myrepo\/.github\/workflows\/myteam-deployment.yml@refs\/pull\/*\"<\/span>\n\n  <span class=\"token punctuation\">}<\/span>\n\n  \n\n  <span class=\"token property\">bound_claims_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"glob\"<\/span>\n\n  \n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<p>The workflow file itself is constructed similarly to the previous examples. We recommend that teams working in a monorepo make liberal use of GitHub\u2019s <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/using-workflows\/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore\">paths\/paths-ignore filters<\/a> so their workflows only trigger when necessary.<\/p>\n<pre class=\"language-yaml\"><code><span class=\"token key atrule\">on<\/span><span class=\"token punctuation\">:<\/span>\n\n  <span class=\"token key atrule\">push<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">branches<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> main\n\n    <span class=\"token key atrule\">paths<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token string\">'src\/teams\/myteam\/**'<\/span> \n<\/code><\/pre>\n<h3 id=\"reusable-and-shareable-workflows\"><a href=\"#reusable-and-shareable-workflows\" onclick=\"navigator.clipboard.writeText(this.href);\">Reusable and shareable workflows<\/a><a class=\"hash-anchor\" href=\"#reusable-and-shareable-workflows\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p><em>Example: An internal reusable workflow, such as a set of tasks encapsulating publishing artifacts to Artifactory, can access its needed secrets when called from any repository across multiple GitHub organizations. Consumers invoking the workflow do not need to configure anything unique for access to secrets.<\/em><\/p>\n<p><a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/using-workflows\/reusing-workflows\">Reusable workflows<\/a> should also make use of <code>sub<\/code> and <code>job_workflow_ref<\/code>, however in this case we will add a wildcard to the bound subject. How exactly the subject should be constructed will depend on how widely you desire the reusable workflow to be used.<\/p>\n<p>For example, within a GitHub Enterprise Server instance in which every GitHub organization belongs to the company, you could use <code>sub = \u201crepo:*\u201d<\/code> combined with a specific <code>job_workflow_ref<\/code>. Or replace the bound subject with a similar wildcard claim like <code>repository = \u201c*\u201d<\/code>. If, however, you want to grant widespread access within only one GitHub organization in your GitHub Enterprise Server (or you are on <a href=\"http:\/\/github.com\">github.com<\/a>, in which case you should restrict access to just your company\u2019s organization), you can set a wildcard subject like <code>sub = \u201crepo:digitalocean\/*\u201d<\/code>. Don\u2019t forget to set <code>bound_claims_type = \u201cglob\u201d<\/code>!<\/p>\n<p>Regardless of the bound subject, your <code>job_workflow_ref<\/code> should point to the reusable workflow you expect the organization to trigger. Certain claims in the JWT, such as <code>workflow<\/code> and <code>ref<\/code>, refer to the <em>caller<\/em> workflow, the repo whose workflow is invoking a reusable workflow. But <code>job_workflow_ref<\/code> refers to the <em>called<\/em> workflow, which is the workflow that is actually running (our reusable workflow). GitHub provides <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/deployment\/security-hardening-your-deployments\/using-openid-connect-with-reusable-workflows#how-the-token-works-with-reusable-workflows\">further information about how the JWT works with reusable workflows<\/a>. To understand the distinction between caller and called workflows, we\u2019ll use the following example:<\/p>\n<p>Let\u2019s say a platform engineering team sets up a reusable workflow to help deploy artifacts to Artifactory. They create this reusable workflow in the repository <code>digitalocean\/shared-workflows<\/code> and the path to the reusable workflow file inside that repo is <code>.github\/workflows\/artifactory.yml<\/code>. A developer wants to consume this reusable workflow in their repo. They create a <code>digitalocean\/myproject<\/code> repository, and create a <code>.github\/workflow\/deploy.yml<\/code> workflow file. The developer\u2019s workflow file might look like:<\/p>\n<pre class=\"language-yaml\"><code>\n<span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Deploy to Artifactory\n\n<span class=\"token key atrule\">on<\/span><span class=\"token punctuation\">:<\/span>\n\n  <span class=\"token key atrule\">release<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">types<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> published\n\n\n\n<span class=\"token key atrule\">jobs<\/span><span class=\"token punctuation\">:<\/span>\n\n  <span class=\"token key atrule\">deploy<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> push to artifactory\n\n    <span class=\"token key atrule\">runs-on<\/span><span class=\"token punctuation\">:<\/span> ubuntu<span class=\"token punctuation\">-<\/span>latest\n\n    <span class=\"token key atrule\">permissions<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token key atrule\">contents<\/span><span class=\"token punctuation\">:<\/span> read\n\n      <span class=\"token key atrule\">id-token<\/span><span class=\"token punctuation\">:<\/span> write\n\n    <span class=\"token key atrule\">steps<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">uses<\/span><span class=\"token punctuation\">:<\/span> actions@checkout@v3\n\n\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Deploy\n\n        <span class=\"token key atrule\">uses<\/span><span class=\"token punctuation\">:<\/span> digitalocean\/shared<span class=\"token punctuation\">-<\/span>workflows\/.github\/workflows\/artifactory.yml@main\n\n        <span class=\"token key atrule\">with<\/span><span class=\"token punctuation\">:<\/span>\n\n          <span class=\"token key atrule\">inputs<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"...\"<\/span>\n\n<\/code><\/pre>\n<p>Inside the reusable workflow, a <code>hashicorp\/vault-action<\/code> step retrieves secrets using OIDC. Notably, the <code>permissions<\/code> block must be set on the developer\u2019s workflow, while the secrets will be retrieved inside the reusable workflow.<\/p>\n<p>When the developer\u2019s workflow file is triggered, the <em>caller<\/em> workflow will be <code>digitalocean\/myproject\/.github.workflows\/deploy.yml@refs\/\u2026 <\/code>. The <em>called<\/em> workflow will be <code>digitalocean\/shared-workflows\/.github\/workflows\/artifactory.yml@refs\/\u2026<\/code> .<\/p>\n<p>Therefore, the Vault role we want to construct, which the reusable workflow will use to retrieve its secrets, is:<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"reusable-workflow\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">role_name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"reusable-workflow\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span>\n\n    <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/*\"<\/span>\n\n    <span class=\"token property\">job_workflow_ref<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"digitalocean\/shared-workflows\/.github\/workflows\/artifactory.yml@refs\/heads\/main\"<\/span>\n\n  <span class=\"token punctuation\">}<\/span>\n\n  <span class=\"token property\">bound_claims_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"glob\"<\/span>\n\n  \n\n<span class=\"token punctuation\">}<\/span>\n\n<\/code><\/pre>\n<p>This allows any repository inside the digitalocean organization to access the Vault role <code>reusable-workflow<\/code>, but only from the <em>called<\/em> workflow, the reusable workflow, at <code>digitalocean\/shared-workflows\/.github\/workflows\/artifactory.yml@refs\/heads\/main<\/code>. We recommend such reusable workflow roles pin the ref of the <code>job_workflow_ref<\/code> to the reusable workflow\u2019s default branch or to a specific tag (e.g. <code>@refs\/heads\/main<\/code>). This determines what version of the workflow file someone else can invoke to successfully retrieve a Vault role; all other versions of the <code>artifactory.yml<\/code> reusable workflow will fail to authenticate.<\/p>\n<p>As a benefit of this construction, any team in the <code>digitalocean<\/code> organization can use this reusable workflow to push to Artifactory with credentials, but no team has access to the actual secrets in their workflows. They are retrieved and handled inside the reusable workflow, and the <em>caller<\/em> workflow cannot influence or extract any information from the <em>called<\/em> workflow that isn\u2019t <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/using-workflows\/reusing-workflows#using-outputs-from-a-reusable-workflow\">pre-configured<\/a>.<\/p>\n<p>With all of these possibilities, however, come a plethora of opportunities to misconfigure a Vault role, resulting in frustratingly vague 400 errors when trying to authenticate to Vault (although that <a href=\"https:\/\/github.com\/hashicorp\/vault-action\/pull\/409\">may be improved<\/a> in recent hashicorp\/vault-action versions). That\u2019s not a great developer experience! Asking all of your developers to learn the intricacies of the GitHub JWT bound subject filtering conditions or the impacts of combining <code>sub<\/code> and <code>job_workflow_ref<\/code>, or other claims, will lead to a lot of pain. Our <a href=\"https:\/\/www.digitalocean.com\/blog\/enabling-engineering-teams-developer-first-secrets-management\">previous article<\/a> emphasizes the importance of security initiatives solving problems for developers, not introducing them!<\/p>\n<p>This is the point where the security team should, with a developer-first security mindset, invest in providing paved path tooling to solve the security concerns &#8211; use these least-privilege Vault roles &#8211; while solving the developer concern &#8211; let me get secrets and move on with my day! The internals of OIDC claim construction within Vault roles should be encapsulated through tooling that makes it easy for developers to get the right Vault role configuration for their use case. At DigitalOcean, the security team offers a command-line wizard to interactively walk a developer through the steps to create a Vault role for their workflow.<\/p>\n<p><iframe src=\"https:\/\/fast.wistia.net\/embed\/iframe\/06ldzmh6yw\" class=\"wistia\" height=\"500\" width=\"700\" style=\"aspect-ratio: 7\/5\" frameborder=\"0\" allowfullscreen=\"\"><br \/>\n    <a href=\"https:\/\/fast.wistia.net\/embed\/iframe\/06ldzmh6yw\" target=\"_blank\" rel=\"noopener\">View Wistia video<\/a><br \/>\n<\/iframe><\/p>\n<p>The wizard sets up the necessary configurations on both Vault and, if they need GitHub Environments, applies the necessary changes to their GitHub repository. Experienced users can generate configurations non-interactively as well. This not only provides a paved path for the secrets management that security cares about, but a solution that makes it easier for developers to deploy their engineering pipelines, inside of which secrets consumption is just a small part. Crucially, the wizard does not ask the developer if they want to do \u201csecret task A\u201d or \u201csecret task B.\u201d The developer is not expected to understand the intricacies involved in making that decision. Instead, the developer is asked which <em>engineering<\/em> task they want to perform, and the tooling guides them through the relevant security steps for that task.<\/p>\n<p>The wizard also offers to create a pull request onto the developer\u2019s repository with a \u201chello world\u201d deployment workflow leveraging their specific Vault role in whatever authentication pattern they\u2019ve requested.<\/p>\n<pre class=\"language-yaml\"><code>\n<span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Your Job\n\n<span class=\"token key atrule\">on<\/span><span class=\"token punctuation\">:<\/span>\n\n  <span class=\"token key atrule\">push<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">branches<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> main\n\n\n\n<span class=\"token key atrule\">jobs<\/span><span class=\"token punctuation\">:<\/span>\n\n  <span class=\"token key atrule\">FROM_THE_MAIN_BRANCH<\/span><span class=\"token punctuation\">:<\/span>\n\n    <span class=\"token key atrule\">runs-on<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> Linux  \n\n\n\n    <span class=\"token key atrule\">permissions<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token key atrule\">contents<\/span><span class=\"token punctuation\">:<\/span> read\n\n      <span class=\"token key atrule\">id-token<\/span><span class=\"token punctuation\">:<\/span> write\n\n\n\n    <span class=\"token key atrule\">steps<\/span><span class=\"token punctuation\">:<\/span>\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Import Secrets\n\n        <span class=\"token key atrule\">id<\/span><span class=\"token punctuation\">:<\/span> secrets\n\n        <span class=\"token key atrule\">uses<\/span><span class=\"token punctuation\">:<\/span> do<span class=\"token punctuation\">-<\/span>actions\/hashicorp<span class=\"token punctuation\">-<\/span>vault<span class=\"token punctuation\">-<\/span>action@v2\n\n        <span class=\"token key atrule\">with<\/span><span class=\"token punctuation\">:<\/span>\n\n          <span class=\"token key atrule\">jwtGithubAudience<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">'digitalocean:secrets:product_security:secrets_example'<\/span>\n\n          <span class=\"token key atrule\">role<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">'digitalocean-secrets-product_security-secrets_example-main'<\/span>\n\n          <span class=\"token key atrule\">secrets<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token punctuation\">|<\/span>\n\n            secret\/data\/product_security\/secrets_example your_secret <span class=\"token punctuation\">|<\/span> OUTPUT_VALUE_TO_REFERENCE ;\n\n            secret\/data\/product_security\/secrets_example your_secret2 <span class=\"token punctuation\">|<\/span> OUTPUT_VALUE_TO_REFERENCE2 ;\n\n\n\n      <span class=\"token punctuation\">-<\/span> <span class=\"token key atrule\">name<\/span><span class=\"token punctuation\">:<\/span> Your next steps\n\n        <span class=\"token key atrule\">run<\/span><span class=\"token punctuation\">:<\/span> <span class=\"token string\">\"echo 'Reference secrets in your steps like this: ${{ steps.secrets.outputs.OUTPUT_VALUE_TO_REFERENCE }}'\"<\/span>\n\n<\/code><\/pre>\n<p>The <code>do-actions\/hashicorp-vault-action<\/code> role is an internal vendored version of <code>hashicorp\/vault-action<\/code> with company defaults pre-configured, so developers do not need to set the Vault URL, CA certificate, authentication backend, and other common defaults for our environment.<\/p>\n<p>In the previous workflow example, we are configuring the optional <code>jwtGitHubAudience<\/code> parameter on <a href=\"https:\/\/github.com\/hashicorp\/vault-action\">HashiCorp\u2019s vault-action<\/a> action. The reason for this is due to the security use case we defined above:<\/p>\n<ul>\n<li>Secrets consumption must be fully auditable &#8211; we must be able to determine what repository accessed what Vault role (and therefore consumed what secrets) at some specific time. We must also be able to determine what secrets could be consumed by which repositories or workflows at any given time.<\/li>\n<\/ul>\n<p>There are a few ways to audit the behavior of workflows accessing Vault secrets through GitHub OIDC. The <code>jwtGitHubAudience<\/code> parameter in HashiCorp\u2019s GitHub action lets us customize the value of the <code>aud<\/code> claim in the OIDC token. By default, the audience is the owner of the given repository on GitHub: the GitHub user or organization to which a repo belongs. For the repo <code>https:\/\/github.com\/digitalocean\/myrepo<\/code>, the aud claim is <code>https:\/\/github.com\/digitalocean<\/code> (replace <a href=\"http:\/\/github.com\">github.com<\/a> with your GitHub Enterprise Server domain, if necessary). We have chosen to enforce an <code>org:repo:team:service<\/code> format to audiences, so we can consistently attribute individual Vault roles and activity to the team and owning service that owns the secrets and usage of a given Vault role.<\/p>\n<h3 id=\"default-user-claim\"><a href=\"#default-user-claim\" onclick=\"navigator.clipboard.writeText(this.href);\">Default User Claim<\/a><a class=\"hash-anchor\" href=\"#default-user-claim\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p>Additionally, we\u2019ve set the default <code>user_claim<\/code> to <code>job_workflow_ref<\/code>. <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/deployment\/security-hardening-your-deployments\/configuring-openid-connect-in-hashicorp-vault\">GitHub\u2019s examples<\/a> typically use the <code>actor<\/code> claim; however, we believe that <code>job_workflow_ref<\/code> gives us more actionable information within an audit log entry than the <code>actor<\/code> claim.<\/p>\n<p>The user claim is how you want Vault to uniquely identify a client and can be set to any claim present in the GitHub JWT. This value is used for the name of the identity entity alias upon a successful login. This essentially means that, in Vault\u2019s audit log, the value of the user claim will be retrievable as <code>auth.display_name<\/code>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/doimages.nyc3.cdn.digitaloceanspaces.com\/002Blog%2Ffine%20grained%20security%20image3.png\" alt=\"security image3\"\/><\/p>\n<p>Using <code>job_workflow_ref<\/code> allows us to attribute activity on Vault\u2019s end to the specific GitHub repository and workflow file that triggered it, as well as the pull request, tagged release, or other activity tied to that Vault role session. The <code>auth.display_name<\/code> syntax is <code>[auth backend mount name]-[chosen user claim]<\/code>. In the above screenshot, our JWT backend is configured at the auth mount <code>github-actions<\/code> and the rest is the value of the <code>job_workflow_ref<\/code> for this audit log entry.<\/p>\n<p>We use <code>job_workflow_ref<\/code> instead of <code>actor<\/code> because the <code>actor<\/code> claim reflects the <code>github.actor<\/code> field in the <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/actions\/learn-github-actions\/contexts#github-context\">github context<\/a> and corresponds to the username who triggered a workflow run. However, re-running a workflow will re-use the original actor that kicked off the initial workflow run, even if that is not the current actor that just triggered the re-run.<\/p>\n<p>In recent months, GitHub has added a <a href=\"https:\/\/github.blog\/changelog\/2022-07-19-differentiating-triggering-actor-from-executing-actor\/\">new <code>github.triggering_actor<\/code> field<\/a> to the github context to differentiate between the initial executing actor and whoever triggered a re-run of a workflow. However, the GitHub JWT\u2019s <code>actor<\/code> claim currently always refers to the <code>github.actor<\/code> context attribute. Therefore, we prefer to set the default user claim to <code>job_workflow_ref<\/code> and correlate workflow runs specified by that ref with GitHub audit logs if we are interested in identifying an actor. With an example like the image above, however, we can also go to the pull request in question and look at the activity. We believe the <code>job_workflow_ref<\/code> gives us more actionable information on an audit log entry than other user claims.<\/p>\n<pre class=\"language-hcl\"><code><span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"only_main_branch\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  \n\n  <span class=\"token property\">user_claim<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"job_workflow_ref\"<\/span>\n\n  <span class=\"token property\">role-name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-main\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:ref:refs\/heads\/main\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<h3 id=\"short-lived-ttls\"><a href=\"#short-lived-ttls\" onclick=\"navigator.clipboard.writeText(this.href);\">Short-Lived TTLs<\/a><a class=\"hash-anchor\" href=\"#short-lived-ttls\" aria-hidden=\"true\" onclick=\"navigator.clipboard.writeText(this.href);\"\/><\/h3>\n<p>And as for our other security use case:<\/p>\n<ul>\n<li>Credentials must be short-lived. Compromise of any workflow must present an extremely minimal window of opportunity for a malicious entity to exploit these credentials.<\/li>\n<\/ul>\n<p>As mentioned previously, we set the Vault token TTL to five minutes by default:<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_jwt_auth_backend_role\"<\/span><\/span> <span class=\"token string\">\"only_main_branch\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  \n\n  <span class=\"token property\">token_ttl<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token number\">300<\/span>  \n\n  <span class=\"token property\">token_type<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"batch\"<\/span>\n\n  <span class=\"token property\">role-name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"myrepo-main\"<\/span>\n\n  <span class=\"token property\">bound_claims<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">{<\/span> <span class=\"token property\">sub<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"repo:digitalocean\/myrepo:ref:refs\/heads\/main\"<\/span> <span class=\"token punctuation\">}<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<p>A compromise of our CI\/CD environment in which an attacker extracts the local environment or secrets present in a workflow will find they have only 5 minutes to leverage the granted Vault token before it becomes useless to them. If GitHub Actions follows in the path of other CI\/CD providers in recent years and our workflows become compromised via their platform, any malicious activity within the 5-minute validity period of a Vault token could be traced in our logs to a specific <code>job_workflow_ref<\/code> and therefore all Vault activity connected to their behavior, without needing to sift through loads of legitimate traffic.<\/p>\n<p>Since we use <a href=\"https:\/\/developer.hashicorp.com\/vault\/tutorials\/tokens\/batch-tokens\">batch tokens<\/a> for this OIDC use case, it is extremely cheap to generate thousands of auth tokens on Vault and these tokens cannot be renewed beyond the initial TTL set on the token. We lose the capability to revoke or list these tokens, but they live for such a short time period that this is not a concern to us.<\/p>\n<p>There are <a href=\"https:\/\/registry.terraform.io\/providers\/hashicorp\/vault\/latest\/docs\/resources\/jwt_auth_backend_role#common-token-arguments\">additional token attributes<\/a> that could be set on the Vault role configuration, but we do not currently use them today. We set the <code>token_ttl<\/code> default to a short time period and allow teams to customize that if they have a use case. Responses in the Vault audit log include the field <code>auth.token_ttl<\/code> so we can observe any unexpectedly long TTLs and investigate further.<\/p>\n<p>We have <a href=\"https:\/\/github.com\/digitalocean\/terraform-vault-github-oidc\">open sourced a Terraform module<\/a> to assist organizations with configuring GitHub OIDC authentication to Vault with the fine-grained role claims setup discussed in this article. Organizations can define the audience, desired Vault role name, bound subject, and any additional claims they desire and the module will hook everything up on Vault.<\/p>\n<pre class=\"language-hcl\"><code>\n<span class=\"token keyword\">module<span class=\"token type variable\"> \"github-vault-oidc\" <\/span><\/span><span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">source<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"digitalocean\/github-oidc\/vault\"<\/span>\n\n  <span class=\"token property\">version<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"~&gt; 2.1.0\"<\/span>\n\n\n\n  <span class=\"token property\">oidc_bindings<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span>\n\n    <span class=\"token punctuation\">{<\/span>\n\n      audience : <span class=\"token string\">\"https:\/\/github.com\/artis3n\"<\/span>,\n\n      vault_role_name : <span class=\"token string\">\"oidc-dev-role\"<\/span>,\n\n      bound_subject : <span class=\"token string\">\"repo:artis3n\/github-oidc-vault-example:pull_request\"<\/span>,\n\n      vault_policies : <span class=\"token punctuation\">[<\/span>\n\n        vault_policy.dev.name,\n\n      <span class=\"token punctuation\">]<\/span>,\n\n    <span class=\"token punctuation\">}<\/span>,\n\n    <span class=\"token punctuation\">{<\/span>\n\n      audience : <span class=\"token string\">\"https:\/\/github.com\/artis3n\"<\/span>,\n\n      vault_role_name : <span class=\"token string\">\"oidc-deploy-role\"<\/span>,\n\n      bound_subject : <span class=\"token string\">\"repo:artis3n\/github-oidc-vault-example:ref:refs\/heads\/main\"<\/span>,\n\n      vault_policies : <span class=\"token punctuation\">[<\/span>\n\n        vault_policy.deployment.name,\n\n      <span class=\"token punctuation\">]<\/span>,\n\n    <span class=\"token punctuation\">}<\/span>,\n\n  <span class=\"token punctuation\">]<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n\n\n\n<span class=\"token keyword\">data <span class=\"token type variable\">\"vault_policy_document\"<\/span><\/span> <span class=\"token string\">\"dev\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token keyword\">rule<\/span> <span class=\"token punctuation\">{<\/span>\n\n    <span class=\"token property\">path<\/span>         <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"secret\/data\/dev\/foo\"<\/span>\n\n    <span class=\"token property\">capabilities<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"read\"<\/span><span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token punctuation\">}<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n\n\n\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_policy\"<\/span><\/span> <span class=\"token string\">\"dev\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">name<\/span>   <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"oidc-dev\"<\/span>\n\n  <span class=\"token property\">policy<\/span> <span class=\"token punctuation\">=<\/span> data.vault_policy_document.dev.hcl\n\n<span class=\"token punctuation\">}<\/span>\n\n\n\n<span class=\"token keyword\">data <span class=\"token type variable\">\"vault_policy_document\"<\/span><\/span> <span class=\"token string\">\"deployment\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token keyword\">rule<\/span> <span class=\"token punctuation\">{<\/span>\n\n    <span class=\"token property\">path<\/span>         <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"secret\/data\/prod\/bar\"<\/span>\n\n    <span class=\"token property\">capabilities<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token punctuation\">[<\/span><span class=\"token string\">\"read\"<\/span><span class=\"token punctuation\">]<\/span>\n\n  <span class=\"token punctuation\">}<\/span>\n\n<span class=\"token punctuation\">}<\/span>\n\n\n\n<span class=\"token keyword\">resource <span class=\"token type variable\">\"vault_policy\"<\/span><\/span> <span class=\"token string\">\"deployment\"<\/span> <span class=\"token punctuation\">{<\/span>\n\n  <span class=\"token property\">name<\/span> <span class=\"token punctuation\">=<\/span> <span class=\"token string\">\"oidc-deploy\"<\/span>\n\n  <span class=\"token property\">policy<\/span> <span class=\"token punctuation\">=<\/span> data.vault_policy_document.deployment.hcl\n\n<span class=\"token punctuation\">}<\/span>\n<\/code><\/pre>\n<p>To assist organizations with managing these Vault roles at scale, we support <a href=\"https:\/\/github.com\/digitalocean\/terraform-vault-github-oidc\/tree\/main\/examples\/json-files\">a JSON files construction<\/a> in which individual development teams can be empowered via <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/repositories\/managing-your-repositorys-settings-and-features\/customizing-your-repository\/about-code-owners\">CODEOWNER<\/a>-ship of their own Vault roles and the module will import all the files and create the requested Vault roles. Or, Vault operators can leverage the JSON files for better organization of their GitHub OIDC Vault roles.<\/p>\n<p>We are <a href=\"https:\/\/github.com\/digitalocean\/terraform-vault-github-oidc\/issues\/88\">working on<\/a> building a generic version of our wizard to help users create the appropriate <code>oidc_bindings<\/code> objects for their desired use cases, which we will include in this repository in the future.<\/p>\n<p>In this article, we discussed DigitalOcean\u2019s approach to securing CI\/CD through GitHub Actions, OIDC, and HashiCorp Vault. We showed you five examples of real-world developer use cases to help build a mental model you can apply to your organization, and we showed you how we\u2019re living our mission to build developer-first approaches to security and secrets management with our paved path secrets wizard.<\/p>\n<p>If you would like to explore setting up GitHub OIDC Vault roles in a hands-on course following the first three developer use cases from this article, check out <a href=\"https:\/\/github.com\/artis3n\/course-vault-github-oidc\">this GitHub Skills course<\/a>.<\/p>\n<p>We hope this was enlightening and will help you more easily apply safe secrets management concepts to your organization. We plan to share more details about our developer-first security tooling and approach in future articles.<\/p>\n<p><em>Ari Kalfus is the Manager of Product Security at DigitalOcean. The Product Security team are internal advisors focused on enabling the business to safely innovate and experiment with risks. The team guides secure architecture design and reduces risk in the organization by constructing guardrails and paved paths that empower engineers to make informed security decisions.<\/em><\/p>\n<\/div>\n<p><br \/>\n<br \/><a href=\"https:\/\/www.digitalocean.com\/blog\/fine-grained-rbac-for-github-action-workflows-hashicorp-vault\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In our last article, we discussed how a developer-first, contextual approach to secrets management enables a security program to meet the speed and scale of modern businesses. In this article, we explain how we accomplished this by leveraging GitHub\u2019s OpenID Connect (OIDC) support for authentication to fine-grained Hashicorp Vault roles, resulting in a \u201ccredentials-free\u201d experience [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":8532,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[16],"tags":[],"class_list":["post-8531","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-app-developer"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/posts\/8531","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/comments?post=8531"}],"version-history":[{"count":0,"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/posts\/8531\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/media\/8532"}],"wp:attachment":[{"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=8531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/categories?post=8531"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.satup.xyz\/index.php\/wp-json\/wp\/v2\/tags?post=8531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}