Skip to content
Open
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
15 changes: 15 additions & 0 deletions internal/kube/secrets/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
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.

Have we committed anywhere else that the Secret must have type kubernetes.io/tls? IIRC I've used opaque before just out of kubectl convenience and it works out fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Was matching existing code. ProfilesWatcher and kube-adaptor secrets.Sync already only handle kubernetes.io/tls secrets, and doc/tls/README.md documents that layout. It states Skupper expects TLS credentials in standard kubernetes.io/tls format with tls.crt, tls.key, and optionally ca.crt. I kept the same check here so we don’t treat a secret as “ready” if the adaptor won’t sync it. Let me know if you prefer to change it.

return true
}
}
return false
}

func (w *ProfilesWatcher) checkPriorValidity(secret *corev1.Secret) uint64 {
result := w.pvProvider.TLSPriorValidRevisions()
if secret.ObjectMeta.Annotations == nil {
Expand Down
3 changes: 3 additions & 0 deletions internal/kube/site/attached_connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.IsTlsSecretPresent(definition.Spec.TlsCredentials) {
return updated
}
connector := &skupperv2alpha1.Connector{
ObjectMeta: metav1.ObjectMeta{
Name: definition.Name,
Expand Down
15 changes: 15 additions & 0 deletions internal/kube/site/extended_bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,9 @@ func (b *ExtendedBindings) Apply(config *qdr.RouterConfig) bool {
}
}
for _, ptl := range b.perTargetListeners {
if ptl.definition.Spec.TlsCredentials != "" && !b.bindings.IsTlsSecretPresent(ptl.definition.Spec.TlsCredentials) {
continue
}
if ptl.updateBridgeConfig(b.bindings.SiteId, &desired) {
updated = true
}
Expand All @@ -426,6 +429,14 @@ func (b *ExtendedBindings) AddSslProfiles(config *qdr.RouterConfig, definitions
profiles := map[string]qdr.SslProfile{}
for _, c := range definitions {
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
Expand All @@ -451,6 +462,7 @@ func (b *ExtendedBindings) AddSslProfiles(config *qdr.RouterConfig, definitions

func (b *ExtendedBindings) SetSite(site *Site) {
b.bindings.SetSiteId(site.site.GetSiteId())
b.bindings.SetIsTlsSecretPresent(site.tlsCredentialSecretPresent)
b.site = site
}

Expand Down Expand Up @@ -527,6 +539,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.IsTlsSecretPresent(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 {
Expand Down
157 changes: 143 additions & 14 deletions internal/kube/site/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -736,6 +736,79 @@ 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)
}

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
}

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.DesiredConfigWithAvailableCredentials(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",
Expand Down Expand Up @@ -770,7 +843,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.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),
Expand All @@ -783,7 +856,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.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),
Expand Down Expand Up @@ -934,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 {
Expand Down Expand Up @@ -1040,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 {
Expand Down Expand Up @@ -1176,6 +1283,14 @@ func (s *Site) link(linkconfig *skupperv2alpha1.Link) error {
config.UpdateProxyConfig(currentProxyConfig)
}
}
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, tlsErr)
}
err := s.updateRouterConfig(config)
return s.updateLinkConfiguredCondition(linkconfig, err)
} else {
Expand Down Expand Up @@ -1531,12 +1646,23 @@ func (s *Site) CheckRouterAccess(name string, la *skupperv2alpha1.RouterAccess)
if !s.initialised {
return nil
}
var configuredErr error
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),
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.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))
Expand Down Expand Up @@ -1568,6 +1694,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
Expand Down
Loading
Loading