diff --git a/api/bases/nova.openstack.org_nova.yaml b/api/bases/nova.openstack.org_nova.yaml index 6fefa0194..7fe1fbb3c 100644 --- a/api/bases/nova.openstack.org_nova.yaml +++ b/api/bases/nova.openstack.org_nova.yaml @@ -326,6 +326,36 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + api: + description: API tls type which encapsulates for API services + properties: + internal: + description: Internal GenericService - holds the secret + for the internal endpoint + properties: + secretName: + description: SecretName - holding the cert, key for + the service + type: string + type: object + public: + description: Public GenericService - holds the secret + for the public endpoint + properties: + secretName: + description: SecretName - holding the cert, key for + the service + type: string + type: object + type: object + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in + a pre-created bundle file + type: string + type: object type: object cellTemplates: additionalProperties: @@ -745,6 +775,18 @@ spec: value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs + in a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for + the service + type: string + type: object type: object noVNCProxyServiceTemplate: description: NoVNCProxyServiceTemplate - defines the novncproxy @@ -1040,6 +1082,18 @@ spec: value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs + in a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for + the service + type: string + type: object type: object nodeSelector: additionalProperties: @@ -1497,6 +1551,17 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in + a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object type: object nodeSelector: additionalProperties: diff --git a/api/bases/nova.openstack.org_novaapis.yaml b/api/bases/nova.openstack.org_novaapis.yaml index e520a11b8..ef45bc78b 100644 --- a/api/bases/nova.openstack.org_novaapis.yaml +++ b/api/bases/nova.openstack.org_novaapis.yaml @@ -383,6 +383,36 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + api: + description: API tls type which encapsulates for API services + properties: + internal: + description: Internal GenericService - holds the secret for + the internal endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + public: + description: Public GenericService - holds the secret for + the public endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + type: object + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - apiDatabaseHostname - cell0DatabaseHostname diff --git a/api/bases/nova.openstack.org_novacells.yaml b/api/bases/nova.openstack.org_novacells.yaml index 0170ac22c..6a7bc9b6d 100644 --- a/api/bases/nova.openstack.org_novacells.yaml +++ b/api/bases/nova.openstack.org_novacells.yaml @@ -455,6 +455,17 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in + a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object type: object noVNCProxyServiceTemplate: description: NoVNCProxyServiceTemplate - defines the novvncproxy service @@ -729,6 +740,17 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in + a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object type: object nodeSelector: additionalProperties: @@ -861,6 +883,14 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - cellDatabaseHostname - cellName diff --git a/api/bases/nova.openstack.org_novacomputes.yaml b/api/bases/nova.openstack.org_novacomputes.yaml index 271191c96..e40199c89 100644 --- a/api/bases/nova.openstack.org_novacomputes.yaml +++ b/api/bases/nova.openstack.org_novacomputes.yaml @@ -189,6 +189,14 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - cellName - computeDriver diff --git a/api/bases/nova.openstack.org_novaconductors.yaml b/api/bases/nova.openstack.org_novaconductors.yaml index 1371c5a44..a588a3da4 100644 --- a/api/bases/nova.openstack.org_novaconductors.yaml +++ b/api/bases/nova.openstack.org_novaconductors.yaml @@ -201,6 +201,14 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - cellName - keystoneAuthURL diff --git a/api/bases/nova.openstack.org_novametadata.yaml b/api/bases/nova.openstack.org_novametadata.yaml index d18df7ebb..77cc99e42 100644 --- a/api/bases/nova.openstack.org_novametadata.yaml +++ b/api/bases/nova.openstack.org_novametadata.yaml @@ -378,6 +378,17 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object required: - keystoneAuthURL - secret diff --git a/api/bases/nova.openstack.org_novanovncproxies.yaml b/api/bases/nova.openstack.org_novanovncproxies.yaml index fb77c4c9b..3073a61ae 100644 --- a/api/bases/nova.openstack.org_novanovncproxies.yaml +++ b/api/bases/nova.openstack.org_novanovncproxies.yaml @@ -356,6 +356,17 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object required: - cellDatabaseHostname - cellName diff --git a/api/bases/nova.openstack.org_novaschedulers.yaml b/api/bases/nova.openstack.org_novaschedulers.yaml index 4fe449479..d169f8993 100644 --- a/api/bases/nova.openstack.org_novaschedulers.yaml +++ b/api/bases/nova.openstack.org_novaschedulers.yaml @@ -204,6 +204,14 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - apiDatabaseHostname - cell0DatabaseHostname diff --git a/api/go.mod b/api/go.mod index d2eeece76..0a4421e1d 100644 --- a/api/go.mod +++ b/api/go.mod @@ -68,3 +68,5 @@ require ( // mschuppert: map to latest commit from release-4.13 tag // must consistent within modules and service operators replace github.com/openshift/api => github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7 //allow-merging + +replace github.com/openstack-k8s-operators/lib-common/modules/common => github.com/deydra71/lib-common/modules/common v0.0.0-20231221132238-bb04f7477236 diff --git a/api/go.sum b/api/go.sum index 84d59c694..3bca94b4c 100644 --- a/api/go.sum +++ b/api/go.sum @@ -62,6 +62,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deydra71/lib-common/modules/common v0.0.0-20231221132238-bb04f7477236 h1:Fo59uOmrnWdVX9WanZofoB2YnmlxDP2wbm7jHGgBIOA= +github.com/deydra71/lib-common/modules/common v0.0.0-20231221132238-bb04f7477236/go.mod h1:YgWd1xXF9VgsfPIwkCv3Q0j2akpnojs9zgso87tvCXY= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -220,8 +222,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231230095328-700482794743 h1:nElSEojlu7JxfpmF5c4zb2F3bjbQigpeiheV6Eus6RI= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231230095328-700482794743/go.mod h1:IDd4i2ZXWELCF+Y8Zu9bQBobE6yy3HOEjUeLnSuSWaM= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/api/v1beta1/novaapi_types.go b/api/v1beta1/novaapi_types.go index 7a3b639a4..cc0eca20d 100644 --- a/api/v1beta1/novaapi_types.go +++ b/api/v1beta1/novaapi_types.go @@ -19,6 +19,7 @@ package v1beta1 import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" service "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -72,6 +73,11 @@ type NovaAPITemplate struct { // +kubebuilder:validation:Optional // Override, provides the ability to override the generated manifest of several child resources. Override APIOverrideSpec `json:"override,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.API `json:"tls,omitempty"` } // APIOverrideSpec to override the generated manifest of several child resources. @@ -153,6 +159,11 @@ type NovaAPISpec struct { // reconfigured to trigger refresh of the in memory cell caches of the // service. RegisteredCells map[string]string `json:"registeredCells"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.API `json:"tls,omitempty"` } // NovaAPIStatus defines the observed state of NovaAPI diff --git a/api/v1beta1/novacell_types.go b/api/v1beta1/novacell_types.go index 1a54a5b7f..95ba25185 100644 --- a/api/v1beta1/novacell_types.go +++ b/api/v1beta1/novacell_types.go @@ -18,6 +18,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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -173,6 +174,11 @@ type NovaCellSpec struct { // +kubebuilder:validation:Required // ServiceAccount - service account name used internally to provide Nova services the default SA name ServiceAccount string `json:"serviceAccount"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.Ca `json:"tls,omitempty"` } // NovaCellStatus defines the observed state of NovaCell diff --git a/api/v1beta1/novacompute_types.go b/api/v1beta1/novacompute_types.go index 45efbc15d..501351fe0 100644 --- a/api/v1beta1/novacompute_types.go +++ b/api/v1beta1/novacompute_types.go @@ -18,6 +18,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" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -118,6 +119,11 @@ type NovaComputeSpec struct { // +kubebuilder:validation:Enum=ironic.IronicDriver;fake.FakeDriver // ComputeDriver defines which driver to use for controlling virtualization ComputeDriver string `json:"computeDriver"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.Ca `json:"tls,omitempty"` } // NovaComputeStatus defines the observed state of NovaCompute @@ -213,6 +219,7 @@ func NewNovaComputeSpec( ServiceUser: novaCell.ServiceUser, ServiceAccount: novaCell.ServiceAccount, ComputeDriver: computeTemplate.ComputeDriver, + TLS: novaCell.TLS, } return novacomputeSpec } diff --git a/api/v1beta1/novaconductor_types.go b/api/v1beta1/novaconductor_types.go index b6b048277..937f6db03 100644 --- a/api/v1beta1/novaconductor_types.go +++ b/api/v1beta1/novaconductor_types.go @@ -18,6 +18,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" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -127,6 +128,11 @@ type NovaConductorSpec struct { // +kubebuilder:validation:Required // ServiceAccount - service account name used internally to provide Nova services the default SA name ServiceAccount string `json:"serviceAccount"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.Ca `json:"tls,omitempty"` } // NovaConductorStatus defines the observed state of NovaConductor @@ -196,6 +202,7 @@ func NewNovaConductorSpec( KeystoneAuthURL: novaCell.KeystoneAuthURL, ServiceUser: novaCell.ServiceUser, ServiceAccount: novaCell.ServiceAccount, + TLS: novaCell.TLS, } return conductorSpec } diff --git a/api/v1beta1/novametadata_types.go b/api/v1beta1/novametadata_types.go index ac134b488..773379c22 100644 --- a/api/v1beta1/novametadata_types.go +++ b/api/v1beta1/novametadata_types.go @@ -19,6 +19,7 @@ package v1beta1 import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" service "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -83,6 +84,11 @@ type NovaMetadataTemplate struct { // +kubebuilder:validation:Optional // Override, provides the ability to override the generated manifest of several child resources. Override MetadataOverrideSpec `json:"override,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.SimpleService `json:"tls,omitempty"` } // MetadataOverrideSpec to override the generated manifest of several child resources. @@ -171,6 +177,11 @@ type NovaMetadataSpec struct { // service. // This is empty for the case when nova-metadata runs within the cell. RegisteredCells map[string]string `json:"registeredCells,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.SimpleService `json:"tls,omitempty"` } // NovaMetadataStatus defines the observed state of NovaMetadata @@ -249,6 +260,7 @@ func NewNovaMetadataSpec( ServiceUser: novaCell.ServiceUser, ServiceAccount: novaCell.ServiceAccount, Override: novaCell.MetadataServiceTemplate.Override, + TLS: novaCell.MetadataServiceTemplate.TLS, } return metadataSpec } diff --git a/api/v1beta1/novanovncproxy_types.go b/api/v1beta1/novanovncproxy_types.go index 1a9e872e4..0e3e5bb4c 100644 --- a/api/v1beta1/novanovncproxy_types.go +++ b/api/v1beta1/novanovncproxy_types.go @@ -19,6 +19,7 @@ package v1beta1 import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" service "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -79,6 +80,11 @@ type NovaNoVNCProxyTemplate struct { // +kubebuilder:validation:Optional // Override, provides the ability to override the generated manifest of several child resources. Override VNCProxyOverrideSpec `json:"override,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.SimpleService `json:"tls,omitempty"` } // VNCProxyOverrideSpec to override the generated manifest of several child resources. @@ -140,6 +146,11 @@ type NovaNoVNCProxySpec struct { // +kubebuilder:validation:Required // ServiceAccount - service account name used internally to provide Nova services the default SA name ServiceAccount string `json:"serviceAccount"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.SimpleService `json:"tls,omitempty"` } // NovaNoVNCProxyStatus defines the observed state of NovaNoVNCProxy @@ -216,6 +227,7 @@ func NewNovaNoVNCProxySpec( ServiceUser: novaCell.ServiceUser, ServiceAccount: novaCell.ServiceAccount, Override: novaCell.NoVNCProxyServiceTemplate.Override, + TLS: novaCell.NoVNCProxyServiceTemplate.TLS, } return noVNCProxSpec } diff --git a/api/v1beta1/novascheduler_types.go b/api/v1beta1/novascheduler_types.go index 27ae38f89..054080f11 100644 --- a/api/v1beta1/novascheduler_types.go +++ b/api/v1beta1/novascheduler_types.go @@ -18,6 +18,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" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -127,6 +128,11 @@ type NovaSchedulerSpec struct { // reconfigured to trigger refresh of the in memory cell caches of the // service. RegisteredCells map[string]string `json:"registeredCells"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.Ca `json:"tls,omitempty"` } // NovaSchedulerStatus defines the observed state of NovaScheduler diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 2f7916260..2ce3e86d3 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -213,6 +213,7 @@ func (in *NovaAPISpec) DeepCopyInto(out *NovaAPISpec) { (*out)[key] = val } } + in.TLS.DeepCopyInto(&out.TLS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaAPISpec. @@ -298,6 +299,7 @@ func (in *NovaAPITemplate) DeepCopyInto(out *NovaAPITemplate) { copy(*out, *in) } in.Override.DeepCopyInto(&out.Override) + in.TLS.DeepCopyInto(&out.TLS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaAPITemplate. @@ -405,6 +407,7 @@ func (in *NovaCellSpec) DeepCopyInto(out *NovaCellSpec) { (*out)[key] = *val.DeepCopy() } } + out.TLS = in.TLS } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaCellSpec. @@ -580,6 +583,7 @@ func (in *NovaComputeSpec) DeepCopyInto(out *NovaComputeSpec) { *out = *in out.Debug = in.Debug in.NovaServiceBase.DeepCopyInto(&out.NovaServiceBase) + out.TLS = in.TLS } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaComputeSpec. @@ -755,6 +759,7 @@ func (in *NovaConductorSpec) DeepCopyInto(out *NovaConductorSpec) { *out = *in out.Debug = in.Debug in.NovaServiceBase.DeepCopyInto(&out.NovaServiceBase) + out.TLS = in.TLS } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaConductorSpec. @@ -985,6 +990,7 @@ func (in *NovaMetadataSpec) DeepCopyInto(out *NovaMetadataSpec) { (*out)[key] = val } } + in.TLS.DeepCopyInto(&out.TLS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaMetadataSpec. @@ -1075,6 +1081,7 @@ func (in *NovaMetadataTemplate) DeepCopyInto(out *NovaMetadataTemplate) { copy(*out, *in) } in.Override.DeepCopyInto(&out.Override) + in.TLS.DeepCopyInto(&out.TLS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaMetadataTemplate. @@ -1167,6 +1174,7 @@ func (in *NovaNoVNCProxySpec) DeepCopyInto(out *NovaNoVNCProxySpec) { out.Debug = in.Debug in.NovaServiceBase.DeepCopyInto(&out.NovaServiceBase) in.Override.DeepCopyInto(&out.Override) + in.TLS.DeepCopyInto(&out.TLS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaNoVNCProxySpec. @@ -1257,6 +1265,7 @@ func (in *NovaNoVNCProxyTemplate) DeepCopyInto(out *NovaNoVNCProxyTemplate) { copy(*out, *in) } in.Override.DeepCopyInto(&out.Override) + in.TLS.DeepCopyInto(&out.TLS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaNoVNCProxyTemplate. @@ -1355,6 +1364,7 @@ func (in *NovaSchedulerSpec) DeepCopyInto(out *NovaSchedulerSpec) { (*out)[key] = val } } + out.TLS = in.TLS } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NovaSchedulerSpec. diff --git a/config/crd/bases/nova.openstack.org_nova.yaml b/config/crd/bases/nova.openstack.org_nova.yaml index 6fefa0194..7fe1fbb3c 100644 --- a/config/crd/bases/nova.openstack.org_nova.yaml +++ b/config/crd/bases/nova.openstack.org_nova.yaml @@ -326,6 +326,36 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + api: + description: API tls type which encapsulates for API services + properties: + internal: + description: Internal GenericService - holds the secret + for the internal endpoint + properties: + secretName: + description: SecretName - holding the cert, key for + the service + type: string + type: object + public: + description: Public GenericService - holds the secret + for the public endpoint + properties: + secretName: + description: SecretName - holding the cert, key for + the service + type: string + type: object + type: object + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in + a pre-created bundle file + type: string + type: object type: object cellTemplates: additionalProperties: @@ -745,6 +775,18 @@ spec: value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs + in a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for + the service + type: string + type: object type: object noVNCProxyServiceTemplate: description: NoVNCProxyServiceTemplate - defines the novncproxy @@ -1040,6 +1082,18 @@ spec: value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs + in a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for + the service + type: string + type: object type: object nodeSelector: additionalProperties: @@ -1497,6 +1551,17 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in + a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object type: object nodeSelector: additionalProperties: diff --git a/config/crd/bases/nova.openstack.org_novaapis.yaml b/config/crd/bases/nova.openstack.org_novaapis.yaml index e520a11b8..ef45bc78b 100644 --- a/config/crd/bases/nova.openstack.org_novaapis.yaml +++ b/config/crd/bases/nova.openstack.org_novaapis.yaml @@ -383,6 +383,36 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + api: + description: API tls type which encapsulates for API services + properties: + internal: + description: Internal GenericService - holds the secret for + the internal endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + public: + description: Public GenericService - holds the secret for + the public endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + type: object + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - apiDatabaseHostname - cell0DatabaseHostname diff --git a/config/crd/bases/nova.openstack.org_novacells.yaml b/config/crd/bases/nova.openstack.org_novacells.yaml index 0170ac22c..6a7bc9b6d 100644 --- a/config/crd/bases/nova.openstack.org_novacells.yaml +++ b/config/crd/bases/nova.openstack.org_novacells.yaml @@ -455,6 +455,17 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in + a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object type: object noVNCProxyServiceTemplate: description: NoVNCProxyServiceTemplate - defines the novvncproxy service @@ -729,6 +740,17 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in + a pre-created bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object type: object nodeSelector: additionalProperties: @@ -861,6 +883,14 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - cellDatabaseHostname - cellName diff --git a/config/crd/bases/nova.openstack.org_novacomputes.yaml b/config/crd/bases/nova.openstack.org_novacomputes.yaml index 271191c96..e40199c89 100644 --- a/config/crd/bases/nova.openstack.org_novacomputes.yaml +++ b/config/crd/bases/nova.openstack.org_novacomputes.yaml @@ -189,6 +189,14 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - cellName - computeDriver diff --git a/config/crd/bases/nova.openstack.org_novaconductors.yaml b/config/crd/bases/nova.openstack.org_novaconductors.yaml index 1371c5a44..a588a3da4 100644 --- a/config/crd/bases/nova.openstack.org_novaconductors.yaml +++ b/config/crd/bases/nova.openstack.org_novaconductors.yaml @@ -201,6 +201,14 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - cellName - keystoneAuthURL diff --git a/config/crd/bases/nova.openstack.org_novametadata.yaml b/config/crd/bases/nova.openstack.org_novametadata.yaml index d18df7ebb..77cc99e42 100644 --- a/config/crd/bases/nova.openstack.org_novametadata.yaml +++ b/config/crd/bases/nova.openstack.org_novametadata.yaml @@ -378,6 +378,17 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object required: - keystoneAuthURL - secret diff --git a/config/crd/bases/nova.openstack.org_novanovncproxies.yaml b/config/crd/bases/nova.openstack.org_novanovncproxies.yaml index fb77c4c9b..3073a61ae 100644 --- a/config/crd/bases/nova.openstack.org_novanovncproxies.yaml +++ b/config/crd/bases/nova.openstack.org_novanovncproxies.yaml @@ -356,6 +356,17 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + secretName: + description: SecretName - holding the cert, key for the service + type: string + type: object required: - cellDatabaseHostname - cellName diff --git a/config/crd/bases/nova.openstack.org_novaschedulers.yaml b/config/crd/bases/nova.openstack.org_novaschedulers.yaml index 4fe449479..d169f8993 100644 --- a/config/crd/bases/nova.openstack.org_novaschedulers.yaml +++ b/config/crd/bases/nova.openstack.org_novaschedulers.yaml @@ -204,6 +204,14 @@ spec: description: ServiceUser - optional username used for this service to register in keystone type: string + tls: + description: TLS - Parameters related to the TLS + properties: + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object required: - apiDatabaseHostname - cell0DatabaseHostname diff --git a/controllers/nova_controller.go b/controllers/nova_controller.go index 2a2ddc1a2..c6d720893 100644 --- a/controllers/nova_controller.go +++ b/controllers/nova_controller.go @@ -845,6 +845,7 @@ func (r *NovaReconciler) ensureCell( ServiceUser: instance.Spec.ServiceUser, KeystoneAuthURL: keystoneAuthURL, ServiceAccount: instance.RbacResourceName(), + TLS: instance.Spec.APIServiceTemplate.TLS.Ca, } if cellTemplate.HasAPIAccess { cellSpec.APIDatabaseHostname = apiDB.GetDatabaseHostname() @@ -1005,6 +1006,7 @@ func (r *NovaReconciler) ensureAPI( ServiceUser: instance.Spec.ServiceUser, ServiceAccount: instance.RbacResourceName(), RegisteredCells: instance.Status.RegisteredCells, + TLS: instance.Spec.APIServiceTemplate.TLS, } api := &novav1.NovaAPI{ ObjectMeta: metav1.ObjectMeta{ @@ -1080,6 +1082,7 @@ func (r *NovaReconciler) ensureScheduler( ServiceUser: instance.Spec.ServiceUser, ServiceAccount: instance.RbacResourceName(), RegisteredCells: instance.Status.RegisteredCells, + TLS: instance.Spec.APIServiceTemplate.TLS.Ca, } scheduler := &novav1.NovaScheduler{ ObjectMeta: metav1.ObjectMeta{ @@ -1427,6 +1430,7 @@ func (r *NovaReconciler) ensureMetadata( KeystoneAuthURL: keystoneAuthURL, ServiceAccount: instance.RbacResourceName(), RegisteredCells: instance.Status.RegisteredCells, + TLS: instance.Spec.MetadataServiceTemplate.TLS, } metadata = &novav1.NovaMetadata{ ObjectMeta: metav1.ObjectMeta{ diff --git a/controllers/novaapi_controller.go b/controllers/novaapi_controller.go index 204655c1e..97616a888 100644 --- a/controllers/novaapi_controller.go +++ b/controllers/novaapi_controller.go @@ -22,11 +22,17 @@ import ( v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" "github.com/go-logr/logr" @@ -40,6 +46,7 @@ import ( nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" @@ -201,6 +208,54 @@ func (r *NovaAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re } hashes["cells"] = env.SetValue(cellHash) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + h.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + hashes[tls.CABundleKey] = env.SetValue(hash) + } + } + + // Validate API service certs secrets + certsHash, ctrlResult, err := instance.Spec.TLS.API.ValidateCertSecrets(ctx, h, instance.Namespace) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + hashes[tls.TLSHashName] = env.SetValue(certsHash) + + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + inputHash, err := util.HashOfInputHashes(hashes) if err != nil { return ctrl.Result{}, err @@ -302,6 +357,11 @@ func (r *NovaAPIReconciler) initConditions( condition.InitReason, condition.NetworkAttachmentsReadyInitMessage, ), + condition.UnknownCondition( + condition.TLSInputReadyCondition, + condition.InitReason, + condition.InputReadyInitMessage, + ), ) instance.Status.Conditions.Init(&cl) @@ -357,7 +417,23 @@ func (r *NovaAPIReconciler) generateConfigs( "default_user_domain": "Default", // fixme "transport_url": string(secret.Data[TransportURLSelector]), "log_file": "/var/log/nova/nova-api.log", + "tls": false, + } + // create httpd vhost template parameters + httpdVhostConfig := map[string]interface{}{} + for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { + endptConfig := map[string]interface{}{} + endptConfig["ServerName"] = fmt.Sprintf("nova-%s.%s.svc", endpt.String(), instance.Namespace) + endptConfig["tls"] = false // default TLS to false, and set it bellow to true if enabled + if instance.Spec.TLS.API.Enabled(endpt) { + templateParameters["tls"] = true + endptConfig["tls"] = true + endptConfig["SSLCertificateFile"] = fmt.Sprintf("/etc/pki/tls/certs/%s.crt", endpt.String()) + endptConfig["SSLCertificateKeyFile"] = fmt.Sprintf("/etc/pki/tls/private/%s.key", endpt.String()) + } + httpdVhostConfig[endpt.String()] = endptConfig } + templateParameters["VHosts"] = httpdVhostConfig extraData := map[string]string{} if instance.Spec.CustomServiceConfig != "" { extraData["02-nova-override.conf"] = instance.Spec.CustomServiceConfig @@ -386,7 +462,18 @@ func (r *NovaAPIReconciler) ensureDeployment( ) (ctrl.Result, error) { Log := r.GetLogger(ctx) - ss := statefulset.NewStatefulSet(novaapi.StatefulSet(instance, inputHash, getAPIServiceLabels(), annotations), r.RequeueTimeout) + ssSpec, err := novaapi.StatefulSet(instance, inputHash, getAPIServiceLabels(), annotations) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DeploymentReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + + ss := statefulset.NewStatefulSet(ssSpec, r.RequeueTimeout) ctrlResult, err := ss.CreateOrPatch(ctx, h) if err != nil && !k8s_errors.IsNotFound(err) { Log.Error(err, "Deployment failed") @@ -552,7 +639,12 @@ func (r *NovaAPIReconciler) ensureServiceExposed( } // create service - end - // TODO: TLS, pass in https as protocol + // if TLS is enabled + if instance.Spec.TLS.API.Enabled(endpointType) { + // set endpoint protocol to https + data.Protocol = ptr.To(service.ProtocolHTTPS) + } + apiEndpoints[string(endpointType)], err = svc.GetAPIEndpoint( svcOverride.EndpointURL, data.Protocol, data.Path) if err != nil { @@ -663,8 +755,92 @@ func getAPIServiceLabels() map[string]string { } } +func (r *NovaAPIReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("NovaAPI") + + for _, field := range apiWatchFields { + crList := &novav1.NovaAPIList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.Client.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + +// fields to index to reconcile when change +const ( + caBundleSecretNameField = ".spec.tls.caBundleSecretName" + tlsAPIInternalField = ".spec.tls.api.internal.secretName" + tlsAPIPublicField = ".spec.tls.api.public.secretName" +) + +var ( + apiWatchFields = []string{ + caBundleSecretNameField, + tlsAPIInternalField, + tlsAPIPublicField, + } +) + // SetupWithManager sets up the controller with the Manager. func (r *NovaAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaAPI{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaAPI) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + + // index tlsAPIInternalField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaAPI{}, tlsAPIInternalField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaAPI) + if cr.Spec.TLS.API.Internal.SecretName == nil { + return nil + } + return []string{*cr.Spec.TLS.API.Internal.SecretName} + }); err != nil { + return err + } + + // index tlsAPIPublicField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaAPI{}, tlsAPIPublicField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaAPI) + if cr.Spec.TLS.API.Public.SecretName == nil { + return nil + } + return []string{*cr.Spec.TLS.API.Public.SecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&novav1.NovaAPI{}). Owns(&v1.StatefulSet{}). @@ -673,5 +849,10 @@ func (r *NovaAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.Secret{}). Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.GetSecretMapperFor(&novav1.NovaAPIList{}, context.TODO()))). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } diff --git a/controllers/novacompute_controller.go b/controllers/novacompute_controller.go index fe171f9c1..8bc980612 100644 --- a/controllers/novacompute_controller.go +++ b/controllers/novacompute_controller.go @@ -22,10 +22,15 @@ import ( v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" "github.com/go-logr/logr" @@ -36,6 +41,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/labels" nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" @@ -161,6 +167,39 @@ func (r *NovaComputeReconciler) Reconcile(ctx context.Context, req ctrl.Request) // all our input checks out so report InputReady instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + h.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + hashes[tls.CABundleKey] = env.SetValue(hash) + } + } + + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + err = r.ensureConfigs(ctx, h, instance, &hashes, secret) if err != nil { return ctrl.Result{}, err @@ -236,6 +275,11 @@ func (r *NovaComputeReconciler) initConditions( condition.InitReason, condition.NetworkAttachmentsReadyInitMessage, ), + condition.UnknownCondition( + condition.TLSInputReadyCondition, + condition.InitReason, + condition.InputReadyInitMessage, + ), ) instance.Status.Conditions.Init(&cl) @@ -393,13 +437,70 @@ func getComputeServiceLabels(cell string) map[string]string { } } +func (r *NovaComputeReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("NovaCompute") + + for _, field := range cmpWatchFields { + crList := &novav1.NovaComputeList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.Client.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + +// fields to index to reconcile when change +var ( + cmpWatchFields = []string{ + caBundleSecretNameField, + } +) + // SetupWithManager sets up the controller with the Manager. func (r *NovaComputeReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaCompute{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaCompute) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&novav1.NovaCompute{}). Owns(&v1.StatefulSet{}). Owns(&corev1.Secret{}). Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.GetSecretMapperFor(&novav1.NovaComputeList{}, context.TODO()))). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } diff --git a/controllers/novaconductor_controller.go b/controllers/novaconductor_controller.go index 498ceddce..b6c08212d 100644 --- a/controllers/novaconductor_controller.go +++ b/controllers/novaconductor_controller.go @@ -24,10 +24,15 @@ import ( batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" "github.com/go-logr/logr" @@ -39,6 +44,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/labels" nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" "github.com/openstack-k8s-operators/nova-operator/pkg/novaconductor" @@ -163,6 +169,39 @@ func (r *NovaConductorReconciler) Reconcile(ctx context.Context, req ctrl.Reques // all our input checks out so report InputReady instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + h.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + hashes[tls.CABundleKey] = env.SetValue(hash) + } + } + + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + err = r.ensureConfigs(ctx, h, instance, &hashes, secret) if err != nil { return ctrl.Result{}, err @@ -257,6 +296,11 @@ func (r *NovaConductorReconciler) initConditions( condition.InitReason, condition.NetworkAttachmentsReadyInitMessage, ), + condition.UnknownCondition( + condition.TLSInputReadyCondition, + condition.InitReason, + condition.InputReadyInitMessage, + ), ) instance.Status.Conditions.Init(&cl) @@ -469,8 +513,60 @@ func (r *NovaConductorReconciler) cleanServiceFromNovaDb( return cleanNovaServiceFromNovaDb(ctx, computeClient, "nova-conductor") } +func (r *NovaConductorReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("NovaConductor") + + for _, field := range cdWatchFields { + crList := &novav1.NovaConductorList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.Client.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + +// fields to index to reconcile when change +var ( + cdWatchFields = []string{ + caBundleSecretNameField, + } +) + // SetupWithManager sets up the controller with the Manager. func (r *NovaConductorReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaConductor{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaConductor) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&novav1.NovaConductor{}). Owns(&v1.StatefulSet{}). @@ -478,5 +574,10 @@ func (r *NovaConductorReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.Secret{}). Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.GetSecretMapperFor(&novav1.NovaConductorList{}, context.TODO()))). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } diff --git a/controllers/novametadata_controller.go b/controllers/novametadata_controller.go index e3deafd39..58b8c3943 100644 --- a/controllers/novametadata_controller.go +++ b/controllers/novametadata_controller.go @@ -23,11 +23,16 @@ import ( v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" "github.com/go-logr/logr" @@ -40,6 +45,7 @@ import ( common_secret "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/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" "github.com/openstack-k8s-operators/nova-operator/pkg/nova" @@ -167,6 +173,55 @@ func (r *NovaMetadataReconciler) Reconcile(ctx context.Context, req ctrl.Request // all our input checks out so report InputReady instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + h.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + hashes[tls.CABundleKey] = env.SetValue(hash) + } + } + + // Validate metadata service cert secret + if instance.Spec.TLS.Enabled() { + hash, ctrlResult, err := instance.Spec.TLS.ValidateCertSecret(ctx, h, instance.Namespace) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + hashes[tls.TLSHashName] = env.SetValue(hash) + } + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + err = r.ensureConfigs(ctx, h, instance, &hashes, secret) if err != nil { return ctrl.Result{}, err @@ -283,6 +338,11 @@ func (r *NovaMetadataReconciler) initConditions( condition.InitReason, novav1.NovaComputeServiceConfigInitMessage, ), + condition.UnknownCondition( + condition.TLSInputReadyCondition, + condition.InitReason, + condition.InputReadyInitMessage, + ), ) instance.Status.Conditions.Init(&cl) @@ -331,6 +391,8 @@ func (r *NovaMetadataReconciler) generateConfigs( "metadata_secret": string(secret.Data[MetadataSecretSelector]), "log_file": "/var/log/nova/nova-metadata.log", "transport_url": string(secret.Data[TransportURLSelector]), + "tls": false, + "ServerName": fmt.Sprintf("%s.%s.svc", novametadata.ServiceName, instance.Namespace), } if instance.Spec.CellName == "" { @@ -344,6 +406,13 @@ func (r *NovaMetadataReconciler) generateConfigs( templateParameters["local_metadata_per_cell"] = true } + // create httpd tls template parameters + if instance.Spec.TLS.GenericService.Enabled() { + templateParameters["tls"] = true + templateParameters["SSLCertificateFile"] = fmt.Sprintf("/etc/pki/tls/certs/%s.crt", novametadata.ServiceName) + templateParameters["SSLCertificateKeyFile"] = fmt.Sprintf("/etc/pki/tls/private/%s.key", novametadata.ServiceName) + } + extraData := map[string]string{} if instance.Spec.CustomServiceConfig != "" { extraData["02-nova-override.conf"] = instance.Spec.CustomServiceConfig @@ -373,7 +442,18 @@ func (r *NovaMetadataReconciler) ensureDeployment( Log := r.GetLogger(ctx) serviceLabels := getMetadataServiceLabels(instance.Spec.CellName) - ss := statefulset.NewStatefulSet(novametadata.StatefulSet(instance, inputHash, serviceLabels, annotations), r.RequeueTimeout) + ssSpec, err := novametadata.StatefulSet(instance, inputHash, serviceLabels, annotations) + if err != nil { + Log.Error(err, "Deployment failed") + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DeploymentReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + ss := statefulset.NewStatefulSet(ssSpec, r.RequeueTimeout) ctrlResult, err := ss.CreateOrPatch(ctx, h) if err != nil && !k8s_errors.IsNotFound(err) { Log.Error(err, "Deployment failed") @@ -517,9 +597,14 @@ func (r *NovaMetadataReconciler) ensureServiceExposed( } // create service - end - // TODO: TLS, pass in https as protocol + // if TLS is enabled + proto := ptr.To(service.ProtocolHTTP) + if instance.Spec.TLS.Enabled() { + // set endpoint protocol to https + proto = ptr.To(service.ProtocolHTTPS) + } apiEndpoint, err := svc.GetAPIEndpoint( - nil, ptr.To(service.ProtocolHTTP), "") + nil, proto, "") if err != nil { return "", ctrl.Result{}, err } @@ -632,8 +717,77 @@ func (r *NovaMetadataReconciler) generateNeutronConfigs( return nil } +func (r *NovaMetadataReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("NovaMetadata") + + for _, field := range metaWatchFields { + crList := &novav1.NovaMetadataList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.Client.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + +// fields to index to reconcile when change +const ( + tlsMetadataField = ".spec.tls.secretName" +) + +var ( + metaWatchFields = []string{ + caBundleSecretNameField, + tlsMetadataField, + } +) + // SetupWithManager sets up the controller with the Manager. func (r *NovaMetadataReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaMetadata{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaMetadata) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + + // index tlsMetadataField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaMetadata{}, tlsMetadataField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaMetadata) + if cr.Spec.TLS.SecretName == nil { + return nil + } + return []string{*cr.Spec.TLS.SecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&novav1.NovaMetadata{}). Owns(&v1.StatefulSet{}). @@ -641,5 +795,10 @@ func (r *NovaMetadataReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.Secret{}). Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.GetSecretMapperFor(&novav1.NovaMetadataList{}, context.TODO()))). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } diff --git a/controllers/novanovncproxy_controller.go b/controllers/novanovncproxy_controller.go index 90e947924..535315770 100644 --- a/controllers/novanovncproxy_controller.go +++ b/controllers/novanovncproxy_controller.go @@ -22,10 +22,15 @@ import ( v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" "github.com/go-logr/logr" @@ -37,6 +42,7 @@ import ( nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" "github.com/openstack-k8s-operators/nova-operator/pkg/nova" @@ -161,6 +167,56 @@ func (r *NovaNoVNCProxyReconciler) Reconcile(ctx context.Context, req ctrl.Reque // all our input checks out so report InputReady instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + h.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + hashes[tls.CABundleKey] = env.SetValue(hash) + } + } + + // Validate metadata service cert secret + if instance.Spec.TLS.Enabled() { + hash, ctrlResult, err := instance.Spec.TLS.ValidateCertSecret(ctx, h, instance.Namespace) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + hashes[tls.TLSHashName] = env.SetValue(hash) + } + + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + result, err = r.ensureServiceExposed(ctx, h, instance) if (err != nil || result != ctrl.Result{}) { // We can ignore RequeueAfter as we are watching the Service resource @@ -269,6 +325,11 @@ func (r *NovaNoVNCProxyReconciler) initConditions( condition.InitReason, condition.NetworkAttachmentsReadyInitMessage, ), + condition.UnknownCondition( + condition.TLSInputReadyCondition, + condition.InitReason, + condition.InputReadyInitMessage, + ), ) instance.Status.Conditions.Init(&cl) @@ -292,11 +353,14 @@ func (r *NovaNoVNCProxyReconciler) generateConfigs( "cell_db_address": instance.Spec.CellDatabaseHostname, "cell_db_port": 3306, "transport_url": string(secret.Data[TransportURLSelector]), - "openstack_cacert": "", // fixme "openstack_region_name": "regionOne", // fixme "default_project_domain": "Default", // fixme "default_user_domain": "Default", // fixme } + if instance.Spec.TLS.GenericService.Enabled() { + templateParameters["SSLCertificateFile"] = fmt.Sprintf("/etc/pki/tls/certs/%s.crt", novncproxy.ServiceName) + templateParameters["SSLCertificateKeyFile"] = fmt.Sprintf("/etc/pki/tls/private/%s.key", novncproxy.ServiceName) + } extraData := map[string]string{} if instance.Spec.CustomServiceConfig != "" { extraData["02-nova-override.conf"] = instance.Spec.CustomServiceConfig @@ -326,8 +390,18 @@ func (r *NovaNoVNCProxyReconciler) ensureDeployment( Log := r.GetLogger(ctx) serviceLabels := getNoVNCProxyServiceLabels(instance.Spec.CellName) - ss := statefulset.NewStatefulSet( - novncproxy.StatefulSet(instance, inputHash, serviceLabels, annotations), r.RequeueTimeout) + ssSpec, err := novncproxy.StatefulSet(instance, inputHash, serviceLabels, annotations) + if err != nil { + Log.Info("Deployment failed") + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DeploymentReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + ss := statefulset.NewStatefulSet(ssSpec, r.RequeueTimeout) ctrlResult, err := ss.CreateOrPatch(ctx, h) if err != nil && !k8s_errors.IsNotFound(err) { Log.Info("Deployment failed") @@ -506,8 +580,77 @@ func getNoVNCProxyServiceLabels(cell string) map[string]string { } } +func (r *NovaNoVNCProxyReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("NovaNoVNCProxy") + + for _, field := range noVNCProxyWatchFields { + crList := &novav1.NovaNoVNCProxyList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.Client.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + +// fields to index to reconcile when change +const ( + tlsNoVNCProxyField = ".spec.tls.secretName" +) + +var ( + noVNCProxyWatchFields = []string{ + caBundleSecretNameField, + tlsNoVNCProxyField, + } +) + // SetupWithManager sets up the controller with the Manager. func (r *NovaNoVNCProxyReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaNoVNCProxy{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaNoVNCProxy) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + + // index tlsNoVNCProxyField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaNoVNCProxy{}, tlsNoVNCProxyField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaNoVNCProxy) + if cr.Spec.TLS.SecretName == nil { + return nil + } + return []string{*cr.Spec.TLS.SecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&novav1.NovaNoVNCProxy{}). Owns(&v1.StatefulSet{}). @@ -515,5 +658,10 @@ func (r *NovaNoVNCProxyReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.Secret{}). Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.GetSecretMapperFor(&novav1.NovaNoVNCProxyList{}, context.TODO()))). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } diff --git a/controllers/novascheduler_controller.go b/controllers/novascheduler_controller.go index 76a5becd4..6f94a5589 100644 --- a/controllers/novascheduler_controller.go +++ b/controllers/novascheduler_controller.go @@ -23,10 +23,15 @@ import ( v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" "github.com/go-logr/logr" @@ -37,6 +42,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/labels" nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" util "github.com/openstack-k8s-operators/lib-common/modules/common/util" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" @@ -161,6 +167,39 @@ func (r *NovaSchedulerReconciler) Reconcile(ctx context.Context, req ctrl.Reques // all our input checks out so report InputReady instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + // + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, ctrlResult, err := tls.ValidateCACertSecret( + ctx, + h.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + if hash != "" { + hashes[tls.CABundleKey] = env.SetValue(hash) + } + } + + // all cert input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + err = r.ensureConfigs(ctx, h, instance, &hashes, secret) if err != nil { return ctrl.Result{}, err @@ -205,14 +244,71 @@ func (r *NovaSchedulerReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, nil } +func (r *NovaSchedulerReconciler) findObjectsForSrc(src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + l := log.FromContext(context.Background()).WithName("Controllers").WithName("NovaScheduler") + + for _, field := range schedulerWatchFields { + crList := &novav1.NovaSchedulerList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.Client.List(context.TODO(), crList, listOps) + if err != nil { + return []reconcile.Request{} + } + + for _, item := range crList.Items { + l.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + +// fields to index to reconcile when change +var ( + schedulerWatchFields = []string{ + caBundleSecretNameField, + } +) + // SetupWithManager sets up the controller with the Manager. func (r *NovaSchedulerReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &novav1.NovaScheduler{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*novav1.NovaScheduler) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&novav1.NovaScheduler{}). Owns(&v1.StatefulSet{}). Owns(&corev1.Secret{}). Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.GetSecretMapperFor(&novav1.NovaSchedulerList{}, context.TODO()))). + Watches( + &source.Kind{Type: &corev1.Secret{}}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). Complete(r) } @@ -265,6 +361,11 @@ func (r *NovaSchedulerReconciler) initConditions( condition.InitReason, condition.NetworkAttachmentsReadyInitMessage, ), + condition.UnknownCondition( + condition.TLSInputReadyCondition, + condition.InitReason, + condition.InputReadyInitMessage, + ), ) instance.Status.Conditions.Init(&cl) diff --git a/go.mod b/go.mod index c4986c8b1..13e3de518 100644 --- a/go.mod +++ b/go.mod @@ -88,3 +88,5 @@ replace github.com/openstack-k8s-operators/nova-operator/api => ./api // mschuppert: map to latest commit from release-4.13 tag // must consistent within modules and service operators replace github.com/openshift/api => github.com/openshift/api v0.0.0-20230414143018-3367bc7e6ac7 //allow-merging + +replace github.com/openstack-k8s-operators/lib-common/modules/common => github.com/deydra71/lib-common/modules/common v0.0.0-20231221132238-bb04f7477236 diff --git a/go.sum b/go.sum index 9187a60c7..cdc36c64f 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deydra71/lib-common/modules/common v0.0.0-20231221132238-bb04f7477236 h1:Fo59uOmrnWdVX9WanZofoB2YnmlxDP2wbm7jHGgBIOA= +github.com/deydra71/lib-common/modules/common v0.0.0-20231221132238-bb04f7477236/go.mod h1:YgWd1xXF9VgsfPIwkCv3Q0j2akpnojs9zgso87tvCXY= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -238,8 +240,6 @@ github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231218083014-f github.com/openstack-k8s-operators/infra-operator/apis v0.3.1-0.20231218083014-f777af139aa2/go.mod h1:LRHMkCVHaoyxF3YZeP1T+32qSt+2sNo8ZU1ILYMQBWA= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231208104910-f8433c1c9399 h1:Te7JSPGGUhkzjig/1CjlPmQgMpHT0+yHWoTxbVJGJ74= github.com/openstack-k8s-operators/keystone-operator/api v0.3.1-0.20231208104910-f8433c1c9399/go.mod h1:kDtQ2LCkf28F7xgK8GBFAMPDhXnL6iRb8NztHhrYaO0= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231230095328-700482794743 h1:nElSEojlu7JxfpmF5c4zb2F3bjbQigpeiheV6Eus6RI= -github.com/openstack-k8s-operators/lib-common/modules/common v0.3.1-0.20231230095328-700482794743/go.mod h1:IDd4i2ZXWELCF+Y8Zu9bQBobE6yy3HOEjUeLnSuSWaM= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231230095328-700482794743 h1:Csah4t609IfGYZ5Ekfprjnmu3PrzaM/z4NXWx/JLKV4= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.3.1-0.20231230095328-700482794743/go.mod h1:4nmd2iUcjIAkF5Lw1ow+nsTeT3ifXbooGsjPSKG1+IA= github.com/openstack-k8s-operators/lib-common/modules/test v0.3.1-0.20231230095328-700482794743 h1:XT/nipCZzwQJcdegypnxf7UiCeOtOtmT0WgwLpD6qOA= diff --git a/pkg/nova/cellmapping.go b/pkg/nova/cellmapping.go index 0b63ced51..55976eb12 100644 --- a/pkg/nova/cellmapping.go +++ b/pkg/nova/cellmapping.go @@ -40,6 +40,22 @@ func CellMappingJob( jobName := instance.Name + "-" + cell.Spec.CellName + "-cell-mapping" + volumes := []corev1.Volume{ + GetConfigVolume(configName), + GetScriptVolume(scriptName), + } + volumeMounts := []corev1.VolumeMount{ + GetConfigVolumeMount(), + GetScriptVolumeMount(), + GetKollaConfigVolumeMount("cell-mapping"), + } + + // add CA cert if defined + if instance.Spec.APIServiceTemplate.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.APIServiceTemplate.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.APIServiceTemplate.TLS.CreateVolumeMounts(nil)...) + } + job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: jobName, @@ -51,9 +67,11 @@ func CellMappingJob( Spec: corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyOnFailure, ServiceAccountName: instance.RbacResourceName(), - Volumes: []corev1.Volume{ - GetConfigVolume(configName), - GetScriptVolume(scriptName), + Volumes: volumes, + SecurityContext: &corev1.PodSecurityContext{ + // since we run as NovaUserID, e.g. certs need to be + // readable by the user, instead of root + FSGroup: ptr.To(NovaUserID), }, Containers: []corev1.Container{ { @@ -66,12 +84,8 @@ func CellMappingJob( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - GetConfigVolumeMount(), - GetScriptVolumeMount(), - GetKollaConfigVolumeMount("cell-mapping"), - }, + Env: env, + VolumeMounts: volumeMounts, }, }, }, diff --git a/pkg/nova/host_discover.go b/pkg/nova/host_discover.go index c7104cc0e..c0292e5a4 100644 --- a/pkg/nova/host_discover.go +++ b/pkg/nova/host_discover.go @@ -52,6 +52,22 @@ func HostDiscoveryJob( jobName := instance.Name + "-host-discover" + volumes := []corev1.Volume{ + GetConfigVolume(configName), + GetScriptVolume(scriptName), + } + volumeMounts := []corev1.VolumeMount{ + GetConfigVolumeMount(), + GetScriptVolumeMount(), + GetKollaConfigVolumeMount("host-discover"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: jobName, @@ -63,9 +79,11 @@ func HostDiscoveryJob( Spec: corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyOnFailure, ServiceAccountName: instance.Spec.ServiceAccount, - Volumes: []corev1.Volume{ - GetConfigVolume(configName), - GetScriptVolume(scriptName), + Volumes: volumes, + SecurityContext: &corev1.PodSecurityContext{ + // since we run as NovaUserID, e.g. certs need to be + // readable by the user, instead of root + FSGroup: ptr.To(NovaUserID), }, Containers: []corev1.Container{ { @@ -78,12 +96,8 @@ func HostDiscoveryJob( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - GetConfigVolumeMount(), - GetScriptVolumeMount(), - GetKollaConfigVolumeMount("host-discover"), - }, + Env: env, + VolumeMounts: volumeMounts, }, }, }, diff --git a/pkg/novaapi/deployment.go b/pkg/novaapi/deployment.go index b29c709ea..d29cc5d4c 100644 --- a/pkg/novaapi/deployment.go +++ b/pkg/novaapi/deployment.go @@ -20,6 +20,8 @@ import ( common "github.com/openstack-k8s-operators/lib-common/modules/common" affinity "github.com/openstack-k8s-operators/lib-common/modules/common/affinity" env "github.com/openstack-k8s-operators/lib-common/modules/common/env" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" "github.com/openstack-k8s-operators/nova-operator/pkg/nova" @@ -36,7 +38,7 @@ func StatefulSet( configHash string, labels map[string]string, annotations map[string]string, -) *appsv1.StatefulSet { +) (*appsv1.StatefulSet, error) { // This allows the pod to start up slowly. The pod will only be killed // if it does not succeed a probe in 60 seconds. startupProbe := &corev1.Probe{ @@ -85,6 +87,12 @@ func StatefulSet( startupProbe.HTTPGet = &corev1.HTTPGetAction{ Port: intstr.IntOrString{Type: intstr.Int, IntVal: int32(APIServicePort)}, } + + if instance.Spec.TLS.API.Enabled(service.EndpointPublic) { + livenessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + readinessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + startupProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + } } nodeSelector := map[string]string{} @@ -101,6 +109,42 @@ func StatefulSet( envVars["CONFIG_HASH"] = env.SetValue(configHash) env := env.MergeEnvs([]corev1.EnvVar{}, envVars) + // create Volume and VolumeMounts + volumes := []corev1.Volume{ + nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetLogVolume(), + } + volumeMounts := []corev1.VolumeMount{ + nova.GetConfigVolumeMount(), + nova.GetLogVolumeMount(), + nova.GetKollaConfigVolumeMount("nova-api"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + + for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { + if instance.Spec.TLS.API.Enabled(endpt) { + var tlsEndptCfg tls.GenericService + switch endpt { + case service.EndpointPublic: + tlsEndptCfg = instance.Spec.TLS.API.Public + case service.EndpointInternal: + tlsEndptCfg = instance.Spec.TLS.API.Internal + } + + svc, err := tlsEndptCfg.ToService() + if err != nil { + return nil, err + } + volumes = append(volumes, svc.CreateVolume(endpt.String())) + volumeMounts = append(volumeMounts, svc.CreateVolumeMounts(endpt.String())...) + } + } + statefulset := &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: instance.Name, @@ -119,9 +163,11 @@ func StatefulSet( }, Spec: corev1.PodSpec{ ServiceAccountName: instance.Spec.ServiceAccount, - Volumes: []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), - nova.GetLogVolume(), + Volumes: volumes, + SecurityContext: &corev1.PodSecurityContext{ + // since we run as NovaUserID, e.g. certs need to be + // readable by the user, instead of root + FSGroup: ptr.To(nova.NovaUserID), }, Containers: []corev1.Container{ // the first container in a pod is the default selected @@ -160,12 +206,8 @@ func StatefulSet( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(nova.NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - nova.GetConfigVolumeMount(), - nova.GetLogVolumeMount(), - nova.GetKollaConfigVolumeMount("nova-api"), - }, + Env: env, + VolumeMounts: volumeMounts, Resources: instance.Spec.Resources, StartupProbe: startupProbe, ReadinessProbe: readinessProbe, @@ -188,5 +230,5 @@ func StatefulSet( }, } - return statefulset + return statefulset, nil } diff --git a/pkg/novacompute/deployment.go b/pkg/novacompute/deployment.go index abef38d88..92a8e3aae 100644 --- a/pkg/novacompute/deployment.go +++ b/pkg/novacompute/deployment.go @@ -91,6 +91,21 @@ func StatefulSet( envVars["CONFIG_HASH"] = env.SetValue(configHash) env := env.MergeEnvs([]corev1.EnvVar{}, envVars) + // create Volume and VolumeMounts + volumes := []corev1.Volume{ + nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + } + volumeMounts := []corev1.VolumeMount{ + nova.GetConfigVolumeMount(), + nova.GetKollaConfigVolumeMount("nova-compute"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + statefulset := &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: instance.Name, @@ -109,9 +124,7 @@ func StatefulSet( }, Spec: corev1.PodSpec{ ServiceAccountName: instance.Spec.ServiceAccount, - Volumes: []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), - }, + Volumes: volumes, Containers: []corev1.Container{ { Name: instance.Name + "-compute", @@ -123,11 +136,8 @@ func StatefulSet( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(nova.NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - nova.GetConfigVolumeMount(), - nova.GetKollaConfigVolumeMount("nova-compute"), - }, + Env: env, + VolumeMounts: volumeMounts, Resources: instance.Spec.Resources, ReadinessProbe: readinessProbe, LivenessProbe: livenessProbe, diff --git a/pkg/novaconductor/dbsync.go b/pkg/novaconductor/dbsync.go index 8dc324060..b16d0827a 100644 --- a/pkg/novaconductor/dbsync.go +++ b/pkg/novaconductor/dbsync.go @@ -50,6 +50,23 @@ func CellDBSyncJob( env := env.MergeEnvs([]corev1.EnvVar{}, envVars) + // create Volume and VolumeMounts + volumes := []corev1.Volume{ + nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetScriptVolume(nova.GetScriptSecretName(instance.Name)), + } + volumeMounts := []corev1.VolumeMount{ + nova.GetConfigVolumeMount(), + nova.GetScriptVolumeMount(), + nova.GetKollaConfigVolumeMount("nova-conductor-dbsync"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: instance.Name + "-db-sync", @@ -62,10 +79,7 @@ func CellDBSyncJob( Spec: corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyOnFailure, ServiceAccountName: instance.Spec.ServiceAccount, - Volumes: []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), - nova.GetScriptVolume(nova.GetScriptSecretName(instance.Name)), - }, + Volumes: volumes, Containers: []corev1.Container{ { Name: instance.Name + "-db-sync", @@ -77,12 +91,8 @@ func CellDBSyncJob( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(nova.NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - nova.GetConfigVolumeMount(), - nova.GetScriptVolumeMount(), - nova.GetKollaConfigVolumeMount("nova-conductor-dbsync"), - }, + Env: env, + VolumeMounts: volumeMounts, }, }, }, diff --git a/pkg/novaconductor/deployment.go b/pkg/novaconductor/deployment.go index 91233bfba..c9d8effe5 100644 --- a/pkg/novaconductor/deployment.go +++ b/pkg/novaconductor/deployment.go @@ -97,6 +97,21 @@ func StatefulSet( envVars["CONFIG_HASH"] = env.SetValue(configHash) env := env.MergeEnvs([]corev1.EnvVar{}, envVars) + // create Volume and VolumeMounts + volumes := []corev1.Volume{ + nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + } + volumeMounts := []corev1.VolumeMount{ + nova.GetConfigVolumeMount(), + nova.GetKollaConfigVolumeMount("nova-conductor"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + statefulset := &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: instance.Name, @@ -115,9 +130,7 @@ func StatefulSet( }, Spec: corev1.PodSpec{ ServiceAccountName: instance.Spec.ServiceAccount, - Volumes: []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), - }, + Volumes: volumes, Containers: []corev1.Container{ { Name: instance.Name + "-conductor", @@ -129,11 +142,8 @@ func StatefulSet( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(nova.NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - nova.GetConfigVolumeMount(), - nova.GetKollaConfigVolumeMount("nova-conductor"), - }, + Env: env, + VolumeMounts: volumeMounts, Resources: instance.Spec.Resources, ReadinessProbe: readinessProbe, LivenessProbe: livenessProbe, diff --git a/pkg/novametadata/deployment.go b/pkg/novametadata/deployment.go index f7cf36a73..ff6c86b80 100644 --- a/pkg/novametadata/deployment.go +++ b/pkg/novametadata/deployment.go @@ -36,7 +36,7 @@ func StatefulSet( configHash string, labels map[string]string, annotations map[string]string, -) *appsv1.StatefulSet { +) (*appsv1.StatefulSet, error) { // This allows the pod to start up slowly. The pod will only be killed // if it does not succeed a probe in 60 seconds. startupProbe := &corev1.Probe{ @@ -85,6 +85,12 @@ func StatefulSet( startupProbe.HTTPGet = &corev1.HTTPGetAction{ Port: intstr.IntOrString{Type: intstr.Int, IntVal: int32(APIServicePort)}, } + + if instance.Spec.TLS.GenericService.Enabled() { + livenessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + readinessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + startupProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + } } nodeSelector := map[string]string{} @@ -101,6 +107,32 @@ func StatefulSet( envVars["CONFIG_HASH"] = env.SetValue(configHash) env := env.MergeEnvs([]corev1.EnvVar{}, envVars) + // create Volume and VolumeMounts + volumes := []corev1.Volume{ + nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetLogVolume(), + } + volumeMounts := []corev1.VolumeMount{ + nova.GetConfigVolumeMount(), + nova.GetLogVolumeMount(), + nova.GetKollaConfigVolumeMount("nova-metadata"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + + if instance.Spec.TLS.GenericService.Enabled() { + svc, err := instance.Spec.TLS.GenericService.ToService() + if err != nil { + return nil, err + } + volumes = append(volumes, svc.CreateVolume(ServiceName)) + volumeMounts = append(volumeMounts, svc.CreateVolumeMounts(ServiceName)...) + } + statefulset := &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: instance.Name, @@ -119,9 +151,11 @@ func StatefulSet( }, Spec: corev1.PodSpec{ ServiceAccountName: instance.Spec.ServiceAccount, - Volumes: []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), - nova.GetLogVolume(), + Volumes: volumes, + SecurityContext: &corev1.PodSecurityContext{ + // since we run as NovaUserID, e.g. certs need to be + // readable by the user, instead of root + FSGroup: ptr.To(nova.NovaUserID), }, Containers: []corev1.Container{ // the first container in a pod is the default selected @@ -143,10 +177,8 @@ func StatefulSet( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(nova.NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - nova.GetLogVolumeMount(), - }, + Env: env, + VolumeMounts: []corev1.VolumeMount{nova.GetLogVolumeMount()}, Resources: instance.Spec.Resources, StartupProbe: startupProbe, ReadinessProbe: readinessProbe, @@ -162,12 +194,8 @@ func StatefulSet( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(nova.NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - nova.GetConfigVolumeMount(), - nova.GetLogVolumeMount(), - nova.GetKollaConfigVolumeMount("nova-metadata"), - }, + Env: env, + VolumeMounts: volumeMounts, Resources: instance.Spec.Resources, StartupProbe: startupProbe, ReadinessProbe: readinessProbe, @@ -190,5 +218,5 @@ func StatefulSet( }, } - return statefulset + return statefulset, nil } diff --git a/pkg/novascheduler/deployment.go b/pkg/novascheduler/deployment.go index 9724e8a1e..350cc1652 100644 --- a/pkg/novascheduler/deployment.go +++ b/pkg/novascheduler/deployment.go @@ -106,6 +106,21 @@ func StatefulSet( envVars["CONFIG_HASH"] = env.SetValue(configHash) env := env.MergeEnvs([]corev1.EnvVar{}, envVars) + // create Volume and VolumeMounts + volumes := []corev1.Volume{ + nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + } + volumeMounts := []corev1.VolumeMount{ + nova.GetConfigVolumeMount(), + nova.GetKollaConfigVolumeMount("nova-scheduler"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + statefulset := &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: instance.Name, @@ -124,9 +139,7 @@ func StatefulSet( }, Spec: corev1.PodSpec{ ServiceAccountName: instance.Spec.ServiceAccount, - Volumes: []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), - }, + Volumes: volumes, Containers: []corev1.Container{ { Name: instance.Name + "-scheduler", @@ -138,11 +151,8 @@ func StatefulSet( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(nova.NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - nova.GetConfigVolumeMount(), - nova.GetKollaConfigVolumeMount("nova-scheduler"), - }, + Env: env, + VolumeMounts: volumeMounts, Resources: instance.Spec.Resources, StartupProbe: startupProbe, ReadinessProbe: readinessProbe, diff --git a/pkg/novncproxy/deployment.go b/pkg/novncproxy/deployment.go index e9f89646a..a382e5078 100644 --- a/pkg/novncproxy/deployment.go +++ b/pkg/novncproxy/deployment.go @@ -36,7 +36,7 @@ func StatefulSet( configHash string, labels map[string]string, annotations map[string]string, -) *appsv1.StatefulSet { +) (*appsv1.StatefulSet, error) { // This allows the pod to start up slowly. The pod will only be killed // if it does not succeed a probe in 60 seconds. startupProbe := &corev1.Probe{ @@ -88,6 +88,12 @@ func StatefulSet( Port: intstr.IntOrString{Type: intstr.Int, IntVal: int32(NoVNCProxyPort)}, Path: "/vnc_lite.html", } + + if instance.Spec.TLS.GenericService.Enabled() { + livenessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + readinessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + startupProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + } } nodeSelector := map[string]string{} @@ -104,6 +110,30 @@ func StatefulSet( envVars["CONFIG_HASH"] = env.SetValue(configHash) env := env.MergeEnvs([]corev1.EnvVar{}, envVars) + // create Volume and VolumeMounts + volumes := []corev1.Volume{ + nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + } + volumeMounts := []corev1.VolumeMount{ + nova.GetConfigVolumeMount(), + nova.GetKollaConfigVolumeMount("nova-novncproxy"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + + if instance.Spec.TLS.GenericService.Enabled() { + svc, err := instance.Spec.TLS.GenericService.ToService() + if err != nil { + return nil, err + } + volumes = append(volumes, svc.CreateVolume(ServiceName)) + volumeMounts = append(volumeMounts, svc.CreateVolumeMounts(ServiceName)...) + } + statefulset := &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: instance.Name, @@ -122,8 +152,11 @@ func StatefulSet( }, Spec: corev1.PodSpec{ ServiceAccountName: instance.Spec.ServiceAccount, - Volumes: []corev1.Volume{ - nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + Volumes: volumes, + SecurityContext: &corev1.PodSecurityContext{ + // since we run as NovaUserID, e.g. certs need to be + // readable by the user, instead of root + FSGroup: ptr.To(nova.NovaUserID), }, Containers: []corev1.Container{ { @@ -136,11 +169,8 @@ func StatefulSet( SecurityContext: &corev1.SecurityContext{ RunAsUser: ptr.To(nova.NovaUserID), }, - Env: env, - VolumeMounts: []corev1.VolumeMount{ - nova.GetConfigVolumeMount(), - nova.GetKollaConfigVolumeMount("nova-novncproxy"), - }, + Env: env, + VolumeMounts: volumeMounts, Resources: instance.Spec.Resources, StartupProbe: startupProbe, ReadinessProbe: readinessProbe, @@ -163,5 +193,5 @@ func StatefulSet( }, } - return statefulset + return statefulset, nil } diff --git a/templates/nova.conf b/templates/nova.conf index b9c91b16f..d89a8ee3a 100644 --- a/templates/nova.conf +++ b/templates/nova.conf @@ -42,6 +42,14 @@ metadata_workers=1 enabled_apis=metadata {{end}} +{{if eq .service_name "nova-novncproxy"}} +{{ if (index . "SSLCertificateFile") }} +ssl_only=true +cert={{.SSLCertificateFile}} +key={{.SSLCertificateKeyFile}} +{{end}} +{{end}} + [oslo_concurrency] lock_path = /var/lib/nova/tmp @@ -211,7 +219,6 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -cafile = {{ .openstack_cacert }} region_name = {{ .openstack_region_name }} # This is part of hardening related to CVE-2023-2088 # https://docs.openstack.org/nova/latest/configuration/config.html#keystone_authtoken.service_token_roles_required @@ -226,7 +233,6 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -cafile = {{ .openstack_cacert }} region_name = {{ .openstack_region_name }} valid_interfaces = internal @@ -238,7 +244,6 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -cafile = {{ .openstack_cacert }} region_name = {{ .openstack_region_name }} valid_interfaces = internal {{if (index . "debug") }}debug=true{{end}} @@ -251,7 +256,6 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -cafile = {{ .openstack_cacert }} region_name = {{ .openstack_region_name }} valid_interfaces = internal {{if eq .service_name "nova-metadata"}} @@ -267,7 +271,6 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -cafile = {{ .openstack_cacert }} region_name = {{ .openstack_region_name }} catalog_info = volumev3:cinderv3:internalURL @@ -279,7 +282,6 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -cafile = {{ .openstack_cacert }} region_name = {{ .openstack_region_name }} barbican_endpoint_type = internal @@ -292,7 +294,6 @@ user_domain_name = {{ .default_user_domain}} project_name = service username = {{ .nova_keystone_user }} password = {{ .nova_keystone_password }} -cafile = {{ .openstack_cacert }} {{ if (index . "compute_driver") }} {{if eq .compute_driver "ironic.IronicDriver"}} diff --git a/templates/novaapi/config/httpd.conf b/templates/novaapi/config/httpd.conf index 17e35370f..f79ff2ca1 100644 --- a/templates/novaapi/config/httpd.conf +++ b/templates/novaapi/config/httpd.conf @@ -13,6 +13,10 @@ Listen 8774 TypesConfig /etc/mime.types Include conf.modules.d/*.conf +{{- if .tls }} +## TODO: fix default ssl.conf to comment not available tls certs. Than we can remove this condition +Include conf.d/*.conf +{{- end }} LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy @@ -25,32 +29,60 @@ CustomLog /dev/stdout proxy env=forwarded ## set default apache log level to info from warning LogLevel info +{{ range $endpt, $vhost := .VHosts }} +# {{ $endpt }} vhost {{ $vhost.ServerName }} configuration = 2.4> ErrorLogFormat "%M" SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded + + ServerName {{ $vhost.ServerName }} + + ## Vhost docroot + DocumentRoot "/var/www/cgi-bin" + + ## Directories, there should at least be a declaration for /var/www/cgi-bin + + Options -Indexes +FollowSymLinks +MultiViews + AllowOverride None + Require all granted + + + ## Logging ErrorLog /dev/stdout + ServerSignature Off CustomLog /dev/stdout combined env=!forwarded CustomLog /dev/stdout proxy env=forwarded ## set nova vhost log level to debug LogLevel debug +{{- if $vhost.tls }} + SetEnvIf X-Forwarded-Proto https HTTPS=1 + + ## SSL directives + SSLEngine on + SSLCertificateFile "{{ $vhost.SSLCertificateFile }}" + SSLCertificateKeyFile "{{ $vhost.SSLCertificateKeyFile }}" +{{- end }} + ## WSGI configuration - WSGIProcessGroup nova-api + WSGIProcessGroup {{ $endpt }} + #WSGIProcessGroup nova-api WSGIApplicationGroup %{GLOBAL} WSGIPassAuthorization On ## In general we want nova-api to scale via k8s replicas but we need ## two processes per replica to always has a room for a healthecheck query - WSGIDaemonProcess nova-api processes=2 threads=1 user=nova group=nova display-name=nova-api - WSGIScriptAlias / /usr/bin/nova-api-wsgi + WSGIDaemonProcess {{ $endpt }} display-name={{ $endpt }} processes=2 threads=1 user=nova group=nova + WSGIScriptAlias / "/usr/bin/nova-api-wsgi" +{{ end }} Alias /nova-api /usr/bin/nova-api-wsgi SetHandler wsgi-script Options +ExecCGI - WSGIProcessGroup nova-api + WSGIProcessGroup public WSGIApplicationGroup %{GLOBAL} WSGIPassAuthorization On diff --git a/templates/novaapi/config/nova-api-config.json b/templates/novaapi/config/nova-api-config.json index 5d4a9edde..649dbd250 100644 --- a/templates/novaapi/config/nova-api-config.json +++ b/templates/novaapi/config/nova-api-config.json @@ -25,6 +25,12 @@ "dest": "/etc/httpd/conf/httpd.conf", "owner": "apache", "perm": "0644" + }, + { + "source": "/var/lib/openstack/config/ssl.conf", + "dest": "/etc/httpd/conf.d/ssl.conf", + "owner": "root", + "perm": "0644" } ], "permissions": [ diff --git a/templates/novaapi/config/ssl.conf b/templates/novaapi/config/ssl.conf new file mode 100644 index 000000000..e3da4ecb2 --- /dev/null +++ b/templates/novaapi/config/ssl.conf @@ -0,0 +1,21 @@ + + SSLRandomSeed startup builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + SSLRandomSeed connect file:/dev/urandom 512 + + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + SSLPassPhraseDialog builtin + SSLSessionCache "shmcb:/var/cache/mod_ssl/scache(512000)" + SSLSessionCacheTimeout 300 + Mutex default + SSLCryptoDevice builtin + SSLHonorCipherOrder On + SSLUseStapling Off + SSLStaplingCache "shmcb:/run/httpd/ssl_stapling(32768)" + SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4:!3DES + SSLProtocol all -SSLv2 -SSLv3 -TLSv1 + SSLOptions StdEnvVars + diff --git a/templates/novametadata/config/httpd.conf b/templates/novametadata/config/httpd.conf index b66a707c0..14bfb5fd5 100644 --- a/templates/novametadata/config/httpd.conf +++ b/templates/novametadata/config/httpd.conf @@ -13,6 +13,10 @@ Listen 8775 TypesConfig /etc/mime.types Include conf.modules.d/*.conf +{{- if .tls }} +## TODO: fix default ssl.conf to comment not available tls certs. Than we can remove this condition +Include conf.d/*.conf +{{- end }} LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy @@ -30,12 +34,24 @@ LogLevel info ErrorLogFormat "%M" SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded + + ServerName {{ .ServerName }} + ErrorLog /dev/stdout CustomLog /dev/stdout combined env=!forwarded CustomLog /dev/stdout proxy env=forwarded ## set nova vhost log level to debug LogLevel debug +{{- if .tls }} + SetEnvIf X-Forwarded-Proto https HTTPS=1 + + ## SSL directives + SSLEngine on + SSLCertificateFile "{{ .SSLCertificateFile }}" + SSLCertificateKeyFile "{{ .SSLCertificateKeyFile }}" +{{- end }} + ## WSGI configuration WSGIProcessGroup nova-metadata WSGIApplicationGroup %{GLOBAL} diff --git a/templates/novametadata/config/nova-metadata-config.json b/templates/novametadata/config/nova-metadata-config.json index d005385e6..8fc942ab0 100644 --- a/templates/novametadata/config/nova-metadata-config.json +++ b/templates/novametadata/config/nova-metadata-config.json @@ -6,25 +6,31 @@ "dest": "/etc/nova/nova.conf", "owner": "nova", "perm": "0600" - }, - { + }, + { "source": "/var/lib/openstack/config/01-nova.conf", "dest": "/etc/nova/nova.conf.d/01-nova.conf", "owner": "nova", "perm": "0600" - }, - { + }, + { "source": "/var/lib/openstack/config/02-nova-override.conf", "dest": "/etc/nova/nova.conf.d/02-nova-override.conf", "owner": "nova", "perm": "0600", "optional": true - }, + }, { "source": "/var/lib/openstack/config/httpd.conf", "dest": "/etc/httpd/conf/httpd.conf", "owner": "apache", "perm": "0644" + }, + { + "source": "/var/lib/openstack/config/ssl.conf", + "dest": "/etc/httpd/conf.d/ssl.conf", + "owner": "root", + "perm": "0644" } ], "permissions": [ diff --git a/templates/novametadata/config/ssl.conf b/templates/novametadata/config/ssl.conf new file mode 100644 index 000000000..e3da4ecb2 --- /dev/null +++ b/templates/novametadata/config/ssl.conf @@ -0,0 +1,21 @@ + + SSLRandomSeed startup builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + SSLRandomSeed connect file:/dev/urandom 512 + + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + SSLPassPhraseDialog builtin + SSLSessionCache "shmcb:/var/cache/mod_ssl/scache(512000)" + SSLSessionCacheTimeout 300 + Mutex default + SSLCryptoDevice builtin + SSLHonorCipherOrder On + SSLUseStapling Off + SSLStaplingCache "shmcb:/run/httpd/ssl_stapling(32768)" + SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4:!3DES + SSLProtocol all -SSLv2 -SSLv3 -TLSv1 + SSLOptions StdEnvVars + diff --git a/test/functional/base_test.go b/test/functional/base_test.go index cfe1f807b..741f8683b 100644 --- a/test/functional/base_test.go +++ b/test/functional/base_test.go @@ -43,6 +43,9 @@ const ( // asserts using `Consistently`. consistencyTimeout = timeout ironicComputeName = "ironic-compute" + //PublicCertSecretName = "public-tls-certs" + //InternalCertSecretName = "internal-tls-certs" + //CABundleSecretName = "combined-ca-bundle" ) type NovaAPIFixture struct { @@ -657,6 +660,9 @@ type NovaNames struct { APIKeystoneEndpointName types.NamespacedName APIStatefulSetName types.NamespacedName APIConfigDataName types.NamespacedName + InternalCertSecretName types.NamespacedName + PublicCertSecretName types.NamespacedName + CaBundleSecretName types.NamespacedName // refers internal API network for all Nova services (not just nova API) InternalAPINetworkNADName types.NamespacedName SchedulerName types.NamespacedName @@ -727,6 +733,16 @@ func GetNovaNames(novaName types.NamespacedName, cellNames []string) NovaNames { Namespace: novaAPI.Namespace, Name: novaAPI.Name + "-config-data", }, + InternalCertSecretName: types.NamespacedName{ + Namespace: novaAPI.Namespace, + Name: "internal-tls-certs"}, + PublicCertSecretName: types.NamespacedName{ + Namespace: novaAPI.Namespace, + Name: "public-tls-certs"}, + CaBundleSecretName: types.NamespacedName{ + Namespace: novaName.Namespace, + Name: "combined-ca-bundle"}, + InternalAPINetworkNADName: types.NamespacedName{ Namespace: novaName.Namespace, Name: "internalapi", diff --git a/test/functional/nova_compute_ironic_controller_test.go b/test/functional/nova_compute_ironic_controller_test.go index a4fe7f466..ebe6807d5 100644 --- a/test/functional/nova_compute_ironic_controller_test.go +++ b/test/functional/nova_compute_ironic_controller_test.go @@ -17,6 +17,7 @@ package functional_test import ( "encoding/json" + "fmt" networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" . "github.com/onsi/ginkgo/v2" @@ -529,3 +530,64 @@ var _ = Describe("NovaCompute with ironic diver controller", func() { }) }) }) + +var _ = Describe("NovaCompute with ironic diver controller", func() { + When("NovaCompute is created with TLS CA cert secret", func() { + BeforeEach(func() { + spec := GetDefaultNovaComputeSpec(cell1) + spec["tls"] = map[string]interface{}{ + "caBundleSecretName": novaNames.CaBundleSecretName.Name, + } + novaCompute := CreateNovaCompute(cell1.NovaComputeName, spec) + DeferCleanup(th.DeleteInstance, novaCompute) + DeferCleanup( + k8sClient.Delete, + ctx, + CreateCellInternalSecret(cell1), + ) + }) + + It("reports that the CA secret is missing", func() { + th.ExpectConditionWithDetails( + cell1.NovaComputeName, + ConditionGetterFunc(NovaComputeConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/combined-ca-bundle not found", novaNames.Namespace), + ) + th.ExpectCondition( + cell1.NovaComputeName, + ConditionGetterFunc(NovaComputeConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("creates a StatefulSet for nova-compute service with TLS CA cert attached", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + th.SimulateStatefulSetReplicaReady(cell1.NovaComputeStatefulSetName) + + ss := th.GetStatefulSet(cell1.NovaComputeStatefulSetName) + + // Check the resulting deployment fields + Expect(int(*ss.Spec.Replicas)).To(Equal(1)) + Expect(ss.Spec.Template.Spec.Volumes).To(HaveLen(2)) + Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(1)) + + // cert deployment volumes + th.AssertVolumeExists(novaNames.CaBundleSecretName.Name, ss.Spec.Template.Spec.Volumes) + + // CA container certs + apiContainer := ss.Spec.Template.Spec.Containers[0] + th.AssertVolumeMountExists(novaNames.CaBundleSecretName.Name, "tls-ca-bundle.pem", apiContainer.VolumeMounts) + + th.ExpectCondition( + cell1.NovaComputeName, + ConditionGetterFunc(NovaComputeConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + }) +}) diff --git a/test/functional/nova_metadata_controller_test.go b/test/functional/nova_metadata_controller_test.go index e4bfad1ee..46cec8ca8 100644 --- a/test/functional/nova_metadata_controller_test.go +++ b/test/functional/nova_metadata_controller_test.go @@ -27,6 +27,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/util" @@ -734,3 +735,102 @@ var _ = Describe("NovaMetadata controller", func() { }) }) }) + +var _ = Describe("NovaMetadata controller", func() { + When("NovaMetadata is created with TLS CA cert secret", func() { + BeforeEach(func() { + DeferCleanup( + k8sClient.Delete, ctx, CreateInternalTopLevelSecret(novaNames)) + + spec := GetDefaultNovaMetadataSpec(novaNames.InternalTopLevelSecretName) + spec["tls"] = map[string]interface{}{ + "secretName": ptr.To(novaNames.InternalCertSecretName.Name), + "caBundleSecretName": novaNames.CaBundleSecretName.Name, + } + + DeferCleanup(th.DeleteInstance, CreateNovaMetadata(novaNames.MetadataName, spec)) + }) + + It("reports that the CA secret is missing", func() { + th.ExpectConditionWithDetails( + novaNames.MetadataName, + ConditionGetterFunc(NovaMetadataConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/combined-ca-bundle not found", novaNames.Namespace), + ) + th.ExpectCondition( + novaNames.MetadataName, + ConditionGetterFunc(NovaMetadataConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reports that the service cert secret is missing", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + + th.ExpectConditionWithDetails( + novaNames.MetadataName, + ConditionGetterFunc(NovaMetadataConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/internal-tls-certs not found", novaNames.Namespace), + ) + th.ExpectCondition( + novaNames.MetadataName, + ConditionGetterFunc(NovaMetadataConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("creates a StatefulSet for nova-metadata service with TLS CA cert attached", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(novaNames.InternalCertSecretName)) + th.SimulateStatefulSetReplicaReady(novaNames.MetadataStatefulSetName) + + ss := th.GetStatefulSet(novaNames.MetadataStatefulSetName) + + // Check the resulting deployment fields + Expect(int(*ss.Spec.Replicas)).To(Equal(1)) + Expect(ss.Spec.Template.Spec.Volumes).To(HaveLen(4)) + Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(2)) + + // cert deployment volumes + th.AssertVolumeExists(novaNames.CaBundleSecretName.Name, ss.Spec.Template.Spec.Volumes) + th.AssertVolumeExists("nova-metadata-tls-certs", ss.Spec.Template.Spec.Volumes) + + // CA container certs + apiContainer := ss.Spec.Template.Spec.Containers[1] + th.AssertVolumeMountExists(novaNames.CaBundleSecretName.Name, "tls-ca-bundle.pem", apiContainer.VolumeMounts) + th.AssertVolumeMountExists("nova-metadata-tls-certs", "tls.key", apiContainer.VolumeMounts) + th.AssertVolumeMountExists("nova-metadata-tls-certs", "tls.crt", apiContainer.VolumeMounts) + + th.ExpectCondition( + novaNames.MetadataName, + ConditionGetterFunc(NovaMetadataConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + + configDataMap := th.GetSecret(novaNames.MetadataConfigDataName) + Expect(configDataMap).ShouldNot(BeNil()) + Expect(configDataMap.Data).Should(HaveKey("httpd.conf")) + Expect(configDataMap.Data).Should(HaveKey("ssl.conf")) + configData := string(configDataMap.Data["httpd.conf"]) + Expect(configData).Should(ContainSubstring("SSLEngine on")) + Expect(configData).Should(ContainSubstring("SSLCertificateFile \"/etc/pki/tls/certs/nova-metadata.crt\"")) + Expect(configData).Should(ContainSubstring("SSLCertificateKeyFile \"/etc/pki/tls/private/nova-metadata.key\"")) + + computeConfigData := th.GetSecret(novaNames.MetadataNeutronConfigDataName) + Expect(computeConfigData).ShouldNot(BeNil()) + Expect(computeConfigData.Data).To(HaveLen(1)) + Expect(computeConfigData.Data).Should(HaveKey("05-nova-metadata.conf")) + configData = string(computeConfigData.Data["05-nova-metadata.conf"]) + Expect(configData).Should(ContainSubstring("nova_metadata_protocol = https")) + }) + }) +}) diff --git a/test/functional/nova_novncproxy_test.go b/test/functional/nova_novncproxy_test.go index af504788e..3fce85906 100644 --- a/test/functional/nova_novncproxy_test.go +++ b/test/functional/nova_novncproxy_test.go @@ -27,6 +27,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ) var _ = Describe("NovaNoVNCProxy controller", func() { @@ -643,3 +644,97 @@ var _ = Describe("NovaNoVNCProxy controller", func() { }) }) }) + +var _ = Describe("NovaNoVNCProxy controller", func() { + When("NovaNoVNCProxy is created with TLS CA cert secret", func() { + BeforeEach(func() { + spec := GetDefaultNovaNoVNCProxySpec(cell1) + spec["tls"] = map[string]interface{}{ + "secretName": ptr.To(novaNames.InternalCertSecretName.Name), + "caBundleSecretName": novaNames.CaBundleSecretName.Name, + } + DeferCleanup(th.DeleteInstance, CreateNovaNoVNCProxy(cell1.NoVNCProxyName, spec)) + DeferCleanup( + k8sClient.Delete, ctx, CreateCellInternalSecret(cell1)) + }) + + It("reports that the CA secret is missing", func() { + th.ExpectConditionWithDetails( + cell1.NoVNCProxyName, + ConditionGetterFunc(NoVNCProxyConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/combined-ca-bundle not found", novaNames.Namespace), + ) + th.ExpectCondition( + cell1.NoVNCProxyName, + ConditionGetterFunc(NoVNCProxyConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reports that the service cert secret is missing", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + + th.ExpectConditionWithDetails( + cell1.NoVNCProxyName, + ConditionGetterFunc(NoVNCProxyConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/internal-tls-certs not found", novaNames.Namespace), + ) + th.ExpectCondition( + cell1.NoVNCProxyName, + ConditionGetterFunc(NoVNCProxyConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("creates a StatefulSet for nova-metadata service with TLS CA cert attached", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(novaNames.InternalCertSecretName)) + th.SimulateStatefulSetReplicaReady(cell1.NoVNCProxyStatefulSetName) + + ss := th.GetStatefulSet(cell1.NoVNCProxyStatefulSetName) + + // Check the resulting deployment fields + Expect(int(*ss.Spec.Replicas)).To(Equal(1)) + Expect(ss.Spec.Template.Spec.Volumes).To(HaveLen(3)) + Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(1)) + + // cert deployment volumes + th.AssertVolumeExists(novaNames.CaBundleSecretName.Name, ss.Spec.Template.Spec.Volumes) + th.AssertVolumeExists("nova-novncproxy-tls-certs", ss.Spec.Template.Spec.Volumes) + + // CA container certs + apiContainer := ss.Spec.Template.Spec.Containers[0] + th.AssertVolumeMountExists(novaNames.CaBundleSecretName.Name, "tls-ca-bundle.pem", apiContainer.VolumeMounts) + th.AssertVolumeMountExists("nova-novncproxy-tls-certs", "tls.key", apiContainer.VolumeMounts) + th.AssertVolumeMountExists("nova-novncproxy-tls-certs", "tls.crt", apiContainer.VolumeMounts) + + th.ExpectCondition( + cell1.NoVNCProxyName, + ConditionGetterFunc(NoVNCProxyConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + + configDataMap := th.GetSecret( + types.NamespacedName{ + Namespace: cell1.NoVNCProxyName.Namespace, + Name: fmt.Sprintf("%s-config-data", cell1.NoVNCProxyName.Name), + }, + ) + Expect(configDataMap).ShouldNot(BeNil()) + Expect(configDataMap.Data).Should(HaveKey("01-nova.conf")) + configData := string(configDataMap.Data["01-nova.conf"]) + Expect(configData).Should(ContainSubstring("ssl_only=true")) + Expect(configData).Should(ContainSubstring("cert=/etc/pki/tls/certs/nova-novncproxy.crt")) + Expect(configData).Should(ContainSubstring("key=/etc/pki/tls/private/nova-novncproxy.key")) + }) + }) +}) diff --git a/test/functional/nova_scheduler_test.go b/test/functional/nova_scheduler_test.go index 4251db7b9..539bda76b 100644 --- a/test/functional/nova_scheduler_test.go +++ b/test/functional/nova_scheduler_test.go @@ -597,3 +597,60 @@ var _ = Describe("NovaScheduler controller cleaning", func() { }) }) }) + +var _ = Describe("NovaScheduler controller", func() { + When("NovaScheduler is created with TLS CA cert secret", func() { + BeforeEach(func() { + spec := GetDefaultNovaSchedulerSpec(novaNames) + spec["tls"] = map[string]interface{}{ + "caBundleSecretName": novaNames.CaBundleSecretName.Name, + } + + DeferCleanup( + k8sClient.Delete, ctx, CreateInternalTopLevelSecret(novaNames)) + DeferCleanup(th.DeleteInstance, CreateNovaScheduler(novaNames.SchedulerName, spec)) + }) + + It("reports that the CA secret is missing", func() { + th.ExpectConditionWithDetails( + novaNames.SchedulerName, + ConditionGetterFunc(NovaSchedulerConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/combined-ca-bundle not found", novaNames.Namespace), + ) + th.ExpectCondition( + novaNames.SchedulerName, + ConditionGetterFunc(NovaSchedulerConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("creates a StatefulSet for nova-scheduler service with TLS CA cert attached", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + th.SimulateStatefulSetReplicaReady(novaNames.SchedulerStatefulSetName) + + ss := th.GetStatefulSet(novaNames.SchedulerStatefulSetName) + // Check the resulting deployment fields + Expect(int(*ss.Spec.Replicas)).To(Equal(1)) + Expect(ss.Spec.Template.Spec.Volumes).To(HaveLen(2)) + Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(1)) + + // cert deployment volumes + th.AssertVolumeExists(novaNames.CaBundleSecretName.Name, ss.Spec.Template.Spec.Volumes) + + // CA container certs + apiContainer := ss.Spec.Template.Spec.Containers[0] + th.AssertVolumeMountExists(novaNames.CaBundleSecretName.Name, "tls-ca-bundle.pem", apiContainer.VolumeMounts) + + th.ExpectCondition( + novaNames.SchedulerName, + ConditionGetterFunc(NovaSchedulerConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + }) +}) diff --git a/test/functional/novaapi_controller_test.go b/test/functional/novaapi_controller_test.go index 0a747738b..9bb1c3152 100644 --- a/test/functional/novaapi_controller_test.go +++ b/test/functional/novaapi_controller_test.go @@ -804,3 +804,144 @@ var _ = Describe("NovaAPI controller", func() { }) }) }) + +var _ = Describe("NovaAPI controller", func() { + When("NovaAPI is created with TLS cert secrets", func() { + BeforeEach(func() { + spec := GetDefaultNovaAPISpec(novaNames) + spec["tls"] = map[string]interface{}{ + "api": map[string]interface{}{ + "internal": map[string]interface{}{ + "secretName": novaNames.InternalCertSecretName.Name, + }, + "public": map[string]interface{}{ + "secretName": novaNames.PublicCertSecretName.Name, + }, + }, + "caBundleSecretName": novaNames.CaBundleSecretName.Name, + } + + DeferCleanup( + k8sClient.Delete, ctx, CreateInternalTopLevelSecret(novaNames)) + DeferCleanup(th.DeleteInstance, CreateNovaAPI(novaNames.APIName, spec)) + }) + + It("reports that the CA secret is missing", func() { + th.ExpectConditionWithDetails( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/combined-ca-bundle not found", novaNames.Namespace), + ) + th.ExpectCondition( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reports that the internal cert secret is missing", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + + th.ExpectConditionWithDetails( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/internal-tls-certs not found", novaNames.Namespace), + ) + th.ExpectCondition( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reports that the public cert secret is missing", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(novaNames.InternalCertSecretName)) + + th.ExpectConditionWithDetails( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/public-tls-certs not found", novaNames.Namespace), + ) + th.ExpectCondition( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("creates a StatefulSet for nova-api service with TLS certs attached", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(novaNames.InternalCertSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(novaNames.PublicCertSecretName)) + th.SimulateStatefulSetReplicaReady(novaNames.APIStatefulSetName) + keystone.SimulateKeystoneEndpointReady(novaNames.APIKeystoneEndpointName) + + ss := th.GetStatefulSet(novaNames.APIStatefulSetName) + // Check the resulting deployment fields + Expect(int(*ss.Spec.Replicas)).To(Equal(1)) + Expect(ss.Spec.Template.Spec.Volumes).To(HaveLen(5)) + Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(2)) + + // cert deployment volumes + th.AssertVolumeExists(novaNames.CaBundleSecretName.Name, ss.Spec.Template.Spec.Volumes) + th.AssertVolumeExists(novaNames.InternalCertSecretName.Name, ss.Spec.Template.Spec.Volumes) + th.AssertVolumeExists(novaNames.PublicCertSecretName.Name, ss.Spec.Template.Spec.Volumes) + + // httpd container certs + apiContainer := ss.Spec.Template.Spec.Containers[1] + th.AssertVolumeMountExists(novaNames.InternalCertSecretName.Name, "tls.key", apiContainer.VolumeMounts) + th.AssertVolumeMountExists(novaNames.InternalCertSecretName.Name, "tls.crt", apiContainer.VolumeMounts) + th.AssertVolumeMountExists(novaNames.PublicCertSecretName.Name, "tls.key", apiContainer.VolumeMounts) + th.AssertVolumeMountExists(novaNames.PublicCertSecretName.Name, "tls.crt", apiContainer.VolumeMounts) + th.AssertVolumeMountExists(novaNames.CaBundleSecretName.Name, "tls-ca-bundle.pem", apiContainer.VolumeMounts) + + Expect(apiContainer.ReadinessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) + Expect(apiContainer.LivenessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) + Expect(apiContainer.StartupProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) + + configDataMap := th.GetSecret(novaNames.APIConfigDataName) + Expect(configDataMap).ShouldNot(BeNil()) + Expect(configDataMap.Data).Should(HaveKey("httpd.conf")) + Expect(configDataMap.Data).Should(HaveKey("ssl.conf")) + configData := string(configDataMap.Data["httpd.conf"]) + Expect(configData).Should(ContainSubstring("SSLEngine on")) + Expect(configData).Should(ContainSubstring("SSLCertificateFile \"/etc/pki/tls/certs/internal.crt\"")) + Expect(configData).Should(ContainSubstring("SSLCertificateKeyFile \"/etc/pki/tls/private/internal.key\"")) + Expect(configData).Should(ContainSubstring("SSLCertificateFile \"/etc/pki/tls/certs/public.crt\"")) + Expect(configData).Should(ContainSubstring("SSLCertificateKeyFile \"/etc/pki/tls/private/public.key\"")) + }) + + It("TLS Endpoints are created", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(novaNames.InternalCertSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(novaNames.PublicCertSecretName)) + th.SimulateStatefulSetReplicaReady(novaNames.APIStatefulSetName) + keystone.SimulateKeystoneEndpointReady(novaNames.APIKeystoneEndpointName) + + keystoneEndpoint := keystone.GetKeystoneEndpoint(types.NamespacedName{Namespace: novaNames.APIName.Namespace, Name: "nova"}) + endpoints := keystoneEndpoint.Spec.Endpoints + Expect(endpoints).To(HaveKeyWithValue("public", string("https://nova-public."+novaNames.APIName.Namespace+".svc:8774/v2.1"))) + Expect(endpoints).To(HaveKeyWithValue("internal", "https://nova-internal."+novaNames.APIName.Namespace+".svc:8774/v2.1")) + + th.ExpectCondition( + novaNames.APIName, + ConditionGetterFunc(NovaAPIConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + }) +}) diff --git a/test/functional/novaconductor_controller_test.go b/test/functional/novaconductor_controller_test.go index aae60525d..368983e39 100644 --- a/test/functional/novaconductor_controller_test.go +++ b/test/functional/novaconductor_controller_test.go @@ -685,3 +685,62 @@ var _ = Describe("NovaConductor controller cleaning", func() { }) }) }) + +var _ = Describe("NovaConductor controller", func() { + When("NovaConductor is created with TLS CA cert secret", func() { + BeforeEach(func() { + DeferCleanup( + k8sClient.Delete, ctx, CreateCellInternalSecret(cell0)) + + spec := GetDefaultNovaConductorSpec(cell0) + spec["tls"] = map[string]interface{}{ + "caBundleSecretName": novaNames.CaBundleSecretName.Name, + } + DeferCleanup(th.DeleteInstance, CreateNovaConductor(cell0.ConductorName, spec)) + }) + + It("reports that the CA secret is missing", func() { + th.ExpectConditionWithDetails( + cell0.ConductorName, + ConditionGetterFunc(NovaConductorConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + fmt.Sprintf("TLSInput error occured in TLS sources Secret %s/combined-ca-bundle not found", novaNames.Namespace), + ) + th.ExpectCondition( + cell0.ConductorName, + ConditionGetterFunc(NovaConductorConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("creates a StatefulSet for nova-conductor service with TLS CA cert attached", func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(novaNames.CaBundleSecretName)) + th.SimulateJobSuccess(cell0.DBSyncJobName) + th.SimulateStatefulSetReplicaReady(cell0.ConductorStatefulSetName) + + ss := th.GetStatefulSet(cell0.ConductorStatefulSetName) + + // Check the resulting deployment fields + Expect(int(*ss.Spec.Replicas)).To(Equal(1)) + Expect(ss.Spec.Template.Spec.Volumes).To(HaveLen(2)) + Expect(ss.Spec.Template.Spec.Containers).To(HaveLen(1)) + + // cert deployment volumes + th.AssertVolumeExists(novaNames.CaBundleSecretName.Name, ss.Spec.Template.Spec.Volumes) + + // CA container certs + apiContainer := ss.Spec.Template.Spec.Containers[0] + th.AssertVolumeMountExists(novaNames.CaBundleSecretName.Name, "tls-ca-bundle.pem", apiContainer.VolumeMounts) + + th.ExpectCondition( + cell0.ConductorName, + ConditionGetterFunc(NovaConductorConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + }) +})