Skip to content

Commit fb81c89

Browse files
matifalimafredri
andauthored
feat(vault-jwt): Add Vault JWT/OIDC module (#297)
Co-authored-by: Mathias Fredriksson <[email protected]>
1 parent 1628087 commit fb81c89

File tree

5 files changed

+266
-1
lines changed

5 files changed

+266
-1
lines changed

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "esnext",
4-
"module": "esnext",
4+
"module": "nodenext",
55
"strict": true,
66
"allowSyntheticDefaultImports": true,
77
"moduleResolution": "nodenext",

vault-jwt/README.md

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
display_name: Hashicorp Vault Integration (JWT)
3+
description: Authenticates with Vault using a JWT from Coder's OIDC provider
4+
icon: ../.icons/vault.svg
5+
maintainer_github: coder
6+
partner_github: hashicorp
7+
verified: true
8+
tags: [helper, integration, vault, jwt, oidc]
9+
---
10+
11+
# Hashicorp Vault Integration (JWT)
12+
13+
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.
14+
15+
```tf
16+
module "vault" {
17+
source = "registry.coder.com/modules/vault-jwt/coder"
18+
version = "1.0.19"
19+
agent_id = coder_agent.example.id
20+
vault_addr = "https://vault.example.com"
21+
vault_jwt_role = "coder" # The Vault role to use for authentication
22+
}
23+
```
24+
25+
Then you can use the Vault CLI in your workspaces to fetch secrets from Vault:
26+
27+
```shell
28+
vault kv get -namespace=coder -mount=secrets coder
29+
```
30+
31+
or using the Vault API:
32+
33+
```shell
34+
curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/data/coder"
35+
```
36+
37+
## Examples
38+
39+
### Configure Vault integration with a non standard auth path (default is "jwt")
40+
41+
```tf
42+
module "vault" {
43+
source = "registry.coder.com/modules/vault-jwt/coder"
44+
version = "1.0.19"
45+
agent_id = coder_agent.example.id
46+
vault_addr = "https://vault.example.com"
47+
vault_jwt_auth_path = "oidc"
48+
vault_jwt_role = "coder" # The Vault role to use for authentication
49+
}
50+
```
51+
52+
### Map workspace owner's group to a Vault role
53+
54+
```tf
55+
data "coder_workspace_owner" "me" {}
56+
57+
module "vault" {
58+
source = "registry.coder.com/modules/vault-jwt/coder"
59+
version = "1.0.19"
60+
agent_id = coder_agent.example.id
61+
vault_addr = "https://vault.example.com"
62+
vault_jwt_role = data.coder_workspace_owner.me.groups[0]
63+
}
64+
```
65+
66+
### Install a specific version of the Vault CLI
67+
68+
```tf
69+
module "vault" {
70+
source = "registry.coder.com/modules/vault-jwt/coder"
71+
version = "1.0.19"
72+
agent_id = coder_agent.example.id
73+
vault_addr = "https://vault.example.com"
74+
vault_jwt_role = "coder" # The Vault role to use for authentication
75+
vault_cli_version = "1.17.5"
76+
}
77+
```

vault-jwt/main.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { describe } from "bun:test";
2+
import { runTerraformInit, testRequiredVariables } from "../test";
3+
4+
describe("vault-jwt", async () => {
5+
await runTerraformInit(import.meta.dir);
6+
7+
testRequiredVariables(import.meta.dir, {
8+
agent_id: "foo",
9+
vault_addr: "foo",
10+
vault_jwt_role: "foo",
11+
});
12+
});

vault-jwt/main.tf

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 0.12.4"
8+
}
9+
}
10+
}
11+
12+
# Add required variables for your modules and remove any unneeded variables
13+
variable "agent_id" {
14+
type = string
15+
description = "The ID of a Coder agent."
16+
}
17+
18+
variable "vault_addr" {
19+
type = string
20+
description = "The address of the Vault server."
21+
}
22+
23+
variable "vault_jwt_auth_path" {
24+
type = string
25+
description = "The path to the Vault JWT auth method."
26+
default = "jwt"
27+
}
28+
29+
variable "vault_jwt_role" {
30+
type = string
31+
description = "The name of the Vault role to use for authentication."
32+
}
33+
34+
variable "vault_cli_version" {
35+
type = string
36+
description = "The version of Vault to install."
37+
default = "latest"
38+
validation {
39+
condition = can(regex("^(latest|[0-9]+\\.[0-9]+\\.[0-9]+)$", var.vault_cli_version))
40+
error_message = "Vault version must be in the format 0.0.0 or latest"
41+
}
42+
}
43+
44+
resource "coder_script" "vault" {
45+
agent_id = var.agent_id
46+
display_name = "Vault (GitHub)"
47+
icon = "/icon/vault.svg"
48+
script = templatefile("${path.module}/run.sh", {
49+
CODER_OIDC_ACCESS_TOKEN : data.coder_workspace_owner.me.oidc_access_token,
50+
VAULT_JWT_AUTH_PATH : var.vault_jwt_auth_path,
51+
VAULT_JWT_ROLE : var.vault_jwt_role,
52+
VAULT_CLI_VERSION : var.vault_cli_version,
53+
})
54+
run_on_start = true
55+
start_blocks_login = true
56+
}
57+
58+
resource "coder_env" "vault_addr" {
59+
agent_id = var.agent_id
60+
name = "VAULT_ADDR"
61+
value = var.vault_addr
62+
}
63+
64+
data "coder_workspace_owner" "me" {}

