diff --git a/charts/gardener-extension-provider-stackit/templates/deployment.yaml b/charts/gardener-extension-provider-stackit/templates/deployment.yaml index 73882df6..3f13b58e 100644 --- a/charts/gardener-extension-provider-stackit/templates/deployment.yaml +++ b/charts/gardener-extension-provider-stackit/templates/deployment.yaml @@ -130,10 +130,6 @@ spec: volumeMounts: - name: config mountPath: /etc/{{ include "name" . }}/config - - name: custom-certs - mountPath: /etc/ssl/certs/customca.crt - readOnly: true - subPath: customca.crt {{- if .Values.imageVectorOverwrite }} - name: imagevector-overwrite mountPath: /charts_overwrite/ @@ -144,10 +140,6 @@ spec: configMap: name: {{ include "name" . }}-configmap defaultMode: 420 - - name: custom-certs - secret: - secretName: {{ include "name" . }}-ca - optional: true {{- if .Values.imageVectorOverwrite }} - name: imagevector-overwrite configMap: diff --git a/charts/gardener-extension-provider-stackit/templates/secret.yaml b/charts/gardener-extension-provider-stackit/templates/secret.yaml deleted file mode 100644 index 1f159aad..00000000 --- a/charts/gardener-extension-provider-stackit/templates/secret.yaml +++ /dev/null @@ -1,9 +0,0 @@ -{{- if .Values.config.stackitAPIEndpoints.caBundle }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "name" . }}-ca - namespace: {{ .Release.Namespace }} -data: - customca.crt: {{ .Values.config.stackitAPIEndpoints.caBundle }} -{{- end }} diff --git a/charts/gardener-extension-provider-stackit/values.yaml b/charts/gardener-extension-provider-stackit/values.yaml index f72ae03f..fb60b55d 100644 --- a/charts/gardener-extension-provider-stackit/values.yaml +++ b/charts/gardener-extension-provider-stackit/values.yaml @@ -75,9 +75,6 @@ config: capacity: 25Gi provisioner: block-storage.csi.stackit.cloud volumeBindingMode: WaitForFirstConsumer - stackitAPIEndpoints: - # Must be base64 encoded - caBundle: "" registryCaches: # - server: reg.example.com # cache: reg-cache.example.com diff --git a/charts/internal/cloud-provider-config/templates/stackit-ca-bundle.yaml b/charts/internal/cloud-provider-config/templates/stackit-ca-bundle.yaml new file mode 100644 index 00000000..14185525 --- /dev/null +++ b/charts/internal/cloud-provider-config/templates/stackit-ca-bundle.yaml @@ -0,0 +1,12 @@ +{{- if .Values.CABundle }} +apiVersion: v1 +kind: Secret +metadata: + name: cloudprofile-ca-bundle + namespace: {{ .Release.Namespace }} +type: Opaque +data: + {{- if .Values.CABundle }} + stackit-ca.crt: {{ .Values.CABundle | b64enc }} + {{- end }} +{{- end }} diff --git a/charts/internal/cloud-provider-config/values.yaml b/charts/internal/cloud-provider-config/values.yaml index 2f537842..fd0fbbd7 100644 --- a/charts/internal/cloud-provider-config/values.yaml +++ b/charts/internal/cloud-provider-config/values.yaml @@ -1,4 +1,5 @@ stackitOnly: false +CABundle: "" # [Global] authUrl: fooURL domainName: fooDomain diff --git a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-driver-controller.yaml b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-driver-controller.yaml index df9581f8..ebab6de2 100644 --- a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-driver-controller.yaml +++ b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-driver-controller.yaml @@ -89,13 +89,9 @@ spec: mountPath: {{ .Values.socketPath }} - name: cloud-provider-config mountPath: /etc/config - # Host certificates are mounted to accommodate OpenStack endpoints that might be served with a certificate - # signed by a CA that is not globally trusted. - - mountPath: /usr/share/ca-certificates - name: usr-share-ca-certificates - readOnly: true - - mountPath: /etc/ssl - name: etc-ssl + - mountPath: /etc/ssl/certs/stackit-ca.crt + name: stackit-ca + subPath: stackit-ca.crt readOnly: true securityContext: allowPrivilegeEscalation: false @@ -344,15 +340,11 @@ spec: path: token name: shoot-access-csi-resizer optional: false - - name: usr-share-ca-certificates - hostPath: - path: /usr/share/ca-certificates - name: cloud-provider-config configMap: defaultMode: 420 name: {{ .Values.prefix }}-cloud-provider-config - # Host certificates are mounted to accommodate OpenStack endpoints that might be served with a certificate - # signed by a CA that is not globally trusted. - - name: etc-ssl - hostPath: - path: /etc/ssl + - name: stackit-ca + secret: + optional: true + secretName: cloudprofile-ca-bundle diff --git a/charts/internal/seed-controlplane/charts/stackit-cloud-controller-manager/templates/stackit-cloud-controller-manager.yaml b/charts/internal/seed-controlplane/charts/stackit-cloud-controller-manager/templates/stackit-cloud-controller-manager.yaml index 5099c90e..44168188 100644 --- a/charts/internal/seed-controlplane/charts/stackit-cloud-controller-manager/templates/stackit-cloud-controller-manager.yaml +++ b/charts/internal/seed-controlplane/charts/stackit-cloud-controller-manager/templates/stackit-cloud-controller-manager.yaml @@ -89,9 +89,9 @@ spec: name: config-volume - mountPath: /etc/serviceaccount name: sa-secret - - mountPath: /etc/ssl/certs/keystone-ca.crt - name: cloud-config-ca - subPath: keystone-ca.crt + - mountPath: /etc/ssl/certs/stackit-ca.crt + name: stackit-ca + subPath: stackit-ca.crt readOnly: true securityContext: allowPrivilegeEscalation: false @@ -111,9 +111,9 @@ spec: items: - key: "serviceaccount.json" path: "serviceaccount.json" - - name: cloud-config-ca + - name: stackit-ca secret: - secretName: cloud-provider-config + secretName: cloudprofile-ca-bundle optional: true - name: kubeconfig projected: diff --git a/charts/internal/seed-controlplane/charts/stackit-cloud-controller-manager/values.yaml b/charts/internal/seed-controlplane/charts/stackit-cloud-controller-manager/values.yaml index b4ed7ae0..4f60b830 100644 --- a/charts/internal/seed-controlplane/charts/stackit-cloud-controller-manager/values.yaml +++ b/charts/internal/seed-controlplane/charts/stackit-cloud-controller-manager/values.yaml @@ -5,9 +5,10 @@ config: stackitProjectID: foo stackitNetworkID: foo stackitRegion: foo - #loadBalancerApiUrl: foo.bar.example.com/v1 - #iaasApiUrl: foo.bar.example.com/v1 - #tokenUrl: foo.bar.example.com/v1 + loadBalancerApiUrl: "" + iaasApiUrl: "" + tokenUrl: "" + loadBalancerEmergencyToken: "" port: 10258 metricsPort: 9090 podAnnotations: {} diff --git a/hack/api-reference/api.md b/hack/api-reference/api.md index 77bdac1f..8981aef5 100644 --- a/hack/api-reference/api.md +++ b/hack/api-reference/api.md @@ -430,7 +430,7 @@ string (Optional) -

