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

200 lines
8 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# vault-tls-check
# ----------------------------------------------------------------------
# Features
# - Live TLS (and mTLS) handshake check against Vault (or any TLS server)
# - SNI support
# - CA file inspection (--show) to print Subject/Issuer/Validity chain
# - Smart defaults from current users HOME (override with flags or env)
# - Helpful hints & non-zero exit when verification fails
#
# Defaults (overridable by flags or env):
# CA: ${VAULTTLS_CAFILE:-$HOME/vault/ca/ca.pem}
# mTLS cert: ${VAULTTLS_CERT:-$HOME/vault/mtls/agent.crt}
# mTLS key: ${VAULTTLS_KEY:-$HOME/vault/mtls/agent.key}
#
# Examples (one-liners):
# - Live verify via Public IP with SNI using current user's defaults:
# vault-tls-check --addr 109.199.99.183:22300 --sni vault.test.privsec.ch
# - Live verify via loopback (server cert has 127.0.0.1 SAN):
# vault-tls-check --addr 127.0.0.1:22300 --sni vault.test.privsec.ch
# - Show whats inside your CA file (no network):
# vault-tls-check --show
# - Live verify with explicit paths (override defaults):
# vault-tls-check --addr 109.199.99.183:22300 --sni vault.test.privsec.ch \
# --cafile /some/ca/chain.pem --mtls-cert /path/agent.crt --mtls-key /path/agent.key
#
# Exit codes: 0 OK, 1 verify/connect error, 2 usage error
# ----------------------------------------------------------------------
set -Eeuo pipefail
# ---------- styling ----------
if [[ -t 1 ]]; then
B=$'\e[1m'; R=$'\e[0m'; BL=$'\e[34m'; G=$'\e[32m'; Y=$'\e[33m'; E=$'\e[31m'
else B= R= BL= G= Y= E=; fi
ts(){ date +"%Y-%m-%d %H:%M:%S"; }
info(){ echo -e "[$(ts)] ${BL}${B}INFO${R} $*"; }
ok(){ echo -e "[$(ts)] ${G}${B}OK${R} $*"; }
warn(){ echo -e "[$(ts)] ${Y}${B}WARN${R} $*"; }
err(){ echo -e "[$(ts)] ${E}${B}ERR${R} $*" >&2; }
usage(){
sed -n '1,120p' "$0" | sed -n '1,80p'
cat <<USAGE
Usage:
vault-tls-check --addr host:port [--sni name] [--cafile file] [--mtls-cert file] [--mtls-key file] [--timeout N]
vault-tls-check --show [--cafile file]
Options:
--addr host:port Target address (required for live handshake)
--sni name SNI / servername to verify CN/SAN properly
--cafile file CA bundle / chain (defaults to \$HOME/vault/ca/ca.pem)
--mtls-cert file Client cert for mTLS (defaults to \$HOME/vault/mtls/agent.crt if readable)
--mtls-key file Client key for mTLS (defaults to \$HOME/vault/mtls/agent.key if readable)
--timeout N s_client -connect timeout (default: 5 seconds)
--show Only inspect the CA file (no network)
-h|--help This help
Env overrides (same meaning as flags):
VAULTTLS_CAFILE, VAULTTLS_CERT, VAULTTLS_KEY
Examples:
vault-tls-check --addr 109.199.99.183:22300 --sni vault.test.privsec.ch
vault-tls-check --addr 127.0.0.1:22300 --sni vault.test.privsec.ch
vault-tls-check --show
vault-tls-check --addr 109.199.99.183:22300 --sni vault.test.privsec.ch --cafile /home/vault/tls-test/ca_chain.pem
USAGE
}
need(){ command -v "$1" >/dev/null 2>&1 || { err "missing: $1"; exit 2; }; }
need openssl
need awk
need sed
ADDR=""
SNI=""
SHOW=0
TIMEOUT=5
# Defaults from env → HOME
CAF_DEFAULT="${VAULTTLS_CAFILE:-${HOME}/vault/ca/ca.pem}"
CRT_DEFAULT="${VAULTTLS_CERT:-${HOME}/vault/mtls/agent.crt}"
KEY_DEFAULT="${VAULTTLS_KEY:-${HOME}/vault/mtls/agent.key}"
CAF="$CAF_DEFAULT"
MTLS_CERT=""
MTLS_KEY=""
# ---------- parse args ----------
while [[ $# -gt 0 ]]; do
case "$1" in
--addr) ADDR="$2"; shift 2;;
--sni) SNI="$2"; shift 2;;
--cafile) CAF="$2"; shift 2;;
--mtls-cert) MTLS_CERT="$2"; shift 2;;
--mtls-key) MTLS_KEY="$2"; shift 2;;
--timeout) TIMEOUT="$2"; shift 2;;
--show) SHOW=1; shift;;
-h|--help) usage; exit 0;;
*) err "Unknown option: $1"; usage; exit 2;;
esac
done
# ---------- resolve defaults for mTLS if not provided ----------
# Only set these if user didnt pass flags and files exist/readable
if [[ -z "${MTLS_CERT}" && -r "${CRT_DEFAULT}" ]]; then MTLS_CERT="${CRT_DEFAULT}"; fi
if [[ -z "${MTLS_KEY}" && -r "${KEY_DEFAULT}" ]]; then MTLS_KEY="${KEY_DEFAULT}"; fi
# ---------- CA inspection only ----------
if (( SHOW == 1 )); then
if [[ -z "${CAF}" ]]; then CAF="${CAF_DEFAULT}"; fi
[[ -r "${CAF}" ]] || { err "CA file not readable: ${CAF}"; exit 1; }
info "Inspecting CA file: ${CAF}"
# Try to print multiple certs if a bundle
# shellcheck disable=SC2005
echo "$(openssl crl2pkcs7 -nocrl -certfile "${CAF}" 2>/dev/null \
| openssl pkcs7 -print_certs -noout -text 2>/dev/null \
| sed -n 's/^ *Subject: */Subject: /p; s/^ *Issuer: */Issuer: /p; s/^ *Not Before: */NotBefore: /p; s/^ *Not After : */NotAfter: /p')" \
|| warn "Could not parse bundle (is it a single PEM?). Falling back to x509 view…"
if ! openssl x509 -in "${CAF}" -noout -subject -issuer -dates >/dev/null 2>&1; then
exit 0
fi
openssl x509 -in "${CAF}" -noout -subject -issuer -dates
exit 0
fi
# ---------- live verify mode ----------
[[ -n "${ADDR}" ]] || { err "--addr is required (or use --show)"; usage; exit 2; }
[[ -r "${CAF}" ]] || { err "CA file not readable: ${CAF}"; exit 1; }
info "Live verify -> ${ADDR} SNI: ${SNI:-<none>}"
# Build s_client arguments
SC_ARGS=( -connect "${ADDR}" -CAfile "${CAF}" -verify_return_error -servername "${SNI:-}" -showcerts -brief -timeout "${TIMEOUT}" )
# Remove empty -servername if not set
if [[ -z "${SNI}" ]]; then SC_ARGS=( -connect "${ADDR}" -CAfile "${CAF}" -verify_return_error -showcerts -brief -timeout "${TIMEOUT}" ); fi
# Add mTLS if provided (or discovered)
if [[ -n "${MTLS_CERT}" || -n "${MTLS_KEY}" ]]; then
if [[ ! -r "${MTLS_CERT}" ]]; then err "mtls cert not readable: ${MTLS_CERT:-<unset>}"; exit 1; fi
if [[ ! -r "${MTLS_KEY}" ]]; then err "mtls key not readable: ${MTLS_KEY:-<unset>}"; exit 1; fi
SC_ARGS+=( -cert "${MTLS_CERT}" -key "${MTLS_KEY}" )
fi
TMPDIR="$(mktemp -d)"
trap 'rm -rf "${TMPDIR}"' EXIT
LEAF="${TMPDIR}/leaf.pem"
LOGF="${TMPDIR}/sclient.out"
# We feed empty stdin so s_client exits after handshake
if ! printf '' | openssl s_client "${SC_ARGS[@]}" 2>&1 | tee "${LOGF}" >/dev/null; then
err "openssl s_client failed to connect/verify"
# Print a concise hint if mTLS likely required
if grep -qi 'certificate required' "${LOGF}"; then
warn "mTLS required by server; provide --mtls-cert/--mtls-key (or place them in \$HOME/vault/mtls/)."
fi
exit 1
fi
# Extract first PEM cert from output (the leaf)
awk '/-----BEGIN CERTIFICATE-----/{f=1} f{print} /-----END CERTIFICATE-----/{exit}' "${LOGF}" > "${LEAF}" || true
if [[ -s "${LEAF}" ]]; then
echo "----- Leaf summary -----"
openssl x509 -in "${LEAF}" -noout -subject -issuer -dates -ext subjectAltName 2>/dev/null || \
openssl x509 -in "${LEAF}" -noout -subject -issuer -dates 2>/dev/null || true
echo "------------------------"
fi
# Determine verification outcome
# Prefer "Verification: OK" (OpenSSL >= 3 brief), else parse "Verify return code"
if grep -q 'Verification: OK' "${LOGF}"; then
ok "TLS verification OK"
exit 0
fi
VERIFY_CODE="$(grep -Eo 'Verify return code: [0-9]+' "${LOGF}" | awk '{print $4}' | tail -n1 || true)"
if [[ -n "${VERIFY_CODE}" && "${VERIFY_CODE}" != "0" ]]; then
err "TLS verification FAILED (verify code: ${VERIFY_CODE})"
# Hints
if grep -qi 'unable to get issuer certificate' "${LOGF}"; then
warn "Check CA chain (--cafile). Your CA must contain the issuer(s) of the server cert."
fi
if grep -qi 'certificate is valid for 127.0.0.1' "${LOGF}"; then
warn "Add --sni vault.test.privsec.ch (or include public IP in the certs SANs)."
fi
if grep -qi 'certificate required' "${LOGF}"; then
warn "mTLS required by server; supply --mtls-cert/--mtls-key (defaults from \$HOME/vault/mtls/ are auto-used if readable)."
end
exit 1
fi
# Fallback: if neither pattern found, assume success when no obvious errors present
if grep -qiE 'verify error|handshake failure|certificate required|unable to get local issuer|unable to verify' "${LOGF}"; then
err "TLS verification FAILED (see output above)"
exit 1
fi
ok "TLS verification OK"
exit 0