vault-ops/infra/versions/02_intermediate_in_vault_sign_with_root-v1.0.sh
2026-04-14 11:45:15 +07:00

123 lines
4.5 KiB
Bash
Executable file

#!/usr/bin/env bash
set -Eeuo pipefail
# 02_intermediate_in_vault_sign_with_root.sh
# ===== Pretty logging =====
if [[ -t 1 ]]; then
B=$'\e[1m'; R=$'\e[0m'; G=$'\e[32m'; Y=$'\e[33m'; E=$'\e[31m'
else B=""; R=""; G=""; Y=""; E=""; fi
ok(){ echo -e "🟩 ${G}${B}$*${R}"; }
warn(){ echo -e "🟨 ${Y}${B}$*${R}"; }
die(){ echo -e "🟥 ${E}${B}$*${R}" >&2; exit 1; }
# ===== Defaults / Args =====
CFG="${CFG:-./config/apps.yaml}"
ENV_NAME="test"
API_ADDR="http://127.0.0.1:22300" # switch to https after step 04
INT_TTL="43800h" # 5 years
INT_CN="PrivSec Intermediate CA"
while [[ $# -gt 0 ]]; do
case "$1" in
--config) CFG="$2"; shift 2;;
--env) ENV_NAME="$2"; shift 2;;
--api) API_ADDR="$2"; shift 2;;
-h|--help) sed -n '1,200p' "$0"; exit 0;;
*) die "Unknown arg: $1";;
esac
done
# ===== Tooling =====
need(){ command -v "$1" >/dev/null || die "missing: $1"; }
need vault; need jq; need python3; need openssl
# ===== Auth =====
if [[ -z "${VAULT_TOKEN:-}" && -n "${VAULT_ADMIN_TOKEN:-}" ]]; then
export VAULT_TOKEN="$VAULT_ADMIN_TOKEN"
fi
: "${VAULT_TOKEN:?Set VAULT_TOKEN or VAULT_ADMIN_TOKEN}"
# ===== Load config YAML -> JSON =====
CFG_ABS="$(readlink -f "$CFG")" || die "cannot resolve $CFG"
CFGJSON="$(python3 - "$CFG_ABS" <<'PY'
import yaml, json, sys
p = sys.argv[1]
with open(p, "r", encoding="utf-8") as f:
print(json.dumps(yaml.safe_load(f)))
PY
)" || die "failed to parse YAML"
jqenv(){ echo "$CFGJSON" | jq -r ".environments.\"$ENV_NAME\"$1"; }
VAULT_ADDR="$(jqenv '.vault_addr')"
INT_MOUNT="$(jqenv '.pki_mount')"
[[ "$VAULT_ADDR" != "null" && "$INT_MOUNT" != "null" ]] || die "incomplete config: vault_addr/pki_mount"
export VAULT_ADDR
# ===== Canonical OFFLINE ROOT paths =====
: "${ENV_NAME:?use --env test|prod}"
ME_HOME="$(cd ~ && pwd)"
ROOT_DIR="${ME_HOME}/vault/offline-root/${ENV_NAME}"
ROOT_KEY="${ROOT_DIR}/root-ca.key"
ROOT_CRT="${ROOT_DIR}/root-ca.pem"
[[ -r "$ROOT_KEY" ]] || die "Root KEY not found: $ROOT_KEY"
[[ -r "$ROOT_CRT" ]] || die "Root PEM not found: $ROOT_CRT"
chmod 0400 "$ROOT_KEY" 2>/dev/null || true
ok "Using Vault @ ${VAULT_ADDR} (mount: ${INT_MOUNT})"
ok "Using OFFLINE ROOT @ ${ROOT_DIR}"
# ===== Enable/tune Intermediate PKI mount =====
vault secrets enable -path="${INT_MOUNT}" pki >/dev/null 2>&1 || true
vault secrets tune -max-lease-ttl="${INT_TTL}" "${INT_MOUNT}" >/dev/null
# ===== Generate CSR inside Vault =====
CSR="$(vault write -format=json "${INT_MOUNT}/intermediate/generate/internal" \
common_name="${INT_CN} (${ENV_NAME})" ttl="${INT_TTL}" \
| jq -r .data.csr)"
[[ -n "$CSR" && "$CSR" != "null" ]] || die "failed to generate intermediate CSR in Vault"
# ===== Sign CSR with OFFLINE ROOT (v3_intermediate_ca) =====
OPENSSL_INT_CONF="$(mktemp)"
cat > "$OPENSSL_INT_CONF" <<'CNF'
[ v3_intermediate_ca ]
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,keyCertSign,cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
CNF
SERIAL_FILE="${ROOT_DIR}/root-ca.srl"
if [[ -n "${ROOT_CA_PASSPHRASE:-}" ]]; then
SIGNED="$(openssl x509 -req \
-in <(printf '%s\n' "$CSR") \
-CA "$ROOT_CRT" -CAkey "$ROOT_KEY" -passin env:ROOT_CA_PASSPHRASE \
-CAcreateserial -CAserial "$SERIAL_FILE" \
-days 1825 -sha256 -extfile "$OPENSSL_INT_CONF" -extensions v3_intermediate_ca)"
else
SIGNED="$(openssl x509 -req \
-in <(printf '%s\n' "$CSR") \
-CA "$ROOT_CRT" -CAkey "$ROOT_KEY" \
-CAcreateserial -CAserial "$SERIAL_FILE" \
-days 1825 -sha256 -extfile "$OPENSSL_INT_CONF" -extensions v3_intermediate_ca)"
fi
rm -f "$OPENSSL_INT_CONF"
[[ -n "$SIGNED" ]] || die "OpenSSL did not produce a signed intermediate"
# ===== Upload signed Intermediate to Vault =====
printf '%s\n' "$SIGNED" | vault write "${INT_MOUNT}/intermediate/set-signed" certificate=- >/dev/null
# ===== Configure PKI URLs (HTTP now; update to HTTPS after step 04) =====
vault write "${INT_MOUNT}/config/urls" \
issuing_certificates="${API_ADDR}/v1/${INT_MOUNT}/ca" \
crl_distribution_points="${API_ADDR}/v1/${INT_MOUNT}/crl" >/dev/null
# ===== Copy public root CA cert to Vault host (public only) =====
TLSDIR="/home/vault/tls-${ENV_NAME}"
sudo install -d -m 0755 -o vault -g vault "${TLSDIR}"
sudo install -m 0644 "$ROOT_CRT" "${TLSDIR}/root_ca.pem"
ok "Intermediate ready at mount ${INT_MOUNT}."
ok "Public root copied to ${TLSDIR}/root_ca.pem (private key remains OFFLINE at ${ROOT_DIR}/root-ca.key)."
ok "After enabling Vault HTTPS, re-run URL config with --api https://… to switch issuing/CRL URLs."