-
Notifications
You must be signed in to change notification settings - Fork 152
EV-6336: feat(istio): waypoint pull secret support for private registries #4483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
7ad2934
feat(istio): pass imagePullSecrets to istiod via Helm values
electricjesus e347827
feat(istio): add waypoint pull secret sub-controller
electricjesus 71cac55
refactor(istio): use NewPassthrough objectsToDelete for waypoint secr…
electricjesus ab5da53
docs(istio): expand WaypointPullSecretLabel comment on label vs owner…
electricjesus 6959b27
fix(istio): key waypoint secret staleness on (namespace, name) pairs
electricjesus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
213 changes: 213 additions & 0 deletions
213
pkg/controller/istio/waypoint/waypoint_secrets_controller.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,213 @@ | ||
| // Copyright (c) 2026 Tigera, Inc. All rights reserved. | ||
|
caseydavenport marked this conversation as resolved.
|
||
|
|
||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package waypoint | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| corev1 "k8s.io/api/core/v1" | ||
| "k8s.io/apimachinery/pkg/api/errors" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| "sigs.k8s.io/controller-runtime/pkg/controller" | ||
| "sigs.k8s.io/controller-runtime/pkg/event" | ||
| "sigs.k8s.io/controller-runtime/pkg/handler" | ||
| logf "sigs.k8s.io/controller-runtime/pkg/log" | ||
| "sigs.k8s.io/controller-runtime/pkg/manager" | ||
| "sigs.k8s.io/controller-runtime/pkg/predicate" | ||
| "sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
| gapi "sigs.k8s.io/gateway-api/apis/v1" | ||
|
|
||
| operatorv1 "github.com/tigera/operator/api/v1" | ||
| "github.com/tigera/operator/pkg/common" | ||
| "github.com/tigera/operator/pkg/controller/options" | ||
| "github.com/tigera/operator/pkg/controller/utils" | ||
| "github.com/tigera/operator/pkg/ctrlruntime" | ||
| "github.com/tigera/operator/pkg/render" | ||
| "github.com/tigera/operator/pkg/render/common/secret" | ||
| ) | ||
|
|
||
| const ( | ||
| // IstioWaypointClassName is the GatewayClass name used by Istio waypoints. | ||
| IstioWaypointClassName = "istio-waypoint" | ||
|
|
||
| // WaypointPullSecretLabel labels secrets copied by this controller. We use a label rather | ||
| // than owner references because the controller needs to efficiently find and clean up its | ||
| // managed secrets during reconciliation — for example, when pull secrets are removed from | ||
| // Installation or when the Istio CR is deleted. A label selector provides a simple, | ||
| // cross-namespace query that covers all cleanup scenarios, whereas owner references would | ||
| // only automate Gateway-deletion cleanup via Kubernetes garbage collection. | ||
| WaypointPullSecretLabel = "operator.tigera.io/istio-waypoint-pull-secret" | ||
|
caseydavenport marked this conversation as resolved.
|
||
| ) | ||
|
|
||
| var log = logf.Log.WithName("controller_istio_waypoint") | ||
|
|
||
| // Add creates the waypoint pull secrets controller and adds it to the Manager. | ||
| func Add(mgr manager.Manager, opts options.ControllerOptions) error { | ||
| if !opts.EnterpriseCRDExists { | ||
| return nil | ||
| } | ||
|
|
||
| r := &ReconcileWaypointSecrets{ | ||
| Client: mgr.GetClient(), | ||
| scheme: mgr.GetScheme(), | ||
| } | ||
|
|
||
| c, err := ctrlruntime.NewController("istio-waypoint-secrets-controller", mgr, controller.Options{Reconciler: r}) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create istio-waypoint-secrets-controller: %w", err) | ||
| } | ||
|
|
||
| // Watch Gateway resources, filtering for istio-waypoint class only. | ||
| err = c.WatchObject(&gapi.Gateway{}, &handler.EnqueueRequestForObject{}, predicate.Funcs{ | ||
| CreateFunc: func(e event.CreateEvent) bool { | ||
| gw, ok := e.Object.(*gapi.Gateway) | ||
| return ok && string(gw.Spec.GatewayClassName) == IstioWaypointClassName | ||
| }, | ||
| UpdateFunc: func(e event.UpdateEvent) bool { | ||
| gw, ok := e.ObjectNew.(*gapi.Gateway) | ||
| return ok && string(gw.Spec.GatewayClassName) == IstioWaypointClassName | ||
| }, | ||
| DeleteFunc: func(e event.DeleteEvent) bool { | ||
| gw, ok := e.Object.(*gapi.Gateway) | ||
| return ok && string(gw.Spec.GatewayClassName) == IstioWaypointClassName | ||
| }, | ||
| GenericFunc: func(e event.GenericEvent) bool { | ||
| gw, ok := e.Object.(*gapi.Gateway) | ||
| return ok && string(gw.Spec.GatewayClassName) == IstioWaypointClassName | ||
| }, | ||
| }) | ||
| if err != nil { | ||
| return fmt.Errorf("istio-waypoint-secrets-controller failed to watch Gateway resource: %w", err) | ||
| } | ||
|
|
||
| // Watch Istio CR for pull secret config changes. | ||
| err = c.WatchObject(&operatorv1.Istio{}, &handler.EnqueueRequestForObject{}) | ||
| if err != nil { | ||
| return fmt.Errorf("istio-waypoint-secrets-controller failed to watch Istio resource: %w", err) | ||
| } | ||
|
|
||
| // Watch Installation for pull secret changes. | ||
| if err = utils.AddInstallationWatch(c); err != nil { | ||
| return fmt.Errorf("istio-waypoint-secrets-controller failed to watch Installation resource: %w", err) | ||
| } | ||
|
|
||
| // Periodic reconcile as a backstop. | ||
| if err = utils.AddPeriodicReconcile(c, utils.PeriodicReconcileTime, &handler.EnqueueRequestForObject{}); err != nil { | ||
| return fmt.Errorf("istio-waypoint-secrets-controller failed to create periodic reconcile watch: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // ReconcileWaypointSecrets copies pull secrets to namespaces that contain | ||
| // istio-waypoint Gateways so that waypoint pods can pull images from private registries. | ||
| type ReconcileWaypointSecrets struct { | ||
| client.Client | ||
| scheme *runtime.Scheme | ||
| } | ||
|
|
||
| func (r *ReconcileWaypointSecrets) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { | ||
| reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) | ||
| reqLogger.V(1).Info("Reconciling waypoint pull secrets") | ||
|
|
||
| // Determine which secrets need to exist (toCreate) based on current state, | ||
| // and which existing secrets are stale (toDelete). | ||
| var toCreate []client.Object | ||
| var toDelete []client.Object | ||
|
|
||
| // Get the Istio CR - if not found or being deleted, all existing secrets are stale. | ||
| instance := &operatorv1.Istio{} | ||
| err := r.Get(ctx, utils.DefaultInstanceKey, instance) | ||
| istioActive := err == nil && instance.DeletionTimestamp.IsZero() | ||
| if err != nil && !errors.IsNotFound(err) { | ||
| return reconcile.Result{}, err | ||
| } | ||
|
|
||
| // Build the desired set of secrets if Istio is active. | ||
| targetNamespaces := map[string]bool{} | ||
| if istioActive { | ||
| _, installationSpec, err := utils.GetInstallationSpec(ctx, r) | ||
| if err != nil { | ||
| if errors.IsNotFound(err) { | ||
| reqLogger.V(1).Info("Installation not found") | ||
| return reconcile.Result{}, nil | ||
| } | ||
| return reconcile.Result{}, err | ||
| } | ||
|
|
||
| pullSecrets, err := utils.GetInstallationPullSecrets(installationSpec, r) | ||
| if err != nil { | ||
| return reconcile.Result{}, err | ||
| } | ||
|
|
||
| // List all Gateway resources and filter for istio-waypoint class. | ||
| gatewayList := &gapi.GatewayList{} | ||
| if err := r.List(ctx, gatewayList); err != nil { | ||
| return reconcile.Result{}, fmt.Errorf("failed to list Gateways: %w", err) | ||
| } | ||
|
|
||
| for i := range gatewayList.Items { | ||
| gw := &gatewayList.Items[i] | ||
| if string(gw.Spec.GatewayClassName) == IstioWaypointClassName && | ||
| gw.Namespace != common.OperatorNamespace() { | ||
| targetNamespaces[gw.Namespace] = true | ||
| } | ||
| } | ||
|
|
||
| // Build desired secrets for each target namespace. | ||
| for ns := range targetNamespaces { | ||
| copied := secret.CopyToNamespace(ns, pullSecrets...) | ||
| for _, s := range copied { | ||
| if s.Labels == nil { | ||
| s.Labels = map[string]string{} | ||
| } | ||
| s.Labels[WaypointPullSecretLabel] = "true" | ||
| toCreate = append(toCreate, s) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Build the desired set keyed on (namespace, name) so that renamed or removed | ||
| // secrets are correctly detected as stale. | ||
| desiredSecrets := map[types.NamespacedName]bool{} | ||
| for _, obj := range toCreate { | ||
| desiredSecrets[types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}] = true | ||
| } | ||
|
|
||
| // List all existing secrets managed by this controller and mark stale ones for deletion. | ||
| existingSecrets := &corev1.SecretList{} | ||
| if err := r.List(ctx, existingSecrets, client.MatchingLabels{WaypointPullSecretLabel: "true"}); err != nil { | ||
| return reconcile.Result{}, fmt.Errorf("failed to list waypoint pull secrets: %w", err) | ||
| } | ||
| for i := range existingSecrets.Items { | ||
| s := &existingSecrets.Items[i] | ||
| key := types.NamespacedName{Namespace: s.Namespace, Name: s.Name} | ||
| if !desiredSecrets[key] { | ||
| toDelete = append(toDelete, s) | ||
| } | ||
| } | ||
|
|
||
| // Use a single passthrough component to handle both creation and deletion. | ||
| hdlr := utils.NewComponentHandler(log, r, r.scheme, nil) | ||
| component := render.NewPassthrough(toCreate, toDelete) | ||
| if err := hdlr.CreateOrUpdateOrDelete(ctx, component, nil); err != nil { | ||
| return reconcile.Result{}, fmt.Errorf("failed to reconcile waypoint pull secrets: %w", err) | ||
| } | ||
|
|
||
| return reconcile.Result{}, nil | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.