Skip to content

Commit de7fef2

Browse files
committed
fix: propagate registry auth secret to Fleet workspaces on rotation
The SettingsReconciler wrote the registry basic-auth secret (e.g. ngc-helm-auth) only to cattle-system. The Fleet-workspace copies that HelmOps reference via helmSecretName were written per-workload by ensureFleetAuthSecret, which only runs on an AIWorkload reconcile — not on a key rotation. So a rotated NGC key left fleet-local/fleet-default stale, and gated blueprint installs failed with a 403 reading the helm index. Write the auth secret to all consuming namespaces (cattle-system + fleet-local + fleet-default) so a rotation propagates in one reconcile; the existing Secret watch already triggers it. Delete from all on prune.
1 parent 2d5c88a commit de7fef2

2 files changed

Lines changed: 51 additions & 19 deletions

File tree

aif-operator/internal/controller/settings/settings_controller.go

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -295,10 +295,19 @@ func (r *SettingsReconciler) mirrorGitCredSecret(ctx context.Context, s *aiplatf
295295
)
296296
}
297297

298+
// authSecretNamespaces lists every namespace the operator-managed registry
299+
// basic-auth secret must exist in: cattle-system for ClusterRepo catalog pulls,
300+
// and the Fleet workspaces for HelmOp `helmSecretName` chart pulls. Writing all
301+
// of them here keeps a rotated key in lockstep across copies — the per-workload
302+
// ensureFleetAuthSecret only refreshes the Fleet mirrors on an AIWorkload
303+
// reconcile, which a key rotation does not trigger, so they would otherwise go
304+
// stale and gated HelmOp installs would fail with a 403 reading the index.
305+
var authSecretNamespaces = []string{"cattle-system", "fleet-local", "fleet-default"}
306+
298307
func (r *SettingsReconciler) applyRegistryAuthSecret(
299308
ctx context.Context,
300309
ns string,
301-
cattleSecretName string,
310+
secretName string,
302311
userRef, tokenRef *aiplatformv1alpha1.SecretKeyRef,
303312
) (string, error) {
304313
user, token, ok, err := credentials.ReadPair(ctx, r.Client, ns, userRef, tokenRef)
@@ -309,24 +318,30 @@ func (r *SettingsReconciler) applyRegistryAuthSecret(
309318
return "", nil
310319
}
311320

312-
mirror := &corev1.Secret{
313-
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"},
314-
ObjectMeta: metav1.ObjectMeta{
315-
Name: cattleSecretName,
316-
Namespace: "cattle-system",
317-
},
318-
Type: corev1.SecretTypeBasicAuth,
319-
Data: map[string][]byte{
320-
"username": []byte(user),
321-
"password": []byte(token),
322-
},
323-
}
324-
325-
if err := r.Patch(ctx, mirror, client.Apply, client.ForceOwnership, client.FieldOwner("aif-operator-settings")); err != nil {
326-
return "", fmt.Errorf("apply auth secret %s: %w", cattleSecretName, err)
321+
for _, targetNS := range authSecretNamespaces {
322+
mirror := &corev1.Secret{
323+
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Secret"},
324+
ObjectMeta: metav1.ObjectMeta{
325+
Name: secretName,
326+
Namespace: targetNS,
327+
},
328+
Type: corev1.SecretTypeBasicAuth,
329+
Data: map[string][]byte{
330+
"username": []byte(user),
331+
"password": []byte(token),
332+
},
333+
}
334+
if err := r.Patch(ctx, mirror, client.Apply, client.ForceOwnership, client.FieldOwner("aif-operator-settings")); err != nil {
335+
// The Fleet workspaces are absent on clusters without Fleet; only
336+
// cattle-system is mandatory (the ClusterRepo's clientSecret lives there).
337+
if targetNS != "cattle-system" && errors.IsNotFound(err) {
338+
continue
339+
}
340+
return "", fmt.Errorf("apply auth secret %s/%s: %w", targetNS, secretName, err)
341+
}
327342
}
328343

329-
return cattleSecretName, nil
344+
return secretName, nil
330345
}
331346

332347
func (r *SettingsReconciler) applyClusterRepo(ctx context.Context, name, url, clientSecretName string) error {
@@ -363,8 +378,14 @@ func (r *SettingsReconciler) deleteClusterRepo(ctx context.Context, name string)
363378
// deleteAuthSecret removes a cattle-system basic-auth mirror by name, ignoring
364379
// NotFound. Pairs with deleteClusterRepo when pruning a registry.
365380
func (r *SettingsReconciler) deleteAuthSecret(ctx context.Context, name string) error {
366-
sec := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "cattle-system"}}
367-
return client.IgnoreNotFound(r.Delete(ctx, sec))
381+
var firstErr error
382+
for _, ns := range authSecretNamespaces {
383+
sec := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns}}
384+
if err := client.IgnoreNotFound(r.Delete(ctx, sec)); err != nil && firstErr == nil {
385+
firstErr = err
386+
}
387+
}
388+
return firstErr
368389
}
369390

370391
func (r *SettingsReconciler) reconcileClusterRepos(ctx context.Context, s *aiplatformv1alpha1.Settings) error {

aif-operator/internal/controller/settings/settings_controller_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,17 @@ func TestSettingsController_WiresWellKnownSecretsAndCreatesClusterRepos(t *testi
386386
t.Errorf("nvidia-blueprint clientSecret = %q, want %q", nvSecret, credentials.AuthSecretNvidia)
387387
}
388388

389+
// ngc-helm-auth must be mirrored to every consuming namespace so a rotated
390+
// key propagates without needing an AIWorkload reconcile: cattle-system (the
391+
// ClusterRepo's clientSecret) plus the Fleet workspaces (HelmOp
392+
// helmSecretName). Regression guard for the stale-fleet-mirror bug.
393+
for _, ns := range []string{"cattle-system", "fleet-local", "fleet-default"} {
394+
authSec := &corev1.Secret{}
395+
if err := c.Get(context.Background(), types.NamespacedName{Name: credentials.AuthSecretNvidia, Namespace: ns}, authSec); err != nil {
396+
t.Errorf("expected %s in namespace %s: %v", credentials.AuthSecretNvidia, ns, err)
397+
}
398+
}
399+
389400
// The public NGC charts catalog must be ANONYMOUS (no clientSecret).
390401
// Presenting a key not entitled to the full /nvidia path makes NGC return
391402
// 403 (surfaced by Rancher as "no API version specified"); anonymous access

0 commit comments

Comments
 (0)