Skip to content

Commit 2cfbe5f

Browse files
authored
feat: add vault-cli module with optional token configuration (#575)
1 parent 186e0c4 commit 2cfbe5f

File tree

4 files changed

+556
-0
lines changed

4 files changed

+556
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
display_name: Vault CLI
3+
description: Installs the Hashicorp Vault CLI and optionally configures token authentication
4+
icon: ../../../../.icons/vault.svg
5+
verified: true
6+
tags: [helper, integration, vault, cli]
7+
---
8+
9+
# Vault CLI
10+
11+
Installs the [Vault](https://www.vaultproject.io/) CLI and optionally configures token authentication. This module focuses on CLI installation and can be used standalone or as a base for other authentication methods.
12+
13+
```tf
14+
module "vault_cli" {
15+
source = "registry.coder.com/coder/vault-cli/coder"
16+
version = "1.0.0"
17+
agent_id = coder_agent.example.id
18+
vault_addr = "https://vault.example.com"
19+
}
20+
```
21+
22+
## Prerequisites
23+
24+
The following tools are required in the workspace image:
25+
26+
- **HTTP client**: `curl`, `wget`, or `busybox` (at least one)
27+
- **Archive utility**: `unzip` or `busybox` (at least one)
28+
- **jq**: Optional but recommended for reliable JSON parsing (falls back to sed if not available)
29+
30+
## With Token Authentication
31+
32+
If you have a Vault token, you can provide it to automatically configure authentication:
33+
34+
```tf
35+
module "vault_cli" {
36+
source = "registry.coder.com/coder/vault-cli/coder"
37+
version = "1.0.0"
38+
agent_id = coder_agent.example.id
39+
vault_addr = "https://vault.example.com"
40+
vault_token = var.vault_token # Optional
41+
}
42+
```
43+
44+
## Examples
45+
46+
### Basic Installation (CLI Only)
47+
48+
Install the Vault CLI without any authentication:
49+
50+
```tf
51+
module "vault_cli" {
52+
source = "registry.coder.com/coder/vault-cli/coder"
53+
version = "1.0.0"
54+
agent_id = coder_agent.example.id
55+
vault_addr = "https://vault.example.com"
56+
}
57+
```
58+
59+
### With Specific Version
60+
61+
```tf
62+
module "vault_cli" {
63+
source = "registry.coder.com/coder/vault-cli/coder"
64+
version = "1.0.0"
65+
agent_id = coder_agent.example.id
66+
vault_addr = "https://vault.example.com"
67+
vault_cli_version = "1.15.0"
68+
}
69+
```
70+
71+
### Custom Installation Directory
72+
73+
```tf
74+
module "vault_cli" {
75+
source = "registry.coder.com/coder/vault-cli/coder"
76+
version = "1.0.0"
77+
agent_id = coder_agent.example.id
78+
vault_addr = "https://vault.example.com"
79+
install_dir = "/home/coder/bin"
80+
}
81+
```
82+
83+
### With Vault Enterprise Namespace
84+
85+
For Vault Enterprise users who need to specify a namespace:
86+
87+
```tf
88+
module "vault_cli" {
89+
source = "registry.coder.com/coder/vault-cli/coder"
90+
version = "1.0.0"
91+
agent_id = coder_agent.example.id
92+
vault_addr = "https://vault.example.com"
93+
vault_token = var.vault_token
94+
vault_namespace = "admin/my-namespace"
95+
}
96+
```
97+
98+
## Related Modules
99+
100+
For more advanced authentication methods, see:
101+
102+
- [vault-github](https://registry.coder.com/modules/coder/vault-github) - Authenticate with Vault using GitHub tokens
103+
- [vault-jwt](https://registry.coder.com/modules/coder/vault-jwt) - Authenticate with Vault using OIDC/JWT
104+
105+
For simple token-based authentication, see:
106+
107+
- [vault-token](https://registry.coder.com/modules/coder/vault-token) - Authenticate with Vault using a token
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 0.17"
8+
}
9+
}
10+
}
11+
12+
variable "agent_id" {
13+
type = string
14+
description = "The ID of a Coder agent."
15+
}
16+
17+
variable "vault_addr" {
18+
type = string
19+
description = "The address of the Vault server."
20+
}
21+
22+
variable "vault_token" {
23+
type = string
24+
description = "The Vault token to use for authentication. If not provided, only the CLI will be installed."
25+
default = ""
26+
sensitive = true
27+
}
28+
29+
variable "install_dir" {
30+
type = string
31+
description = "The directory to install the Vault CLI to."
32+
default = "/usr/local/bin"
33+
}
34+
35+
variable "vault_cli_version" {
36+
type = string
37+
description = "The version of the Vault CLI to install."
38+
default = "latest"
39+
validation {
40+
condition = var.vault_cli_version == "latest" || can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+$", var.vault_cli_version))
41+
error_message = "vault_cli_version must be either 'latest' or a semantic version (e.g., '1.15.0')."
42+
}
43+
}
44+
45+
variable "vault_namespace" {
46+
type = string
47+
description = "The Vault Enterprise namespace to use. If not provided, no namespace will be configured."
48+
default = null
49+
}
50+
51+
data "coder_workspace" "me" {}
52+
53+
resource "coder_script" "vault_cli" {
54+
agent_id = var.agent_id
55+
display_name = "Vault CLI"
56+
icon = "/icon/vault.svg"
57+
script = templatefile("${path.module}/run.sh", {
58+
VAULT_ADDR = var.vault_addr
59+
VAULT_TOKEN = var.vault_token
60+
INSTALL_DIR = var.install_dir
61+
VAULT_CLI_VERSION = var.vault_cli_version
62+
})
63+
run_on_start = true
64+
start_blocks_login = true
65+
}
66+
67+
resource "coder_env" "vault_addr" {
68+
agent_id = var.agent_id
69+
name = "VAULT_ADDR"
70+
value = var.vault_addr
71+
}
72+
73+
resource "coder_env" "vault_token" {
74+
count = var.vault_token != "" ? 1 : 0
75+
agent_id = var.agent_id
76+
name = "VAULT_TOKEN"
77+
value = var.vault_token
78+
}
79+
80+
resource "coder_env" "vault_namespace" {
81+
count = var.vault_namespace != null ? 1 : 0
82+
agent_id = var.agent_id
83+
name = "VAULT_NAMESPACE"
84+
value = var.vault_namespace
85+
}
86+
87+
output "vault_cli_version" {
88+
description = "The version of the Vault CLI that was installed."
89+
value = var.vault_cli_version
90+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
mock_provider "coder" {}
2+
3+
variables {
4+
agent_id = "test-agent-id"
5+
vault_addr = "https://vault.example.com"
6+
}
7+
8+
run "test_vault_cli_without_token" {
9+
assert {
10+
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
11+
error_message = "Display name should be 'Vault CLI'"
12+
}
13+
14+
assert {
15+
condition = resource.coder_env.vault_addr.name == "VAULT_ADDR"
16+
error_message = "VAULT_ADDR environment variable should be set"
17+
}
18+
19+
assert {
20+
condition = resource.coder_env.vault_addr.value == "https://vault.example.com"
21+
error_message = "VAULT_ADDR should match the provided vault_addr"
22+
}
23+
24+
assert {
25+
condition = length(resource.coder_env.vault_token) == 0
26+
error_message = "VAULT_TOKEN should not be set when vault_token is not provided"
27+
}
28+
29+
assert {
30+
condition = length(resource.coder_env.vault_namespace) == 0
31+
error_message = "VAULT_NAMESPACE should not be set when vault_namespace is not provided"
32+
}
33+
}
34+
35+
run "test_vault_cli_with_token" {
36+
variables {
37+
vault_token = "test-vault-token"
38+
}
39+
40+
assert {
41+
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
42+
error_message = "Display name should be 'Vault CLI'"
43+
}
44+
45+
assert {
46+
condition = resource.coder_env.vault_addr.name == "VAULT_ADDR"
47+
error_message = "VAULT_ADDR environment variable should be set"
48+
}
49+
50+
assert {
51+
condition = length(resource.coder_env.vault_token) == 1
52+
error_message = "VAULT_TOKEN should be set when vault_token is provided"
53+
}
54+
55+
assert {
56+
condition = resource.coder_env.vault_token[0].name == "VAULT_TOKEN"
57+
error_message = "VAULT_TOKEN environment variable name should be correct"
58+
}
59+
60+
assert {
61+
condition = resource.coder_env.vault_token[0].value == "test-vault-token"
62+
error_message = "VAULT_TOKEN should match the provided vault_token"
63+
}
64+
}
65+
66+
run "test_vault_cli_custom_version" {
67+
variables {
68+
vault_cli_version = "1.15.0"
69+
}
70+
71+
assert {
72+
condition = output.vault_cli_version == "1.15.0"
73+
error_message = "Vault CLI version output should match the provided version"
74+
}
75+
}
76+
77+
run "test_vault_cli_custom_install_dir" {
78+
variables {
79+
install_dir = "/custom/install/dir"
80+
}
81+
82+
assert {
83+
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
84+
error_message = "Display name should be 'Vault CLI'"
85+
}
86+
}
87+
88+
run "test_vault_cli_invalid_version" {
89+
command = plan
90+
91+
variables {
92+
vault_cli_version = "invalid-version"
93+
}
94+
95+
expect_failures = [var.vault_cli_version]
96+
}
97+
98+
run "test_vault_cli_valid_semver" {
99+
variables {
100+
vault_cli_version = "1.18.3"
101+
}
102+
103+
assert {
104+
condition = output.vault_cli_version == "1.18.3"
105+
error_message = "Vault CLI version output should match the provided version"
106+
}
107+
}
108+
109+
run "test_vault_cli_rejects_v_prefix" {
110+
command = plan
111+
112+
variables {
113+
vault_cli_version = "v1.18.3"
114+
}
115+
116+
expect_failures = [var.vault_cli_version]
117+
}
118+
119+
run "test_vault_cli_with_namespace" {
120+
variables {
121+
vault_namespace = "admin/my-namespace"
122+
}
123+
124+
assert {
125+
condition = length(resource.coder_env.vault_namespace) == 1
126+
error_message = "VAULT_NAMESPACE should be set when vault_namespace is provided"
127+
}
128+
129+
assert {
130+
condition = resource.coder_env.vault_namespace[0].name == "VAULT_NAMESPACE"
131+
error_message = "VAULT_NAMESPACE environment variable name should be correct"
132+
}
133+
134+
assert {
135+
condition = resource.coder_env.vault_namespace[0].value == "admin/my-namespace"
136+
error_message = "VAULT_NAMESPACE should match the provided vault_namespace"
137+
}
138+
}
139+
140+
run "test_vault_cli_with_token_and_namespace" {
141+
variables {
142+
vault_token = "test-vault-token"
143+
vault_namespace = "admin/my-namespace"
144+
}
145+
146+
assert {
147+
condition = length(resource.coder_env.vault_token) == 1
148+
error_message = "VAULT_TOKEN should be set when vault_token is provided"
149+
}
150+
151+
assert {
152+
condition = length(resource.coder_env.vault_namespace) == 1
153+
error_message = "VAULT_NAMESPACE should be set when vault_namespace is provided"
154+
}
155+
156+
assert {
157+
condition = resource.coder_env.vault_token[0].value == "test-vault-token"
158+
error_message = "VAULT_TOKEN should match the provided vault_token"
159+
}
160+
161+
assert {
162+
condition = resource.coder_env.vault_namespace[0].value == "admin/my-namespace"
163+
error_message = "VAULT_NAMESPACE should match the provided vault_namespace"
164+
}
165+
}

0 commit comments

Comments
 (0)