Feat rebrand armature (#43)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #43.
This commit is contained in:
237
scripts/armature-ca.sh
Executable file
237
scripts/armature-ca.sh
Executable file
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# armature-ca.sh — Certificate provisioning for Armature mTLS.
|
||||
#
|
||||
# Wraps openssl to generate a cluster CA, node certificates (ServerAuth +
|
||||
# ClientAuth), and user certificates (ClientAuth only). All output is PEM.
|
||||
#
|
||||
# Usage:
|
||||
# armature-ca init
|
||||
# armature-ca issue-node --name <node-name> --san <dns1,ip1,...>
|
||||
# armature-ca issue-user --cn <username> [--email <email>]
|
||||
#
|
||||
# Files are written to the current directory under ca/, nodes/, users/.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CA_DIR="./ca"
|
||||
NODES_DIR="./nodes"
|
||||
USERS_DIR="./users"
|
||||
CA_DAYS=3650 # 10 years for CA
|
||||
NODE_DAYS=365 # 1 year for nodes
|
||||
USER_DAYS=90 # 90 days for users
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
die() { echo "error: $*" >&2; exit 1; }
|
||||
|
||||
require_openssl() {
|
||||
command -v openssl >/dev/null 2>&1 || die "openssl not found in PATH"
|
||||
}
|
||||
|
||||
require_ca() {
|
||||
[ -f "$CA_DIR/cluster-ca.key" ] || die "CA not initialized. Run: $0 init"
|
||||
[ -f "$CA_DIR/cluster-ca.crt" ] || die "CA not initialized. Run: $0 init"
|
||||
}
|
||||
|
||||
# ── init ─────────────────────────────────────────────────────────────
|
||||
|
||||
cmd_init() {
|
||||
require_openssl
|
||||
mkdir -p "$CA_DIR"
|
||||
|
||||
if [ -f "$CA_DIR/cluster-ca.key" ]; then
|
||||
die "CA already exists at $CA_DIR/cluster-ca.key — remove it first to reinitialize"
|
||||
fi
|
||||
|
||||
# Generate ECDSA P-256 CA key
|
||||
openssl ecparam -genkey -name prime256v1 -noout -out "$CA_DIR/cluster-ca.key" 2>/dev/null
|
||||
|
||||
# Self-signed CA certificate
|
||||
openssl req -new -x509 \
|
||||
-key "$CA_DIR/cluster-ca.key" \
|
||||
-out "$CA_DIR/cluster-ca.crt" \
|
||||
-days "$CA_DAYS" \
|
||||
-subj "/CN=Armature Cluster CA" \
|
||||
-addext "basicConstraints=critical,CA:TRUE" \
|
||||
-addext "keyUsage=critical,keyCertSign,cRLSign" \
|
||||
2>/dev/null
|
||||
|
||||
echo "CA initialized:"
|
||||
echo " $CA_DIR/cluster-ca.crt"
|
||||
echo " $CA_DIR/cluster-ca.key"
|
||||
echo ""
|
||||
echo "Keep cluster-ca.key secure. It never needs to be on a running node."
|
||||
}
|
||||
|
||||
# ── issue-node ───────────────────────────────────────────────────────
|
||||
|
||||
cmd_issue_node() {
|
||||
require_openssl
|
||||
require_ca
|
||||
|
||||
local name=""
|
||||
local san=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) name="$2"; shift 2 ;;
|
||||
--san) san="$2"; shift 2 ;;
|
||||
*) die "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$name" ] || die "usage: $0 issue-node --name <node-name> --san <dns1,ip1,...>"
|
||||
[ -n "$san" ] || die "usage: $0 issue-node --name <node-name> --san <dns1,ip1,...>"
|
||||
|
||||
mkdir -p "$NODES_DIR"
|
||||
|
||||
# Generate node key
|
||||
openssl ecparam -genkey -name prime256v1 -noout -out "$NODES_DIR/$name.key" 2>/dev/null
|
||||
|
||||
# Build SAN entries from comma-separated list
|
||||
local san_entries=""
|
||||
IFS=',' read -ra ADDRS <<< "$san"
|
||||
for addr in "${ADDRS[@]}"; do
|
||||
addr=$(echo "$addr" | xargs) # trim whitespace
|
||||
if [[ "$addr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
san_entries="${san_entries}IP:${addr},"
|
||||
else
|
||||
san_entries="${san_entries}DNS:${addr},"
|
||||
fi
|
||||
done
|
||||
san_entries="${san_entries%,}" # remove trailing comma
|
||||
|
||||
# Create CSR
|
||||
openssl req -new \
|
||||
-key "$NODES_DIR/$name.key" \
|
||||
-out "$NODES_DIR/$name.csr" \
|
||||
-subj "/CN=$name" \
|
||||
2>/dev/null
|
||||
|
||||
# Create extensions config
|
||||
local ext_file
|
||||
ext_file=$(mktemp)
|
||||
cat > "$ext_file" <<EOF
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = critical, digitalSignature
|
||||
extendedKeyUsage = serverAuth, clientAuth
|
||||
subjectAltName = ${san_entries}
|
||||
EOF
|
||||
|
||||
# Sign with CA
|
||||
openssl x509 -req \
|
||||
-in "$NODES_DIR/$name.csr" \
|
||||
-CA "$CA_DIR/cluster-ca.crt" \
|
||||
-CAkey "$CA_DIR/cluster-ca.key" \
|
||||
-CAcreateserial \
|
||||
-out "$NODES_DIR/$name.crt" \
|
||||
-days "$NODE_DAYS" \
|
||||
-extfile "$ext_file" \
|
||||
2>/dev/null
|
||||
|
||||
rm -f "$NODES_DIR/$name.csr" "$ext_file"
|
||||
|
||||
echo "Node cert issued ($NODE_DAYS days):"
|
||||
echo " $NODES_DIR/$name.crt"
|
||||
echo " $NODES_DIR/$name.key"
|
||||
echo ""
|
||||
echo "Deploy to the node along with $CA_DIR/cluster-ca.crt"
|
||||
}
|
||||
|
||||
# ── issue-user ───────────────────────────────────────────────────────
|
||||
|
||||
cmd_issue_user() {
|
||||
require_openssl
|
||||
require_ca
|
||||
|
||||
local cn=""
|
||||
local email=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--cn) cn="$2"; shift 2 ;;
|
||||
--email) email="$2"; shift 2 ;;
|
||||
*) die "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$cn" ] || die "usage: $0 issue-user --cn <username> [--email <email>]"
|
||||
|
||||
mkdir -p "$USERS_DIR"
|
||||
|
||||
# Generate user key
|
||||
openssl ecparam -genkey -name prime256v1 -noout -out "$USERS_DIR/$cn.key" 2>/dev/null
|
||||
|
||||
# Build subject
|
||||
local subj="/CN=$cn"
|
||||
local san_line=""
|
||||
if [ -n "$email" ]; then
|
||||
san_line="email:${email}"
|
||||
fi
|
||||
|
||||
# Create CSR
|
||||
openssl req -new \
|
||||
-key "$USERS_DIR/$cn.key" \
|
||||
-out "$USERS_DIR/$cn.csr" \
|
||||
-subj "$subj" \
|
||||
2>/dev/null
|
||||
|
||||
# Create extensions config
|
||||
local ext_file
|
||||
ext_file=$(mktemp)
|
||||
cat > "$ext_file" <<EOF
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = critical, digitalSignature
|
||||
extendedKeyUsage = clientAuth
|
||||
EOF
|
||||
|
||||
if [ -n "$san_line" ]; then
|
||||
echo "subjectAltName = ${san_line}" >> "$ext_file"
|
||||
fi
|
||||
|
||||
# Sign with CA
|
||||
openssl x509 -req \
|
||||
-in "$USERS_DIR/$cn.csr" \
|
||||
-CA "$CA_DIR/cluster-ca.crt" \
|
||||
-CAkey "$CA_DIR/cluster-ca.key" \
|
||||
-CAcreateserial \
|
||||
-out "$USERS_DIR/$cn.crt" \
|
||||
-days "$USER_DAYS" \
|
||||
-extfile "$ext_file" \
|
||||
2>/dev/null
|
||||
|
||||
rm -f "$USERS_DIR/$cn.csr" "$ext_file"
|
||||
|
||||
echo "User cert issued ($USER_DAYS days):"
|
||||
echo " $USERS_DIR/$cn.crt"
|
||||
echo " $USERS_DIR/$cn.key"
|
||||
echo ""
|
||||
echo "Import both files into the user's browser or CLI tool."
|
||||
}
|
||||
|
||||
# ── Main dispatch ────────────────────────────────────────────────────
|
||||
|
||||
case "${1:-}" in
|
||||
init)
|
||||
shift
|
||||
cmd_init "$@"
|
||||
;;
|
||||
issue-node)
|
||||
shift
|
||||
cmd_issue_node "$@"
|
||||
;;
|
||||
issue-user)
|
||||
shift
|
||||
cmd_issue_user "$@"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 <command>"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " init Initialize cluster CA"
|
||||
echo " issue-node --name <n> --san <addrs> Issue node cert (server+client auth)"
|
||||
echo " issue-user --cn <name> [--email <e>] Issue user cert (client auth only)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user