fix(installer): k3s --reset path hardening (kdc deploy, no-TTY 1password, context overrides)

Five fixes Junie surfaced while running the kdc-trust-reset-repeatable
Junie brief end-to-end (companion to commit 6f99f95). All hit during
the unattended `install.sh --mode k3s --reset` pipeline.

  - knoe/core/milestones.py (KerberosMilestone):
    For k3s and k3d modes, deploy the KDC pod via `init_kdc.sh start`
    before running init_kerberos.sh. init_kerberos.sh only chains into
    init_kdc.sh when PROLE_KDC_STANDALONE=1; without this hook the
    cluster came up with no KDC pod and the cross-realm trust principals
    had nowhere to land.

  - knoe/milestone.py (Milestone._get_script_env):
    Clear KUBECTL_CONTEXT in addition to KUBECONTEXT so stale entries
    from a different machine's cfg don't override the kubeconfig's
    own current-context.

  - etc/knoe_cfg.sh (_knoe_read_cfg):
    Skip KUBECTL_CONTEXT / KUBE_CONTEXT_NAME / KUBECONTEXT entries when
    reading cfg in k3s mode. Same theme: kubeconfig current-context is
    authoritative.

  - etc/init_1password.sh + knoe/core/onepassword.py:
    When running non-interactively (no TTY on stdin) and no `op`
    session exists, skip rather than hang on `op signin`. Lets the
    unattended pipeline proceed for k3s/k3d where in-cluster secrets
    are managed separately from 1Password.

Co-authored-by: Junie <junie@jetbrains.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chrisfu 2026-05-11 13:34:10 -07:00
parent 6f99f95f84
commit 3f34fa8b32
5 changed files with 49 additions and 4 deletions

View File

@ -38,6 +38,11 @@ _info "op CLI found: $(op --version)"
# ── sign in ────────────────────────────────────────────────────────────────
if ! op whoami >/dev/null 2>&1; then
# If running non-interactively (no TTY), skip rather than hang.
if [[ ! -t 0 ]]; then
_warn "No active 1Password session and no TTY — skipping 1Password preflight."
exit 0
fi
_info "No active 1Password session. Signing in..."
op signin
fi

View File

@ -163,6 +163,10 @@ _knoe_cfg_set_default() {
_knoe_read_cfg() {
local cfg="$1" line key value
# In k3s mode the kubeconfig current-context is authoritative; never set
# KUBECTL_CONTEXT / KUBE_CONTEXT_NAME / KUBECONTEXT from cfg file entries.
local _knoe_read_cfg_mode
_knoe_read_cfg_mode=$(knoe_normalize_mode "${DEPLOYMENT_MODE:-${KNOE_MODE:-${CLUSTER_ENV:-${knoe_mode:-}}}}" 2>/dev/null || true)
while IFS= read -r line || [[ -n "$line" ]]; do
line="$(_knoe_trim "$line")"
[[ -z "$line" ]] && continue
@ -178,6 +182,12 @@ _knoe_read_cfg() {
# Normalize to a valid shell variable key for safe export.
key="$(_knoe_cfg_normalize_key "$key")"
[[ -z "$key" ]] && continue
# k3s: kubeconfig current-context is authoritative; skip context overrides from cfg.
if [[ "$_knoe_read_cfg_mode" == "k3s" ]]; then
case "$key" in
KUBECTL_CONTEXT|KUBE_CONTEXT_NAME|KUBECONTEXT) continue ;;
esac
fi
value="$(_knoe_cfg_expand "$value")"
_knoe_cfg_set_default "$key" "$value"
fi
@ -790,7 +800,7 @@ if [[ -n "$_knoe_cfg_file" ]]; then
;;
esac
fi
if [[ $_override_runtime_ctx -eq 1 ]]; then
if [[ $_override_runtime_ctx -eq 1 && "$_cfg_mode_hint" != "k3s" ]]; then
export KUBE_CONTEXT_NAME="$_cfg_ctx"
export KUBECTL_CONTEXT="$_cfg_ctx"
export KUBECONTEXT="$_cfg_ctx"

View File

@ -1862,6 +1862,21 @@ class KerberosMilestone(Milestone):
if progress:
progress("Initializing Kerberos...", 0.4)
# For k3s and k3d modes, deploy the KDC pod (authority-knoe-auth) via
# init_kdc.sh before running init_kerberos.sh. init_kerberos.sh only
# calls init_kdc.sh when PROLE_KDC_STANDALONE=1; we invoke it directly
# here so the pod is always present regardless of that flag.
if mode in ("k3s", "k3d"):
kdc_args = ["--mode", mode, "start"] if mode else ["start"]
self.logger.info("Deploying KDC pod via init_kdc.sh start ...")
rc_kdc = state.controller.run_script(
"init_kdc.sh", args=kdc_args, env=env, on_line=_stream_line
)
if rc_kdc != 0:
msg = f"init_kdc.sh start failed (code {rc_kdc})"
self.logger.error(msg)
raise Exception(msg)
rc = state.controller.run_script(
"init_kerberos.sh", args=args, env=env, on_line=_stream_line
)

View File

@ -41,7 +41,14 @@ def _op(*args: str, check: bool = True, timeout: int = 15) -> subprocess.Complet
def ensure_op_signed_in() -> None:
"""Ensure the op CLI has an active session; trigger sign-in if not."""
"""Ensure the op CLI has an active session; trigger sign-in if not.
When running non-interactively (no TTY on stdin) and no session exists,
skip rather than hang or crash the installer can proceed without 1Password
for k3s/k3d modes where secrets are managed separately.
"""
import sys
if not op_available():
raise RuntimeError(
"1Password CLI (op) not found. Install: brew install 1password-cli"
@ -52,6 +59,12 @@ def ensure_op_signed_in() -> None:
text=True,
)
if result.returncode != 0:
if not sys.stdin.isatty():
print(
"[WARN] No active 1Password session and no TTY — skipping op signin.",
flush=True,
)
return
subprocess.run(["op", "signin"], check=True)

View File

@ -169,9 +169,11 @@ class Milestone(ABC):
env["KUBECONFIG"] = str(_kc)
break
# k3s kubeconfig's current-context is authoritative (typically "default").
# Clear any stale KUBECONTEXT written from a different machine's config so
# kubectl uses KUBECONFIG without a --context override.
# Clear any stale KUBECONTEXT/KUBECTL_CONTEXT written from a different
# machine's config so kubectl uses KUBECONFIG without a --context override.
env.pop("KUBECONTEXT", None)
env.pop("KUBECTL_CONTEXT", None)
env.pop("KUBE_CONTEXT_NAME", None)
elif mode == "k3d":
# Ensure k3d dev clusters have a resolvable KUBECONFIG.
# The cluster may already be running from a previous session;