diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0d1f68d --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,60 @@ +name: deploy +on: + push: + branches: [main] + workflow_dispatch: +jobs: + run: + permissions: + contents: read + id-token: write + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + + - uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.5.7 + + - name: lint + run: terraform fmt **/*.tf + + - id: 'auth' + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.GCLOUD_OIDC_POOL }} + service_account: ${{ secrets.GSA }} + token_format: 'access_token' + + - uses: google-github-actions/setup-gcloud@v2 + with: + version: 'latest' + + - name: Configure gcloud + run: | + gcloud config set project ${{ secrets.GCLOUD_PROJECT }} + gcloud config set disable_prompts true + + - uses: 'docker/login-action@v3' + name: 'Docker login' + with: + registry: 'us-docker.pkg.dev' + username: 'oauth2accesstoken' + password: '${{ steps.auth.outputs.access_token }}' + + - name: terraform apply + run: ./ci/tf.sh + env: + TF_VAR_project: ${{ secrets.GCLOUD_PROJECT }} + TF_VAR_region: ${{ secrets.GCLOUD_REGION }} + + - name: Upload logs as artifacts + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: terraform.log + path: /tmp/terraform.log + + - name: cleanup + if: ${{ always() }} + run: rm /tmp/terraform.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aed9472 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +root-token* +.terraform* diff --git a/00-main.tf b/00-main.tf new file mode 100644 index 0000000..2d8e4a2 --- /dev/null +++ b/00-main.tf @@ -0,0 +1,52 @@ +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "= 3.0.1" + } + google = { + source = "hashicorp/google" + version = "= 6.15.0" + } + google-beta = { + source = "hashicorp/google-beta" + version = "= 6.15.0" + } + vault = { + source = "hashicorp/vault" + version = "= 4.5.0" + } + } + + backend "gcs" { + bucket = "libops-vault-terraform" + prefix = "/github" + } +} + +provider "google" { + project = var.project +} + +provider "docker" { + registry_auth { + address = "us-docker.pkg.dev" + config_file = pathexpand("~/.docker/config.json") + } +} + +module "vault" { + source = "git::https://github.com/LibOps/terraform-vault-cloudrun?ref=171626a2fb1ddaa47e700b17ecbad30b7a9ae082" + providers = { + docker = docker + google = google + google-beta = google-beta + } + project = var.project + region = var.region + init_image = "jcorall/vault-init:0.4.0" +} + +provider "vault" { + address = module.vault.vault-url +} diff --git a/00-secrets.tf b/00-secrets.tf new file mode 100644 index 0000000..4ce8a66 --- /dev/null +++ b/00-secrets.tf @@ -0,0 +1,8 @@ +resource "vault_mount" "kv_v1" { + path = "kv-v1" + type = "kv" + + options = { + version = 1 + } +} diff --git a/01-policies.tf b/01-policies.tf new file mode 100644 index 0000000..1ccddcd --- /dev/null +++ b/01-policies.tf @@ -0,0 +1,11 @@ +# allow GCP auth access to paths in the GSA's project +# https://developer.hashicorp.com/vault/api-docs/auth/gcp#sample-payload-5 +resource "vault_policy" "project-read-kv" { + name = "kv-v1-per-project" + + policy = <<-EOT + path "kv-v1/{{identity.entity.metadata.project_id}}/*" { + capabilities = ["create", "read", "update", "delete", "list"] + } + EOT +} diff --git a/02-auth.tf b/02-auth.tf new file mode 100644 index 0000000..d2c464b --- /dev/null +++ b/02-auth.tf @@ -0,0 +1,18 @@ +resource "vault_auth_backend" "gcp" { + path = "gcp" + type = "gcp" +} + +resource "vault_gcp_auth_backend_role" "ghat" { + backend = vault_auth_backend.gcp.path + role = "ghat" + type = "iam" + bound_service_accounts = ["ghat-cr@libops-ghat.iam.gserviceaccount.com"] + bound_projects = ["libops-ghat"] + token_ttl = 300 + token_max_ttl = 600 + token_policies = [ + vault_policy.project-read-kv.name + ] + add_group_aliases = true +} diff --git a/README.md b/README.md index 2ffc4df..16135eb 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # libops-vault + +Use [LibOps/terraform-vault-cloudrun](https://github.com/LibOps/terraform-vault-cloudrun) to create a Vault server for LibOps running on Google Cloud Run. diff --git a/ci/tf.sh b/ci/tf.sh new file mode 100755 index 0000000..ed62c33 --- /dev/null +++ b/ci/tf.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + + +get_token() { + gsutil cp "gs://${TF_VAR_project}-key/root-token.enc" . > /dev/null 2>&1 +} + +terraform init -upgrade > /tmp/terraform.log 2>&1 + +# To solve the bootstrapping problem of creating Vault in CR +# and then being able to apply policies to the Vault instance +# We first run a targeted apply to just the module that creates the Vault server +# but only need to do this once +# and we'll know if it's done if we can't download the encrypted token +get_token || (terraform apply -target=module.vault -auto-approve >> /tmp/terraform.log 2>&1 && get_token) + +# fetch the token from KMS and store it in VAULT_TOKEN +base64 -d -i root-token.enc > root-token.dc +gcloud kms decrypt --key=vault --keyring=vault-server --location=global \ + --project="${TF_VAR_project}" \ + --ciphertext-file=root-token.dc \ + --plaintext-file=root-token +export VAULT_TOKEN="$(cat root-token)" + +# cleanup +rm root-token root-token.enc root-token.dc + +# Now we can apply all of the terraform with a valid Vault token +terraform apply -auto-approve >> /tmp/terraform.log 2>&1 diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..08d2526 --- /dev/null +++ b/variables.tf @@ -0,0 +1,9 @@ +variable "project" { + type = string + description = "The GCP project to create Vault server inside of" +} + +variable "region" { + type = string + description = "The GCP region to create the Vault server in" +}