diff --git a/apis/bases/client.openstack.org_openstackclients.yaml b/apis/bases/client.openstack.org_openstackclients.yaml index 95f3e38c2..fb0d6a218 100644 --- a/apis/bases/client.openstack.org_openstackclients.yaml +++ b/apis/bases/client.openstack.org_openstackclients.yaml @@ -36,6 +36,8 @@ spec: type: object spec: properties: + caSecretName: + type: string containerImage: type: string nodeSelector: @@ -43,8 +45,10 @@ spec: type: string type: object openStackConfigMap: + default: openstack-config type: string openStackConfigSecret: + default: openstack-config-secret type: string required: - containerImage diff --git a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml index a8fcca8f6..253ac8935 100644 --- a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -205,6 +205,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -2348,6 +2353,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -3537,6 +3547,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object cnfAPIOverride: properties: @@ -3638,6 +3653,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4089,6 +4109,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4316,6 +4341,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4420,6 +4450,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object template: properties: @@ -5034,6 +5069,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -5308,6 +5348,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -6534,6 +6579,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -7557,6 +7607,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object cellOverride: additionalProperties: @@ -7661,6 +7716,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object type: object type: object @@ -8470,6 +8530,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -8969,6 +9034,30 @@ spec: - secret type: object type: object + openstackclient: + properties: + template: + properties: + caSecretName: + type: string + containerImage: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + openStackConfigMap: + default: openstack-config + type: string + openStackConfigSecret: + default: openstack-config-secret + type: string + required: + - containerImage + - openStackConfigMap + - openStackConfigSecret + type: object + type: object ovn: properties: enabled: @@ -9297,6 +9386,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -13620,6 +13714,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object template: properties: @@ -13778,6 +13877,24 @@ spec: - swiftStorage type: object type: object + tls: + default: + endpoint: + internal: + enabled: false + public: + enabled: true + properties: + caSecretName: + type: string + endpoint: + additionalProperties: + properties: + enabled: + type: boolean + type: object + type: object + type: object required: - secret - storageClass diff --git a/apis/client/v1beta1/openstackclient_types.go b/apis/client/v1beta1/openstackclient_types.go index d12ff693d..dc3e4724e 100644 --- a/apis/client/v1beta1/openstackclient_types.go +++ b/apis/client/v1beta1/openstackclient_types.go @@ -15,6 +15,7 @@ package v1beta1 import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -31,16 +32,25 @@ type OpenStackClientSpec struct { // +kubebuilder:validation:Required // ContainerImage for the the OpenstackClient container (will be set to environmental default if empty) ContainerImage string `json:"containerImage"` + // +kubebuilder:validation:Required + // +kubebuilder:default=openstack-config // OpenStackConfigMap is the name of the ConfigMap containing the clouds.yaml - OpenStackConfigMap string `json:"openStackConfigMap"` + OpenStackConfigMap *string `json:"openStackConfigMap"` + // +kubebuilder:validation:Required + // +kubebuilder:default=openstack-config-secret // OpenStackConfigSecret is the name of the Secret containing the secure.yaml - OpenStackConfigSecret string `json:"openStackConfigSecret"` + OpenStackConfigSecret *string `json:"openStackConfigSecret"` // +kubebuilder:validation:Optional // NodeSelector to target subset of worker nodes running control plane services (currently only applies to KeystoneAPI and PlacementAPI) NodeSelector map[string]string `json:"nodeSelector,omitempty"` + + // +kubebuilder:validation:Optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // Secret containing any CA certificates which should be added to deployment pods + tls.Ca `json:",inline"` } // OpenStackClientStatus defines the observed state of OpenStackClient diff --git a/apis/client/v1beta1/zz_generated.deepcopy.go b/apis/client/v1beta1/zz_generated.deepcopy.go index 6d43290bf..4dd597559 100644 --- a/apis/client/v1beta1/zz_generated.deepcopy.go +++ b/apis/client/v1beta1/zz_generated.deepcopy.go @@ -103,6 +103,16 @@ func (in *OpenStackClientList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenStackClientSpec) DeepCopyInto(out *OpenStackClientSpec) { *out = *in + if in.OpenStackConfigMap != nil { + in, out := &in.OpenStackConfigMap, &out.OpenStackConfigMap + *out = new(string) + **out = **in + } + if in.OpenStackConfigSecret != nil { + in, out := &in.OpenStackConfigSecret, &out.OpenStackConfigSecret + *out = new(string) + **out = **in + } if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = make(map[string]string, len(*in)) @@ -110,6 +120,7 @@ func (in *OpenStackClientSpec) DeepCopyInto(out *OpenStackClientSpec) { (*out)[key] = val } } + out.Ca = in.Ca } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackClientSpec. diff --git a/apis/core/v1beta1/conditions.go b/apis/core/v1beta1/conditions.go index 900b30888..6a06ba06f 100644 --- a/apis/core/v1beta1/conditions.go +++ b/apis/core/v1beta1/conditions.go @@ -102,6 +102,9 @@ const ( // OpenStackControlPlaneDNSReadyCondition Status=True condition which indicates if DNSMasq is configured and operational OpenStackControlPlaneDNSReadyCondition condition.Type = "OpenStackControlPlaneDNSReadyCondition" + // OpenStackControlPlaneCAReadyCondition Status=True condition which indicates if the CAs are configured and operational + OpenStackControlPlaneCAReadyCondition condition.Type = "OpenStackControlPlaneCAReadyCondition" + // OpenStackControlPlaneCeilometerReadyCondition Status=True condition which indicates if OpenStack Ceilometer service is configured and operational OpenStackControlPlaneCeilometerReadyCondition condition.Type = "OpenStackControlPlaneCeilometerReady" @@ -384,4 +387,16 @@ const ( // OpenStackControlPlaneExposeServiceReadyMessage OpenStackControlPlaneExposeServiceReadyMessage = "OpenStackControlPlane %s service exposed" + + // OpenStackControlPlaneCAReadyInitMessage + OpenStackControlPlaneCAReadyInitMessage = "OpenStackControlPlane CAs not started" + + // OpenStackControlPlaneCAReadyMessage + OpenStackControlPlaneCAReadyMessage = "OpenStackControlPlane CAs completed" + + // OpenStackControlPlaneCAReadyRunningMessage + OpenStackControlPlaneCAReadyRunningMessage = "OpenStackControlPlane CAs in progress" + + // OpenStackControlPlaneCAReadyErrorMessage + OpenStackControlPlaneCAReadyErrorMessage = "OpenStackControlPlane CAs %s %s error occured %s" ) diff --git a/apis/core/v1beta1/openstackcontrolplane_types.go b/apis/core/v1beta1/openstackcontrolplane_types.go index dde7f7e87..50d6d4b80 100644 --- a/apis/core/v1beta1/openstackcontrolplane_types.go +++ b/apis/core/v1beta1/openstackcontrolplane_types.go @@ -23,10 +23,13 @@ import ( horizonv1 "github.com/openstack-k8s-operators/horizon-operator/api/v1beta1" memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" networkv1 "github.com/openstack-k8s-operators/infra-operator/apis/network/v1beta1" + redisv1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/route" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" "github.com/openstack-k8s-operators/lib-common/modules/storage" manilav1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" @@ -34,12 +37,12 @@ import ( neutronv1 "github.com/openstack-k8s-operators/neutron-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1" + "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" ovnv1 "github.com/openstack-k8s-operators/ovn-operator/api/v1beta1" placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" swiftv1 "github.com/openstack-k8s-operators/swift-operator/api/v1beta1" telemetryv1 "github.com/openstack-k8s-operators/telemetry-operator/api/v1beta1" rabbitmqv2 "github.com/rabbitmq/cluster-operator/v2/api/v1beta1" - redisv1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -69,6 +72,12 @@ type OpenStackControlPlaneSpec struct { // NodeSelector to target subset of worker nodes running control plane services (currently only applies to KeystoneAPI and PlacementAPI) NodeSelector map[string]string `json:"nodeSelector,omitempty"` + // +kubebuilder:validation:Optional + // +kubebuilder:default={endpoint: {public: {enabled: true}, internal: {enabled: false}}} + //+operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS TLSSection `json:"tls"` + // +kubebuilder:validation:Optional //+operator-sdk:csv:customresourcedefinitions:type=spec // DNS - Parameters related to the DNSMasq service @@ -158,6 +167,11 @@ type OpenStackControlPlaneSpec struct { // Redis - Parameters related to the Redis service Redis RedisSection `json:"redis,omitempty"` + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // OpenStackClient - Parameters related to the OpenStackClient + OpenStackClient OpenStackClientSection `json:"openstackclient,omitempty"` + // ExtraMounts containing conf files and credentials that should be provided // to the underlying operators. // This struct can be defined in the top level CR and propagated to the @@ -168,6 +182,29 @@ type OpenStackControlPlaneSpec struct { ExtraMounts []OpenStackExtraVolMounts `json:"extraMounts,omitempty"` } +// TLSSection defines the desired state of TLS configuration +type TLSSection struct { + // +kubebuilder:validation:optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // The key must be the endpoint type (public, internal) + Endpoint map[service.Endpoint]TLSEndpointConfig `json:"endpoint,omitempty"` + + // +kubebuilder:validation:optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // Secret containing any additional CA certificates, which should be added to deployment pods. + // If services get configured to use a custom cert/key, add the CA cert to validate those in this + // CA secret. + tls.Ca `json:",inline"` +} + +// TLSEndpointConfig defines the desired state of TLSEndpoint configuration +type TLSEndpointConfig struct { + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + // Enabled - Whether TLS should be enabled for endpoint type + Enabled bool `json:"enabled"` +} + // DNSMasqSection defines the desired state of DNSMasq service type DNSMasqSection struct { // +kubebuilder:validation:Optional @@ -206,6 +243,20 @@ type Override struct { // +kubebuilder:validation:Optional // Route overrides to use when creating the public service endpoint Route *route.OverrideSpec `json:"route,omitempty"` + + // +kubebuilder:validation:Optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - overrides tls parameters for public endpoint + TLS *TLSServiceOverride `json:"tls,omitempty"` +} + +// TLSServiceOverride overrides tls parameters for publioc endpoint +type TLSServiceOverride struct { + // +kubebuilder:validation:Optional + // Name of a Secret in the same Namespace as the service, containing the server's private key, public certificate + // and CA certificate for TLS. + // The Secret must store these as tls.key, tls.crt and ca.crt respectively. + SecretName string `json:"secretName,omitempty"` } // PlacementSection defines the desired state of Placement service @@ -561,6 +612,14 @@ type RedisSection struct { Templates map[string]redisv1.RedisSpec `json:"templates,omitempty"` } +// OpenStackClientSection defines the desired state of the OpenStackClient +type OpenStackClientSection struct { + // +kubebuilder:validation:Optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // Template - Overrides to use when creating the OpenStackClient Resource + Template v1beta1.OpenStackClientSpec `json:"template,omitempty"` +} + // OpenStackControlPlaneStatus defines the observed state of OpenStackControlPlane type OpenStackControlPlaneStatus struct { //+operator-sdk:csv:customresourcedefinitions:type=status,xDescriptors={"urn:alm:descriptor:io.kubernetes.conditions"} @@ -642,6 +701,7 @@ func (instance *OpenStackControlPlane) InitConditions() { condition.UnknownCondition(OpenStackControlPlaneSwiftReadyCondition, condition.InitReason, OpenStackControlPlaneSwiftReadyInitMessage), condition.UnknownCondition(OpenStackControlPlaneOctaviaReadyCondition, condition.InitReason, OpenStackControlPlaneOctaviaReadyInitMessage), condition.UnknownCondition(OpenStackControlPlaneRedisReadyCondition, condition.InitReason, OpenStackControlPlaneRedisReadyInitMessage), + condition.UnknownCondition(OpenStackControlPlaneCAReadyCondition, condition.InitReason, OpenStackControlPlaneCAReadyInitMessage), // Also add the overall status condition as Unknown condition.UnknownCondition(condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage), diff --git a/apis/core/v1beta1/zz_generated.deepcopy.go b/apis/core/v1beta1/zz_generated.deepcopy.go index bbe5b7cdc..05cb7bfb2 100644 --- a/apis/core/v1beta1/zz_generated.deepcopy.go +++ b/apis/core/v1beta1/zz_generated.deepcopy.go @@ -26,6 +26,7 @@ import ( redisv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/redis/v1beta1" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/route" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/storage" apiv1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" ovn_operatorapiv1beta1 "github.com/openstack-k8s-operators/ovn-operator/api/v1beta1" @@ -325,6 +326,22 @@ func (in *OctaviaSection) DeepCopy() *OctaviaSection { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenStackClientSection) DeepCopyInto(out *OpenStackClientSection) { + *out = *in + in.Template.DeepCopyInto(&out.Template) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenStackClientSection. +func (in *OpenStackClientSection) DeepCopy() *OpenStackClientSection { + if in == nil { + return nil + } + out := new(OpenStackClientSection) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenStackControlPlane) DeepCopyInto(out *OpenStackControlPlane) { *out = *in @@ -409,6 +426,7 @@ func (in *OpenStackControlPlaneSpec) DeepCopyInto(out *OpenStackControlPlaneSpec (*out)[key] = val } } + in.TLS.DeepCopyInto(&out.TLS) in.DNS.DeepCopyInto(&out.DNS) in.Keystone.DeepCopyInto(&out.Keystone) in.Placement.DeepCopyInto(&out.Placement) @@ -429,6 +447,7 @@ func (in *OpenStackControlPlaneSpec) DeepCopyInto(out *OpenStackControlPlaneSpec in.Swift.DeepCopyInto(&out.Swift) in.Octavia.DeepCopyInto(&out.Octavia) in.Redis.DeepCopyInto(&out.Redis) + in.OpenStackClient.DeepCopyInto(&out.OpenStackClient) if in.ExtraMounts != nil { in, out := &in.ExtraMounts, &out.ExtraMounts *out = make([]OpenStackExtraVolMounts, len(*in)) @@ -500,6 +519,11 @@ func (in *Override) DeepCopyInto(out *Override) { *out = new(route.OverrideSpec) (*in).DeepCopyInto(*out) } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSServiceOverride) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Override. @@ -645,3 +669,56 @@ func (in *SwiftSection) DeepCopy() *SwiftSection { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSEndpointConfig) DeepCopyInto(out *TLSEndpointConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSEndpointConfig. +func (in *TLSEndpointConfig) DeepCopy() *TLSEndpointConfig { + if in == nil { + return nil + } + out := new(TLSEndpointConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSSection) DeepCopyInto(out *TLSSection) { + *out = *in + if in.Endpoint != nil { + in, out := &in.Endpoint, &out.Endpoint + *out = make(map[service.Endpoint]TLSEndpointConfig, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + out.Ca = in.Ca +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSSection. +func (in *TLSSection) DeepCopy() *TLSSection { + if in == nil { + return nil + } + out := new(TLSSection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSServiceOverride) DeepCopyInto(out *TLSServiceOverride) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSServiceOverride. +func (in *TLSServiceOverride) DeepCopy() *TLSServiceOverride { + if in == nil { + return nil + } + out := new(TLSServiceOverride) + in.DeepCopyInto(out) + return out +} diff --git a/apis/go.mod b/apis/go.mod index b00e43267..675f481a1 100644 --- a/apis/go.mod +++ b/apis/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/onsi/ginkgo/v2 v2.13.0 - github.com/onsi/gomega v1.28.0 + github.com/onsi/gomega v1.28.1 github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20231018031823-48a662e6dcfe github.com/openstack-k8s-operators/glance-operator/api v0.3.1-0.20231013112543-a07d8273b82d github.com/openstack-k8s-operators/heat-operator/api v0.3.1-0.20231016213913-eacfd44e505f @@ -12,7 +12,7 @@ require ( github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231018060345-8518a89de1be github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5c72e6381cbc github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 - github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f + github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991 github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186 @@ -52,7 +52,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.1 // indirect github.com/gophercloud/gophercloud v1.7.0 // indirect @@ -73,7 +73,7 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.16.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect diff --git a/apis/go.sum b/apis/go.sum index 65249cb1e..5ac24d08e 100644 --- a/apis/go.sum +++ b/apis/go.sum @@ -81,8 +81,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -124,8 +125,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= -github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= +github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7 h1:rncLxJBpFGqBztyxCMwNRnMjhhIDOWHJowi6q8G6koI= github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7/go.mod h1:ctXNyWanKEjGj8sss1KjjHQ3ENKFm33FFnS5BKaIPh4= github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20231018031823-48a662e6dcfe h1:5lAcgOSjDrh0HrJa2w3l9rOJBaH8iSP6Bth169XI7Nw= @@ -142,8 +143,8 @@ github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5 github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5c72e6381cbc/go.mod h1:QJA4yh2B6lgZzrOOb0iW8JyDmB7uRT/Mokiwalca3Cw= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 h1:EgglSFb+05sWY5Z2JivMnXT7XSdGEOqpPfnlsc0oqvE= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46/go.mod h1:sDYtAWryP7mF2v4XfmKdAoFquVAMts2J5PuYFV9VBQU= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f h1:GhwRUVLRKKCEkK06uUXTMmT/waLJQPP5kMGpN6ozbc0= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:xXAuy7HtWN4p7LF5Q+NHLkwAsKVh0KrzpnuPYIG3XaA= +github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c h1:Noq2+ZY8161j7+pclJY+3CXsiOIuuP0GFft7sZW7fBw= +github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c/go.mod h1:YRRcnOxf/V+A/LkoTbh3KQvHXjnegp1tcbGtGrQ7Me0= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f h1:UItXdH97OtXMhW+gLa/xeCTDlm/9wMy3hiGoOLlY01I= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:+J/GuQH7/JJF8SJtWYWxgDsYlqDv+fucxwFNP+QTcKk= github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231019091705-f3aa3d057b0f h1:WBoCIlbxWeg+hIaawVvQX/XiuEw80WHEHouw4H+0Ksk= @@ -244,8 +245,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= diff --git a/config/crd/bases/client.openstack.org_openstackclients.yaml b/config/crd/bases/client.openstack.org_openstackclients.yaml index 95f3e38c2..fb0d6a218 100644 --- a/config/crd/bases/client.openstack.org_openstackclients.yaml +++ b/config/crd/bases/client.openstack.org_openstackclients.yaml @@ -36,6 +36,8 @@ spec: type: object spec: properties: + caSecretName: + type: string containerImage: type: string nodeSelector: @@ -43,8 +45,10 @@ spec: type: string type: object openStackConfigMap: + default: openstack-config type: string openStackConfigSecret: + default: openstack-config-secret type: string required: - containerImage diff --git a/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml b/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml index a8fcca8f6..253ac8935 100644 --- a/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -205,6 +205,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -2348,6 +2353,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -3537,6 +3547,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object cnfAPIOverride: properties: @@ -3638,6 +3653,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4089,6 +4109,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4316,6 +4341,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -4420,6 +4450,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object template: properties: @@ -5034,6 +5069,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -5308,6 +5348,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -6534,6 +6579,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -7557,6 +7607,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object cellOverride: additionalProperties: @@ -7661,6 +7716,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object type: object type: object @@ -8470,6 +8530,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: false @@ -8969,6 +9034,30 @@ spec: - secret type: object type: object + openstackclient: + properties: + template: + properties: + caSecretName: + type: string + containerImage: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + openStackConfigMap: + default: openstack-config + type: string + openStackConfigSecret: + default: openstack-config-secret + type: string + required: + - containerImage + - openStackConfigMap + - openStackConfigSecret + type: object + type: object ovn: properties: enabled: @@ -9297,6 +9386,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object enabled: default: true @@ -13620,6 +13714,11 @@ spec: type: string type: object type: object + tls: + properties: + secretName: + type: string + type: object type: object template: properties: @@ -13778,6 +13877,24 @@ spec: - swiftStorage type: object type: object + tls: + default: + endpoint: + internal: + enabled: false + public: + enabled: true + properties: + caSecretName: + type: string + endpoint: + additionalProperties: + properties: + enabled: + type: boolean + type: object + type: object + type: object required: - secret - storageClass diff --git a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml index f9e92873d..4dabc0876 100644 --- a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml @@ -42,6 +42,9 @@ spec: of several child resources. displayName: APIOverride path: cinder.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: cinder.apiOverride.tls - description: Enabled - Whether Cinder service should be deployed and managed displayName: Enabled path: cinder.enabled @@ -85,6 +88,9 @@ spec: of several child resources. displayName: APIOverride path: glance.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: glance.apiOverride.tls - description: Enabled - Whether Glance service should be deployed and managed displayName: Enabled path: glance.enabled @@ -100,10 +106,16 @@ spec: of several child resources. displayName: APIOverride path: heat.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: heat.apiOverride.tls - description: CnfAPIOverride, provides the ability to override the generated manifest of several child resources. displayName: Cnf APIOverride path: heat.cnfAPIOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: heat.cnfAPIOverride.tls - description: Enabled - Whether Heat services should be deployed and managed displayName: Enabled path: heat.enabled @@ -119,6 +131,9 @@ spec: of several child resources. displayName: APIOverride path: horizon.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: horizon.apiOverride.tls - description: Ironic - Parameters related to the Ironic services displayName: Ironic path: ironic @@ -126,6 +141,9 @@ spec: of several child resources. displayName: APIOverride path: ironic.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: ironic.apiOverride.tls - description: Enabled - Whether Ironic services should be deployed and managed displayName: Enabled path: ironic.enabled @@ -135,6 +153,9 @@ spec: manifest of several child resources. displayName: Inspector Override path: ironic.inspectorOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: ironic.inspectorOverride.tls - description: Template - Overrides to use when creating the Ironic services displayName: Template path: ironic.template @@ -145,6 +166,9 @@ spec: of several child resources. displayName: APIOverride path: keystone.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: keystone.apiOverride.tls - description: Enabled - Whether Keystone service should be deployed and managed displayName: Enabled path: keystone.enabled @@ -157,6 +181,9 @@ spec: of several child resources. displayName: APIOverride path: manila.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: manila.apiOverride.tls - description: Enabled - Whether Manila service should be deployed and managed displayName: Enabled path: manila.enabled @@ -191,6 +218,9 @@ spec: of several child resources. displayName: APIOverride path: neutron.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: neutron.apiOverride.tls - description: Enabled - Whether Neutron service should be deployed and managed displayName: Enabled path: neutron.enabled @@ -210,12 +240,18 @@ spec: of several child resources. displayName: APIOverride path: nova.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: nova.apiOverride.tls - description: CellOverride, provides the ability to override the generated manifest of several child resources for a nova cell. cell0 never have compute nodes and therefore it won't have a noVNCProxy deployed. Providing an override for cell0 noVNCProxy does not have an effect. displayName: Cell Override path: nova.cellOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: nova.cellOverride.noVNCProxy.tls - description: Enabled - Whether Nova services should be deployed and managed displayName: Enabled path: nova.enabled @@ -228,6 +264,9 @@ spec: of several child resources. displayName: APIOverride path: octavia.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: octavia.apiOverride.tls - description: Enabled - Whether the Octavia service should be deployed and managed displayName: Enabled @@ -237,6 +276,13 @@ spec: - description: Template - Overrides to use when creating Octavia Resources displayName: Template path: octavia.template + - description: OpenStackClient - Parameters related to the OpenStackClient + displayName: Open Stack Client + path: openstackclient + - description: Template - Overrides to use when creating the OpenStackClient + Resource + displayName: Template + path: openstackclient.template - description: Ovn - Overrides to use when creating the OVN Services displayName: Ovn path: ovn @@ -266,6 +312,9 @@ spec: of several child resources. displayName: APIOverride path: placement.apiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: placement.apiOverride.tls - description: Enabled - Whether Placement service should be deployed and managed displayName: Enabled path: placement.enabled @@ -312,9 +361,23 @@ spec: manifest of several child resources. displayName: Proxy Override path: swift.proxyOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: swift.proxyOverride.tls - description: Template - Overrides to use when creating Swift Resources displayName: Template path: swift.template + - description: TLS - Parameters related to the TLS + displayName: TLS + path: tls + - description: The key must be the endpoint type (public, internal) + displayName: Endpoint + path: tls.endpoint + - description: Enabled - Whether TLS should be enabled for endpoint type + displayName: Enabled + path: tls.endpoint.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch statusDescriptors: - description: Conditions displayName: Conditions diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 6c13715fa..1d3dd158b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -27,6 +27,30 @@ rules: - list - update - watch +- apiGroups: + - cert-manager.io + resources: + - certificates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cert-manager.io + resources: + - issuers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - cinder.openstack.org resources: @@ -78,8 +102,12 @@ rules: resources: - secrets verbs: + - create + - delete - get - list + - patch + - update - watch - apiGroups: - "" diff --git a/config/samples/core_v1beta1_openstackcontrolplane.yaml b/config/samples/core_v1beta1_openstackcontrolplane.yaml index 8ad4d51cd..c27c8b6ab 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane.yaml @@ -5,6 +5,12 @@ metadata: spec: secret: osp-secret storageClass: local-storage + #tls: + # endpoint: + # internal: + # enabled: false + # public: + # enabled: true keystone: template: databaseInstance: openstack diff --git a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml index 4e1901f91..cde87aefd 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml @@ -5,6 +5,12 @@ metadata: spec: secret: osp-secret storageClass: local-storage + #tls: + # endpoint: + # internal: + # enabled: false + # public: + # enabled: true dns: template: override: diff --git a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml index d71d83006..1e48fc5bd 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation_3replicas.yaml @@ -5,6 +5,12 @@ metadata: spec: secret: osp-secret storageClass: local-storage + #tls: + # endpoint: + # internal: + # enabled: false + # public: + # enabled: true dns: template: override: diff --git a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml index 0619f35d2..32e3a53f0 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml @@ -5,6 +5,12 @@ metadata: spec: secret: osp-secret storageClass: local-storage + #tls: + # endpoint: + # internal: + # enabled: false + # public: + # enabled: true dns: template: override: diff --git a/controllers/client/openstackclient_controller.go b/controllers/client/openstackclient_controller.go index 5ecaf3fd4..9e0d179e6 100644 --- a/controllers/client/openstackclient_controller.go +++ b/controllers/client/openstackclient_controller.go @@ -30,8 +30,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/common" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/configmap" + "github.com/openstack-k8s-operators/lib-common/modules/common/env" helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac" "github.com/openstack-k8s-operators/lib-common/modules/common/secret" @@ -172,7 +174,13 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{RequeueAfter: time.Duration(5) * time.Second}, nil } - _, configMapHash, err := configmap.GetConfigMapAndHashWithName(ctx, helper, instance.Spec.OpenStackConfigMap, instance.Namespace) + clientLabels := map[string]string{ + common.AppSelector: "openstackclient", + } + + configVars := make(map[string]env.Setter) + + _, configMapHash, err := configmap.GetConfigMapAndHashWithName(ctx, helper, *instance.Spec.OpenStackConfigMap, instance.Namespace) if err != nil { if k8s_errors.IsNotFound(err) { instance.Status.Conditions.Set(condition.FalseCondition( @@ -190,8 +198,9 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ err.Error())) return ctrl.Result{}, err } + configVars[*instance.Spec.OpenStackConfigMap] = env.SetValue(configMapHash) - _, secretHash, err := secret.GetSecret(ctx, helper, instance.Spec.OpenStackConfigSecret, instance.Namespace) + _, secretHash, err := secret.GetSecret(ctx, helper, *instance.Spec.OpenStackConfigSecret, instance.Namespace) if err != nil { if k8s_errors.IsNotFound(err) { instance.Status.Conditions.Set(condition.FalseCondition( @@ -209,17 +218,39 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ err.Error())) return ctrl.Result{}, err } + configVars[*instance.Spec.OpenStackConfigSecret] = env.SetValue(secretHash) - instance.Status.Conditions.Set(condition.FalseCondition( - clientv1.OpenStackClientReadyCondition, - condition.RequestedReason, - condition.SeverityInfo, - clientv1.OpenStackClientInputReady)) + if instance.Spec.CaSecretName != "" { + _, secretHash, err = secret.GetSecret(ctx, helper, instance.Spec.CaSecretName, instance.Namespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + instance.Status.Conditions.Set(condition.FalseCondition( + clientv1.OpenStackClientReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + clientv1.OpenStackClientSecretWaitingMessage)) + return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil + } + instance.Status.Conditions.Set(condition.FalseCondition( + clientv1.OpenStackClientReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + clientv1.OpenStackClientReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + configVars[instance.Spec.CaSecretName] = env.SetValue(secretHash) + } - clientLabels := map[string]string{ - "app": "openstackclient", + configVarsHash, err := util.HashOfInputHashes(configVars) + if err != nil { + return ctrl.Result{}, err + } + + pod, err := openstackclient.ClientPod(ctx, instance, helper, clientLabels, configVarsHash) + if err != nil { + return ctrl.Result{}, err } - pod := openstackclient.ClientPod(instance, clientLabels, configMapHash, secretHash) op, err := controllerutil.CreateOrPatch(ctx, r.Client, pod, func() error { pod.Spec.Containers[0].Image = instance.Spec.ContainerImage diff --git a/controllers/core/openstackcontrolplane_controller.go b/controllers/core/openstackcontrolplane_controller.go index 2ce0515f1..8c6e0aa4d 100644 --- a/controllers/core/openstackcontrolplane_controller.go +++ b/controllers/core/openstackcontrolplane_controller.go @@ -31,12 +31,14 @@ import ( keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + corev1 "k8s.io/api/core/v1" manilav1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" neutronv1 "github.com/openstack-k8s-operators/neutron-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1" + clientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" corev1beta1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" "github.com/openstack-k8s-operators/openstack-operator/pkg/openstack" @@ -94,6 +96,9 @@ type OpenStackControlPlaneReconciler struct { //+kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;watch;create;update;patch;delete; //+kubebuilder:rbac:groups=route.openshift.io,resources=routes/custom-host,verbs=create;update;patch //+kubebuilder:rbac:groups=core,resources=services,verbs=get;list; +//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=cert-manager.io,resources=issuers,verbs=get;list;watch;create;update;patch;delete; +//+kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete; // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -164,7 +169,14 @@ func (r *OpenStackControlPlaneReconciler) Reconcile(ctx context.Context, req ctr func (r *OpenStackControlPlaneReconciler) reconcileNormal(ctx context.Context, instance *corev1beta1.OpenStackControlPlane, helper *helper.Helper) (ctrl.Result, error) { - ctrlResult, err := openstack.ReconcileDNSMasqs(ctx, instance, helper) + ctrlResult, err := openstack.ReconcileCAs(ctx, instance, helper) + if err != nil { + return ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + ctrlResult, err = openstack.ReconcileDNSMasqs(ctx, instance, helper) if err != nil { return ctrl.Result{}, err } else if (ctrlResult != ctrl.Result{}) { @@ -318,6 +330,8 @@ func (r *OpenStackControlPlaneReconciler) reconcileNormal(ctx context.Context, i func (r *OpenStackControlPlaneReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&corev1beta1.OpenStackControlPlane{}). + Owns(&clientv1.OpenStackClient{}). + Owns(&corev1.Secret{}). Owns(&mariadbv1.MariaDB{}). Owns(&mariadbv1.Galera{}). Owns(&memcachedv1.Memcached{}). diff --git a/go.mod b/go.mod index 34c82768c..5df2333f1 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,12 @@ module github.com/openstack-k8s-operators/openstack-operator go 1.19 require ( + github.com/cert-manager/cert-manager v1.11.5 github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.16 github.com/onsi/ginkgo/v2 v2.13.0 - github.com/onsi/gomega v1.28.0 + github.com/onsi/gomega v1.28.1 github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20231018031823-48a662e6dcfe github.com/openstack-k8s-operators/dataplane-operator/api v0.3.1-0.20231019162005-1acbd3e775f1 github.com/openstack-k8s-operators/glance-operator/api v0.3.1-0.20231013112543-a07d8273b82d @@ -16,7 +17,8 @@ require ( github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231018060345-8518a89de1be github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5c72e6381cbc github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 - github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f + github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231023090155-357f2fa52453 + github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231017114157-49522e78f991 github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 github.com/openstack-k8s-operators/neutron-operator/api v0.3.1-0.20231019125621-7c2a5a4a5186 @@ -32,6 +34,7 @@ require ( github.com/operator-framework/api v0.17.6 github.com/rabbitmq/cluster-operator/v2 v2.5.0 go.uber.org/zap v1.26.0 + golang.org/x/exp v0.0.0-20231006140011-7918f672742d k8s.io/api v0.27.2 k8s.io/apimachinery v0.27.4 k8s.io/client-go v0.27.2 @@ -44,8 +47,8 @@ require ( github.com/google/pprof v0.0.0-20230510103437-eeec1cb781c3 // indirect github.com/metal3-io/baremetal-operator/apis v0.3.1 // indirect github.com/metal3-io/baremetal-operator/pkg/hardwareutils v0.2.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/tools v0.14.0 // indirect + sigs.k8s.io/gateway-api v0.6.0 // indirect ) require ( @@ -63,7 +66,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.1 // indirect github.com/gophercloud/gophercloud v1.7.0 // indirect @@ -85,7 +88,7 @@ require ( github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.16.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect diff --git a/go.sum b/go.sum index 489277c7e..b1a6d36bf 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cert-manager/cert-manager v1.11.5 h1:K2LurvwIE4hIhODQZnkOW6ljYe3lVMAliS/to+gI05o= +github.com/cert-manager/cert-manager v1.11.5/go.mod h1:zNOyoTEwdn9Rtj5Or2pjBY1Bqwtw4vBElP2fKSP8/g8= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -84,8 +86,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -131,8 +134,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= -github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= +github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7 h1:rncLxJBpFGqBztyxCMwNRnMjhhIDOWHJowi6q8G6koI= github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7/go.mod h1:ctXNyWanKEjGj8sss1KjjHQ3ENKFm33FFnS5BKaIPh4= github.com/openstack-k8s-operators/cinder-operator/api v0.3.1-0.20231018031823-48a662e6dcfe h1:5lAcgOSjDrh0HrJa2w3l9rOJBaH8iSP6Bth169XI7Nw= @@ -151,8 +154,10 @@ github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5 github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231019064410-5c72e6381cbc/go.mod h1:QJA4yh2B6lgZzrOOb0iW8JyDmB7uRT/Mokiwalca3Cw= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46 h1:EgglSFb+05sWY5Z2JivMnXT7XSdGEOqpPfnlsc0oqvE= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231017110713-7b1a1e54bd46/go.mod h1:sDYtAWryP7mF2v4XfmKdAoFquVAMts2J5PuYFV9VBQU= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f h1:GhwRUVLRKKCEkK06uUXTMmT/waLJQPP5kMGpN6ozbc0= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:xXAuy7HtWN4p7LF5Q+NHLkwAsKVh0KrzpnuPYIG3XaA= +github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231023090155-357f2fa52453 h1:mwmH2t1u5WisOxSWl9DDVFzTvPbomFe0GVFoGdH//gE= +github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231023090155-357f2fa52453/go.mod h1:wQc5eZGAaUkAi8aOw/hS4A3y5ODcXAIwLt4t0cBT62g= +github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c h1:Noq2+ZY8161j7+pclJY+3CXsiOIuuP0GFft7sZW7fBw= +github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231024141124-a219eee17f6c/go.mod h1:YRRcnOxf/V+A/LkoTbh3KQvHXjnegp1tcbGtGrQ7Me0= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f h1:UItXdH97OtXMhW+gLa/xeCTDlm/9wMy3hiGoOLlY01I= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231019091705-f3aa3d057b0f/go.mod h1:+J/GuQH7/JJF8SJtWYWxgDsYlqDv+fucxwFNP+QTcKk= github.com/openstack-k8s-operators/lib-common/modules/storage v0.3.1-0.20231019091705-f3aa3d057b0f h1:WBoCIlbxWeg+hIaawVvQX/XiuEw80WHEHouw4H+0Ksk= @@ -261,8 +266,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= @@ -383,6 +388,8 @@ k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSn k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.14.6 h1:oxstGVvXGNnMvY7TAESYk+lzr6S3V5VFxQ6d92KcwQA= sigs.k8s.io/controller-runtime v0.14.6/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= +sigs.k8s.io/gateway-api v0.6.0 h1:v2FqrN2ROWZLrSnI2o91taHR8Sj3s+Eh3QU7gLNWIqA= +sigs.k8s.io/gateway-api v0.6.0/go.mod h1:EYJT+jlPWTeNskjV0JTki/03WX1cyAnBhwBJfYHpV/0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= diff --git a/main.go b/main.go index 8d2816934..346521765 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,7 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cinderv1 "github.com/openstack-k8s-operators/cinder-operator/api/v1beta1" dataplanev1beta1 "github.com/openstack-k8s-operators/dataplane-operator/api/v1beta1" glancev1 "github.com/openstack-k8s-operators/glance-operator/api/v1beta1" @@ -102,6 +103,7 @@ func init() { utilruntime.Must(clientv1.AddToScheme(scheme)) utilruntime.Must(redisv1.AddToScheme(scheme)) utilruntime.Must(routev1.AddToScheme(scheme)) + utilruntime.Must(certmgrv1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } diff --git a/pkg/openstack/ca.go b/pkg/openstack/ca.go new file mode 100644 index 000000000..28729ff94 --- /dev/null +++ b/pkg/openstack/ca.go @@ -0,0 +1,437 @@ +package openstack + +import ( + "context" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "math" + "os" + "time" + + certmgrv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmgrmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/openstack-k8s-operators/lib-common/modules/certmanager" + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + "golang.org/x/exp/slices" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + + corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + + ctrl "sigs.k8s.io/controller-runtime" +) + +const ( + // CombinedCASecret - + CombinedCASecret = "combined-ca-bundle" + // TLSCABundleFile - + TLSCABundleFile = "tls-ca-bundle.pem" + // DefaultCAPrefix - + DefaultCAPrefix = "rootca-" + // DownstreamTLSCABundlePath - + DownstreamTLSCABundlePath = "/etc/pki/ca-trust/extracted/pem/" + TLSCABundleFile + // UpstreamTLSCABundlePath - + UpstreamTLSCABundlePath = "/etc/ssl/certs/ca-certificates.crt" +) + +// ReconcileCAs - +func ReconcileCAs(ctx context.Context, instance *corev1.OpenStackControlPlane, helper *helper.Helper) (ctrl.Result, error) { + // create selfsigned-issuer + issuerReq := certmanager.SelfSignedIssuer( + "selfsigned-issuer", + instance.GetNamespace(), + map[string]string{}, + ) + /* + // Cleanuo? + if !instance.Spec.TLS.Enabled { + if err := cert.Delete(ctx, helper); err != nil { + return ctrl.Result{}, err + } + instance.Status.Conditions.Remove(corev1beta1.OpenStackControlPlaneCAsReadyCondition) + + return ctrl.Result{}, nil + } + */ + + helper.GetLogger().Info("Reconciling CAs", "Namespace", instance.Namespace, "Name", issuerReq.Name) + + issuer := certmanager.NewIssuer(issuerReq, 5) + ctrlResult, err := issuer.CreateOrPatch(ctx, helper) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + issuerReq.Kind, + issuerReq.GetName(), + err.Error())) + + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return ctrlResult, nil + } + + bundle := newBundle() + + // load current CA bundle from secret if exist + currentCASecret, _, err := secret.GetSecret(ctx, helper, CombinedCASecret, instance.Namespace) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + if currentCASecret != nil { + if _, ok := currentCASecret.Data[TLSCABundleFile]; ok { + err = bundle.getCertsFromPEM(currentCASecret.Data[TLSCABundleFile]) + if err != nil { + return ctrl.Result{}, err + } + } + } + + // create RootCA cert and Issuer that uses the generated CA certificate to issue certs + for endpoint, config := range instance.Spec.TLS.Endpoint { + if config.Enabled { + + labels := map[string]string{} + if endpoint == service.EndpointInternal { + labels[certmanager.RootCAIssuerInternalLabel] = "" + } + // always create a root CA and issuer for the endpoint as we can + // not expect that all services are yet configured to be provided with + // a custom secret holding the cert/private key + caCert, ctrlResult, err := createRootCACertAndIssuer( + ctx, + instance, + helper, + issuerReq, + DefaultCAPrefix+string(endpoint), + labels, + ) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + err = bundle.getCertsFromPEM(caCert) + if err != nil { + return ctrl.Result{}, err + } + } + } + instance.Status.Conditions.MarkTrue(corev1.OpenStackControlPlaneCAReadyCondition, corev1.OpenStackControlPlaneCAReadyMessage) + + // create/update combined CA secret + if instance.Spec.TLS.CaSecretName != "" { + caSecret, _, err := secret.GetSecret(ctx, helper, instance.Spec.TLS.CaSecretName, instance.Namespace) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + "secret", + instance.Spec.TLS.CaSecretName, + err.Error())) + + return ctrlResult, err + } + + for _, caCert := range caSecret.Data { + err = bundle.getCertsFromPEM(caCert) + if err != nil { + return ctrl.Result{}, err + } + } + } + + // get CA bundle from operator image. Downstream and upstream build use a different + // base image, so the ca bundle cert file can be in different locations + caBundle, err := getOperatorCABundle(DownstreamTLSCABundlePath) + if err != nil { + // if the DownstreamTLSCABundlePath does not exist in the operator image, + // check for UpstreamTLSCABundlePath + if errors.Is(err, os.ErrNotExist) { + helper.GetLogger().Info(fmt.Sprintf("Downstream CA bundle not found using: %s", UpstreamTLSCABundlePath)) + caBundle, err = getOperatorCABundle(UpstreamTLSCABundlePath) + if err != nil { + return ctrl.Result{}, err + } + } else { + return ctrl.Result{}, err + } + } + err = bundle.getCertsFromPEM(caBundle) + if err != nil { + return ctrl.Result{}, err + } + + saSecretTemplate := []util.Template{ + { + Name: CombinedCASecret, + Namespace: instance.Namespace, + Type: util.TemplateTypeNone, + InstanceType: instance.Kind, + AdditionalTemplate: nil, + Annotations: map[string]string{}, + Labels: map[string]string{ + tls.CABundleLabel: "", + }, + ConfigOptions: nil, + CustomData: map[string]string{TLSCABundleFile: bundle.getBundlePEM()}, + }, + } + + if err := secret.EnsureSecrets(ctx, helper, instance, saSecretTemplate, nil); err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + "secret", + CombinedCASecret, + err.Error())) + + return ctrlResult, err + } + + return ctrl.Result{}, nil +} + +func createRootCACertAndIssuer( + ctx context.Context, + instance *corev1.OpenStackControlPlane, + helper *helper.Helper, + selfsignedIssuerReq *certmgrv1.Issuer, + caName string, + labels map[string]string, +) ([]byte, ctrl.Result, error) { + // create RootCA Certificate used to sign certificates + caCertReq := certmanager.Cert( + caName, + instance.Namespace, + map[string]string{}, + certmgrv1.CertificateSpec{ + IsCA: true, + CommonName: caName, + SecretName: caName, + PrivateKey: &certmgrv1.CertificatePrivateKey{ + Algorithm: "ECDSA", + Size: 256, + }, + IssuerRef: certmgrmetav1.ObjectReference{ + Name: selfsignedIssuerReq.Name, + Kind: selfsignedIssuerReq.Kind, + Group: selfsignedIssuerReq.GroupVersionKind().Group, + }, + }) + cert := certmanager.NewCertificate(caCertReq, 5) + + ctrlResult, err := cert.CreateOrPatch(ctx, helper) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + caCertReq.Kind, + caCertReq.Name, + err.Error())) + + return nil, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return nil, ctrlResult, nil + } + + // create Issuer that uses the generated CA certificate to issue certs + issuerReq := certmanager.CAIssuer( + caCertReq.Name, + instance.GetNamespace(), + labels, + caCertReq.Name, + ) + + issuer := certmanager.NewIssuer(issuerReq, 5) + ctrlResult, err = issuer.CreateOrPatch(ctx, helper) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + issuerReq.Kind, + issuerReq.GetName(), + err.Error())) + + return nil, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return nil, ctrlResult, nil + } + + caCert, ctrlResult, err := getCAFromSecret(ctx, instance, helper, caName) + if err != nil { + return nil, ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return nil, ctrlResult, nil + } + + return caCert, ctrl.Result{}, nil +} + +func getCAFromSecret( + ctx context.Context, + instance *corev1.OpenStackControlPlane, + helper *helper.Helper, + secretName string, +) ([]byte, ctrl.Result, error) { + caSecret, ctrlResult, err := secret.GetDataFromSecret(ctx, helper, secretName, time.Duration(5), "ca.crt") + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + "secret", + secretName, + err.Error())) + + return nil, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return nil, ctrlResult, nil + } + + return []byte(caSecret), ctrl.Result{}, nil +} + +func getOperatorCABundle(caFile string) ([]byte, error) { + contents, err := os.ReadFile(caFile) + if err != nil { + return nil, fmt.Errorf("File reading error %w", err) + } + + return contents, nil +} + +func days(t time.Time) int { + return int(math.Round(time.Since(t).Hours() / 24)) +} + +type caBundle struct { + certs []caCert +} + +type caCert struct { + hash string + cert *x509.Certificate +} + +// newBundle returns a new, empty Bundle +func newBundle() *caBundle { + return &caBundle{ + certs: make([]caCert, 0), + } +} + +func (cab *caBundle) getCertsFromPEM(PEMdata []byte) error { + if PEMdata == nil { + return fmt.Errorf("certificate data can't be nil") + } + + for { + var block *pem.Block + block, PEMdata = pem.Decode(PEMdata) + + if block == nil { + break + } + + if block.Type != "CERTIFICATE" { + // only certificates are allowed in a bundle + return fmt.Errorf("invalid PEM block in bundle: only CERTIFICATE blocks are permitted but found '%s'", block.Type) + } + + if len(block.Headers) != 0 { + return fmt.Errorf("invalid PEM block in bundle; blocks are not permitted to have PEM headers") + } + + certificate, err := x509.ParseCertificate(block.Bytes) + if err != nil { + // the presence of an invalid cert (including things which aren't certs) + // should cause the bundle to be rejected + return fmt.Errorf("invalid PEM block in bundle; invalid PEM certificate: %w", err) + } + + if certificate == nil { + return fmt.Errorf("failed appending a certificate: certificate is nil") + } + + // validate if the CA expired + if -days(certificate.NotAfter) <= 0 { + continue + } + + blockHash, err := util.ObjectHash(block.Bytes) + if err != nil { + return fmt.Errorf("failed calc hash of PEM block : %w", err) + } + + // if cert is not already in bundle list add it + // validate of nextip is already in a reservation and its not us + f := func(c caCert) bool { + return c.hash == blockHash + } + idx := slices.IndexFunc(cab.certs, f) + if idx == -1 { + cab.certs = append(cab.certs, + caCert{ + hash: blockHash, + cert: certificate, + }) + } + } + + return nil +} + +// Create PEM bundle from certificates +func (cab *caBundle) getBundlePEM() string { + var bundleData string + + for _, cert := range cab.certs { + bundleData += "# " + cert.cert.Issuer.CommonName + "\n" + + string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.cert.Raw})) + } + + return bundleData +} diff --git a/pkg/openstack/cinder.go b/pkg/openstack/cinder.go index a0be02e74..dba54f71c 100644 --- a/pkg/openstack/cinder.go +++ b/pkg/openstack/cinder.go @@ -68,14 +68,14 @@ func ReconcileCinder(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - instance.Spec.Cinder.Template.CinderAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Cinder.Template.CinderAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, cinder, svcs, instance.Spec.Cinder.Template.CinderAPI.Override.Service, - instance.Spec.Cinder.APIOverride.Route, + instance.Spec.Cinder.APIOverride, corev1beta1.OpenStackControlPlaneExposeCinderReadyCondition, ) if err != nil { diff --git a/pkg/openstack/common.go b/pkg/openstack/common.go index 91de48804..c403c7a08 100644 --- a/pkg/openstack/common.go +++ b/pkg/openstack/common.go @@ -6,19 +6,24 @@ import ( "time" routev1 "github.com/openshift/api/route/v1" + "github.com/openstack-k8s-operators/lib-common/modules/certmanager" "github.com/openstack-k8s-operators/lib-common/modules/common" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/helper" "github.com/openstack-k8s-operators/lib-common/modules/common/route" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/util" corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + k8s_corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) // EnsureDeleted - Delete the object which in turn will clean the sub resources @@ -52,17 +57,41 @@ func AddServiceComponentLabel(svcOverride service.RoutedOverrideSpec, value stri return svcOverride } +// EndpointDetails - endpoint details +type EndpointDetails struct { + Name string + Namespace string + Type service.Endpoint + Annotations map[string]string + Labels map[string]string + Service ServiceDetails + Route RouteDetails + Hostname *string + EndpointURL string + TLS TLSDetails +} + +// TLSDetails - tls settings for the endpoint +type TLSDetails struct { + Enabled bool + Issuer string + CertSecret *string + InternalCA string + + //PublicEndpoint bool + //InternalEndpoint bool +} + +// ServiceDetails - service details +type ServiceDetails struct { + Spec *k8s_corev1.Service + OverrideSpec service.RoutedOverrideSpec +} + // RouteDetails - route details type RouteDetails struct { - RouteName string - Namespace string - Endpoint service.Endpoint - RouteOverrideSpec *route.OverrideSpec - ServiceLabel map[string]string - ServiceSpec *k8s_corev1.Service - endpointURL string - hostname *string - route *routev1.Route + Route *routev1.Route + OverrideSpec route.OverrideSpec } // GetRoutesListWithLabel - Get all routes in namespace of the obj matching label selector @@ -86,170 +115,308 @@ func GetRoutesListWithLabel( return routeList, nil } -// EnsureRoute - -func EnsureRoute( +// EnsureEndpointConfig - +func EnsureEndpointConfig( ctx context.Context, instance *corev1.OpenStackControlPlane, helper *helper.Helper, owner metav1.Object, svcs *k8s_corev1.ServiceList, svcOverrides map[service.Endpoint]service.RoutedOverrideSpec, - overrideSpec *route.OverrideSpec, + publicOverride corev1.Override, condType condition.Type, ) (map[service.Endpoint]service.RoutedOverrideSpec, ctrl.Result, error) { - - cleanCondition := true - for _, svc := range svcs.Items { - rd := RouteDetails{ - RouteName: svc.Name, - Namespace: svc.Namespace, - Endpoint: service.Endpoint(svc.Annotations[service.AnnotationEndpointKey]), - RouteOverrideSpec: overrideSpec, - ServiceSpec: &svc, - } - svcOverride := svcOverrides[rd.Endpoint] - - // check if there is already a route with common.AppSelector from the service - if svcLabelVal, ok := svc.Labels[common.AppSelector]; ok { - routes, err := GetRoutesListWithLabel( - ctx, - helper, - instance.Namespace, - map[string]string{common.AppSelector: svcLabelVal}, - ) - if err != nil { - return svcOverrides, ctrl.Result{}, err - } + ed := EndpointDetails{ + Name: svc.Name, + Namespace: svc.Namespace, + Type: service.Endpoint(svc.Annotations[service.AnnotationEndpointKey]), + Service: ServiceDetails{ + Spec: &svc, + }, + } + if publicOverride.Route != nil { + ed.Route.OverrideSpec = *publicOverride.Route + } - // check the routes if name changed where we are the owner - for _, r := range routes.Items { - instanceRef := metav1.OwnerReference{ - APIVersion: instance.APIVersion, - Kind: instance.Kind, - Name: instance.GetName(), - UID: instance.GetUID(), - BlockOwnerDeletion: ptr.To(true), - Controller: ptr.To(true), - } + if tlsEndpointConfig := instance.Spec.TLS.Endpoint[ed.Type]; tlsEndpointConfig.Enabled { + ed.TLS.Enabled = true + ed.TLS.Issuer = DefaultCAPrefix + string(ed.Type) - owner := metav1.GetControllerOf(&r.ObjectMeta) + // TODO: (mschuppert) for TLSE create TLS cert for service + //if ed.Type == service.EndpointInternal { + // TODO: for TLSE create TLS cert for service + //} + } - // Delete the route if the service was changed not to expose a route - if svc.ObjectMeta.Annotations[service.AnnotationIngressCreateKey] == "false" && - r.Spec.To.Name == svc.Name && - owner != nil && owner.UID == instance.GetUID() { - // Delete any other owner refs from ref list to not block deletion until owners are gone - r.SetOwnerReferences([]metav1.OwnerReference{instanceRef}) + if instance.Spec.TLS.Endpoint[service.EndpointInternal].Enabled { + // TODO: (mschuppert) get the CA cert for internal CA to add it to the route + // to be able to connect to the TLS internal endpoint + ed.TLS.InternalCA = "" + } - // Delete route - err := helper.GetClient().Delete(ctx, &r) - if err != nil && !k8s_errors.IsNotFound(err) { - err = fmt.Errorf("Error deleting route %s: %w", r.Name, err) - return svcOverrides, ctrl.Result{}, err - } + ed.Service.OverrideSpec = svcOverrides[ed.Type] - if svcOverride.EndpointURL != nil { - svcOverride.EndpointURL = nil - helper.GetLogger().Info(fmt.Sprintf("Service %s override endpointURL removed", svc.Name)) - } + if ed.Type == service.EndpointPublic { + if ed.TLS.Enabled { + // if a custom cert secret was provided we'll use this for + // the route, otherwise the issuer is used to request one + // for the endpoint. + if publicOverride.TLS != nil && publicOverride.TLS.SecretName != "" { + ed.TLS.CertSecret = ptr.To(publicOverride.TLS.SecretName) } } - } - // If the service has the create ingress annotation and its a default ClusterIP service -> create route - if svc.ObjectMeta.Annotations[service.AnnotationIngressCreateKey] == "true" && svc.Spec.Type == k8s_corev1.ServiceTypeClusterIP { - if instance.Status.Conditions.Get(condType) == nil { - instance.Status.Conditions.Set(condition.UnknownCondition( - condType, - condition.InitReason, - corev1.OpenStackControlPlaneExposeServiceReadyInitMessage, - owner.GetName(), - svc.Name, - )) + ctrlResult, err := ed.ensureRoute(ctx, instance, helper, &svc, owner, condType) + if err != nil { + return svcOverrides, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return svcOverrides, ctrlResult, nil } + } - if svcOverride.EmbeddedLabelsAnnotations == nil { - svcOverride.EmbeddedLabelsAnnotations = &service.EmbeddedLabelsAnnotations{} - } + // update override for the service with the endpoint url + if ed.EndpointURL != "" { + // Any trailing path will be added on the service-operator level. + ed.Service.OverrideSpec.EndpointURL = &ed.EndpointURL + instance.Status.Conditions.MarkTrue(condType, corev1.OpenStackControlPlaneExposeServiceReadyMessage, owner.GetName()) + } - if labelVal, ok := svcOverride.EmbeddedLabelsAnnotations.Labels[common.AppSelector]; ok { - rd.ServiceLabel = map[string]string{common.AppSelector: labelVal} - } + svcOverrides[ed.Type] = ed.Service.OverrideSpec + } - ctrlResult, err := rd.CreateRoute(ctx, helper, owner) - if err != nil { - instance.Status.Conditions.Set(condition.FalseCondition( - condType, - condition.ErrorReason, - condition.SeverityWarning, - corev1.OpenStackControlPlaneExposeServiceReadyErrorMessage, - owner.GetName(), - rd.RouteName, - err.Error())) + return svcOverrides, ctrl.Result{}, nil +} - return svcOverrides, ctrlResult, err - } else if (ctrlResult != ctrl.Result{}) { - return svcOverrides, ctrlResult, nil +func (ed *EndpointDetails) ensureRoute( + ctx context.Context, + instance *corev1.OpenStackControlPlane, + helper *helper.Helper, + svc *k8s_corev1.Service, + owner metav1.Object, + condType condition.Type, +) (ctrl.Result, error) { + // check if there is already a route with common.AppSelector from the service + if svcLabelVal, ok := svc.Labels[common.AppSelector]; ok { + routes, err := GetRoutesListWithLabel( + ctx, + helper, + instance.Namespace, + map[string]string{common.AppSelector: svcLabelVal}, + ) + if err != nil { + return ctrl.Result{}, err + } + + // check the routes if name changed where we are the owner + for _, r := range routes.Items { + instanceRef := metav1.OwnerReference{ + APIVersion: instance.APIVersion, + Kind: instance.Kind, + Name: instance.GetName(), + UID: instance.GetUID(), + BlockOwnerDeletion: ptr.To(true), + Controller: ptr.To(true), } - cleanCondition = false + owner := metav1.GetControllerOf(&r.ObjectMeta) - // update override for the service with the route endpoint url - if rd.endpointURL != "" { - // Any trailing path will be added on the service-operator level. - svcOverride.EndpointURL = &rd.endpointURL - instance.Status.Conditions.MarkTrue(condType, corev1.OpenStackControlPlaneExposeServiceReadyMessage, owner.GetName()) + // Delete the route if the service was changed not to expose a route + if svc.ObjectMeta.Annotations[service.AnnotationIngressCreateKey] == "false" && + r.Spec.To.Name == ed.Name && + owner != nil && owner.UID == instance.GetUID() { + // Delete any other owner refs from ref list to not block deletion until owners are gone + r.SetOwnerReferences([]metav1.OwnerReference{instanceRef}) + + // Delete route + err := helper.GetClient().Delete(ctx, &r) + if err != nil && !k8s_errors.IsNotFound(err) { + err = fmt.Errorf("Error deleting route %s: %w", r.Name, err) + return ctrl.Result{}, err + } + + if ed.Service.OverrideSpec.EndpointURL != nil { + ed.Service.OverrideSpec.EndpointURL = nil + helper.GetLogger().Info(fmt.Sprintf("Service %s override endpointURL removed", svc.Name)) + } } } - - svcOverrides[rd.Endpoint] = svcOverride } - if cleanCondition { - instance.Status.Conditions.Remove(condType) + // If the service has the create ingress annotation and its a default ClusterIP service -> create route + if svc.ObjectMeta.Annotations[service.AnnotationIngressCreateKey] == "true" && svc.Spec.Type == k8s_corev1.ServiceTypeClusterIP { + if instance.Status.Conditions.Get(condType) == nil { + instance.Status.Conditions.Set(condition.UnknownCondition( + condType, + condition.InitReason, + corev1.OpenStackControlPlaneExposeServiceReadyInitMessage, + owner.GetName(), + svc.Name, + )) + } + if ed.Service.OverrideSpec.EmbeddedLabelsAnnotations == nil { + ed.Service.OverrideSpec.EmbeddedLabelsAnnotations = &service.EmbeddedLabelsAnnotations{} + } + + if labelVal, ok := ed.Service.OverrideSpec.EmbeddedLabelsAnnotations.Labels[common.AppSelector]; ok { + ed.Labels = map[string]string{common.AppSelector: labelVal} + } + + ctrlResult, err := ed.CreateRoute(ctx, helper, owner) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condType, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneExposeServiceReadyErrorMessage, + owner.GetName(), + ed.Name, + err.Error())) + + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + return ctrl.Result{}, nil } + instance.Status.Conditions.Remove(condType) - return svcOverrides, ctrl.Result{}, nil + return ctrl.Result{}, nil } // CreateRoute - -func (rd *RouteDetails) CreateRoute( +func (ed *EndpointDetails) CreateRoute( ctx context.Context, helper *helper.Helper, owner metav1.Object, ) (ctrl.Result, error) { - // TODO TLS - routeOverrides := []route.OverrideSpec{} - if rd.RouteOverrideSpec != nil { - routeOverrides = append(routeOverrides, *rd.RouteOverrideSpec) - } - route, err := route.NewRoute( + // initialize the route with any custom provided route override + enptRoute, err := route.NewRoute( route.GenericRoute(&route.GenericRouteDetails{ - Name: rd.RouteName, - Namespace: rd.Namespace, - Labels: rd.ServiceLabel, - ServiceName: rd.ServiceSpec.Name, - TargetPortName: rd.ServiceSpec.Name, + Name: ed.Name, + Namespace: ed.Namespace, + Labels: ed.Labels, + ServiceName: ed.Service.Spec.Name, + TargetPortName: ed.Service.Spec.Name, }), time.Duration(5)*time.Second, - routeOverrides, + []route.OverrideSpec{ed.Route.OverrideSpec}, ) if err != nil { return ctrl.Result{}, err } - route.OwnerReferences = append(route.OwnerReferences, owner) + enptRoute.OwnerReferences = append(enptRoute.OwnerReferences, owner) + + // if route TLS is disabled -> create the route + // if TLS is enabled and the route does not yet exist -> create the route + // to get the hostname for creating the cert + serviceRoute := &routev1.Route{} + err = helper.GetClient().Get(ctx, types.NamespacedName{Name: ed.Name, Namespace: ed.Namespace}, serviceRoute) + if !ed.TLS.Enabled || (ed.TLS.Enabled && err != nil && k8s_errors.IsNotFound(err)) { + ctrlResult, err := enptRoute.CreateOrPatch(ctx, helper) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } - ctrlResult, err := route.CreateOrPatch(ctx, helper) - if err != nil { - return ctrlResult, err - } else if (ctrlResult != ctrl.Result{}) { - return ctrlResult, nil + ed.Hostname = ptr.To(enptRoute.GetHostname()) + } else if err != nil { + return ctrl.Result{}, err + } else { + ed.Hostname = &serviceRoute.Spec.Host } - rd.hostname = ptr.To(route.GetHostname()) - rd.endpointURL = "http://" + *rd.hostname - rd.route = route.GetRoute() + // if the issuer is provided TLS is enabled + if ed.TLS.Enabled { + var ctrlResult reconcile.Result + + certSecret := &k8s_corev1.Secret{} + + // if a custom cert secret was provided, check if it exist + // and has the required cert, key and cacert + // Right now there is no check if certificate is valid for + // the hostname of the route. If the referenced secret is + // there and has the required files it is just being used. + if ed.TLS.CertSecret != nil { + certSecret, _, err = secret.GetSecret(ctx, helper, *ed.TLS.CertSecret, ed.Namespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("certificate secret %s not found: %w", *ed.TLS.CertSecret, err) + } + + return ctrl.Result{}, err + } + + // check if secret has the expected entries tls.crt, tls.key and ca.crt + if certSecret != nil { + for _, key := range []string{"tls.crt", "tls.key", "ca.crt"} { + if _, exist := certSecret.Data[key]; !exist { + return ctrl.Result{}, fmt.Errorf("certificate secret %s does not provide %s", *ed.TLS.CertSecret, key) + } + } + } + } else { + //create the cert using our issuer for the endpoint + certSecret, ctrlResult, err = certmanager.EnsureCert( + ctx, + helper, + ed.TLS.Issuer, + ed.Name, + nil, + []string{*ed.Hostname}, + ed.Annotations, + ed.Labels) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + } + + // create default TLS route override + tlsConfig := &routev1.TLSConfig{ + Termination: routev1.TLSTerminationEdge, + Certificate: string(certSecret.Data["tls.crt"]), + Key: string(certSecret.Data["tls.key"]), + CACertificate: string(certSecret.Data["ca.crt"]), + InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, + } + + // for internal TLS (TLSE) use routev1.TLSTerminationReencrypt + if ed.TLS.InternalCA != "" { + tlsConfig.Termination = routev1.TLSTerminationReencrypt + tlsConfig.DestinationCACertificate = ed.TLS.InternalCA + } + + enptRoute, err = route.NewRoute( + enptRoute.GetRoute(), + time.Duration(5)*time.Second, + []route.OverrideSpec{ + { + Spec: &route.Spec{ + TLS: tlsConfig, + }, + }, + ed.Route.OverrideSpec, + }, + ) + if err != nil { + return ctrl.Result{}, err + } + + ctrlResult, err = enptRoute.CreateOrPatch(ctx, helper) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + ed.EndpointURL = "https://" + *ed.Hostname + } else { + ed.EndpointURL = "http://" + *ed.Hostname + } return ctrl.Result{}, nil } diff --git a/pkg/openstack/glance.go b/pkg/openstack/glance.go index 447235f64..ca6e4fada 100644 --- a/pkg/openstack/glance.go +++ b/pkg/openstack/glance.go @@ -72,14 +72,14 @@ func ReconcileGlance(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - serviceOverrides, ctrlResult, err = EnsureRoute( + serviceOverrides, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, glance, svcs, serviceOverrides, - instance.Spec.Glance.APIOverride.Route, + instance.Spec.Glance.APIOverride, corev1beta1.OpenStackControlPlaneExposeGlanceReadyCondition, ) if err != nil { diff --git a/pkg/openstack/heat.go b/pkg/openstack/heat.go index 033699b2d..123ba56d7 100644 --- a/pkg/openstack/heat.go +++ b/pkg/openstack/heat.go @@ -77,14 +77,14 @@ func ReconcileHeat(ctx context.Context, instance *corev1beta1.OpenStackControlPl } var ctrlResult reconcile.Result - instance.Spec.Heat.Template.HeatAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Heat.Template.HeatAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, heat, svcs, instance.Spec.Heat.Template.HeatAPI.Override.Service, - instance.Spec.Heat.APIOverride.Route, + instance.Spec.Heat.APIOverride, corev1beta1.OpenStackControlPlaneExposeHeatReadyCondition, ) if err != nil { @@ -107,14 +107,14 @@ func ReconcileHeat(ctx context.Context, instance *corev1beta1.OpenStackControlPl } var ctrlResult reconcile.Result - instance.Spec.Heat.Template.HeatCfnAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Heat.Template.HeatCfnAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, heat, svcs, instance.Spec.Heat.Template.HeatCfnAPI.Override.Service, - instance.Spec.Heat.CnfAPIOverride.Route, + instance.Spec.Heat.CnfAPIOverride, corev1beta1.OpenStackControlPlaneExposeHeatReadyCondition, ) if err != nil { diff --git a/pkg/openstack/horizon.go b/pkg/openstack/horizon.go index ce61ed5d7..a4b31a574 100644 --- a/pkg/openstack/horizon.go +++ b/pkg/openstack/horizon.go @@ -79,14 +79,14 @@ func ReconcileHorizon(ctx context.Context, instance *corev1beta1.OpenStackContro } var ctrlResult reconcile.Result - serviceOverrides, ctrlResult, err = EnsureRoute( + serviceOverrides, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, horizon, svcs, serviceOverrides, - instance.Spec.Horizon.APIOverride.Route, + instance.Spec.Horizon.APIOverride, corev1beta1.OpenStackControlPlaneExposeHorizonReadyCondition, ) if err != nil { diff --git a/pkg/openstack/ironic.go b/pkg/openstack/ironic.go index 44d0f5d74..66f9b2b52 100644 --- a/pkg/openstack/ironic.go +++ b/pkg/openstack/ironic.go @@ -77,14 +77,14 @@ func ReconcileIronic(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - instance.Spec.Ironic.Template.IronicAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Ironic.Template.IronicAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, ironic, svcs, instance.Spec.Ironic.Template.IronicAPI.Override.Service, - instance.Spec.Ironic.APIOverride.Route, + instance.Spec.Ironic.APIOverride, corev1beta1.OpenStackControlPlaneExposeIronicReadyCondition, ) if err != nil { @@ -107,14 +107,14 @@ func ReconcileIronic(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - instance.Spec.Ironic.Template.IronicInspector.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Ironic.Template.IronicInspector.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, ironic, svcs, instance.Spec.Ironic.Template.IronicInspector.Override.Service, - instance.Spec.Ironic.InspectorOverride.Route, + instance.Spec.Ironic.InspectorOverride, corev1beta1.OpenStackControlPlaneExposeIronicReadyCondition, ) if err != nil { diff --git a/pkg/openstack/keystone.go b/pkg/openstack/keystone.go index 4aa507c12..3a1b58c18 100644 --- a/pkg/openstack/keystone.go +++ b/pkg/openstack/keystone.go @@ -68,14 +68,14 @@ func ReconcileKeystoneAPI(ctx context.Context, instance *corev1beta1.OpenStackCo } var ctrlResult reconcile.Result - instance.Spec.Keystone.Template.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Keystone.Template.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, keystoneAPI, svcs, instance.Spec.Keystone.Template.Override.Service, - instance.Spec.Keystone.APIOverride.Route, + instance.Spec.Keystone.APIOverride, corev1beta1.OpenStackControlPlaneExposeKeystoneAPIReadyCondition, ) if err != nil { diff --git a/pkg/openstack/manila.go b/pkg/openstack/manila.go index 735a804fc..4c4412ee0 100644 --- a/pkg/openstack/manila.go +++ b/pkg/openstack/manila.go @@ -69,14 +69,14 @@ func ReconcileManila(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - instance.Spec.Manila.Template.ManilaAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Manila.Template.ManilaAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, manila, svcs, instance.Spec.Manila.Template.ManilaAPI.Override.Service, - instance.Spec.Manila.APIOverride.Route, + instance.Spec.Manila.APIOverride, corev1beta1.OpenStackControlPlaneExposeManilaReadyCondition, ) if err != nil { diff --git a/pkg/openstack/neutron.go b/pkg/openstack/neutron.go index 7a15f0e26..c933cd3d7 100644 --- a/pkg/openstack/neutron.go +++ b/pkg/openstack/neutron.go @@ -68,14 +68,14 @@ func ReconcileNeutron(ctx context.Context, instance *corev1beta1.OpenStackContro } var ctrlResult reconcile.Result - instance.Spec.Neutron.Template.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Neutron.Template.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, neutronAPI, svcs, instance.Spec.Neutron.Template.Override.Service, - instance.Spec.Neutron.APIOverride.Route, + instance.Spec.Neutron.APIOverride, corev1beta1.OpenStackControlPlaneExposeNeutronReadyCondition, ) if err != nil { diff --git a/pkg/openstack/nova.go b/pkg/openstack/nova.go index 58ebf5e1f..0366fc730 100644 --- a/pkg/openstack/nova.go +++ b/pkg/openstack/nova.go @@ -105,14 +105,14 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl } var ctrlResult reconcile.Result - instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, nova, svcs, instance.Spec.Nova.Template.APIServiceTemplate.Override.Service, - instance.Spec.Nova.APIOverride.Route, + instance.Spec.Nova.APIOverride, corev1beta1.OpenStackControlPlaneExposeNovaReadyCondition, ) if err != nil { @@ -147,7 +147,7 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl } var ctrlResult reconcile.Result - routedOverrideSpec, ctrlResult, err := EnsureRoute( + routedOverrideSpec, ctrlResult, err := EnsureEndpointConfig( ctx, instance, helper, @@ -156,7 +156,7 @@ func ReconcileNova(ctx context.Context, instance *corev1beta1.OpenStackControlPl map[service.Endpoint]service.RoutedOverrideSpec{ service.EndpointPublic: *cellTemplate.NoVNCProxyServiceTemplate.Override.Service, }, - instance.Spec.Nova.CellOverride[cellName].NoVNCProxy.Route, + instance.Spec.Nova.CellOverride[cellName].NoVNCProxy, corev1beta1.OpenStackControlPlaneExposeNovaReadyCondition, ) if err != nil { diff --git a/pkg/openstack/octavia.go b/pkg/openstack/octavia.go index 6715197f1..cb534fa8c 100644 --- a/pkg/openstack/octavia.go +++ b/pkg/openstack/octavia.go @@ -84,14 +84,14 @@ func ReconcileOctavia(ctx context.Context, instance *corev1beta1.OpenStackContro } var ctrlResult reconcile.Result - instance.Spec.Octavia.Template.OctaviaAPI.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Octavia.Template.OctaviaAPI.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, octavia, svcs, instance.Spec.Octavia.Template.OctaviaAPI.Override.Service, - instance.Spec.Octavia.APIOverride.Route, + instance.Spec.Octavia.APIOverride, corev1beta1.OpenStackControlPlaneExposeOctaviaReadyCondition, ) if err != nil { diff --git a/pkg/openstack/openstackclient.go b/pkg/openstack/openstackclient.go index 2481daab4..87c1f0bf5 100644 --- a/pkg/openstack/openstackclient.go +++ b/pkg/openstack/openstackclient.go @@ -43,10 +43,14 @@ func ReconcileOpenStackClient(ctx context.Context, instance *corev1.OpenStackCon helper.GetLogger().Info("Reconciling OpenStackClient", "OpenStackClient.Namespace", instance.Namespace, "OpenStackClient.Name", openstackclient.Name) op, err := controllerutil.CreateOrPatch(ctx, helper.GetClient(), openstackclient, func() error { - // the following are created/owned by keystoneclient - openstackclient.Spec.OpenStackConfigMap = "openstack-config" - openstackclient.Spec.OpenStackConfigSecret = "openstack-config-secret" - openstackclient.Spec.NodeSelector = instance.Spec.NodeSelector + instance.Spec.OpenStackClient.Template.DeepCopyInto(&openstackclient.Spec) + + for _, config := range instance.Spec.TLS.Endpoint { + if config.Enabled { + openstackclient.Spec.Ca.CaSecretName = CombinedCASecret + break + } + } err := controllerutil.SetControllerReference(helper.GetBeforeObject(), openstackclient, helper.GetScheme()) if err != nil { diff --git a/pkg/openstack/placement.go b/pkg/openstack/placement.go index 388d5a1dd..7dc484c46 100644 --- a/pkg/openstack/placement.go +++ b/pkg/openstack/placement.go @@ -67,14 +67,14 @@ func ReconcilePlacementAPI(ctx context.Context, instance *corev1beta1.OpenStackC } var ctrlResult reconcile.Result - instance.Spec.Placement.Template.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Placement.Template.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, placementAPI, svcs, instance.Spec.Placement.Template.Override.Service, - instance.Spec.Placement.APIOverride.Route, + instance.Spec.Placement.APIOverride, corev1beta1.OpenStackControlPlaneExposePlacementAPIReadyCondition, ) if err != nil { diff --git a/pkg/openstack/swift.go b/pkg/openstack/swift.go index 006c3a50c..e152d5203 100644 --- a/pkg/openstack/swift.go +++ b/pkg/openstack/swift.go @@ -68,14 +68,14 @@ func ReconcileSwift(ctx context.Context, instance *corev1beta1.OpenStackControlP } var ctrlResult reconcile.Result - instance.Spec.Swift.Template.SwiftProxy.Override.Service, ctrlResult, err = EnsureRoute( + instance.Spec.Swift.Template.SwiftProxy.Override.Service, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, swift, svcs, instance.Spec.Swift.Template.SwiftProxy.Override.Service, - instance.Spec.Swift.ProxyOverride.Route, + instance.Spec.Swift.ProxyOverride, corev1beta1.OpenStackControlPlaneExposeSwiftReadyCondition, ) if err != nil { diff --git a/pkg/openstackclient/funcs.go b/pkg/openstackclient/funcs.go index cf9fd7044..512c3ade1 100644 --- a/pkg/openstackclient/funcs.go +++ b/pkg/openstackclient/funcs.go @@ -13,19 +13,26 @@ limitations under the License. package openstackclient import ( + "context" + + env "github.com/openstack-k8s-operators/lib-common/modules/common/env" + "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" clientv1 "github.com/openstack-k8s-operators/openstack-operator/apis/client/v1beta1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" ) // ClientPod func func ClientPod( + ctx context.Context, instance *clientv1.OpenStackClient, + helper *helper.Helper, labels map[string]string, configHash string, - secretHash string, -) *corev1.Pod { +) (*corev1.Pod, error) { clientPod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -34,76 +41,83 @@ func ClientPod( }, } - var terminationGracePeriodSeconds int64 = 0 - runAsUser := int64(42401) - runAsGroup := int64(42401) + envVars := map[string]env.Setter{} + envVars["OS_CLOUD"] = env.SetValue("default") + envVars["CONFIG_HASH"] = env.SetValue(configHash) + clientPod.ObjectMeta = metav1.ObjectMeta{ Name: instance.Name, Namespace: instance.Namespace, Labels: labels, } - clientPod.Spec.TerminationGracePeriodSeconds = &terminationGracePeriodSeconds + clientPod.Spec.TerminationGracePeriodSeconds = ptr.To[int64](0) clientPod.Spec.ServiceAccountName = instance.RbacResourceName() - clientPod.Spec.Containers = []corev1.Container{ - { - Name: "openstackclient", - Image: instance.Spec.ContainerImage, - Command: []string{"/bin/sleep"}, - Args: []string{"infinity"}, - SecurityContext: &corev1.SecurityContext{ - RunAsUser: &runAsUser, - RunAsGroup: &runAsGroup, - }, - Env: []corev1.EnvVar{ - { - Name: "OS_CLOUD", - Value: "default", - }, - { - Name: "CONFIG_HASH", - Value: configHash, - }, - { - Name: "SECRET_HASH", - Value: secretHash, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "openstack-config", - MountPath: "/etc/openstack/clouds.yaml", - SubPath: "clouds.yaml", - }, - { - Name: "openstack-config-secret", - MountPath: "/etc/openstack/secure.yaml", - SubPath: "secure.yaml", + clientContainer := corev1.Container{ + Name: "openstackclient", + Image: instance.Spec.ContainerImage, + Command: []string{"/bin/sleep"}, + Args: []string{"infinity"}, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: ptr.To[int64](42401), + RunAsGroup: ptr.To[int64](42401), + RunAsNonRoot: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", }, }, }, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), } - clientPod.Spec.Volumes = clientPodVolumes(instance, labels, configHash, secretHash) + + tlsConfig, err := tls.NewTLS(ctx, helper, instance.Namespace, nil, &instance.Spec.Ca) + if err != nil { + return nil, err + } + clientContainer.VolumeMounts = clientPodVolumeMounts(tlsConfig) + + clientPod.Spec.Containers = []corev1.Container{clientContainer} + + clientPod.Spec.Volumes = clientPodVolumes(instance, tlsConfig) if instance.Spec.NodeSelector != nil && len(instance.Spec.NodeSelector) > 0 { clientPod.Spec.NodeSelector = instance.Spec.NodeSelector } - return clientPod + return clientPod, nil +} + +func clientPodVolumeMounts( + tlsConfig *tls.TLS, +) []corev1.VolumeMount { + volumeMounts := []corev1.VolumeMount{ + { + Name: "openstack-config", + MountPath: "/home/cloud-admin/.config/openstack/clouds.yaml", + SubPath: "clouds.yaml", + }, + { + Name: "openstack-config-secret", + MountPath: "/home/cloud-admin/.config/openstack/secure.yaml", + SubPath: "secure.yaml", + }, + } + volumeMounts = append(volumeMounts, tlsConfig.CreateVolumeMounts()...) + + return volumeMounts } func clientPodVolumes( instance *clientv1.OpenStackClient, - labels map[string]string, - configHash string, - secretHash string, + tlsConfig *tls.TLS, ) []corev1.Volume { - - return []corev1.Volume{ + volumes := []corev1.Volume{ { Name: "openstack-config", VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ LocalObjectReference: corev1.LocalObjectReference{ - Name: instance.Spec.OpenStackConfigMap, + Name: *instance.Spec.OpenStackConfigMap, }, }, }, @@ -112,10 +126,12 @@ func clientPodVolumes( Name: "openstack-config-secret", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: instance.Spec.OpenStackConfigSecret, + SecretName: *instance.Spec.OpenStackConfigSecret, }, }, }, } + volumes = append(volumes, tlsConfig.CreateVolumes()...) + return volumes }