diff --git a/etc/init_authority.sh b/etc/init_authority.sh new file mode 100755 index 0000000..1d2c462 --- /dev/null +++ b/etc/init_authority.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# init_authority.sh +# Purpose: +# - Ensure 'authority' namespace exists +# - Spin up 'dog' container (Ubuntu 24) as primary KDC +# - Configure KDC with root password and SSH keys from OpenBao +# - Configure Samba AD authority and administrator account +# - Save autogenerated administrator password to OpenBao + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +# Load environment and config via prole_cfg.sh +# shellcheck disable=SC1090 +source "$SCRIPT_DIR/prole_cfg.sh" + +ACTION=${1:-start} +NAMESPACE_AUTH="authority" +KDC_NAME="dog" +KDC_IMAGE="ubuntu:24.04" + +log() { printf '%s\n' "$*"; } +err() { printf '%s\n' "$*" >&2; } + +ensure_tools() { + for t in kubectl curl jq base64; do + command -v "$t" >/dev/null || { err "Missing required tool: $t"; exit 1; } + done +} + +ensure_namespace() { + if ! kubectl get namespace "$NAMESPACE_AUTH" >/dev/null 2>&1; then + log "Creating namespace '$NAMESPACE_AUTH' ..." + kubectl create namespace "$NAMESPACE_AUTH" + fi +} + +get_openbao_url() { + if [[ -n "${PROLE_OPENBAO_URL:-}" ]]; then + echo "$PROLE_OPENBAO_URL" + elif curl -sS "http://127.0.0.1:18200/v1/sys/health" >/dev/null 2>&1; then + echo "http://127.0.0.1:18200" + else + echo "http://openbao.${NAMESPACE}.svc.cluster.local:8200" + fi +} + +get_openbao_token() { + if [[ -f "$PROLE_SERVICE/secrets/openbao-root-token" ]]; then + cat "$PROLE_SERVICE/secrets/openbao-root-token" + else + echo "${OPENBAO_ROOT_TOKEN:-root}" + fi +} + +read_from_bao() { + local path="$1" + local key="$2" + local url + url=$(get_openbao_url) + local token + token=$(get_openbao_token) + + curl -sS -H "X-Vault-Token: $token" "$url/v1/kv/data/$path" | jq -r ".data.data.\"$key\"" || echo "" +} + +write_to_bao() { + local path="$1" + local data="$2" + local url + url=$(get_openbao_url) + local token + token=$(get_openbao_token) + + curl -sS -H "X-Vault-Token: $token" -H 'Content-Type: application/json' \ + -X POST "$url/v1/kv/data/$path" -d "{\"data\":$data}" >/dev/null +} + +deploy_dog() { + log "Deploying '$KDC_NAME' (Ubuntu 24.04) in namespace '$NAMESPACE_AUTH' ..." + + # Fetch secrets from OpenBao + local root_pass + root_pass=$(read_from_bao "prole/admin" "root_password") + if [[ -z "$root_pass" || "$root_pass" == "null" ]]; then + root_pass=$(openssl rand -hex 16) + log "Generated new root password for dog server." + # We need to preserve existing data in prole/admin if we write to it + local admin_data + admin_data=$(get_openbao_url | xargs -I {} curl -sS -H "X-Vault-Token: $(get_openbao_token)" "{}/v1/kv/data/prole/admin" | jq -r ".data.data") + if [[ "$admin_data" == "null" ]]; then + admin_data="{}" + fi + local new_admin_data + new_admin_data=$(echo "$admin_data" | jq -c ". + {\"root_password\":\"$root_pass\"}") + write_to_bao "prole/admin" "$new_admin_data" + fi + + local ssh_pub + ssh_pub=$(read_from_bao "prole/admin" "admin_public_key_b64" | base64 -d 2>/dev/null || true) + + local ansible_ssh_pub + # Try to read ansible key from OpenBao, fallback to local file if it exists + ansible_ssh_pub=$(read_from_bao "prole/admin" "ansible_public_key_b64" | base64 -d 2>/dev/null || true) + if [[ -z "$ansible_ssh_pub" && -f "$HOME/.ssh/id_ed25519_ansible.pub" ]]; then + ansible_ssh_pub=$(cat "$HOME/.ssh/id_ed25519_ansible.pub") + fi + + # Properly indent SSH keys for the YAML manifest + # Use 10 spaces to be safe inside the 'args' block's literal scalar + local ssh_pub_indented + ssh_pub_indented=$(printf '%s' "$ssh_pub" | sed 's/^/ /') + local ansible_ssh_pub_indented + ansible_ssh_pub_indented=$(printf '%s' "$ansible_ssh_pub" | sed 's/^/ /') + + # Use a separate variable for the YAML to help with debugging and clarity + local manifest + manifest=$(cat < /root/.ssh/authorized_keys + chmod 600 /root/.ssh/authorized_keys + + # Setup ansible user + id ansible >/dev/null 2>&1 || useradd -m -s /bin/bash ansible + echo 'ansible ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/90-ansible + chmod 440 /etc/sudoers.d/90-ansible + + # Setup ansible SSH + mkdir -p /home/ansible/.ssh + printf '%s\n' "$ansible_ssh_pub" > /home/ansible/.ssh/authorized_keys + chmod 600 /home/ansible/.ssh/authorized_keys + chown -R ansible:ansible /home/ansible/.ssh + + /usr/sbin/sshd -D + ports: + - containerPort: 22 + - containerPort: 88 + - containerPort: 464 + - containerPort: 389 + - containerPort: 445 +EOF +) + + # Debug: log the manifest to a file if needed + # echo "$manifest" > /tmp/dog-manifest.yaml + + if ! echo "$manifest" | kubectl apply -n "$NAMESPACE_AUTH" -f -; then + err "Kubectl apply failed. Manifest was:" + err "$manifest" + exit 1 + fi + + kubectl rollout status deployment/$KDC_NAME -n "$NAMESPACE_AUTH" --timeout=120s +} + +configure_authority() { + log "Configuring Samba AD and Kerberos on '$KDC_NAME' ..." + + local pod_name + pod_name=$(kubectl get pod -n "$NAMESPACE_AUTH" -l app=$KDC_NAME -o jsonpath='{.items[0].metadata.name}') + + # Provision Samba AD (simplified for version 1) + # In a real scenario, this would involve samba-tool domain provision + # We'll generate a password and save it to OpenBao + + local admin_pass + admin_pass=$(openssl rand -base64 16) + + # Mocking the configuration steps for now as per instructions + kubectl exec -n "$NAMESPACE_AUTH" "$pod_name" -- bash -c "echo 'Setting up AD...'" + + log "Saving administrator password to OpenBao ..." + write_to_bao "prole/authority" "{\"administrator_password\":\"$admin_pass\"}" + log "Administrator password saved to kv/prole/authority/administrator_password" + + local kdc_ip + kdc_ip=$(kubectl get svc -n "$NAMESPACE_AUTH" "$KDC_NAME" -o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo "dog.${NAMESPACE_AUTH}.svc.cluster.local") + + log "----------------------------------------------------------" + log "Kerberos Configuration for prole-db nodes (Version 1):" + log "KDC Server: $kdc_ip" + log "Realm: ${KRB5_REALM:-PROLE.ORG}" + log "Admin Server: $kdc_ip" + log "----------------------------------------------------------" +} + +case "$ACTION" in + start) + ensure_tools + ensure_namespace + deploy_dog + configure_authority + ;; + status) + kubectl get pods,svc -n "$NAMESPACE_AUTH" + ;; + stop) + kubectl delete namespace "$NAMESPACE_AUTH" + ;; + *) + err "Usage: $0 {start|stop|status}" + exit 1 + ;; +esac diff --git a/etc/init_openbao.sh b/etc/init_openbao.sh index a5c7d82..523399e 100755 --- a/etc/init_openbao.sh +++ b/etc/init_openbao.sh @@ -268,9 +268,15 @@ init_openbao_kv_and_store_admin_key() { echo "Admin key pair in OpenBao kv/prole/admin is already up to date." else echo "Writing admin key pair to kv/prole/admin ..." + # Preserve other fields (like root_password or ansible_public_key_b64) + local current_json + current_json=$(echo "$existing_data" | jq -c ".data.data") + local new_json + new_json=$(echo "$current_json" | jq -c ". + {\"admin_private_key_b64\":\"$priv\",\"admin_public_key_b64\":\"$pub\"}") + curl -sS -H "X-Vault-Token: $token" -H 'Content-Type: application/json' \ -X POST "$svc/v1/kv/data/prole/admin" \ - -d "{\"data\":{\"admin_private_key_b64\":\"$priv\",\"admin_public_key_b64\":\"$pub\"}}" >/dev/null + -d "{\"data\":$new_json}" >/dev/null echo "Stored admin key pair in OpenBao kv/prole/admin." fi else @@ -281,6 +287,24 @@ init_openbao_kv_and_store_admin_key() { echo "Stored admin key pair in OpenBao kv/prole/admin." fi + # Store Ansible public key if it exists + local ansible_pub_path="$HOME/.ssh/id_ed25519_ansible.pub" + if [[ -f "$ansible_pub_path" ]]; then + echo "Storing Ansible public key in OpenBao kv/prole/admin ..." + local a_pub + a_pub=$(base64 <"$ansible_pub_path" | tr -d '\n') + + local current_admin_data + current_admin_data=$(curl -sS -H "X-Vault-Token: $token" "$svc/v1/kv/data/prole/admin" | jq -c ".data.data" 2>/dev/null || echo "{}") + local updated_admin_data + updated_admin_data=$(echo "$current_admin_data" | jq -c ". + {\"ansible_public_key_b64\":\"$a_pub\"}") + + curl -sS -H "X-Vault-Token: $token" -H 'Content-Type: application/json' \ + -X POST "$svc/v1/kv/data/prole/admin" \ + -d "{\"data\":$updated_admin_data}" >/dev/null + echo "Ansible public key stored." + fi + # Store User SSH keys if they exist local user_key_priv="$HOME/.ssh/id_prole_ed25519" local user_key_pub="$HOME/.ssh/id_prole_ed25519.pub" @@ -532,7 +556,8 @@ case "$ACTION" in # Ensure port-forward is running for OpenBao echo "Ensuring port-forward for OpenBao is active ..." - "$SCRIPT_DIR/init_port_forwards.sh" restart openbao & + "$SCRIPT_DIR/init_port_forwards.sh" stop openbao || true + "$SCRIPT_DIR/init_port_forwards.sh" start openbao & sleep 2 max_retries=15 diff --git a/k8s/openbao/kerberos-configmap.yaml b/k8s/openbao/kerberos-configmap.yaml index 2037587..0d516ec 100644 --- a/k8s/openbao/kerberos-configmap.yaml +++ b/k8s/openbao/kerberos-configmap.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: prole-krb5-conf - namespace: prole-chrisfu-abacababa + namespace: prole-chrisfu-abracadabra data: krb5.conf: | [libdefaults] diff --git a/prole/tests/test_init_authority.sh b/prole/tests/test_init_authority.sh new file mode 100644 index 0000000..b9f42fa --- /dev/null +++ b/prole/tests/test_init_authority.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# prole/tests/test_init_authority.sh +# Validates the YAML generation of init_authority.sh + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd) +INIT_AUTHORITY="$PROLE_HOME/etc/init_authority.sh" + +if [[ ! -f "$INIT_AUTHORITY" ]]; then + echo "ERROR: $INIT_AUTHORITY not found" + exit 1 +fi + +# Mock environment +export PROLE_SERVICE="$PROLE_HOME/etc" +export NAMESPACE="test-namespace" +export PROLE_HOME="$PROLE_HOME" + +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR"' EXIT + +# Mock other tools to avoid actual execution +mock_tool() { + cat < "$TMP_DIR/$1" +#!/usr/bin/env bash +# LOG THE CALL +echo "Mocked $1 called with \$@" >> "$TMP_DIR/mock_calls.log" +case "\$1" in + *v1/kv/data/prole/admin) + echo '{"data":{"data":{"root_password":"root", "admin_public_key_b64":"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF6S3Z4CkNvS0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo="}}}' + ;; + *) + if [[ "$1" == "base64" ]]; then + /usr/bin/base64 "\$@" + elif [[ "$1" == "jq" ]]; then + /usr/bin/jq "\$@" + elif [[ "$1" == "kubectl" ]]; then + case "\$*" in + *apply*) + if [[ ! -t 0 ]]; then + cat > "$TMP_DIR/generated_manifest.yaml" + fi + # If the manifest is NOT valid YAML, we want to simulate the failure + if python3 -c "import yaml, sys; yaml.safe_load(open('$TMP_DIR/generated_manifest.yaml'))" 2>/dev/null; then + echo "deployment.apps/dog created" + exit 0 + else + echo "error: error parsing STDIN: error converting YAML to JSON: yaml: line 34: could not find expected ':'" >&2 + exit 1 + fi + ;; + *rollout*) + echo "deployment \"dog\" successfully rolled out" + exit 0 + ;; + *get*pod*) + echo "dog-pod-123" + exit 0 + ;; + *) + exit 0 + ;; + esac + else + exit 0 + fi + ;; +esac +M_EOF + chmod +x "$TMP_DIR/$1" +} + +mock_tool curl +mock_tool kubectl +export PATH="$TMP_DIR:/usr/bin:/bin" + +# We'll run a subshell where we override read_from_bao if possible, +# but init_authority.sh sources prole_cfg.sh and defines its own functions. +# Instead, we will rely on the fact that it uses curl/jq which we mocked. + +echo "Running init_authority.sh start (mocked)..." +# We need to suppress the errors from configure_authority which follows deploy_dog +# Also ensure the PATH is exported to subshells +export PATH +bash "$INIT_AUTHORITY" start > "$TMP_DIR/out" 2>&1 || true + +if [[ -f "$TMP_DIR/generated_manifest.yaml" ]]; then + echo "Validating generated YAML..." + # Use python3 to check if it's valid YAML + if python3 -c "import yaml, sys; yaml.safe_load(open('$TMP_DIR/generated_manifest.yaml'))" 2>"$TMP_DIR/yaml_error"; then + echo "SUCCESS: YAML is valid." + else + echo "FAILURE: YAML is invalid." + cat "$TMP_DIR/yaml_error" + echo "--- Generated YAML ---" + cat "$TMP_DIR/generated_manifest.yaml" + echo "----------------------" + exit 1 + fi +else + echo "FAILURE: No YAML manifest generated." + echo "--- Output ---" + cat "$TMP_DIR/out" + echo "--------------" + echo "--- Mock calls ---" + cat "$TMP_DIR/mock_calls.log" || echo "No mock calls log" + echo "------------------" + exit 1 +fi