diff --git a/pkg/apis/service/v1alpha1/types.go b/pkg/apis/service/v1alpha1/types.go index e3bb85a8..fa5af1b2 100644 --- a/pkg/apis/service/v1alpha1/types.go +++ b/pkg/apis/service/v1alpha1/types.go @@ -14,6 +14,9 @@ const CertManagementResourceNameSeed = "extension-shoot-cert-service-seed" // CertManagementResourceNameShoot is the name for Cert-Management resources in the shoot. const CertManagementResourceNameShoot = "extension-shoot-cert-service-shoot" +// CertManagementResourceNameRuntime is the name for Cert-Management resources in the runtime cluster. +const CertManagementResourceNameRuntime = "extension-shoot-cert-service-runtime" + // CertManagementImageName is the name of the Cert-Management image in the image vector. const CertManagementImageName = "cert-management" @@ -29,6 +32,9 @@ const CertManagementChartNameSeed = "shoot-cert-management-seed" // CertManagementChartNameShoot is the name of the chart for Cert-Management in the shoot. const CertManagementChartNameShoot = "shoot-cert-management-shoot" +// CertManagementChartNameRuntime is the name of the chart for Cert-Management in the runtime cluster. +const CertManagementChartNameRuntime = "cert-management-garden-runtime" + // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/service/validation/validation.go b/pkg/apis/service/validation/validation.go index 1cccbecd..d5751f82 100644 --- a/pkg/apis/service/validation/validation.go +++ b/pkg/apis/service/validation/validation.go @@ -23,6 +23,26 @@ import ( // ValidateCertConfig validates the passed configuration instance. func ValidateCertConfig(config *service.CertConfig, cluster *controller.Cluster) field.ErrorList { allErrs := field.ErrorList{} + + if cluster == nil { + if len(config.Issuers) > 0 { + allErrs = append(allErrs, field.Forbidden(field.NewPath("issuers"), "issuers are not allowed in extension on runtime cluster. Please add issuer in the Helm values of the gardener-extension-shoot-cert-service extension")) + } + if config.PrecheckNameservers != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("precheckNameservers"), "precheckNameservers are not allowed in extension on runtime cluster.")) + } + if config.DNSChallengeOnShoot != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("dnsChallengeOnShoot"), "dnsChallengeOnShoot is not allowed in extension on runtime cluster.")) + } + if config.ShootIssuers != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("shootIssuers"), "shootIssuers is not allowed in extension on runtime cluster.")) + } + if config.Alerting != nil { + allErrs = append(allErrs, field.Forbidden(field.NewPath("alerting"), "alerting is not allowed in extension on runtime cluster.")) + } + return allErrs + } + allErrs = append(allErrs, validateIssuers(cluster, config.Issuers, field.NewPath("issuers"))...) allErrs = append(allErrs, validateDNSChallengeOnShoot(config.DNSChallengeOnShoot, field.NewPath("dnsChallengeOnShoot"))...) diff --git a/pkg/apis/service/validation/validation_test.go b/pkg/apis/service/validation/validation_test.go index 21e7a54a..c3745397 100644 --- a/pkg/apis/service/validation/validation_test.go +++ b/pkg/apis/service/validation/validation_test.go @@ -45,7 +45,7 @@ var _ = Describe("Validation", func() { }, } ) - DescribeTable("#ValidateCertConfig", + DescribeTable("#ValidateCertConfigShoot", func(config service.CertConfig, match gomegatypes.GomegaMatcher) { err := validation.ValidateCertConfig(&config, cluster) Expect(err).To(match) @@ -291,4 +291,62 @@ var _ = Describe("Validation", func() { })), )), ) + DescribeTable("#ValidateCertConfigRuntimeCluster", + func(config service.CertConfig, match gomegatypes.GomegaMatcher) { + err := validation.ValidateCertConfig(&config, nil) + Expect(err).To(match) + }, + Entry("No issuers", service.CertConfig{}, BeEmpty()), + Entry("Unsupported issuer", service.CertConfig{ + Issuers: []service.IssuerConfig{ + { + Name: "issuer", + Server: "https://acme-v02.api.letsencrypt.org/directory", + Email: "john@example.com", + }, + }, + }, ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("issuers"), + })), + )), + Entry("Unsupported DNSChallengeOnShoot", service.CertConfig{ + DNSChallengeOnShoot: &service.DNSChallengeOnShoot{ + Enabled: true, + Namespace: "kube-system", + }, + }, ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("dnsChallengeOnShoot"), + })), + )), + Entry("Unsupported PrecheckNameservers", service.CertConfig{ + PrecheckNameservers: &nameservers, + }, ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("precheckNameservers"), + })), + )), + Entry("Unsupported ShootIssuer", service.CertConfig{ + ShootIssuers: &service.ShootIssuers{ + Enabled: true, + }, + }, ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("shootIssuers"), + })), + )), + Entry("Unsupported Alerting", service.CertConfig{ + Alerting: &service.Alerting{}, + }, ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(field.ErrorTypeForbidden), + "Field": Equal("alerting"), + })), + )), + ) }) diff --git a/pkg/controller/actuator.go b/pkg/controller/actuator.go index 1d619f61..88dce9c3 100644 --- a/pkg/controller/actuator.go +++ b/pkg/controller/actuator.go @@ -71,6 +71,10 @@ type actuator struct { // Reconcile the Extension resource. func (a *actuator) Reconcile(ctx context.Context, log logr.Logger, ex *extensionsv1alpha1.Extension) error { + if a.extensionClass == extensionsv1alpha1.ExtensionClassGarden { + return a.reconcileOnRuntimeCluster(ctx, log, ex) + } + namespace := ex.GetNamespace() cluster, err := controller.GetCluster(ctx, a.client, namespace) @@ -103,6 +107,10 @@ func (a *actuator) Reconcile(ctx context.Context, log logr.Logger, ex *extension // Delete the Extension resource. func (a *actuator) Delete(ctx context.Context, log logr.Logger, ex *extensionsv1alpha1.Extension) error { + if a.extensionClass == extensionsv1alpha1.ExtensionClassGarden { + return a.deleteOnRuntimeCluster(ctx, log, ex) + } + namespace := ex.GetNamespace() a.logger.Info("Component is being deleted", "component", "cert-management", "namespace", namespace) if err := a.deleteShootResources(ctx, namespace); err != nil { @@ -250,14 +258,20 @@ func (a *actuator) createSeedResources(ctx context.Context, certConfig *service. return err } - dnsChallengeOnShoot, err := createDNSChallengeOnShootValues(certConfig.DNSChallengeOnShoot) - if err != nil { - return err - } + var ( + restricted bool + restrictedDomains *string + replicaCount = 1 + ) - if cluster.Shoot.Spec.DNS == nil || cluster.Shoot.Spec.DNS.Domain == nil { - a.logger.Info("no domain given for shoot %s/%s - aborting", cluster.Shoot.Name, cluster.Shoot.Namespace) - return nil + if cluster != nil { + replicaCount = controller.GetReplicas(cluster, 1) + if cluster.Shoot.Spec.DNS == nil || cluster.Shoot.Spec.DNS.Domain == nil { + a.logger.Info("no domain given for shoot %s/%s - aborting", cluster.Shoot.Name, cluster.Shoot.Namespace) + return nil + } + restricted = *a.serviceConfig.RestrictIssuer + restrictedDomains = cluster.Shoot.Spec.DNS.Domain } var propagationTimeout string @@ -265,29 +279,33 @@ func (a *actuator) createSeedResources(ctx context.Context, certConfig *service. propagationTimeout = a.serviceConfig.ACME.PropagationTimeout.Duration.String() } - var ( - shootIssuers = a.createShootIssuersValues(certConfig) - certManagementConfig = map[string]interface{}{ - "replicaCount": controller.GetReplicas(cluster, 1), - "defaultIssuer": map[string]interface{}{ - "name": a.serviceConfig.IssuerName, - "restricted": *a.serviceConfig.RestrictIssuer, - "domains": cluster.Shoot.Spec.DNS.Domain, - }, - "issuers": issuers, - "configuration": map[string]interface{}{ - "propagationTimeout": propagationTimeout, - }, - "dnsChallengeOnShoot": dnsChallengeOnShoot, - "shootIssuers": shootIssuers, - "genericTokenKubeconfigSecretName": extensions.GenericTokenKubeconfigSecretNameFromCluster(cluster), + certManagementConfig := map[string]interface{}{ + "replicaCount": replicaCount, + "defaultIssuer": map[string]interface{}{ + "name": a.serviceConfig.IssuerName, + "restricted": restricted, + "domains": restrictedDomains, + }, + "issuers": issuers, + "configuration": map[string]interface{}{ + "propagationTimeout": propagationTimeout, + }, + } + + if cluster != nil { + dnsChallengeOnShoot, err := createDNSChallengeOnShootValues(certConfig.DNSChallengeOnShoot) + if err != nil { + return err } - ) + certManagementConfig["shootIssuers"] = a.createShootIssuersValues(certConfig) + certManagementConfig["dnsChallengeOnShoot"] = dnsChallengeOnShoot + if err := gutil.NewShootAccessSecret(v1alpha1.ShootAccessSecretName, namespace).Reconcile(ctx, a.client); err != nil { + return err + } + certManagementConfig["genericTokenKubeconfigSecretName"] = extensions.GenericTokenKubeconfigSecretNameFromCluster(cluster) - if err := gutil.NewShootAccessSecret(v1alpha1.ShootAccessSecretName, namespace).Reconcile(ctx, a.client); err != nil { - return err + certManagementConfig["shootClusterSecret"] = gutil.SecretNamePrefixShootAccess + v1alpha1.ShootAccessSecretName } - certManagementConfig["shootClusterSecret"] = gutil.SecretNamePrefixShootAccess + v1alpha1.ShootAccessSecretName cfg := certManagementConfig["configuration"].(map[string]interface{}) if a.serviceConfig.DefaultRequestsPerDayQuota != nil { @@ -336,13 +354,15 @@ func (a *actuator) createSeedResources(ctx context.Context, certConfig *service. } // TODO(rfranzke): Delete this after August 2024. - gep19Monitoring := a.client.Get(ctx, client.ObjectKey{Name: "prometheus-shoot", Namespace: namespace}, &appsv1.StatefulSet{}) == nil - if gep19Monitoring { - if err := kutil.DeleteObject(ctx, a.client, &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "cert-controller-manager-observability-config", Namespace: namespace}}); err != nil { - return fmt.Errorf("failed deleting cert-controller-manager-observability-config ConfigMap: %w", err) + if cluster != nil { + gep19Monitoring := a.client.Get(ctx, client.ObjectKey{Name: "prometheus-shoot", Namespace: namespace}, &appsv1.StatefulSet{}) == nil + if gep19Monitoring { + if err := kutil.DeleteObject(ctx, a.client, &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "cert-controller-manager-observability-config", Namespace: namespace}}); err != nil { + return fmt.Errorf("failed deleting cert-controller-manager-observability-config ConfigMap: %w", err) + } } + certManagementConfig["gep19Monitoring"] = gep19Monitoring } - certManagementConfig["gep19Monitoring"] = gep19Monitoring certManagementConfig, err = chart.InjectImages(certManagementConfig, imagevector.ImageVector(), []string{v1alpha1.CertManagementImageName}) if err != nil { @@ -356,7 +376,13 @@ func (a *actuator) createSeedResources(ctx context.Context, certConfig *service. a.logger.Info("Component is being applied", "component", "cert-management", "namespace", namespace) - return a.createManagedResource(ctx, namespace, v1alpha1.CertManagementResourceNameSeed, "seed", renderer, v1alpha1.CertManagementChartNameSeed, namespace, certManagementConfig, nil) + resourceName := v1alpha1.CertManagementResourceNameSeed + chartName := v1alpha1.CertManagementChartNameSeed + if cluster == nil { + resourceName = v1alpha1.CertManagementResourceNameRuntime + chartName = v1alpha1.CertManagementChartNameRuntime + } + return a.createManagedResource(ctx, namespace, resourceName, "seed", renderer, chartName, namespace, certManagementConfig, nil) } func (a *actuator) createShootResources(ctx context.Context, certConfig *service.CertConfig, cluster *controller.Cluster, namespace string) error { @@ -452,6 +478,41 @@ func (a *actuator) createShootIssuersValues(certConfig *service.CertConfig) map[ } } +func (a *actuator) reconcileOnRuntimeCluster(ctx context.Context, _ logr.Logger, ex *extensionsv1alpha1.Extension) error { + namespace := ex.GetNamespace() + + certConfig := &service.CertConfig{} + if ex.Spec.ProviderConfig != nil { + if _, _, err := a.decoder.Decode(ex.Spec.ProviderConfig.Raw, nil, certConfig); err != nil { + return fmt.Errorf("failed to decode provider config: %+v", err) + } + if errs := validation.ValidateCertConfig(certConfig, nil); len(errs) > 0 { + return errs.ToAggregate() + } + } + + if err := a.createSeedResources(ctx, certConfig, nil, namespace); err != nil { + return err + } + + return a.updateStatus(ctx, ex, certConfig) +} + +func (a *actuator) deleteOnRuntimeCluster(ctx context.Context, logger logr.Logger, ex *extensionsv1alpha1.Extension) error { + namespace := ex.GetNamespace() + a.logger.Info("Component is being deleted", "component", "cert-management", "namespace", namespace) + + a.logger.Info("Deleting managed resource for runtime", "namespace", namespace) + + if err := managedresources.Delete(ctx, a.client, namespace, v1alpha1.CertManagementResourceNameRuntime, false); err != nil { + return err + } + + timeoutCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) + defer cancel() + return managedresources.WaitUntilDeleted(timeoutCtx, a.client, namespace, v1alpha1.CertManagementResourceNameRuntime) +} + func mergeServers(serversList ...string) string { existing := map[string]struct{}{} merged := []string{}