Repository navigation
fix(security): restrict node secret file perms and avoid logging AD realm password - #9776
Calvin S. (calvin197) wants to merge 6 commits into
Conversation
…ealm password Addresses Mythos findings tracked under ADO 37518878: - 37518906/911/915: bootstrap-kubeconfig embeds the TLS bootstrap token but was written world-readable (chmod 0644). Restrict to 0600 so unprivileged local users can't read the token. - 37518920/916: the kubelet client private key (/etc/kubernetes/certs/client.key) was written with no explicit mode (umask-dependent, typically 0644). touch + chmod 0600 before writing so it's never world-readable. - 37518908: setup-custom-search-domains.sh runs under `set -x`, echoing the AD realm join password into provisioning logs. Wrap the realm join in `set +x` / `set -x`. Verified: go test ./pkg/agent/... and aks-node-controller ./parser/... pass; bash -n on all three scripts; no ShellSpec assertions reference the changed lines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 350c2577-7031-4d0d-adc1-e99d053fc981
Windows Unit Test Results 3 files 18 suites 1m 13s ⏱️ Results for commit 8ac3817. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
🟡 Changes recommended
Restoring tracing masks realm-join failures, and the new security invariants lack required ShellSpec coverage.
3 open findings
What changed in this PR
Hardens Linux node provisioning against secret exposure.
Changes:
- Restricts kubelet private key and bootstrap kubeconfig permissions.
- Suppresses tracing while passing the AD realm password.
| File | Description |
|---|---|
setup-custom-search-domains.sh |
Disables tracing around realm join. |
cse_config.sh |
Sets client key permissions to 0600. |
cse_config_kubelet.sh |
Sets bootstrap kubeconfig permissions to 0600. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # client.key is the kubelet client private key; restrict to root before writing | ||
| touch /etc/kubernetes/certs/client.key | ||
| chmod 0600 /etc/kubernetes/certs/client.key |
…-secret-hardening # Conflicts: # parts/linux/cloud-init/artifacts/cse_config_kubelet.sh Co-authored-by: calvin197 <44663669+calvin197@users.noreply.github.com>
Co-authored-by: calvin197 <44663669+calvin197@users.noreply.github.com>
Co-authored-by: calvin197 <44663669+calvin197@users.noreply.github.com>
| command mkdir -p "$(dirname "${CLIENT_KEY_PATH}")" | ||
| command rm -f "${CLIENT_KEY_PATH}" |
There was a problem hiding this comment.
Copilot pushed some changes, looks good now?
There was a problem hiding this comment.
Not quite. configureK8s still ignores failures from touch and chmod, then proceeds to write the key. If chmod fails on an existing loosely-permissioned file but writing succeeds, the key can remain readable. Guard both operations and return before writing; the spec should also cover creation-time mode and failure behavior. The realm-join status/logging fix and isolated test path look good, and both focused ShellSpec suites pass.
There was a problem hiding this comment.
Copilot pushed some changes, looks good now?
This reverts commit 32b2d80.
…om touching real host path Addresses two issues flagged by Copilot's PR review on #9776: - cse_config.sh: touch+chmod left a brief window where client.key was world-readable (default umask 022) before chmod tightened it to 0600. A process with an already-open fd from that window retains read access even after chmod. Create the file under a scoped umask 077 so it is never world-readable, and keep the chmod to also cover pre-existing files. Path is now overridable via $CLIENT_KEY_PATH for testing. - cse_config_spec.sh: the new client-key permissions test deleted and overwrote the real /etc/kubernetes/certs/client.key path. Per spec/README.md, ShellSpec can be run natively as root instead of in the recommended container, which would destroy a real node's kubelet credential with no restoration. Test now uses a throwaway mktemp dir. Verified locally: bash -n on both files, and the full cse_config_spec.sh ShellSpec suite (modern bash 5.3 + GNU stat, matching CI) - client-key test passes, confirmed /etc/kubernetes/certs/client.key is never touched by the test run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 350c2577-7031-4d0d-adc1-e99d053fc981
…chmod failures; stop test from touching real host path Addresses issues flagged by Copilot's PR review on #9776: - cse_config.sh: touch+chmod left a brief window where client.key was world-readable (default umask 022) before chmod tightened it to 0600. Create the file under a scoped umask 077 so it's never world-readable at creation time (no TOCTOU window), and keep chmod to also cover pre-existing files. - Guard both touch and chmod explicitly with '|| return 1': this repo's CSE provisioning scripts do NOT run under 'set -e' (verified: neither cse_main.sh nor cse_config.sh enables errexit at the top level; error handling here is manual, via '<cmd> || exit $ERR_X' throughout this file). Without the explicit guard, a chmod failure on a pre-existing, loosely-permissioned client.key would be silently ignored and the private key would be written into that file with its stale, possibly world-readable permissions. Empirically reproduced this exact failure mode locally before the fix, and confirmed it's closed after. - cse_config_spec.sh: the client-key permissions test deleted and overwrote the real /etc/kubernetes/certs/client.key path. Per spec/README.md, ShellSpec can be run natively as root instead of in the recommended container, which would destroy a real node's kubelet credential with no restoration. Test now uses a throwaway mktemp dir. Added coverage for creation-time mode (umask effect, independent of chmod) and for both touch- and chmod-failure paths. Path is now overridable via $CLIENT_KEY_PATH for testing. Verified locally: bash -n on both files, and the full cse_config_spec.sh ShellSpec suite (modern bash 5.3 + GNU stat, matching CI) - all 4 client-key tests pass; only 2 pre-existing, unrelated configureAzureJson failures remain (confirmed present on this branch before this commit, in a different function untouched by this change). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 350c2577-7031-4d0d-adc1-e99d053fc981
06e35e0 to
8ac3817
Compare
|



What this PR does / why we need it:
Hardens three node-provisioning scripts so secrets aren't exposed via file permissions or provisioning logs. Addresses Mythos security findings (tracked under ADO 37518878):
• bootstrap-kubeconfig (ADO 37518906/911/915) — /var/lib/kubelet/bootstrap-kubeconfig embeds the TLS bootstrap token but was written world-readable ( chmod 0644 ). Tightened to chmod 0600 so unprivileged local users can't read the token.
• kubelet client key (ADO 37518920/916) — /etc/kubernetes/certs/client.key (kubelet client private key) was written with no explicit mode (umask-dependent, typically 0644). Now touch + chmod 0600 before writing, so there's never a world-readable window.
• AD realm password in logs (ADO 37518908) — setup-custom-search-domains.sh runs under set -x , which echoed the AD realm-join password into provisioning logs. Wrapped the realm join in set +x / set -x .
No functional behavior change — only file modes and log-tracing scope. Validated with go test ./pkg/agent/... and aks-node-controller ./parser/... (both pass), bash -n on all three scripts, and confirmed no ShellSpec assertions reference the changed lines.
Which issue(s) this PR fixes:
Fixes #