diff --git a/apis/bases/client.openstack.org_openstackclients.yaml b/apis/bases/client.openstack.org_openstackclients.yaml index 753c8839f..d087e626b 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 97723274d..a43856510 100644 --- a/apis/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/apis/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -8964,6 +8964,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: @@ -13773,6 +13797,25 @@ spec: - swiftStorage type: object type: object + tls: + properties: + caSecretName: + type: string + internalEndpoints: + properties: + enabled: + default: false + type: boolean + type: object + publicEndpoints: + properties: + enabled: + default: true + type: boolean + issuer: + type: string + 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..e72eb7a0a 100644 --- a/apis/core/v1beta1/openstackcontrolplane_types.go +++ b/apis/core/v1beta1/openstackcontrolplane_types.go @@ -23,10 +23,12 @@ 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/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 +36,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 +71,11 @@ 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 + //+operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS TLSSection `json:"tls,omitempty"` + // +kubebuilder:validation:Optional //+operator-sdk:csv:customresourcedefinitions:type=spec // DNS - Parameters related to the DNSMasq service @@ -158,6 +165,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 +180,47 @@ 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 + // PublicEndpoints tls configuration + PublicEndpoints TLSPublicEndpointSection `json:"publicEndpoints,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // InternalEndpoints tls configuration + InternalEndpoints TLSInternalEndpointSection `json:"internalEndpoints,omitempty"` + + // +kubebuilder:validation:optional + //+operator-sdk:csv:customresourcedefinitions:type=spec + // Secret containing any additional CA certificates, which should be added to deployment pods + tls.Ca `json:",inline"` +} + +// TLSPublicEndpointSection defines the desired state of public TLSEndpoint configuration +type TLSPublicEndpointSection struct { + // +kubebuilder:validation:Optional + // +kubebuilder:default=true + // +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"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // Issuer - cert-manager issuer to be used for the endpoint type. If not specified a self signed will be created. + Issuer *string `json:"issuer,omitempty"` +} + +// TLSInternalEndpointSection defines the desired state of internal TLSEndpoint configuration +type TLSInternalEndpointSection struct { + // +kubebuilder:validation:Optional + // +kubebuilder:default=false + // +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 @@ -561,6 +614,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 +703,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..1985cb9ce 100644 --- a/apis/core/v1beta1/zz_generated.deepcopy.go +++ b/apis/core/v1beta1/zz_generated.deepcopy.go @@ -325,6 +325,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 +425,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 +446,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)) @@ -645,3 +663,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 *TLSInternalEndpointSection) DeepCopyInto(out *TLSInternalEndpointSection) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSInternalEndpointSection. +func (in *TLSInternalEndpointSection) DeepCopy() *TLSInternalEndpointSection { + if in == nil { + return nil + } + out := new(TLSInternalEndpointSection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSPublicEndpointSection) DeepCopyInto(out *TLSPublicEndpointSection) { + *out = *in + if in.Issuer != nil { + in, out := &in.Issuer, &out.Issuer + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSPublicEndpointSection. +func (in *TLSPublicEndpointSection) DeepCopy() *TLSPublicEndpointSection { + if in == nil { + return nil + } + out := new(TLSPublicEndpointSection) + 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 + in.PublicEndpoints.DeepCopyInto(&out.PublicEndpoints) + out.InternalEndpoints = in.InternalEndpoints + 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 +} diff --git a/config/crd/bases/client.openstack.org_openstackclients.yaml b/config/crd/bases/client.openstack.org_openstackclients.yaml index 753c8839f..d087e626b 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 97723274d..a43856510 100644 --- a/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml +++ b/config/crd/bases/core.openstack.org_openstackcontrolplanes.yaml @@ -8964,6 +8964,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: @@ -13773,6 +13797,25 @@ spec: - swiftStorage type: object type: object + tls: + properties: + caSecretName: + type: string + internalEndpoints: + properties: + enabled: + default: false + type: boolean + type: object + publicEndpoints: + properties: + enabled: + default: true + type: boolean + issuer: + type: string + 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..b8d5d3884 100644 --- a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml @@ -237,6 +237,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 @@ -315,6 +322,29 @@ spec: - 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: InternalEndpoints tls configuration + displayName: Internal Endpoints + path: tls.internalEndpoints + - description: Enabled - Whether TLS should be enabled for endpoint type + displayName: Enabled + path: tls.internalEndpoints.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: PublicEndpoints tls configuration + displayName: Public Endpoints + path: tls.publicEndpoints + - description: Enabled - Whether TLS should be enabled for endpoint type + displayName: Enabled + path: tls.publicEndpoints.enabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Issuer - cert-manager issuer to be used for the endpoint type. + If not specified a self signed will be created. + displayName: Issuer + path: tls.publicEndpoints.issuer 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..4a1623aeb 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane.yaml @@ -5,6 +5,14 @@ metadata: spec: secret: osp-secret storageClass: local-storage + tls: + internalEndpoints: + enabled: false + publicEndpoints: + enabled: true + openstackclient: + template: + containerImage: quay.io/mschuppe/openstack-openstackclient:current-podified 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..bc42ec9d4 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_galera_network_isolation.yaml @@ -5,6 +5,14 @@ metadata: spec: secret: osp-secret storageClass: local-storage + tls: + internalEndpoints: + enabled: false + publicEndpoints: + enabled: true + openstackclient: + template: + containerImage: quay.io/mschuppe/openstack-openstackclient:current-podified 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..2bd1623bb 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,14 @@ metadata: spec: secret: osp-secret storageClass: local-storage + tls: + internalEndpoints: + enabled: false + publicEndpoints: + enabled: true + openstackclient: + template: + containerImage: quay.io/mschuppe/openstack-openstackclient:current-podified 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..bff45b8cb 100644 --- a/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml +++ b/config/samples/core_v1beta1_openstackcontrolplane_network_isolation.yaml @@ -5,6 +5,14 @@ metadata: spec: secret: osp-secret storageClass: local-storage + tls: + internalEndpoints: + enabled: false + publicEndpoints: + enabled: true + openstackclient: + template: + containerImage: quay.io/mschuppe/openstack-openstackclient:current-podified dns: template: override: diff --git a/controllers/client/openstackclient_controller.go b/controllers/client/openstackclient_controller.go index 5ecaf3fd4..196fc71bc 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,6 +218,34 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ err.Error())) return ctrl.Result{}, err } + configVars[*instance.Spec.OpenStackConfigSecret] = env.SetValue(secretHash) + + 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) + } + + configVarsHash, err := util.HashOfInputHashes(configVars) + if err != nil { + return ctrl.Result{}, err + } instance.Status.Conditions.Set(condition.FalseCondition( clientv1.OpenStackClientReadyCondition, @@ -216,10 +253,10 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ condition.SeverityInfo, clientv1.OpenStackClientInputReady)) - clientLabels := map[string]string{ - "app": "openstackclient", + 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..fc74de612 100644 --- a/controllers/core/openstackcontrolplane_controller.go +++ b/controllers/core/openstackcontrolplane_controller.go @@ -94,6 +94,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 +167,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{}) { diff --git a/go.mod b/go.mod index c0f7ca06d..287bfcae1 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ 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 @@ -16,6 +17,7 @@ require ( github.com/openstack-k8s-operators/infra-operator/apis v0.1.1-0.20231001103054-f74a88ed4971 github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231006045519-4d73854cb09a github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231005132119-e75019792469 + github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231006072650-7fe7fe16bcd1 github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231011120827-7fd4e4c731e5 github.com/openstack-k8s-operators/manila-operator/api v0.3.1-0.20231009201721-085058b855a5 github.com/openstack-k8s-operators/mariadb-operator/api v0.3.0 @@ -46,6 +48,7 @@ require ( 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 ( diff --git a/go.sum b/go.sum index aee3c5f15..96270ef2e 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= @@ -151,6 +153,8 @@ github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231006045519-4 github.com/openstack-k8s-operators/ironic-operator/api v0.3.1-0.20231006045519-4d73854cb09a/go.mod h1:NR5xmmZQz/v1EgGfSrL/4yCbQpRbaWLoIx1CgpGiWck= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231005132119-e75019792469 h1:gi9kvJAF8YrsQnRhOVjTWtcjhOqFe7rD3uU6g6pKCFQ= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231005132119-e75019792469/go.mod h1:5v0ngxNmFp8QsINo2bufx1/COJc0q6jm3FMhP3xIAWE= +github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231006072650-7fe7fe16bcd1 h1:sE/qio/WNUEng0VBmefSr46e/cq4R83payEzge/Y48U= +github.com/openstack-k8s-operators/lib-common/modules/certmanager v0.0.0-20231006072650-7fe7fe16bcd1/go.mod h1:u1pqzqGNLcof95aqhLfU6xHVTD6ZTc5gWy2FE03UrZQ= github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231011120827-7fd4e4c731e5 h1:xQI8tkHRVqcrp623yStvQVqmEEgUocq5JUZgf0UE2nQ= github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231011120827-7fd4e4c731e5/go.mod h1:xXAuy7HtWN4p7LF5Q+NHLkwAsKVh0KrzpnuPYIG3XaA= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231011120827-7fd4e4c731e5 h1:9ST0OsJYurogqai8f4pJtM2xumd8DjQ0cNYZ7hD8TYw= @@ -383,6 +387,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..823a23ae6 --- /dev/null +++ b/pkg/openstack/ca.go @@ -0,0 +1,292 @@ +package openstack + +import ( + "context" + "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/util" + + corev1 "github.com/openstack-k8s-operators/openstack-operator/apis/core/v1beta1" + + ctrl "sigs.k8s.io/controller-runtime" +) + +const ( + // CombinedCASecret - + CombinedCASecret = "combined-ca-bundle" + // DefaultPublicCAName - + DefaultPublicCAName = "rootca-" + string(service.EndpointPublic) + // DefaultInternalCAName - + DefaultInternalCAName = "rootca-" + string(service.EndpointInternal) +) + +// 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 + } + + caCerts := map[string]string{} + + // create RootCA cert and Issuer that uses the generated CA certificate to issue certs + if instance.Spec.TLS.PublicEndpoints.Enabled && instance.Spec.TLS.PublicEndpoints.Issuer == nil { + caCert, ctrlResult, err := createRootCACertAndIssuer( + ctx, + instance, + helper, + issuerReq, + DefaultPublicCAName, + map[string]string{}, + ) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + caCerts[DefaultPublicCAName] = string(caCert) + } + if instance.Spec.TLS.InternalEndpoints.Enabled { + caCert, ctrlResult, err := createRootCACertAndIssuer( + ctx, + instance, + helper, + issuerReq, + DefaultInternalCAName, + map[string]string{ + certmanager.RootCAIssuerInternalLabel: "", + }, + ) + if err != nil { + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + caCerts[DefaultInternalCAName] = string(caCert) + } + 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 key, ca := range caSecret.Data { + key := instance.Spec.TLS.CaSecretName + "-" + key + caCerts[key] = string(ca) + } + } + + saSecretTemplate := []util.Template{ + { + Name: CombinedCASecret, + Namespace: instance.Namespace, + Type: util.TemplateTypeNone, + InstanceType: instance.Kind, + AdditionalTemplate: nil, + Annotations: map[string]string{}, + Labels: map[string]string{ + CombinedCASecret: "", + }, + ConfigOptions: nil, + CustomData: caCerts, + }, + } + + 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, +) (string, ctrl.Result, error) { + var caCert string + // 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 caCert, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return caCert, 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 caCert, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return caCert, ctrlResult, nil + } + + caCert, ctrlResult, err = getCAFromSecret(ctx, instance, helper, caName) + if err != nil { + return caCert, ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return caCert, ctrlResult, nil + } + + return caCert, ctrl.Result{}, nil +} + +func getCAFromSecret( + ctx context.Context, + instance *corev1.OpenStackControlPlane, + helper *helper.Helper, + caName string, +) (string, ctrl.Result, error) { + caSecret, ctrlResult, err := secret.GetDataFromSecret(ctx, helper, caName, time.Duration(5), "ca.crt") + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + corev1.OpenStackControlPlaneCAReadyErrorMessage, + "secret", + caName, + err.Error())) + + return caSecret, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + corev1.OpenStackControlPlaneCAReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + corev1.OpenStackControlPlaneCAReadyRunningMessage)) + + return caSecret, ctrlResult, nil + } + + return caSecret, ctrl.Result{}, nil +} diff --git a/pkg/openstack/cinder.go b/pkg/openstack/cinder.go index a0be02e74..547e0a9e0 100644 --- a/pkg/openstack/cinder.go +++ b/pkg/openstack/cinder.go @@ -68,7 +68,7 @@ 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, diff --git a/pkg/openstack/common.go b/pkg/openstack/common.go index 91de48804..43836c178 100644 --- a/pkg/openstack/common.go +++ b/pkg/openstack/common.go @@ -6,6 +6,7 @@ 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" @@ -13,9 +14,11 @@ import ( "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" @@ -52,17 +55,38 @@ 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 { + Issuer *string + PublicEndpoint bool + InternalEndpoint bool + InternalCA string +} + +// 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 +110,268 @@ 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, + routeOverrideSpec *route.OverrideSpec, condType condition.Type, ) (map[service.Endpoint]service.RoutedOverrideSpec, ctrl.Result, error) { + for _, svc := range svcs.Items { + ed := EndpointDetails{ + Name: svc.Name, + Namespace: svc.Namespace, + Type: service.Endpoint(svc.Annotations[service.AnnotationEndpointKey]), + Service: ServiceDetails{ + Spec: &svc, + }, + } + if routeOverrideSpec != nil { + ed.Route.OverrideSpec = *routeOverrideSpec + } + + // check if there is a custom issuer name reference provided + if ed.Type == service.EndpointPublic && instance.Spec.TLS.PublicEndpoints.Enabled { + if instance.Spec.TLS.PublicEndpoints.Issuer != nil { + ed.TLS.Issuer = instance.Spec.TLS.PublicEndpoints.Issuer + } else { + ed.TLS.Issuer = ptr.To(DefaultPublicCAName) + } + ed.TLS.PublicEndpoint = true + } + if ed.Type == service.EndpointInternal && instance.Spec.TLS.InternalEndpoints.Enabled { + ed.TLS.Issuer = ptr.To(DefaultInternalCAName) + ed.TLS.InternalEndpoint = true + // TODO: for TLSE create TLS cert for service + } - cleanCondition := true + ed.Service.OverrideSpec = svcOverrides[ed.Type] - 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 ed.Type == service.EndpointPublic { + ctrlResult, err := ed.ensureRoute(ctx, instance, helper, &svc, owner, condType) if err != nil { - return svcOverrides, ctrl.Result{}, err + return svcOverrides, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return svcOverrides, ctrlResult, nil } + } - // 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), - } + // 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()) + } - owner := metav1.GetControllerOf(&r.ObjectMeta) - - // 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}) - - // 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 - } - - if svcOverride.EndpointURL != nil { - svcOverride.EndpointURL = nil - helper.GetLogger().Info(fmt.Sprintf("Service %s override endpointURL removed", svc.Name)) - } - } - } + svcOverrides[ed.Type] = ed.Service.OverrideSpec + } + + return svcOverrides, ctrl.Result{}, 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 } - // 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, - )) + // 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 svcOverride.EmbeddedLabelsAnnotations == nil { - svcOverride.EmbeddedLabelsAnnotations = &service.EmbeddedLabelsAnnotations{} - } + owner := metav1.GetControllerOf(&r.ObjectMeta) - if labelVal, ok := svcOverride.EmbeddedLabelsAnnotations.Labels[common.AppSelector]; ok { - rd.ServiceLabel = map[string]string{common.AppSelector: labelVal} - } + // 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}) - 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())) + // 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 + } - return svcOverrides, ctrlResult, err - } else if (ctrlResult != ctrl.Result{}) { - return svcOverrides, ctrlResult, nil + if ed.Service.OverrideSpec.EndpointURL != nil { + ed.Service.OverrideSpec.EndpointURL = nil + helper.GetLogger().Info(fmt.Sprintf("Service %s override endpointURL removed", svc.Name)) + } } + } + } - cleanCondition = false + // 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{} + } - // 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()) - } + if labelVal, ok := ed.Service.OverrideSpec.EmbeddedLabelsAnnotations.Labels[common.AppSelector]; ok { + ed.Labels = map[string]string{common.AppSelector: labelVal} } - svcOverrides[rd.Endpoint] = svcOverride - } + 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 + } - if cleanCondition { - instance.Status.Conditions.Remove(condType) + 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.PublicEndpoint && err != nil && k8s_errors.IsNotFound(err)) || !ed.TLS.PublicEndpoint { + 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.Issuer != nil { + //create the cert + 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 TLSE use routev1.TLSTerminationReencrypt + if ed.TLS.InternalEndpoint { + 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..eec84b8d9 100644 --- a/pkg/openstack/glance.go +++ b/pkg/openstack/glance.go @@ -72,7 +72,7 @@ func ReconcileGlance(ctx context.Context, instance *corev1beta1.OpenStackControl } var ctrlResult reconcile.Result - serviceOverrides, ctrlResult, err = EnsureRoute( + serviceOverrides, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, diff --git a/pkg/openstack/heat.go b/pkg/openstack/heat.go index 033699b2d..5b46ec379 100644 --- a/pkg/openstack/heat.go +++ b/pkg/openstack/heat.go @@ -77,7 +77,7 @@ 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, @@ -107,7 +107,7 @@ 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, diff --git a/pkg/openstack/horizon.go b/pkg/openstack/horizon.go index ce61ed5d7..00002e758 100644 --- a/pkg/openstack/horizon.go +++ b/pkg/openstack/horizon.go @@ -79,7 +79,7 @@ func ReconcileHorizon(ctx context.Context, instance *corev1beta1.OpenStackContro } var ctrlResult reconcile.Result - serviceOverrides, ctrlResult, err = EnsureRoute( + serviceOverrides, ctrlResult, err = EnsureEndpointConfig( ctx, instance, helper, diff --git a/pkg/openstack/ironic.go b/pkg/openstack/ironic.go index 44d0f5d74..1acd5e2c6 100644 --- a/pkg/openstack/ironic.go +++ b/pkg/openstack/ironic.go @@ -77,7 +77,7 @@ 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, @@ -107,7 +107,7 @@ 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, diff --git a/pkg/openstack/keystone.go b/pkg/openstack/keystone.go index 4aa507c12..c4b9dadad 100644 --- a/pkg/openstack/keystone.go +++ b/pkg/openstack/keystone.go @@ -68,7 +68,7 @@ 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, diff --git a/pkg/openstack/manila.go b/pkg/openstack/manila.go index 735a804fc..5d570ebec 100644 --- a/pkg/openstack/manila.go +++ b/pkg/openstack/manila.go @@ -69,7 +69,7 @@ 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, diff --git a/pkg/openstack/neutron.go b/pkg/openstack/neutron.go index 7a15f0e26..0825f496d 100644 --- a/pkg/openstack/neutron.go +++ b/pkg/openstack/neutron.go @@ -68,7 +68,7 @@ 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, diff --git a/pkg/openstack/nova.go b/pkg/openstack/nova.go index 58ebf5e1f..701022b88 100644 --- a/pkg/openstack/nova.go +++ b/pkg/openstack/nova.go @@ -105,7 +105,7 @@ 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, @@ -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, diff --git a/pkg/openstack/octavia.go b/pkg/openstack/octavia.go index 6715197f1..5ebded59b 100644 --- a/pkg/openstack/octavia.go +++ b/pkg/openstack/octavia.go @@ -84,7 +84,7 @@ 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, diff --git a/pkg/openstack/openstackclient.go b/pkg/openstack/openstackclient.go index 2481daab4..588027149 100644 --- a/pkg/openstack/openstackclient.go +++ b/pkg/openstack/openstackclient.go @@ -43,10 +43,11 @@ 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) + + if instance.Spec.TLS.PublicEndpoints.Enabled || instance.Spec.TLS.InternalEndpoints.Enabled { + openstackclient.Spec.Ca.CaSecretName = CombinedCASecret + } 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..1c66971e9 100644 --- a/pkg/openstack/placement.go +++ b/pkg/openstack/placement.go @@ -67,7 +67,7 @@ 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, diff --git a/pkg/openstack/swift.go b/pkg/openstack/swift.go index 006c3a50c..b4d98b262 100644 --- a/pkg/openstack/swift.go +++ b/pkg/openstack/swift.go @@ -68,7 +68,7 @@ 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, 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 }