diff --git a/Makefile b/Makefile index 3b69a45d6..0c6e88276 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ prod-quick: prod-build-quick prod-build-quick: pre-build @echo "Quick build without running tests..." - docker build --build-arg RUN_TESTS=0 --build-arg BUILD_VERSION=$(BUILD_VERSION) --build-arg BUILD_INFO=$(BUILD_INFO) -t k8s-bigip-ctlr:latest -f build-tools/Dockerfile.$(BASE_OS) . + docker build --platform linux/amd64 --build-arg RUN_TESTS=0 --build-arg BUILD_VERSION=$(BUILD_VERSION) --build-arg BUILD_INFO=$(BUILD_INFO) -t k8s-bigip-ctlr:latest -f build-tools/Dockerfile.$(BASE_OS) . dev-license: pre-build @echo "Running with tests and licenses generated will be in all_attributions.txt..." diff --git a/config/apis/cis/v1/types.go b/config/apis/cis/v1/types.go index d755e95e8..422a33c6f 100644 --- a/config/apis/cis/v1/types.go +++ b/config/apis/cis/v1/types.go @@ -27,6 +27,12 @@ type CustomResourceStatus struct { Error string `json:"error,omitempty"` } +type TLSProfileStatus struct { + Status string `json:"status,omitempty"` + LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + Error string `json:"error,omitempty"` +} + // VirtualServerSpec is the spec of the VirtualServer resource. type VirtualServerSpec struct { Host string `json:"host,omitempty"` @@ -196,13 +202,15 @@ type VirtualServerList struct { // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:subresource:status // TLSProfile is a Custom Resource for TLS server type TLSProfile struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec TLSProfileSpec `json:"spec"` + Spec TLSProfileSpec `json:"spec"` + Status TLSProfileStatus `json:"status,omitempty"` } // TLSProfileSpec is spec for TLSServer diff --git a/config/apis/cis/v1/zz_generated.deepcopy.go b/config/apis/cis/v1/zz_generated.deepcopy.go index 62af94c4f..7851d7aab 100644 --- a/config/apis/cis/v1/zz_generated.deepcopy.go +++ b/config/apis/cis/v1/zz_generated.deepcopy.go @@ -63,6 +63,44 @@ func (in *AnalyticsProfiles) DeepCopy() *AnalyticsProfiles { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientSSLParams) DeepCopyInto(out *ClientSSLParams) { + *out = *in + if in.RenegotiationEnabled != nil { + in, out := &in.RenegotiationEnabled, &out.RenegotiationEnabled + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientSSLParams. +func (in *ClientSSLParams) DeepCopy() *ClientSSLParams { + if in == nil { + return nil + } + out := new(ClientSSLParams) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomResourceStatus) DeepCopyInto(out *CustomResourceStatus) { + *out = *in + in.LastUpdated.DeepCopyInto(&out.LastUpdated) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceStatus. +func (in *CustomResourceStatus) DeepCopy() *CustomResourceStatus { + if in == nil { + return nil + } + out := new(CustomResourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DNSPool) DeepCopyInto(out *DNSPool) { *out = *in @@ -195,13 +233,46 @@ func (in *ExternalDNSSpec) DeepCopy() *ExternalDNSSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPProfiles) DeepCopyInto(out *HTTPProfiles) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPProfiles. +func (in *HTTPProfiles) DeepCopy() *HTTPProfiles { + if in == nil { + return nil + } + out := new(HTTPProfiles) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostPersistence) DeepCopyInto(out *HostPersistence) { + *out = *in + out.PersistMetaData = in.PersistMetaData + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostPersistence. +func (in *HostPersistence) DeepCopy() *HostPersistence { + if in == nil { + return nil + } + out := new(HostPersistence) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IngressLink) DeepCopyInto(out *IngressLink) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -269,6 +340,19 @@ func (in *IngressLinkSpec) DeepCopyInto(out *IngressLinkSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.MultiClusterServices != nil { + in, out := &in.MultiClusterServices, &out.MultiClusterServices + *out = make([]MultiClusterServiceReference, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.TLS.DeepCopyInto(&out.TLS) + if in.Monitors != nil { + in, out := &in.Monitors, &out.Monitors + *out = make([]Monitor, len(*in)) + copy(*out, *in) + } return } @@ -311,6 +395,7 @@ func (in *L3PolicySpec) DeepCopy() *L3PolicySpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *L7PolicySpec) DeepCopyInto(out *L7PolicySpec) { *out = *in + out.ProfileAdapt = in.ProfileAdapt return } @@ -394,6 +479,22 @@ func (in *MultiPoolPersistence) DeepCopy() *MultiPoolPersistence { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PersistMetaData) DeepCopyInto(out *PersistMetaData) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistMetaData. +func (in *PersistMetaData) DeepCopy() *PersistMetaData { + if in == nil { + return nil + } + out := new(PersistMetaData) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Policy) DeepCopyInto(out *Policy) { *out = *in @@ -468,6 +569,7 @@ func (in *PolicySpec) DeepCopyInto(out *PolicySpec) { } in.Profiles.DeepCopyInto(&out.Profiles) out.PoolSettings = in.PoolSettings + in.DefaultPool.DeepCopyInto(&out.DefaultPool) return } @@ -498,6 +600,22 @@ func (in *PoolSettingsSpec) DeepCopy() *PoolSettingsSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProfileAdapt) DeepCopyInto(out *ProfileAdapt) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileAdapt. +func (in *ProfileAdapt) DeepCopy() *ProfileAdapt { + if in == nil { + return nil + } + out := new(ProfileAdapt) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProfileHTTP2) DeepCopyInto(out *ProfileHTTP2) { *out = *in @@ -518,6 +636,7 @@ func (in *ProfileHTTP2) DeepCopy() *ProfileHTTP2 { func (in *ProfileSpec) DeepCopyInto(out *ProfileSpec) { *out = *in out.TCP = in.TCP + out.HTTPProfiles = in.HTTPProfiles out.HTTP2 = in.HTTP2 if in.LogProfiles != nil { in, out := &in.LogProfiles, &out.LogProfiles @@ -621,6 +740,27 @@ func (in *SSLProfiles) DeepCopy() *SSLProfiles { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServerSSLParams) DeepCopyInto(out *ServerSSLParams) { + *out = *in + if in.RenegotiationEnabled != nil { + in, out := &in.RenegotiationEnabled, &out.RenegotiationEnabled + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSSLParams. +func (in *ServerSSLParams) DeepCopy() *ServerSSLParams { + if in == nil { + return nil + } + out := new(ServerSSLParams) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceAddress) DeepCopyInto(out *ServiceAddress) { *out = *in @@ -650,6 +790,8 @@ func (in *TLS) DeepCopyInto(out *TLS) { *out = make([]string, len(*in)) copy(*out, *in) } + in.ClientSSLParams.DeepCopyInto(&out.ClientSSLParams) + in.ServerSSLParams.DeepCopyInto(&out.ServerSSLParams) return } @@ -669,6 +811,7 @@ func (in *TLSProfile) DeepCopyInto(out *TLSProfile) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } @@ -690,6 +833,27 @@ func (in *TLSProfile) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSProfileCipher) DeepCopyInto(out *TLSProfileCipher) { + *out = *in + if in.DisableTLSVersions != nil { + in, out := &in.DisableTLSVersions, &out.DisableTLSVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSProfileCipher. +func (in *TLSProfileCipher) DeepCopy() *TLSProfileCipher { + if in == nil { + return nil + } + out := new(TLSProfileCipher) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSProfileList) DeepCopyInto(out *TLSProfileList) { *out = *in @@ -732,6 +896,7 @@ func (in *TLSProfileSpec) DeepCopyInto(out *TLSProfileSpec) { copy(*out, *in) } in.TLS.DeepCopyInto(&out.TLS) + in.TLSCipher.DeepCopyInto(&out.TLSCipher) return } @@ -745,6 +910,49 @@ func (in *TLSProfileSpec) DeepCopy() *TLSProfileSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSProfileStatus) DeepCopyInto(out *TLSProfileStatus) { + *out = *in + in.LastUpdated.DeepCopyInto(&out.LastUpdated) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSProfileStatus. +func (in *TLSProfileStatus) DeepCopy() *TLSProfileStatus { + if in == nil { + return nil + } + out := new(TLSProfileStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSTransportServer) DeepCopyInto(out *TLSTransportServer) { + *out = *in + if in.ClientSSLs != nil { + in, out := &in.ClientSSLs, &out.ClientSSLs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ServerSSLs != nil { + in, out := &in.ServerSSLs, &out.ServerSSLs + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSTransportServer. +func (in *TLSTransportServer) DeepCopy() *TLSTransportServer { + if in == nil { + return nil + } + out := new(TLSTransportServer) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TSPool) DeepCopyInto(out *TSPool) { *out = *in @@ -760,6 +968,13 @@ func (in *TSPool) DeepCopyInto(out *TSPool) { *out = new(int32) **out = **in } + if in.AlternateBackends != nil { + in, out := &in.AlternateBackends, &out.AlternateBackends + *out = make([]AlternateBackend, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.MultiClusterServices != nil { in, out := &in.MultiClusterServices, &out.MultiClusterServices *out = make([]MultiClusterServiceReference, len(*in)) @@ -786,7 +1001,7 @@ func (in *TransportServer) DeepCopyInto(out *TransportServer) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -861,6 +1076,7 @@ func (in *TransportServerSpec) DeepCopyInto(out *TransportServerSpec) { copy(*out, *in) } out.Profiles = in.Profiles + in.TLS.DeepCopyInto(&out.TLS) return } @@ -923,7 +1139,7 @@ func (in *VirtualServer) DeepCopyInto(out *VirtualServer) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -981,6 +1197,11 @@ func (in *VirtualServerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualServerSpec) DeepCopyInto(out *VirtualServerSpec) { *out = *in + if in.HostAliases != nil { + in, out := &in.HostAliases, &out.HostAliases + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.AdditionalVirtualServerAddresses != nil { in, out := &in.AdditionalVirtualServerAddresses, &out.AdditionalVirtualServerAddresses *out = make([]string, len(*in)) @@ -1020,6 +1241,8 @@ func (in *VirtualServerSpec) DeepCopyInto(out *VirtualServerSpec) { *out = new(bool) **out = **in } + out.HostPersistence = in.HostPersistence + out.ProfileAdapt = in.ProfileAdapt return } @@ -1032,19 +1255,3 @@ func (in *VirtualServerSpec) DeepCopy() *VirtualServerSpec { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CustomResourceStatus) DeepCopyInto(out *CustomResourceStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualServerStatus. -func (in *CustomResourceStatus) DeepCopy() *CustomResourceStatus { - if in == nil { - return nil - } - out := new(CustomResourceStatus) - in.DeepCopyInto(out) - return out -} diff --git a/config/client/clientset/versioned/typed/cis/v1/fake/fake_tlsprofile.go b/config/client/clientset/versioned/typed/cis/v1/fake/fake_tlsprofile.go index 78ce9c934..098517a6a 100644 --- a/config/client/clientset/versioned/typed/cis/v1/fake/fake_tlsprofile.go +++ b/config/client/clientset/versioned/typed/cis/v1/fake/fake_tlsprofile.go @@ -102,6 +102,18 @@ func (c *FakeTLSProfiles) Update(ctx context.Context, tLSProfile *cisv1.TLSProfi return obj.(*cisv1.TLSProfile), err } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeTLSProfiles) UpdateStatus(ctx context.Context, tLSProfile *cisv1.TLSProfile, opts v1.UpdateOptions) (*cisv1.TLSProfile, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(tlsprofilesResource, "status", c.ns, tLSProfile), &cisv1.TLSProfile{}) + + if obj == nil { + return nil, err + } + return obj.(*cisv1.TLSProfile), err +} + // Delete takes name of the tLSProfile and deletes it. Returns an error if one occurs. func (c *FakeTLSProfiles) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. diff --git a/config/client/clientset/versioned/typed/cis/v1/tlsprofile.go b/config/client/clientset/versioned/typed/cis/v1/tlsprofile.go index 33ed12f45..d96a93bc9 100644 --- a/config/client/clientset/versioned/typed/cis/v1/tlsprofile.go +++ b/config/client/clientset/versioned/typed/cis/v1/tlsprofile.go @@ -40,6 +40,7 @@ type TLSProfilesGetter interface { type TLSProfileInterface interface { Create(ctx context.Context, tLSProfile *v1.TLSProfile, opts metav1.CreateOptions) (*v1.TLSProfile, error) Update(ctx context.Context, tLSProfile *v1.TLSProfile, opts metav1.UpdateOptions) (*v1.TLSProfile, error) + UpdateStatus(ctx context.Context, tLSProfile *v1.TLSProfile, opts metav1.UpdateOptions) (*v1.TLSProfile, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.TLSProfile, error) @@ -135,6 +136,22 @@ func (c *tLSProfiles) Update(ctx context.Context, tLSProfile *v1.TLSProfile, opt return } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *tLSProfiles) UpdateStatus(ctx context.Context, tLSProfile *v1.TLSProfile, opts metav1.UpdateOptions) (result *v1.TLSProfile, err error) { + result = &v1.TLSProfile{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tlsprofiles"). + Name(tLSProfile.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tLSProfile). + Do(ctx). + Into(result) + return +} + // Delete takes name of the tLSProfile and deletes it. Returns an error if one occurs. func (c *tLSProfiles) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { return c.client.Delete(). diff --git a/docs/config_examples/customResourceDefinitions/incubator/customresourcedefinitions.yml b/docs/config_examples/customResourceDefinitions/incubator/customresourcedefinitions.yml index e92ada471..596b5234e 100644 --- a/docs/config_examples/customResourceDefinitions/incubator/customresourcedefinitions.yml +++ b/docs/config_examples/customResourceDefinitions/incubator/customresourcedefinitions.yml @@ -604,6 +604,19 @@ spec: enum: [ bigip, secret ] required: - termination + status: + type: object + properties: + status: + type: string + default: Pending + lastUpdated: + type: string + error: + type: string + subresources: + status: { } + --- apiVersion: apiextensions.k8s.io/v1 diff --git a/go.mod b/go.mod index 2ea59f477..a49849e9f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.0 require ( github.com/F5Networks/f5-ipam-controller v0.1.8 - github.com/f5devcentral/go-bigip v0.0.0-20250731061239-628be0470a84 + github.com/f5devcentral/go-bigip v0.0.0-20250808131030-ef245d09723a github.com/f5devcentral/go-bigip/f5teem v0.0.0-20250731061239-628be0470a84 github.com/f5devcentral/mockhttpclient v0.0.0-20210630101009-cc12e8b81051 github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 0a03289e3..8b5e61f38 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/f5devcentral/go-bigip v0.0.0-20250731061239-628be0470a84 h1:gu36kWKVu+5ZyF+kxUpZJfWGnDQErCaVQszMmiyBsXU= -github.com/f5devcentral/go-bigip v0.0.0-20250731061239-628be0470a84/go.mod h1:0Lkr0fBU6O1yBxF2mt9JFwXpaFbIb/wAY7oM3dMJDdA= +github.com/f5devcentral/go-bigip v0.0.0-20250808131030-ef245d09723a h1:gjr9xlHrWMh5sKEAFHNgwKUvzXFawE8gEX+NiGB9Wwk= +github.com/f5devcentral/go-bigip v0.0.0-20250808131030-ef245d09723a/go.mod h1:0Lkr0fBU6O1yBxF2mt9JFwXpaFbIb/wAY7oM3dMJDdA= github.com/f5devcentral/go-bigip/f5teem v0.0.0-20250731061239-628be0470a84 h1:xnwDbMt3bvEtl7jmJS90VtLL4tnukB/ULy4HyCtfCXc= github.com/f5devcentral/go-bigip/f5teem v0.0.0-20250731061239-628be0470a84/go.mod h1:r7o5I22EvO+fps2u10bz4ZUlTlNHopQSWzVcW19hK3U= github.com/f5devcentral/mockhttpclient v0.0.0-20210630101009-cc12e8b81051 h1:q2HUQbEFbJ4EIECxyKpnZ5+wz/HLAndzSYmd0VS8c4M= diff --git a/helm-charts/f5-bigip-ctlr/templates/f5-bigip-ctlr-clusterrole.yaml b/helm-charts/f5-bigip-ctlr/templates/f5-bigip-ctlr-clusterrole.yaml index 3b4d3447b..acc242273 100644 --- a/helm-charts/f5-bigip-ctlr/templates/f5-bigip-ctlr-clusterrole.yaml +++ b/helm-charts/f5-bigip-ctlr/templates/f5-bigip-ctlr-clusterrole.yaml @@ -68,6 +68,7 @@ rules: - transportservers/status - virtualservers/status - ingresslinks/status + - tlsprofiles/status - policies {{- if .Values.args.ipam }} - verbs: diff --git a/pkg/bigiphandler/bigiphandler.go b/pkg/bigiphandler/bigiphandler.go index 0253bc8af..c33dea3a0 100644 --- a/pkg/bigiphandler/bigiphandler.go +++ b/pkg/bigiphandler/bigiphandler.go @@ -1,6 +1,7 @@ package bigiphandler import ( + "context" // "crypto/tls" "crypto/x509" "fmt" @@ -51,6 +52,7 @@ type BigIPClient interface { GetUniversalPersistenceProfile(name string) (*bigip.UniversalPersistenceProfile, error) GetSSLPersistenceProfile(name string) (*bigip.SSLPersistenceProfile, error) GetAnalyticsProfile(name string) (*bigip.AnalyticsProfile, error) + GetMonitor(name string, parent string) (*bigip.Monitor, error) } func CreateSession(host, token, userAgent, trustedCerts string, insecure, teem bool) *bigip.BigIP { @@ -120,6 +122,7 @@ type BigIPHandlerInterface interface { GetHTMLProfile(name string) (any, error) GetFTPProfile(name string) (any, error) GetHTTPCompressionProfile(name string) (any, error) + GetMonitor(name string) (*bigip.Monitor, error) // Add more methods as needed for other BIG-IP resources } @@ -528,3 +531,62 @@ func (handler *BigIPHandler) GetHTTPCompressionProfile(name string) (any, error) } return profile, nil } + +func (handler *BigIPHandler) GetMonitor(name string) (*bigip.Monitor, error) { + // monitorTypes := []string{"http", "tcp", "icmp", "https", "gateway icmp"} + + var f5MonitorParentTypes = []string{ + "http", "https", "tcp", "icmp", "gateway_icmp", "dns", "external", "ftp", "imap", + "inband", "ldap", "mssql", "mysql", "oracle", "pop3", "postgresql", "radius", + "radius_accounting", "real_server", "rpc", "sip", "smtp", "snmp_dca", "snmp_dca_base", + "soap", "tcp_echo", "tcp_half_open", "udp", "virtual_location", + "diameter", "firepass", "http2", "module_score", "mqtt", "nntp", + "sasp", "scripted", "smb", "wap", "wmi", + } + + type result struct { + monitor *bigip.Monitor + err error + } + + resultCh := make(chan result, len(f5MonitorParentTypes)) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var wg sync.WaitGroup + + for _, mType := range f5MonitorParentTypes { + wg.Add(1) + go func(mType string) { + defer wg.Done() + select { + case <-ctx.Done(): + return + default: + monitor, err := handler.Bigip.GetMonitor(name, mType) + if err == nil { + log.Debugf("Found monitor %s of type %s,monitor: %v", name, mType, monitor) + // Found a valid monitor, send result and cancel others + resultCh <- result{monitor, nil} + cancel() + } + } + }(mType) + } + + // Close result channel once all goroutines are done + go func() { + wg.Wait() + close(resultCh) + }() + + for res := range resultCh { + // Return the first successful monitor + if res.err == nil { + return res.monitor, nil + } + } + + // If no monitor found + return nil, fmt.Errorf("monitor %s not found", name) +} diff --git a/pkg/bigiphandler/bigiphandler_test.go b/pkg/bigiphandler/bigiphandler_test.go index bfdea4fdc..dfe3ac897 100644 --- a/pkg/bigiphandler/bigiphandler_test.go +++ b/pkg/bigiphandler/bigiphandler_test.go @@ -296,6 +296,11 @@ type MockBigIPClientForPersistence struct { errorMsg string } +func (m *MockBigIPClientForPersistence) GetMonitor(name string, parent string) (*bigip.Monitor, error) { + //TODO implement me + return nil, nil +} + func NewMockBigIPClientForPersistence(successfulProfile string, shouldError bool, errorMsg string) *MockBigIPClientForPersistence { return &MockBigIPClientForPersistence{ successfulProfile: successfulProfile, @@ -417,6 +422,10 @@ func (m *MockBigIPClientForPersistence) GetAnalyticsProfile(name string) (*bigip return &bigip.AnalyticsProfile{Name: name}, nil } +func (m *MockBigIPClient) GetMonitor(name, parent string) (*bigip.Monitor, error) { + return &bigip.Monitor{Name: name}, nil +} + // Persistence profile methods that control the test behavior func (m *MockBigIPClientForPersistence) GetCookiePersistenceProfile(name string) (*bigip.CookiePersistenceProfile, error) { if m.successfulProfile == "cookie" && !m.shouldError { diff --git a/pkg/controller/clusterHandler.go b/pkg/controller/clusterHandler.go index b14998d88..98ac1bc10 100644 --- a/pkg/controller/clusterHandler.go +++ b/pkg/controller/clusterHandler.go @@ -445,6 +445,56 @@ func (ch *ClusterHandler) UpdateResourceStatus(rscStatus ResourceStatus) { il.Status = rscStatus.ResourceObj.(cisv1.CustomResourceStatus) } _, updateErr = clusterConfig.kubeCRClient.CisV1().IngressLinks(il.ObjectMeta.Namespace).UpdateStatus(context.TODO(), il, metav1.UpdateOptions{}) + case TLSProfile: + informer := ch.getCRInformerForCluster(rscStatus.ResourceKey.clusterName, rscStatus.ResourceKey.namespace) + if informer == nil { + updateErr = fmt.Errorf("failed to get informer") + break + } + var tlsProfile *cisv1.TLSProfile + var found bool + // Get the latest version of the resource. + // If status update is for delete event which is indicated by clearKeyFromCache flag, then use kubeclient as it + // helps in identifying whether resource is actually deleted or it's label has been removed, otherwise use informers, + // which is usually faster. + if !rscStatus.ClearKeyFromCache { + item, exists, err := informer.tlsInformer.GetIndexer().GetByKey(rscStatus.ResourceKey.namespace + "/" + + rscStatus.ResourceKey.name) + if err != nil { + updateErr = fmt.Errorf("failed to fetch TLSProfile. %v", err) + break + } else if !exists { + // Object is deleted + return + } + tlsProfile, found = item.(*cisv1.TLSProfile) + } else { + var err error + tlsProfile, err = clusterConfig.kubeCRClient.CisV1().TLSProfiles(rscStatus.ResourceKey.namespace). + Get(context.Background(), rscStatus.ResourceKey.name, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + // Resource is not found indicates it's actually deleted, so it eliminates the case of watch label + // removal from the resource. So, no need to go for status update. + return + } else { + updateErr = fmt.Errorf("failed to fetch TLSProfile. %v", err) + break + } + } + found = true + } + if found { + tlsStatus := rscStatus.ResourceObj.(cisv1.CustomResourceStatus) + tlsProfile.Status = cisv1.TLSProfileStatus{ + Status: tlsStatus.Status, + Error: tlsStatus.Error, + LastUpdated: tlsStatus.LastUpdated, + } + } + _, updateErr = clusterConfig.kubeCRClient.CisV1().TLSProfiles(tlsProfile.ObjectMeta.Namespace). + UpdateStatus(context.TODO(), tlsProfile, metav1.UpdateOptions{}) + case Service: informer := ch.getCommonInformerForCluster(rscStatus.ResourceKey.clusterName, rscStatus.ResourceKey.namespace) if informer == nil { diff --git a/pkg/controller/controller_suit_test.go b/pkg/controller/controller_suit_test.go index 2b4b2ed40..a00597e70 100644 --- a/pkg/controller/controller_suit_test.go +++ b/pkg/controller/controller_suit_test.go @@ -54,6 +54,10 @@ type ( } ) +func (m *mockBigIPHandler) GetMonitor(name string) (*bigip.Monitor, error) { + return nil, nil +} + func newMockController() *mockController { return &mockController{ Controller: &Controller{ diff --git a/pkg/controller/informers.go b/pkg/controller/informers.go index 198ca2cf1..6de8891f0 100644 --- a/pkg/controller/informers.go +++ b/pkg/controller/informers.go @@ -958,6 +958,16 @@ func (ctlr *Controller) enqueueDeletedVirtualServer(obj interface{}) { func (ctlr *Controller) enqueueTLSProfile(obj interface{}, event string) { tls := obj.(*cisapiv1.TLSProfile) + if !ctlr.webhookServer.IsWebhookServerRunning() { // check if the virutal server matches all the requirements. + tlsKey := tls.ObjectMeta.Namespace + "/" + tls.ObjectMeta.Name + valid, errMsg := ctlr.checkValidTLSProfile(tls) + if !valid { + log.Errorf("TLSProfile %s is not valid: %s", tlsKey, errMsg) + ctlr.updateTLSProfileStatus(tls, StatusError, errors.New(errMsg)) + return + } + } + ctlr.updateTLSProfileStatus(tls, "Ok", nil) log.Debugf("Enqueueing TLSProfile: %v", tls) key := &rqKey{ namespace: tls.ObjectMeta.Namespace, diff --git a/pkg/controller/resourceConfig.go b/pkg/controller/resourceConfig.go index 80de2bf2f..e629e01c9 100644 --- a/pkg/controller/resourceConfig.go +++ b/pkg/controller/resourceConfig.go @@ -1746,36 +1746,37 @@ func (ctlr *Controller) handleVirtualServerTLS( // validate TLSProfile // validation includes valid parameters for the type of termination(edge, re-encrypt and Pass-through) -func validateTLSProfile(tls *cisapiv1.TLSProfile) bool { +func validateTLSProfile(tls *cisapiv1.TLSProfile) (bool, error) { // validation for re-encrypt termination + var err error if tls.Spec.TLS.Termination == "reencrypt" { // Should contain both client and server SSL profiles if (tls.Spec.TLS.ClientSSL == "" || tls.Spec.TLS.ServerSSL == "") && (len(tls.Spec.TLS.ClientSSLs) == 0 || len(tls.Spec.TLS.ServerSSLs) == 0) { - log.Errorf("TLSProfile %s of type re-encrypt termination should contain both "+ + err = fmt.Errorf("TLSProfile %s of type re-encrypt termination should contain both "+ "ClientSSLs and ServerSSLs", tls.ObjectMeta.Name) - return false + return false, err } } else if tls.Spec.TLS.Termination == "edge" { // Should contain only client SSL if tls.Spec.TLS.ClientSSL == "" && len(tls.Spec.TLS.ClientSSLs) == 0 { - log.Errorf("TLSProfile %s of type edge termination should contain ClientSSLs", + err = fmt.Errorf("TLSProfile %s of type edge termination should contain ClientSSLs", tls.ObjectMeta.Name) - return false + return false, err } if tls.Spec.TLS.ServerSSL != "" || len(tls.Spec.TLS.ServerSSLs) != 0 { - log.Errorf("TLSProfile %s of type edge termination should NOT contain ServerSSLs", + err = fmt.Errorf("TLSProfile %s of type edge termination should NOT contain ServerSSLs", tls.ObjectMeta.Name) - return false + return false, err } } else { // Pass-through if (tls.Spec.TLS.ClientSSL != "") || (tls.Spec.TLS.ServerSSL != "") || len(tls.Spec.TLS.ClientSSLs) != 0 || len(tls.Spec.TLS.ServerSSLs) != 0 { - log.Errorf("TLSProfile %s of type Pass-through termination should NOT contain either "+ + err = fmt.Errorf("TLSProfile %s of type Pass-through termination should NOT contain either "+ "ClientSSLs or ServerSSLs", tls.ObjectMeta.Name) - return false + return false, err } } - return true + return true, nil } // ConvertStringToProfileRef converts strings to profile references diff --git a/pkg/controller/resourceConfig_test.go b/pkg/controller/resourceConfig_test.go index b60982ec0..bcf85109b 100644 --- a/pkg/controller/resourceConfig_test.go +++ b/pkg/controller/resourceConfig_test.go @@ -1017,13 +1017,13 @@ var _ = Describe("Resource Config Tests", func() { }, ) - ok := validateTLSProfile(tlsRenc) + ok, _ := validateTLSProfile(tlsRenc) Expect(ok).To(BeTrue(), "TLS Re-encryption Validation Failed") - ok = validateTLSProfile(tlsEdge) + ok, _ = validateTLSProfile(tlsEdge) Expect(ok).To(BeTrue(), "TLS Edge Validation Failed") - ok = validateTLSProfile(tlsPst) + ok, _ = validateTLSProfile(tlsPst) Expect(ok).To(BeTrue(), "TLS Passthrough Validation Failed") // Negative cases @@ -1031,17 +1031,17 @@ var _ = Describe("Resource Config Tests", func() { tlsEdge.Spec.TLS.Termination = TLSReencrypt tlsRenc.Spec.TLS.Termination = TLSPassthrough - ok = validateTLSProfile(tlsRenc) + ok, _ = validateTLSProfile(tlsRenc) Expect(ok).To(BeFalse(), "TLS Re-encryption Validation Failed") - ok = validateTLSProfile(tlsEdge) + ok, _ = validateTLSProfile(tlsEdge) Expect(ok).To(BeFalse(), "TLS Edge Validation Failed") - ok = validateTLSProfile(tlsPst) + ok, _ = validateTLSProfile(tlsPst) Expect(ok).To(BeFalse(), "TLS Passthrough Validation Failed") tlsRenc.Spec.TLS.Termination = TLSEdge - ok = validateTLSProfile(tlsRenc) + ok, _ = validateTLSProfile(tlsRenc) Expect(ok).To(BeFalse(), "TLS Edge Validation Failed") }) @@ -1095,16 +1095,16 @@ var _ = Describe("Resource Config Tests", func() { }, ) - ok := validateTLSProfile(tlsRenc) + ok, _ := validateTLSProfile(tlsRenc) Expect(ok).To(BeTrue(), "TLS Re-encryption Validation Failed") - ok = validateTLSProfile(tlsRencComb) + ok, _ = validateTLSProfile(tlsRencComb) Expect(ok).To(BeFalse(), "TLS Re-encryption Validation Failed") - ok = validateTLSProfile(tlsEdge) + ok, _ = validateTLSProfile(tlsEdge) Expect(ok).To(BeTrue(), "TLS Edge Validation Failed") - ok = validateTLSProfile(tlsPst) + ok, _ = validateTLSProfile(tlsPst) Expect(ok).To(BeTrue(), "TLS Passthrough Validation Failed") // Negative cases @@ -1112,17 +1112,17 @@ var _ = Describe("Resource Config Tests", func() { tlsEdge.Spec.TLS.Termination = TLSReencrypt tlsRenc.Spec.TLS.Termination = TLSPassthrough - ok = validateTLSProfile(tlsRenc) + ok, _ = validateTLSProfile(tlsRenc) Expect(ok).To(BeFalse(), "TLS Re-encryption Validation Failed") - ok = validateTLSProfile(tlsEdge) + ok, _ = validateTLSProfile(tlsEdge) Expect(ok).To(BeFalse(), "TLS Edge Validation Failed") - ok = validateTLSProfile(tlsPst) + ok, _ = validateTLSProfile(tlsPst) Expect(ok).To(BeFalse(), "TLS Passthrough Validation Failed") tlsRenc.Spec.TLS.Termination = TLSEdge - ok = validateTLSProfile(tlsRenc) + ok, _ = validateTLSProfile(tlsRenc) Expect(ok).To(BeFalse(), "TLS Edge Validation Failed") }) diff --git a/pkg/controller/statusUpdateUtils.go b/pkg/controller/statusUpdateUtils.go index 1a21f7617..640f05130 100644 --- a/pkg/controller/statusUpdateUtils.go +++ b/pkg/controller/statusUpdateUtils.go @@ -55,6 +55,27 @@ func (ctlr *Controller) updateTSStatus(ts *cisapiv1.TransportServer, ip string, } } +// updateTLSProfileStatus prepares the status update for TLSProfile +func (ctlr *Controller) updateTLSProfileStatus(tlsProfile *cisapiv1.TLSProfile, status string, err error) { + tlsStatus := cisapiv1.CustomResourceStatus{ + Status: status, + LastUpdated: metav1.Now(), + } + if err != nil { + tlsStatus.Error = err.Error() + } + ctlr.multiClusterHandler.statusUpdate.ResourceStatusUpdateChan <- ResourceStatus{ + ResourceObj: tlsStatus, + ResourceKey: resourceRef{ + kind: TLSProfile, + name: tlsProfile.Name, + namespace: tlsProfile.Namespace, + clusterName: ctlr.multiClusterHandler.LocalClusterName, + }, + Timestamp: metav1.Now(), + } +} + // updateILStatus prepares the status update IL func (ctlr *Controller) updateILStatus(il *cisapiv1.IngressLink, ip string, status string, err error) { ilStatus := cisapiv1.CustomResourceStatus{ diff --git a/pkg/controller/validate.go b/pkg/controller/validate.go index 73a587642..69b5ec0c4 100644 --- a/pkg/controller/validate.go +++ b/pkg/controller/validate.go @@ -111,7 +111,16 @@ func (ctlr *Controller) validateResource(req *admissionv1.AdmissionRequest) *adm } allowed, errMsg = ctlr.checkValidIngressLink(il) case TLSProfile: - allowed = true + tlsProf := &cisapiv1.TLSProfile{} + if _, _, err := deserializer.Decode(req.Object.Raw, nil, tlsProf); err != nil { + return &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Message: fmt.Sprintf("could not decode object: %v", err), + }, + } + } + allowed, errMsg = ctlr.checkValidTLSProfile(tlsProf) case CustomPolicy: pl := &cisapiv1.Policy{} @@ -252,6 +261,126 @@ func (ctlr *Controller) checkValidVirtualServer( bigipSession := bigiphandler.CreateSession(ctlr.agentParams.PrimaryParams.BIGIPURL, ctlr.PrimaryBigIPWorker.getPostManager().GetToken(), ctlr.agentParams.UserAgent, ctlr.agentParams.PrimaryParams.TrustedCerts, ctlr.agentParams.PrimaryParams.SSLInsecure, false) validator := &bigiphandler.BigIPHandler{Bigip: bigipSession} + // Check the referenced ltm default pool + wg.Add(1) + go func() { + defer wg.Done() + if vsResource.Spec.DefaultPool.Reference == BIGIP { + if _, err := validator.GetLTMPool(vsResource.Spec.DefaultPool.Name); err != nil { + errChan <- fmt.Sprintf("Referenced LTM pool '%s' does not exist on BIGIP for VirtualServer %s "+ + "(defaultPool.name: %s): %v", vsResource.Spec.DefaultPool.Name, vsResource.Name, + vsResource.Spec.DefaultPool.Name, err) + cancel() + return + } + } + }() + + // Check Pool Monitors + for _, pool := range vsResource.Spec.Pools { + for _, monitor := range pool.Monitors { + if monitor.Reference != BIGIP { + continue + } + + wg.Add(1) + go func(monitor, poolName string) { + defer wg.Done() + if _, err := validator.GetMonitor(monitor); err != nil { + errChan <- fmt.Sprintf("Referenced monitor '%s' does not exist on "+ + "BIGIP for VirtualServer %s, pool:%s: %v", monitor, vsResource.Name, poolName, err) + cancel() + return + } + }(monitor.Name, pool.Name) + } + if pool.Monitor.Reference == BIGIP { + wg.Add(1) + go func(monitor, poolName string) { + defer wg.Done() + if _, err := validator.GetMonitor(monitor); err != nil { + errChan <- fmt.Sprintf("Referenced monitor '%s' does not exist on "+ + "BIGIP for VirtualServer %s, pool:%s: %v", monitor, vsResource.Name, poolName, err) + cancel() + return + } + }(pool.Monitor.Name, pool.Name) + } + } + + // Validate AllowVLANs + for _, vlan := range vsResource.Spec.AllowVLANs { + validateProfileReference("vlan", vlan, "VirtualServer", vsResource.Name, + "spec.allowVlans", validator, errChan, cancel, &wg) + } + + // Validate PersistenceProfile + validateProfileReference("persistence", vsResource.Spec.PersistenceProfile, + "VirtualServer", vsResource.Name, "spec.persistenceProfile", validator, errChan, cancel, &wg) + + // Validate HTTPCompressionProfile + validateProfileReference("httpCompression", vsResource.Spec.HTTPCompressionProfile, + "VirtualServer", vsResource.Name, "spec.httpCompressionProfile", validator, errChan, cancel, &wg) + + // Validate ProfileMultiplex + validateProfileReference("multiplex", vsResource.Spec.ProfileMultiplex, + "VirtualServer", vsResource.Name, "spec.profileMultiplex", validator, errChan, cancel, &wg) + + // Validate DOS + validateProfileReference("dos", vsResource.Spec.DOS, "VirtualServer", + vsResource.Name, "spec.dos", validator, errChan, cancel, &wg) + + // Validate BotDefense + validateProfileReference("botDefense", vsResource.Spec.BotDefense, "VirtualServer", + vsResource.Name, "spec.botDefense", validator, errChan, cancel, &wg) + + // Validate WAF + validateProfileReference("waf", vsResource.Spec.WAF, "VirtualServer", + vsResource.Name, "spec.waf", validator, errChan, cancel, &wg) + + // Validate HTMLProfile + validateProfileReference("html", vsResource.Spec.HTMLProfile, "VirtualServer", + vsResource.Name, "spec.htmlProfile", validator, errChan, cancel, &wg) + + // Validate ProfileAccess + validateProfileReference("profileAccess", vsResource.Spec.ProfileAccess, "VirtualServer", + vsResource.Name, "spec.profileAccess", validator, errChan, cancel, &wg) + + // Validate PolicyPerRequestAccess + validateProfileReference("policyPerRequestAccess", vsResource.Spec.PolicyPerRequestAccess, + "VirtualServer", vsResource.Name, "spec.policyPerRequestAccess", validator, + errChan, cancel, &wg) + + // Validate ProfileAdapt (request/response) + if !reflect.DeepEqual(vsResource.Spec.ProfileAdapt, cisapiv1.ProfileAdapt{}) { + validateProfileReference("profileAdaptRequest", vsResource.Spec.ProfileAdapt.Request, + "VirtualServer", vsResource.Name, "spec.profileAdapt.request", validator, + errChan, cancel, &wg) + validateProfileReference("profileAdaptResponse", vsResource.Spec.ProfileAdapt.Response, + "VirtualServer", vsResource.Name, "spec.profileAdapt.response", validator, + errChan, cancel, &wg) + } + + // Validate TCP profiles + if !reflect.DeepEqual(vsResource.Spec.Profiles.TCP, cisapiv1.ProfileTCP{}) { + validateProfileReference("tcpClient", vsResource.Spec.Profiles.TCP.Client, + "VirtualServer", vsResource.Name, "spec.profiles.tcp.client", validator, + errChan, cancel, &wg) + validateProfileReference("tcpServer", vsResource.Spec.Profiles.TCP.Server, + "VirtualServer", vsResource.Name, "spec.profiles.tcp.server", validator, + errChan, cancel, &wg) + } + + // Validate HTTP2 profiles + if !reflect.DeepEqual(vsResource.Spec.Profiles.HTTP2, cisapiv1.ProfileHTTP2{}) { + validateProfileReference("http2Client", vsResource.Spec.Profiles.HTTP2.Client, + "VirtualServer", vsResource.Name, "spec.profiles.http2.client", + validator, errChan, cancel, &wg) + validateProfileReference("http2Server", vsResource.Spec.Profiles.HTTP2.Server, + "VirtualServer", vsResource.Name, "spec.profiles.http2.server", validator, + errChan, cancel, &wg) + } + // Validate iRules for _, irule := range vsResource.Spec.IRules { // if Irule is set to none, skip the check @@ -485,6 +614,72 @@ func (ctlr *Controller) checkValidIngressLink( } }() + bigiIPSession := bigiphandler.CreateSession(ctlr.agentParams.PrimaryParams.BIGIPURL, + ctlr.PrimaryBigIPWorker.getPostManager().GetToken(), ctlr.agentParams.UserAgent, + ctlr.agentParams.PrimaryParams.TrustedCerts, ctlr.agentParams.PrimaryParams.SSLInsecure, false) + validator := &bigiphandler.BigIPHandler{Bigip: bigiIPSession} + + // Validate ClientSSL Profile if TLSProfileName is set + if il.Spec.TLS.Reference == BIGIP { + for _, clientSSL := range il.Spec.TLS.ClientSSLs { + wg.Add(1) + go func(clientSSL string) { + defer wg.Done() + if _, err := validator.GetClientSSLProfile(clientSSL); err != nil { + errChan <- fmt.Sprintf("Referenced ClientSSL Profile '%s' does not exist on "+ + "BIGIP for IngressLink %s: %v", clientSSL, il.Name, err) + cancel() + return + } + }(clientSSL) + } + + // Validate ServerSSL Profile if TLSProfileName is set + for _, serverSSL := range il.Spec.TLS.ServerSSLs { + wg.Add(1) + go func(serverSSL string) { + defer wg.Done() + if _, err := validator.GetServerSSLProfile(serverSSL); err != nil { + errChan <- fmt.Sprintf("Referenced ServerSSL Profile '%s' does not exist on "+ + "BIGIP for IngressLink %s: %v", serverSSL, il.Name, err) + cancel() + return + } + }(serverSSL) + } + } + + // Validate iRules + for _, iRule := range il.Spec.IRules { + wg.Add(1) + go func(iRule string) { + defer wg.Done() + if _, err := validator.GetIRule(iRule); err != nil { + errChan <- fmt.Sprintf("Referenced iRule '%s' does not exist on "+ + "BIGIP for IngressLink %s: %v", iRule, il.Name, err) + cancel() + return + } + }(iRule) + } + + // Validate monitors + for _, monitor := range il.Spec.Monitors { + if monitor.Reference != BIGIP { + continue + } + wg.Add(1) + go func(monitor string) { + defer wg.Done() + if _, err := validator.GetMonitor(monitor); err != nil { + errChan <- fmt.Sprintf("Referenced monitor '%s' does not exist on "+ + "BIGIP for IngressLink %s: %v", monitor, il.Name, err) + cancel() + return + } + }(monitor.Name) + } + go func() { wg.Wait() close(doneChan) @@ -499,6 +694,95 @@ func (ctlr *Controller) checkValidIngressLink( } } +func (ctlr *Controller) checkValidTLSProfile(tlsProfile *cisapiv1.TLSProfile) (bool, string) { + isValid, err := validateTLSProfile(tlsProfile) + if !isValid { + return false, err.Error() + } + + if tlsProfile.Spec.TLS.Reference == Secret { + return true, "" + } + + bigipSession := bigiphandler.CreateSession( + ctlr.agentParams.PrimaryParams.BIGIPURL, + ctlr.PrimaryBigIPWorker.getPostManager().GetToken(), + ctlr.agentParams.UserAgent, + ctlr.agentParams.PrimaryParams.TrustedCerts, + ctlr.agentParams.PrimaryParams.SSLInsecure, + false, + ) + var wg sync.WaitGroup + errChan := make(chan string, 1) + doneChan := make(chan struct{}) + _, cancel := context.WithCancel(context.Background()) + defer cancel() + + validator := &bigiphandler.BigIPHandler{Bigip: bigipSession} + + // Collect serverSSLs and clientSSLs into arrays, giving precedence to plural forms + var serverSSLs []string + if len(tlsProfile.Spec.TLS.ServerSSLs) > 0 { + serverSSLs = append(serverSSLs, tlsProfile.Spec.TLS.ServerSSLs...) + } else if tlsProfile.Spec.TLS.ServerSSL != "" { + serverSSLs = append(serverSSLs, tlsProfile.Spec.TLS.ServerSSL) + } + + var clientSSLs []string + if len(tlsProfile.Spec.TLS.ClientSSLs) > 0 { + clientSSLs = append(clientSSLs, tlsProfile.Spec.TLS.ClientSSLs...) + } else if tlsProfile.Spec.TLS.ClientSSL != "" { + clientSSLs = append(clientSSLs, tlsProfile.Spec.TLS.ClientSSL) + } + + if tlsProfile.Spec.TLS.Reference == BIGIP { + validateSSLProfiles(clientSSLs, func(name string) (interface{}, error) { + return validator.GetClientSSLProfile(name) + }, "ClientSSL", errChan, &wg) + + validateSSLProfiles(serverSSLs, func(name string) (interface{}, error) { + return validator.GetServerSSLProfile(name) + }, "ServerSSL", errChan, &wg) + } else if tlsProfile.Spec.TLS.Reference == Hybrid { + if tlsProfile.Spec.TLS.ClientSSLParams.ProfileReference == BIGIP { + validateSSLProfiles(clientSSLs, func(name string) (interface{}, error) { + return validator.GetClientSSLProfile(name) + }, "ClientSSL", errChan, &wg) + } + if tlsProfile.Spec.TLS.ServerSSLParams.ProfileReference == BIGIP { + validateSSLProfiles(serverSSLs, func(name string) (interface{}, error) { + return validator.GetServerSSLProfile(name) + }, "ServerSSL", errChan, &wg) + } + } + + go func() { + wg.Wait() + close(doneChan) + }() + + select { + case errMsg := <-errChan: + return false, errMsg + case <-doneChan: + close(errChan) + return true, "" + } +} + +func validateSSLProfiles(profiles []string, validateFunc func(string) (interface{}, error), + profileType string, errChan chan<- string, wg *sync.WaitGroup) { + for _, profile := range profiles { + wg.Add(1) + go func(p string) { + defer wg.Done() + if _, err := validateFunc(p); err != nil { + errChan <- fmt.Sprintf("%s Profile '%s' does not exist on BIG-IP: %v", profileType, p, err) + } + }(profile) + } +} + // checkValidMultiClusterService checks if extended service is valid or not func (ctlr *Controller) checkValidMultiClusterService(mcs cisapiv1.MultiClusterServiceReference, isSpec bool) error { // Check if cis running in multiCluster mode @@ -1072,3 +1356,79 @@ func IsValidIPOrCIDR(s string) bool { } return false } + +func validateProfileReference( + profileType string, + value string, + resourceType string, + resourceName string, + fieldPath string, + validator *bigiphandler.BigIPHandler, + errChan chan<- string, + cancel context.CancelFunc, + wg *sync.WaitGroup, +) { + if value == "" { + return + } + wg.Add(1) + go func() { + defer wg.Done() + var err error + var errMsg string + switch profileType { + case "vlan": + _, err = validator.GetVLAN(value) + errMsg = fmt.Sprintf("Referenced vlan '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "persistence": + _, err = validator.GetPersistenceProfile(value) + errMsg = fmt.Sprintf("Referenced persistence profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "httpCompression": + _, err = validator.GetHTTPCompressionProfile(value) + errMsg = fmt.Sprintf("Referenced HTTP Compression profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "multiplex": + _, err = validator.GetMultiplexProfile(value) + errMsg = fmt.Sprintf("Referenced multiplex profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "dos": + _, err = validator.GetDOSProfile(value) + errMsg = fmt.Sprintf("Referenced DOS profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "botDefense": + _, err = validator.GetBotDefenseProfile(value) + errMsg = fmt.Sprintf("Referenced botDefense profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "waf": + _, err = validator.GetWAF(value) + errMsg = fmt.Sprintf("Referenced WAF policy '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "html": + _, err = validator.GetHTMLProfile(value) + errMsg = fmt.Sprintf("Referenced HTML profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "profileAccess": + _, err = validator.GetProfileAccess(value) + errMsg = fmt.Sprintf("Referenced profileAccess '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "policyPerRequestAccess": + _, err = validator.GetPolicyPerRequestAccess(value) + errMsg = fmt.Sprintf("Referenced policyPerRequestAccess '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "profileAdaptRequest": + _, err = validator.GetProfileAdaptRequest(value) + errMsg = fmt.Sprintf("Referenced profileAdapt request '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "profileAdaptResponse": + _, err = validator.GetProfileAdaptResponse(value) + errMsg = fmt.Sprintf("Referenced profileAdapt response '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "tcpClient": + _, err = validator.GetTCPProfile(value) + errMsg = fmt.Sprintf("Referenced client side tcp profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "tcpServer": + _, err = validator.GetTCPProfile(value) + errMsg = fmt.Sprintf("Referenced server side tcp profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "http2Client": + _, err = validator.GetHTTP2Profile(value) + errMsg = fmt.Sprintf("Referenced client side http2 profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + case "http2Server": + _, err = validator.GetHTTP2Profile(value) + errMsg = fmt.Sprintf("Referenced server side http2 profile '%s' does not exist on BIGIP for %s %s (%s): %v", value, resourceType, resourceName, fieldPath, err) + } + if err != nil { + errChan <- errMsg + cancel() + } + }() +} diff --git a/pkg/controller/worker.go b/pkg/controller/worker.go index dff10e572..a8ad37b55 100644 --- a/pkg/controller/worker.go +++ b/pkg/controller/worker.go @@ -1250,7 +1250,7 @@ func (ctlr *Controller) getTLSProfileForVirtualServer(vs *cisapiv1.VirtualServer } // validate TLSProfile - validation := validateTLSProfile(tlsProfile) + validation, _ := validateTLSProfile(tlsProfile) if validation == false { return nil } diff --git a/vendor/github.com/f5devcentral/go-bigip/bigip.go b/vendor/github.com/f5devcentral/go-bigip/bigip.go index 37fd9062b..552204bb7 100644 --- a/vendor/github.com/f5devcentral/go-bigip/bigip.go +++ b/vendor/github.com/f5devcentral/go-bigip/bigip.go @@ -153,18 +153,16 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { Timeout struct { Timeout int64 } + RefreshToken any `json:"refreshToken,omitempty"` } type timeoutReq struct { Timeout int64 `json:"timeout"` } - // type timeoutResp struct { - // Timeout struct { - // Timeout int64 - // } - // } - + if bigipConfig.LoginReference == "" { + bigipConfig.LoginReference = "tmos" + } auth := authReq{ bigipConfig.Username, bigipConfig.Password, @@ -227,7 +225,7 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { b.Token = aresp.Token.Token //Once we have obtained a token, we should actually apply the configured timeout to it - if time.Duration(aresp.Timeout.Timeout)*time.Second != bigipConfig.ConfigOptions.TokenTimeout { // The inital value is the max timespan + if aresp.RefreshToken == nil && time.Duration(aresp.Timeout.Timeout)*time.Second != bigipConfig.ConfigOptions.TokenTimeout { // The inital value is the max timespan timeout := timeoutReq{ int64(bigipConfig.ConfigOptions.TokenTimeout.Seconds()), } diff --git a/vendor/github.com/f5devcentral/go-bigip/ltm.go b/vendor/github.com/f5devcentral/go-bigip/ltm.go index df4c9170e..57ed5216e 100644 --- a/vendor/github.com/f5devcentral/go-bigip/ltm.go +++ b/vendor/github.com/f5devcentral/go-bigip/ltm.go @@ -1108,6 +1108,7 @@ type monitorDTO struct { RecvRow string `json:"recvRow,omitempty"` RecvColumn string `json:"recvColumn,omitempty"` SSLProfile string `json:"sslProfile,omitempty"` + Domain string `json:"domain,omitempty"` // ldap specifics Base string `json:"base,omitempty"` Filter string `json:"filter,omitempty"` @@ -1623,8 +1624,8 @@ type Originsrecords struct { } type Originsrecord struct { - Name string `json:"name"` - app_service string `json:"appService,omitempty"` + Name string `json:"name"` + AppService string `json:"appService,omitempty"` } func (p *Snat) MarshalJSON() ([]byte, error) { diff --git a/vendor/modules.txt b/vendor/modules.txt index 00fb84d82..a7f0116d0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -23,7 +23,7 @@ github.com/davecgh/go-spew/spew # github.com/evanphx/json-patch v4.9.0+incompatible ## explicit github.com/evanphx/json-patch -# github.com/f5devcentral/go-bigip v0.0.0-20250731061239-628be0470a84 +# github.com/f5devcentral/go-bigip v0.0.0-20250808131030-ef245d09723a ## explicit; go 1.20 github.com/f5devcentral/go-bigip # github.com/f5devcentral/go-bigip/f5teem v0.0.0-20250731061239-628be0470a84