Fix YAML parsing errors in init_authority.sh and improve SSH key management. Resolved 'invalid Yaml document separator' and 'could not find expected :' errors in etc/init_authority.sh by using printf to inject multi-line SSH keys safely. Added 'ansible' user setup. Updated etc/init_openbao.sh to store ansible keys. Created prole/tests/test_init_authority.sh for validation.

This commit is contained in:
chrisfu 2026-02-02 13:15:31 -08:00
parent f908585f23
commit 5ab6381982
4 changed files with 370 additions and 3 deletions

233
etc/init_authority.sh Executable file
View File

@ -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 <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: $KDC_NAME
labels:
app: $KDC_NAME
spec:
replicas: 1
selector:
matchLabels:
app: $KDC_NAME
template:
metadata:
labels:
app: $KDC_NAME
spec:
containers:
- name: dog
image: $KDC_IMAGE
command: ["/bin/bash", "-c"]
args:
- |
apt-get update && apt-get install -y openssh-server krb5-kdc krb5-admin-server samba winbind libpam-winbind libnss-winbind sudo
mkdir -p /run/sshd
echo "root:\$root_pass" | chpasswd
# Setup root SSH
mkdir -p /root/.ssh
printf '%s\n' "$ssh_pub" > /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

View File

@ -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

View File

@ -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]

View File

@ -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 <<M_EOF > "$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