453 lines
15 KiB
Makefile
453 lines
15 KiB
Makefile
set fallback := true
|
|
|
|
export K8S_VAULT_NAMESPACE := env("K8S_VAULT_NAMESPACE", "vault")
|
|
export VAULT_CHART_VERSION := env("VAULT_CHART_VERSION", "0.29.1")
|
|
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 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 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 -euo pipefail
|
|
just create-namespace
|
|
just add-helm-repo
|
|
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
|
|
|
|
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 - <<EOF
|
|
path "sys/auth" {
|
|
capabilities = ["read", "list", "sudo"]
|
|
}
|
|
path "sys/auth/*" {
|
|
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
|
}
|
|
path "secret/*" {
|
|
capabilities = ["create", "read", "update", "delete", "list"]
|
|
}
|
|
path "auth/*" {
|
|
capabilities = ["create", "read", "update", "delete", "list"]
|
|
}
|
|
path "sys/policy/*" {
|
|
capabilities = ["create", "read", "update", "delete", "list"]
|
|
}
|
|
path "sys/policies/acl/*" {
|
|
capabilities = ["create", "read", "update", "delete", "list"]
|
|
}
|
|
path "auth/token/create" {
|
|
capabilities = ["create", "update"]
|
|
}
|
|
path "auth/token/create/*" {
|
|
capabilities = ["create", "update"]
|
|
}
|
|
EOF
|
|
echo "Admin policy created successfully"
|
|
|
|
# Create secrets engine
|
|
create-secrets-engine path root_token='':
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
export VAULT_TOKEN="{{ root_token }}"
|
|
while [ -z "${VAULT_TOKEN}" ]; do
|
|
VAULT_TOKEN=$(gum input --prompt="Vault root token: " --password --width=100)
|
|
done
|
|
vault secrets enable -path="{{ path }}" kv-v2
|
|
|
|
# Setup Kubernetes authentication
|
|
setup-kubernetes-auth root_token='':
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
export VAULT_TOKEN="{{ root_token }}"
|
|
while [ -z "${VAULT_TOKEN}" ]; do
|
|
VAULT_TOKEN=$(gum input --prompt="Vault root token: " --password --width=100)
|
|
done
|
|
|
|
gomplate -f ./serviceaccount.gomplate.yaml | kubectl apply -n "${K8S_VAULT_NAMESPACE}" -f -
|
|
gomplate -f ./rolebinding.gomplate.yaml | kubectl apply -n "${K8S_VAULT_NAMESPACE}" -f -
|
|
kubectl apply -n "${K8S_VAULT_NAMESPACE}" -f ./auth-token-secret.yaml
|
|
|
|
SA_SECRET="vault-auth-token"
|
|
SA_JWT=$(kubectl get secret -n ${K8S_VAULT_NAMESPACE} ${SA_SECRET} -o jsonpath='{.data.token}' | \
|
|
base64 --decode)
|
|
SA_CA=$(kubectl get secret -n ${K8S_VAULT_NAMESPACE} ${SA_SECRET} -o jsonpath='{.data.ca\.crt}' | \
|
|
base64 --decode)
|
|
|
|
vault auth list -format=json | jq -e '.["kubernetes/"]' >/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 "${KEYCLOAK_REALM}" "vault" "${redirect_urls}" "${oidc_client_secret}"
|
|
echo "Using client secret: ${oidc_client_secret}"
|
|
just keycloak::add-audience-mapper "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 - <<EOF
|
|
path "secret/data/*" {
|
|
capabilities = ["create", "read", "update", "delete", "list"]
|
|
}
|
|
path "secret/metadata/*" {
|
|
capabilities = ["list"]
|
|
}
|
|
EOF
|
|
# Create default role for all authenticated users
|
|
vault write auth/oidc/role/default \
|
|
bound_audiences="vault" \
|
|
allowed_redirect_uris="https://${VAULT_HOST}/ui/vault/auth/oidc/oidc/callback" \
|
|
allowed_redirect_uris="http://localhost:8250/oidc/callback" \
|
|
allowed_redirect_uris="http://localhost:8200/ui/vault/auth/oidc/oidc/callback" \
|
|
user_claim="preferred_username" \
|
|
groups_claim="groups" \
|
|
token_policies="default"
|
|
# Create admin role for vault-admins group
|
|
vault write auth/oidc/role/admin \
|
|
bound_audiences="vault" \
|
|
allowed_redirect_uris="https://${VAULT_HOST}/ui/vault/auth/oidc/oidc/callback" \
|
|
allowed_redirect_uris="http://localhost:8250/oidc/callback" \
|
|
allowed_redirect_uris="http://localhost:8200/ui/vault/auth/oidc/oidc/callback" \
|
|
bound_claims/groups="vault-admins" \
|
|
user_claim="preferred_username" \
|
|
groups_claim="groups" \
|
|
token_policies="admin"
|
|
echo "✓ Vault OIDC authentication configured"
|
|
|
|
echo ""
|
|
echo "=== OIDC Setup Complete ==="
|
|
echo "You can now login to Vault using:"
|
|
echo " VAULT_ADDR=${VAULT_ADDR} vault login -method=oidc"
|
|
|
|
# Disable OIDC authentication
|
|
disable-oidc-auth:
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
{{ _vault_root_env_setup }}
|
|
vault auth disable oidc
|
|
|
|
# Setup JWT authentication for Keycloak tokens (not used currently)
|
|
setup-jwt-auth audience role policy='default':
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
{{ _vault_root_env_setup }}
|
|
|
|
echo "Setting up JWT authentication for audience: {{ audience }}"
|
|
|
|
# Enable JWT auth if not already enabled
|
|
vault auth list -format=json | jq -e '.["jwt/"]' >/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 }}
|