diff --git a/conf/prole.cfg b/conf/prole.cfg index 730f1ae..a32eafd 100644 --- a/conf/prole.cfg +++ b/conf/prole.cfg @@ -1,5 +1,5 @@ ; Prole Master Configuration File -; Generated by install.py on 2026-02-28 23:26:36 +; Generated by install.py on 2026-03-01 00:30:19 ; This file is used as input for Ansible deployment and k8s cluster creation. [User] @@ -27,40 +27,40 @@ dependencies.opentofu.install = true dependencies.python.install = true dependencies.verify_all = false disk_selection.disk_type = local -disk_selection.local_path = /Users/chrisfu +disk_selection.local_path = ${PROLE_HOME}/prole-tools-app/dist disk_selection.removable_mount = -env_setup.NAMESPACE = ${NAMESPACE} +env_setup.NAMESPACE = prole-hq0-db0 env_setup.PROLE_CONF = ${PROLE_CONF} env_setup.PROLE_DATA = ${PROLE_DATA} env_setup.PROLE_HOME = ${PROLE_HOME} env_setup.PROLE_LOGS = ${PROLE_LOGS} env_setup.PROLE_SERVICE = ${PROLE_SERVICE} init_cluster.at_rest_encryption_enabled = true -init_cluster.cluster_env = dev -init_cluster.deployment_target = prole-dev-cluster +init_cluster.cluster_env = service +init_cluster.deployment_target = prole-service-cluster init_cluster.gitops_enabled = false -init_cluster.k3s_server_url = -init_cluster.k3s_token = -init_cluster.kerberos_enabled = false -init_cluster.mode = k3d +init_cluster.k3s_server_url = https://myrddin.prole.org:6443 +init_cluster.k3s_token = ${OPENBAO:REDACTED} +init_cluster.kerberos_enabled = true +init_cluster.mode = k3s init_cluster.start_cluster = true init_cluster.supabase_enabled = false init_cnpg_deploy.force_rollout = false init_cnpg_deploy.run_deploy = true init_db_build.run_build = true init_password.db_host_port = 5432 -init_password.db_namespace = ${NAMESPACE} -init_password.db_password = -init_password.db_password_confirm = -init_password.db_username = root +init_password.db_namespace = prole-hq0-db0 +init_password.db_password = ${OPENBAO:kv/prole/${NAMESPACE}/db#password} +init_password.db_password_confirm = ${OPENBAO:kv/prole/${NAMESPACE}/db#password} +init_password.db_username = chrisfu init_password.generate_ssh_key = true init_scripts.run_scripts = true -kerberos_config.enabled = false -kerberos_config.kdc = -kerberos_config.password = -kerberos_config.realm = -kerberos_config.test_connection = false -kerberos_config.user = +kerberos_config.enabled = true +kerberos_config.kdc = 10.0.0.3 +kerberos_config.password = ${OPENBAO:kv/prole/${NAMESPACE}/kerberos#password} +kerberos_config.realm = PROLE.ORG +kerberos_config.test_connection = true +kerberos_config.user = chrisfu network_scan.run = true ollama_config.model = ollama_config.server_host = @@ -68,16 +68,16 @@ ollama_config.server_port = 11434 [Global] ; Variables used by name in more than one place or assumed global scope -CLUSTER_ENV = dev +CLUSTER_ENV = service DB_HOST_PORT = 5432 -DB_PASSWORD = -DEPLOYMENT_MODE = k3d -DEPLOYMENT_TARGET = prole-dev-cluster +DB_PASSWORD = ${OPENBAO:kv/prole/${NAMESPACE}/db#password} +DEPLOYMENT_MODE = k3s +DEPLOYMENT_TARGET = prole-service-cluster NAMESPACE = ${NAMESPACE} -PROLE_DB_USER = root +PROLE_DB_USER = chrisfu PROLE_HOME = ${PROLE_HOME} -PROLE_K3S_SERVER = -PROLE_K3S_TOKEN = +PROLE_K3S_SERVER = https://myrddin.prole.org:6443 +PROLE_K3S_TOKEN = ${OPENBAO:REDACTED} PROLE_OPENTOFU_URL = http://127.0.0.1:8080 SERVICE_NAMESPACE = default @@ -96,6 +96,8 @@ ANSIBLE_INVENTORY = ${PROLE_HOME}/infrastructure/inventory ANSIBLE_REALM = PROLE.ORG ANSIBLE_TOPOLOGY = {"domain":"prole.org","realm":"PROLE.ORG","internal_records":{"aventage.prole.org":"10.0.0.206","fairyland.prole.org":"10.0.0.208","loghost.prole.org":"10.0.0.3","morana.prole.org":"10.0.0.66","morgoth.prole.org":"10.0.0.204","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207","synology.prole.org":"10.0.0.203","zinfandel.prole.org":"10.0.0.205"},"ad_dc":{"host":"myrddin.prole.org","ip":"10.0.0.3"},"k3s":{"server_url":"https://myrddin.prole.org:6443","server_host":"myrddin.prole.org","token_present":true},"groups":{"iscsi":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_hosts":["myrddin.prole.org","pi.prole.org","retropie.prole.org"],"linux_hosts":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org"]},"hosts":{"myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207"},"unmapped_hosts":[]} KDC_ANSIBLE_DETECTED = 10.0.0.3 +KDC_AUTO_DETECTED = 10.0.0.3 +KERBEROS_AUTO_ENABLED = True [Port Forwards] ; No configuration values captured yet for this section. @@ -111,13 +113,27 @@ PROLE_SERVICE = ${PROLE_SERVICE} ; No configuration values captured yet for this section. [Kerberos Authentication] -; No configuration values captured yet for this section. +AD_PORT_FORWARD = 1 +AD_PROXY_HOST_NETWORK = 1 +AD_PROXY_IMAGE = alpine/socat +AD_PROXY_SERVICE = prole-kerberos-ad-dc +AD_TCP_PORTS = 88 389 445 464 636 +AD_UDP_PORTS = 88 464 +ENABLED = True +KDC = 10.0.0.3 +PASSWORD = ${OPENBAO:kv/prole/${NAMESPACE}/kerberos#password} +REALM = PROLE.ORG +SERVER = 10.0.0.3 +USER = chrisfu [Ollama] ; No configuration values captured yet for this section. [Optional Features] +AT_REST_ENCRYPTION_ENABLED = True GITOPS_ENABLED = False +KERBEROS_ENABLED = True +SUPABASE_ENABLED = False [GitOps] ; No configuration values captured yet for this section. @@ -126,38 +142,41 @@ GITOPS_ENABLED = False NAMESPACE = ${NAMESPACE} [Initialize Cluster] -; No configuration values captured yet for this section. +ENVIRONMENT = service +K3S_SERVER_URL = https://myrddin.prole.org:6443 +K3S_TOKEN = ${OPENBAO:REDACTED} [Dev Cluster (k3d)] CLUSTER_ENV = dev DISPLAY_NAME = prole-dev-cluster -KUBECTL_CONTEXT = +KUBECTL_CONTEXT = prole-k3s MODE = k3d [Service Cluster (k3s)] CLUSTER_ENV = prole-service-cluster DISPLAY_NAME = prole-service-cluster -K3S_SERVER_URL = -K3S_TOKEN = +K3S_SERVER_URL = https://myrddin.prole.org:6443 +K3S_TOKEN = ${OPENBAO:REDACTED} MODE = k3s PIPELINE_URL = http://127.0.0.1:8080 [Prod Cluster (k8s)] -ARTIFACTS_DIR = /private/var/folders/rt/pywlnmxj3dn7t5552vwdcpp80000gn/T/pytest-of-chrisfu/pytest-67/test_encryption_toggle0/data/staging +ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging CLUSTER_ENV = prole-prod-cluster DISPLAY_NAME = prole-prod-cluster MODE = k8s PIPELINE_URL = http://127.0.0.1:8080 [Docker Build] -; No configuration values captured yet for this section. +LOCAL_REGISTRY = localhost:5000 +LOCAL_REGISTRY_INTERNAL = k3d-prole-registry.localhost:5000 [Initialization Scripts] ; No configuration values captured yet for this section. [Deployment] -MODE = k3d -TARGET = prole-dev-cluster +MODE = k3s +TARGET = prole-service-cluster [Install] ; No configuration values captured yet for this section. diff --git a/etc/init_kerberos.sh b/etc/init_kerberos.sh index c0200fe..dbe489c 100755 --- a/etc/init_kerberos.sh +++ b/etc/init_kerberos.sh @@ -100,6 +100,23 @@ ensure_namespace_named() { fi } +preferred_service_namespace() { + local ns="${SERVICE_NAMESPACE:-}" + local primary="${NAMESPACE:-}" + + if [[ -n "$primary" && "$primary" != "default" ]]; then + if [[ -z "$ns" || "$ns" == "default" ]]; then + ns="$primary" + fi + fi + + if [[ -z "$ns" ]]; then + ns="${primary:-default}" + fi + + printf '%s' "$ns" +} + is_ip_address() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] } @@ -411,7 +428,7 @@ sync_prole_kdc_trust() { return 0 fi local svc_ns - svc_ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}" + svc_ns=$(preferred_service_namespace) SERVICE_NAMESPACE="$svc_ns" \ KRB5_REALM="$KRB5_REALM" \ KRB5_KDC="$KRB5_KDC" \ @@ -460,7 +477,7 @@ ensure_krb5_conf_configmap() { .$domain_val = $realm_val $domain_val = $realm_val EOF - kubectl -n "$NAMESPACE" create configmap prole-krb5-conf --from-file=krb5.conf="$TMP" --dry-run=client -o yaml | kubectl apply -f - + kubectl -n "$NAMESPACE" create configmap prole-krb5-conf --from-file=krb5.conf="$TMP" --dry-run=client -o yaml | kubectl apply --validate=false -f - rm -f "$TMP" } @@ -705,7 +722,7 @@ apply_ad_forwarder() { local forward_cmd="${tcp_cmd}${udp_cmd}wait" log "Applying AD DC forwarder '${KRB5_AD_PROXY_NAME}' in namespace '${KRB5_AD_NAMESPACE}' (target ${kdc_host}) ..." - cat < str: + prefix = f"{key}:" + for line in content.splitlines(): + stripped = line.strip() + if not stripped or stripped.startswith("#"): + continue + if not stripped.startswith(prefix): + continue + value = stripped[len(prefix) :].strip() + if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"): + value = value[1:-1] + return value + return "" + + @staticmethod + def _vault_password_files() -> list[Path]: + files: list[Path] = [] + env_pw = os.environ.get("ANSIBLE_VAULT_PASSWORD_FILE", "").strip() + if env_pw: + files.append(Path(env_pw)) + for base in (Path.cwd(), PROJECT_ROOT): + files.append(base / ".vault_pass") + seen: set[Path] = set() + unique: list[Path] = [] + for item in files: + if item in seen: + continue + seen.add(item) + unique.append(item) + return unique + + def _resolve_samba_admin_password_from_vault(self) -> str: + vault_path = ( + PROJECT_ROOT + / "infrastructure" + / "inventory" + / "group_vars" + / "ad_dc" + / "vault.yml" + ) + if not vault_path.exists(): + return "" + + try: + raw = vault_path.read_text(encoding="utf-8") + except Exception: + raw = "" + if raw and "$ANSIBLE_VAULT;" not in raw: + return self._yaml_scalar(raw, "vault_samba_dns_admin_pass") + + if shutil.which("ansible-vault") is None: + return "" + + _ensure_ansible_vault_credentials(prompt_ui=False) + for password_file in self._vault_password_files(): + if not password_file.is_file(): + continue + try: + res = subprocess.run( + [ + "ansible-vault", + "view", + str(vault_path), + "--vault-password-file", + str(password_file), + ], + capture_output=True, + text=True, + check=False, + ) + except Exception: + continue + if res.returncode != 0: + continue + value = self._yaml_scalar(res.stdout or "", "vault_samba_dns_admin_pass") + if value: + return value + return "" + def _load_secret_cache_from_cfg(self): try: if self._cfg_path_override is not None: @@ -389,6 +475,8 @@ class SecurityScreenMixin: krb_plain = krb_val else: krb_plain = "" + if not krb_plain: + krb_plain = self._resolve_samba_admin_password_from_vault() if krb_plain: try: self.kerberos_password.set(krb_plain) diff --git a/k8s/openbao/deployment.yaml b/k8s/openbao/deployment.yaml index 0456d46..e7f6e23 100644 --- a/k8s/openbao/deployment.yaml +++ b/k8s/openbao/deployment.yaml @@ -2,7 +2,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: openbao - namespace: default spec: replicas: 1 selector: @@ -24,7 +23,6 @@ apiVersion: v1 kind: Service metadata: name: openbao - namespace: default spec: selector: app: openbao diff --git a/mock_val/init_kerberos.sh b/mock_val/init_kerberos.sh index ddeec39..a141cc8 100755 --- a/mock_val/init_kerberos.sh +++ b/mock_val/init_kerberos.sh @@ -100,6 +100,23 @@ ensure_namespace_named() { fi } +preferred_service_namespace() { + local ns="${SERVICE_NAMESPACE:-}" + local primary="${NAMESPACE:-}" + + if [[ -n "$primary" && "$primary" != "default" ]]; then + if [[ -z "$ns" || "$ns" == "default" ]]; then + ns="$primary" + fi + fi + + if [[ -z "$ns" ]]; then + ns="${primary:-default}" + fi + + printf '%s' "$ns" +} + is_ip_address() { [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] } @@ -411,7 +428,7 @@ sync_prole_kdc_trust() { return 0 fi local svc_ns - svc_ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}" + svc_ns=$(preferred_service_namespace) SERVICE_NAMESPACE="$svc_ns" \ KRB5_REALM="$KRB5_REALM" \ KRB5_KDC="$KRB5_KDC" \ @@ -460,7 +477,7 @@ ensure_krb5_conf_configmap() { .$domain_val = $realm_val $domain_val = $realm_val EOF - kubectl -n "$NAMESPACE" create configmap prole-krb5-conf --from-file=krb5.conf="$TMP" --dry-run=client -o yaml | kubectl apply -f - + kubectl -n "$NAMESPACE" create configmap prole-krb5-conf --from-file=krb5.conf="$TMP" --dry-run=client -o yaml | kubectl apply --validate=false -f - rm -f "$TMP" } @@ -705,7 +722,7 @@ apply_ad_forwarder() { local forward_cmd="${tcp_cmd}${udp_cmd}wait" log "Applying AD DC forwarder '${KRB5_AD_PROXY_NAME}' in namespace '${KRB5_AD_NAMESPACE}' (target ${kdc_host}) ..." - cat < "$WORK_DIR/etc/init_kerberos_test.sh" +#!/usr/bin/env bash +exit 0 +EOF +chmod +x "$WORK_DIR/etc/init_kerberos_test.sh" + +cat < "$WORK_DIR/env.sh" +#!/usr/bin/env bash +export PROLE_HOME="$WORK_DIR" +export PROLE_CONF="$WORK_DIR/conf" +EOF + +cat < "$WORK_DIR/conf/prole.cfg" +[globals] +prole.home = $WORK_DIR +prole.mode = k3s +EOF + +export KUBECTL_LOG="$TMP_DIR/kubectl.log" +export KUBECTL_PAYLOAD_LOG="$TMP_DIR/kubectl-payload.log" + +cat <<'EOF' > "$BIN_DIR/kubectl" +#!/usr/bin/env bash +set -euo pipefail + +args=("$@") +printf '%s\n' "${args[*]}" >> "${KUBECTL_LOG}" + +sub="" +sub_idx=-1 +i=0 +while [[ $i -lt ${#args[@]} ]]; do + case "${args[$i]}" in + -n|--namespace|--context|--kubeconfig) + i=$((i + 2)) + ;; + --namespace=*|--context=*|--kubeconfig=*) + i=$((i + 1)) + ;; + -*) + i=$((i + 1)) + ;; + *) + sub="${args[$i]}" + sub_idx=$i + break + ;; + esac +done + +case "$sub" in + get) + resource="${args[$((sub_idx + 1))]:-}" + if [[ "$resource" == "namespace" ]]; then + exit 1 + fi + if [[ "$resource" == "pods" ]]; then + printf '' + exit 0 + fi + exit 0 + ;; + create) + resource="${args[$((sub_idx + 1))]:-}" + if [[ "$resource" == "namespace" ]]; then + exit 0 + fi + if [[ "$resource" == "configmap" ]]; then + cat <> "${KUBECTL_PAYLOAD_LOG}" + + case " ${args[*]} " in + *" --validate=false "*) + ;; + *) + echo "error: error validating \"STDIN\": error validating data: failed to download openapi: the server has asked for the client to provide credentials" >&2 + exit 1 + ;; + esac + + if [[ "$payload" == *"name: prole-kdc-egress"* ]]; then + if [[ "$payload" != *"cidr: 10.0.0.3/32"* ]]; then + echo "missing expected kdc cidr in NetworkPolicy payload" >&2 + exit 1 + fi + if [[ "$payload" == *"cidr: 0.0.0.0/0"* ]]; then + echo "unexpected broad egress cidr in NetworkPolicy payload" >&2 + exit 1 + fi + fi + + exit 0 + ;; + wait|exec|delete|rollout|patch|config) + exit 0 + ;; + *) + exit 0 + ;; +esac +EOF +chmod +x "$BIN_DIR/kubectl" + +cat <<'EOF' > "$BIN_DIR/curl" +#!/usr/bin/env bash +exit 0 +EOF +chmod +x "$BIN_DIR/curl" + +cat <<'EOF' > "$BIN_DIR/jq" +#!/usr/bin/env bash +exit 0 +EOF +chmod +x "$BIN_DIR/jq" + +export PATH="$BIN_DIR:$PATH" + +export PROLE_HOME="$WORK_DIR" +export PROLE_CONF="$WORK_DIR/conf" +export NAMESPACE="prole-db-a0001" +export SERVICE_NAMESPACE="default" +export PROLE_MODE="k3s" +export KRB5_REALM="PROLE.ORG" +export KRB5_KDC="10.0.0.3" +export KRB5_ADMIN="10.0.0.3" +export KRB5_PASSWORD="test-password" + +if "$WORK_DIR/etc/init_kerberos.sh" test >"$TMP_DIR/stdout" 2>"$TMP_DIR/stderr"; then + : +else + echo "FAILURE: init_kerberos.sh test exited non-zero" + cat "$TMP_DIR/stderr" + exit 1 +fi + +if ! grep -q "Using KDC endpoint for test: 10.0.0.3" "$TMP_DIR/stdout"; then + echo "FAILURE: expected real KDC endpoint log line not found" + cat "$TMP_DIR/stdout" + exit 1 +fi + +if ! grep -q "apply --validate=false" "$KUBECTL_LOG"; then + echo "FAILURE: expected kubectl apply --validate=false invocation not found" + cat "$KUBECTL_LOG" + exit 1 +fi + +if ! grep -q "apply --validate=false -n prole-db-a0001 -f -" "$KUBECTL_LOG"; then + echo "FAILURE: expected KDC policy apply in resolved namespace not found" + cat "$KUBECTL_LOG" + exit 1 +fi + +if grep -q "apply --validate=false -n default -f -" "$KUBECTL_LOG"; then + echo "FAILURE: unexpected KDC policy apply against default namespace" + cat "$KUBECTL_LOG" + exit 1 +fi + +echo "SUCCESS" diff --git a/tests/installer/test_core_classes.py b/tests/installer/test_core_classes.py index 1cdcc91..1c18ace 100644 --- a/tests/installer/test_core_classes.py +++ b/tests/installer/test_core_classes.py @@ -12,6 +12,7 @@ from installer.state import InstallerState from installer.milestone import Milestone, ProgressCallback from installer.build import get_build_command, BuildMilestone from installer.core.controller import ProleController +from installer.core.actions import ProleSilentInstaller # ===== InstallerState ===== @@ -270,6 +271,10 @@ class TestProleController: assert c.project_root == tmp_path assert c.verbose is False assert c.state is not None + installer = ProleSilentInstaller(c) + defaults = installer._default_inputs() + assert defaults.get("kerberos_config.user") == "administrator" + assert defaults.get("kerberos_config.password") == "" def test_init_verbose(self, tmp_path): c = ProleController(tmp_path, verbose=True)