vault-jwt/run.sh

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env bash
2+
3+
# Convert all templated variables to shell variables
4+
VAULT_CLI_VERSION=${VAULT_CLI_VERSION}
5+
VAULT_JWT_AUTH_PATH=${VAULT_JWT_AUTH_PATH}
6+
VAULT_JWT_ROLE=${VAULT_JWT_ROLE}
7+
CODER_OIDC_ACCESS_TOKEN=${CODER_OIDC_ACCESS_TOKEN}
8+
9+
fetch() {
10+
dest="$1"
11+
url="$2"
12+
if command -v curl > /dev/null 2>&1; then
13+
curl -sSL --fail "$${url}" -o "$${dest}"
14+
elif command -v wget > /dev/null 2>&1; then
15+
wget -O "$${dest}" "$${url}"
16+
elif command -v busybox > /dev/null 2>&1; then
17+
busybox wget -O "$${dest}" "$${url}"
18+
else
19+
printf "curl, wget, or busybox is not installed. Please install curl or wget in your image.\n"
20+
exit 1
21+
fi
22+
}
23+
24+
unzip_safe() {
25+
if command -v unzip > /dev/null 2>&1; then
26+
command unzip "$@"
27+
elif command -v busybox > /dev/null 2>&1; then
28+
busybox unzip "$@"
29+
else
30+
printf "unzip or busybox is not installed. Please install unzip in your image.\n"
31+
exit 1
32+
fi
33+
}
34+
35+
install() {
36+
# Get the architecture of the system
37+
ARCH=$(uname -m)
38+
if [ "$${ARCH}" = "x86_64" ]; then
39+
ARCH="amd64"
40+
elif [ "$${ARCH}" = "aarch64" ]; then
41+
ARCH="arm64"
42+
else
43+
printf "Unsupported architecture: $${ARCH}\n"
44+
return 1
45+
fi
46+
# Fetch the latest version of Vault if VAULT_CLI_VERSION is 'latest'
47+
if [ "$${VAULT_CLI_VERSION}" = "latest" ]; then
48+
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)
49+
printf "Latest version of Vault is %s.\n\n" "$${LATEST_VERSION}"
50+
if [ -z "$${LATEST_VERSION}" ]; then
51+
printf "Failed to determine the latest Vault version.\n"
52+
return 1
53+
fi
54+
VAULT_CLI_VERSION=$${VAULT_CLI_VERSION}
55+
fi
56+
57+
# Check if the vault CLI is installed and has the correct version
58+
installation_needed=1
59+
if command -v vault > /dev/null 2>&1; then
60+
CURRENT_VERSION=$(vault version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
61+
if [ "$${CURRENT_VERSION}" = "$${VAULT_CLI_VERSION}" ]; then
62+
printf "Vault version %s is already installed and up-to-date.\n\n" "$${CURRENT_VERSION}"
63+
installation_needed=0
64+
fi
65+
fi
66+
67+
if [ $${installation_needed} -eq 1 ]; then
68+
# Download and install Vault
69+
if [ -z "$${CURRENT_VERSION}" ]; then
70+
printf "Installing Vault CLI ...\n\n"
71+
else
72+
printf "Upgrading Vault CLI from version %s to %s ...\n\n" "$${CURRENT_VERSION}" "${VAULT_CLI_VERSION}"
73+
fi
74+
fetch vault.zip "https://releases.hashicorp.com/vault/$${VAULT_CLI_VERSION}/vault_$${VAULT_CLI_VERSION}_linux_$${ARCH}.zip"
75+
if [ $? -ne 0 ]; then
76+
printf "Failed to download Vault.\n"
77+
return 1
78+
fi
79+
if ! unzip_safe vault.zip; then
80+
printf "Failed to unzip Vault.\n"
81+
return 1
82+
fi
83+
rm vault.zip
84+
if sudo mv vault /usr/local/bin/vault 2> /dev/null; then
85+
printf "Vault installed successfully!\n\n"
86+
else
87+
mkdir -p ~/.local/bin
88+
if ! mv vault ~/.local/bin/vault; then
89+
printf "Failed to move Vault to local bin.\n"
90+
return 1
91+
fi
92+
printf "Please add ~/.local/bin to your PATH to use vault CLI.\n"
93+
fi
94+
fi
95+
return 0
96+
}
97+
98+
TMP=$(mktemp -d)
99+
if ! (
100+
cd "$TMP"
101+
install
102+
); then
103+
echo "Failed to install Vault CLI."
104+
exit 1
105+
fi
106+
rm -rf "$TMP"
107+
108+
# Authenticate with Vault
109+
printf "🔑 Authenticating with Vault ...\n\n"
110+
echo "$${CODER_OIDC_ACCESS_TOKEN}" | vault write auth/"$${VAULT_JWT_AUTH_PATH}"/login role="$${VAULT_JWT_ROLE}" jwt=-
111+
printf "🥳 Vault authentication complete!\n\n"
112+
printf "You can now use Vault CLI to access secrets.\n"

0 commit comments

Comments
 (0)