set fallback := true export K8S_VAULT_NAMESPACE := env("K8S_VAULT_NAMESPACE", "vault") export VAULT_CHART_VERSION := env("VAULT_CHART_VERSION", "0.31.0") export VAULT_HOST := env("VAULT_HOST", "") export VAULT_ADDR := "https://" + VAULT_HOST export VAULT_DEBUG := env("VAULT_DEBUG", "false") SECRET_PATH := "secret" # Vault environment setup scripts [private] _vault_root_env_setup := ''' if [ -z "${VAULT_TOKEN:-}" ]; then if [ "${VAULT_DEBUG}" = "true" ]; then echo "" >&2 echo "💡 To avoid entering Vault admin or root token repeatedly:" >&2 echo " • Set environment variable: export VAULT_TOKEN=your_root_token" >&2 echo " • or write it in .env.local file: VAULT_TOKEN=your_root_token" >&2 echo " • Use 1Password reference: VAULT_TOKEN=op://vault/root/token" >&2 echo "" >&2 fi VAULT_TOKEN=$(gum input --prompt="Vault admin or root token: " --password --width=100) elif [[ "${VAULT_TOKEN}" == op://* ]]; then if ! command -v op &>/dev/null; then echo "Error: 1Password CLI (op) is not installed." >&2 echo "" >&2 echo "To use 1Password secret references (op://...), please install the 1Password CLI:" >&2 echo " https://developer.1password.com/docs/cli/get-started/" >&2 exit 1 fi VAULT_TOKEN=$(op read "${VAULT_TOKEN}") fi export VAULT_TOKEN ''' [private] _vault_oidc_env_setup := ''' if [ -z "${VAULT_TOKEN:-}" ]; then if [ "${VAULT_DEBUG}" = "true" ]; then echo "" >&2 echo "💡 Authenticating with OIDC..." >&2 echo " • Browser will open for authentication" >&2 echo " • After login, token will be automatically set" >&2 echo "" >&2 fi vault login -method=oidc &>/dev/null VAULT_TOKEN=$(vault print token) fi export VAULT_TOKEN ''' [private] default: @just --list --unsorted --list-submodules # Add Helm repository add-helm-repo: helm repo add hashicorp https://helm.releases.hashicorp.com helm repo update # Remove Helm repository remove-helm-repo: helm repo remove hashicorp # Create Keycloak namespace create-namespace: @kubectl get namespace ${K8S_VAULT_NAMESPACE} &>/dev/null || \ kubectl create namespace ${K8S_VAULT_NAMESPACE} # Delete Keycloak namespace delete-namespace: @kubectl delete namespace ${K8S_VAULT_NAMESPACE} --ignore-not-found # Install Vault install: check-env #!/bin/bash set -eu just create-namespace just add-helm-repo kubectl label namespace ${K8S_VAULT_NAMESPACE} \ pod-security.kubernetes.io/enforce=restricted --overwrite gomplate -f vault-values.gomplate.yaml -o vault-values.yaml helm upgrade --cleanup-on-fail --install vault hashicorp/vault \ --version ${VAULT_CHART_VERSION} -n ${K8S_VAULT_NAMESPACE} --wait -f vault-values.yaml # Wait for the primary vault pod to complete init containers and be ready to accept commands kubectl wait pod --for=condition=PodReadyToStartContainers \ -n ${K8S_VAULT_NAMESPACE} vault-0 --timeout=5m # Wait for Vault service to be ready to accept connections echo "Waiting for Vault service to be ready..." for i in {1..30}; do if kubectl exec -n ${K8S_VAULT_NAMESPACE} vault-0 -- \ vault status 2>&1 | grep -qE "(Initialized|Sealed)"; then echo "✓ Vault service is ready" break fi if [ $i -eq 30 ]; then echo "Error: Timeout waiting for Vault service to be ready" exit 1 fi sleep 3 done init_output=$(kubectl exec -n ${K8S_VAULT_NAMESPACE} vault-0 -- \ vault operator init -key-shares=1 -key-threshold=1 -format=json || true) root_token="" if echo "${init_output}" | grep -q "Vault is already initialized"; then echo "Vault is already initialized" while [ -z "${root_token}" ]; do root_token=$(gum input --prompt="Vault root token: " --password --width=100) done else unseal_key=$(echo "${init_output}" | jq -r '.unseal_keys_b64[0]') root_token=$(echo "${init_output}" | jq -r '.root_token') kubectl exec -n ${K8S_VAULT_NAMESPACE} vault-0 -- \ vault operator unseal "${unseal_key}" echo "Vault initialized and unsealed successfully" echo "Root Token: ${root_token}" echo "Unseal Key: ${unseal_key}" echo "Please save these credentials securely!" fi # Wait for all vault instances to pass readiness checks and be ready to serve requests kubectl wait pod --for=condition=ready -n ${K8S_VAULT_NAMESPACE} \ -l app.kubernetes.io/name=vault --timeout=5m just setup-kubernetes-auth "${root_token}" just create-secrets-engine {{ SECRET_PATH }} "${root_token}" just create-admin-policy "${root_token}" echo "Installing External Secrets Operator is recommended to manage secrets in Kubernetes." echo "It can fetch secrets from Vault and sync them to Kubernetes Secret resources." if gum confirm "Install External Secrets Operator?"; then just external-secrets::install fi # Uninstall Vault uninstall delete-ns='false': #!/bin/bash set -euo pipefail helm uninstall vault -n ${K8S_VAULT_NAMESPACE} --ignore-not-found --wait just delete-namespace # Create admin token create-admin-token root_token='': check-env #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} # Create admin policy first just create-admin-policy "${VAULT_TOKEN}" # Create token with admin policy vault token create -policy=admin # Create token with specified policy and store in Vault create-token-and-store policy path ttl="24h" max_ttl="" root_token='': check-env #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} echo "Creating token with policy '{{ policy }}'..." # Create token with specified policy max_ttl_arg="" if [ -n "{{ max_ttl }}" ]; then max_ttl_arg="-explicit-max-ttl={{ max_ttl }}" fi token_output=$(vault token create -policy={{ policy }} -ttl={{ ttl }} ${max_ttl_arg} -format=json) service_token=$(echo "${token_output}" | jq -r '.auth.client_token') echo "Storing token in Vault at path '{{ path }}'..." # Store the token in Vault itself for later retrieval vault kv put -mount=secret {{ path }} token="${service_token}" echo "✓ Token created and stored in Vault" echo "Policy: {{ policy }}" echo "Path: secret/{{ path }}" echo "Token (first 20 chars): ${service_token:0:20}..." echo "" echo "To retrieve the token later:" echo " just vault::get {{ path }} token" # Create admin policy for Vault create-admin-policy root_token='': #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} vault policy write admin - </dev/null 2>&1 || \ vault auth enable kubernetes vault write auth/kubernetes/config \ token_reviewer_jwt="${SA_JWT}" \ kubernetes_host="https://kubernetes.default.svc" \ kubernetes_ca_cert="${SA_CA}" # Setup OIDC authentication with Keycloak setup-oidc-auth: #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} echo "Creating Keycloak client for Vault..." just keycloak::delete-client "${KEYCLOAK_REALM}" "vault" || true oidc_client_secret=$(just utils::random-password) redirect_urls="https://${VAULT_HOST}/ui/vault/auth/oidc/oidc/callback,http://localhost:8250/oidc/callback,http://localhost:8200/ui/vault/auth/oidc/oidc/callback" just keycloak::create-client realm="${KEYCLOAK_REALM}" client_id="vault" redirect_url="${redirect_urls}" client_secret="${oidc_client_secret}" echo "Using client secret: ${oidc_client_secret}" just keycloak::add-audience-mapper "vault" "vault" just keycloak::add-groups-mapper "vault" echo "✓ Keycloak client 'vault' created" echo "Configuring Vault OIDC authentication..." # Enable OIDC auth method vault auth list -format=json | jq -e '.["oidc/"]' >/dev/null 2>&1 || \ vault auth enable oidc # Configure OIDC with Keycloak vault write auth/oidc/config \ oidc_discovery_url="https://${KEYCLOAK_HOST}/realms/${KEYCLOAK_REALM}" \ oidc_client_id="vault" \ oidc_client_secret="${oidc_client_secret}" \ default_role="default" # Create default policy for secret access vault policy write default - </dev/null 2>&1 || \ vault auth enable -path=jwt jwt # Configure JWT to validate Keycloak tokens vault write auth/jwt/config \ jwks_url="https://${KEYCLOAK_HOST}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/certs" \ bound_issuer="https://${KEYCLOAK_HOST}/realms/${KEYCLOAK_REALM}" # Delete existing role if it exists vault delete auth/jwt/role/{{ role }} || true # Create role for the specified audience vault write auth/jwt/role/{{ role }} \ role_type="jwt" \ bound_audiences="{{ audience }},account" \ user_claim="preferred_username" \ token_policies="{{ policy }}" \ ttl="1h" \ max_ttl="48h" echo "✓ JWT authentication configured" echo " Audience: {{ audience }}" echo " Role: {{ role }}" echo " Policy: {{ policy }}" echo "" echo "Usage: client.auth.jwt.jwt_login(role='{{ role }}', jwt=token, path='jwt')" # Get key value get path field: #!/bin/bash set -euo pipefail {{ _vault_oidc_env_setup }} vault kv get -mount=secret -field={{ field }} {{ path }} # Get key value with root token get-root path field: #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} vault kv get -mount=secret -field={{ field }} {{ path }} # Put key value put path *args: #!/bin/bash set -euo pipefail {{ _vault_oidc_env_setup }} vault kv put -mount=secret {{ path }} {{ args }} # Put key value with root token put-root path *args: #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} vault kv put -mount=secret {{ path }} {{ args }} # Delete key value delete path: #!/bin/bash set -euo pipefail {{ _vault_oidc_env_setup }} vault kv delete -mount=secret {{ path }} # Delete key value with root token delete-root path: #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} vault kv delete -mount=secret {{ path }} # Check if key exists [no-exit-message] exist path: #!/bin/bash set -euo pipefail {{ _vault_oidc_env_setup }} vault kv get -mount=secret {{ path }} &>/dev/null # Check if key exists with root token [no-exit-message] exist-root path: #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} vault kv get -mount=secret {{ path }} &>/dev/null # Check the environment [private] check-env: #!/bin/bash set -euo pipefail if [ -z "${VAULT_HOST}" ]; then while [ -z "${VAULT_HOST}" ]; do VAULT_HOST=$( gum input --prompt="Vault host: " --width=100 --placeholder="vault.example.com" ) done just env::set VAULT_HOST="${VAULT_HOST}" fi # Setup vault environment with root token (for initial configuration) setup-root-token: #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} # Setup vault environment with OIDC token (for regular usage) setup-token: #!/bin/bash set -euo pipefail {{ _vault_oidc_env_setup }} # Print vault URL address vault-addr: #!/bin/bash set -euo pipefail if [ -z "${VAULT_HOST}" ]; then echo "Error: VAULT_HOST is not set." >&2 exit 1 fi echo "https://${VAULT_HOST}" # Write data to Vault at the given path write *args: #!/bin/bash set -euo pipefail {{ _vault_oidc_env_setup }} vault write {{ args }} # Write data to Vault at the given path with root token root-write *args: #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} vault write {{ args }} # Upload a policy to Vault write-policy name file: #!/bin/bash set -euo pipefail {{ _vault_root_env_setup }} vault policy write {{ name }} {{ file }} # Login to Vault using OIDC login: @vault login -method=oidc # NOTE: Vault monitoring is not supported # Reason: Prometheus ServiceMonitor does not support custom HTTP headers (X-Vault-Token) # Alternative: Use Vault Exporter or manual Prometheus scrape_configs