From 2dfce27329606b95ecbc32a9bbb69c4515604e83 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 13 Sep 2024 18:29:03 +0500 Subject: [PATCH 1/8] feat: add Hashicorp Vault JWT integration module --- vault-jwt/README.md | 83 ++++++++++++++++++++++++++++++ vault-jwt/main.test.ts | 12 +++++ vault-jwt/main.tf | 64 +++++++++++++++++++++++ vault-jwt/run.sh | 112 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 271 insertions(+) create mode 100644 vault-jwt/README.md create mode 100644 vault-jwt/main.test.ts create mode 100644 vault-jwt/main.tf create mode 100644 vault-jwt/run.sh diff --git a/vault-jwt/README.md b/vault-jwt/README.md new file mode 100644 index 00000000..01f4341f --- /dev/null +++ b/vault-jwt/README.md @@ -0,0 +1,83 @@ +--- +display_name: Hashicorp Vault Integration (JWT) +description: Authenticates with Vault using a JWT from Coder's OIDC provider +icon: ../.icons/vault.svg +maintainer_github: coder +partner_github: hashicorp +verified: true +tags: [helper, integration, vault, jwt, oidc] +--- + +# Hashicorp Vault Integration (JWT) + +This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces using a [JWT](https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication) from Coder's OIDC provider. + +```tf +module "vault" { + source = "registry.coder.com/modules/vault-jwt/coder" + version = "1.0.17" + agent_id = coder_agent.example.id + vault_addr = "https://vault.example.com" + vault_jwt_role = "coder" # The Vault role to use for authentication +} +``` + +Then you can use the Vault CLI in your workspaces to fetch secrets from Vault: + +```shell +vault kv get -namespace=coder -mount=secrets coder +``` + +or using the Vault API: + +```shell +curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/data/coder" +``` + +![Vault login](#) + +## Configuration + +To configure the Vault module, you must set up a Vault JWT auth method. See the [Vault documentation](https://developer.hashicorp.com/vault/docs/auth/jwt#configuration) for more information. + +## Examples + +### Configure Vault integration with a non standard auth path + +```tf +module "vault" { + source = "registry.coder.com/modules/vault-jwt/coder" + version = "1.0.17" + agent_id = coder_agent.example.id + vault_addr = "https://vault.example.com" + vault_jwt_auth_path = "oidc" + vault_jwt_role = "coder" # The Vault role to use for authentication +} +``` + +### Configure Vault integration with a role from your OIDC provider + +```tf +data "coder_workspace_owner" "me" {} + +module "vault" { + source = "registry.coder.com/modules/vault-jwt/coder" + version = "1.0.7" + agent_id = coder_agent.example.id + vault_addr = "https://vault.example.com" + vault_jwt_role = data.coder_workspace_owner.me.groups[0] +} +``` + +### Configure Vault integration and install a specific version of the Vault CLI + +```tf +module "vault" { + source = "registry.coder.com/modules/vault-jwt/coder" + version = "1.0.17" + agent_id = coder_agent.example.id + vault_addr = "https://vault.example.com" + vault_jwt_role = "coder" # The Vault role to use for authentication + vault_cli_version = "1.17.5" +} +``` diff --git a/vault-jwt/main.test.ts b/vault-jwt/main.test.ts new file mode 100644 index 00000000..2fda3d7c --- /dev/null +++ b/vault-jwt/main.test.ts @@ -0,0 +1,12 @@ +import { describe } from "bun:test"; +import { runTerraformInit, testRequiredVariables } from "../test"; + +describe("vault-jwt", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + vault_addr: "foo", + vault_jwt_role: "foo", + }); +}); diff --git a/vault-jwt/main.tf b/vault-jwt/main.tf new file mode 100644 index 00000000..a8bf4ab3 --- /dev/null +++ b/vault-jwt/main.tf @@ -0,0 +1,64 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 0.12.4" + } + } +} + +# Add required variables for your modules and remove any unneeded variables +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "vault_addr" { + type = string + description = "The address of the Vault server." +} + +variable "vault_jwt_auth_path" { + type = string + description = "The path to the Vault JWT auth method." + default = "jwt" +} + +variable "vault_jwt_role" { + type = string + description = "The name of the Vault role to use for authentication." +} + +variable "vault_cli_version" { + type = string + description = "The version of Vault to install." + default = "latest" + validation { + condition = can(regex("^(latest|[0-9]+\\.[0-9]+\\.[0-9]+)$", var.vault_cli_version)) + error_message = "Vault version must be in the format 0.0.0 or latest" + } +} + +resource "coder_script" "vault" { + agent_id = var.agent_id + display_name = "Vault (GitHub)" + icon = "/icon/vault.svg" + script = templatefile("${path.module}/run.sh", { + CODER_OIDC_ACCESS_TOKEN : data.coder_workspace_owner.me.oidc_token, + VAULT_JWT_AUTH_PATH : var.vault_jwt_auth_path, + VAULT_ROLE : var.vault_jwt_role, + VAULT_CLI_VERSION : var.vault_cli_version, + }) + run_on_start = true + start_blocks_login = true +} + +resource "coder_env" "vault_addr" { + agent_id = var.agent_id + name = "VAULT_ADDR" + value = var.vault_addr +} + +data "coder_workspace_owner" "me" {} diff --git a/vault-jwt/run.sh b/vault-jwt/run.sh new file mode 100644 index 00000000..0c305e22 --- /dev/null +++ b/vault-jwt/run.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +# Convert all templated variables to shell variables +VAULT_CLI_VERSION=${VAULT_CLI_VERSION} +VAULT_JWT_AUTH_PATH=${VAULT_JWT_AUTH_PATH} +VAULT_JWT_ROLE=${VAULT_JWT_ROLE} +CODER_OIDC_ACCESS_TOKEN=${CODER_OIDC_ACCESS_TOKEN} + +fetch() { + dest="$1" + url="$2" + if command -v curl > /dev/null 2>&1; then + curl -sSL --fail "$${url}" -o "$${dest}" + elif command -v wget > /dev/null 2>&1; then + wget -O "$${dest}" "$${url}" + elif command -v busybox > /dev/null 2>&1; then + busybox wget -O "$${dest}" "$${url}" + else + printf "curl, wget, or busybox is not installed. Please install curl or wget in your image.\n" + exit 1 + fi +} + +unzip_safe() { + if command -v unzip > /dev/null 2>&1; then + command unzip "$@" + elif command -v busybox > /dev/null 2>&1; then + busybox unzip "$@" + else + printf "unzip or busybox is not installed. Please install unzip in your image.\n" + exit 1 + fi +} + +install() { + # Get the architecture of the system + ARCH=$(uname -m) + if [ "$${ARCH}" = "x86_64" ]; then + ARCH="amd64" + elif [ "$${ARCH}" = "aarch64" ]; then + ARCH="arm64" + else + printf "Unsupported architecture: $${ARCH}\n" + return 1 + fi + # Fetch the latest version of Vault if INSTALL_VERSION is 'latest' + if [ "$${INSTALL_VERSION}" = "latest" ]; then + LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -v 'rc' | grep -oE 'vault/[0-9]+\.[0-9]+\.[0-9]+' | sed 's/vault\///' | sort -V | tail -n 1) + printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}" + if [ -z "$${LATEST_VERSION}" ]; then + printf "Failed to determine the latest Vault version.\n" + return 1 + fi + INSTALL_VERSION=$${LATEST_VERSION} + fi + + # Check if the vault CLI is installed and has the correct version + installation_needed=1 + if command -v vault > /dev/null 2>&1; then + CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + if [ "$${CURRENT_VERSION}" = "$${INSTALL_VERSION}" ]; then + printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}" + installation_needed=0 + fi + fi + + if [ $${installation_needed} -eq 1 ]; then + # Download and install Vault + if [ -z "$${CURRENT_VERSION}" ]; then + printf "Installing Vault CLI ...\n\n" + else + printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${INSTALL_VERSION}" + fi + fetch vault.zip "https://releases.hashicorp.com/vault/$${INSTALL_VERSION}/vault_$${INSTALL_VERSION}_linux_$${ARCH}.zip" + if [ $? -ne 0 ]; then + printf "Failed to download Vault.\n" + return 1 + fi + if ! unzip_safe vault.zip; then + printf "Failed to unzip Vault.\n" + return 1 + fi + rm vault.zip + if sudo mv vault /usr/local/bin/vault 2> /dev/null; then + printf "Vault installed successfully!\n\n" + else + mkdir -p ~/.local/bin + if ! mv vault ~/.local/bin/vault; then + printf "Failed to move Vault to local bin.\n" + return 1 + fi + printf "Please add ~/.local/bin to your PATH to use vault CLI.\n" + fi + fi + return 0 +} + +TMP=$(mktemp -d) +if ! ( + cd "$TMP" + install +); then + echo "Failed to install Vault CLI." + exit 1 +fi +rm -rf "$TMP" + +# Authenticate with Vault +printf "🔑 Authenticating with Vault ...\n\n" +echo "$${CODER_OIDC_ACCESS_TOKEN}" | vault write auth/$${VAULT_JWT_AUTH_PATH}/login role=$${VAULT_JWT_ROLE} jwt=- +printf "🥳 Vault authentication complete!\n\n" +printf "You can now use Vault CLI to access secrets.\n" From c9b1651ff509fb2ee3e4de0dbf4cf7bccb2f60e7 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 13 Sep 2024 18:58:57 +0500 Subject: [PATCH 2/8] Clarify role mapping in Vault integration docs --- vault-jwt/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault-jwt/README.md b/vault-jwt/README.md index 01f4341f..968785dc 100644 --- a/vault-jwt/README.md +++ b/vault-jwt/README.md @@ -55,7 +55,7 @@ module "vault" { } ``` -### Configure Vault integration with a role from your OIDC provider +### Configure Vault integration with a role from your OIDC provider by mapping the workspace owner's group to a Vault role ```tf data "coder_workspace_owner" "me" {} From b02c4de8245c71a5e1c32dd8f71d6348910d235e Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Wed, 18 Sep 2024 15:17:20 +0500 Subject: [PATCH 3/8] docs: update OIDC link in Vault JWT README --- vault-jwt/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault-jwt/README.md b/vault-jwt/README.md index 968785dc..8f9e2bb1 100644 --- a/vault-jwt/README.md +++ b/vault-jwt/README.md @@ -10,7 +10,7 @@ tags: [helper, integration, vault, jwt, oidc] # Hashicorp Vault Integration (JWT) -This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces using a [JWT](https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication) from Coder's OIDC provider. +This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces using a [JWT](https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication) from Coder's [OIDC authentication method](https://coder.com/docs/admin/auth#openid-connect). ```tf module "vault" { From bf24358b89ac62a5c6a61955b934bea08b12808b Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Mon, 23 Sep 2024 14:28:53 +0500 Subject: [PATCH 4/8] Update module in tsconfig to 'nodenext' --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index dd38e58a..5de8ae31 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "esnext", - "module": "esnext", + "module": "nodenext", "strict": true, "allowSyntheticDefaultImports": true, "moduleResolution": "nodenext", From d3a796e91d0c4ed40df438257e5711b5b3ab240d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 24 Sep 2024 11:28:33 +0500 Subject: [PATCH 5/8] Fix variable names in Vault JWT module scripts - Correct the variable name in `main.tf` and `run.sh` to ensure they are consistent and match expected inputs for Vault CLI interactions. --- vault-jwt/main.tf | 4 ++-- vault-jwt/run.sh | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vault-jwt/main.tf b/vault-jwt/main.tf index a8bf4ab3..adcc34d4 100644 --- a/vault-jwt/main.tf +++ b/vault-jwt/main.tf @@ -46,9 +46,9 @@ resource "coder_script" "vault" { display_name = "Vault (GitHub)" icon = "/icon/vault.svg" script = templatefile("${path.module}/run.sh", { - CODER_OIDC_ACCESS_TOKEN : data.coder_workspace_owner.me.oidc_token, + CODER_OIDC_ACCESS_TOKEN : data.coder_workspace_owner.me.oidc_access_token, VAULT_JWT_AUTH_PATH : var.vault_jwt_auth_path, - VAULT_ROLE : var.vault_jwt_role, + VAULT_JWT_ROLE : var.vault_jwt_role, VAULT_CLI_VERSION : var.vault_cli_version, }) run_on_start = true diff --git a/vault-jwt/run.sh b/vault-jwt/run.sh index 0c305e22..61b69f40 100644 --- a/vault-jwt/run.sh +++ b/vault-jwt/run.sh @@ -43,22 +43,22 @@ install() { printf "Unsupported architecture: $${ARCH}\n" return 1 fi - # Fetch the latest version of Vault if INSTALL_VERSION is 'latest' - if [ "$${INSTALL_VERSION}" = "latest" ]; then + # Fetch the latest version of Vault if VAULT_CLI_VERSION is 'latest' + if [ "$${VAULT_CLI_VERSION}" = "latest" ]; then LATEST_VERSION=$(curl -s https://releases.hashicorp.com/vault/ | grep -v 'rc' | grep -oE 'vault/[0-9]+\.[0-9]+\.[0-9]+' | sed 's/vault\///' | sort -V | tail -n 1) printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}" if [ -z "$${LATEST_VERSION}" ]; then printf "Failed to determine the latest Vault version.\n" return 1 fi - INSTALL_VERSION=$${LATEST_VERSION} + VAULT_CLI_VERSION=$${VAULT_CLI_VERSION} fi # Check if the vault CLI is installed and has the correct version installation_needed=1 if command -v vault > /dev/null 2>&1; then CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') - if [ "$${CURRENT_VERSION}" = "$${INSTALL_VERSION}" ]; then + if [ "$${CURRENT_VERSION}" = "$${VAULT_CLI_VERSION}" ]; then printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}" installation_needed=0 fi @@ -69,9 +69,9 @@ install() { if [ -z "$${CURRENT_VERSION}" ]; then printf "Installing Vault CLI ...\n\n" else - printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${INSTALL_VERSION}" + printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${VAULT_CLI_VERSION}" fi - fetch vault.zip "https://releases.hashicorp.com/vault/$${INSTALL_VERSION}/vault_$${INSTALL_VERSION}_linux_$${ARCH}.zip" + fetch vault.zip "https://releases.hashicorp.com/vault/$${VAULT_CLI_VERSION}/vault_$${VAULT_CLI_VERSION}_linux_$${ARCH}.zip" if [ $? -ne 0 ]; then printf "Failed to download Vault.\n" return 1 From a94fda951ad810ff4f0bec8da253583b9f4aab10 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 27 Sep 2024 10:46:17 -0700 Subject: [PATCH 6/8] Update vault-jwt/run.sh Co-authored-by: Mathias Fredriksson --- vault-jwt/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vault-jwt/run.sh b/vault-jwt/run.sh index 61b69f40..662b378e 100644 --- a/vault-jwt/run.sh +++ b/vault-jwt/run.sh @@ -107,6 +107,6 @@ rm -rf "$TMP" # Authenticate with Vault printf "🔑 Authenticating with Vault ...\n\n" -echo "$${CODER_OIDC_ACCESS_TOKEN}" | vault write auth/$${VAULT_JWT_AUTH_PATH}/login role=$${VAULT_JWT_ROLE} jwt=- +echo "$${CODER_OIDC_ACCESS_TOKEN}" | vault write auth/"$${VAULT_JWT_AUTH_PATH}"/login role="$${VAULT_JWT_ROLE}" jwt=- printf "🥳 Vault authentication complete!\n\n" printf "You can now use Vault CLI to access secrets.\n" From 1dc02569e30056ac9c453c685b262b7340c9b022 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 27 Sep 2024 23:18:42 +0500 Subject: [PATCH 7/8] Clarify Hashicorp Vault Integration setup instructions - Simplified explanation of using OIDC access token. - Made language around configuration more direct. - Enhanced section titles for improved clarity. --- vault-jwt/README.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/vault-jwt/README.md b/vault-jwt/README.md index 8f9e2bb1..b801ff95 100644 --- a/vault-jwt/README.md +++ b/vault-jwt/README.md @@ -10,7 +10,7 @@ tags: [helper, integration, vault, jwt, oidc] # Hashicorp Vault Integration (JWT) -This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces using a [JWT](https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication) from Coder's [OIDC authentication method](https://coder.com/docs/admin/auth#openid-connect). +This module lets you authenticate with [Hashicorp Vault](https://www.vaultproject.io/) in your Coder workspaces by reusing the [OIDC](https://coder.com/docs/admin/auth#openid-connect) access token from Coder's OIDC authentication method. This requires configuring the Vault [JWT/OIDC](https://developer.hashicorp.com/vault/docs/auth/jwt#configuration) auth method. ```tf module "vault" { @@ -34,15 +34,9 @@ or using the Vault API: curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/data/coder" ``` -![Vault login](#) - -## Configuration - -To configure the Vault module, you must set up a Vault JWT auth method. See the [Vault documentation](https://developer.hashicorp.com/vault/docs/auth/jwt#configuration) for more information. - ## Examples -### Configure Vault integration with a non standard auth path +### Configure Vault integration with a non standard auth path (default is "jwt") ```tf module "vault" { @@ -55,7 +49,7 @@ module "vault" { } ``` -### Configure Vault integration with a role from your OIDC provider by mapping the workspace owner's group to a Vault role +### Map workspace owner's group to a Vault role ```tf data "coder_workspace_owner" "me" {} @@ -69,7 +63,7 @@ module "vault" { } ``` -### Configure Vault integration and install a specific version of the Vault CLI +### Install a specific version of the Vault CLI ```tf module "vault" { From 864ff241168cd8ec5411c8b564089ad69e8d4a5a Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 27 Sep 2024 23:19:40 +0500 Subject: [PATCH 8/8] Bump Vault JWT module version to 1.0.19 in README --- vault-jwt/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vault-jwt/README.md b/vault-jwt/README.md index b801ff95..67aa7e5c 100644 --- a/vault-jwt/README.md +++ b/vault-jwt/README.md @@ -15,7 +15,7 @@ This module lets you authenticate with [Hashicorp Vault](https://www.vaultprojec ```tf module "vault" { source = "registry.coder.com/modules/vault-jwt/coder" - version = "1.0.17" + version = "1.0.19" agent_id = coder_agent.example.id vault_addr = "https://vault.example.com" vault_jwt_role = "coder" # The Vault role to use for authentication @@ -41,7 +41,7 @@ curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/d ```tf module "vault" { source = "registry.coder.com/modules/vault-jwt/coder" - version = "1.0.17" + version = "1.0.19" agent_id = coder_agent.example.id vault_addr = "https://vault.example.com" vault_jwt_auth_path = "oidc" @@ -56,7 +56,7 @@ data "coder_workspace_owner" "me" {} module "vault" { source = "registry.coder.com/modules/vault-jwt/coder" - version = "1.0.7" + version = "1.0.19" agent_id = coder_agent.example.id vault_addr = "https://vault.example.com" vault_jwt_role = data.coder_workspace_owner.me.groups[0] @@ -68,7 +68,7 @@ module "vault" { ```tf module "vault" { source = "registry.coder.com/modules/vault-jwt/coder" - version = "1.0.17" + version = "1.0.19" agent_id = coder_agent.example.id vault_addr = "https://vault.example.com" vault_jwt_role = "coder" # The Vault role to use for authentication