123 lines
4.5 KiB
Bash
Executable file
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."
|
|
|