Skip to content

Instantly share code, notes, and snippets.

@kmott
Last active March 15, 2024 07:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kmott/035931f0bb0503942a6691b6c90c74d6 to your computer and use it in GitHub Desktop.
Save kmott/035931f0bb0503942a6691b6c90c74d6 to your computer and use it in GitHub Desktop.
Terraform - JWT Auth for GitLab using Vault
//
// This enabled the JWT Auth backend in Vault for GitLab to authenticate with (obtains a Vault token based on $CI_JOB_JWT
// in pipeline jobs)
//
resource "vault_jwt_auth_backend" "gitlab" {
type = "jwt"
path = var.gitlab.jwt_auth_path
jwks_url = format("https://%s/-/jwks", var.gitlab.hostname)
bound_issuer = var.gitlab.hostname
}
resource "vault_policy" "policy" {
count = length(var.gitlab.projects)
name = format("gitlab-%s", replace(var.gitlab.projects[count.index].name, "/", "-"))
policy = <<EOT
%{ for _, policy in var.gitlab.projects[count.index].policies ~}
path "${policy.path}" {
capabilities = ["${join("\",\"", policy.capabilities)}"]
}
%{ endfor ~}
EOT
}
resource "vault_jwt_auth_backend_role" "role" {
count = length(var.gitlab.projects)
backend = vault_jwt_auth_backend.gitlab.path
role_type = "jwt"
token_policies = [format("gitlab-%s", replace(var.gitlab.projects[count.index].name, "/", "-"))]
role_name = format("gitlab-%s-readonly", replace(var.gitlab.projects[count.index].name, "/", "-"))
user_claim = "user_email"
token_explicit_max_ttl = 300
bound_claims = {
project_id = var.gitlab.projects[count.index].id
}
}
variable "gitlab" {
type = object({
hostname = string
jwt_auth_path = string
projects = list(object({
name = string
id = number
token_max_ttl = number
policies = list(object({
path = string
capabilities = list(string)
}))
}))
})
}
module "vault-gitlab" {
source = "./modules/vault-gitlab"
gitlab = {
hostname = "gitlab.example.org"
jwt_auth_path = "path/to/gitlab-jwt-auth"
//
// For each project, the user can specify a role when they log in to vault from their CI/CD pipeline
// to obtain their token:
// - vault write -namespace="" -field="token" ${VAULT_AUTH_JWT_PATH} role=${VAULT_AUTH_JWT_ROLE} jwt=${CI_JOB_JWT}
//
// The var 'VAULT_AUTH_JWT_PATH' is a globally set var in GitLab that defaults to the `jwt_auth_path` value
// from above.
//
// The var 'VAULT_AUTH_JWT_ROLE' is a globally set var in GitLab that defaults to
// 'gitlab-${CI_PROJECT_PATH_SLUG}-readonly', matching what Terraform is applying to Vault in the module above.
//
projects = [
{
name = "group1/proj1"
id = 999
token_max_ttl = 300
policies = [
{
path = "path/to/data/my/foo/*"
capabilities = ["read"]
},
{
path = "path/to/data/my/other/foo/specialSecrets"
capabilities = ["read"]
}
]
},
{
name = "group2/subgroup2/project1"
id = 1024
token_max_ttl = 300
policies = [
{
path = "some/other/data/for/different/group/secret"
capabilities = ["read"]
}
]
}
]
}
}
#
# This is the .gitlab-ci.yml for a given repo that can obtain a VAULT_TOKEN using the `vault` CLI, and then use that
# token to obtain secrets from various paths in Vault as a part of the CI/CD pipeline steps.
#
# Setting `-namespace=""` is required if you are using Enterprise Vault with namespaces and the JWT auth method is in
# the root namespace. If that is not the case, set the namespace accordingly (or leave out entirely if not uisng
# Enterprise Vault).
#
deploy:
image: vault:1.7.6
stage: deploy
tags:
- default
variables:
VAULT_ADDR: https://vault.example.org
VAULT_AUTH_JWT_PATH: path/to/gitlab-jwt-auth
VAULT_AUTH_JWT_ROLE: gitlab-${CI_PROJECT_PATH_SLUG}-readonly
VAULT_SECRETS_BASE_PATH: secrets/path/to/${CI_PROJECT_PATH} # For project specific secrets, not required
before_script:
- export VAULT_TOKEN="$(vault write -namespace="" -field="token" ${VAULT_AUTH_JWT_PATH} role=${VAULT_AUTH_JWT_ROLE} jwt=$CI_JOB_JWT)"
- export MY_SECRET="$(vault kv get -field="data" -format="json" ${VAULT_SECRETS_BASE_PATH}/test | jq -r '.secret')"
script:
- 'echo "MY_SECRET: ${MY_SECRET}"' # DO NOT DO THIS, FOR TESTING ONLY!!
@base698
Copy link

base698 commented Jan 18, 2022

We could add this as an import.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment