#!/usr/bin/env bash set -euo pipefail # ========================================== # Root CA bootstrap + install script # For Linux/macOS/WSL/Git Bash # ========================================== # Defaults can be overridden with env vars or CLI args CA_URL="${CA_URL:-https://ca.insmw.internal}" CA_FINGERPRINT="${CA_FINGERPRINT:-}" STEP_VERSION="${STEP_VERSION:-0.28.7}" STEP_BIN_DIR="${STEP_BIN_DIR:-/usr/local/bin}" STEP_CONFIG_DIR="${STEP_CONFIG_DIR:-$HOME/.step}" FORCE="${FORCE:-0}" usage() { cat < --fingerprint [--force] Examples: curl -fsSL https://your-opengist/raw/install-ca.sh | bash -s -- \\ --ca-url https://ca.insmw.internal \\ --fingerprint abcdef1234567890... Environment variables: CA_URL CA_FINGERPRINT STEP_VERSION STEP_BIN_DIR STEP_CONFIG_DIR FORCE=1 EOF } log() { printf '\n[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" } fail() { echo "ERROR: $*" >&2 exit 1 } need_cmd() { command -v "$1" >/dev/null 2>&1 } download_file() { local url="$1" local out="$2" if need_cmd curl; then curl -fsSL "$url" -o "$out" elif need_cmd wget; then wget -qO "$out" "$url" else fail "Neither curl nor wget is installed" fi } detect_os() { local uname_s uname_s="$(uname -s 2>/dev/null || true)" case "$uname_s" in Linux*) echo "linux" ;; Darwin*) echo "darwin" ;; MINGW*|MSYS*|CYGWIN*) echo "windows_bash" ;; *) echo "unknown" ;; esac } detect_arch() { local uname_m uname_m="$(uname -m 2>/dev/null || true)" case "$uname_m" in x86_64|amd64) echo "amd64" ;; aarch64|arm64) echo "arm64" ;; armv7l) echo "armv7" ;; *) echo "$uname_m" ;; esac } install_step_linux() { local arch="$1" local tmpdir pkg_ext pkg_name url tmpdir="$(mktemp -d)" trap 'rm -rf "$tmpdir"' RETURN if need_cmd apk; then pkg_ext="apk" case "$arch" in amd64) pkg_name="step-cli_${STEP_VERSION}_amd64.apk" ;; arm64) pkg_name="step-cli_${STEP_VERSION}_arm64.apk" ;; *) fail "Unsupported architecture for Alpine: $arch" ;; esac url="https://github.com/smallstep/cli/releases/download/v${STEP_VERSION}/${pkg_name}" log "Downloading step-cli from $url" download_file "$url" "$tmpdir/$pkg_name" sudo apk add --allow-untrusted "$tmpdir/$pkg_name" return fi if need_cmd dpkg; then pkg_ext="deb" case "$arch" in amd64) pkg_name="step-cli_${STEP_VERSION}_amd64.deb" ;; arm64) pkg_name="step-cli_${STEP_VERSION}_arm64.deb" ;; *) fail "Unsupported architecture for Debian/Ubuntu: $arch" ;; esac url="https://github.com/smallstep/cli/releases/download/v${STEP_VERSION}/${pkg_name}" log "Downloading step-cli from $url" download_file "$url" "$tmpdir/$pkg_name" sudo dpkg -i "$tmpdir/$pkg_name" || sudo apt-get update && sudo apt-get install -f -y return fi fail "Unsupported Linux distribution. Supported: Debian/Ubuntu, Alpine" } install_step_darwin() { if need_cmd brew; then log "Installing step via Homebrew" brew install step return fi fail "Homebrew is required on macOS to install step automatically" } ensure_step() { local os arch if need_cmd step; then log "step CLI already installed" return fi os="$(detect_os)" arch="$(detect_arch)" log "step CLI not found, installing for $os/$arch" case "$os" in linux) install_step_linux "$arch" ;; darwin) install_step_darwin ;; windows_bash) fail "For native Windows, use the PowerShell installer instead of the bash script" ;; *) fail "Unsupported OS: $os" ;; esac need_cmd step || fail "step CLI installation failed" } bootstrap_step() { [ -n "$CA_FINGERPRINT" ] || fail "CA fingerprint is required" if [ "$FORCE" = "1" ]; then log "Removing previous step configuration because FORCE=1" rm -rf "$STEP_CONFIG_DIR" fi mkdir -p "$STEP_CONFIG_DIR" log "Bootstrapping step against $CA_URL" step ca bootstrap \ --ca-url "$CA_URL" \ --fingerprint "$CA_FINGERPRINT" \ --install \ --force } install_linux_trust() { local root_cert root_cert="$STEP_CONFIG_DIR/certs/root_ca.crt" [ -f "$root_cert" ] || fail "Root certificate not found at $root_cert" if need_cmd update-ca-certificates; then log "Installing root CA into system trust store using update-ca-certificates" sudo mkdir -p /usr/local/share/ca-certificates sudo cp "$root_cert" /usr/local/share/ca-certificates/insmw-root-ca.crt sudo update-ca-certificates return fi if need_cmd trust; then log "Installing root CA into system trust store using p11-kit trust" sudo trust anchor "$root_cert" return fi if [ -d /etc/ssl/certs ]; then log "Copying certificate to /etc/ssl/certs as fallback" sudo cp "$root_cert" /etc/ssl/certs/insmw-root-ca.crt return fi fail "Could not determine how to install the CA into the Linux trust store" } install_macos_trust() { local root_cert root_cert="$STEP_CONFIG_DIR/certs/root_ca.crt" [ -f "$root_cert" ] || fail "Root certificate not found at $root_cert" log "Installing root CA into macOS System keychain" sudo security add-trusted-cert \ -d \ -r trustRoot \ -k /Library/Keychains/System.keychain \ "$root_cert" } install_trust_store() { local os os="$(detect_os)" case "$os" in linux) install_linux_trust ;; darwin) install_macos_trust ;; *) fail "Unsupported OS for trust installation: $os" ;; esac } verify_install() { local root_cert root_cert="$STEP_CONFIG_DIR/certs/root_ca.crt" log "Installed root CA at: $root_cert" log "Certificate subject:" step certificate inspect "$root_cert" --short || true echo echo "Done." echo "You may need to restart applications that cache trust settings, such as browsers or Docker." } parse_args() { while [ $# -gt 0 ]; do case "$1" in --ca-url) CA_URL="$2" shift 2 ;; --fingerprint) CA_FINGERPRINT="$2" shift 2 ;; --force) FORCE=1 shift ;; -h|--help) usage exit 0 ;; *) fail "Unknown argument: $1" ;; esac done } main() { parse_args "$@" [ -n "$CA_URL" ] || fail "CA URL is required" [ -n "$CA_FINGERPRINT" ] || fail "CA fingerprint is required" ensure_step bootstrap_step install_trust_store verify_install } main "$@"