diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index cec4423a..de3c2b20 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -20,6 +20,7 @@ rules: - infrastructuremanager.kyma-project.io resources: - gardenerclusters + - runtimes verbs: - create - delete diff --git a/hack/runtime-migrator/cmd/migration.go b/hack/runtime-migrator/cmd/migration.go index b8266813..9de9e56a 100644 --- a/hack/runtime-migrator/cmd/migration.go +++ b/hack/runtime-migrator/cmd/migration.go @@ -16,7 +16,6 @@ import ( "github.com/kyma-project/infrastructure-manager/pkg/config" "github.com/kyma-project/infrastructure-manager/pkg/gardener/kubeconfig" "github.com/kyma-project/infrastructure-manager/pkg/gardener/shoot/extender/auditlogs" - "github.com/pkg/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -139,7 +138,7 @@ func (m Migration) Do(ctx context.Context, runtimeIDs []string) error { return } - reportSuccess(runtimeID, shoot.Name, "Runtime have been applied") + reportSuccess(runtimeID, shoot.Name, "Runtime has been applied") } } diff --git a/hack/runtime-migrator/internal/runtime/verifier.go b/hack/runtime-migrator/internal/runtime/verifier.go index af3e40b0..f9cbe20c 100644 --- a/hack/runtime-migrator/internal/runtime/verifier.go +++ b/hack/runtime-migrator/internal/runtime/verifier.go @@ -66,13 +66,36 @@ func (v Verifier) newConverter(shootToMatch v1beta1.Shoot) (gardener_shoot.Conve return gardener_shoot.Converter{}, err } + imgName, imgVersion := getImageNameAndVersion(shootToMatch.Spec.Provider.Workers) + return gardener_shoot.NewConverterPatch(gardener_shoot.PatchOpts{ - ConverterConfig: v.converterConfig, - AuditLogData: auditLogData, - Zones: getZones(shootToMatch.Spec.Provider.Workers), + ConverterConfig: v.converterConfig, + AuditLogData: auditLogData, + Zones: getZones(shootToMatch.Spec.Provider.Workers), + ShootK8SVersion: shootToMatch.Spec.Kubernetes.Version, + ShootImageName: imgName, + ShootImageVersion: imgVersion, + Extensions: shootToMatch.Spec.Extensions, + Resources: shootToMatch.Spec.Resources, }), nil } +func getImageNameAndVersion(workers []v1beta1.Worker) (string, string) { + var imageName, imageVersion string + + for _, worker := range workers { + if worker.Machine.Image != nil { + imageName = worker.Machine.Image.Name + if worker.Machine.Image.Version != nil { + imageVersion = *worker.Machine.Image.Version + } + break + } + } + + return imageName, imageVersion +} + func getZones(workers []v1beta1.Worker) []string { var zones []string diff --git a/hack/shoot-comparator/pkg/shoot/matcher.go b/hack/shoot-comparator/pkg/shoot/matcher.go index e90105df..911fb1d4 100644 --- a/hack/shoot-comparator/pkg/shoot/matcher.go +++ b/hack/shoot-comparator/pkg/shoot/matcher.go @@ -5,6 +5,8 @@ import ( "reflect" "strings" + "github.com/kyma-project/infrastructure-manager/hack/shoot-comparator/pkg/runtime" + "github.com/gardener/gardener/pkg/apis/core/v1beta1" "github.com/kyma-project/infrastructure-manager/hack/shoot-comparator/pkg/errors" "github.com/onsi/gomega" @@ -164,6 +166,15 @@ func (m *Matcher) Match(actual interface{}) (success bool, err error) { actual: shootToMatch.Labels, path: "metadata/labels", }, + { + GomegaMatcher: gstruct.MatchElements( + idResource, + gstruct.IgnoreMissing, + resources(shootToMatch.Spec.Resources), + ), + actual: shootActual.Spec.Resources, + path: "spec/resources", + }, } for _, matcher := range matchers { @@ -230,6 +241,14 @@ func idToleration(v interface{}) string { return fmt.Sprintf("%s:%s", toleration.Key, val(toleration.Value)) } +func idResource(v interface{}) string { + res, ok := v.(v1beta1.NamedResourceReference) + if !ok { + panic("invalid type") + } + return fmt.Sprintf("%s", res.Name) +} + func tolerations(ts []v1beta1.Toleration) gstruct.Elements { out := map[string]types.GomegaMatcher{} for _, t := range ts { @@ -242,6 +261,18 @@ func tolerations(ts []v1beta1.Toleration) gstruct.Elements { return out } +func resources(ts []v1beta1.NamedResourceReference) gstruct.Elements { + out := map[string]types.GomegaMatcher{} + for _, t := range ts { + ID := idResource(t) + out[ID] = gstruct.MatchAllFields(gstruct.Fields{ + "Name": gomega.BeComparableTo(t.Name), + "ResourceRef": gomega.BeComparableTo(t.ResourceRef), + }) + } + return out +} + func idProvider(v interface{}) string { provider, ok := v.(v1beta1.DNSProvider) if !ok { @@ -339,7 +370,7 @@ func newKubeAPIServerMatcher(k v1beta1.Kubernetes) types.GomegaMatcher { "KubernetesConfig": gstruct.Ignore(), "AdmissionPlugins": gstruct.Ignore(), "APIAudiences": gstruct.Ignore(), - "AuditConfig": gstruct.Ignore(), + "AuditConfig": gomega.BeComparableTo(k.KubeAPIServer.AuditConfig), "RuntimeConfig": gstruct.Ignore(), "ServiceAccountConfig": gstruct.Ignore(), "WatchCacheSizes": gstruct.Ignore(), @@ -393,7 +424,7 @@ func extensions(es []v1beta1.Extension) gstruct.Elements { ID := idExtension(e) out[ID] = gstruct.MatchAllFields(gstruct.Fields{ "Type": gomega.BeComparableTo(e.Type), - "ProviderConfig": newProviderCfgMatcher(e.Type, e.ProviderConfig), + "ProviderConfig": runtime.NewRawExtensionMatcher(e.ProviderConfig), "Disabled": gomega.BeComparableTo(e.Disabled), }) } diff --git a/hack/shoot-comparator/pkg/shoot/matcher_test.go b/hack/shoot-comparator/pkg/shoot/matcher_test.go index 86220456..9a9f56ec 100644 --- a/hack/shoot-comparator/pkg/shoot/matcher_test.go +++ b/hack/shoot-comparator/pkg/shoot/matcher_test.go @@ -934,32 +934,6 @@ var _ = Describe(":: shoot matcher :: ", func() { })), true, ), - Entry( - "should find no differences in spec/extensions #2", - deepCp(empty, withShootSpec(v1beta1.ShootSpec{ - Extensions: []v1beta1.Extension{ - { - Type: "shoot-dns-service", - Disabled: ptr.To[bool](true), - ProviderConfig: &runtime.RawExtension{ - Raw: []byte("{\"apiVersion\":\"service.dns.extensions.gardener.cloud/v1alpha1\",\"kind\":\"DNSConfig\",\"dnsProviderReplication\":{\"enabled\":true},\"providers\":[{\"domains\":{\"include\":[\"a50de45.dev.kyma.ondemand.com\"]},\"secretName\":\"route53-secret-dev\",\"type\":\"aws-route53\"}],\"syncProvidersFromShootSpecDNS\":true}"), - }, - }, - }, - })), - deepCp(empty, withShootSpec(v1beta1.ShootSpec{ - Extensions: []v1beta1.Extension{ - { - Type: "shoot-dns-service", - Disabled: ptr.To[bool](true), - ProviderConfig: &runtime.RawExtension{ - Raw: []byte("{\"apiVersion\":\"service.dns.extensions.gardener.cloud/v1alpha1\",\"kind\":\"DNSConfig\",\"dnsProviderReplication\":{\"enabled\":true},\"providers\":[{\"domains\":{\"include\":[\"a50de45.dev.kyma.ondemand.com\"]},\"secretName\":\"xxx-route53-secret-dev\",\"type\":\"aws-route53\"}],\"syncProvidersFromShootSpecDNS\":true}"), - }, - }, - }, - })), - true, - ), Entry( "should find no differences in spec/extensions #3", deepCp(empty, withShootSpec(v1beta1.ShootSpec{ diff --git a/internal/controller/runtime/fsm/runtime_fsm_configure_oidc.go b/internal/controller/runtime/fsm/runtime_fsm_configure_oidc.go index fa7b140a..433cc3f0 100644 --- a/internal/controller/runtime/fsm/runtime_fsm_configure_oidc.go +++ b/internal/controller/runtime/fsm/runtime_fsm_configure_oidc.go @@ -25,6 +25,19 @@ func sFnConfigureOidc(ctx context.Context, m *fsm, s *systemState) (stateFn, *ct "True", "OIDC extension disabled", ) + + return switchState(sFnApplyClusterRoleBindings) + } + + if !multiOidcSupported(s.instance) { + // New OIDC functionality is supported only for new clusters + m.log.Info("Multi OIDC is not supported for migrated runtimes") + s.instance.UpdateStatePending( + imv1.ConditionTypeOidcConfigured, + imv1.ConditionReasonOidcConfigured, + "True", + "Multi OIDC not supported for migrated runtimes", + ) return switchState(sFnApplyClusterRoleBindings) } @@ -37,6 +50,7 @@ func sFnConfigureOidc(ctx context.Context, m *fsm, s *systemState) (stateFn, *ct return updateStatusAndStopWithError(err) } + m.log.Info("OIDC has been configured", "Name", s.shoot.Name) s.instance.UpdateStatePending( imv1.ConditionTypeOidcConfigured, imv1.ConditionReasonOidcConfigured, @@ -44,8 +58,6 @@ func sFnConfigureOidc(ctx context.Context, m *fsm, s *systemState) (stateFn, *ct "OIDC configuration completed", ) - m.log.Info("OIDC has been configured", "Name", s.shoot.Name) - return switchState(sFnApplyClusterRoleBindings) } @@ -108,6 +120,10 @@ func isOidcExtensionEnabled(shoot gardener.Shoot) bool { return false } +func multiOidcSupported(runtime imv1.Runtime) bool { + return runtime.Labels["operator.kyma-project.io/created-by-migrator"] != "true" //nolint:all +} + func createOpenIDConnectResource(additionalOidcConfig gardener.OIDCConfig, oidcID int) *authenticationv1alpha1.OpenIDConnect { toSupportedSigningAlgs := func(signingAlgs []string) []authenticationv1alpha1.SigningAlgorithm { var supportedSigningAlgs []authenticationv1alpha1.SigningAlgorithm diff --git a/internal/controller/runtime/fsm/runtime_fsm_configure_oidc_test.go b/internal/controller/runtime/fsm/runtime_fsm_configure_oidc_test.go index e74ba1ab..f3e1639c 100644 --- a/internal/controller/runtime/fsm/runtime_fsm_configure_oidc_test.go +++ b/internal/controller/runtime/fsm/runtime_fsm_configure_oidc_test.go @@ -53,6 +53,45 @@ func TestOidcState(t *testing.T) { assertEqualConditions(t, expectedRuntimeConditions, systemState.instance.Status.Conditions) }) + t.Run("Should switch state to ApplyClusterRoleBindings when multi OIDC support is disabled", func(t *testing.T) { + // given + ctx := context.Background() + fsm := &fsm{} + + runtimeStub := runtimeForTest() + runtimeStub.ObjectMeta.Labels = map[string]string{ + "operator.kyma-project.io/created-by-migrator": "true", + } + + shootStub := shootForTest() + oidcService := gardener.Extension{ + Type: "shoot-oidc-service", + Disabled: ptr.To(false), + } + shootStub.Spec.Extensions = append(shootStub.Spec.Extensions, oidcService) + + systemState := &systemState{ + instance: runtimeStub, + shoot: shootStub, + } + + expectedRuntimeConditions := []metav1.Condition{ + { + Type: string(imv1.ConditionTypeOidcConfigured), + Reason: string(imv1.ConditionReasonOidcConfigured), + Status: "True", + Message: "Multi OIDC not supported for migrated runtimes", + }, + } + + // when + stateFn, _, _ := sFnConfigureOidc(ctx, fsm, systemState) + + // then + require.Contains(t, stateFn.name(), "sFnApplyClusterRoleBindings") + assertEqualConditions(t, expectedRuntimeConditions, systemState.instance.Status.Conditions) + }) + t.Run("Should configure OIDC using defaults", func(t *testing.T) { // given ctx := context.Background() diff --git a/internal/controller/runtime/fsm/runtime_fsm_patch_shoot.go b/internal/controller/runtime/fsm/runtime_fsm_patch_shoot.go index 7fa4af11..4b2795d8 100644 --- a/internal/controller/runtime/fsm/runtime_fsm_patch_shoot.go +++ b/internal/controller/runtime/fsm/runtime_fsm_patch_shoot.go @@ -45,6 +45,8 @@ func sFnPatchExistingShoot(ctx context.Context, m *fsm, s *systemState) (stateFn ShootK8SVersion: s.shoot.Spec.Kubernetes.Version, ShootImageName: imgName, ShootImageVersion: imgVersion, + Extensions: s.shoot.Spec.Extensions, + Resources: s.shoot.Spec.Resources, }) if err != nil { diff --git a/internal/controller/runtime/suite_test.go b/internal/controller/runtime/suite_test.go index 8eb94681..52fd1155 100644 --- a/internal/controller/runtime/suite_test.go +++ b/internal/controller/runtime/suite_test.go @@ -19,6 +19,8 @@ package runtime import ( "context" "encoding/json" + "github.com/kyma-project/infrastructure-manager/pkg/gardener/shoot/extender/extensions" + v12 "k8s.io/api/core/v1" "path/filepath" "testing" "time" @@ -36,7 +38,6 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/mock" v1 "k8s.io/api/autoscaling/v1" - v12 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" //nolint:revive @@ -346,9 +347,13 @@ func fixConverterConfigForTests() config.Config { } func addAuditLogConfigToShoot(shoot *gardener_api.Shoot) { - shoot.Spec.Kubernetes.KubeAPIServer.AuditConfig = &gardener_api.AuditConfig{ - AuditPolicy: &gardener_api.AuditPolicy{ - ConfigMapRef: &v12.ObjectReference{Name: "policy-config-map"}, + shoot.Spec.Kubernetes = gardener_api.Kubernetes{ + KubeAPIServer: &gardener_api.KubeAPIServerConfig{ + AuditConfig: &gardener_api.AuditConfig{ + AuditPolicy: &gardener_api.AuditPolicy{ + ConfigMapRef: &v12.ObjectReference{Name: "policy-config-map"}, + }, + }, }, } @@ -373,7 +378,7 @@ func addAuditLogConfigToShoot(shoot *gardener_api.Shoot) { ext := &shoot.Spec.Extensions[len(shoot.Spec.Extensions)-1] - cfg := auditlogs.AuditlogExtensionConfig{ + cfg := extensions.AuditlogExtensionConfig{ TypeMeta: metav1.TypeMeta{ Kind: extensionKind, APIVersion: extensionVersion, diff --git a/pkg/gardener/shoot/converter.go b/pkg/gardener/shoot/converter.go index f0ce449d..27c5c027 100644 --- a/pkg/gardener/shoot/converter.go +++ b/pkg/gardener/shoot/converter.go @@ -8,6 +8,7 @@ import ( "github.com/kyma-project/infrastructure-manager/pkg/config" extender2 "github.com/kyma-project/infrastructure-manager/pkg/gardener/shoot/extender" "github.com/kyma-project/infrastructure-manager/pkg/gardener/shoot/extender/auditlogs" + "github.com/kyma-project/infrastructure-manager/pkg/gardener/shoot/extender/extensions" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -20,10 +21,7 @@ func baseExtenders(cfg config.ConverterConfig) []Extend { extender2.ExtendWithSeedSelector, extender2.NewOidcExtender(cfg.Kubernetes.DefaultOperatorOidc), extender2.ExtendWithCloudProfile, - extender2.ExtendWithNetworkFilter, - extender2.ExtendWithCertConfig, extender2.ExtendWithExposureClassName, - extender2.ExtendWithTolerations, extender2.NewMaintenanceExtender(cfg.Kubernetes.EnableKubernetesVersionAutoUpdate, cfg.Kubernetes.EnableMachineImageVersionAutoUpdate), } } @@ -52,40 +50,43 @@ type PatchOpts struct { ShootK8SVersion string ShootImageName string ShootImageVersion string + Extensions []gardener.Extension + Resources []gardener.NamedResourceReference } func NewConverterCreate(opts CreateOpts) Converter { - baseExtenders := baseExtenders(opts.ConverterConfig) + extendersForCreate := baseExtenders(opts.ConverterConfig) - baseExtenders = append(baseExtenders, + extendersForCreate = append(extendersForCreate, extender2.NewProviderExtenderForCreateOperation( opts.Provider.AWS.EnableIMDSv2, opts.MachineImage.DefaultName, opts.MachineImage.DefaultVersion, - )) - - baseExtenders = append(baseExtenders, + ), extender2.NewDNSExtender(opts.DNS.SecretName, opts.DNS.DomainPrefix, opts.DNS.ProviderType), + extender2.ExtendWithTolerations, ) - baseExtenders = append(baseExtenders, + extendersForCreate = append(extendersForCreate, extensions.NewExtensionsExtenderForCreate(opts.ConverterConfig, opts.AuditLogData)) + + extendersForCreate = append(extendersForCreate, extender2.NewKubernetesExtender(opts.Kubernetes.DefaultVersion, "")) var zero auditlogs.AuditLogData if opts.AuditLogData != zero { - baseExtenders = append(baseExtenders, - auditlogs.NewAuditlogExtender( + extendersForCreate = append(extendersForCreate, + auditlogs.NewAuditlogExtenderForCreate( opts.AuditLog.PolicyConfigMapName, opts.AuditLogData)) } - return newConverter(opts.ConverterConfig, baseExtenders...) + return newConverter(opts.ConverterConfig, extendersForCreate...) } func NewConverterPatch(opts PatchOpts) Converter { - baseExtenders := baseExtenders(opts.ConverterConfig) + extendersForPatch := baseExtenders(opts.ConverterConfig) - baseExtenders = append(baseExtenders, + extendersForPatch = append(extendersForPatch, extender2.NewProviderExtenderPatchOperation( opts.Provider.AWS.EnableIMDSv2, opts.MachineImage.DefaultName, @@ -94,18 +95,19 @@ func NewConverterPatch(opts PatchOpts) Converter { opts.ShootImageVersion, opts.Zones)) - baseExtenders = append(baseExtenders, - extender2.NewKubernetesExtender(opts.Kubernetes.DefaultVersion, opts.ShootK8SVersion)) + extendersForPatch = append(extendersForPatch, + extensions.NewExtensionsExtenderForPatch(opts.AuditLogData, opts.Extensions), + extender2.NewResourcesExtenderForPatch(opts.Resources)) + + extendersForPatch = append(extendersForPatch, extender2.NewKubernetesExtender(opts.Kubernetes.DefaultVersion, opts.ShootK8SVersion)) var zero auditlogs.AuditLogData if opts.AuditLogData != zero { - baseExtenders = append(baseExtenders, - auditlogs.NewAuditlogExtender( - opts.AuditLog.PolicyConfigMapName, - opts.AuditLogData)) + extendersForPatch = append(extendersForPatch, + auditlogs.NewAuditlogExtenderForPatch(opts.AuditLog.PolicyConfigMapName)) } - return newConverter(opts.ConverterConfig, baseExtenders...) + return newConverter(opts.ConverterConfig, extendersForPatch...) } func (c Converter) ToShoot(runtime imv1.Runtime) (gardener.Shoot, error) { diff --git a/pkg/gardener/shoot/converter_test.go b/pkg/gardener/shoot/converter_test.go index afc00b40..2cf236c3 100644 --- a/pkg/gardener/shoot/converter_test.go +++ b/pkg/gardener/shoot/converter_test.go @@ -83,7 +83,7 @@ func TestConverter(t *testing.T) { assert.Nil(t, shoot.Spec.DNS) extensionLen := len(shoot.Spec.Extensions) - require.Equalf(t, extensionLen, 3, "unexpected number of extensions: %d, expected: 3", extensionLen) + require.Equalf(t, extensionLen, 2, "unexpected number of extensions: %d, expected: 2", extensionLen) // consider switchin to NotElementsMatch, whem released https://github.com/Antonboom/testifylint/issues/99 for _, extension := range shoot.Spec.Extensions { assert.NotEqual(t, "shoot-dns-service", extension.Type, "unexpected immutable field extension: 'shoot-dns-service'") diff --git a/pkg/gardener/shoot/extender/auditlogs/auditlogs_configuration.go b/pkg/gardener/shoot/extender/auditlogs/auditlogs_configuration.go index dd5fcff2..26ada3c1 100644 --- a/pkg/gardener/shoot/extender/auditlogs/auditlogs_configuration.go +++ b/pkg/gardener/shoot/extender/auditlogs/auditlogs_configuration.go @@ -10,6 +10,12 @@ type region = string type providerType = string +type AuditLogData struct { + TenantID string `json:"tenantID" validate:"required"` + ServiceURL string `json:"serviceURL" validate:"required,url"` + SecretName string `json:"secretName" validate:"required"` +} + type Configuration map[providerType]map[region]AuditLogData func (a Configuration) GetAuditLogData(providerType, region string) (AuditLogData, error) { diff --git a/pkg/gardener/shoot/extender/auditlogs/extender.go b/pkg/gardener/shoot/extender/auditlogs/extender.go index e35e44a3..93e2b369 100644 --- a/pkg/gardener/shoot/extender/auditlogs/extender.go +++ b/pkg/gardener/shoot/extender/auditlogs/extender.go @@ -9,11 +9,10 @@ type Extend = func(runtime imv1.Runtime, shoot *gardener.Shoot) error type operation = func(*gardener.Shoot) error -func NewAuditlogExtender(policyConfigMapName string, data AuditLogData) Extend { +func NewAuditlogExtenderForCreate(policyConfigMapName string, data AuditLogData) Extend { return func(_ imv1.Runtime, shoot *gardener.Shoot) error { for _, f := range []operation{ oSetSecret(data.SecretName), - oSetExtension(data), oSetPolicyConfigmap(policyConfigMapName), } { if err := f(shoot); err != nil { @@ -23,3 +22,9 @@ func NewAuditlogExtender(policyConfigMapName string, data AuditLogData) Extend { return nil } } + +func NewAuditlogExtenderForPatch(policyConfigMapName string) Extend { + return func(_ imv1.Runtime, shoot *gardener.Shoot) error { + return oSetPolicyConfigmap(policyConfigMapName)(shoot) + } +} diff --git a/pkg/gardener/shoot/extender/auditlogs/extender_test.go b/pkg/gardener/shoot/extender/auditlogs/extender_test.go index 87a41ba0..0c36b634 100644 --- a/pkg/gardener/shoot/extender/auditlogs/extender_test.go +++ b/pkg/gardener/shoot/extender/auditlogs/extender_test.go @@ -24,7 +24,7 @@ func Test_AuditlogExtender(t *testing.T) { }, } { // given - extendWithAuditlogs := NewAuditlogExtender(tc.policyConfigmapName, tc.data) + extendWithAuditlogs := NewAuditlogExtenderForCreate(tc.policyConfigmapName, tc.data) // when err := extendWithAuditlogs(zero, &tc.shoot) diff --git a/pkg/gardener/shoot/extender/auditlogs/set_extension.go b/pkg/gardener/shoot/extender/auditlogs/set_extension.go deleted file mode 100644 index b3ac7be4..00000000 --- a/pkg/gardener/shoot/extender/auditlogs/set_extension.go +++ /dev/null @@ -1,72 +0,0 @@ -package auditlogs - -import ( - "bytes" - "slices" - - gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/json" -) - -const ( - auditlogExtensionType = "shoot-auditlog-service" - auditlogReferenceName = "auditlog-credentials" -) - -type AuditLogData struct { - TenantID string `json:"tenantID" validate:"required"` - ServiceURL string `json:"serviceURL" validate:"required,url"` - SecretName string `json:"secretName" validate:"required"` -} - -type AuditlogExtensionConfig struct { - metav1.TypeMeta `json:",inline"` - // Type is the type of auditlog service provider. - Type string `json:"type"` - // TenantID is the id of the tenant. - TenantID string `json:"tenantID"` - // ServiceURL is the URL of the auditlog service. - ServiceURL string `json:"serviceURL"` - // SecretReferenceName is the name of the reference for the secret containing the auditlog service credentials. - SecretReferenceName string `json:"secretReferenceName"` -} - -func oSetExtension(d AuditLogData) operation { - return func(s *gardener.Shoot) error { - cfg := AuditlogExtensionConfig{ - TypeMeta: metav1.TypeMeta{ - Kind: "AuditlogConfig", - APIVersion: "service.auditlog.extensions.gardener.cloud/v1alpha1", - }, - Type: "standard", - TenantID: d.TenantID, - ServiceURL: d.ServiceURL, - SecretReferenceName: auditlogReferenceName, - } - var buffer bytes.Buffer - if err := json.NewEncoder(&buffer).Encode(&cfg); err != nil { - return err - } - - extension := gardener.Extension{ - Type: auditlogExtensionType, - ProviderConfig: &runtime.RawExtension{ - Raw: buffer.Bytes(), - }, - } - - index := slices.IndexFunc(s.Spec.Extensions, func(e gardener.Extension) bool { - return e.Type == auditlogExtensionType - }) - - if index == -1 { // add extension - s.Spec.Extensions = append(s.Spec.Extensions, extension) - return nil - } - - s.Spec.Extensions[index] = extension // update extension - return nil - } -} diff --git a/pkg/gardener/shoot/extender/auditlogs/set_extension_test.go b/pkg/gardener/shoot/extender/auditlogs/set_extension_test.go deleted file mode 100644 index 2df89228..00000000 --- a/pkg/gardener/shoot/extender/auditlogs/set_extension_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package auditlogs - -import ( - "bytes" - "encoding/json" - "slices" - "testing" - - gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_oSetExtension(t *testing.T) { - for _, testCase := range []struct { - shoot gardener.Shoot - data AuditLogData - }{ - { - shoot: gardener.Shoot{}, - data: AuditLogData{ - TenantID: "tenant-id", - ServiceURL: "testme", - }, - }, - } { - // given - operate := oSetExtension(testCase.data) - - // when - err := operate(&testCase.shoot) - - // then - require.NoError(t, err) - requireNoErrorAssetContainsAuditlogExtension(t, testCase.data, testCase.shoot.Spec.Extensions) - } -} - -func requireNoErrorAssetContainsAuditlogExtension(t *testing.T, data AuditLogData, actual []gardener.Extension) { - index := slices.IndexFunc(actual, func(e gardener.Extension) bool { - return e.Type == auditlogExtensionType - }) - - assert.NotEqual(t, -1, index, "no %s extension found", auditlogExtensionType) - - reader := bytes.NewReader(actual[index].ProviderConfig.Raw) - var cfg AuditlogExtensionConfig - - err := json.NewDecoder(reader).Decode(&cfg) - require.NoError(t, err) - - assert.Equal(t, data.TenantID, cfg.TenantID) - assert.Equal(t, data.ServiceURL, cfg.ServiceURL) - assert.Equal(t, auditlogReferenceName, cfg.SecretReferenceName) -} diff --git a/pkg/gardener/shoot/extender/dns.go b/pkg/gardener/shoot/extender/dns.go index 75b494a0..deee3dd8 100644 --- a/pkg/gardener/shoot/extender/dns.go +++ b/pkg/gardener/shoot/extender/dns.go @@ -1,14 +1,10 @@ package extender import ( - "encoding/json" "fmt" gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" imv1 "github.com/kyma-project/infrastructure-manager/api/v1" - autoscaling "k8s.io/api/autoscaling/v1" - apimachineryruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/ptr" ) // The types were copied from the following file: https://github.com/gardener/gardener-extension-shoot-dns-service/blob/master/pkg/apis/service/types.go @@ -55,24 +51,6 @@ type DNSProviderReplication struct { Enabled bool `json:"enabled"` } -func newDNSExtensionConfig(domain, secretName, dnsProviderType string) *DNSExtensionProviderConfig { - return &DNSExtensionProviderConfig{ - APIVersion: "service.dns.extensions.gardener.cloud/v1alpha1", - Kind: "DNSConfig", - DNSProviderReplication: &DNSProviderReplication{Enabled: true}, - SyncProvidersFromShootSpecDNS: ptr.To(true), - Providers: []DNSProvider{ - { - Domains: &DNSIncludeExclude{ - Include: []string{domain}, - }, - SecretName: ptr.To(secretName), - Type: ptr.To(dnsProviderType), - }, - }, - } -} - func NewDNSExtender(secretName, domainPrefix, dnsProviderType string) func(runtime imv1.Runtime, shoot *gardener.Shoot) error { return func(runtime imv1.Runtime, shoot *gardener.Shoot) error { domain := fmt.Sprintf("%s.%s", runtime.Spec.Shoot.Name, domainPrefix) @@ -94,30 +72,6 @@ func NewDNSExtender(secretName, domainPrefix, dnsProviderType string) func(runti }, } - extensionJSON, err := json.Marshal(newDNSExtensionConfig(domain, secretName, dnsProviderType)) - if err != nil { - return err - } - - dnsExtension := gardener.Extension{ - Type: "shoot-dns-service", - ProviderConfig: &apimachineryruntime.RawExtension{ - Raw: extensionJSON, - }, - } - - shoot.Spec.Extensions = append(shoot.Spec.Extensions, dnsExtension) - - secretReference := gardener.NamedResourceReference{ - Name: secretName, - ResourceRef: autoscaling.CrossVersionObjectReference{ - Kind: "Secret", - Name: secretName, - APIVersion: "v1", - }, - } - shoot.Spec.Resources = append(shoot.Spec.Resources, secretReference) - return nil } } diff --git a/pkg/gardener/shoot/extender/dns_test.go b/pkg/gardener/shoot/extender/dns_test.go index 9bd42963..4bb3b161 100644 --- a/pkg/gardener/shoot/extender/dns_test.go +++ b/pkg/gardener/shoot/extender/dns_test.go @@ -1,7 +1,6 @@ package extender import ( - "encoding/json" "testing" gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" @@ -9,11 +8,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" ) func TestDNSExtender(t *testing.T) { - t.Run("Create DNS config", func(t *testing.T) { + t.Run("Create DNS config for create scenario", func(t *testing.T) { // given secretName := "my-secret" domainPrefix := "dev.mydomain.com" @@ -38,28 +36,9 @@ func TestDNSExtender(t *testing.T) { assert.Equal(t, dnsProviderType, *shoot.Spec.DNS.Providers[0].Type) assert.Equal(t, secretName, *shoot.Spec.DNS.Providers[0].SecretName) assert.Equal(t, true, *shoot.Spec.DNS.Providers[0].Primary) - assert.NotEmpty(t, shoot.Spec.Extensions[0].ProviderConfig) - assertExtensionConfig(t, shoot.Spec.Extensions[0].ProviderConfig) - assert.Equal(t, secretName, shoot.Spec.Resources[0].Name) - assert.Equal(t, secretName, shoot.Spec.Resources[0].ResourceRef.Name) }) } -func assertExtensionConfig(t *testing.T, rawExtension *runtime.RawExtension) { - var extension DNSExtensionProviderConfig - err := json.Unmarshal(rawExtension.Raw, &extension) - - require.NoError(t, err) - assert.Equal(t, "DNSConfig", extension.Kind) - assert.Equal(t, "service.dns.extensions.gardener.cloud/v1alpha1", extension.APIVersion) - assert.Equal(t, true, extension.DNSProviderReplication.Enabled) - assert.Equal(t, true, *extension.SyncProvidersFromShootSpecDNS) - assert.Equal(t, 1, len(extension.Providers)) - assert.Equal(t, "myshoot.dev.mydomain.com", extension.Providers[0].Domains.Include[0]) - assert.Equal(t, "my-secret", *extension.Providers[0].SecretName) - assert.Equal(t, "aws-route53", *extension.Providers[0].Type) -} - func fixEmptyGardenerShoot(name, namespace string) gardener.Shoot { return gardener.Shoot{ ObjectMeta: v1.ObjectMeta{ diff --git a/pkg/gardener/shoot/extender/extensions/auditlog.go b/pkg/gardener/shoot/extender/extensions/auditlog.go new file mode 100644 index 00000000..1e7563be --- /dev/null +++ b/pkg/gardener/shoot/extender/extensions/auditlog.go @@ -0,0 +1,52 @@ +package extensions + +import ( + "bytes" + "encoding/json" + + gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + "github.com/kyma-project/infrastructure-manager/pkg/gardener/shoot/extender/auditlogs" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + AuditlogExtensionType = "shoot-auditlog-service" + auditlogReferenceName = "auditlog-credentials" +) + +type AuditlogExtensionConfig struct { + metav1.TypeMeta `json:",inline"` + // Type is the type of auditlog service provider. + Type string `json:"type"` + // TenantID is the id of the tenant. + TenantID string `json:"tenantID"` + // ServiceURL is the URL of the auditlog service. + ServiceURL string `json:"serviceURL"` + // SecretReferenceName is the name of the reference for the secret containing the auditlog service credentials. + SecretReferenceName string `json:"secretReferenceName"` +} + +func NewAuditLogExtension(d auditlogs.AuditLogData) (*gardener.Extension, error) { + cfg := AuditlogExtensionConfig{ + TypeMeta: metav1.TypeMeta{ + Kind: "AuditlogConfig", + APIVersion: "service.auditlog.extensions.gardener.cloud/v1alpha1", + }, + Type: "standard", + TenantID: d.TenantID, + ServiceURL: d.ServiceURL, + SecretReferenceName: auditlogReferenceName, + } + var buffer bytes.Buffer + if err := json.NewEncoder(&buffer).Encode(&cfg); err != nil { + return nil, err + } + + return &gardener.Extension{ + Type: AuditlogExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: buffer.Bytes(), + }, + }, nil +} diff --git a/pkg/gardener/shoot/extender/extensions/cert.go b/pkg/gardener/shoot/extender/extensions/cert.go new file mode 100644 index 00000000..d9cb1bd5 --- /dev/null +++ b/pkg/gardener/shoot/extender/extensions/cert.go @@ -0,0 +1,49 @@ +package extensions + +import ( + "encoding/json" + + gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + apimachineryRuntime "k8s.io/apimachinery/pkg/runtime" +) + +const CertExtensionType = "shoot-cert-service" + +type ExtensionProviderConfig struct { + // APIVersion is gardener extension api version + APIVersion string `json:"apiVersion"` + // DnsProviderReplication indicates whether dnsProvider replication is on + DNSProviderReplication *DNSProviderReplication `json:"dnsProviderReplication,omitempty"` + // ShootIssuers indicates whether shoot Issuers are on + ShootIssuers *ShootIssuers `json:"shootIssuers,omitempty"` + // Kind is extension type + Kind string `json:"kind"` +} + +type ShootIssuers struct { + // Enabled indicates whether shoot Issuers are on + Enabled bool `json:"enabled"` +} + +func NewCertConfig() *ExtensionProviderConfig { + return &ExtensionProviderConfig{ + APIVersion: "service.cert.extensions.gardener.cloud/v1alpha1", + ShootIssuers: &ShootIssuers{Enabled: true}, + Kind: "CertConfig", + } +} + +func NewCertExtension() (*gardener.Extension, error) { + certConfig := NewCertConfig() + jsonCertConfig, encodingErr := json.Marshal(certConfig) + if encodingErr != nil { + return nil, encodingErr + } + + certServiceExtension := &gardener.Extension{ + Type: CertExtensionType, + ProviderConfig: &apimachineryRuntime.RawExtension{Raw: jsonCertConfig}, + } + + return certServiceExtension, nil +} diff --git a/pkg/gardener/shoot/extender/extensions/dns.go b/pkg/gardener/shoot/extender/extensions/dns.go new file mode 100644 index 00000000..e2ab0ceb --- /dev/null +++ b/pkg/gardener/shoot/extender/extensions/dns.go @@ -0,0 +1,90 @@ +package extensions + +import ( + "encoding/json" + "fmt" + + gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" +) + +const DNSExtensionType = "shoot-dns-service" + +// The types were copied from the following file: https://github.com/gardener/gardener-extension-shoot-dns-service/blob/master/pkg/apis/service/types.go +type DNSExtensionProviderConfig struct { + // APIVersion is gardener extension api version + APIVersion string `json:"apiVersion"` + // Kind is extension type + Kind string `json:"kind"` + + // DnsProviderReplication indicates whether dnsProvider replication is on + DNSProviderReplication *DNSProviderReplication `json:"dnsProviderReplication,omitempty"` + // Providers is a list of additional DNS providers that shall be enabled for this shoot cluster. + // The primary ("external") provider at `spec.dns.provider` is added automatically + Providers []DNSProvider `json:"providers"` + // SyncProvidersFromShootSpecDNS is an optional flag for migrating and synchronising the providers given in the + // shoot manifest at section `spec.dns.providers`. If true, any direct changes on the `providers` section + // are overwritten with the content of section `spec.dns.providers`. + SyncProvidersFromShootSpecDNS *bool `json:"syncProvidersFromShootSpecDNS,omitempty"` +} + +// DNSProvider contains information about a DNS provider. +type DNSProvider struct { + // Domains contains information about which domains shall be included/excluded for this provider. + Domains *DNSIncludeExclude `json:"domains,omitempty"` + // SecretName is a name of a secret containing credentials for the stated domain and the + // provider. + SecretName *string `json:"secretName,omitempty"` + // Type is the DNS provider type. + Type *string `json:"type,omitempty"` + // Zones contains information about which hosted zones shall be included/excluded for this provider. + Zones *DNSIncludeExclude `json:"zones,omitempty"` +} + +// DNSIncludeExclude contains information about which domains shall be included/excluded. +type DNSIncludeExclude struct { + // Include is a list of domains that shall be included. + Include []string `json:"include,omitempty"` + // Exclude is a list of domains that shall be excluded. + Exclude []string `json:"exclude,omitempty"` +} + +type DNSProviderReplication struct { + // Enabled indicates whether replication is on + Enabled bool `json:"enabled"` +} + +func newDNSExtensionConfig(domain, secretName, dnsProviderType string) *DNSExtensionProviderConfig { + return &DNSExtensionProviderConfig{ + APIVersion: "service.dns.extensions.gardener.cloud/v1alpha1", + Kind: "DNSConfig", + DNSProviderReplication: &DNSProviderReplication{Enabled: true}, + SyncProvidersFromShootSpecDNS: ptr.To(true), + Providers: []DNSProvider{ + { + Domains: &DNSIncludeExclude{ + Include: []string{domain}, + }, + SecretName: ptr.To(secretName), + Type: ptr.To(dnsProviderType), + }, + }, + } +} + +func NewDNSExtension(shootName, secretName, domainSuffix, dnsProviderType string) (*gardener.Extension, error) { + domain := fmt.Sprintf("%s.%s", shootName, domainSuffix) + + extensionJSON, err := json.Marshal(newDNSExtensionConfig(domain, secretName, dnsProviderType)) + if err != nil { + return nil, err + } + + return &gardener.Extension{ + Type: DNSExtensionType, + ProviderConfig: &apimachineryruntime.RawExtension{ + Raw: extensionJSON, + }, + }, nil +} diff --git a/pkg/gardener/shoot/extender/extensions/extender.go b/pkg/gardener/shoot/extender/extensions/extender.go new file mode 100644 index 00000000..7760e187 --- /dev/null +++ b/pkg/gardener/shoot/extender/extensions/extender.go @@ -0,0 +1,126 @@ +package extensions + +import ( + "encoding/json" + "slices" + + gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + imv1 "github.com/kyma-project/infrastructure-manager/api/v1" + "github.com/kyma-project/infrastructure-manager/pkg/config" + "github.com/kyma-project/infrastructure-manager/pkg/gardener/shoot/extender/auditlogs" +) + +type CreateExtensionFunc func(runtime imv1.Runtime, shoot gardener.Shoot) (*gardener.Extension, error) + +type Extension struct { + Type string + Create CreateExtensionFunc +} + +func NewExtensionsExtenderForCreate(config config.ConverterConfig, auditLogData auditlogs.AuditLogData) func(runtime imv1.Runtime, shoot *gardener.Shoot) error { + return newExtensionsExtender([]Extension{ + { + Type: NetworkFilterType, + Create: func(runtime imv1.Runtime, _ gardener.Shoot) (*gardener.Extension, error) { + return NewNetworkFilterExtension(!runtime.Spec.Security.Networking.Filter.Egress.Enabled) + }, + }, + { + Type: CertExtensionType, + Create: func(_ imv1.Runtime, _ gardener.Shoot) (*gardener.Extension, error) { + return NewCertExtension() + }, + }, + { + Type: DNSExtensionType, + Create: func(_ imv1.Runtime, shoot gardener.Shoot) (*gardener.Extension, error) { + return NewDNSExtension(shoot.Name, config.DNS.SecretName, config.DNS.DomainPrefix, config.DNS.ProviderType) + }, + }, + { + Type: OidcExtensionType, + Create: func(_ imv1.Runtime, _ gardener.Shoot) (*gardener.Extension, error) { + return NewOIDCExtension() + }, + }, + { + Type: AuditlogExtensionType, + Create: func(_ imv1.Runtime, _ gardener.Shoot) (*gardener.Extension, error) { + return NewAuditLogExtension(auditLogData) + }, + }, + }, nil) +} + +func NewExtensionsExtenderForPatch(auditLogData auditlogs.AuditLogData, extensionsOnTheShoot []gardener.Extension) func(runtime imv1.Runtime, shoot *gardener.Shoot) error { + return newExtensionsExtender([]Extension{ + { + Type: AuditlogExtensionType, + Create: func(_ imv1.Runtime, shoot gardener.Shoot) (*gardener.Extension, error) { + newAuditLogExtension, err := NewAuditLogExtension(auditLogData) + if err != nil { + return nil, err + } + + auditLogIndex := slices.IndexFunc(shoot.Spec.Extensions, func(e gardener.Extension) bool { + return e.Type == AuditlogExtensionType + }) + + if auditLogIndex == -1 { + return newAuditLogExtension, nil + } + var existingAuditLogConfig AuditlogExtensionConfig + if err := json.Unmarshal(shoot.Spec.Extensions[auditLogIndex].ProviderConfig.Raw, &existingAuditLogConfig); err != nil { + return nil, err + } + + var newAuditLogConfig AuditlogExtensionConfig + if err := json.Unmarshal(newAuditLogExtension.ProviderConfig.Raw, &newAuditLogConfig); err != nil { + return nil, err + } + + if newAuditLogConfig != existingAuditLogConfig { + return newAuditLogExtension, nil + } + + return nil, nil + }, + }, + { + Type: NetworkFilterType, + Create: func(runtime imv1.Runtime, _ gardener.Shoot) (*gardener.Extension, error) { + return NewNetworkFilterExtension(!runtime.Spec.Security.Networking.Filter.Egress.Enabled) + }, + }, + }, extensionsOnTheShoot) +} + +func newExtensionsExtender(extensionsToApply []Extension, currentGardenerExtensions []gardener.Extension) func(runtime imv1.Runtime, shoot *gardener.Shoot) error { + return func(runtime imv1.Runtime, shoot *gardener.Shoot) error { + shoot.Spec.Extensions = currentGardenerExtensions + + for _, ext := range extensionsToApply { + gardenerExtension, err := ext.Create(runtime, *shoot) + if err != nil { + return err + } + + // If the extension should not be applied we skip it + if gardenerExtension == nil { + continue + } + + index := slices.IndexFunc(currentGardenerExtensions, func(e gardener.Extension) bool { + return e.Type == ext.Type + }) + + if index == -1 { + shoot.Spec.Extensions = append(shoot.Spec.Extensions, *gardenerExtension) + } else { + shoot.Spec.Extensions[index] = *gardenerExtension + } + } + + return nil + } +} diff --git a/pkg/gardener/shoot/extender/extensions/extensions_extender_test.go b/pkg/gardener/shoot/extender/extensions/extensions_extender_test.go new file mode 100644 index 00000000..39ac8d46 --- /dev/null +++ b/pkg/gardener/shoot/extender/extensions/extensions_extender_test.go @@ -0,0 +1,402 @@ +package extensions + +import ( + "encoding/json" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + + "testing" + + gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + imv1 "github.com/kyma-project/infrastructure-manager/api/v1" + "github.com/kyma-project/infrastructure-manager/pkg/config" + "github.com/kyma-project/infrastructure-manager/pkg/gardener/shoot/extender/auditlogs" + "github.com/stretchr/testify/assert" +) + +func TestNewExtensionsExtenderForCreate(t *testing.T) { + config := config.ConverterConfig{ + DNS: config.DNSConfig{ + SecretName: "test-dns-secret", + DomainPrefix: "test-domain", + ProviderType: "test-provider", + }, + } + auditLogData := auditlogs.AuditLogData{ + TenantID: "test-auditlog-tenant", + ServiceURL: "test-auditlog-service-url", + SecretName: "doesnt matter", + } + + runtime := fixRuntimeCRForExtensionExtenderTests(false) + + shoot := &gardener.Shoot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-shoot-name", + }, + } + + extender := NewExtensionsExtenderForCreate(config, auditLogData) + + err := extender(runtime, shoot) + assert.NoError(t, err) + assert.NotNil(t, shoot.Spec.Extensions) + require.Len(t, shoot.Spec.Extensions, 5) + + orderMap := getExpectedExtensionsOrderMapForCreate() + + // checks if all Shoot extensions are correctly filled with data and are generated in the right order + for idx, ext := range shoot.Spec.Extensions { + assert.NotEmpty(t, ext.Type) + assert.Equal(t, orderMap[ext.Type], idx) + switch ext.Type { + case NetworkFilterType: + verifyNetworkFilterExtension(t, ext, true) + + case CertExtensionType: + verifyCertExtension(t, ext) + + case DNSExtensionType: + verifyDNSExtension(t, ext) + + case OidcExtensionType: + verifyOIDCExtension(t, ext) + + case AuditlogExtensionType: + verifyAuditLogExtension(t, ext, auditLogData) + } + } +} + +func TestNewExtensionsExtenderForPatch(t *testing.T) { + + oldAuditLogData := auditlogs.AuditLogData{ + TenantID: "test-auditlog-tenant", + ServiceURL: "test-auditlog-service-url", + SecretName: "doesnt matter", + } + + newAuditLogData := auditlogs.AuditLogData{ + TenantID: "test-auditlog-new-tenant", + ServiceURL: "test-auditlog-new-service", + SecretName: "doesnt matter", + } + + for _, testCase := range []struct { + name string + auditLogData auditlogs.AuditLogData + disableNetworkFilter bool + previousExtensions []gardener.Extension + }{ + { + name: "Existing extensions should not change order during patching if nothing has changed", + previousExtensions: fixAllExtensionsOnTheShoot(), + auditLogData: oldAuditLogData, + disableNetworkFilter: true, + }, + { + name: "Should update Audit Log extension without changing order and data of other extensions", + previousExtensions: fixAllExtensionsOnTheShoot(), + auditLogData: newAuditLogData, + disableNetworkFilter: true, + }, + { + name: "Should update Network filter extension without changing order and data of other extensions", + previousExtensions: fixAllExtensionsOnTheShoot(), + auditLogData: oldAuditLogData, + disableNetworkFilter: false, + }, + { + name: "Should add Network filter extension at the end without changing order and data of other extensions", + previousExtensions: fixExtensionsOnTheShootWithoutNetworkFilter(), + auditLogData: oldAuditLogData, + disableNetworkFilter: true, + }, + { + name: "Should add Auditlog extension at the end without changing order and data of other extensions", + previousExtensions: fixExtensionsOnTheShootWithoutAuditLogs(), + auditLogData: oldAuditLogData, + disableNetworkFilter: true, + }, + { + name: "Should add Auditlog and Network filter extensions at the end without changing order and data of other extensions", + previousExtensions: fixExtensionsOnTheShootWithoutAuditLogsAndNetworkFilter(), + auditLogData: oldAuditLogData, + disableNetworkFilter: true, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + runtime := fixRuntimeCRForExtensionExtenderTests(!testCase.disableNetworkFilter) + + shoot := &gardener.Shoot{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-shoot-name", + }, + } + + extender := NewExtensionsExtenderForPatch(testCase.auditLogData, testCase.previousExtensions) + orderMap := getExpectedExtensionsOrderMap(testCase.previousExtensions) + + err := extender(runtime, shoot) + assert.NoError(t, err) + assert.NotNil(t, shoot.Spec.Extensions) + require.Len(t, shoot.Spec.Extensions, 5) + + for idx, ext := range shoot.Spec.Extensions { + assert.NotEmpty(t, ext.Type) + assert.Equal(t, orderMap[ext.Type], idx) + + switch ext.Type { + case NetworkFilterType: + verifyNetworkFilterExtension(t, ext, testCase.disableNetworkFilter) + + case CertExtensionType: + verifyCertExtension(t, ext) + + case DNSExtensionType: + verifyDNSExtension(t, ext) + + case OidcExtensionType: + verifyOIDCExtension(t, ext) + + case AuditlogExtensionType: + verifyAuditLogExtension(t, ext, testCase.auditLogData) + } + } + }) + } +} + +func fixAllExtensionsOnTheShoot() []gardener.Extension { + return []gardener.Extension{ + { + Type: AuditlogExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"type":"standard","tenantID":"test-auditlog-tenant","serviceURL":"test-auditlog-service-url","secretReferenceName":"test-auditlog-secret"}`), + }, + }, + { + Type: DNSExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"service.dns.extensions.gardener.cloud/v1alpha1","dnsProviderReplication":{"enabled":true},"syncProvidersFromShootSpecDNS":true,"providers":[{"domains":{"include":["test-shoot-name.test-domain"],"exclude":null},"secretName":"test-dns-secret","type":"test-provider"}],"kind":"DNSConfig"}`), + }, + }, + { + Type: CertExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"service.cert.extensions.gardener.cloud/v1alpha1","kind":"CertConfig","shootIssuers":{"enabled":true}}`), + }, + }, + { + Type: NetworkFilterType, + Disabled: ptr.To(true), + }, + { + Type: OidcExtensionType, + Disabled: ptr.To(false), + }, + } +} + +func fixExtensionsOnTheShootWithoutAuditLogs() []gardener.Extension { + return []gardener.Extension{ + { + Type: DNSExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"service.dns.extensions.gardener.cloud/v1alpha1","dnsProviderReplication":{"enabled":true},"syncProvidersFromShootSpecDNS":true,"providers":[{"domains":{"include":["test-shoot-name.test-domain"],"exclude":null},"secretName":"test-dns-secret","type":"test-provider"}],"kind":"DNSConfig"}`), + }, + }, + { + Type: CertExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"service.cert.extensions.gardener.cloud/v1alpha1","kind":"CertConfig","shootIssuers":{"enabled":true}}`), + }, + }, + { + Type: NetworkFilterType, + Disabled: ptr.To(true), + }, + { + Type: OidcExtensionType, + Disabled: ptr.To(false), + }, + } +} + +func fixExtensionsOnTheShootWithoutNetworkFilter() []gardener.Extension { + return []gardener.Extension{ + { + Type: AuditlogExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"type":"standard","tenantID":"test-auditlog-tenant","serviceURL":"test-auditlog-service-url","secretReferenceName":"test-auditlog-secret"}`), + }, + }, + { + Type: DNSExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"service.dns.extensions.gardener.cloud/v1alpha1","dnsProviderReplication":{"enabled":true},"syncProvidersFromShootSpecDNS":true,"providers":[{"domains":{"include":["test-shoot-name.test-domain"],"exclude":null},"secretName":"test-dns-secret","type":"test-provider"}],"kind":"DNSConfig"}`), + }, + }, + { + Type: CertExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"service.cert.extensions.gardener.cloud/v1alpha1","kind":"CertConfig","shootIssuers":{"enabled":true}}`), + }, + }, + { + Type: OidcExtensionType, + Disabled: ptr.To(false), + }, + } +} + +func fixExtensionsOnTheShootWithoutAuditLogsAndNetworkFilter() []gardener.Extension { + return []gardener.Extension{ + { + Type: DNSExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"service.dns.extensions.gardener.cloud/v1alpha1","dnsProviderReplication":{"enabled":true},"syncProvidersFromShootSpecDNS":true,"providers":[{"domains":{"include":["test-shoot-name.test-domain"],"exclude":null},"secretName":"test-dns-secret","type":"test-provider"}],"kind":"DNSConfig"}`), + }, + }, + { + Type: CertExtensionType, + ProviderConfig: &runtime.RawExtension{ + Raw: []byte(`{"apiVersion":"service.cert.extensions.gardener.cloud/v1alpha1","kind":"CertConfig","shootIssuers":{"enabled":true}}`), + }, + }, + { + Type: OidcExtensionType, + Disabled: ptr.To(false), + }, + } +} + +// returns a map with the expected index order of extensions for different ExtenderForPatch unit tests +func getExpectedExtensionsOrderMap(extensions []gardener.Extension) map[string]int { + extensionOrderMap := make(map[string]int) + + for idx, ext := range extensions { + extensionOrderMap[ext.Type] = idx + } + + if len(extensions) == 4 { + if _, ok := extensionOrderMap[AuditlogExtensionType]; !ok { + extensionOrderMap[AuditlogExtensionType] = 4 + } + + if _, ok := extensionOrderMap[NetworkFilterType]; !ok { + extensionOrderMap[NetworkFilterType] = 4 + } + } + + if len(extensions) == 3 { + extensionOrderMap[AuditlogExtensionType] = 3 + extensionOrderMap[NetworkFilterType] = 4 + } + + return extensionOrderMap +} + +// returns a map with the expected index order of extensions for ExtenderForCreate create unit test +func getExpectedExtensionsOrderMapForCreate() map[string]int { + extensionOrderMap := make(map[string]int) + + extensionOrderMap[NetworkFilterType] = 0 + extensionOrderMap[CertExtensionType] = 1 + extensionOrderMap[DNSExtensionType] = 2 + extensionOrderMap[OidcExtensionType] = 3 + extensionOrderMap[AuditlogExtensionType] = 4 + + return extensionOrderMap +} + +func verifyAuditLogExtension(t *testing.T, ext gardener.Extension, expected auditlogs.AuditLogData) { + var auditlogConfig AuditlogExtensionConfig + + err := json.Unmarshal(ext.ProviderConfig.Raw, &auditlogConfig) + require.NoError(t, err) + + assert.Equal(t, "standard", auditlogConfig.Type) + assert.Equal(t, expected.TenantID, auditlogConfig.TenantID) + assert.Equal(t, expected.ServiceURL, auditlogConfig.ServiceURL) + assert.Equal(t, auditlogReferenceName, auditlogConfig.SecretReferenceName) + assert.Equal(t, "service.auditlog.extensions.gardener.cloud/v1alpha1", auditlogConfig.APIVersion) + assert.Equal(t, "AuditlogConfig", auditlogConfig.Kind) +} + +func verifyOIDCExtension(t *testing.T, ext gardener.Extension) { + require.NotNil(t, ext.Disabled) + assert.Equal(t, false, *ext.Disabled) +} + +func verifyDNSExtension(t *testing.T, ext gardener.Extension) { + require.NotNil(t, ext.ProviderConfig) + require.NotNil(t, ext.ProviderConfig.Raw) + + var dnsConfig DNSExtensionProviderConfig + + err := json.Unmarshal(ext.ProviderConfig.Raw, &dnsConfig) + require.NoError(t, err) + require.NotNil(t, dnsConfig.DNSProviderReplication) + require.NotNil(t, dnsConfig.SyncProvidersFromShootSpecDNS) + + assert.Equal(t, "service.dns.extensions.gardener.cloud/v1alpha1", dnsConfig.APIVersion) + assert.Equal(t, true, dnsConfig.DNSProviderReplication.Enabled) + assert.Equal(t, true, *dnsConfig.SyncProvidersFromShootSpecDNS) + assert.Equal(t, "DNSConfig", dnsConfig.Kind) + + require.Len(t, dnsConfig.Providers, 1) + provider := dnsConfig.Providers[0] + + require.NotNil(t, provider.Domains) + require.NotNil(t, provider.SecretName) + require.NotNil(t, provider.Type) + + assert.Equal(t, "test-dns-secret", *provider.SecretName) + assert.Equal(t, "test-provider", *provider.Type) + + require.Len(t, provider.Domains.Include, 1) + assert.Equal(t, "test-shoot-name.test-domain", provider.Domains.Include[0]) +} + +func verifyCertExtension(t *testing.T, ext gardener.Extension) { + require.NotNil(t, ext.ProviderConfig) + require.NotNil(t, ext.ProviderConfig.Raw) + + var certConfig ExtensionProviderConfig + + err := json.Unmarshal(ext.ProviderConfig.Raw, &certConfig) + require.NoError(t, err) + require.NotNil(t, certConfig.ShootIssuers) + assert.Equal(t, "service.cert.extensions.gardener.cloud/v1alpha1", certConfig.APIVersion) + assert.Equal(t, true, certConfig.ShootIssuers.Enabled) + assert.Equal(t, "CertConfig", certConfig.Kind) +} + +func verifyNetworkFilterExtension(t *testing.T, ext gardener.Extension, isDisabled bool) { + require.NotNil(t, ext.Disabled) + assert.Equal(t, isDisabled, *ext.Disabled) +} + +func fixRuntimeCRForExtensionExtenderTests(networkFilterEnabled bool) imv1.Runtime { + runtime := imv1.Runtime{ + Spec: imv1.RuntimeSpec{ + Shoot: imv1.RuntimeShoot{ + Name: "myshoot", + }, + Security: imv1.Security{ + Networking: imv1.NetworkingSecurity{ + Filter: imv1.Filter{ + Egress: imv1.Egress{ + Enabled: networkFilterEnabled, + }, + }, + }, + }, + }, + } + + return runtime +} diff --git a/pkg/gardener/shoot/extender/extensions/networkfilter.go b/pkg/gardener/shoot/extender/extensions/networkfilter.go new file mode 100644 index 00000000..812f8059 --- /dev/null +++ b/pkg/gardener/shoot/extender/extensions/networkfilter.go @@ -0,0 +1,12 @@ +package extensions + +import gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + +const NetworkFilterType = "shoot-networking-filter" + +func NewNetworkFilterExtension(disabled bool) (*gardener.Extension, error) { + return &gardener.Extension{ + Type: NetworkFilterType, + Disabled: &disabled, + }, nil +} diff --git a/pkg/gardener/shoot/extender/extensions/oidc.go b/pkg/gardener/shoot/extender/extensions/oidc.go new file mode 100644 index 00000000..a448fa68 --- /dev/null +++ b/pkg/gardener/shoot/extender/extensions/oidc.go @@ -0,0 +1,17 @@ +package extensions + +import ( + gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + "k8s.io/utils/ptr" +) + +const ( + OidcExtensionType = "shoot-oidc-service" +) + +func NewOIDCExtension() (*gardener.Extension, error) { + return &gardener.Extension{ + Type: OidcExtensionType, + Disabled: ptr.To(false), + }, nil +} diff --git a/pkg/gardener/shoot/extender/oidc.go b/pkg/gardener/shoot/extender/oidc.go index 0b1e2759..6685bf6d 100644 --- a/pkg/gardener/shoot/extender/oidc.go +++ b/pkg/gardener/shoot/extender/oidc.go @@ -4,7 +4,6 @@ import ( gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" imv1 "github.com/kyma-project/infrastructure-manager/api/v1" "github.com/kyma-project/infrastructure-manager/pkg/config" - "k8s.io/utils/ptr" ) const ( @@ -17,10 +16,6 @@ func shouldDefaultOidcConfig(config gardener.OIDCConfig) bool { func NewOidcExtender(oidcProvider config.OidcProvider) func(runtime imv1.Runtime, shoot *gardener.Shoot) error { return func(runtime imv1.Runtime, shoot *gardener.Shoot) error { - if CanEnableExtension(runtime) { - setOIDCExtension(shoot) - } - oidcConfig := runtime.Spec.Shoot.Kubernetes.KubeAPIServer.OidcConfig if shouldDefaultOidcConfig(oidcConfig) { oidcConfig = gardener.OIDCConfig{ @@ -38,19 +33,6 @@ func NewOidcExtender(oidcProvider config.OidcProvider) func(runtime imv1.Runtime } } -func CanEnableExtension(runtime imv1.Runtime) bool { - return runtime.Labels["operator.kyma-project.io/created-by-migrator"] != "true" -} - -func setOIDCExtension(shoot *gardener.Shoot) { - oidcService := gardener.Extension{ - Type: OidcExtensionType, - Disabled: ptr.To(false), - } - - shoot.Spec.Extensions = append(shoot.Spec.Extensions, oidcService) -} - func setKubeAPIServerOIDCConfig(shoot *gardener.Shoot, oidcConfig gardener.OIDCConfig) { shoot.Spec.Kubernetes.KubeAPIServer = &gardener.KubeAPIServerConfig{ OIDCConfig: &gardener.OIDCConfig{ diff --git a/pkg/gardener/shoot/extender/oidc_test.go b/pkg/gardener/shoot/extender/oidc_test.go index 5a749484..f39b2541 100644 --- a/pkg/gardener/shoot/extender/oidc_test.go +++ b/pkg/gardener/shoot/extender/oidc_test.go @@ -12,77 +12,44 @@ import ( ) func TestOidcExtender(t *testing.T) { - const migratorLabel = "operator.kyma-project.io/created-by-migrator" - for _, testCase := range []struct { - name string - migratorLabel map[string]string - expectedOidcExtensionEnabled bool - }{ - { - name: "label created-by-migrator=true should not configure OIDC", - migratorLabel: map[string]string{migratorLabel: "true"}, - expectedOidcExtensionEnabled: false, - }, - { - name: "label created-by-migrator=false should configure OIDC", - migratorLabel: map[string]string{migratorLabel: "false"}, - expectedOidcExtensionEnabled: true, - }, - { - name: "label created-by-migrator unset should configure OIDC", - migratorLabel: nil, - expectedOidcExtensionEnabled: true, - }, - } { - t.Run(testCase.name, func(t *testing.T) { - // given - defaultOidc := config.OidcProvider{ - ClientID: "client-id", - GroupsClaim: "groups", - IssuerURL: "https://my.cool.tokens.com", - SigningAlgs: []string{"RS256"}, - UsernameClaim: "sub", - UsernamePrefix: "-", - } + defaultOidc := config.OidcProvider{ + ClientID: "client-id", + GroupsClaim: "groups", + IssuerURL: "https://my.cool.tokens.com", + SigningAlgs: []string{"RS256"}, + UsernameClaim: "sub", + UsernamePrefix: "-", + } - shoot := fixEmptyGardenerShoot("test", "kcp-system") - runtimeShoot := imv1.Runtime{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - migratorLabel: testCase.migratorLabel[migratorLabel], - }, - }, - Spec: imv1.RuntimeSpec{ - Shoot: imv1.RuntimeShoot{ - Kubernetes: imv1.Kubernetes{ - KubeAPIServer: imv1.APIServer{ - OidcConfig: gardener.OIDCConfig{ - ClientID: &defaultOidc.ClientID, - GroupsClaim: &defaultOidc.GroupsClaim, - IssuerURL: &defaultOidc.IssuerURL, - SigningAlgs: defaultOidc.SigningAlgs, - UsernameClaim: &defaultOidc.UsernameClaim, - }, + t.Run("OIDC should be added in create scenario", func(t *testing.T) { + // given + shoot := fixEmptyGardenerShoot("test", "kcp-system") + runtimeShoot := imv1.Runtime{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: imv1.RuntimeSpec{ + Shoot: imv1.RuntimeShoot{ + Kubernetes: imv1.Kubernetes{ + KubeAPIServer: imv1.APIServer{ + OidcConfig: gardener.OIDCConfig{ + ClientID: &defaultOidc.ClientID, + GroupsClaim: &defaultOidc.GroupsClaim, + IssuerURL: &defaultOidc.IssuerURL, + SigningAlgs: defaultOidc.SigningAlgs, + UsernameClaim: &defaultOidc.UsernameClaim, }, }, }, }, - } + }, + } - // when - extender := NewOidcExtender(defaultOidc) - err := extender(runtimeShoot, &shoot) + // when + extender := NewOidcExtender(defaultOidc) + err := extender(runtimeShoot, &shoot) - // then - require.NoError(t, err) + // then + require.NoError(t, err) - assert.Equal(t, runtimeShoot.Spec.Shoot.Kubernetes.KubeAPIServer.OidcConfig, *shoot.Spec.Kubernetes.KubeAPIServer.OIDCConfig) - if testCase.expectedOidcExtensionEnabled { - assert.Equal(t, testCase.expectedOidcExtensionEnabled, !*shoot.Spec.Extensions[0].Disabled) - assert.Equal(t, "shoot-oidc-service", shoot.Spec.Extensions[0].Type) - } else { - assert.Equal(t, 0, len(shoot.Spec.Extensions)) - } - }) - } + assert.Equal(t, runtimeShoot.Spec.Shoot.Kubernetes.KubeAPIServer.OidcConfig, *shoot.Spec.Kubernetes.KubeAPIServer.OIDCConfig) + }) } diff --git a/pkg/gardener/shoot/extender/resources.go b/pkg/gardener/shoot/extender/resources.go new file mode 100644 index 00000000..76f6f1e9 --- /dev/null +++ b/pkg/gardener/shoot/extender/resources.go @@ -0,0 +1,16 @@ +package extender + +import ( + gardener "github.com/gardener/gardener/pkg/apis/core/v1beta1" + imv1 "github.com/kyma-project/infrastructure-manager/api/v1" +) + +func NewResourcesExtenderForPatch(resources []gardener.NamedResourceReference) func(runtime imv1.Runtime, shoot *gardener.Shoot) error { + return func(_ imv1.Runtime, shoot *gardener.Shoot) error { + if resources != nil { + shoot.Spec.Resources = resources + } + + return nil + } +}