From f0a59d1db35ff466f40c758ac69536e6a6218c0e Mon Sep 17 00:00:00 2001 From: Aryan Puttur Date: Fri, 15 May 2026 16:31:48 -0400 Subject: [PATCH 1/3] Skip router ConfigMap entries when TLS credential secrets are missing Fixes #2433 --- internal/kube/secrets/manager.go | 15 ++++ internal/kube/site/attached_connector.go | 3 + internal/kube/site/extended_bindings.go | 10 +++ internal/kube/site/site.go | 94 +++++++++++++++++++++++- internal/site/bindings.go | 35 +++++++++ internal/site/routeraccess.go | 20 ++++- internal/site/routeraccess_test.go | 24 ++++++ 7 files changed, 195 insertions(+), 6 deletions(-) diff --git a/internal/kube/secrets/manager.go b/internal/kube/secrets/manager.go index 040741861..46d30c5ba 100644 --- a/internal/kube/secrets/manager.go +++ b/internal/kube/secrets/manager.go @@ -241,6 +241,21 @@ func (w *ProfilesWatcher) keyfunc(name string) string { return w.namespace + "/" + name } +// TlsCredentialSecretPresent reports whether a TLS secret for the credential is in the +// ProfilesWatcher informer cache (same source kube-adaptor uses for ssl profiles). +func (w *ProfilesWatcher) TlsCredentialSecretPresent(credentialName string) bool { + if credentialName == "" || w.Cache == nil { + return false + } + for _, secretName := range profileSecrets(credentialName) { + secret, err := w.Cache.Get(w.keyfunc(secretName)) + if err == nil && secret != nil && secret.Type == corev1.SecretTypeTLS { + return true + } + } + return false +} + func (w *ProfilesWatcher) checkPriorValidity(secret *corev1.Secret) uint64 { result := w.pvProvider.TLSPriorValidRevisions() if secret.ObjectMeta.Annotations == nil { diff --git a/internal/kube/site/attached_connector.go b/internal/kube/site/attached_connector.go index bbe7b7bbc..757eee62d 100644 --- a/internal/kube/site/attached_connector.go +++ b/internal/kube/site/attached_connector.go @@ -304,6 +304,9 @@ func (a *AttachedConnector) updateBridgeConfig(siteId string, config *qdr.Bridge if definition == nil || a.watcher == nil { return updated } + if definition.Spec.TlsCredentials != "" && !a.parent.bindings.TlsCredentialIncluded(definition.Spec.TlsCredentials) { + return updated + } connector := &skupperv2alpha1.Connector{ ObjectMeta: metav1.ObjectMeta{ Name: definition.Name, diff --git a/internal/kube/site/extended_bindings.go b/internal/kube/site/extended_bindings.go index d283fec79..2af79bd65 100644 --- a/internal/kube/site/extended_bindings.go +++ b/internal/kube/site/extended_bindings.go @@ -406,6 +406,9 @@ func (b *ExtendedBindings) Apply(config *qdr.RouterConfig) bool { } } for _, ptl := range b.perTargetListeners { + if ptl.definition.Spec.TlsCredentials != "" && !b.bindings.TlsCredentialIncluded(ptl.definition.Spec.TlsCredentials) { + continue + } if ptl.updateBridgeConfig(b.bindings.SiteId, &desired) { updated = true } @@ -425,6 +428,9 @@ func (b *ExtendedBindings) Apply(config *qdr.RouterConfig) bool { func (b *ExtendedBindings) AddSslProfiles(config *qdr.RouterConfig, definitions map[string]*skupperv2alpha1.AttachedConnector) bool { profiles := map[string]qdr.SslProfile{} for _, c := range definitions { + if c.Spec.TlsCredentials != "" && !b.bindings.TlsCredentialIncluded(c.Spec.TlsCredentials) { + continue + } if c.Spec.TlsCredentials != "" { if !c.Spec.UseClientCert { //if only ca is used, need to qualify the profile to ensure that it does not collide with @@ -451,6 +457,7 @@ func (b *ExtendedBindings) AddSslProfiles(config *qdr.RouterConfig, definitions func (b *ExtendedBindings) SetSite(site *Site) { b.bindings.SetSiteId(site.site.GetSiteId()) + b.bindings.SetTlsSecretAllowed(site.tlsCredentialSecretPresent) b.site = site } @@ -527,6 +534,9 @@ func (b *ExtendedBindings) attachedConnectorUnreferenced(namespace string, name func (b *ExtendedBindings) networkUpdated(network []skupperv2alpha1.SiteRecord) qdr.ConfigUpdate { changed := false for _, ptl := range b.perTargetListeners { + if ptl.definition.Spec.TlsCredentials != "" && !b.bindings.TlsCredentialIncluded(ptl.definition.Spec.TlsCredentials) { + continue + } update, err := ptl.extractTargets(network, b.mapping, b.exposed, b.context) if err != nil { if err := b.site.updateListenerStatus(ptl.definition, err); err != nil { diff --git a/internal/kube/site/site.go b/internal/kube/site/site.go index 8742b58eb..695238d57 100644 --- a/internal/kube/site/site.go +++ b/internal/kube/site/site.go @@ -90,7 +90,7 @@ func NewSite(namespace string, eventProcessor *watchers.EventProcessor, certs ce site.profiles = secrets.NewProfilesWatcher( sslSecretsWatcher(namespace, eventProcessor), eventProcessor.GetKubeClient(), - site.updateRouterConfig, + site.reconcileAfterTlsSecretChange, site, namespace, logger.With( @@ -736,6 +736,72 @@ func (s *Site) ownerReferences() []metav1.OwnerReference { } } +// tlsCredentialSecretPresent reports whether a TLS credential secret is visible in the +// ProfilesWatcher cache (not a live API GET). +func (s *Site) tlsCredentialSecretPresent(secretName string) bool { + if s.profiles == nil { + return false + } + return s.profiles.TlsCredentialSecretPresent(secretName) +} + +// eligibleLinksConfig applies only links whose TLS credential secrets are present. +type eligibleLinksConfig struct { + site *Site +} + +func (e *eligibleLinksConfig) Apply(config *qdr.RouterConfig) bool { + changed := false + eligible := map[string]struct{}{} + for name, link := range e.site.links { + d := link.Definition() + if d == nil { + continue + } + if d.Spec.TlsCredentials != "" && !e.site.tlsCredentialSecretPresent(d.Spec.TlsCredentials) { + continue + } + eligible[name] = struct{}{} + if link.Apply(config) { + changed = true + } + } + // Remove only connectors owned by links that are ineligible (e.g. missing TLS secret). + // Do not use site.LinkMap.Apply: its cleanup removes every non-auto-mesh connector not in the map, + // which would strip inter-router and other non-link connectors. + for name, link := range e.site.links { + if _, ok := eligible[name]; ok { + continue + } + d := link.Definition() + if d != nil && d.Spec.TlsCredentials != "" && !e.site.tlsCredentialSecretPresent(d.Spec.TlsCredentials) { + if site.NewRemoveConnector(name).Apply(config) { + changed = true + } + } + } + return changed +} + +// reconcileAfterTlsSecretChange reapplies desired router configuration when TLS-related secrets change, +// so resources that were omitted while a secret was missing are added once it exists. +func (s *Site) reconcileAfterTlsSecretChange(pw qdr.ConfigUpdate) error { + groups := s.groups() + for i, group := range groups { + op := ConfigUpdateList{ + s.bindings, + s, + s.linkAccess.DesiredConfigAllowingTlsSecrets(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent), + &eligibleLinksConfig{site: s}, + pw, + } + if err := s.updateRouterConfigForGroup(op, group); err != nil { + return err + } + } + return nil +} + func (s *Site) recoverRouterConfig(update bool) ([]*qdr.RouterConfig, error) { list, err := s.clients.GetKubeClient().CoreV1().ConfigMaps(s.namespace).List(context.TODO(), metav1.ListOptions{ LabelSelector: "internal.skupper.io/router-config", @@ -770,7 +836,7 @@ func (s *Site) recoverRouterConfig(update bool) ([]*qdr.RouterConfig, error) { for i, group := range groups { if config, ok := byName[group]; ok { if update { - op := ConfigUpdateList{s.bindings, s, s.linkAccess.DesiredConfig(groups[:i], SSL_PROFILE_PATH)} + op := ConfigUpdateList{s.bindings, s, s.linkAccess.DesiredConfigAllowingTlsSecrets(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent)} if err := kubeqdr.UpdateRouterConfig(s.clients.GetKubeClient(), group, s.namespace, context.TODO(), op, s.labelling); err != nil { s.logger.Error("Failed to update router config map", slog.String("namespace", s.namespace), @@ -783,7 +849,7 @@ func (s *Site) recoverRouterConfig(update bool) ([]*qdr.RouterConfig, error) { } else { routerConfig := s.initialRouterConfig() s.bindings.Apply(routerConfig) - s.linkAccess.DesiredConfig(groups[:i], SSL_PROFILE_PATH).Apply(routerConfig) + s.linkAccess.DesiredConfigAllowingTlsSecrets(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent).Apply(routerConfig) if err := s.createRouterConfigForGroup(group, routerConfig); err != nil { s.logger.Error("Failed to create router config map", slog.String("namespace", s.namespace), @@ -1176,6 +1242,14 @@ func (s *Site) link(linkconfig *skupperv2alpha1.Link) error { config.UpdateProxyConfig(currentProxyConfig) } } + if linkconfig.Spec.TlsCredentials != "" && !s.tlsCredentialSecretPresent(linkconfig.Spec.TlsCredentials) { + s.logger.Info("Deferring link router configuration until TLS credentials secret exists", + slog.String("namespace", s.namespace), + slog.String("link", linkconfig.Name), + slog.String("secret", linkconfig.Spec.TlsCredentials), + ) + return s.updateLinkConfiguredCondition(linkconfig, fmt.Errorf("TLS credentials secret %q not found", linkconfig.Spec.TlsCredentials)) + } err := s.updateRouterConfig(config) return s.updateLinkConfiguredCondition(linkconfig, err) } else { @@ -1531,12 +1605,21 @@ func (s *Site) CheckRouterAccess(name string, la *skupperv2alpha1.RouterAccess) if !s.initialised { return nil } + var configuredErr error + if la != nil && la.Spec.TlsCredentials != "" && !s.tlsCredentialSecretPresent(la.Spec.TlsCredentials) { + configuredErr = fmt.Errorf("TLS credentials secret %q not found", la.Spec.TlsCredentials) + s.logger.Info("Deferring RouterAccess router configuration until TLS credentials secret exists", + slog.String("namespace", s.namespace), + slog.String("routerAccess", la.Name), + slog.String("secret", la.Spec.TlsCredentials), + ) + } var previousGroups []string groups := s.groups() var errors []string for i, group := range groups { if specChanged || !la.IsConfigured() { - if err := s.updateRouterConfigForGroup(s.linkAccess.DesiredConfig(previousGroups, SSL_PROFILE_PATH), group); err != nil { + if err := s.updateRouterConfigForGroup(s.linkAccess.DesiredConfigAllowingTlsSecrets(previousGroups, SSL_PROFILE_PATH, s.tlsCredentialSecretPresent), group); err != nil { s.logger.Error("Error updating router config", slog.String("namespace", s.namespace), slog.Any("error", err)) @@ -1568,6 +1651,9 @@ func (s *Site) CheckRouterAccess(name string, la *skupperv2alpha1.RouterAccess) if len(errors) > 0 { err = fmt.Errorf("%s", strings.Join(errors, ", ")) } + if configuredErr != nil { + err = stderrors.Join(configuredErr, err) + } if la != nil && la.SetConfigured(err) { if err := s.updateRouterAccessStatus(la); err != nil { return err diff --git a/internal/site/bindings.go b/internal/site/bindings.go index 72da8de09..1a2ea2cca 100644 --- a/internal/site/bindings.go +++ b/internal/site/bindings.go @@ -29,6 +29,7 @@ type Bindings struct { listeners map[string]*skupperv2alpha1.Listener multiKeyListeners map[string]*skupperv2alpha1.MultiKeyListener handler BindingEventHandler + tlsSecretAllowed func(secretName string) bool configure struct { listener ListenerConfiguration connector ConnectorConfiguration @@ -53,6 +54,22 @@ func (b *Bindings) SetSiteId(siteId string) { b.SiteId = siteId } +func (b *Bindings) SetTlsSecretAllowed(f func(string) bool) { + b.tlsSecretAllowed = f +} + +// TlsCredentialIncluded reports whether TLS material for the given secret name may be +// referenced in generated router configuration. Empty name always returns true. +func (b *Bindings) TlsCredentialIncluded(secretName string) bool { + if secretName == "" { + return true + } + if b.tlsSecretAllowed == nil { + return true + } + return b.tlsSecretAllowed(secretName) +} + func (b *Bindings) SetListenerConfiguration(configuration ListenerConfiguration) { b.configure.listener = configuration } @@ -231,12 +248,21 @@ func (b *Bindings) ToBridgeConfig() qdr.BridgeConfig { ListenerAddresses: qdr.ListenerAddressMap{}, } for _, c := range b.connectors { + if c.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(c.Spec.TlsCredentials) { + continue + } b.configure.connector(b.SiteId, c, &config) } for _, l := range b.listeners { + if l.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(l.Spec.TlsCredentials) { + continue + } b.configure.listener(b.SiteId, l, &config) } for _, mkl := range b.multiKeyListeners { + if mkl.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(mkl.Spec.TlsCredentials) { + continue + } b.configure.multiKeyListener(b.SiteId, mkl, &config) } @@ -246,6 +272,9 @@ func (b *Bindings) ToBridgeConfig() qdr.BridgeConfig { func (b *Bindings) AddSslProfiles(config *qdr.RouterConfig) bool { profiles := map[string]qdr.SslProfile{} for _, c := range b.connectors { + if c.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(c.Spec.TlsCredentials) { + continue + } if c.Spec.TlsCredentials != "" { if !c.Spec.UseClientCert { //if only ca is used, need to qualify the profile to ensure that it does not collide with @@ -262,11 +291,17 @@ func (b *Bindings) AddSslProfiles(config *qdr.RouterConfig) bool { } } for _, l := range b.listeners { + if l.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(l.Spec.TlsCredentials) { + continue + } if _, ok := profiles[l.Spec.TlsCredentials]; l.Spec.TlsCredentials != "" && !ok { profiles[l.Spec.TlsCredentials] = qdr.ConfigureSslProfile(l.Spec.TlsCredentials, b.ProfilePath, true) } } for _, mkl := range b.multiKeyListeners { + if mkl.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(mkl.Spec.TlsCredentials) { + continue + } if _, ok := profiles[mkl.Spec.TlsCredentials]; mkl.Spec.TlsCredentials != "" && !ok { profiles[mkl.Spec.TlsCredentials] = qdr.ConfigureSslProfile(mkl.Spec.TlsCredentials, b.ProfilePath, true) } diff --git a/internal/site/routeraccess.go b/internal/site/routeraccess.go index ea312d9ef..027082d61 100644 --- a/internal/site/routeraccess.go +++ b/internal/site/routeraccess.go @@ -60,9 +60,25 @@ func (m RouterAccessMap) findInterRouterRole() (*skupperv2alpha1.RouterAccessRol } func (m RouterAccessMap) DesiredConfig(targetGroups []string, profilePath string) *RouterAccessConfig { + return m.DesiredConfigAllowingTlsSecrets(targetGroups, profilePath, nil) +} + +// DesiredConfigAllowingTlsSecrets is like DesiredConfig but skips RouterAccess entries whose +// spec.tlsCredentials is set when tlsSecretAllowed is non-nil and returns false for that name. +func (m RouterAccessMap) DesiredConfigAllowingTlsSecrets(targetGroups []string, profilePath string, tlsSecretAllowed func(string) bool) *RouterAccessConfig { + source := m + if tlsSecretAllowed != nil { + source = make(RouterAccessMap, len(m)) + for k, ra := range m { + if ra.Spec.TlsCredentials != "" && !tlsSecretAllowed(ra.Spec.TlsCredentials) { + continue + } + source[k] = ra + } + } return &RouterAccessConfig{ - listeners: m.desiredListeners(), - connectors: m.desiredConnectors(targetGroups), + listeners: source.desiredListeners(), + connectors: source.desiredConnectors(targetGroups), profilePath: profilePath, } } diff --git a/internal/site/routeraccess_test.go b/internal/site/routeraccess_test.go index a32b3ac61..e416217b6 100644 --- a/internal/site/routeraccess_test.go +++ b/internal/site/routeraccess_test.go @@ -453,3 +453,27 @@ func TestRouterAccessMap_DesiredConfig(t *testing.T) { }) } } + +func TestRouterAccessMap_DesiredConfigAllowingTlsSecrets(t *testing.T) { + ra := &skupperv2alpha1.RouterAccess{ + ObjectMeta: v1.ObjectMeta{Name: "ra", Namespace: "ns"}, + Spec: skupperv2alpha1.RouterAccessSpec{ + TlsCredentials: "missing-secret", + BindHost: "0.0.0.0", + Roles: []skupperv2alpha1.RouterAccessRole{ + {Name: "inter-router", Port: 55671}, + }, + }, + } + m := RouterAccessMap{"ra": ra} + allow := func(name string) bool { return name != "missing-secret" } + got := m.DesiredConfigAllowingTlsSecrets([]string{"g1"}, "/certs", allow) + if len(got.listeners) != 0 || len(got.connectors) != 0 { + t.Fatalf("expected empty desired when TLS secret disallowed, got listeners=%d connectors=%d", + len(got.listeners), len(got.connectors)) + } + got2 := m.DesiredConfigAllowingTlsSecrets([]string{"g1"}, "/certs", func(string) bool { return true }) + if len(got2.listeners) == 0 { + t.Fatal("expected listeners when TLS secret allowed") + } +} From dddb71aa4bb057a85b08110f6608e9cb747182b4 Mon Sep 17 00:00:00 2001 From: Aryan Puttur Date: Wed, 27 May 2026 14:25:05 -0400 Subject: [PATCH 2/3] Changed field names --- internal/kube/site/attached_connector.go | 2 +- internal/kube/site/extended_bindings.go | 8 ++--- internal/kube/site/site.go | 8 ++--- internal/site/bindings.go | 42 ++++++++++++------------ internal/site/routeraccess.go | 12 +++---- internal/site/routeraccess_test.go | 6 ++-- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/internal/kube/site/attached_connector.go b/internal/kube/site/attached_connector.go index 757eee62d..1d7aa4016 100644 --- a/internal/kube/site/attached_connector.go +++ b/internal/kube/site/attached_connector.go @@ -304,7 +304,7 @@ func (a *AttachedConnector) updateBridgeConfig(siteId string, config *qdr.Bridge if definition == nil || a.watcher == nil { return updated } - if definition.Spec.TlsCredentials != "" && !a.parent.bindings.TlsCredentialIncluded(definition.Spec.TlsCredentials) { + if definition.Spec.TlsCredentials != "" && !a.parent.bindings.IsTlsSecretPresent(definition.Spec.TlsCredentials) { return updated } connector := &skupperv2alpha1.Connector{ diff --git a/internal/kube/site/extended_bindings.go b/internal/kube/site/extended_bindings.go index 2af79bd65..4e765947e 100644 --- a/internal/kube/site/extended_bindings.go +++ b/internal/kube/site/extended_bindings.go @@ -406,7 +406,7 @@ func (b *ExtendedBindings) Apply(config *qdr.RouterConfig) bool { } } for _, ptl := range b.perTargetListeners { - if ptl.definition.Spec.TlsCredentials != "" && !b.bindings.TlsCredentialIncluded(ptl.definition.Spec.TlsCredentials) { + if ptl.definition.Spec.TlsCredentials != "" && !b.bindings.IsTlsSecretPresent(ptl.definition.Spec.TlsCredentials) { continue } if ptl.updateBridgeConfig(b.bindings.SiteId, &desired) { @@ -428,7 +428,7 @@ func (b *ExtendedBindings) Apply(config *qdr.RouterConfig) bool { func (b *ExtendedBindings) AddSslProfiles(config *qdr.RouterConfig, definitions map[string]*skupperv2alpha1.AttachedConnector) bool { profiles := map[string]qdr.SslProfile{} for _, c := range definitions { - if c.Spec.TlsCredentials != "" && !b.bindings.TlsCredentialIncluded(c.Spec.TlsCredentials) { + if c.Spec.TlsCredentials != "" && !b.bindings.IsTlsSecretPresent(c.Spec.TlsCredentials) { continue } if c.Spec.TlsCredentials != "" { @@ -457,7 +457,7 @@ func (b *ExtendedBindings) AddSslProfiles(config *qdr.RouterConfig, definitions func (b *ExtendedBindings) SetSite(site *Site) { b.bindings.SetSiteId(site.site.GetSiteId()) - b.bindings.SetTlsSecretAllowed(site.tlsCredentialSecretPresent) + b.bindings.SetIsTlsSecretPresent(site.tlsCredentialSecretPresent) b.site = site } @@ -534,7 +534,7 @@ func (b *ExtendedBindings) attachedConnectorUnreferenced(namespace string, name func (b *ExtendedBindings) networkUpdated(network []skupperv2alpha1.SiteRecord) qdr.ConfigUpdate { changed := false for _, ptl := range b.perTargetListeners { - if ptl.definition.Spec.TlsCredentials != "" && !b.bindings.TlsCredentialIncluded(ptl.definition.Spec.TlsCredentials) { + if ptl.definition.Spec.TlsCredentials != "" && !b.bindings.IsTlsSecretPresent(ptl.definition.Spec.TlsCredentials) { continue } update, err := ptl.extractTargets(network, b.mapping, b.exposed, b.context) diff --git a/internal/kube/site/site.go b/internal/kube/site/site.go index 695238d57..ea28db148 100644 --- a/internal/kube/site/site.go +++ b/internal/kube/site/site.go @@ -791,7 +791,7 @@ func (s *Site) reconcileAfterTlsSecretChange(pw qdr.ConfigUpdate) error { op := ConfigUpdateList{ s.bindings, s, - s.linkAccess.DesiredConfigAllowingTlsSecrets(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent), + s.linkAccess.DesiredConfigWithAvailableCredentials(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent), &eligibleLinksConfig{site: s}, pw, } @@ -836,7 +836,7 @@ func (s *Site) recoverRouterConfig(update bool) ([]*qdr.RouterConfig, error) { for i, group := range groups { if config, ok := byName[group]; ok { if update { - op := ConfigUpdateList{s.bindings, s, s.linkAccess.DesiredConfigAllowingTlsSecrets(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent)} + op := ConfigUpdateList{s.bindings, s, s.linkAccess.DesiredConfigWithAvailableCredentials(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent)} if err := kubeqdr.UpdateRouterConfig(s.clients.GetKubeClient(), group, s.namespace, context.TODO(), op, s.labelling); err != nil { s.logger.Error("Failed to update router config map", slog.String("namespace", s.namespace), @@ -849,7 +849,7 @@ func (s *Site) recoverRouterConfig(update bool) ([]*qdr.RouterConfig, error) { } else { routerConfig := s.initialRouterConfig() s.bindings.Apply(routerConfig) - s.linkAccess.DesiredConfigAllowingTlsSecrets(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent).Apply(routerConfig) + s.linkAccess.DesiredConfigWithAvailableCredentials(groups[:i], SSL_PROFILE_PATH, s.tlsCredentialSecretPresent).Apply(routerConfig) if err := s.createRouterConfigForGroup(group, routerConfig); err != nil { s.logger.Error("Failed to create router config map", slog.String("namespace", s.namespace), @@ -1619,7 +1619,7 @@ func (s *Site) CheckRouterAccess(name string, la *skupperv2alpha1.RouterAccess) var errors []string for i, group := range groups { if specChanged || !la.IsConfigured() { - if err := s.updateRouterConfigForGroup(s.linkAccess.DesiredConfigAllowingTlsSecrets(previousGroups, SSL_PROFILE_PATH, s.tlsCredentialSecretPresent), group); err != nil { + if err := s.updateRouterConfigForGroup(s.linkAccess.DesiredConfigWithAvailableCredentials(previousGroups, SSL_PROFILE_PATH, s.tlsCredentialSecretPresent), group); err != nil { s.logger.Error("Error updating router config", slog.String("namespace", s.namespace), slog.Any("error", err)) diff --git a/internal/site/bindings.go b/internal/site/bindings.go index 1a2ea2cca..45d16ca11 100644 --- a/internal/site/bindings.go +++ b/internal/site/bindings.go @@ -23,14 +23,14 @@ type ListenerFunction func(*skupperv2alpha1.Listener) *skupperv2alpha1.Listener type MultiKeyListenerFunction func(*skupperv2alpha1.MultiKeyListener) *skupperv2alpha1.MultiKeyListener type Bindings struct { - SiteId string - ProfilePath string - connectors map[string]*skupperv2alpha1.Connector - listeners map[string]*skupperv2alpha1.Listener - multiKeyListeners map[string]*skupperv2alpha1.MultiKeyListener - handler BindingEventHandler - tlsSecretAllowed func(secretName string) bool - configure struct { + SiteId string + ProfilePath string + connectors map[string]*skupperv2alpha1.Connector + listeners map[string]*skupperv2alpha1.Listener + multiKeyListeners map[string]*skupperv2alpha1.MultiKeyListener + handler BindingEventHandler + isTlsSecretPresent func(secretName string) bool + configure struct { listener ListenerConfiguration connector ConnectorConfiguration multiKeyListener MultiKeyListenerConfiguration @@ -54,20 +54,20 @@ func (b *Bindings) SetSiteId(siteId string) { b.SiteId = siteId } -func (b *Bindings) SetTlsSecretAllowed(f func(string) bool) { - b.tlsSecretAllowed = f +func (b *Bindings) SetIsTlsSecretPresent(f func(string) bool) { + b.isTlsSecretPresent = f } -// TlsCredentialIncluded reports whether TLS material for the given secret name may be -// referenced in generated router configuration. Empty name always returns true. -func (b *Bindings) TlsCredentialIncluded(secretName string) bool { +// IsTlsSecretPresent reports whether TLS material for the given secret name is available +// (e.g. in the ProfilesWatcher cache). Empty name always returns true. +func (b *Bindings) IsTlsSecretPresent(secretName string) bool { if secretName == "" { return true } - if b.tlsSecretAllowed == nil { + if b.isTlsSecretPresent == nil { return true } - return b.tlsSecretAllowed(secretName) + return b.isTlsSecretPresent(secretName) } func (b *Bindings) SetListenerConfiguration(configuration ListenerConfiguration) { @@ -248,19 +248,19 @@ func (b *Bindings) ToBridgeConfig() qdr.BridgeConfig { ListenerAddresses: qdr.ListenerAddressMap{}, } for _, c := range b.connectors { - if c.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(c.Spec.TlsCredentials) { + if c.Spec.TlsCredentials != "" && !b.IsTlsSecretPresent(c.Spec.TlsCredentials) { continue } b.configure.connector(b.SiteId, c, &config) } for _, l := range b.listeners { - if l.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(l.Spec.TlsCredentials) { + if l.Spec.TlsCredentials != "" && !b.IsTlsSecretPresent(l.Spec.TlsCredentials) { continue } b.configure.listener(b.SiteId, l, &config) } for _, mkl := range b.multiKeyListeners { - if mkl.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(mkl.Spec.TlsCredentials) { + if mkl.Spec.TlsCredentials != "" && !b.IsTlsSecretPresent(mkl.Spec.TlsCredentials) { continue } b.configure.multiKeyListener(b.SiteId, mkl, &config) @@ -272,7 +272,7 @@ func (b *Bindings) ToBridgeConfig() qdr.BridgeConfig { func (b *Bindings) AddSslProfiles(config *qdr.RouterConfig) bool { profiles := map[string]qdr.SslProfile{} for _, c := range b.connectors { - if c.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(c.Spec.TlsCredentials) { + if c.Spec.TlsCredentials != "" && !b.IsTlsSecretPresent(c.Spec.TlsCredentials) { continue } if c.Spec.TlsCredentials != "" { @@ -291,7 +291,7 @@ func (b *Bindings) AddSslProfiles(config *qdr.RouterConfig) bool { } } for _, l := range b.listeners { - if l.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(l.Spec.TlsCredentials) { + if l.Spec.TlsCredentials != "" && !b.IsTlsSecretPresent(l.Spec.TlsCredentials) { continue } if _, ok := profiles[l.Spec.TlsCredentials]; l.Spec.TlsCredentials != "" && !ok { @@ -299,7 +299,7 @@ func (b *Bindings) AddSslProfiles(config *qdr.RouterConfig) bool { } } for _, mkl := range b.multiKeyListeners { - if mkl.Spec.TlsCredentials != "" && !b.TlsCredentialIncluded(mkl.Spec.TlsCredentials) { + if mkl.Spec.TlsCredentials != "" && !b.IsTlsSecretPresent(mkl.Spec.TlsCredentials) { continue } if _, ok := profiles[mkl.Spec.TlsCredentials]; mkl.Spec.TlsCredentials != "" && !ok { diff --git a/internal/site/routeraccess.go b/internal/site/routeraccess.go index 027082d61..8b44463d5 100644 --- a/internal/site/routeraccess.go +++ b/internal/site/routeraccess.go @@ -60,17 +60,17 @@ func (m RouterAccessMap) findInterRouterRole() (*skupperv2alpha1.RouterAccessRol } func (m RouterAccessMap) DesiredConfig(targetGroups []string, profilePath string) *RouterAccessConfig { - return m.DesiredConfigAllowingTlsSecrets(targetGroups, profilePath, nil) + return m.DesiredConfigWithAvailableCredentials(targetGroups, profilePath, nil) } -// DesiredConfigAllowingTlsSecrets is like DesiredConfig but skips RouterAccess entries whose -// spec.tlsCredentials is set when tlsSecretAllowed is non-nil and returns false for that name. -func (m RouterAccessMap) DesiredConfigAllowingTlsSecrets(targetGroups []string, profilePath string, tlsSecretAllowed func(string) bool) *RouterAccessConfig { +// DesiredConfigWithAvailableCredentials is like DesiredConfig but skips RouterAccess entries whose +// spec.tlsCredentials is set when isTlsSecretPresent is non-nil and returns false for that name. +func (m RouterAccessMap) DesiredConfigWithAvailableCredentials(targetGroups []string, profilePath string, isTlsSecretPresent func(string) bool) *RouterAccessConfig { source := m - if tlsSecretAllowed != nil { + if isTlsSecretPresent != nil { source = make(RouterAccessMap, len(m)) for k, ra := range m { - if ra.Spec.TlsCredentials != "" && !tlsSecretAllowed(ra.Spec.TlsCredentials) { + if ra.Spec.TlsCredentials != "" && !isTlsSecretPresent(ra.Spec.TlsCredentials) { continue } source[k] = ra diff --git a/internal/site/routeraccess_test.go b/internal/site/routeraccess_test.go index e416217b6..bfd0fc492 100644 --- a/internal/site/routeraccess_test.go +++ b/internal/site/routeraccess_test.go @@ -454,7 +454,7 @@ func TestRouterAccessMap_DesiredConfig(t *testing.T) { } } -func TestRouterAccessMap_DesiredConfigAllowingTlsSecrets(t *testing.T) { +func TestRouterAccessMap_DesiredConfigWithAvailableCredentials(t *testing.T) { ra := &skupperv2alpha1.RouterAccess{ ObjectMeta: v1.ObjectMeta{Name: "ra", Namespace: "ns"}, Spec: skupperv2alpha1.RouterAccessSpec{ @@ -467,12 +467,12 @@ func TestRouterAccessMap_DesiredConfigAllowingTlsSecrets(t *testing.T) { } m := RouterAccessMap{"ra": ra} allow := func(name string) bool { return name != "missing-secret" } - got := m.DesiredConfigAllowingTlsSecrets([]string{"g1"}, "/certs", allow) + got := m.DesiredConfigWithAvailableCredentials([]string{"g1"}, "/certs", allow) if len(got.listeners) != 0 || len(got.connectors) != 0 { t.Fatalf("expected empty desired when TLS secret disallowed, got listeners=%d connectors=%d", len(got.listeners), len(got.connectors)) } - got2 := m.DesiredConfigAllowingTlsSecrets([]string{"g1"}, "/certs", func(string) bool { return true }) + got2 := m.DesiredConfigWithAvailableCredentials([]string{"g1"}, "/certs", func(string) bool { return true }) if len(got2.listeners) == 0 { t.Fatal("expected listeners when TLS secret allowed") } From 711938ede7227524f442f132b83215ef19062c0c Mon Sep 17 00:00:00 2001 From: Aryan Puttur Date: Thu, 28 May 2026 11:04:29 -0400 Subject: [PATCH 3/3] Report missing TLS credentials on Listener and Connector status --- internal/kube/site/extended_bindings.go | 11 ++-- internal/kube/site/site.go | 71 ++++++++++++++++++++----- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/internal/kube/site/extended_bindings.go b/internal/kube/site/extended_bindings.go index 4e765947e..ed95f3df9 100644 --- a/internal/kube/site/extended_bindings.go +++ b/internal/kube/site/extended_bindings.go @@ -428,10 +428,15 @@ func (b *ExtendedBindings) Apply(config *qdr.RouterConfig) bool { func (b *ExtendedBindings) AddSslProfiles(config *qdr.RouterConfig, definitions map[string]*skupperv2alpha1.AttachedConnector) bool { profiles := map[string]qdr.SslProfile{} for _, c := range definitions { - if c.Spec.TlsCredentials != "" && !b.bindings.IsTlsSecretPresent(c.Spec.TlsCredentials) { - continue - } if c.Spec.TlsCredentials != "" { + if !b.bindings.IsTlsSecretPresent(c.Spec.TlsCredentials) { + b.logger.Info("Skipping attached connector TLS profile until credentials secret exists", + slog.String("namespace", c.Namespace), + slog.String("name", c.Name), + slog.String("secret", c.Spec.TlsCredentials), + ) + continue + } if !c.Spec.UseClientCert { //if only ca is used, need to qualify the profile to ensure that it does not collide with // use of the same secret where client auth *is* required diff --git a/internal/kube/site/site.go b/internal/kube/site/site.go index ea28db148..e8f8078ba 100644 --- a/internal/kube/site/site.go +++ b/internal/kube/site/site.go @@ -745,6 +745,13 @@ func (s *Site) tlsCredentialSecretPresent(secretName string) bool { return s.profiles.TlsCredentialSecretPresent(secretName) } +func (s *Site) missingTlsCredentialsErr(tlsCredentials string) error { + if tlsCredentials == "" || s.tlsCredentialSecretPresent(tlsCredentials) { + return nil + } + return fmt.Errorf("TLS credentials secret %q not found", tlsCredentials) +} + // eligibleLinksConfig applies only links whose TLS credential secrets are present. type eligibleLinksConfig struct { site *Site @@ -1000,21 +1007,38 @@ func (s *Site) updateConnectorConfiguredStatusWithSelectedPods(connector *skuppe } func (s *Site) CheckConnector(name string, connector *skupperv2alpha1.Connector) error { - update := s.bindings.UpdateConnector(name, connector) if s.site == nil { if connector == nil { return nil } return s.updateConnectorConfiguredStatus(connector, stderrors.New("No active site in namespace")) } - if update == nil { - return nil + var tlsErr error + if connector != nil { + tlsErr = s.missingTlsCredentialsErr(connector.Spec.TlsCredentials) + if tlsErr != nil { + s.logger.Info("Deferring connector router configuration until TLS credentials secret exists", + slog.String("namespace", s.namespace), + slog.String("connector", connector.Name), + slog.String("secret", connector.Spec.TlsCredentials), + ) + } } - err := s.updateRouterConfig(update) + update := s.bindings.UpdateConnector(name, connector) if connector == nil { - return err + if update != nil { + return s.updateRouterConfig(update) + } + return nil + } + if update == nil { + if tlsErr != nil { + return s.updateConnectorConfiguredStatus(connector, tlsErr) + } + return nil } - return s.updateConnectorConfiguredStatus(connector, err) + routerErr := s.updateRouterConfig(update) + return s.updateConnectorConfiguredStatus(connector, stderrors.Join(tlsErr, routerErr)) } func (s *Site) updateListenerStatus(listener *skupperv2alpha1.Listener, err error) error { @@ -1106,15 +1130,32 @@ func (s *Site) CheckListener(name string, listener *skupperv2alpha1.Listener, sv } } } + var tlsErr error + if listener != nil { + tlsErr = s.missingTlsCredentialsErr(listener.Spec.TlsCredentials) + if tlsErr != nil { + s.logger.Info("Deferring listener router configuration until TLS credentials secret exists", + slog.String("namespace", s.namespace), + slog.String("listener", listener.Name), + slog.String("secret", listener.Spec.TlsCredentials), + ) + } + } update, err1 := s.bindings.UpdateListener(name, listener) + if listener == nil { + if update == nil { + return nil + } + return stderrors.Join(err1, s.updateRouterConfig(update)) + } if update == nil { + if tlsErr != nil { + return s.updateListenerStatus(listener, tlsErr) + } return nil } err2 := s.updateRouterConfig(update) - if listener == nil { - return stderrors.Join(err1, err2) - } - return s.updateListenerStatus(listener, stderrors.Join(err1, err2)) + return s.updateListenerStatus(listener, stderrors.Join(tlsErr, err1, err2)) } func (s *Site) CheckMultiKeyListener(name string, mkl *skupperv2alpha1.MultiKeyListener) error { @@ -1242,13 +1283,13 @@ func (s *Site) link(linkconfig *skupperv2alpha1.Link) error { config.UpdateProxyConfig(currentProxyConfig) } } - if linkconfig.Spec.TlsCredentials != "" && !s.tlsCredentialSecretPresent(linkconfig.Spec.TlsCredentials) { + if tlsErr := s.missingTlsCredentialsErr(linkconfig.Spec.TlsCredentials); tlsErr != nil { s.logger.Info("Deferring link router configuration until TLS credentials secret exists", slog.String("namespace", s.namespace), slog.String("link", linkconfig.Name), slog.String("secret", linkconfig.Spec.TlsCredentials), ) - return s.updateLinkConfiguredCondition(linkconfig, fmt.Errorf("TLS credentials secret %q not found", linkconfig.Spec.TlsCredentials)) + return s.updateLinkConfiguredCondition(linkconfig, tlsErr) } err := s.updateRouterConfig(config) return s.updateLinkConfiguredCondition(linkconfig, err) @@ -1606,8 +1647,10 @@ func (s *Site) CheckRouterAccess(name string, la *skupperv2alpha1.RouterAccess) return nil } var configuredErr error - if la != nil && la.Spec.TlsCredentials != "" && !s.tlsCredentialSecretPresent(la.Spec.TlsCredentials) { - configuredErr = fmt.Errorf("TLS credentials secret %q not found", la.Spec.TlsCredentials) + if la != nil { + configuredErr = s.missingTlsCredentialsErr(la.Spec.TlsCredentials) + } + if configuredErr != nil { s.logger.Info("Deferring RouterAccess router configuration until TLS credentials secret exists", slog.String("namespace", s.namespace), slog.String("routerAccess", la.Name),