Skip to content
Merged
107 changes: 107 additions & 0 deletions registry/coder/modules/vault-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
display_name: Vault CLI
description: Installs the Hashicorp Vault CLI and optionally configures token authentication
icon: ../../../../.icons/vault.svg
verified: true
tags: [helper, integration, vault, cli]
---

# Vault CLI

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.

```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
}
```

## Prerequisites

The following tools are required in the workspace image:

- **HTTP client**: `curl`, `wget`, or `busybox` (at least one)
- **Archive utility**: `unzip` or `busybox` (at least one)
- **jq**: Optional but recommended for reliable JSON parsing (falls back to sed if not available)

## With Token Authentication

If you have a Vault token, you can provide it to automatically configure authentication:

```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_token = var.vault_token # Optional
}
```

## Examples

### Basic Installation (CLI Only)

Install the Vault CLI without any authentication:

```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
}
```

### With Specific Version

```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_cli_version = "1.15.0"
}
```

### Custom Installation Directory

```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
install_dir = "/home/coder/bin"
}
```

### With Vault Enterprise Namespace

For Vault Enterprise users who need to specify a namespace:

```tf
module "vault_cli" {
source = "registry.coder.com/coder/vault-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com"
vault_token = var.vault_token
vault_namespace = "admin/my-namespace"
}
```

## Related Modules

For more advanced authentication methods, see:

- [vault-github](https://registry.coder.com/modules/coder/vault-github) - Authenticate with Vault using GitHub tokens
- [vault-jwt](https://registry.coder.com/modules/coder/vault-jwt) - Authenticate with Vault using OIDC/JWT

For simple token-based authentication, see:

- [vault-token](https://registry.coder.com/modules/coder/vault-token) - Authenticate with Vault using a token
90 changes: 90 additions & 0 deletions registry/coder/modules/vault-cli/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
terraform {
required_version = ">= 1.0"

required_providers {
coder = {
source = "coder/coder"
version = ">= 0.17"
}
}
}

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_token" {
type = string
description = "The Vault token to use for authentication. If not provided, only the CLI will be installed."
default = ""
sensitive = true
}

variable "install_dir" {
type = string
description = "The directory to install the Vault CLI to."
default = "/usr/local/bin"
}

variable "vault_cli_version" {
type = string
description = "The version of the Vault CLI to install."
default = "latest"
validation {
condition = var.vault_cli_version == "latest" || can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+$", var.vault_cli_version))
error_message = "vault_cli_version must be either 'latest' or a semantic version (e.g., '1.15.0')."
}
}

variable "vault_namespace" {
type = string
description = "The Vault Enterprise namespace to use. If not provided, no namespace will be configured."
default = null
}

data "coder_workspace" "me" {}

resource "coder_script" "vault_cli" {
agent_id = var.agent_id
display_name = "Vault CLI"
icon = "/icon/vault.svg"
script = templatefile("${path.module}/run.sh", {
VAULT_ADDR = var.vault_addr
VAULT_TOKEN = var.vault_token
INSTALL_DIR = var.install_dir
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
}

resource "coder_env" "vault_token" {
count = var.vault_token != "" ? 1 : 0
agent_id = var.agent_id
name = "VAULT_TOKEN"
value = var.vault_token
}

resource "coder_env" "vault_namespace" {
count = var.vault_namespace != null ? 1 : 0
agent_id = var.agent_id
name = "VAULT_NAMESPACE"
value = var.vault_namespace
}

output "vault_cli_version" {
description = "The version of the Vault CLI that was installed."
value = var.vault_cli_version
}
165 changes: 165 additions & 0 deletions registry/coder/modules/vault-cli/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
mock_provider "coder" {}

variables {
agent_id = "test-agent-id"
vault_addr = "https://vault.example.com"
}

run "test_vault_cli_without_token" {
assert {
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
error_message = "Display name should be 'Vault CLI'"
}

assert {
condition = resource.coder_env.vault_addr.name == "VAULT_ADDR"
error_message = "VAULT_ADDR environment variable should be set"
}

assert {
condition = resource.coder_env.vault_addr.value == "https://vault.example.com"
error_message = "VAULT_ADDR should match the provided vault_addr"
}

assert {
condition = length(resource.coder_env.vault_token) == 0
error_message = "VAULT_TOKEN should not be set when vault_token is not provided"
}

assert {
condition = length(resource.coder_env.vault_namespace) == 0
error_message = "VAULT_NAMESPACE should not be set when vault_namespace is not provided"
}
}

run "test_vault_cli_with_token" {
variables {
vault_token = "test-vault-token"
}

assert {
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
error_message = "Display name should be 'Vault CLI'"
}

assert {
condition = resource.coder_env.vault_addr.name == "VAULT_ADDR"
error_message = "VAULT_ADDR environment variable should be set"
}

assert {
condition = length(resource.coder_env.vault_token) == 1
error_message = "VAULT_TOKEN should be set when vault_token is provided"
}

assert {
condition = resource.coder_env.vault_token[0].name == "VAULT_TOKEN"
error_message = "VAULT_TOKEN environment variable name should be correct"
}

assert {
condition = resource.coder_env.vault_token[0].value == "test-vault-token"
error_message = "VAULT_TOKEN should match the provided vault_token"
}
}

run "test_vault_cli_custom_version" {
variables {
vault_cli_version = "1.15.0"
}

assert {
condition = output.vault_cli_version == "1.15.0"
error_message = "Vault CLI version output should match the provided version"
}
}

run "test_vault_cli_custom_install_dir" {
variables {
install_dir = "/custom/install/dir"
}

assert {
condition = resource.coder_script.vault_cli.display_name == "Vault CLI"
error_message = "Display name should be 'Vault CLI'"
}
}

run "test_vault_cli_invalid_version" {
command = plan

variables {
vault_cli_version = "invalid-version"
}

expect_failures = [var.vault_cli_version]
}

run "test_vault_cli_valid_semver" {
variables {
vault_cli_version = "1.18.3"
}

assert {
condition = output.vault_cli_version == "1.18.3"
error_message = "Vault CLI version output should match the provided version"
}
}

run "test_vault_cli_rejects_v_prefix" {
command = plan

variables {
vault_cli_version = "v1.18.3"
}

expect_failures = [var.vault_cli_version]
}

run "test_vault_cli_with_namespace" {
variables {
vault_namespace = "admin/my-namespace"
}

assert {
condition = length(resource.coder_env.vault_namespace) == 1
error_message = "VAULT_NAMESPACE should be set when vault_namespace is provided"
}

assert {
condition = resource.coder_env.vault_namespace[0].name == "VAULT_NAMESPACE"
error_message = "VAULT_NAMESPACE environment variable name should be correct"
}

assert {
condition = resource.coder_env.vault_namespace[0].value == "admin/my-namespace"
error_message = "VAULT_NAMESPACE should match the provided vault_namespace"
}
}

run "test_vault_cli_with_token_and_namespace" {
variables {
vault_token = "test-vault-token"
vault_namespace = "admin/my-namespace"
}

assert {
condition = length(resource.coder_env.vault_token) == 1
error_message = "VAULT_TOKEN should be set when vault_token is provided"
}

assert {
condition = length(resource.coder_env.vault_namespace) == 1
error_message = "VAULT_NAMESPACE should be set when vault_namespace is provided"
}

assert {
condition = resource.coder_env.vault_token[0].value == "test-vault-token"
error_message = "VAULT_TOKEN should match the provided vault_token"
}

assert {
condition = resource.coder_env.vault_namespace[0].value == "admin/my-namespace"
error_message = "VAULT_NAMESPACE should match the provided vault_namespace"
}
}
Loading