diff --git a/deploy/0.prepare-certificates.yaml b/deploy/0.prepare-certificates.yaml index 9252c80..afda4c4 100644 --- a/deploy/0.prepare-certificates.yaml +++ b/deploy/0.prepare-certificates.yaml @@ -9,6 +9,7 @@ spec: selfSigned: {} --- + apiVersion: cert-manager.io/v1 kind: Certificate metadata: @@ -25,7 +26,9 @@ spec: name: selfsigned-issuer kind: ClusterIssuer group: cert-manager.io + --- + apiVersion: cert-manager.io/v1 kind: Issuer metadata: @@ -36,6 +39,7 @@ spec: secretName: root-secret --- + apiVersion: cert-manager.io/v1 kind: Certificate metadata: diff --git a/internal/controllers/httproute_controller.go b/internal/controllers/httproute_controller.go index 7998224..c41a70b 100644 --- a/internal/controllers/httproute_controller.go +++ b/internal/controllers/httproute_controller.go @@ -22,9 +22,9 @@ import ( "time" "github.com/f5devcentral/bigip-kubernetes-gateway/internal/pkg" - "github.com/google/uuid" "github.com/f5devcentral/f5-bigip-rest-go/deployer" "github.com/f5devcentral/f5-bigip-rest-go/utils" + "github.com/google/uuid" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -78,7 +78,7 @@ func (r *HttpRouteReconciler) GetResObject() client.Object { func handleDeletingHTTPRoute(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { hr := pkg.ActiveSIGs.GetHTTPRoute(req.NamespacedName.String()) - gws := pkg.ActiveSIGs.GatewayRefsOf(hr) + gws := pkg.ActiveSIGs.GatewayRefsOfHR(hr) drs := map[string]*deployer.DeployRequest{} for _, gw := range gws { if _, f := drs[string(gw.Spec.GatewayClassName)]; !f { @@ -150,7 +150,7 @@ func handleUpsertingHTTPRoute(ctx context.Context, obj *gatewayv1beta1.HTTPRoute slog.Debugf("upserting " + reqnsn) hr := pkg.ActiveSIGs.GetHTTPRoute(reqnsn) - gws := pkg.ActiveSIGs.GatewayRefsOf(hr) + gws := pkg.ActiveSIGs.GatewayRefsOfHR(hr) drs := map[string]*deployer.DeployRequest{} for _, gw := range gws { @@ -182,7 +182,7 @@ func handleUpsertingHTTPRoute(ctx context.Context, obj *gatewayv1beta1.HTTPRoute // We still need to consider gateways that were previously associated but are no longer associated, // Or the previously associated gateways may be recognized as resource deletions. - gws = unifiedGateways(append(gws, pkg.ActiveSIGs.GatewayRefsOf(obj.DeepCopy())...)) + gws = pkg.UnifiedGateways(append(gws, pkg.ActiveSIGs.GatewayRefsOfHR(obj.DeepCopy())...)) for _, gw := range gws { if _, f := drs[string(gw.Spec.GatewayClassName)]; !f { @@ -219,18 +219,3 @@ func handleUpsertingHTTPRoute(ctx context.Context, obj *gatewayv1beta1.HTTPRoute return ctrl.Result{}, nil } - -func unifiedGateways(objs []*gatewayv1beta1.Gateway) []*gatewayv1beta1.Gateway { - - m := map[string]bool{} - rlt := []*gatewayv1beta1.Gateway{} - - for _, obj := range objs { - name := utils.Keyname(obj.Namespace, obj.Name) - if _, f := m[name]; !f { - m[name] = true - rlt = append(rlt, obj) - } - } - return rlt -} diff --git a/internal/controllers/referencegrant_controller.go b/internal/controllers/referencegrant_controller.go index 7cb5a7a..9e2822c 100644 --- a/internal/controllers/referencegrant_controller.go +++ b/internal/controllers/referencegrant_controller.go @@ -18,11 +18,12 @@ package controllers import ( "context" + "fmt" "time" "github.com/f5devcentral/bigip-kubernetes-gateway/internal/pkg" - "github.com/google/uuid" "github.com/f5devcentral/f5-bigip-rest-go/utils" + "github.com/google/uuid" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -44,23 +45,42 @@ func (r *ReferenceGrantReconciler) Reconcile(ctx context.Context, req ctrl.Reque return ctrl.Result{Requeue: true}, nil } + keyname := req.NamespacedName.String() lctx := context.WithValue(ctx, utils.CtxKey_Logger, utils.NewLog().WithRequestID(uuid.New().String()).WithLevel(r.LogLevel)) slog := utils.LogFromContext(lctx) var obj gatewayv1beta1.ReferenceGrant slog.Infof("referencegrant event: %s", req.NamespacedName) - // TODO: update resources mappings since grant items are changed. if err := r.Client.Get(ctx, req.NamespacedName, &obj); err != nil { if client.IgnoreNotFound(err) == nil { // delete resources - pkg.ActiveSIGs.UnsetReferenceGrant(req.NamespacedName.String()) - return ctrl.Result{}, nil + rg := pkg.ActiveSIGs.GetReferenceGrant(keyname) + classNames := pkg.ActiveSIGs.RGImpactedGatewayClasses(rg) + if err := pkg.DeployForEvent(lctx, classNames, func() string { + pkg.ActiveSIGs.UnsetReferenceGrant(keyname) + return fmt.Sprintf("deleting referencegrant %s", keyname) + }); err != nil { + return ctrl.Result{}, err + } else { + return ctrl.Result{}, nil + } } else { return ctrl.Result{}, err } } else { // upsert resources - pkg.ActiveSIGs.SetReferenceGrant(obj.DeepCopy()) - return ctrl.Result{}, nil + org := pkg.ActiveSIGs.GetReferenceGrant(keyname) + nrg := obj.DeepCopy() + ocls := pkg.ActiveSIGs.RGImpactedGatewayClasses(org) + ncls := pkg.ActiveSIGs.RGImpactedGatewayClasses(nrg) + clss := utils.Unified(append(ocls, ncls...)) + if err := pkg.DeployForEvent(lctx, clss, func() string { + pkg.ActiveSIGs.SetReferenceGrant(nrg) + return fmt.Sprintf("upserting referencegrant %s", keyname) + }); err != nil { + return ctrl.Result{}, nil + } else { + return ctrl.Result{}, err + } } } diff --git a/internal/controllers/secret_controller.go b/internal/controllers/secret_controller.go index 8237d14..b5aa459 100644 --- a/internal/controllers/secret_controller.go +++ b/internal/controllers/secret_controller.go @@ -18,11 +18,12 @@ package controllers import ( "context" + "fmt" "time" "github.com/f5devcentral/bigip-kubernetes-gateway/internal/pkg" - "github.com/google/uuid" "github.com/f5devcentral/f5-bigip-rest-go/utils" + "github.com/google/uuid" v1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -48,17 +49,51 @@ func (r *SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr slog := utils.LogFromContext(lctx) var obj v1.Secret - slog.Infof("serect event: %s", req.NamespacedName) + slog.Infof("secret event: %s", req.NamespacedName) if err := r.Client.Get(ctx, req.NamespacedName, &obj); err != nil { - if client.IgnoreNotFound(err) != nil { + if client.IgnoreNotFound(err) == nil { + // delete + scrt := pkg.ActiveSIGs.GetSecret(req.NamespacedName.String()) + gws, err := pkg.ActiveSIGs.GatewayRefsOfSecret(scrt) + if err == nil { + names := []string{} + for _, gw := range gws { + names = append(names, utils.Keyname(gw.Namespace, gw.Name)) + } + if len(names) > 0 { + slog.Warnf("there are still gateways referring to secret '%s': %s "+ + "-- they are not impacted, however, next deployments would fail "+ + "because of missing the secret", req.NamespacedName, names) + } + } + + pkg.ActiveSIGs.UnsetSerect(req.NamespacedName.String()) + return ctrl.Result{}, err + } else { + return ctrl.Result{}, err + } + } else { + // upsert + scrt := obj.DeepCopy() + gws, err := pkg.ActiveSIGs.GatewayRefsOfSecret(scrt) + if err != nil { + pkg.ActiveSIGs.SetSecret(obj.DeepCopy()) return ctrl.Result{}, err } - // Can not find Sercet, remove it from the local cache - pkg.ActiveSIGs.UnsetSerect(req.NamespacedName.String()) + cls := []string{} + for _, gw := range gws { + cls = append(cls, string(gw.Spec.GatewayClassName)) + } + + apply := func() string { + pkg.ActiveSIGs.SetSecret(obj.DeepCopy()) + return fmt.Sprintf("upserting secret %s", req.NamespacedName.String()) + } + if err := pkg.DeployForEvent(lctx, cls, apply); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil } - // Find Secret, add it to the local cache. - pkg.ActiveSIGs.SetSecret(obj.DeepCopy()) - return ctrl.Result{}, nil } diff --git a/internal/controllers/v1_controller.go b/internal/controllers/v1_controller.go index c775cb8..e3c5ee4 100644 --- a/internal/controllers/v1_controller.go +++ b/internal/controllers/v1_controller.go @@ -23,9 +23,9 @@ import ( "github.com/f5devcentral/bigip-kubernetes-gateway/internal/k8s" "github.com/f5devcentral/bigip-kubernetes-gateway/internal/pkg" - "github.com/google/uuid" "github.com/f5devcentral/f5-bigip-rest-go/deployer" "github.com/f5devcentral/f5-bigip-rest-go/utils" + "github.com/google/uuid" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -99,8 +99,9 @@ func (r *EndpointsReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } } else { - defer pkg.ActiveSIGs.SetEndpoints(&obj) - return handleUpsertingEndpoints(lctx, &obj) + eps := obj.DeepCopy() + defer pkg.ActiveSIGs.SetEndpoints(eps) + return handleUpsertingEndpoints(lctx, eps) } } @@ -121,8 +122,9 @@ func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } } else { - defer pkg.ActiveSIGs.SetService(&obj) - return handleUpsertingService(lctx, &obj) + svc := obj.DeepCopy() + defer pkg.ActiveSIGs.SetService(svc) + return handleUpsertingService(lctx, svc) } } diff --git a/internal/pkg/cache.go b/internal/pkg/cache.go index 6d22850..80b99f0 100644 --- a/internal/pkg/cache.go +++ b/internal/pkg/cache.go @@ -223,6 +223,13 @@ func (c *SIGCache) UnsetReferenceGrant(keyname string) { } } +func (c *SIGCache) GetReferenceGrant(keyname string) *gatewayv1beta1.ReferenceGrant { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c.ReferenceGrant[keyname] +} + func (c *SIGCache) AttachedGateways(gwc *gatewayv1beta1.GatewayClass) []*gatewayv1beta1.Gateway { defer utils.TimeItToPrometheus()() @@ -246,16 +253,18 @@ func (c *SIGCache) _attachedGateways(gwc *gatewayv1beta1.GatewayClass) []*gatewa return gws } -func (c *SIGCache) GatewayRefsOf(hr *gatewayv1beta1.HTTPRoute) []*gatewayv1beta1.Gateway { +func (c *SIGCache) GatewayRefsOfHR(hr *gatewayv1beta1.HTTPRoute) []*gatewayv1beta1.Gateway { defer utils.TimeItToPrometheus()() c.mutex.RLock() defer c.mutex.RUnlock() - return c._gatewayRefsOf(hr) + return c._gatewayRefsOfHR(hr) } -func (c *SIGCache) _gatewayRefsOf(hr *gatewayv1beta1.HTTPRoute) []*gatewayv1beta1.Gateway { +func (c *SIGCache) _gatewayRefsOfHR(hr *gatewayv1beta1.HTTPRoute) []*gatewayv1beta1.Gateway { + defer utils.TimeItToPrometheus()() + if hr == nil { return []*gatewayv1beta1.Gateway{} } @@ -283,6 +292,95 @@ func (c *SIGCache) _gatewayRefsOf(hr *gatewayv1beta1.HTTPRoute) []*gatewayv1beta return gws } +func (c *SIGCache) GatewayRefsOfSecret(scrt *v1.Secret) ([]*gatewayv1beta1.Gateway, error) { + defer utils.TimeItToPrometheus()() + + c.mutex.RLock() + defer c.mutex.RUnlock() + + if scrt == nil { + return []*gatewayv1beta1.Gateway{}, nil + } + gws := []*gatewayv1beta1.Gateway{} + + for _, gw := range c.Gateway { + for i, found := 0, false; i < len(gw.Spec.Listeners) && !found; i++ { + listener := gw.Spec.Listeners[i] + if listener.Protocol == gatewayv1beta1.HTTPSProtocolType { + if listener.TLS == nil { + return gws, fmt.Errorf("invalid tls setting in listener") + } + if listener.TLS.Mode == nil || *listener.TLS.Mode == gatewayv1beta1.TLSModeTerminate { + for _, ref := range listener.TLS.CertificateRefs { + ns := gw.Namespace + if ref.Namespace != nil { + ns = string(*ref.Namespace) + } + if ns == scrt.Namespace && ref.Name == gatewayv1beta1.ObjectName(scrt.Name) { + if err := validateSecretType(ref.Group, ref.Kind); err == nil { + if !c._canRefer(gw, scrt) { + return gws, fmt.Errorf("'%s' cannot refer to secret '%s'", + utils.Keyname(gw.Namespace, gw.Name), utils.Keyname(scrt.Namespace, scrt.Name)) + } + gws = append(gws, gw) + found = true + break + } else { + return gws, err + } + } + } + } + } + } + } + + return gws, nil +} + +func (c *SIGCache) AttachedSecrets(gw *gatewayv1beta1.Gateway) (map[string][]*v1.Secret, error) { + defer utils.TimeItToPrometheus()() + + c.mutex.RLock() + defer c.mutex.RUnlock() + + rlt := map[string][]*v1.Secret{} + if gw == nil { + return rlt, nil + } + + for _, listener := range gw.Spec.Listeners { + lsname := gwListenerName(gw, &listener) + if _, ok := rlt[lsname]; !ok { + rlt[lsname] = []*v1.Secret{} + } + if listener.Protocol == gatewayv1beta1.HTTPSProtocolType { + if listener.TLS == nil { + return rlt, fmt.Errorf("invalid listener.TLS setting") + } + if listener.TLS.Mode == nil || *listener.TLS.Mode == gatewayv1beta1.TLSModeTerminate { + for _, ref := range listener.TLS.CertificateRefs { + ns := gw.Namespace + if ref.Namespace != nil { + ns = string(*ref.Namespace) + } + n := utils.Keyname(ns, string(ref.Name)) + scrt := c.Secret[n] + if scrt != nil && c._canRefer(gw, scrt) { + if err := validateSecretType(ref.Group, ref.Kind); err != nil { + return rlt, err + } + rlt[lsname] = append(rlt[lsname], scrt) + } else { + return rlt, fmt.Errorf("secret %s not exist or cannnot refer to", n) + } + } + } + } + } + return rlt, nil +} + func (c *SIGCache) AttachedHTTPRoutes(gw *gatewayv1beta1.Gateway) []*gatewayv1beta1.HTTPRoute { defer utils.TimeItToPrometheus()() @@ -474,7 +572,7 @@ func (c *SIGCache) GetNeighborGateways(gw *gatewayv1beta1.Gateway) []*gatewayv1b gwmap := map[string]*gatewayv1beta1.Gateway{} hrs := c._attachedHTTPRoutes(gw) for _, hr := range hrs { - gws := c._gatewayRefsOf(hr) + gws := c._gatewayRefsOfHR(hr) for _, ng := range gws { kn := utils.Keyname(ng.Namespace, ng.Name) if _, f := gwmap[kn]; !f { @@ -503,7 +601,7 @@ func (c *SIGCache) GetRootGateways(svcs []*v1.Service) []*gatewayv1beta1.Gateway for _, svc := range svcs { hrs := c._HTTPRoutesRefsOf(svc) for _, hr := range hrs { - gws := c._gatewayRefsOf(hr) + gws := c._gatewayRefsOfHR(hr) for _, gw := range gws { gwmap[utils.Keyname(gw.Namespace, gw.Name)] = gw } @@ -516,6 +614,64 @@ func (c *SIGCache) GetRootGateways(svcs []*v1.Service) []*gatewayv1beta1.Gateway return rlt } +func (c *SIGCache) RGImpactedGatewayClasses(rg *gatewayv1beta1.ReferenceGrant) []string { + defer utils.TimeItToPrometheus()() + + c.mutex.RLock() + defer c.mutex.RUnlock() + + hrs := c._rgImpactedHTTPRoutes(rg) + gws := c._rgImpactedGateways(rg) + for _, hr := range hrs { + gws = append(gws, c._gatewayRefsOfHR(hr)...) + } + gws = UnifiedGateways(gws) + return ClassNamesOfGateways(gws) +} + +func (c *SIGCache) _rgImpactedGateways(rg *gatewayv1beta1.ReferenceGrant) []*gatewayv1beta1.Gateway { + defer utils.TimeItToPrometheus()() + + rlt := []*gatewayv1beta1.Gateway{} + if rg == nil { + return rlt + } + + for _, f := range rg.Spec.From { + if gatewayv1beta1.GroupName == f.Group && + reflect.TypeOf(gatewayv1beta1.Gateway{}).Name() == string(f.Kind) { + for _, gw := range c.Gateway { + if gw.Namespace == string(f.Namespace) { + rlt = append(rlt, gw) + } + } + } + } + + return rlt +} + +func (c *SIGCache) _rgImpactedHTTPRoutes(rg *gatewayv1beta1.ReferenceGrant) []*gatewayv1beta1.HTTPRoute { + defer utils.TimeItToPrometheus()() + + rlt := []*gatewayv1beta1.HTTPRoute{} + if rg == nil { + return rlt + } + + for _, f := range rg.Spec.From { + if gatewayv1beta1.GroupName == f.Group && + reflect.TypeOf(gatewayv1beta1.HTTPRoute{}).Name() == string(f.Kind) { + for _, hr := range c.HTTPRoute { + if hr.Namespace == string(f.Namespace) { + rlt = append(rlt, hr) + } + } + } + } + return rlt +} + // CanRefer parameter "from" and "to" MUST NOT be nil. func (c *SIGCache) CanRefer(from, to client.Object) bool { c.mutex.RLock() diff --git a/internal/pkg/parser.go b/internal/pkg/parser.go index 2847413..09e5fed 100644 --- a/internal/pkg/parser.go +++ b/internal/pkg/parser.go @@ -51,6 +51,42 @@ func ParseGatewayRelatedForClass(className string, gwObjs []*gatewayv1beta1.Gate }, nil } +func ParseAllForClass(className string) (map[string]interface{}, error) { + defer utils.TimeItToPrometheus()() + + var gwc *gatewayv1beta1.GatewayClass + if gwc = ActiveSIGs.GetGatewayClass(className); gwc == nil || + gwc.Spec.ControllerName != gatewayv1beta1.GatewayController(ActiveSIGs.ControllerName) { + return map[string]interface{}{}, nil + } + + cgwObjs := ActiveSIGs.AttachedGateways(gwc) + + rlt := map[string]interface{}{} + for _, gw := range cgwObjs { + if cfgs, err := parseGateway(gw); err != nil { + return map[string]interface{}{}, err + } else { + for k, v := range cfgs { + rlt[k] = v + } + } + hrs := ActiveSIGs.AttachedHTTPRoutes(gw) + for _, hr := range hrs { + if cfgs, err := parseHTTPRoute(className, hr); err != nil { + return map[string]interface{}{}, err + } else { + for k, v := range cfgs { + rlt[k] = v + } + } + } + } + return map[string]interface{}{ + "": rlt, + }, nil +} + // ParseServicesRelatedForAll parse all refered services func ParseServicesRelatedForAll() (map[string]interface{}, error) { @@ -125,9 +161,15 @@ func parseGateway(gw *gatewayv1beta1.Gateway) (map[string]interface{}, error) { irules := map[string][]string{} listeners := map[string]*gatewayv1beta1.Listener{} + // listener mapping for i, listener := range gw.Spec.Listeners { vsname := gwListenerName(gw, &listener) listeners[vsname] = &gw.Spec.Listeners[i] + } + + // irules mapping: when listener.Hostname is not nil + for _, listener := range gw.Spec.Listeners { + vsname := gwListenerName(gw, &listener) if listener.Hostname != nil { if _, ok := irules[vsname]; !ok { irules[vsname] = []string{} @@ -147,6 +189,7 @@ func parseGateway(gw *gatewayv1beta1.Gateway) (map[string]interface{}, error) { } } + // irules mapping: for httproutes hrs := ActiveSIGs.AttachedHTTPRoutes(gw) for _, hr := range hrs { for _, pr := range hr.Spec.ParentRefs { @@ -167,6 +210,22 @@ func parseGateway(gw *gatewayv1beta1.Gateway) (map[string]interface{}, error) { } } } + + // clientssl if exists + scrtmap, err := ActiveSIGs.AttachedSecrets(gw) + if err != nil { + return map[string]interface{}{}, err + } + for _, scrts := range scrtmap { + for i, scrt := range scrts { + cfg := parseSecret(scrt, i == 0) + for k, v := range cfg { + rlt[k] = v + } + } + } + + // virtual for _, addr := range gw.Spec.Addresses { if *addr.Type == gatewayv1beta1.IPAddressType { ipaddr := addr.Value @@ -180,15 +239,9 @@ func parseGateway(gw *gatewayv1beta1.Gateway) (map[string]interface{}, error) { ipProtocol = "tcp" case gatewayv1beta1.HTTPSProtocolType: profiles = []interface{}{map[string]string{"name": "http"}} - if listener.TLS == nil { - return map[string]interface{}{}, fmt.Errorf("listener.TLS must be set for protocol %s", listener.Protocol) - } else { - if listener.TLS.Mode == nil || *listener.TLS.Mode == gatewayv1beta1.TLSModeTerminate { - ssl_profiles := parseSSL(&listener, rlt, gw) - for _, ssl := range ssl_profiles { - profiles = append(profiles, map[string]string{"name": ssl}) - } - } + lsname := gwListenerName(gw, &listener) + for _, scrt := range scrtmap[lsname] { + profiles = append(profiles, map[string]string{"name": tlsName(scrt)}) } ipProtocol = "tcp" case gatewayv1beta1.TCPProtocolType: @@ -310,70 +363,43 @@ func parseNodesFrom(svcNamespace, svcName string, rlt map[string]interface{}) er return nil } -func parseSSL(ls *gatewayv1beta1.Listener, rlt map[string]any, gw *gatewayv1beta1.Gateway) []string { - certRefs := ls.TLS.CertificateRefs - var profileNames []string - - ns := gw.Namespace - for i, cert := range certRefs { - if cert.Namespace != nil { - ns = string(*cert.Namespace) - } - nsn := utils.Keyname(ns, string(cert.Name)) - scrt := ActiveSIGs.GetSecret(nsn) - - /* - TODO: what if existed gateway (old/new) (yaml) uses some tls secrets - are deleted by user? we need some ways to tell user the gateway yaml - is not up to date or wrong. - - 1. It prevents user from misconfiguring the gateway. - 2. It prevents bigip from dirty configuration. - - Maybe we needs status or webhook to solve this problem. - - For now, we just IGNORE the problem, and leave some dirty tls - configurations on BigIP. - */ - if scrt == nil || scrt.Type != v1.SecretTypeTLS { - continue - } - if !ActiveSIGs.CanRefer(gw, scrt) { - continue - } - crtContent := string(scrt.Data[v1.TLSCertKey]) - keyContent := string(scrt.Data[v1.TLSPrivateKeyKey]) +func parseSecret(scrt *v1.Secret, sniDefault bool) map[string]interface{} { + rlt := map[string]interface{}{} - crtName := ns + "_" + string(cert.Name) + ".crt" - keyName := ns + "_" + string(cert.Name) + ".key" + if scrt == nil || scrt.Type != v1.SecretTypeTLS { + return rlt + } - rlt["shared/file-transfer/uploads/"+crtName] = map[string]any{ - "content": crtContent, - } - rlt["shared/file-transfer/uploads/"+keyName] = map[string]any{ - "content": keyContent, - } + crtContent := string(scrt.Data[v1.TLSCertKey]) + keyContent := string(scrt.Data[v1.TLSPrivateKeyKey]) - rlt["sys/file/ssl-cert/"+crtName] = map[string]any{ - "name": crtName, - "sourcePath": "file:/var/config/rest/downloads/" + crtName, - } - rlt["sys/file/ssl-key/"+keyName] = map[string]any{ - "name": keyName, - "sourcePath": "file:/var/config/rest/downloads/" + keyName, - "passphrase": "", - } + name := tlsName(scrt) + crtName := name + ".crt" + keyName := name + ".key" - profileName := ns + "_" + string(cert.Name) + rlt["shared/file-transfer/uploads/"+crtName] = map[string]any{ + "content": crtContent, + } + rlt["shared/file-transfer/uploads/"+keyName] = map[string]any{ + "content": keyContent, + } - rlt["ltm/profile/client-ssl/"+profileName] = map[string]any{ - "name": profileName, - "cert": crtName, - "key": keyName, - "sniDefault": i == 0, - } + rlt["sys/file/ssl-cert/"+crtName] = map[string]any{ + "name": crtName, + "sourcePath": "file:/var/config/rest/downloads/" + crtName, + } + rlt["sys/file/ssl-key/"+keyName] = map[string]any{ + "name": keyName, + "sourcePath": "file:/var/config/rest/downloads/" + keyName, + "passphrase": "", + } - profileNames = append(profileNames, profileName) + rlt["ltm/profile/client-ssl/"+name] = map[string]any{ + "name": name, + "cert": crtName, + "key": keyName, + "sniDefault": sniDefault, } - return profileNames + + return rlt } diff --git a/internal/pkg/utils.go b/internal/pkg/utils.go index dd3094c..57c7767 100644 --- a/internal/pkg/utils.go +++ b/internal/pkg/utils.go @@ -1,9 +1,12 @@ package pkg import ( + "context" + "fmt" "reflect" "strings" + "github.com/f5devcentral/f5-bigip-rest-go/deployer" "github.com/f5devcentral/f5-bigip-rest-go/utils" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,6 +34,10 @@ func gwListenerName(gw *gatewayv1beta1.Gateway, ls *gatewayv1beta1.Listener) str return strings.Join([]string{"gw", gw.Namespace, gw.Name, string(ls.Name)}, ".") } +func tlsName(scrt *v1.Secret) string { + return strings.Join([]string{"scrt", scrt.Namespace, scrt.Name}, ".") +} + func RouteMatches(gwNamespace string, listener *gatewayv1beta1.Listener, routeNamespace *v1.Namespace, routeType string) bool { // actually, "listener" may be nil, but ".AllowedRoutes.Namespaces.From" will never be nil if listener == nil || listener.AllowedRoutes == nil { @@ -124,6 +131,107 @@ func stringifyRGTo(rgt *gatewayv1beta1.ReferenceGrantTo, ns string) string { return utils.Keyname(g, string(rgt.Kind), ns, n) } +func UnifiedGateways(objs []*gatewayv1beta1.Gateway) []*gatewayv1beta1.Gateway { + + m := map[string]bool{} + rlt := []*gatewayv1beta1.Gateway{} + + for _, obj := range objs { + name := utils.Keyname(obj.Namespace, obj.Name) + if _, f := m[name]; !f { + m[name] = true + rlt = append(rlt, obj) + } + } + return rlt +} + +func ClassNamesOfGateways(gws []*gatewayv1beta1.Gateway) []string { + rlt := []string{} + + for _, gw := range gws { + rlt = append(rlt, string(gw.Spec.GatewayClassName)) + } + + return utils.Unified(rlt) +} + +func DeployForEvent(ctx context.Context, impactedClasses []string, apply func() string) error { + if len(impactedClasses) == 0 { + apply() + return nil + } + + ocfgs := map[string]interface{}{} + ncfgs := map[string]interface{}{} + opcfgs := map[string]interface{}{} + npcfgs := map[string]interface{}{} + var err error + + preParsinng := func() error { + for _, n := range impactedClasses { + if ocfgs[n], err = ParseAllForClass(n); err != nil { + return err + } + } + if opcfgs, err = ParseServicesRelatedForAll(); err != nil { + return err + } + return nil + } + + postParsing := func() error { + for _, n := range impactedClasses { + if ncfgs[n], err = ParseAllForClass(n); err != nil { + return err + } + } + if npcfgs, err = ParseServicesRelatedForAll(); err != nil { + return err + } + return nil + } + + meta := "" + if err := preParsinng(); err != nil { + apply() + return err + } else { + meta = apply() + if err := postParsing(); err != nil { + return err + } + } + + drs := map[string]*deployer.DeployRequest{} + + for _, n := range impactedClasses { + ocfg := ocfgs[n].(map[string]interface{}) + ncfg := ncfgs[n].(map[string]interface{}) + drs[n] = &deployer.DeployRequest{ + Meta: fmt.Sprintf("Operating on %s for event %s", n, meta), + From: &ocfg, + To: &ncfg, + Partition: n, + Context: ctx, + } + } + + drs["cis-c-tenant"] = &deployer.DeployRequest{ + Meta: fmt.Sprintf("Updating pools for event %s", meta), + From: &opcfgs, + To: &npcfgs, + Partition: "cis-c-tenant", + Context: ctx, + } + + for _, dr := range drs { + PendingDeploys <- *dr + } + + return nil +} + func (rgft *ReferenceGrantFromTo) set(rg *gatewayv1beta1.ReferenceGrant) { ns := rg.Namespace for _, f := range rg.Spec.From { @@ -168,3 +276,18 @@ func (rgft *ReferenceGrantFromTo) exists(from, to string) bool { } } } + +// TODO: combine this function with that in webhooks package +func validateSecretType(group *gatewayv1beta1.Group, kind *gatewayv1beta1.Kind) error { + g, k := v1.GroupName, reflect.TypeOf(v1.Secret{}).Name() + if group != nil { + g = string(*group) + } + if kind != nil { + k = string(*kind) + } + if g != v1.GroupName || k != reflect.TypeOf(v1.Secret{}).Name() { + return fmt.Errorf("not Secret type: '%s'", utils.Keyname(g, k)) + } + return nil +} diff --git a/tests/systest/controllers_basics_test.go b/tests/systest/controllers_basics_test.go index 307bafd..7c5f328 100644 --- a/tests/systest/controllers_basics_test.go +++ b/tests/systest/controllers_basics_test.go @@ -137,4 +137,16 @@ func checkResourcesAsExpected() { slog.Infof("finished bigip resources checking") } -// TODO: Add tests for updating gateway.yaml with addresses changed +// TODO: Add tests for +// updating gateway.yaml with addresses changed +// -> check the virtual address is updated, legacy ones are removed. +// multiple addresses in the gateway +// -> check multiple virtual created +// multiple gateways using the same address. +// -> check virtual address are shared, and still exists when deleting one gateway. +// multiple httproutes(of different classes) referring the same service +// -> service is created and shared; still exists when deleting one httproute. +// referencegrant for gateway <-> secret and httproute <-> service +// -> check service is upserted or deleted as expected. +// secret reconciler test +// -> check gateway tls is up-to-date when secret is CUD-ed. diff --git a/tests/systest/templates/tls/secret.yaml b/tests/systest/templates/tls/secret.yaml index 95c3c15..1e75729 100644 --- a/tests/systest/templates/tls/secret.yaml +++ b/tests/systest/templates/tls/secret.yaml @@ -6,5 +6,5 @@ data: kind: Secret metadata: name: {{ .name }} - namespace: default + namespace: {{ .namespace }} type: kubernetes.io/tls \ No newline at end of file diff --git a/tests/systest/tls_test.go b/tests/systest/tls_test.go index b61f2b1..6dfe159 100644 --- a/tests/systest/tls_test.go +++ b/tests/systest/tls_test.go @@ -23,10 +23,11 @@ var yamlTLSTpl embed.FS var _ = Describe("TLS TEST", Label("tls"), Ordered, func() { const ( tlsGatewayClassName = "bigip-tls" - partition - tlsSecretName = "tls-basic" - tlsGatewayName = "tls-gateway" - ipAddress = "192.168.10.123" + partition = tlsGatewayClassName + secretNamespace = "default" + tlsSecretName = "tls-basic" + tlsGatewayName = "tls-gateway" + ipAddress = "192.168.10.123" ) var ca, caPrivKey, serverCert, serverPrivKey []byte @@ -67,9 +68,10 @@ var _ = Describe("TLS TEST", Label("tls"), Ordered, func() { Expect(k8sResource( "templates/tls/secret.yaml", map[string]interface{}{ - "name": tlsSecretName, - "cert": crt, - "key": key, + "name": tlsSecretName, + "namespace": secretNamespace, + "cert": crt, + "key": key, }, k8s.Apply, )).To(Succeed()) @@ -115,7 +117,7 @@ var _ = Describe("TLS TEST", Label("tls"), Ordered, func() { When("Both TLS Secret and TLS Gateway have been created on K8S", func() { It("Check ssl-cert has been created on BigIP", func() { kind, partition, subfolder := "sys/file/ssl-cert", partition, "" - name := fmt.Sprintf("default_%s.crt", tlsSecretName) + name := tlsName(secretNamespace, tlsSecretName) + ".crt" Eventually(checkExist).WithContext(ctx).WithArguments(kind, name, partition, subfolder, bip.Exist). WithTimeout(time.Second * 10).ProbeEvery(time.Millisecond * 500). Should(BeTrue()) @@ -158,7 +160,7 @@ var _ = Describe("TLS TEST", Label("tls"), Ordered, func() { It("Check ssl-cert has been deleted from BigIP", func() { kind, partition, subfolder := "sys/file/ssl-cert", partition, "" - name := fmt.Sprintf("default_%s.crt", tlsSecretName) + name := tlsName(secretNamespace, tlsSecretName) + ".crt" Eventually(checkExist).WithContext(ctx).WithArguments(kind, name, partition, subfolder, bip.Exist). WithTimeout(time.Second * 10).ProbeEvery(time.Millisecond * 500). Should(BeFalse()) @@ -175,9 +177,10 @@ var _ = Describe("TLS TEST", Label("tls"), Ordered, func() { Expect(k8sResource( "templates/tls/secret.yaml", map[string]interface{}{ - "name": tlsSecretName, - "cert": crt, - "key": key, + "name": tlsSecretName, + "namespace": secretNamespace, + "cert": crt, + "key": key, }, k8s.Delete, )).To(Succeed()) @@ -233,3 +236,7 @@ func checkExist(cxt context.Context, kind, name, partition, subfolder string, ac return false } } + +func tlsName(ns, n string) string { + return strings.Join([]string{"scrt", ns, n}, ".") +}