Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions aks-node-controller/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,64 @@ the provisioning config in CustomData; the automatic size fallback uses the same
config-only payload. Leaving the new flag false preserves legacy, Phase 2, and
Phase 2.5 behavior.

### Native Azure cloud initialization

When provisioning with `--provision-config` alone, ANC initializes Azure cloud
certificates and RepoDepot in Go before starting the provisioning scripts. This path
does not execute `init-aks-cloud.sh`. NBC takes precedence when `--nbc-cmd` is
supplied, so Phase 2, Phase 2.5, and traditional CSE retain their existing shell
initialization. The shell artifact is still delivered for those paths and older
VHD binaries; this change does not remove it from AgentBaker CustomData.

Regions starting with `ussec` or `usnat` use the legacy certificate endpoint.
Other regions check WireServer's root-certificate opt-in before downloading root
and intermediate certificates. Certificate handling retains the shell behavior:
only a JSON boolean `true` enables opt-in; malformed or non-true opt-in responses
skip installation, while failed or empty opt-in requests stop initialization.
Individual operation-certificate download failures are logged and skipped; the
remaining `.crt` files are installed. Missing operation lists and an absence of
installable certificates fail the opted-in path. Legacy certificate retrieval
errors still fail, but legacy trust-store installation errors are logged without
blocking repository initialization or refresh scheduling.

Downloaded payloads are passed to the system trust-store tools without additional
PEM/X.509 validation. Refresh clears all files, including hidden files, in the flat
certificate staging directory, not existing certificates in the system trust store. Raw certificate
responses are never logged; `AKS_CLOUD_LOG_CERTIFICATES=true` enables diagnostics
containing re-encoded public certificates only. Diagnostic parsing errors do not
block installation.

Eligible nodes require a nonempty region and register a daily 19:00 certificate
refresh using the running ANC binary, including when provisioning uses an ANC
hotfix. Ubuntu, Mariner, and Azure Linux use cron; Flatcar and Azure Container
Linux use the existing `azure-ca-refresh.service` and timer. Existing shell
refresh entries are replaced.
As in the shell, an unreadable crontab is treated as empty. Unexpected read errors
and cron installation failures are logged without stopping initialization;
context cancellation and systemd refresh failures still stop initialization.
The refresh command takes the location explicitly and does not configure
repositories or reinstall its schedule:

```sh
/opt/azure/containers/aks-node-controller refresh-cloud-certificates --location westus2
```

RepoDepot initialization is independent of certificate opt-in and runs only when
a repository endpoint is configured. Ubuntu sources and Microsoft keyrings,
Mariner mirrors, and Azure Linux repositories retain their existing roles;
immutable Flatcar/ACL images do not configure package repositories. Cache refreshes
retry up to ten times and report exhausted failures in logs and repository events
without blocking provisioning, matching the shell's best-effort handling. The
cache operation still reports failure; repository configuration errors and
context cancellation still stop initialization. Native initialization honors
the configured CSE timeout, with 30-second HTTP requests and two-minute command
timeouts. Periodic certificate refresh has a 15-minute budget. `provision --dry-run`
does not fetch certificates, change files, or install refresh schedules.

Native initialization and periodic refresh emit guest-agent events through
`EventLogger` using `CloudInitialization`, `CloudRepositoryInitialization`, and
`CloudCertificateRefresh` task suffixes, alongside the existing ANC logs.

### Validating and normalizing AKSNodeConfig

Before serializing a typed config, producers such as RP should call:
Expand Down
26 changes: 25 additions & 1 deletion aks-node-controller/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ func isExpectedDiffCSEVar(key string) bool {
"SUBNET",
"ROUTE_TABLE",
"VM_TYPE",
"PROXY_VARS":
"PROXY_VARS",
"INIT_AKS_CLOUD_FILEPATH":
return true
}
return false
Expand Down Expand Up @@ -102,6 +103,9 @@ type App struct {
// grpcDialContext overrides how the gRPC LPS client dials, letting tests point the client at
// an in-process (bufconn) server. When nil, the real TLS dial to the apiserver front is used.
grpcDialContext func(ctx context.Context, target string) (net.Conn, error)
// Cloud initialization overrides keep provisioning tests independent of the host.
initializeCloud func(context.Context, cloudInitConfig) error
refreshCloudCertificates func(context.Context, string) error
}

// provision.json values are emitted as strings by the shell jq invocation.
Expand Down Expand Up @@ -181,6 +185,19 @@ func (a *App) Run(ctx context.Context, args []string) int {
return nil
},
},
{
Name: "refresh-cloud-certificates",
Usage: "Refresh Azure cloud CA certificates without configuring repositories",
Flags: []cli.Flag{
&cli.StringFlag{Name: "location", Required: true, Usage: "Azure region used to select the certificate endpoint"},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Len() != 0 {
return fmt.Errorf("unexpected refresh-cloud-certificates arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return a.runCloudCertificateRefresh(ctx, cmd.String("location"))
},
},
{
Name: "download-hotfix",
Usage: "Download the requested hotfix binary",
Expand Down Expand Up @@ -685,6 +702,9 @@ func (a *App) Provision(ctx context.Context, flags ProvisionFlags) (*ProvisionRe
cmd, err = buildCmdFromNBCCmd(ctx, flags.NBCCmd)
} else if flags.ProvisionConfig != "" && flags.NBCCmd == "" {
cmd, err = buildCmdFromProvisionConfig(ctx, flags.ProvisionConfig, a.getGPUComponentsFilePath())
if err == nil {
err = a.initializeAKSCloud(ctx, envSliceToMap(cmd.Env))
}
}
if err != nil {
provisionResult.ExitCode = strconv.Itoa(240)
Expand Down Expand Up @@ -770,6 +790,10 @@ func (a *App) runProvision(ctx context.Context, flags ProvisionFlags, dryRun boo

if dryRun {
a.cmdRun = cmdRunnerDryRun
a.initializeCloud = func(_ context.Context, config cloudInitConfig) error {
slog.Info("dry-run: initialize Azure cloud before provisioning", "location", config.Location)
return nil
}
}
provisionResult, provisionErr = a.Provision(ctx, flags)

Expand Down
1 change: 1 addition & 0 deletions aks-node-controller/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func NewTestApp(t *testing.T, cfg TestAppConfig) *TestApp {
eventLogger: eventLogger,
gpuComponentsFilePath: writeTestGPUComponentsFile(t),
readyReportMarkerPath: filepath.Join(t.TempDir(), "experimental_skip_ready_report"),
initializeCloud: func(context.Context, cloudInitConfig) error { return nil },
},
}
}
Expand Down
Loading
Loading