CABundle is the CA certificate bundle for API endpoints.
This field is currently ignored and reserved for future use.

+

CABundle is the CA certificate bundle for API endpoints.
This field is currently ignored and reserved for future use.
Deprecated: Will be removed soon. This field is not used anymore

diff --git a/pkg/admission/mutator/namespacedcloudprofile.go b/pkg/admission/mutator/namespacedcloudprofile.go index d66b6fa6..4c944a2e 100644 --- a/pkg/admission/mutator/namespacedcloudprofile.go +++ b/pkg/admission/mutator/namespacedcloudprofile.go @@ -64,11 +64,6 @@ func (p *namespacedCloudProfile) Mutate(_ context.Context, newObj, _ client.Obje statusConfig.APIEndpoints = specConfig.APIEndpoints } - // Overwrite CA bundle from spec - if specConfig.CABundle != nil { - statusConfig.CABundle = specConfig.CABundle - } - modifiedStatusConfig, err := json.Marshal(statusConfig) if err != nil { return err diff --git a/pkg/admission/mutator/namespacedcloudprofile_test.go b/pkg/admission/mutator/namespacedcloudprofile_test.go index 069efc06..04133e56 100644 --- a/pkg/admission/mutator/namespacedcloudprofile_test.go +++ b/pkg/admission/mutator/namespacedcloudprofile_test.go @@ -96,25 +96,6 @@ var _ = Describe("NamespacedCloudProfile Mutator", func() { Expect(mergedConfig.APIEndpoints.LoadBalancer).To(PointTo(Equal("https://custom-lb.example.com"))) }) - It("should correctly merge caBundle from spec", func() { - namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{ -"apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1", -"kind":"CloudProfileConfig", -"machineImages":[ - {"name":"image-1","versions":[{"version":"1.0","image":"image-name-1"}]} -]}`)} - namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{ -"apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1", -"kind":"CloudProfileConfig", -"caBundle":"-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----"}`)} - - Expect(namespacedCloudProfileMutator.Mutate(ctx, namespacedCloudProfile, nil)).To(Succeed()) - - mergedConfig, err := helper.CloudProfileConfigFromRawExtension(namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig) - Expect(err).ToNot(HaveOccurred()) - Expect(mergedConfig.CABundle).To(PointTo(Equal("-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----"))) - }) - It("should correctly merge extended machineImages", func() { namespacedCloudProfile.Status.CloudProfileSpec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{ "apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1", diff --git a/pkg/admission/validator/namespacedcloudprofile.go b/pkg/admission/validator/namespacedcloudprofile.go index e09b13c5..da3e2641 100644 --- a/pkg/admission/validator/namespacedcloudprofile.go +++ b/pkg/admission/validator/namespacedcloudprofile.go @@ -76,12 +76,11 @@ func (p *namespacedCloudProfile) validateNamespacedCloudProfileProviderConfig(pr }, MachineImages: providerConfig.MachineImages, APIEndpoints: providerConfig.APIEndpoints, - CABundle: providerConfig.CABundle, } if !equality.Semantic.DeepEqual(validationProviderConfig, providerConfig) { allErrs = append(allErrs, field.Forbidden( field.NewPath("spec.providerConfig"), - "must only set machineImages, stackitAPIEndpoints, and stackitCABundle", + "must only set machineImages and stackitAPIEndpoints", )) } diff --git a/pkg/admission/validator/namespacedcloudprofile_test.go b/pkg/admission/validator/namespacedcloudprofile_test.go index 2c63f349..b717fdcb 100644 --- a/pkg/admission/validator/namespacedcloudprofile_test.go +++ b/pkg/admission/validator/namespacedcloudprofile_test.go @@ -281,7 +281,7 @@ var _ = Describe("NamespacedCloudProfile Validator", func() { Expect(err).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{ "Type": Equal(field.ErrorTypeForbidden), "Field": Equal("spec.providerConfig"), - "Detail": Equal("must only set machineImages, stackitAPIEndpoints, and stackitCABundle"), + "Detail": Equal("must only set machineImages and stackitAPIEndpoints"), })))) }) @@ -297,24 +297,11 @@ var _ = Describe("NamespacedCloudProfile Validator", func() { Expect(namespacedCloudProfileValidator.Validate(ctx, namespacedCloudProfile, nil)).To(Succeed()) }) - It("should succeed for NamespacedCloudProfile specifying only caBundle", func() { - namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{ -"apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1", -"kind":"CloudProfileConfig", -"caBundle":"-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----" -}`)} - - Expect(fakeClient.Create(ctx, cloudProfile)).To(Succeed()) - - Expect(namespacedCloudProfileValidator.Validate(ctx, namespacedCloudProfile, nil)).To(Succeed()) - }) - - It("should succeed for NamespacedCloudProfile specifying apiEndpoints, caBundle, and machineImages together", func() { + It("should succeed for NamespacedCloudProfile specifying apiEndpoints, and machineImages together", func() { namespacedCloudProfile.Spec.ProviderConfig = &runtime.RawExtension{Raw: []byte(`{ "apiVersion":"stackit.provider.extensions.gardener.cloud/v1alpha1", "kind":"CloudProfileConfig", "apiEndpoints":{"iaas":"https://custom-iaas.example.com"}, -"caBundle":"-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----", "machineImages":[ {"name":"image-1","versions":[{"version":"1.0","image":"image-name-1","regions":[{"name":"image-region-1","id":"id-img-reg-1"}]}]} ] diff --git a/pkg/apis/stackit/v1alpha1/types_cloudprofile.go b/pkg/apis/stackit/v1alpha1/types_cloudprofile.go index 38a3ad36..bc565426 100644 --- a/pkg/apis/stackit/v1alpha1/types_cloudprofile.go +++ b/pkg/apis/stackit/v1alpha1/types_cloudprofile.go @@ -33,6 +33,8 @@ type CloudProfileConfig struct { APIEndpoints *APIEndpoints `json:"apiEndpoints,omitempty"` // CABundle is the CA certificate bundle for API endpoints. // This field is currently ignored and reserved for future use. + // + // Deprecated: Will be removed soon. This field is not used anymore // +optional CABundle *string `json:"caBundle,omitempty"` // Bastion are the bastion options for configuring the bastion server diff --git a/pkg/controller/controlplane/valuesprovider.go b/pkg/controller/controlplane/valuesprovider.go index 180ac234..d6a9565b 100644 --- a/pkg/controller/controlplane/valuesprovider.go +++ b/pkg/controller/controlplane/valuesprovider.go @@ -135,6 +135,7 @@ var ( Objects: []*chart.Object{ {Type: &corev1.Secret{}, Name: openstack.CloudProviderConfigName}, {Type: &corev1.Secret{}, Name: openstack.CloudProviderDiskConfigName}, + {Type: &corev1.Secret{}, Name: openstack.CloudProfileCASecretName}, }, } @@ -681,6 +682,16 @@ func getConfigChartValues( } } + // Deploy the cloudprofile-ca-bundle when at least one of stackit-mcm, stackit-csi or stackit-ccm is deployed + if feature.UseStackitMachineControllerManager(cluster) || + getCSIDriver(controlPlaneConfig) == stackitv1alpha1.STACKIT || + getCCMController(controlPlaneConfig) == stackitv1alpha1.STACKIT { + if cluster.CloudProfile != nil && cluster.CloudProfile.Spec.CABundle != nil { + caBundle := ptr.Deref(cluster.CloudProfile.Spec.CABundle, "") + values["CABundle"] = caBundle + } + } + return values, nil } diff --git a/pkg/openstack/types.go b/pkg/openstack/types.go index fc3a090c..6b38156b 100644 --- a/pkg/openstack/types.go +++ b/pkg/openstack/types.go @@ -57,6 +57,8 @@ const ( // DNSCABundle is a constant for the key in a DNS secret that holds the Openstack CA Bundle for the KeyStone server. DNSCABundle = "OS_CACERT" + // CloudProfileCASecretName is the name of the secret containing the CloudPorifle caBundle + CloudProfileCASecretName = "cloudprofile-ca-bundle" // CloudProviderConfigName is the name of the secret containing the cloud provider config. CloudProviderConfigName = "cloud-provider-config" // CloudProviderDiskConfigName is the name of the secret containing the cloud provider config for disk/volume handling. It is used by kube-controller-manager. diff --git a/pkg/stackit/client/dns.go b/pkg/stackit/client/dns.go index e28689de..ea786e98 100644 --- a/pkg/stackit/client/dns.go +++ b/pkg/stackit/client/dns.go @@ -13,8 +13,11 @@ import ( "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit" ) -func NewDNSClient(_ context.Context, endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials) (DNSClient, error) { - options := clientOptions(endpoints, credentials) +func NewDNSClient(_ context.Context, endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials, caBundle string) (DNSClient, error) { + options, err := clientOptions(endpoints, credentials, caBundle) + if err != nil { + return nil, err + } if endpoints.DNS != nil { options = append(options, sdkconfig.WithEndpoint(*endpoints.DNS)) @@ -24,6 +27,7 @@ func NewDNSClient(_ context.Context, endpoints stackitv1alpha1.APIEndpoints, cre if err != nil { return nil, err } + return &dnsClient{ api: apiClient.DefaultAPI, projectID: credentials.ProjectID, diff --git a/pkg/stackit/client/factory.go b/pkg/stackit/client/factory.go index 543123a9..cbece73e 100644 --- a/pkg/stackit/client/factory.go +++ b/pkg/stackit/client/factory.go @@ -2,6 +2,10 @@ package client import ( "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" extensionscontroller "github.com/gardener/gardener/extensions/pkg/controller" sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config" @@ -33,18 +37,25 @@ type Factory interface { type factory struct { StackitRegion string StackitAPIEndpoints stackitv1alpha1.APIEndpoints + CABundleB64 string } func New(region string, cluster *extensionscontroller.Cluster) Factory { var apiEndpoints stackitv1alpha1.APIEndpoints + var caBundle string if cloudProfileConfig, err := helper.CloudProfileConfigFromCluster(cluster); err == nil { apiEndpoints = ptr.Deref(cloudProfileConfig.APIEndpoints, stackitv1alpha1.APIEndpoints{}) } + if cluster.CloudProfile != nil && cluster.CloudProfile.Spec.CABundle != nil { + caBundle = ptr.Deref(cluster.CloudProfile.Spec.CABundle, "") + } + return &factory{ StackitRegion: region, StackitAPIEndpoints: apiEndpoints, + CABundleB64: caBundle, } } @@ -54,7 +65,7 @@ func (f factory) LoadBalancing(ctx context.Context, c client.Client, secretRef c return nil, err } - return NewLoadBalancingClient(ctx, f.StackitRegion, f.StackitAPIEndpoints, credentials) + return NewLoadBalancingClient(ctx, f.StackitRegion, f.StackitAPIEndpoints, credentials, f.CABundleB64) } func (f factory) IaaS(ctx context.Context, c client.Client, secretRef corev1.SecretReference) (IaaSClient, error) { @@ -63,7 +74,7 @@ func (f factory) IaaS(ctx context.Context, c client.Client, secretRef corev1.Sec return nil, err } - return NewIaaSClient(f.StackitRegion, f.StackitAPIEndpoints, credentials) + return NewIaaSClient(f.StackitRegion, f.StackitAPIEndpoints, credentials, f.CABundleB64) } func (f factory) DNS(ctx context.Context, c client.Client, secretRef corev1.SecretReference) (DNSClient, error) { @@ -72,10 +83,28 @@ func (f factory) DNS(ctx context.Context, c client.Client, secretRef corev1.Secr return nil, err } - return NewDNSClient(ctx, f.StackitAPIEndpoints, credentials) + return NewDNSClient(ctx, f.StackitAPIEndpoints, credentials, f.CABundleB64) +} + +// newHTTPClientWithCustomCA creates an http.Client with a custom CA +func newHTTPClientWithCustomCA(caBundle []byte) (*http.Client, error) { + caCertPool, err := x509.SystemCertPool() + if err != nil { + // we could also fall back here and use an empty pool via x509.NewCertPool() + return nil, fmt.Errorf("failed to load system cert pool: %w", err) + } + if ok := caCertPool.AppendCertsFromPEM(caBundle); !ok { + return nil, fmt.Errorf("failed to append CA bundle to cert pool") + } + + return &http.Client{Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: caCertPool, + }, + }}, nil } -func clientOptions(endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials) []sdkconfig.ConfigurationOption { +func clientOptions(endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials, caBundle string) ([]sdkconfig.ConfigurationOption, error) { result := []sdkconfig.ConfigurationOption{ sdkconfig.WithUserAgent(UserAgent), sdkconfig.WithServiceAccountKey(credentials.SaKeyJSON), @@ -85,5 +114,13 @@ func clientOptions(endpoints stackitv1alpha1.APIEndpoints, credentials *stackit. result = append(result, sdkconfig.WithTokenEndpoint(*endpoints.TokenEndpoint)) } - return result + if caBundle != "" { + customHttpClient, err := newHTTPClientWithCustomCA([]byte(caBundle)) + if err != nil { + return nil, err + } + result = append(result, sdkconfig.WithHTTPClient(customHttpClient)) + } + + return result, nil } diff --git a/pkg/stackit/client/iaas.go b/pkg/stackit/client/iaas.go index f941ac5c..628f1a96 100644 --- a/pkg/stackit/client/iaas.go +++ b/pkg/stackit/client/iaas.go @@ -126,9 +126,11 @@ func (c iaasClient) GetNetworkByName(ctx context.Context, name string) ([]iaas.N return filteredNetworks, nil } -func NewIaaSClient(region string, endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials) (IaaSClient, error) { - options := clientOptions(endpoints, credentials) - +func NewIaaSClient(region string, endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials, caBundle string) (IaaSClient, error) { + options, err := clientOptions(endpoints, credentials, caBundle) + if err != nil { + return nil, err + } if endpoints.IaaS != nil { options = append(options, sdkconfig.WithEndpoint(*endpoints.IaaS)) } @@ -141,6 +143,7 @@ func NewIaaSClient(region string, endpoints stackitv1alpha1.APIEndpoints, creden if err != nil { return nil, err } + return &iaasClient{ Client: apiClient.DefaultAPI, projectID: credentials.ProjectID, diff --git a/pkg/stackit/client/loadbalancing.go b/pkg/stackit/client/loadbalancing.go index 66bb7e68..c25f7477 100644 --- a/pkg/stackit/client/loadbalancing.go +++ b/pkg/stackit/client/loadbalancing.go @@ -26,9 +26,11 @@ type loadBalancingClient struct { region string } -func NewLoadBalancingClient(_ context.Context, region string, endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials) (LoadBalancingClient, error) { - options := clientOptions(endpoints, credentials) - +func NewLoadBalancingClient(_ context.Context, region string, endpoints stackitv1alpha1.APIEndpoints, credentials *stackit.Credentials, caBundle string) (LoadBalancingClient, error) { + options, err := clientOptions(endpoints, credentials, caBundle) + if err != nil { + return nil, err + } if endpoints.LoadBalancer != nil { options = append(options, sdkconfig.WithEndpoint(*endpoints.LoadBalancer)) } diff --git a/pkg/webhook/controlplane/ensurer.go b/pkg/webhook/controlplane/ensurer.go index 2a1a2adc..4e329a70 100644 --- a/pkg/webhook/controlplane/ensurer.go +++ b/pkg/webhook/controlplane/ensurer.go @@ -34,6 +34,11 @@ import ( "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/webhook/controlplane/registrycache" ) +const ( + CASecretName = "cloudprofile-ca-bundle" + CAVolumeName = "stackit-ca" +) + // NewEnsurer creates a new controlplane ensurer. func NewEnsurer(regCaches []config.RegistryCacheConfiguration, logger logr.Logger) genericmutator.Ensurer { return &ensurer{ @@ -83,15 +88,32 @@ func (e *ensurer) EnsureMachineControllerManagerDeployment(ctx context.Context, apiEndpoints := ptr.Deref(cloudProfileConfig.APIEndpoints, stackitv1alpha1.APIEndpoints{}) + if cluster.CloudProfile != nil && cluster.CloudProfile.Spec.CABundle != nil { + newObj.Spec.Template.Spec.Volumes = extensionswebhook.EnsureVolumeWithName(newObj.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: CAVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: CASecretName, + }, + }, + }) + sidecarContainer.VolumeMounts = extensionswebhook.EnsureVolumeMountWithName(sidecarContainer.VolumeMounts, corev1.VolumeMount{ + Name: CAVolumeName, + MountPath: "/etc/ssl/certs/stackit-ca.crt", + SubPath: "stackit-ca.crt", + ReadOnly: true, + }) + } + sidecarContainer.Env = []corev1.EnvVar{} if apiEndpoints.IaaS != nil { - sidecarContainer.Env = append(sidecarContainer.Env, corev1.EnvVar{ + sidecarContainer.Env = extensionswebhook.EnsureEnvVarWithName(sidecarContainer.Env, corev1.EnvVar{ Name: "STACKIT_IAAS_ENDPOINT", Value: *apiEndpoints.IaaS, }) } if apiEndpoints.TokenEndpoint != nil { - sidecarContainer.Env = append(sidecarContainer.Env, corev1.EnvVar{ + sidecarContainer.Env = extensionswebhook.EnsureEnvVarWithName(sidecarContainer.Env, corev1.EnvVar{ Name: "STACKIT_TOKEN_BASEURL", Value: *apiEndpoints.TokenEndpoint, }) diff --git a/pkg/webhook/controlplane/ensurer_test.go b/pkg/webhook/controlplane/ensurer_test.go index fd55dbc9..f1975176 100644 --- a/pkg/webhook/controlplane/ensurer_test.go +++ b/pkg/webhook/controlplane/ensurer_test.go @@ -6,9 +6,16 @@ package controlplane import ( "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" "encoding/json" + "encoding/pem" + "math/big" "strings" "testing" + "time" "github.com/Masterminds/semver/v3" "github.com/coreos/go-systemd/v22/unit" @@ -114,6 +121,27 @@ var _ = Describe("Ensurer", func() { }, }, ) + eContextK8s127WithSTACKITMCMANDCUSTOMCA = gcontext.NewInternalGardenContext( + &extensionscontroller.Cluster{ + CloudProfile: &gardencorev1beta1.CloudProfile{ + Spec: gardencorev1beta1.CloudProfileSpec{ + CABundle: new(generateRootCA()), + }, + }, + Shoot: &gardencorev1beta1.Shoot{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + feature.ShootUseSTACKITMachineControllerManager: "true", + }, + }, + Spec: gardencorev1beta1.ShootSpec{ + Kubernetes: gardencorev1beta1.Kubernetes{ + Version: "1.27.1", + }, + }, + }, + }, + ) eContextK8s127WithSTACKITMCMAndAPIEndpoints = gcontext.NewInternalGardenContext( &extensionscontroller.Cluster{ CloudProfile: &gardencorev1beta1.CloudProfile{ @@ -576,6 +604,28 @@ WantedBy=multi-user.target expectedContainer.Env = []corev1.EnvVar{} Expect(deployment.Spec.Template.Spec.Containers).To(ConsistOf(expectedContainer)) }) + + It("should add cloudprofile-ca-bundle to Deployment and sidecar when CloudProfile CA Bundle is set", func() { + Expect(deployment.Spec.Template.Spec.Containers).To(BeEmpty()) + Expect(ensurer.EnsureMachineControllerManagerDeployment(context.TODO(), eContextK8s127WithSTACKITMCMANDCUSTOMCA, deployment, nil)).To(Succeed()) + expectedContainer := machinecontrollermanager.ProviderSidecarContainer(shoot, deployment.Namespace, "provider-stackit", "foo:bar") + expectedContainer.Env = []corev1.EnvVar{} + expectedContainer.VolumeMounts = append(expectedContainer.VolumeMounts, corev1.VolumeMount{ + Name: CAVolumeName, + MountPath: "/etc/ssl/certs/stackit-ca.crt", + SubPath: "stackit-ca.crt", + ReadOnly: true, + }) + Expect(deployment.Spec.Template.Spec.Containers).To(ConsistOf(expectedContainer)) + Expect(deployment.Spec.Template.Spec.Volumes).To(ConsistOf(corev1.Volume{ + Name: CAVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: CASecretName, + }, + }, + })) + }) }) Describe("#EnsureMachineControllerManagerDeployment with stackit and custom env vars", func() { @@ -688,3 +738,34 @@ func encode(obj runtime.Object) []byte { data, _ := json.Marshal(obj) return data } + +func generateRootCA() string { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return "" + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "Minimalist Root CA", + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + } + + certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv) + if err != nil { + return "" + } + + pemBlock := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + return string(pemBlock) +} diff --git a/test/integration/infrastructure/stackit/infrastructure_test.go b/test/integration/infrastructure/stackit/infrastructure_test.go index c861c827..569db7db 100644 --- a/test/integration/infrastructure/stackit/infrastructure_test.go +++ b/test/integration/infrastructure/stackit/infrastructure_test.go @@ -132,7 +132,7 @@ var _ = BeforeSuite(func() { // TODO: Consider creating manual STACKIT NLB to ensure stackit NLB deletion works DeferCleanup(testutils.WithFeatureGate(feature.MutableGate, feature.EnsureSTACKITLBDeletion, false)) - iaasClient, err = stackitclient.NewIaaSClient(*region, endpoints, credentials) + iaasClient, err = stackitclient.NewIaaSClient(*region, endpoints, credentials, "") Expect(err).NotTo(HaveOccurred()) repoRoot := filepath.Join("..", "..", "..", "..") diff --git a/test/integration/selfhostedshootexposure/selfhostedshootexposure_test.go b/test/integration/selfhostedshootexposure/selfhostedshootexposure_test.go index d40e7082..2eafc29f 100644 --- a/test/integration/selfhostedshootexposure/selfhostedshootexposure_test.go +++ b/test/integration/selfhostedshootexposure/selfhostedshootexposure_test.go @@ -110,10 +110,10 @@ var _ = BeforeSuite(func() { Expect(*region).NotTo(BeEmpty()) Expect(validateEnvs()).To(Succeed()) - iaasClient, err = stackitclient.NewIaaSClient(*region, endpoints, credentials) + iaasClient, err = stackitclient.NewIaaSClient(*region, endpoints, credentials, "") Expect(err).NotTo(HaveOccurred()) - lbClient, err = stackitclient.NewLoadBalancingClient(ctx, *region, endpoints, credentials) + lbClient, err = stackitclient.NewLoadBalancingClient(ctx, *region, endpoints, credentials, "") Expect(err).NotTo(HaveOccurred()) repoRoot := filepath.Join("..", "..", "..")