Skip to content

fix(windows): write TLS bootstrap-config in NodePrep, not BasePrep#8795

Merged
benjamin-brady merged 7 commits into
mainfrom
bbrady/windows-pis-bootstrap-token
Jul 1, 2026
Merged

fix(windows): write TLS bootstrap-config in NodePrep, not BasePrep#8795
benjamin-brady merged 7 commits into
mainfrom
bbrady/windows-pis-bootstrap-token

Conversation

@benjamin-brady

@benjamin-brady benjamin-brady commented Jun 30, 2026

Copy link
Copy Markdown
Member

Work item: AB#38630884

Summary

Fixes AB#38630884: PIS / VHD-caching Windows nodes fail to register with cannot create certificate signing request: Unauthorized.

Root cause

On PIS/VHD-cached clusters the Windows VHD is baked from a temporary VMSS with TLS bootstrapping enabled. During the bake, BasePrep writes c:\k\bootstrap-config with the bake-time TLS bootstrap token and drops a C:\AzureData\base_prep.complete marker — both captured into the cached VHD.

On the real node the CSE skips BasePrep because that marker exists, so bootstrap-config keeps the stale baked token instead of the fresh $global:TLSBootstrapToken delivered via CustomData. The apiserver then rejects the kubelet's first CSR as Unauthorized.

The bootstrap-config write was simply left in BasePrep when the script was split into BasePrep/NodePrep (commit 63b9d9b9, #6601).

Fix

Move Write-BootstrapKubeConfig from BasePrep into NodePrep, before Install-KubernetesServices. kubelet (installed/started by Install-KubernetesServices) is the only consumer of bootstrap-config, so writing it in NodePrep guarantees the live token is used on every real provision.

  • No VHD rebake required — the Windows CSE script is rendered fresh into CustomData by the RP on every provision; only the on-disk bootstrap-config file is baked. The corrected NodePrep write takes effect on new nodes from existing cached VHDs.
  • A move (not keep-and-refresh) is correctBasePrep never starts kubelet nor connects to the API server, so nothing there consumes the token-based bootstrap-config. Calico in BasePrep uses the separate cert-based config from Write-KubeConfig, which is unchanged.

Validation

  • go test ./pkg/agent/... passes; make generate-testdata produced no snapshot drift.
  • Suggested manual validation (from the work item):
    • Provision a Windows node from a PIS cached VHD; confirm c:\k\bootstrap-config token == $global:TLSBootstrapToken and the node reaches Ready.
    • Rotate the agent pool TLS bootstrap token, provision again from the same cached VHD, and confirm the node still joins.

Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com

PIS/VHD-cached Windows nodes fail to register because the on-disk
bootstrap kubeconfig (c:\k\bootstrap-config) holds the stale bake-time
TLS bootstrap token. BasePrep wrote bootstrap-config, but it is skipped
on cached nodes (base_prep.complete marker), so the live token from
$global:TLSBootstrapToken never overwrote the baked one and the apiserver
rejected the kubelet CSR as Unauthorized.

kubelet, installed/started by Install-KubernetesServices in NodePrep, is
the only consumer of bootstrap-config. Move Write-BootstrapKubeConfig
from BasePrep into NodePrep (before Install-KubernetesServices) so it is
written from the live token on every real provision. No VHD rebake is
required since the CSE script is rendered fresh into CustomData each boot.

The Calico setup in BasePrep uses the separate cert-based 'config' from
Write-KubeConfig, which is unchanged, so BasePrep keeps working.

Work item: 38630884

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Windows node registration failure for PIS / VHD-cached clusters where a stale, bake-time TLS bootstrap token in c:\k\bootstrap-config causes kubelet CSR requests to be rejected as Unauthorized. The fix aligns bootstrap-config generation with the actual provisioning phase so the live $global:TLSBootstrapToken from CustomData is always used when kubelet starts.

Changes:

  • Move Write-BootstrapKubeConfig from BasePrep to NodePrep.
  • Ensure bootstrap-config is written before Install-KubernetesServices (i.e., before kubelet is started) during real node provisioning.

benjamin-brady and others added 2 commits June 30, 2026 14:21
Same PIS/VHD-cache class of bug as the bootstrap-config move. BasePrep
wrote c:\k\config (the cert-based kubeconfig from Write-KubeConfig) with
bake-time cluster material (CA, server, AgentCertificate). BasePrep is
skipped on cached nodes, so on a PIS node config keeps the bake cluster's
values and is never refreshed.

config is only consumed in NodePrep:
  - Calico's GetCalicoKubeConfig (Start-InstallCalico) uses it to reach
    the API server, then it is removed at the end of NodePrep; and
  - in legacy cert-auth mode (no token, no secure bootstrap) it is
    kubelet's own kubeconfig.

Move Write-KubeConfig (and its temporary/permanent log block) into
NodePrep, before Install-KubernetesServices, so it is written from the
live values on every real provision. No consumer of config exists in
BasePrep, so the move is safe; the existing conditional removal at the
end of NodePrep is unchanged.

Work item: 38630884

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Trim the verbose per-kubeconfig comment to a short rationale so it is less
likely to drift from the code as consumers change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 30, 2026 02:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread parts/windows/kuberneteswindowssetup.ps1 Outdated
benjamin-brady and others added 2 commits June 30, 2026 15:34
Add a teeth-y regression guard for the BasePrep->NodePrep kubeconfig fix.
The VHDCaching legacy-TLS-bootstrap scenario now injects a stale sentinel
bootstrap token at the BAKE (pre-provision) stage only, while the real
cluster token is used at provision time. If bootstrap-config were written
in BasePrep (the bug), the cached VHD would carry the stale token and the
node would fail to register; because it is written in NodePrep, the live
token wins. The validator asserts bootstrap-config contains the live token
and never the sentinel.

Adds Config.PreProvisionBootstrapConfigMutator, a bake-stage-only hook,
wired into runScenarioWithPreProvision.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 30, 2026 07:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread e2e/scenario_win_test.go
Move remaining cluster-specific Windows config writes to NodePrep so PIS cached VHDs do not bake stale kubeconfig, cert, azurestack, or azure.json data. NodePrep regenerates the files from live CustomData before runtime consumers start.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 30, 2026 23:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

…compat

The CSE scripts package cached on the VHD (currently v0.0.52, pinned via
windowsCSEScriptsPackageURL) configures containerd during BasePrep and reads
the pause image from C:\k\kubeclusterconfig.json. Moving Write-KubeClusterConfig
entirely to NodePrep meant the file did not exist when the cached containerd
logic ran, failing CSE with 'Cannot find path C:\k\kubeclusterconfig.json' on
both cached and non-cached Windows nodes (a new-CSE / old-VHD-scripts skew).

Write kubeclusterconfig.json in BasePrep again so the cached scripts find it,
while still rewriting it from live CustomData in NodePrep so PIS/VHD-cached
nodes (which skip BasePrep) refresh the cluster-specific values.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@benjamin-brady benjamin-brady force-pushed the bbrady/windows-pis-bootstrap-token branch from ff17e16 to 46181f2 Compare June 30, 2026 23:39
@benjamin-brady benjamin-brady enabled auto-merge (squash) July 1, 2026 00:48
@benjamin-brady

Copy link
Copy Markdown
Member Author

The E2E tests that cover windows and windows VHD caching passed.

@benjamin-brady benjamin-brady merged commit 1a0c644 into main Jul 1, 2026
31 of 32 checks passed
@benjamin-brady benjamin-brady deleted the bbrady/windows-pis-bootstrap-token branch July 1, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants