Fix Kerberos test flow and improve multi-namespace support in k3s

- Resolve namespace drift in Kerberos test flow and OpenBao deployment.
- Fix OpenAPI credential-validation failures in etc scripts by using --validate=false with kubectl.
- Introduce preferred_service_namespace() to accurately target namespaces for KDC policy/test paths.
- Default Kerberos test user to 'administrator' and prefill from Ansible vault secrets.
- Generalized OpenBao deployment logic to correctly handle multi-namespace environments.
- Added regression test 'tests/etc/test_init_kerberos_k3s_real_kdc.sh'.
- Synchronized SERVICE_NAMESPACE extraction in prole_cfg.sh.

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
chrisfu 2026-03-01 00:57:10 -08:00
parent 28b3cd72df
commit 46f31843c2
13 changed files with 413 additions and 65 deletions

View File

@ -1,5 +1,5 @@
; Prole Master Configuration File ; 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. ; This file is used as input for Ansible deployment and k8s cluster creation.
[User] [User]
@ -27,40 +27,40 @@ dependencies.opentofu.install = true
dependencies.python.install = true dependencies.python.install = true
dependencies.verify_all = false dependencies.verify_all = false
disk_selection.disk_type = local 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 = disk_selection.removable_mount =
env_setup.NAMESPACE = ${NAMESPACE} env_setup.NAMESPACE = prole-hq0-db0
env_setup.PROLE_CONF = ${PROLE_CONF} env_setup.PROLE_CONF = ${PROLE_CONF}
env_setup.PROLE_DATA = ${PROLE_DATA} env_setup.PROLE_DATA = ${PROLE_DATA}
env_setup.PROLE_HOME = ${PROLE_HOME} env_setup.PROLE_HOME = ${PROLE_HOME}
env_setup.PROLE_LOGS = ${PROLE_LOGS} env_setup.PROLE_LOGS = ${PROLE_LOGS}
env_setup.PROLE_SERVICE = ${PROLE_SERVICE} env_setup.PROLE_SERVICE = ${PROLE_SERVICE}
init_cluster.at_rest_encryption_enabled = true init_cluster.at_rest_encryption_enabled = true
init_cluster.cluster_env = dev init_cluster.cluster_env = service
init_cluster.deployment_target = prole-dev-cluster init_cluster.deployment_target = prole-service-cluster
init_cluster.gitops_enabled = false init_cluster.gitops_enabled = false
init_cluster.k3s_server_url = init_cluster.k3s_server_url = https://myrddin.prole.org:6443
init_cluster.k3s_token = init_cluster.k3s_token = ${OPENBAO:REDACTED}
init_cluster.kerberos_enabled = false init_cluster.kerberos_enabled = true
init_cluster.mode = k3d init_cluster.mode = k3s
init_cluster.start_cluster = true init_cluster.start_cluster = true
init_cluster.supabase_enabled = false init_cluster.supabase_enabled = false
init_cnpg_deploy.force_rollout = false init_cnpg_deploy.force_rollout = false
init_cnpg_deploy.run_deploy = true init_cnpg_deploy.run_deploy = true
init_db_build.run_build = true init_db_build.run_build = true
init_password.db_host_port = 5432 init_password.db_host_port = 5432
init_password.db_namespace = ${NAMESPACE} init_password.db_namespace = prole-hq0-db0
init_password.db_password = init_password.db_password = ${OPENBAO:kv/prole/${NAMESPACE}/db#password}
init_password.db_password_confirm = init_password.db_password_confirm = ${OPENBAO:kv/prole/${NAMESPACE}/db#password}
init_password.db_username = root init_password.db_username = chrisfu
init_password.generate_ssh_key = true init_password.generate_ssh_key = true
init_scripts.run_scripts = true init_scripts.run_scripts = true
kerberos_config.enabled = false kerberos_config.enabled = true
kerberos_config.kdc = kerberos_config.kdc = 10.0.0.3
kerberos_config.password = kerberos_config.password = ${OPENBAO:kv/prole/${NAMESPACE}/kerberos#password}
kerberos_config.realm = kerberos_config.realm = PROLE.ORG
kerberos_config.test_connection = false kerberos_config.test_connection = true
kerberos_config.user = kerberos_config.user = chrisfu
network_scan.run = true network_scan.run = true
ollama_config.model = ollama_config.model =
ollama_config.server_host = ollama_config.server_host =
@ -68,16 +68,16 @@ ollama_config.server_port = 11434
[Global] [Global]
; Variables used by name in more than one place or assumed global scope ; Variables used by name in more than one place or assumed global scope
CLUSTER_ENV = dev CLUSTER_ENV = service
DB_HOST_PORT = 5432 DB_HOST_PORT = 5432
DB_PASSWORD = DB_PASSWORD = ${OPENBAO:kv/prole/${NAMESPACE}/db#password}
DEPLOYMENT_MODE = k3d DEPLOYMENT_MODE = k3s
DEPLOYMENT_TARGET = prole-dev-cluster DEPLOYMENT_TARGET = prole-service-cluster
NAMESPACE = ${NAMESPACE} NAMESPACE = ${NAMESPACE}
PROLE_DB_USER = root PROLE_DB_USER = chrisfu
PROLE_HOME = ${PROLE_HOME} PROLE_HOME = ${PROLE_HOME}
PROLE_K3S_SERVER = PROLE_K3S_SERVER = https://myrddin.prole.org:6443
PROLE_K3S_TOKEN = PROLE_K3S_TOKEN = ${OPENBAO:REDACTED}
PROLE_OPENTOFU_URL = http://127.0.0.1:8080 PROLE_OPENTOFU_URL = http://127.0.0.1:8080
SERVICE_NAMESPACE = default SERVICE_NAMESPACE = default
@ -96,6 +96,8 @@ ANSIBLE_INVENTORY = ${PROLE_HOME}/infrastructure/inventory
ANSIBLE_REALM = PROLE.ORG 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":[]} 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_ANSIBLE_DETECTED = 10.0.0.3
KDC_AUTO_DETECTED = 10.0.0.3
KERBEROS_AUTO_ENABLED = True
[Port Forwards] [Port Forwards]
; No configuration values captured yet for this section. ; No configuration values captured yet for this section.
@ -111,13 +113,27 @@ PROLE_SERVICE = ${PROLE_SERVICE}
; No configuration values captured yet for this section. ; No configuration values captured yet for this section.
[Kerberos Authentication] [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] [Ollama]
; No configuration values captured yet for this section. ; No configuration values captured yet for this section.
[Optional Features] [Optional Features]
AT_REST_ENCRYPTION_ENABLED = True
GITOPS_ENABLED = False GITOPS_ENABLED = False
KERBEROS_ENABLED = True
SUPABASE_ENABLED = False
[GitOps] [GitOps]
; No configuration values captured yet for this section. ; No configuration values captured yet for this section.
@ -126,38 +142,41 @@ GITOPS_ENABLED = False
NAMESPACE = ${NAMESPACE} NAMESPACE = ${NAMESPACE}
[Initialize Cluster] [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)] [Dev Cluster (k3d)]
CLUSTER_ENV = dev CLUSTER_ENV = dev
DISPLAY_NAME = prole-dev-cluster DISPLAY_NAME = prole-dev-cluster
KUBECTL_CONTEXT = KUBECTL_CONTEXT = prole-k3s
MODE = k3d MODE = k3d
[Service Cluster (k3s)] [Service Cluster (k3s)]
CLUSTER_ENV = prole-service-cluster CLUSTER_ENV = prole-service-cluster
DISPLAY_NAME = prole-service-cluster DISPLAY_NAME = prole-service-cluster
K3S_SERVER_URL = K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = K3S_TOKEN = ${OPENBAO:REDACTED}
MODE = k3s MODE = k3s
PIPELINE_URL = http://127.0.0.1:8080 PIPELINE_URL = http://127.0.0.1:8080
[Prod Cluster (k8s)] [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 CLUSTER_ENV = prole-prod-cluster
DISPLAY_NAME = prole-prod-cluster DISPLAY_NAME = prole-prod-cluster
MODE = k8s MODE = k8s
PIPELINE_URL = http://127.0.0.1:8080 PIPELINE_URL = http://127.0.0.1:8080
[Docker Build] [Docker Build]
; No configuration values captured yet for this section. LOCAL_REGISTRY = localhost:5000
LOCAL_REGISTRY_INTERNAL = k3d-prole-registry.localhost:5000
[Initialization Scripts] [Initialization Scripts]
; No configuration values captured yet for this section. ; No configuration values captured yet for this section.
[Deployment] [Deployment]
MODE = k3d MODE = k3s
TARGET = prole-dev-cluster TARGET = prole-service-cluster
[Install] [Install]
; No configuration values captured yet for this section. ; No configuration values captured yet for this section.

View File

@ -100,6 +100,23 @@ ensure_namespace_named() {
fi 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() { is_ip_address() {
[[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
} }
@ -411,7 +428,7 @@ sync_prole_kdc_trust() {
return 0 return 0
fi fi
local svc_ns local svc_ns
svc_ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}" svc_ns=$(preferred_service_namespace)
SERVICE_NAMESPACE="$svc_ns" \ SERVICE_NAMESPACE="$svc_ns" \
KRB5_REALM="$KRB5_REALM" \ KRB5_REALM="$KRB5_REALM" \
KRB5_KDC="$KRB5_KDC" \ KRB5_KDC="$KRB5_KDC" \
@ -460,7 +477,7 @@ ensure_krb5_conf_configmap() {
.$domain_val = $realm_val .$domain_val = $realm_val
$domain_val = $realm_val $domain_val = $realm_val
EOF 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" rm -f "$TMP"
} }
@ -705,7 +722,7 @@ apply_ad_forwarder() {
local forward_cmd="${tcp_cmd}${udp_cmd}wait" 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}) ..." log "Applying AD DC forwarder '${KRB5_AD_PROXY_NAME}' in namespace '${KRB5_AD_NAMESPACE}' (target ${kdc_host}) ..."
cat <<EOF | kubectl apply -n "$KRB5_AD_NAMESPACE" -f - cat <<EOF | kubectl apply --validate=false -n "$KRB5_AD_NAMESPACE" -f -
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@ -806,11 +823,11 @@ ensure_kdc_network_allow() {
fi fi
fi fi
cidr="${kdc_host}/32" cidr="${kdc_host}/32"
ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}" ns=$(preferred_service_namespace)
ensure_namespace_named "$ns" ensure_namespace_named "$ns"
log "Ensuring KDC egress policy in namespace '${ns}' ..." log "Ensuring KDC egress policy in namespace '${ns}' ..."
cat <<EOF | kubectl apply -n "$ns" -f - cat <<EOF | kubectl apply --validate=false -n "$ns" -f -
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: NetworkPolicy kind: NetworkPolicy
metadata: metadata:
@ -825,7 +842,7 @@ spec:
egress: egress:
- to: - to:
- ipBlock: - ipBlock:
cidr: 0.0.0.0/0 cidr: ${cidr}
ports: ports:
$(for port in $KRB5_AD_TCP_PORTS; do printf " - protocol: TCP\n port: %s\n" "$port"; done) $(for port in $KRB5_AD_TCP_PORTS; do printf " - protocol: TCP\n port: %s\n" "$port"; done)
$(for port in $KRB5_AD_UDP_PORTS; do printf " - protocol: UDP\n port: %s\n" "$port"; done) $(for port in $KRB5_AD_UDP_PORTS; do printf " - protocol: UDP\n port: %s\n" "$port"; done)
@ -854,7 +871,7 @@ spec:
egress: egress:
- to: - to:
- ipBlock: - ipBlock:
cidr: 0.0.0.0/0 cidr: ${cidr}
EOF EOF
} }
@ -921,6 +938,8 @@ run_test() {
prefer_cfg_realm_for_test prefer_cfg_realm_for_test
resolve_krb5_password resolve_krb5_password
KRB5_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}"
if [[ -z "${KRB5_REALM}" || -z "${KRB5_KDC}" || -z "${KRB5_USER}" || -z "${KRB5_PASSWORD}" ]]; then if [[ -z "${KRB5_REALM}" || -z "${KRB5_KDC}" || -z "${KRB5_USER}" || -z "${KRB5_PASSWORD}" ]]; then
err "ERROR: Missing Kerberos configuration. Ensure KRB5_REALM, KRB5_KDC, KRB5_USER, KRB5_PASSWORD are set." err "ERROR: Missing Kerberos configuration. Ensure KRB5_REALM, KRB5_KDC, KRB5_USER, KRB5_PASSWORD are set."
exit 1 exit 1
@ -997,14 +1016,16 @@ run_test() {
if [[ "${KRB5_AD_PORT_FORWARD}" == "1" ]]; then if [[ "${KRB5_AD_PORT_FORWARD}" == "1" ]]; then
test_samba_dns="$effective_kdc" test_samba_dns="$effective_kdc"
fi fi
local kdc_ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}" local kdc_ns
kdc_ns=$(preferred_service_namespace)
run_kerberos_test_loop() { run_kerberos_test_loop() {
local test_attempt=1 local test_attempt=1
local test_max_attempts=2 local test_max_attempts=2
while true; do while true; do
if PROLE_USE_CHILD_REALM="$test_use_child" \ if PROLE_USE_CHILD_REALM="$test_use_child" \
KRB5_REALM="$KRB5_REALM" KRB5_KDC="$effective_kdc" KRB5_ADMIN="$KRB5_ADMIN" \ KRB5_REALM="$KRB5_REALM" KRB5_KDC="$effective_kdc" KRB5_ADMIN="$KRB5_ADMIN" \
KRB5_USER="$KRB5_USER" KRB5_PASSWORD="$KRB5_PASSWORD" \ KRB5_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" KRB5_PASSWORD="$KRB5_PASSWORD" \
SAMBA_ADMIN_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" SAMBA_ADMIN_PASSWORD="$KRB5_PASSWORD" \
SAMBA_DNS_SERVER="$test_samba_dns" \ SAMBA_DNS_SERVER="$test_samba_dns" \
"$SCRIPT_DIR/init_kerberos_test.sh" test; then "$SCRIPT_DIR/init_kerberos_test.sh" test; then
return 0 return 0

View File

@ -244,10 +244,7 @@ _OPENBAO_PF_PID=""
_OPENBAO_PF_STARTED=0 _OPENBAO_PF_STARTED=0
should_skip_deployment() { should_skip_deployment() {
if [[ "${PROLE_MODE:-}" != "k3s" ]]; then # If the current namespace is NOT the primary service namespace,
return 1
fi
# If we are in k3s and the current namespace is NOT the primary service namespace,
# we assume it's a sub-namespace (like prole-db) and we should use the common service. # we assume it's a sub-namespace (like prole-db) and we should use the common service.
if [[ "$NAMESPACE" != "$PRIMARY_SERVICE_NAMESPACE" ]]; then if [[ "$NAMESPACE" != "$PRIMARY_SERVICE_NAMESPACE" ]]; then
return 0 return 0

View File

@ -247,7 +247,11 @@ if [[ -n "$_prole_cfg_file" ]]; then
if [[ -n "$_cfg_ns" ]]; then if [[ -n "$_cfg_ns" ]]; then
export NAMESPACE="$_cfg_ns" export NAMESPACE="$_cfg_ns"
fi fi
unset _cfg_ns _cfg_sns=$(_prole_cfg_extract_key "$_prole_cfg_file" "SERVICE_NAMESPACE")
if [[ -n "$_cfg_sns" ]]; then
export SERVICE_NAMESPACE="$_cfg_sns"
fi
unset _cfg_ns _cfg_sns
fi fi
if [[ -z "${PROLE_HOME:-}" && -d "$_prole_cfg_home_guess" ]]; then if [[ -z "${PROLE_HOME:-}" && -d "$_prole_cfg_home_guess" ]]; then

View File

@ -1713,7 +1713,7 @@ class ProleSilentInstaller(ProleInstallerBase):
inputs["kerberos_config.enabled"] = _bool_str(False) inputs["kerberos_config.enabled"] = _bool_str(False)
inputs["kerberos_config.realm"] = "" inputs["kerberos_config.realm"] = ""
inputs["kerberos_config.kdc"] = "" inputs["kerberos_config.kdc"] = ""
inputs["kerberos_config.user"] = "" inputs["kerberos_config.user"] = "administrator"
inputs["kerberos_config.password"] = "" inputs["kerberos_config.password"] = ""
inputs["kerberos_config.test_connection"] = _bool_str( inputs["kerberos_config.test_connection"] = _bool_str(
DEFAULT_ACTION_FLAGS.get("kerberos_config.test_connection", False) DEFAULT_ACTION_FLAGS.get("kerberos_config.test_connection", False)

View File

@ -596,7 +596,7 @@ class ProleInstaller(
# Kerberos / optional features # Kerberos / optional features
self.kerberos_enabled = tk.BooleanVar(value=False) self.kerberos_enabled = tk.BooleanVar(value=False)
self.kerberos_realm = tk.StringVar() self.kerberos_realm = tk.StringVar()
self.kerberos_user = tk.StringVar() self.kerberos_user = tk.StringVar(value="administrator")
self.kerberos_password = tk.StringVar() self.kerberos_password = tk.StringVar()
self.kerberos_kdc = tk.StringVar() self.kerberos_kdc = tk.StringVar()
self.supabase_enabled = tk.BooleanVar(value=False) self.supabase_enabled = tk.BooleanVar(value=False)

View File

@ -4,6 +4,7 @@ import configparser
import os import os
import shutil import shutil
import subprocess import subprocess
import tempfile
import threading import threading
from pathlib import Path from pathlib import Path
import tkinter as tk import tkinter as tk
@ -15,7 +16,12 @@ from installer.core.env import (
_deployment_target_label, _deployment_target_label,
_ensure_ansible_vault_credentials, _ensure_ansible_vault_credentials,
) )
from installer.config import _decrypt_prole_secret, _is_openbao_ref, _is_prole_secret from installer.config import (
SECRET_KEY_SPECS,
_decrypt_prole_secret,
_is_openbao_ref,
_is_prole_secret,
)
class SecurityScreenMixin: class SecurityScreenMixin:
@ -327,6 +333,86 @@ class SecurityScreenMixin:
ns = os.environ.get("NAMESPACE", "") or "default" ns = os.environ.get("NAMESPACE", "") or "default"
return ns return ns
@staticmethod
def _yaml_scalar(content: str, key: str) -> 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): def _load_secret_cache_from_cfg(self):
try: try:
if self._cfg_path_override is not None: if self._cfg_path_override is not None:
@ -389,6 +475,8 @@ class SecurityScreenMixin:
krb_plain = krb_val krb_plain = krb_val
else: else:
krb_plain = "" krb_plain = ""
if not krb_plain:
krb_plain = self._resolve_samba_admin_password_from_vault()
if krb_plain: if krb_plain:
try: try:
self.kerberos_password.set(krb_plain) self.kerberos_password.set(krb_plain)

View File

@ -2,7 +2,6 @@ apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: openbao name: openbao
namespace: default
spec: spec:
replicas: 1 replicas: 1
selector: selector:
@ -24,7 +23,6 @@ apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: openbao name: openbao
namespace: default
spec: spec:
selector: selector:
app: openbao app: openbao

View File

@ -100,6 +100,23 @@ ensure_namespace_named() {
fi 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() { is_ip_address() {
[[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
} }
@ -411,7 +428,7 @@ sync_prole_kdc_trust() {
return 0 return 0
fi fi
local svc_ns local svc_ns
svc_ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}" svc_ns=$(preferred_service_namespace)
SERVICE_NAMESPACE="$svc_ns" \ SERVICE_NAMESPACE="$svc_ns" \
KRB5_REALM="$KRB5_REALM" \ KRB5_REALM="$KRB5_REALM" \
KRB5_KDC="$KRB5_KDC" \ KRB5_KDC="$KRB5_KDC" \
@ -460,7 +477,7 @@ ensure_krb5_conf_configmap() {
.$domain_val = $realm_val .$domain_val = $realm_val
$domain_val = $realm_val $domain_val = $realm_val
EOF 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" rm -f "$TMP"
} }
@ -705,7 +722,7 @@ apply_ad_forwarder() {
local forward_cmd="${tcp_cmd}${udp_cmd}wait" 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}) ..." log "Applying AD DC forwarder '${KRB5_AD_PROXY_NAME}' in namespace '${KRB5_AD_NAMESPACE}' (target ${kdc_host}) ..."
cat <<EOF | kubectl apply -n "$KRB5_AD_NAMESPACE" -f - cat <<EOF | kubectl apply --validate=false -n "$KRB5_AD_NAMESPACE" -f -
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@ -806,11 +823,11 @@ ensure_kdc_network_allow() {
fi fi
fi fi
cidr="${kdc_host}/32" cidr="${kdc_host}/32"
ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}" ns=$(preferred_service_namespace)
ensure_namespace_named "$ns" ensure_namespace_named "$ns"
log "Ensuring KDC egress policy in namespace '${ns}' ..." log "Ensuring KDC egress policy in namespace '${ns}' ..."
cat <<EOF | kubectl apply -n "$ns" -f - cat <<EOF | kubectl apply --validate=false -n "$ns" -f -
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: NetworkPolicy kind: NetworkPolicy
metadata: metadata:
@ -825,7 +842,7 @@ spec:
egress: egress:
- to: - to:
- ipBlock: - ipBlock:
cidr: 0.0.0.0/0 cidr: ${cidr}
ports: ports:
$(for port in $KRB5_AD_TCP_PORTS; do printf " - protocol: TCP\n port: %s\n" "$port"; done) $(for port in $KRB5_AD_TCP_PORTS; do printf " - protocol: TCP\n port: %s\n" "$port"; done)
$(for port in $KRB5_AD_UDP_PORTS; do printf " - protocol: UDP\n port: %s\n" "$port"; done) $(for port in $KRB5_AD_UDP_PORTS; do printf " - protocol: UDP\n port: %s\n" "$port"; done)
@ -854,7 +871,7 @@ spec:
egress: egress:
- to: - to:
- ipBlock: - ipBlock:
cidr: 0.0.0.0/0 cidr: ${cidr}
EOF EOF
} }
@ -921,6 +938,8 @@ run_test() {
prefer_cfg_realm_for_test prefer_cfg_realm_for_test
resolve_krb5_password resolve_krb5_password
KRB5_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}"
if [[ -z "${KRB5_REALM}" || -z "${KRB5_KDC}" || -z "${KRB5_USER}" || -z "${KRB5_PASSWORD}" ]]; then if [[ -z "${KRB5_REALM}" || -z "${KRB5_KDC}" || -z "${KRB5_USER}" || -z "${KRB5_PASSWORD}" ]]; then
err "ERROR: Missing Kerberos configuration. Ensure KRB5_REALM, KRB5_KDC, KRB5_USER, KRB5_PASSWORD are set." err "ERROR: Missing Kerberos configuration. Ensure KRB5_REALM, KRB5_KDC, KRB5_USER, KRB5_PASSWORD are set."
exit 1 exit 1
@ -997,14 +1016,16 @@ run_test() {
if [[ "${KRB5_AD_PORT_FORWARD}" == "1" ]]; then if [[ "${KRB5_AD_PORT_FORWARD}" == "1" ]]; then
test_samba_dns="$effective_kdc" test_samba_dns="$effective_kdc"
fi fi
local kdc_ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}" local kdc_ns
kdc_ns=$(preferred_service_namespace)
run_kerberos_test_loop() { run_kerberos_test_loop() {
local test_attempt=1 local test_attempt=1
local test_max_attempts=2 local test_max_attempts=2
while true; do while true; do
if PROLE_USE_CHILD_REALM="$test_use_child" \ if PROLE_USE_CHILD_REALM="$test_use_child" \
KRB5_REALM="$KRB5_REALM" KRB5_KDC="$effective_kdc" KRB5_ADMIN="$KRB5_ADMIN" \ KRB5_REALM="$KRB5_REALM" KRB5_KDC="$effective_kdc" KRB5_ADMIN="$KRB5_ADMIN" \
KRB5_USER="$KRB5_USER" KRB5_PASSWORD="$KRB5_PASSWORD" \ KRB5_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" KRB5_PASSWORD="$KRB5_PASSWORD" \
SAMBA_ADMIN_USER="${KERBEROS_TEST_ADMIN_USER:-administrator}" SAMBA_ADMIN_PASSWORD="$KRB5_PASSWORD" \
SAMBA_DNS_SERVER="$test_samba_dns" \ SAMBA_DNS_SERVER="$test_samba_dns" \
"$SCRIPT_DIR/init_kerberos_test.sh" test; then "$SCRIPT_DIR/init_kerberos_test.sh" test; then
return 0 return 0

View File

@ -247,7 +247,11 @@ if [[ -n "$_prole_cfg_file" ]]; then
if [[ -n "$_cfg_ns" ]]; then if [[ -n "$_cfg_ns" ]]; then
export NAMESPACE="$_cfg_ns" export NAMESPACE="$_cfg_ns"
fi fi
unset _cfg_ns _cfg_sns=$(_prole_cfg_extract_key "$_prole_cfg_file" "SERVICE_NAMESPACE")
if [[ -n "$_cfg_sns" ]]; then
export SERVICE_NAMESPACE="$_cfg_sns"
fi
unset _cfg_ns _cfg_sns
fi fi
if [[ -z "${PROLE_HOME:-}" && -d "$_prole_cfg_home_guess" ]]; then if [[ -z "${PROLE_HOME:-}" && -d "$_prole_cfg_home_guess" ]]; then

View File

@ -1 +1 @@
78 81

View File

@ -0,0 +1,191 @@
#!/usr/bin/env bash
# Regression test for etc/init_kerberos.sh in k3s mode with real KDC endpoint.
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROLE_HOME=$(cd "$SCRIPT_DIR/../.." && pwd)
SOURCE_ETC_DIR="$PROLE_HOME/etc"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
WORK_DIR="$TMP_DIR/work"
BIN_DIR="$TMP_DIR/bin"
mkdir -p "$WORK_DIR/etc" "$WORK_DIR/conf" "$BIN_DIR"
cp "$SOURCE_ETC_DIR/init_kerberos.sh" "$WORK_DIR/etc/init_kerberos.sh"
cp "$SOURCE_ETC_DIR/prole_cfg.sh" "$WORK_DIR/etc/prole_cfg.sh"
chmod +x "$WORK_DIR/etc/init_kerberos.sh"
cat <<EOF > "$WORK_DIR/etc/init_kerberos_test.sh"
#!/usr/bin/env bash
exit 0
EOF
chmod +x "$WORK_DIR/etc/init_kerberos_test.sh"
cat <<EOF > "$WORK_DIR/env.sh"
#!/usr/bin/env bash
export PROLE_HOME="$WORK_DIR"
export PROLE_CONF="$WORK_DIR/conf"
EOF
cat <<EOF > "$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 <<YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: prole-krb5-conf
YAML
exit 0
fi
exit 0
;;
apply)
payload="$(cat || true)"
printf '%s\n' "$payload" >> "${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"

View File

@ -12,6 +12,7 @@ from installer.state import InstallerState
from installer.milestone import Milestone, ProgressCallback from installer.milestone import Milestone, ProgressCallback
from installer.build import get_build_command, BuildMilestone from installer.build import get_build_command, BuildMilestone
from installer.core.controller import ProleController from installer.core.controller import ProleController
from installer.core.actions import ProleSilentInstaller
# ===== InstallerState ===== # ===== InstallerState =====
@ -270,6 +271,10 @@ class TestProleController:
assert c.project_root == tmp_path assert c.project_root == tmp_path
assert c.verbose is False assert c.verbose is False
assert c.state is not None 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): def test_init_verbose(self, tmp_path):
c = ProleController(tmp_path, verbose=True) c = ProleController(tmp_path, verbose=True)