GitHub Actions provide a powerful, extensible way to automate software development workflows. When access to outside resources is required, GitHub provides the ability to store encrypted secrets used by GitHub Actions to authenticate against these resources. This makes managing access more simple and secure.
Good secret management practices include following principles of least privilege by narrowly scoping secrets to provide access to only what is required, limiting the manner in which the secret can be invoked, and rotating secrets when necessary. GitHub Actions provide several features to help your organization effectively implement a secret management strategy based on least privilege.
Secrets can be stored within GitHub at three different levels: the organization, a single repository, or a repository environment. The level at which the secret should be stored depends on its scope and intended use.
For example, a Slack bot token with permissions to only post to organization-owned workspaces is used to broadcast status updates as part of CI/CD workflows. This token has no special access to any resources, and many different repositories’ workflows will post similar updates to Slack. This secret might be stored at the organization level. When creating an organization secret, you can choose to make it available to all repositories in the organization, only private and internal repositories, or a selected set of repositories.
A containerized application stores its custom images in AWS Elastic Container Registry (ECR). To ensure the automation used to deploy the application cannot be used to pull other containers, a unique token is created for the repository and stored as a repository-level secret.
That same containerized application is deployed as an Azure Web App in different dev, test, and prod environments. Each environment requires its own, unique publishing profile. Using environments, these can be stored as environment-level secrets within a repository.
Least privilege secret management is concerned not only with what a token can access, but also how can it be used, and by whom. Combining properly scoped secrets with environment protections, CODEOWNERS, and branch protection rules helps ensure secrets are only used for the intended purpose, by those authorized to do so.
Let’s consider the example of a company, MonaCorp, that wants to ensure a secret can only be used to deploy authorized changes of a single Azure Web Application, OctoMittens, to its production environment. Separate publishing profiles exist for dev, test, and production.
First, each of these environments are defined in the repository.
Each environment has its own
AZURE_WEBAPP_PUBLISH_PROFILE secret, storing the respective value.
Without any additional configuration, someone with write access could create a branch
unauthorized-production-deployment, modify a workflow file on that branch, and trigger the workflow—causing the secret to be used to deploy to production. This type of unauthorized credential use can be prevented by:
- Creating an environment protection rule limiting the branch(es) that can deploy to the
productionenvironment. In this case, a rule is created to allow only the
mainbranch to deploy to
- Configuring CODEOWNERS to establish ownership of the
.github/workflowsdirectory. Here the
@monacorp/automation-reviewteam is assigned ownership.
### Actions workflows .github/workflows/ @monacorp/automation-review
- Creating a branch protection rule that requires review from CODEOWNERS for any merge into
mainand prevents force pushes.
With all these in place, the only available way to use the production
AZURE_WEBAPP_PUBLISH_PROFILE secret is in workflows on the
main branch targeting the
production environment. Any attempt to use this secret elsewhere requires merging a workflow change targeting the
production environment to the
main branch, which requires approval by the automatically assigned
Many companies use a centrally-managed secret store, such as HashiCorp Vault or Azure Key Vault, to store secrets and manage access. GitHub Actions can integrate with these stores while following all of the same principles discussed above.
MonaCorp chooses to store all secrets in HashiCorp Vault. Storing those same secrets in multiple places would violate the DRY principle, create additional management overhead, and add unnecessary risk. MonaCorp needs to provide the same limited scope and access to secrets using HashiCorp Vault as they can completely within GitHub.
To do this, MonaCorp first creates unique AppRoles in HashiCorp Vault for each of OctoMittens’ environments: dev, test, and prod. Each AppRole is granted access only to the secrets necessary to deploy to its respective environment.
Second, MonaCorp stores the
secretId for each HashiCorp Vault AppRole in GitHub as secrets in the corresponding environment.
Third, MonaCorp configures their jobs in their GitHub Actions workflows to target a specific environment. Each job uses the Vault Secrets action to authenticate against HashiCorp Vault as the AppRole for that environment, retrieve the desired secrets, and map them to environment variables. The action uses GitHub’s built-in masking to prevent the values from showing up in any output to logs or the console.
Similar capabilities are available for those using Azure Key Vault via the Azure Key Vault – Get Secrets action.
MonaCorp has a defined policy for secret rotation. Secrets maintained in Vault are rotated in Vault. These changes are automatically picked up by Actions workflows as the Vault Secrets action retrieves new credentials with each workflow run. When a secret stored in GitHub needs to be changed, such as the
secretId, MonaCorp can automate updating secrets in GitHub via the REST API.
With appropriately scoped secrets stored in the proper location(s), environment protection rules, CODEOWNERS, and optional integration with centrally-managed secret stores, GitHub provides organizations all the tools they need to securely manage authentication in their Actions workflows.