feat: add 1password module under bpmct namespace (#824)
Adds a 1Password module under the `bpmct` namespace. ## What it does Installs the [1Password CLI](https://developer.1password.com/docs/cli/) (`op`) into Coder workspaces at startup. Two auth paths: - **Service account token** — set `service_account_token` and `OP_SERVICE_ACCOUNT_TOKEN` is injected automatically. Fully headless. - **Personal account** — set `account_address`, `account_email`, `account_secret_key` to pre-register the account. User runs `op signin` in their terminal. Optionally installs the [1Password VS Code extension](https://marketplace.visualstudio.com/items?itemName=1Password.op-vscode) (`1Password.op-vscode`) for code-server and VS Code with `install_vscode_extension = true`. Supports `pre_install_script` and `post_install_script` for custom orchestration. ## What's included - `registry/bpmct/` — new namespace (Ben Potter, community) - `registry/bpmct/modules/1password/` — the module (`main.tf`, `run.sh`, `README.md`) - `.icons/1password.svg` — 1Password logo from Simple Icons ## Tested Spun up a dev Coder instance, pushed the template with a real 1Password service account token, created a workspace, and confirmed: - `op` CLI installs and authenticates - `op vault list` returns vaults - `1Password.op-vscode` extension installs in code-server --------- Co-authored-by: DevCats <christofer@coder.com>
This commit is contained in:
parent
caaff0c1e9
commit
5bc668aa4d
1
.icons/1password.svg
Normal file
1
.icons/1password.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>1Password</title><path d="M12 .007C5.373.007 0 5.376 0 11.999c0 6.624 5.373 11.994 12 11.994S24 18.623 24 12C24 5.376 18.627.007 12 .007Zm-.895 4.857h1.788c.484 0 .729.002.914.096a.86.86 0 0 1 .377.377c.094.185.095.428.095.912v6.016c0 .12 0 .182-.015.238a.427.427 0 0 1-.067.137.923.923 0 0 1-.174.162l-.695.564c-.113.092-.17.138-.191.194a.216.216 0 0 0 0 .15c.02.055.078.101.191.193l.695.565c.094.076.14.115.174.162.03.042.053.087.067.137a.936.936 0 0 1 .015.238v2.746c0 .484-.001.727-.095.912a.86.86 0 0 1-.377.377c-.185.094-.43.096-.914.096h-1.788c-.484 0-.726-.002-.912-.096a.86.86 0 0 1-.377-.377c-.094-.185-.095-.428-.095-.912v-6.016c0-.12 0-.182.015-.238a.437.437 0 0 1 .067-.139c.034-.047.08-.083.174-.16l.695-.564c.113-.092.17-.138.191-.194a.216.216 0 0 0 0-.15c-.02-.055-.078-.101-.191-.193l-.695-.565a.92.92 0 0 1-.174-.162.437.437 0 0 1-.067-.139.92.92 0 0 1-.015-.236V6.25c0-.484.001-.727.095-.912a.86.86 0 0 1 .377-.377c.186-.094.428-.096.912-.096z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
registry/bpmct/.images/avatar.png
Normal file
BIN
registry/bpmct/.images/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
11
registry/bpmct/README.md
Normal file
11
registry/bpmct/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
display_name: Ben Potter
|
||||
bio: Tinkerer and Product Manager at Coder
|
||||
github: bpmct
|
||||
avatar: ./.images/avatar.png
|
||||
status: community
|
||||
---
|
||||
|
||||
# Ben Potter
|
||||
|
||||
Tinkerer and Product Manager at Coder. Building modules to make dev environments better.
|
||||
95
registry/bpmct/modules/onepassword/README.md
Normal file
95
registry/bpmct/modules/onepassword/README.md
Normal file
@ -0,0 +1,95 @@
|
||||
---
|
||||
display_name: "1Password"
|
||||
description: "Install the 1Password CLI and VS Code extension in your Coder workspace"
|
||||
icon: ../../../../.icons/1password.svg
|
||||
verified: false
|
||||
tags: [integration, 1password, secrets]
|
||||
---
|
||||
|
||||
# 1Password
|
||||
|
||||
Install the [1Password CLI](https://developer.1password.com/docs/cli/)
|
||||
(`op`) in your Coder workspace and optionally authenticate with a service
|
||||
account token. Can also install the
|
||||
[1Password VS Code extension](https://marketplace.visualstudio.com/items?itemName=1Password.op-vscode)
|
||||
for code-server and VS Code.
|
||||
|
||||
```tf
|
||||
module "onepassword" {
|
||||
source = "registry.coder.com/bpmct/onepassword/coder"
|
||||
version = "1.0.0"
|
||||
agent_id = coder_agent.main.id
|
||||
service_account_token = var.op_service_account_token
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### Service Account (recommended)
|
||||
|
||||
Create a [1Password service account](https://developer.1password.com/docs/service-accounts/get-started/)
|
||||
and pass the token as a Terraform variable. The module sets
|
||||
`OP_SERVICE_ACCOUNT_TOKEN` in the workspace so `op` commands work
|
||||
immediately.
|
||||
|
||||
```tf
|
||||
variable "op_service_account_token" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
module "onepassword" {
|
||||
source = "registry.coder.com/bpmct/onepassword/coder"
|
||||
version = "1.0.0"
|
||||
agent_id = coder_agent.main.id
|
||||
service_account_token = var.op_service_account_token
|
||||
}
|
||||
```
|
||||
|
||||
### Personal Account
|
||||
|
||||
Pass your account details and the module will pre-register the account.
|
||||
You'll be prompted for your password when you run `op signin` in the
|
||||
terminal.
|
||||
|
||||
```tf
|
||||
module "onepassword" {
|
||||
source = "registry.coder.com/bpmct/onepassword/coder"
|
||||
version = "1.0.0"
|
||||
agent_id = coder_agent.main.id
|
||||
account_address = "myteam.1password.com"
|
||||
account_email = "you@example.com"
|
||||
account_secret_key = var.op_secret_key
|
||||
}
|
||||
```
|
||||
|
||||
## VS Code Extension
|
||||
|
||||
Set `install_vscode_extension = true` to install the 1Password extension
|
||||
for code-server and VS Code.
|
||||
|
||||
```tf
|
||||
module "onepassword" {
|
||||
source = "registry.coder.com/bpmct/onepassword/coder"
|
||||
version = "1.0.0"
|
||||
agent_id = coder_agent.main.id
|
||||
service_account_token = var.op_service_account_token
|
||||
install_vscode_extension = true
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Scripts
|
||||
|
||||
Run custom logic before or after the CLI is installed.
|
||||
|
||||
```tf
|
||||
module "onepassword" {
|
||||
source = "registry.coder.com/bpmct/onepassword/coder"
|
||||
version = "1.0.0"
|
||||
agent_id = coder_agent.main.id
|
||||
service_account_token = var.op_service_account_token
|
||||
post_install_script = <<-EOT
|
||||
op read "op://Vault/item/field" > ~/.secret
|
||||
EOT
|
||||
}
|
||||
```
|
||||
112
registry/bpmct/modules/onepassword/main.tf
Normal file
112
registry/bpmct/modules/onepassword/main.tf
Normal file
@ -0,0 +1,112 @@
|
||||
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 "service_account_token" {
|
||||
type = string
|
||||
description = "A 1Password service account token. If set, account-based sign-in is skipped."
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "account_address" {
|
||||
type = string
|
||||
description = "The 1Password account sign-in address (e.g. myteam.1password.com)."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "account_email" {
|
||||
type = string
|
||||
description = "The email address for the 1Password account."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "account_secret_key" {
|
||||
type = string
|
||||
description = "The Secret Key for the 1Password account."
|
||||
default = ""
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "install_dir" {
|
||||
type = string
|
||||
description = "The directory to install the 1Password CLI to."
|
||||
default = "/usr/local/bin"
|
||||
}
|
||||
|
||||
variable "op_cli_version" {
|
||||
type = string
|
||||
description = "The version of the 1Password CLI to install."
|
||||
default = "latest"
|
||||
validation {
|
||||
condition = var.op_cli_version == "latest" || can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+$", var.op_cli_version))
|
||||
error_message = "op_cli_version must be either 'latest' or a semantic version (e.g., '2.30.0')."
|
||||
}
|
||||
}
|
||||
|
||||
variable "install_vscode_extension" {
|
||||
type = bool
|
||||
description = "Install the 1Password VS Code extension for both VS Code and code-server."
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "pre_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run before installing the 1Password CLI."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "post_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run after installing the 1Password CLI."
|
||||
default = null
|
||||
}
|
||||
|
||||
data "coder_parameter" "account_password" {
|
||||
count = var.account_address != "" && var.service_account_token == "" ? 1 : 0
|
||||
type = "string"
|
||||
name = "op_account_password"
|
||||
display_name = "1Password Account Password"
|
||||
description = "Your 1Password account password. Used to sign in to the CLI."
|
||||
mutable = true
|
||||
default = ""
|
||||
}
|
||||
|
||||
resource "coder_script" "1password" {
|
||||
agent_id = var.agent_id
|
||||
display_name = "1Password CLI"
|
||||
icon = "/icon/1password.svg"
|
||||
script = templatefile("${path.module}/run.sh", {
|
||||
SERVICE_ACCOUNT_TOKEN = var.service_account_token
|
||||
ACCOUNT_ADDRESS = var.account_address
|
||||
ACCOUNT_EMAIL = var.account_email
|
||||
ACCOUNT_SECRET_KEY = var.account_secret_key
|
||||
ACCOUNT_PASSWORD = var.account_address != "" && var.service_account_token == "" ? data.coder_parameter.account_password[0].value : ""
|
||||
INSTALL_DIR = var.install_dir
|
||||
OP_CLI_VERSION = var.op_cli_version
|
||||
INSTALL_VSCODE_EXTENSION = var.install_vscode_extension
|
||||
PRE_INSTALL_SCRIPT = var.pre_install_script != null ? base64encode(var.pre_install_script) : ""
|
||||
POST_INSTALL_SCRIPT = var.post_install_script != null ? base64encode(var.post_install_script) : ""
|
||||
})
|
||||
run_on_start = true
|
||||
start_blocks_login = true
|
||||
}
|
||||
|
||||
resource "coder_env" "op_service_account_token" {
|
||||
count = var.service_account_token != "" ? 1 : 0
|
||||
agent_id = var.agent_id
|
||||
name = "OP_SERVICE_ACCOUNT_TOKEN"
|
||||
value = var.service_account_token
|
||||
}
|
||||
176
registry/bpmct/modules/onepassword/run.sh
Normal file
176
registry/bpmct/modules/onepassword/run.sh
Normal file
@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SERVICE_ACCOUNT_TOKEN="${SERVICE_ACCOUNT_TOKEN}"
|
||||
ACCOUNT_ADDRESS="${ACCOUNT_ADDRESS}"
|
||||
ACCOUNT_EMAIL="${ACCOUNT_EMAIL}"
|
||||
ACCOUNT_SECRET_KEY="${ACCOUNT_SECRET_KEY}"
|
||||
ACCOUNT_PASSWORD="${ACCOUNT_PASSWORD}"
|
||||
INSTALL_DIR="${INSTALL_DIR}"
|
||||
OP_CLI_VERSION="${OP_CLI_VERSION}"
|
||||
INSTALL_VSCODE_EXTENSION="${INSTALL_VSCODE_EXTENSION}"
|
||||
PRE_INSTALL_SCRIPT="${PRE_INSTALL_SCRIPT}"
|
||||
POST_INSTALL_SCRIPT="${POST_INSTALL_SCRIPT}"
|
||||
|
||||
fetch() {
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
curl -sSL --fail "$1"
|
||||
elif command -v wget > /dev/null 2>&1; then
|
||||
wget -qO- "$1"
|
||||
else
|
||||
printf "curl or wget is not installed.\n" && return 1
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_to_file() {
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
curl -sSL --fail "$2" -o "$1"
|
||||
elif command -v wget > /dev/null 2>&1; then
|
||||
wget -O "$1" "$2"
|
||||
else
|
||||
printf "curl or wget is not installed.\n" && return 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_script() {
|
||||
local ENCODED="$1" LABEL="$2"
|
||||
if [ -n "$${ENCODED}" ]; then
|
||||
printf "Running %s script...\n" "$${LABEL}"
|
||||
SCRIPT_PATH=$(mktemp /tmp/op-"$${LABEL}"-XXXXXX.sh)
|
||||
printf '%s' "$${ENCODED}" | base64 -d > "$${SCRIPT_PATH}"
|
||||
chmod +x "$${SCRIPT_PATH}"
|
||||
# shellcheck disable=SC2288
|
||||
"$${SCRIPT_PATH}" || printf "WARNING: %s script failed.\n" "$${LABEL}"
|
||||
rm -f "$${SCRIPT_PATH}"
|
||||
fi
|
||||
}
|
||||
|
||||
install() {
|
||||
ARCH=$(uname -m)
|
||||
if [ "$${ARCH}" = "x86_64" ]; then
|
||||
ARCH="amd64"
|
||||
elif [ "$${ARCH}" = "aarch64" ]; then
|
||||
ARCH="arm64"
|
||||
else
|
||||
printf "Unsupported architecture: %s\n" "$${ARCH}" && return 1
|
||||
fi
|
||||
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
if [ "$${OS}" != "linux" ] && [ "$${OS}" != "darwin" ]; then
|
||||
printf "Unsupported OS: %s\n" "$${OS}" && return 1
|
||||
fi
|
||||
|
||||
if [ "$${OP_CLI_VERSION}" = "latest" ]; then
|
||||
OP_CLI_VERSION=$(fetch "https://app-updates.agilebits.com/check/1/0/CLI2/en/2.0.0/N" \
|
||||
| grep -oE '"version":"[^"]+"' | head -1 | cut -d'"' -f4) || true
|
||||
if [ -z "$${OP_CLI_VERSION}" ]; then
|
||||
printf "Failed to resolve latest version, falling back to 2.30.3.\n"
|
||||
OP_CLI_VERSION="2.30.3"
|
||||
fi
|
||||
fi
|
||||
|
||||
printf "1Password CLI version: %s\n" "$${OP_CLI_VERSION}"
|
||||
|
||||
if command -v op > /dev/null 2>&1; then
|
||||
CURRENT_VERSION=$(op --version 2> /dev/null || true)
|
||||
if [ "$${CURRENT_VERSION}" = "$${OP_CLI_VERSION}" ]; then
|
||||
printf "Already installed.\n"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
DOWNLOAD_URL="https://cache.agilebits.com/dist/1P/op2/pkg/v$${OP_CLI_VERSION}/op_$${OS}_$${ARCH}_v$${OP_CLI_VERSION}.zip"
|
||||
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
cd "$${TEMP_DIR}" || return 1
|
||||
|
||||
if ! fetch_to_file op.zip "$${DOWNLOAD_URL}"; then
|
||||
rm -rf "$${TEMP_DIR}" && return 1
|
||||
fi
|
||||
|
||||
if command -v unzip > /dev/null 2>&1; then
|
||||
unzip -o op.zip -d . > /dev/null
|
||||
elif command -v busybox > /dev/null 2>&1; then
|
||||
busybox unzip op.zip -d .
|
||||
else
|
||||
printf "unzip is not installed.\n"
|
||||
rm -rf "$${TEMP_DIR}" && return 1
|
||||
fi
|
||||
|
||||
chmod +x op
|
||||
|
||||
if [ -n "$${INSTALL_DIR}" ] && [ -w "$${INSTALL_DIR}" ]; then
|
||||
mv op "$${INSTALL_DIR}/op"
|
||||
elif [ -n "$${INSTALL_DIR}" ] && sudo mv op "$${INSTALL_DIR}/op" 2> /dev/null; then
|
||||
true
|
||||
else
|
||||
mkdir -p ~/.local/bin && mv op ~/.local/bin/op
|
||||
INSTALL_DIR=~/.local/bin
|
||||
fi
|
||||
printf "Installed to %s.\n" "$${INSTALL_DIR}"
|
||||
|
||||
rm -rf "$${TEMP_DIR}"
|
||||
}
|
||||
|
||||
run_script "$${PRE_INSTALL_SCRIPT}" "pre-install"
|
||||
|
||||
if ! install; then
|
||||
printf "Failed to install 1Password CLI.\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$${SERVICE_ACCOUNT_TOKEN}" ]; then
|
||||
printf "Service account token configured.\n"
|
||||
elif [ -n "$${ACCOUNT_ADDRESS}" ] && [ -n "$${ACCOUNT_EMAIL}" ]; then
|
||||
ADD_ARGS="--address $${ACCOUNT_ADDRESS} --email $${ACCOUNT_EMAIL}"
|
||||
if [ -n "$${ACCOUNT_SECRET_KEY}" ]; then
|
||||
ADD_ARGS="$${ADD_ARGS} --secret-key $${ACCOUNT_SECRET_KEY}"
|
||||
fi
|
||||
|
||||
if [ -n "$${ACCOUNT_PASSWORD}" ] && command -v expect > /dev/null 2>&1; then
|
||||
OP_SESSION=$(expect -c "
|
||||
log_user 0
|
||||
spawn op account add $${ADD_ARGS} --raw
|
||||
expect \"Enter the password*\"
|
||||
send \"$${ACCOUNT_PASSWORD}\r\"
|
||||
expect eof
|
||||
catch wait result
|
||||
set output \$expect_out(buffer)
|
||||
puts -nonewline \$output
|
||||
" 2>&1)
|
||||
if op account list 2> /dev/null | grep -q "$${ACCOUNT_ADDRESS}"; then
|
||||
printf "Signed in to %s.\n" "$${ACCOUNT_ADDRESS}"
|
||||
if [ -n "$${OP_SESSION}" ]; then
|
||||
mkdir -p "$${HOME}/.op"
|
||||
SESSION_VAR="OP_SESSION_$(printf '%s' "$${ACCOUNT_ADDRESS}" | tr '.' '_' | tr '-' '_')"
|
||||
printf 'export %s="%s"\n' "$${SESSION_VAR}" "$${OP_SESSION}" > "$${HOME}/.op/session"
|
||||
chmod 600 "$${HOME}/.op/session"
|
||||
for rc in "$${HOME}/.bashrc" "$${HOME}/.zshrc"; do
|
||||
if [ -f "$${rc}" ] && ! grep -q ".op/session" "$${rc}" 2> /dev/null; then
|
||||
printf '\n[ -f ~/.op/session ] && . ~/.op/session\n' >> "$${rc}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
printf "Sign-in failed. Run manually: op signin --account %s\n" "$${ACCOUNT_ADDRESS}"
|
||||
fi
|
||||
else
|
||||
printf "To sign in, run in your terminal:\n"
|
||||
printf " op account add %s\n" "$${ADD_ARGS}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$${INSTALL_VSCODE_EXTENSION}" = "true" ]; then
|
||||
EXTENSION_ID="1Password.op-vscode"
|
||||
for _ in 1 2 3 4 5 6; do
|
||||
command -v code-server > /dev/null 2>&1 || command -v code > /dev/null 2>&1 && break
|
||||
sleep 5
|
||||
done
|
||||
if command -v code-server > /dev/null 2>&1; then
|
||||
cd /tmp && code-server --install-extension "$${EXTENSION_ID}" --force 2>&1 || true
|
||||
fi
|
||||
if command -v code > /dev/null 2>&1; then
|
||||
cd /tmp && code --install-extension "$${EXTENSION_ID}" --force 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
run_script "$${POST_INSTALL_SCRIPT}" "post-install"
|
||||
Loading…
x
Reference in New Issue
Block a user