diff --git a/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/Chart.yaml b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/Chart.yaml new file mode 100644 index 00000000..8775571a --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +name: stackit-pod-identity-webhook +version: 0.1.0 diff --git a/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/deployment.yaml b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/deployment.yaml new file mode 100644 index 00000000..0c716418 --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/deployment.yaml @@ -0,0 +1,101 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: stackit-pod-identity-webhook + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: stackit-pod-identity-webhook + high-availability-config.resources.gardener.cloud/type: server +spec: + revisionHistoryLimit: 2 + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: stackit-pod-identity-webhook + template: + metadata: + labels: + app.kubernetes.io/name: stackit-pod-identity-webhook + workload-identity.stackit.cloud/skip-pod-identity-webhook: "true" + gardener.cloud/role: controlplane + networking.gardener.cloud/to-dns: allowed + networking.resources.gardener.cloud/to-kube-apiserver-tcp-443: allowed + spec: + automountServiceAccountToken: false + podSecurityContext: + runAsNonRoot: true + runAsUser: 1239 + runAsGroup: 1239 + fsGroup: 1239 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + priorityClassName: gardener-system-200 + containers: + - name: stackit-pod-identity-webhook + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + image: {{ index .Values.images "stackit-pod-identity-webhook" }} + args: + - --cert-dir=/etc/webhook/certs + - --port={{ .Values.webhook.port }} + - --metrics-bind-address=:{{ .Values.metrics.port }} + env: + - name: KUBECONFIG + value: /var/run/secrets/gardener.cloud/shoot/generic-kubeconfig/kubeconfig + ports: + - name: https + containerPort: {{ .Values.webhook.port }} + protocol: TCP + - name: metrics + containerPort: {{ .Values.metrics.port }} + protocol: TCP + - name: health + containerPort: 8081 + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: health + readinessProbe: + httpGet: + path: /readyz + port: health + resources: + requests: + cpu: 50m + memory: 64Mi + volumeMounts: + - name: certs + mountPath: /etc/webhook/certs + readOnly: true + - mountPath: /var/run/secrets/gardener.cloud/shoot/generic-kubeconfig + name: kubeconfig + readOnly: true + volumes: + - name: certs + secret: + secretName: {{ .Values.webhook.tlsSecretName }} + - name: kubeconfig + projected: + defaultMode: 420 + sources: + - secret: + items: + - key: kubeconfig + path: kubeconfig + name: {{ .Values.global.genericTokenKubeconfigSecretName }} + optional: false + - secret: + items: + - key: token + path: token + name: shoot-access-stackit-pod-identity-webhook + optional: false \ No newline at end of file diff --git a/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/poddisruptionbudget.yaml b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/poddisruptionbudget.yaml new file mode 100644 index 00000000..17cd1a17 --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/poddisruptionbudget.yaml @@ -0,0 +1,13 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: stackit-pod-identity-webhook + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: stackit-pod-identity-webhook +spec: + maxUnavailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: stackit-pod-identity-webhook + unhealthyPodEvictionPolicy: AlwaysAllow diff --git a/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/service.yaml b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/service.yaml new file mode 100644 index 00000000..cb20600c --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/service.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Service +metadata: + name: stackit-pod-identity-webhook + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: stackit-pod-identity-webhook + endpoint-slice-hints.resources.gardener.cloud/consider: "true" + annotations: + networking.resources.gardener.cloud/from-all-webhook-targets-allowed-ports: '[{"protocol":"TCP","port":{{ .Values.webhook.port }}}]' + networking.resources.gardener.cloud/from-all-scrape-targets-allowed-ports: '[{"protocol":"TCP","port":{{ .Values.metrics.port }}}]' + service.kubernetes.io/topology-mode: auto +spec: + type: ClusterIP + ports: + - port: 443 + targetPort: {{ .Values.webhook.port }} + protocol: TCP + name: https + - port: {{ .Values.metrics.port }} + targetPort: {{ .Values.metrics.port }} + protocol: TCP + name: metrics + selector: + app.kubernetes.io/name: stackit-pod-identity-webhook + trafficDistribution: PreferClose diff --git a/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/servicemonitor.yaml b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/servicemonitor.yaml new file mode 100644 index 00000000..7c98f138 --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/servicemonitor.yaml @@ -0,0 +1,14 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: stackit-pod-identity-webhook + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: stackit-pod-identity-webhook + prometheus: shoot +spec: + selector: + matchLabels: + app.kubernetes.io/name: stackit-pod-identity-webhook + endpoints: + - port: metrics \ No newline at end of file diff --git a/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/vpa.yaml b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/vpa.yaml new file mode 100644 index 00000000..2ab5d1ca --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/templates/vpa.yaml @@ -0,0 +1,25 @@ +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + name: stackit-pod-identity-webhook + namespace: {{ .Release.Namespace }} +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: stackit-pod-identity-webhook + updatePolicy: + updateMode: InPlaceOrRecreate + resourcePolicy: + containerPolicies: + - containerName: stackit-pod-identity-webhook + minAllowed: + cpu: {{ .Values.vpa.resourcePolicy.minAllowed.cpu }} + memory: {{ .Values.vpa.resourcePolicy.minAllowed.memory }} + maxAllowed: + cpu: {{ .Values.vpa.resourcePolicy.maxAllowed.cpu }} + memory: {{ .Values.vpa.resourcePolicy.maxAllowed.memory }} + controlledValues: RequestsOnly + controlledResources: + - cpu + - memory \ No newline at end of file diff --git a/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/values.yaml b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/values.yaml new file mode 100644 index 00000000..5bc60eae --- /dev/null +++ b/charts/internal/seed-controlplane/charts/stackit-pod-identity-webhook/values.yaml @@ -0,0 +1,21 @@ +replicaCount: 2 + +images: + stackit-pod-identity-webhook: image-repository:image-tag + +webhook: + port: 9443 + # The secret name containing tls.crt and tls.key for the webhook server + tlsSecretName: "stackit-pod-identity-webhook-certs" + +vpa: + resourcePolicy: + maxAllowed: + cpu: 1 + memory: 512Mi + minAllowed: + cpu: 50m + memory: 64Mi + +metrics: + port: 8080 diff --git a/charts/internal/seed-controlplane/requirements.yaml b/charts/internal/seed-controlplane/requirements.yaml index efa98d6c..b672cf39 100644 --- a/charts/internal/seed-controlplane/requirements.yaml +++ b/charts/internal/seed-controlplane/requirements.yaml @@ -19,3 +19,7 @@ dependencies: repository: http://localhost:10191 version: 0.1.0 condition: stackit-alb-controller-manager.enabled +- name: stackit-pod-identity-webhook + repository: http://localhost:10191 + version: 0.1.0 + condition: stackit-pod-identity-webhook.enabled diff --git a/charts/internal/seed-controlplane/values.yaml b/charts/internal/seed-controlplane/values.yaml index 5164e707..8cf3a7ad 100644 --- a/charts/internal/seed-controlplane/values.yaml +++ b/charts/internal/seed-controlplane/values.yaml @@ -8,3 +8,5 @@ csi-driver-controller: enabled: true stackit-alb-controller-manager: enabled: false +stackit-pod-identity-webhook: + enabled: false \ No newline at end of file diff --git a/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/Chart.yaml b/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/Chart.yaml new file mode 100644 index 00000000..8775571a --- /dev/null +++ b/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +name: stackit-pod-identity-webhook +version: 0.1.0 diff --git a/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/templates/mutatingwebhookconfiguration.yaml b/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/templates/mutatingwebhookconfiguration.yaml new file mode 100644 index 00000000..2e7828be --- /dev/null +++ b/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/templates/mutatingwebhookconfiguration.yaml @@ -0,0 +1,32 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: stackit-pod-identity-webhook + labels: + app.kubernetes.io/name: stackit-pod-identity-webhook +webhooks: + - name: stackit-pod-identity-webhook.stackit.cloud + clientConfig: + url: {{ .Values.webhook.url | quote }} + caBundle: {{ .Values.webhook.caBundle | quote }} + rules: + - operations: ["CREATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: 3 # Do not block control-plane API calls too long. + failurePolicy: Fail + namespaceSelector: + matchExpressions: + - key: gardener.cloud/purpose # see https://github.com/gardener/gardener/blob/master/docs/usage/shoot/shoot_status.md#constraints + operator: NotIn + values: + - kube-system + - key: workload-identity.stackit.cloud/skip-pod-identity-webhook + operator: DoesNotExist + objectSelector: + matchExpressions: + - key: workload-identity.stackit.cloud/skip-pod-identity-webhook + operator: DoesNotExist \ No newline at end of file diff --git a/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/templates/rbac.yaml b/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/templates/rbac.yaml new file mode 100644 index 00000000..2414fce9 --- /dev/null +++ b/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/templates/rbac.yaml @@ -0,0 +1,21 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: extensions.gardener.cloud:provider-stackit:stackit-pod-identity-webhook +rules: +- apiGroups: [""] + resources: ["serviceaccounts"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: extensions.gardener.cloud:provider-stackit:stackit-pod-identity-webhook +subjects: +- kind: ServiceAccount # from shoot access secret + name: stackit-pod-identity-webhook + namespace: kube-system +roleRef: + kind: ClusterRole + name: extensions.gardener.cloud:provider-stackit:stackit-pod-identity-webhook + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/values.yaml b/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/values.yaml new file mode 100644 index 00000000..975eb603 --- /dev/null +++ b/charts/internal/shoot-system-components/charts/stackit-pod-identity-webhook/values.yaml @@ -0,0 +1,2 @@ +webhook: + caBundle: "" # will be set by valuesprovider diff --git a/charts/internal/shoot-system-components/requirements.yaml b/charts/internal/shoot-system-components/requirements.yaml index aed217a2..c78294bc 100644 --- a/charts/internal/shoot-system-components/requirements.yaml +++ b/charts/internal/shoot-system-components/requirements.yaml @@ -15,3 +15,7 @@ dependencies: repository: http://localhost:10191 version: 0.1.0 condition: stackit-blockstorage-csi-driver.enabled +- name: stackit-pod-identity-webhook + repository: http://localhost:10191 + version: 0.1.0 + condition: stackit-pod-identity-webhook.enabled diff --git a/charts/internal/shoot-system-components/values.yaml b/charts/internal/shoot-system-components/values.yaml index 2c397012..d91f54b6 100644 --- a/charts/internal/shoot-system-components/values.yaml +++ b/charts/internal/shoot-system-components/values.yaml @@ -4,3 +4,5 @@ stackit-cloud-controller-manager: enabled: true csi-driver-node: enabled: true +stackit-pod-identity-webhook: + enabled: false \ No newline at end of file diff --git a/go.mod b/go.mod index 20a6e0c1..6a22e416 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/onsi/gomega v1.39.1 github.com/pelletier/go-toml/v2 v2.3.0 github.com/pkg/errors v0.9.1 + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.89.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stackitcloud/stackit-sdk-go/core v0.23.0 @@ -151,7 +152,6 @@ require ( github.com/perses/perses v0.51.0 // indirect github.com/perses/perses-operator v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.89.0 // indirect github.com/prometheus/alertmanager v0.29.0 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect diff --git a/imagevector/images.go b/imagevector/images.go index 1b8da504..c7e393d5 100644 --- a/imagevector/images.go +++ b/imagevector/images.go @@ -35,4 +35,6 @@ const ( ImageNameStackitAlbControllerManager = "stackit-alb-controller-manager" // ImageNameStackitCloudControllerManager is a constant for an image in the image vector with name 'stackit-cloud-controller-manager'. ImageNameStackitCloudControllerManager = "stackit-cloud-controller-manager" + // ImageNameStackitPodIdentityWebhook is a constant for an image in the image vector with name 'stackit-pod-identity-webhook'. + ImageNameStackitPodIdentityWebhook = "stackit-pod-identity-webhook" ) diff --git a/imagevector/images.yaml b/imagevector/images.yaml index ac1212a6..f3835aea 100644 --- a/imagevector/images.yaml +++ b/imagevector/images.yaml @@ -135,3 +135,7 @@ images: - name: stackit-alb-controller-manager repository: reg3.infra.ske.eu01.stackit.cloud/temp/alb-controller-manager tag: "1245" +- name: stackit-pod-identity-webhook + sourceRepository: github.com/stackitcloud/stackit-pod-identity-webhook + repository: ghcr.io/stackitcloud/stackit-pod-identity-webhook + tag: "v0.1.0" diff --git a/pkg/controller/controlplane/valuesprovider.go b/pkg/controller/controlplane/valuesprovider.go index a2e22f7f..19efca75 100644 --- a/pkg/controller/controlplane/valuesprovider.go +++ b/pkg/controller/controlplane/valuesprovider.go @@ -10,7 +10,6 @@ import ( "fmt" "maps" "path/filepath" - "sort" "strings" "github.com/Masterminds/semver/v3" @@ -28,6 +27,8 @@ import ( kutil "github.com/gardener/gardener/pkg/utils/kubernetes" secretutils "github.com/gardener/gardener/pkg/utils/secrets" secretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" @@ -37,13 +38,10 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" vpaautoscalingv1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1" "k8s.io/utils/ptr" k8sclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -59,8 +57,9 @@ import ( ) const ( - caNameControlPlane = "ca-" + openstack.Name + "-controlplane" - cloudControllerManagerServerName = openstack.CloudControllerManagerName + "-server" + caNameControlPlane = "ca-" + openstack.Name + "-controlplane" + cloudControllerManagerServerName = openstack.CloudControllerManagerName + "-server" + stackitPodIdentityWebhookServerName = stackit.PodIdentityWebhookName + "-server" CSIStackitPrefix = "stackit-blockstorage" @@ -103,6 +102,16 @@ func secretConfigsFunc(namespace string) []extensionssecretmanager.SecretConfigW }, Options: []secretsmanager.GenerateOption{secretsmanager.SignedByCA(caNameControlPlane)}, }, + { + Config: &secretutils.CertificateSecretConfig{ + Name: stackitPodIdentityWebhookServerName, + CommonName: stackit.PodIdentityWebhookName, + DNSNames: kutil.DNSNamesForService(stackit.PodIdentityWebhookName, namespace), + CertType: secretutils.ServerCert, + SkipPublishingCACertificate: true, + }, + Options: []secretsmanager.GenerateOption{secretsmanager.SignedByCA(caNameControlPlane)}, + }, } } @@ -114,15 +123,10 @@ func shootAccessSecretsFunc(namespace string) []*gutil.AccessSecret { gutil.NewShootAccessSecret(openstack.CSISnapshotterName, namespace), gutil.NewShootAccessSecret(openstack.CSIResizerName, namespace), gutil.NewShootAccessSecret(openstack.CSISnapshotControllerName, namespace), + gutil.NewShootAccessSecret(stackit.PodIdentityWebhookName, namespace), } } -func makeUnstructured(gvk schema.GroupVersionKind) *unstructured.Unstructured { - obj := &unstructured.Unstructured{} - obj.SetGroupVersionKind(gvk) - return obj -} - var ( configChart = &chart.Chart{ Name: openstack.CloudProviderConfigName, @@ -207,6 +211,17 @@ var ( {Type: &vpaautoscalingv1.VerticalPodAutoscaler{}, Name: openstack.STACKITALBControllerManagerName}, }, }, + { + Name: stackit.PodIdentityWebhookName, + Images: []string{imagevector.ImageNameStackitPodIdentityWebhook}, + Objects: []*chart.Object{ + {Type: &appsv1.Deployment{}, Name: stackit.PodIdentityWebhookName}, + {Type: &policyv1.PodDisruptionBudget{}, Name: stackit.PodIdentityWebhookName}, + {Type: &corev1.Service{}, Name: stackit.PodIdentityWebhookName}, + {Type: &vpaautoscalingv1.VerticalPodAutoscaler{}, Name: stackit.PodIdentityWebhookName}, + {Type: &monitoringv1.ServiceMonitor{}, Name: stackit.PodIdentityWebhookName}, + }, + }, }, } @@ -306,6 +321,14 @@ var ( {Type: &rbacv1.RoleBinding{}, Name: openstack.UsernamePrefix + openstack.CSIResizerName}, }, }, + { + Name: stackit.PodIdentityWebhookName, + Objects: []*chart.Object{ + {Type: &admissionregistrationv1.MutatingWebhookConfiguration{}, Name: stackit.PodIdentityWebhookName}, + {Type: &rbacv1.ClusterRole{}, Name: "extensions.gardener.cloud:provider-stackit:stackit-pod-identity-webhook"}, + {Type: &rbacv1.ClusterRoleBinding{}, Name: "extensions.gardener.cloud:provider-stackit:stackit-pod-identity-webhook"}, + }, + }, }, } @@ -478,7 +501,7 @@ func (vp *valuesProvider) GetControlPlaneShootChartValues( ctx context.Context, cp *extensionsv1alpha1.ControlPlane, cluster *extensionscontroller.Cluster, - _ secretsmanager.Reader, + secretsReader secretsmanager.Reader, _ map[string]string, ) (map[string]any, error) { // Decode providerConfig @@ -493,7 +516,7 @@ func (vp *valuesProvider) GetControlPlaneShootChartValues( if err != nil { return nil, err } - return vp.getControlPlaneShootChartValues(ctx, cpConfig, cp, cloudProfileConfig, cluster) + return vp.getControlPlaneShootChartValues(ctx, cpConfig, cp, cloudProfileConfig, cluster, secretsReader) } // GetStorageClassesChartValues returns the values for the shoot storageclasses chart applied by the generic actuator. @@ -708,6 +731,11 @@ func (vp *valuesProvider) getControlPlaneChartValues(ctx context.Context, cpConf } } + podIdentityWebhook, err := getPodIdentityWebhookChartValues(cluster, secretsReader, scaledDown) + if err != nil { + return nil, err + } + storageCSIDriver := getCSIDriver(cpConfig) switch storageCSIDriver { case stackitv1alpha1.OPENSTACK: @@ -732,6 +760,7 @@ func (vp *valuesProvider) getControlPlaneChartValues(ctx context.Context, cpConf }, openstack.CloudControllerManagerName: ccm, openstack.STACKITCloudControllerManagerName: stackitccm, + stackit.PodIdentityWebhookName: podIdentityWebhook, }) if vp.deployALBIngressController { @@ -1028,7 +1057,7 @@ func DeploySTACKITALB(cpConfig *stackitv1alpha1.ControlPlaneConfig) bool { } // getControlPlaneShootChartValues collects and returns the control plane shoot chart values. -func (vp *valuesProvider) getControlPlaneShootChartValues(ctx context.Context, cpConfig *stackitv1alpha1.ControlPlaneConfig, cp *extensionsv1alpha1.ControlPlane, cloudProfileConfig *stackitv1alpha1.CloudProfileConfig, cluster *extensionscontroller.Cluster) (map[string]any, error) { +func (vp *valuesProvider) getControlPlaneShootChartValues(ctx context.Context, cpConfig *stackitv1alpha1.ControlPlaneConfig, cp *extensionsv1alpha1.ControlPlane, cloudProfileConfig *stackitv1alpha1.CloudProfileConfig, cluster *extensionscontroller.Cluster, secretsReader secretsmanager.Reader) (map[string]any, error) { var csiNodeDriverValues map[string]any values := make(map[string]any) @@ -1056,8 +1085,14 @@ func (vp *valuesProvider) getControlPlaneShootChartValues(ctx context.Context, c return nil, err } + podIdentityWebhook, err := vp.getPodIdentityWebhookShootChartValues(cp.Namespace, secretsReader) + if err != nil { + return nil, err + } + maps.Copy(values, map[string]any{ openstack.CloudControllerManagerName: map[string]any{"enabled": true}, + stackit.PodIdentityWebhookName: podIdentityWebhook, }) return values, nil @@ -1225,16 +1260,6 @@ func (vp *valuesProvider) getControlPlaneShootChartCSISTACKITValues(ctx context. return values } -func (vp *valuesProvider) getAllWorkerPoolsZones(cluster *extensionscontroller.Cluster) []string { - zones := sets.NewString() - for _, worker := range cluster.Shoot.Spec.Provider.Workers { - zones.Insert(worker.Zones...) - } - list := zones.UnsortedList() - sort.Strings(list) - return list -} - func cleanupSeedLegacyCSISnapshotValidation(ctx context.Context, client k8sclient.Client, namespace string) error { stackitSnapShotName := fmt.Sprintf("%s-%s", CSIStackitPrefix, openstack.CSISnapshotValidationName) @@ -1276,3 +1301,45 @@ func cleanupCloudProviderConfigSecret(ctx context.Context, client k8sclient.Clie return nil } + +func getPodIdentityWebhookChartValues( + cluster *extensionscontroller.Cluster, + secretsReader secretsmanager.Reader, + scaledDown bool, +) (map[string]any, error) { + tlsSecret, found := secretsReader.Get(stackitPodIdentityWebhookServerName) + if !found { + return nil, fmt.Errorf("secret %q not found", stackitPodIdentityWebhookServerName) + } + + return map[string]any{ + "enabled": feature.Gate.Enabled(feature.EnableSTACKITWorkloadIdentity), + "replicaCount": extensionscontroller.GetControlPlaneReplicas(cluster, scaledDown, 1), + "webhook": map[string]any{ + "tlsSecretName": tlsSecret.Name, + }, + }, nil +} + +func (vp *valuesProvider) getPodIdentityWebhookShootChartValues( + controlPlaneNamespace string, + secretsReader secretsmanager.Reader, +) (map[string]any, error) { + caSecret, found := secretsReader.Get(caNameControlPlane) + if !found { + return nil, fmt.Errorf("secret %q not found", caNameControlPlane) + } + + caBundle, ok := caSecret.Data[secretutils.DataKeyCertificateBundle] + if !ok || len(caBundle) == 0 { + return nil, fmt.Errorf("secret %q is missing non-empty %q data", caNameControlPlane, secretutils.DataKeyCertificateBundle) + } + + return map[string]any{ + "enabled": feature.Gate.Enabled(feature.EnableSTACKITWorkloadIdentity), + "webhook": map[string]any{ + "caBundle": caBundle, + "url": fmt.Sprintf("https://%s.%s:443/mutate--v1-pod", stackit.PodIdentityWebhookName, controlPlaneNamespace), + }, + }, nil +} diff --git a/pkg/controller/controlplane/valuesprovider_test.go b/pkg/controller/controlplane/valuesprovider_test.go index 6b2c9c29..d9582ce4 100644 --- a/pkg/controller/controlplane/valuesprovider_test.go +++ b/pkg/controller/controlplane/valuesprovider_test.go @@ -16,6 +16,7 @@ import ( v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" "github.com/gardener/gardener/pkg/utils" + secretutils "github.com/gardener/gardener/pkg/utils/secrets" secretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager" fakesecretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager/fake" testutils "github.com/gardener/gardener/pkg/utils/test" @@ -495,6 +496,14 @@ var _ = Describe("ValuesProvider", func() { }, }) + stackitPodIdentityWebhookChartSeedValues := map[string]any{ + "enabled": false, + "replicaCount": 1, + "webhook": map[string]any{ + "tlsSecretName": stackitPodIdentityWebhookServerName, + }, + } + BeforeEach(func() { c.EXPECT().Get(ctx, cpConfigKey, &corev1.Secret{}).DoAndReturn(clientGet(cpConfig)) c.EXPECT().Delete(context.TODO(), &networkingv1.NetworkPolicy{ObjectMeta: metav1.ObjectMeta{Name: "allow-kube-apiserver-to-csi-snapshot-validation", Namespace: cp.Namespace}}) @@ -513,8 +522,9 @@ var _ = Describe("ValuesProvider", func() { c.EXPECT().Delete(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-%s", CSIStackitPrefix, openstack.CloudProviderConfigName), Namespace: namespace}}) By("creating secrets managed outside of this package for whose secretsmanager.Get() will be called") - Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "ca-provider-openstack-controlplane", Namespace: namespace}})).To(Succeed()) + Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "ca-provider-openstack-controlplane", Namespace: namespace}, Data: map[string][]byte{secretutils.DataKeyCertificateBundle: []byte("fake-ca-cert")}})).To(Succeed()) Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "cloud-controller-manager-server", Namespace: namespace}})).To(Succeed()) + Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: stackitPodIdentityWebhookServerName, Namespace: namespace}})).To(Succeed()) // This call is made for emergency Loadbalancer API access. // It will return a NotFound error by default to not interfere with existing tests. @@ -557,6 +567,7 @@ var _ = Describe("ValuesProvider", func() { "replicas": 1, }, }), + stackit.PodIdentityWebhookName: stackitPodIdentityWebhookChartSeedValues, openstack.STACKITALBControllerManagerName: empty(), })) }) @@ -600,6 +611,7 @@ var _ = Describe("ValuesProvider", func() { "replicas": 1, }, }), + stackit.PodIdentityWebhookName: stackitPodIdentityWebhookChartSeedValues, openstack.STACKITALBControllerManagerName: empty(), })) }) @@ -881,9 +893,17 @@ var _ = Describe("ValuesProvider", func() { }) Describe("#GetControlPlaneShootChartValues", func() { + stackitPodIdentityWebhookChartShootValues := map[string]any{ + "enabled": false, + "webhook": map[string]any{ + "caBundle": []byte("fake-ca-cert"), + "url": fmt.Sprintf("https://stackit-pod-identity-webhook.%s:443/mutate--v1-pod", namespace), + }, + } + BeforeEach(func() { By("creating secrets managed outside of this package for whose secretsmanager.Get() will be called") - Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "ca-provider-openstack-controlplane", Namespace: namespace}})).To(Succeed()) + Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "ca-provider-openstack-controlplane", Namespace: namespace}, Data: map[string][]byte{secretutils.DataKeyCertificateBundle: []byte("fake-ca-cert")}})).To(Succeed()) Expect(fakeClient.Create(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "cloud-controller-manager-server", Namespace: namespace}})).To(Succeed()) }) @@ -903,7 +923,8 @@ var _ = Describe("ValuesProvider", func() { "rescanBlockStorageOnResize": rescanBlockStorageOnResize, "userAgentHeaders": []string{domainName, tenantName, technicalID}, }), - openstack.CSINodeName: enabledFalse, + openstack.CSINodeName: enabledFalse, + stackit.PodIdentityWebhookName: stackitPodIdentityWebhookChartShootValues, })) }) @@ -921,7 +942,8 @@ var _ = Describe("ValuesProvider", func() { "rescanBlockStorageOnResize": rescanBlockStorageOnResize, "userAgentHeaders": []string{domainName, tenantName, technicalID}, }), - openstack.CSINodeName: enabledFalse, + openstack.CSINodeName: enabledFalse, + stackit.PodIdentityWebhookName: stackitPodIdentityWebhookChartShootValues, })) }) }) diff --git a/pkg/feature/feature.go b/pkg/feature/feature.go index 2a795d86..018085f5 100644 --- a/pkg/feature/feature.go +++ b/pkg/feature/feature.go @@ -14,7 +14,7 @@ const ( // // MyFeature enables Foo. // MyFeature featuregate.Feature = "MyFeature" - // MutateDisableNTP enables the mutation that disables NTP if any worker's flatcar image version is greater than or eqaul to `FlatcarImageVersion` + // MutateDisableNTP enables the mutation that disables NTP if any worker's flatcar image version is greater than or equal to `FlatcarImageVersion` MutateDisableNTP featuregate.Feature = "MutateDisableNTP" // EnsureSTACKITLBDeletion enables the STACKIT LB deletion cleanup. The function checks for dangling/zombied LB's and then tries to delete them. EnsureSTACKITLBDeletion featuregate.Feature = "EnsureSTACKITLBDeletion" @@ -26,6 +26,8 @@ const ( ShootUseSTACKITMachineControllerManager = "shoot.gardener.cloud/use-stackit-machine-controller-manager" // ShootUseSTACKITAPIInfrastructureController Uses the STACKIT API to create the shoot resources instead of OpenStack for a specific Shoot. ShootUseSTACKITAPIInfrastructureController = "shoot.gardener.cloud/use-stackit-api-infrastructure-controller" + // EnableSTACKITWorkloadIdentity activates the deployment of the stackit-pod-identity-webhook to enable workload identity injection into pods. + EnableSTACKITWorkloadIdentity featuregate.Feature = "EnableSTACKITWorkloadIdentity" ) var ( @@ -46,6 +48,7 @@ var ( EnsureSTACKITLBDeletion: {Default: true, PreRelease: featuregate.Alpha}, UseSTACKITAPIInfrastructureController: {Default: true, PreRelease: featuregate.Alpha}, UseSTACKITMachineControllerManager: {Default: true, PreRelease: featuregate.Alpha}, + EnableSTACKITWorkloadIdentity: {Default: false, PreRelease: featuregate.Alpha}, } ) diff --git a/pkg/stackit/types.go b/pkg/stackit/types.go index a8e8f540..e5e2fa82 100644 --- a/pkg/stackit/types.go +++ b/pkg/stackit/types.go @@ -16,6 +16,9 @@ const ( EtherTypeIPv6 = "IPv6" DirectionEgress = "egress" DirectionIngress = "ingress" + + // PodIdentityWebhookName is a constant for the name of the Pod Identity Webhook. (stackit) + PodIdentityWebhookName = "stackit-pod-identity-webhook" ) var (