From ea75326a0e2f1108f4297dd04b8090cd75266898 Mon Sep 17 00:00:00 2001 From: Benjamin Somhegyi Date: Mon, 29 Apr 2024 12:09:21 +0200 Subject: [PATCH] provisioner: add support for configuring AWS IMDSv2 access method (#3429) * provisioner: add support for configuring AWS IMDSv2 access method * address comments --- components/provisioner/cmd/init.go | 3 +- components/provisioner/cmd/main.go | 6 +- ...resolver_integration_with_gardener_test.go | 3 +- .../internal/model/gardener_config.go | 51 ++++++++++++++- .../internal/model/gardener_config_test.go | 52 ++++++++++----- .../internal/model/infrastructure.go | 15 +++++ .../model/infrastructure/aws/worker.go | 34 ++++++++++ .../internal/provisioning/input_converter.go | 8 ++- .../provisioning/input_converter_test.go | 23 +++++-- .../internal/provisioning/service_test.go | 10 +-- .../provisioner/pkg/gqlschema/models_gen.go | 10 +-- .../provisioner/pkg/gqlschema/schema.graphql | 2 + .../provisioner/pkg/gqlschema/schema_gen.go | 64 ++++++++++++++++++- 13 files changed, 239 insertions(+), 42 deletions(-) create mode 100644 components/provisioner/internal/model/infrastructure/aws/worker.go diff --git a/components/provisioner/cmd/init.go b/components/provisioner/cmd/init.go index 08f0fe76b6..142cdafcbd 100644 --- a/components/provisioner/cmd/init.go +++ b/components/provisioner/cmd/init.go @@ -45,11 +45,12 @@ func newProvisioningService( shootUpgradeQueue queue.OperationQueue, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate bool, + defaultEnableIMDSv2 bool, runtimeRegistrationEnabled bool, dynamicKubeconfigProvider DynamicKubeconfigProvider) provisioning.Service { uuidGenerator := uuid.NewUUIDGenerator() - inputConverter := provisioning.NewInputConverter(uuidGenerator, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate) + inputConverter := provisioning.NewInputConverter(uuidGenerator, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, defaultEnableIMDSv2) graphQLConverter := provisioning.NewGraphQLConverter() return provisioning.NewProvisioningService( diff --git a/components/provisioner/cmd/main.go b/components/provisioner/cmd/main.go index 66ddd81f79..6c7eef009b 100644 --- a/components/provisioner/cmd/main.go +++ b/components/provisioner/cmd/main.go @@ -75,6 +75,7 @@ type config struct { ClusterCleanupResourceSelector string `envconfig:"default=https://service-manager."` DefaultEnableKubernetesVersionAutoUpdate bool `envconfig:"default=false"` DefaultEnableMachineImageVersionAutoUpdate bool `envconfig:"default=false"` + DefaultEnableIMDSv2 bool `envconfig:"default=false"` } LatestDownloadedReleases int `envconfig:"default=5"` @@ -99,7 +100,7 @@ func (c *config) String() string { "DeprovisioningNoInstallTimeoutClusterDeletion: %s, DeprovisioningNoInstallTimeoutWaitingForClusterDeletion: %s "+ "ShootUpgradeTimeout: %s, "+ "OperatorRoleBindingL2SubjectName: %s, OperatorRoleBindingL3SubjectName: %s, OperatorRoleBindingCreatingForAdmin: %t "+ - "GardenerProject: %s, GardenerKubeconfigPath: %s, GardenerAuditLogsPolicyConfigMap: %s, AuditLogsTenantConfigPath: %s, "+ + "GardenerProject: %s, GardenerKubeconfigPath: %s, GardenerAuditLogsPolicyConfigMap: %s, AuditLogsTenantConfigPath: %s, DefaultEnableIMDSv2: %v"+ "LatestDownloadedReleases: %d, DownloadPreReleases: %v, "+ "EnqueueInProgressOperations: %v"+ "LogLevel: %s", @@ -114,7 +115,7 @@ func (c *config) String() string { c.DeprovisioningTimeout.ClusterDeletion.String(), c.DeprovisioningTimeout.WaitingForClusterDeletion.String(), c.ProvisioningTimeout.ShootUpgrade.String(), c.OperatorRoleBinding.L2SubjectName, c.OperatorRoleBinding.L3SubjectName, c.OperatorRoleBinding.CreatingForAdmin, - c.Gardener.Project, c.Gardener.KubeconfigPath, c.Gardener.AuditLogsPolicyConfigMap, c.Gardener.AuditLogsTenantConfigPath, + c.Gardener.Project, c.Gardener.KubeconfigPath, c.Gardener.AuditLogsPolicyConfigMap, c.Gardener.AuditLogsTenantConfigPath, c.Gardener.DefaultEnableIMDSv2, c.LatestDownloadedReleases, c.DownloadPreReleases, c.EnqueueInProgressOperations, c.LogLevel) @@ -232,6 +233,7 @@ func main() { shootUpgradeQueue, cfg.Gardener.DefaultEnableKubernetesVersionAutoUpdate, cfg.Gardener.DefaultEnableMachineImageVersionAutoUpdate, + cfg.Gardener.DefaultEnableIMDSv2, cfg.RuntimeRegistrationEnabled, kubeconfigProvider, ) diff --git a/components/provisioner/internal/api/resolver_integration_with_gardener_test.go b/components/provisioner/internal/api/resolver_integration_with_gardener_test.go index d6a724339b..737449f5b7 100644 --- a/components/provisioner/internal/api/resolver_integration_with_gardener_test.go +++ b/components/provisioner/internal/api/resolver_integration_with_gardener_test.go @@ -77,6 +77,7 @@ const ( defaultEnableKubernetesVersionAutoUpdate = false defaultEnableMachineImageVersionAutoUpdate = false + defaultEnableIMDSv2 = true mockedKubeconfig = `apiVersion: v1 clusters: @@ -203,7 +204,7 @@ func TestProvisioning_ProvisionRuntimeWithDatabase(t *testing.T) { uuidGenerator := uuid.NewUUIDGenerator() provisioner := gardener.NewProvisioner(namespace, shootInterface, dbsFactory, auditLogPolicyCMName, maintenanceWindowConfigPath) - inputConverter := provisioning.NewInputConverter(uuidGenerator, "Project", defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate) + inputConverter := provisioning.NewInputConverter(uuidGenerator, "Project", defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, defaultEnableIMDSv2) graphQLConverter := provisioning.NewGraphQLConverter() runtimeRegistrationEnabled := true diff --git a/components/provisioner/internal/model/gardener_config.go b/components/provisioner/internal/model/gardener_config.go index a0288445a1..6f873f5c0e 100644 --- a/components/provisioner/internal/model/gardener_config.go +++ b/components/provisioner/internal/model/gardener_config.go @@ -29,6 +29,7 @@ const ( ) var networkingType = "calico" +var awsIMDSv2HTTPPutResponseHopLimit int64 = 2 type OIDCConfig struct { ClientID string `json:"clientID"` @@ -553,8 +554,9 @@ func (c AWSGardenerConfig) AsProviderSpecificConfig() gqlschema.ProviderSpecific } return gqlschema.AWSProviderConfig{ - AwsZones: zones, - VpcCidr: &c.input.VpcCidr, + AwsZones: zones, + VpcCidr: &c.input.VpcCidr, + EnableIMDSv2: c.input.EnableIMDSv2, } } @@ -590,7 +592,41 @@ func (c AWSGardenerConfig) ValidateShootConfigChange(shoot *gardener_types.Shoot } func (c AWSGardenerConfig) EditShootConfig(gardenerConfig GardenerConfig, shoot *gardener_types.Shoot) apperrors.AppError { - return updateShootConfig(gardenerConfig, shoot) + err := updateShootConfig(gardenerConfig, shoot) + if err != nil { + return err + } + + if c.input.EnableIMDSv2 != nil && *c.input.EnableIMDSv2 { + var ( + workerConfig *aws.WorkerConfig + ) + if shoot.Spec.Provider.Workers[0].ProviderConfig == nil { + workerConfig = NewAWSWorkerConfig(awsIMDSv2HTTPPutResponseHopLimit) + } else { + workerConfig = &aws.WorkerConfig{} + err := json.Unmarshal(shoot.Spec.Provider.Workers[0].ProviderConfig.Raw, &workerConfig) + if err != nil { + return apperrors.Internal("error decoding aws worker config: %s", err.Error()) + } + if workerConfig.InstanceMetadataOptions == nil { + workerConfig.InstanceMetadataOptions = &aws.InstanceMetadataOptions{} + } + if workerConfig.InstanceMetadataOptions.HTTPTokens == nil || *workerConfig.InstanceMetadataOptions.HTTPTokens != aws.HTTPTokensRequired { + workerConfig.InstanceMetadataOptions.HTTPTokens = &aws.HTTPTokensRequired + } + if workerConfig.InstanceMetadataOptions.HTTPPutResponseHopLimit == nil || *workerConfig.InstanceMetadataOptions.HTTPPutResponseHopLimit != awsIMDSv2HTTPPutResponseHopLimit { + workerConfig.InstanceMetadataOptions.HTTPPutResponseHopLimit = &awsIMDSv2HTTPPutResponseHopLimit + } + } + jsonWCData, err := json.Marshal(workerConfig) + if err != nil { + return apperrors.Internal("error encoding aws worker config: %s", err.Error()) + } + shoot.Spec.Provider.Workers[0].ProviderConfig = &apimachineryRuntime.RawExtension{Raw: jsonWCData} + } + + return nil } func (c AWSGardenerConfig) ExtendShootConfig(gardenerConfig GardenerConfig, shoot *gardener_types.Shoot) apperrors.AppError { @@ -612,6 +648,15 @@ func (c AWSGardenerConfig) ExtendShootConfig(gardenerConfig GardenerConfig, shoo return apperrors.Internal("error encoding control plane config: %s", err.Error()) } + if c.input.EnableIMDSv2 != nil && *c.input.EnableIMDSv2 { + awsWorkerConfig := NewAWSWorkerConfig(awsIMDSv2HTTPPutResponseHopLimit) + jsonWCData, err := json.Marshal(awsWorkerConfig) + if err != nil { + return apperrors.Internal("error encoding aws worker config: %s", err.Error()) + } + workers[0].ProviderConfig = &apimachineryRuntime.RawExtension{Raw: jsonWCData} + } + shoot.Spec.Provider = gardener_types.Provider{ Type: "aws", ControlPlaneConfig: &apimachineryRuntime.RawExtension{Raw: jsonCPData}, diff --git a/components/provisioner/internal/model/gardener_config_test.go b/components/provisioner/internal/model/gardener_config_test.go index 1d3d3f4f51..be9522c857 100644 --- a/components/provisioner/internal/model/gardener_config_test.go +++ b/components/provisioner/internal/model/gardener_config_test.go @@ -23,7 +23,7 @@ func Test_NewGardenerConfigFromJSON(t *testing.T) { azureConfigJSON := `{"vnetCidr":"10.10.11.11/255", "zones":["fix-az-zone-1", "fix-az-zone-2"], "enableNatGateway":true, "idleConnectionTimeoutMinutes":4}` azureNoZonesConfigJSON := `{"vnetCidr":"10.10.11.11/255"}` azureZoneSubnetsConfigJSON := `{"vnetCidr":"10.10.11.11/255", "azureZones":[{"name":1,"cidr":"10.10.11.12/255"}, {"name":2,"cidr":"10.10.11.13/255"}], "enableNatGateway":true, "idleConnectionTimeoutMinutes":4}` - awsConfigJSON := `{"vpcCidr":"10.10.11.11/255","awsZones":[{"name":"zone","publicCidr":"10.10.11.12/255","internalCidr":"10.10.11.13/255","workerCidr":"10.10.11.11/255"}]} + awsConfigJSON := `{"vpcCidr":"10.10.11.11/255","awsZones":[{"name":"zone","publicCidr":"10.10.11.12/255","internalCidr":"10.10.11.13/255","workerCidr":"10.10.11.11/255"}], "enableIMDSv2": true} ` for _, testCase := range []struct { @@ -109,7 +109,8 @@ func Test_NewGardenerConfigFromJSON(t *testing.T) { WorkerCidr: "10.10.11.11/255", }, }, - VpcCidr: "10.10.11.11/255", + VpcCidr: "10.10.11.11/255", + EnableIMDSv2: util.PtrTo(true), }, }, expectedProviderSpecificConfig: gqlschema.AWSProviderConfig{ @@ -121,7 +122,8 @@ func Test_NewGardenerConfigFromJSON(t *testing.T) { WorkerCidr: util.PtrTo("10.10.11.11/255"), }, }, - VpcCidr: util.PtrTo("10.10.11.11/255"), + VpcCidr: util.PtrTo("10.10.11.11/255"), + EnableIMDSv2: util.PtrTo(true), }, }, } { @@ -158,7 +160,7 @@ func TestGardenerConfig_ToShootTemplate(t *testing.T) { azureZoneSubnetsGardenerProvider, err := NewAzureGardenerConfig(fixAzureZoneSubnetsInput(true)) require.NoError(t, err) - awsGardenerProvider, err := NewAWSGardenerConfig(fixAWSGardenerInput()) + awsGardenerProvider, err := NewAWSGardenerConfig(fixAWSGardenerInput(true)) require.NoError(t, err) for _, testCase := range []struct { @@ -202,7 +204,7 @@ func TestGardenerConfig_ToShootTemplate(t *testing.T) { Raw: []byte(`{"kind":"InfrastructureConfig","apiVersion":"gcp.provider.extensions.gardener.cloud/v1alpha1","networks":{"worker":"10.10.10.10/255","workers":"10.10.10.10/255"}}`), }, Workers: []gardener_types.Worker{ - fixWorker([]string{"fix-zone-1", "fix-zone-2"}), + fixWorker([]string{"fix-zone-1", "fix-zone-2"}, nil), }, }, Purpose: &purpose, @@ -284,7 +286,7 @@ func TestGardenerConfig_ToShootTemplate(t *testing.T) { Raw: []byte(`{"kind":"InfrastructureConfig","apiVersion":"azure.provider.extensions.gardener.cloud/v1alpha1","networks":{"vnet":{"cidr":"10.10.11.11/255"},"workers":"10.10.10.10/255","natGateway":{"enabled":true,"idleConnectionTimeoutMinutes":4}},"zoned":true}`), }, Workers: []gardener_types.Worker{ - fixWorker([]string{"fix-zone-1", "fix-zone-2"}), + fixWorker([]string{"fix-zone-1", "fix-zone-2"}, nil), }, }, Purpose: &purpose, @@ -366,7 +368,7 @@ func TestGardenerConfig_ToShootTemplate(t *testing.T) { Raw: []byte(`{"kind":"InfrastructureConfig","apiVersion":"azure.provider.extensions.gardener.cloud/v1alpha1","networks":{"vnet":{"cidr":"10.10.11.11/255"},"workers":"10.10.10.10/255"},"zoned":false}`), }, Workers: []gardener_types.Worker{ - fixWorker(nil), + fixWorker(nil, nil), }, }, Purpose: &purpose, @@ -448,7 +450,7 @@ func TestGardenerConfig_ToShootTemplate(t *testing.T) { Raw: []byte(`{"kind":"InfrastructureConfig","apiVersion":"azure.provider.extensions.gardener.cloud/v1alpha1","networks":{"vnet":{"cidr":"10.10.11.11/255"},"zones":[{"name":1,"cidr":"10.10.11.12/255","natGateway":{"enabled":true,"idleConnectionTimeoutMinutes":4}},{"name":2,"cidr":"10.10.11.13/255","natGateway":{"enabled":true,"idleConnectionTimeoutMinutes":4}}]},"zoned":true}`), }, Workers: []gardener_types.Worker{ - fixWorker([]string{"1", "2"}), + fixWorker([]string{"1", "2"}, nil), }, }, Purpose: &purpose, @@ -530,7 +532,9 @@ func TestGardenerConfig_ToShootTemplate(t *testing.T) { Raw: []byte(`{"kind":"InfrastructureConfig","apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1","networks":{"vpc":{"cidr":"10.10.11.11/255"},"zones":[{"name":"zone","internal":"10.10.11.13/255","public":"10.10.11.12/255","workers":"10.10.11.12/255"}]}}`), }, Workers: []gardener_types.Worker{ - fixWorker([]string{"zone"}), + fixWorker([]string{"zone"}, &apimachineryRuntime.RawExtension{ + Raw: []byte(`{"kind":"WorkerConfig","apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1","instanceMetadataOptions":{"httpTokens":"required","httpPutResponseHopLimit":2}}`), + }), }, }, Purpose: &purpose, @@ -645,7 +649,10 @@ func TestEditShootConfig(t *testing.T) { ToWorker()). ToShoot() - awsProviderConfig, err := NewAWSGardenerConfig(fixAWSGardenerInput()) + awsProviderConfig, err := NewAWSGardenerConfig(fixAWSGardenerInput(false)) + require.NoError(t, err) + + awsProviderConfigWithEnableIMDSv2, err := NewAWSGardenerConfig(fixAWSGardenerInput(true)) require.NoError(t, err) azureProviderConfig, err := NewAzureGardenerConfig(fixAzureGardenerInput(zones, nil)) @@ -665,6 +672,11 @@ func TestEditShootConfig(t *testing.T) { Raw: []byte(azureProviderConfig.RawJSON()), } + expectedShootConfigWithIMDSv2Enabled := expectedShoot.DeepCopy() + expectedShootConfigWithIMDSv2Enabled.Spec.Provider.Workers[0].ProviderConfig = &apimachineryRuntime.RawExtension{ + Raw: []byte(`{"kind":"WorkerConfig","apiVersion":"aws.provider.extensions.gardener.cloud/v1alpha1","instanceMetadataOptions":{"httpTokens":"required","httpPutResponseHopLimit":2}}`), + } + expectedShootWithNATEnabled := expectedShoot.DeepCopy() expectedShootWithNATEnabled.Spec.Provider.InfrastructureConfig = &apimachineryRuntime.RawExtension{ Raw: []byte(`{"networks":{"vnet":{"cidr":"10.10.11.11/255"},"natGateway":{"enabled":true,"idleConnectionTimeoutMinutes":4}},"zoned":false}`), @@ -693,6 +705,12 @@ func TestEditShootConfig(t *testing.T) { initialShoot: initialShoot.DeepCopy(), expectedShoot: expectedShoot.DeepCopy(), }, + {description: "should edit AWS shoot template with IMDSv2 enabled", + provider: "aws", + upgradeConfig: fixGardenerConfig("aws", awsProviderConfigWithEnableIMDSv2), + initialShoot: initialShoot.DeepCopy(), + expectedShoot: expectedShootConfigWithIMDSv2Enabled.DeepCopy(), + }, {description: "should edit Azure shoot template", provider: "az", upgradeConfig: fixGardenerConfig("az", azureProviderConfig), @@ -806,7 +824,7 @@ func fixGardenerConfig(provider string, providerCfg GardenerProviderConfig) Gard } } -func fixAWSGardenerInput() *gqlschema.AWSProviderConfigInput { +func fixAWSGardenerInput(enableIMDSv2 bool) *gqlschema.AWSProviderConfigInput { return &gqlschema.AWSProviderConfigInput{ AwsZones: []*gqlschema.AWSZoneInput{ { @@ -816,7 +834,8 @@ func fixAWSGardenerInput() *gqlschema.AWSProviderConfigInput { WorkerCidr: "10.10.11.12/255", }, }, - VpcCidr: "10.10.11.11/255", + VpcCidr: "10.10.11.11/255", + EnableIMDSv2: &enableIMDSv2, } } @@ -846,7 +865,7 @@ func fixAzureZoneSubnetsInput(enableNAT bool) *gqlschema.AzureProviderConfigInpu } } -func fixWorker(zones []string) gardener_types.Worker { +func fixWorker(zones []string, providerConfig *apimachineryRuntime.RawExtension) gardener_types.Worker { return gardener_types.Worker{ Name: "cpu-worker-0", MaxSurge: util.PtrTo(intstr.FromInt(30)), @@ -862,9 +881,10 @@ func fixWorker(zones []string) gardener_types.Worker { Type: util.PtrTo("SSD"), VolumeSize: "30Gi", }, - Maximum: 3, - Minimum: 1, - Zones: zones, + Maximum: 3, + Minimum: 1, + Zones: zones, + ProviderConfig: providerConfig, } } diff --git a/components/provisioner/internal/model/infrastructure.go b/components/provisioner/internal/model/infrastructure.go index c39e71033b..6e67a01208 100644 --- a/components/provisioner/internal/model/infrastructure.go +++ b/components/provisioner/internal/model/infrastructure.go @@ -13,6 +13,7 @@ import ( const ( infrastructureConfigKind = "InfrastructureConfig" controlPlaneConfigKind = "ControlPlaneConfig" + workerConfigKind = "WorkerConfig" gcpAPIVersion = "gcp.provider.extensions.gardener.cloud/v1alpha1" azureAPIVersion = "azure.provider.extensions.gardener.cloud/v1alpha1" @@ -166,3 +167,17 @@ func NewOpenStackControlPlane(loadBalancerProvider string) *openstack.ControlPla LoadBalancerProvider: loadBalancerProvider, } } + +func NewAWSWorkerConfig(httpPutResponseHopLimit int64) *aws.WorkerConfig { + + return &aws.WorkerConfig{ + TypeMeta: v1.TypeMeta{ + APIVersion: awsAPIVersion, + Kind: workerConfigKind, + }, + InstanceMetadataOptions: &aws.InstanceMetadataOptions{ + HTTPTokens: &aws.HTTPTokensRequired, + HTTPPutResponseHopLimit: util.PtrTo(httpPutResponseHopLimit), + }, + } +} diff --git a/components/provisioner/internal/model/infrastructure/aws/worker.go b/components/provisioner/internal/model/infrastructure/aws/worker.go new file mode 100644 index 0000000000..568bfabef8 --- /dev/null +++ b/components/provisioner/internal/model/infrastructure/aws/worker.go @@ -0,0 +1,34 @@ +package aws + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// WorkerConfig contains configuration settings for the worker nodes. +type WorkerConfig struct { + metav1.TypeMeta `json:",inline"` + + // InstanceMetadataOptions contains configuration for controlling access to the metadata API. + InstanceMetadataOptions *InstanceMetadataOptions `json:"instanceMetadataOptions,omitempty"` +} + +// HTTPTokensValue is a constant for HTTPTokens values. +type HTTPTokensValue string + +var ( + // HTTPTokensRequired is a constant for requiring the use of tokens to access IMDS. Effectively disables access via + // the IMDSv1 endpoints. + HTTPTokensRequired HTTPTokensValue = "required" + // HTTPTokensOptional that makes the use of tokens for IMDS optional. Effectively allows access via both IMDSv1 and + // IMDSv2 endpoints. + HTTPTokensOptional HTTPTokensValue = "optional" +) + +// InstanceMetadataOptions contains configuration for controlling access to the metadata API. +type InstanceMetadataOptions struct { + // HTTPTokens enforces the use of metadata v2 API. + HTTPTokens *HTTPTokensValue `json:"httpTokens,omitempty"` + // HTTPPutResponseHopLimit is the response hop limit for instance metadata requests. + // Valid values are between 1 and 64. + HTTPPutResponseHopLimit *int64 `json:"httpPutResponseHopLimit,omitempty"` +} diff --git a/components/provisioner/internal/provisioning/input_converter.go b/components/provisioner/internal/provisioning/input_converter.go index 9ec43f500b..2739cb8ccd 100644 --- a/components/provisioner/internal/provisioning/input_converter.go +++ b/components/provisioner/internal/provisioning/input_converter.go @@ -28,7 +28,8 @@ func NewInputConverter( uuidGenerator uuid.UUIDGenerator, gardenerProject string, defaultEnableKubernetesVersionAutoUpdate, - defaultEnableMachineImageVersionAutoUpdate bool) InputConverter { + defaultEnableMachineImageVersionAutoUpdate bool, + defaultEnableIMDSv2 bool) InputConverter { return &converter{ uuidGenerator: uuidGenerator, @@ -37,6 +38,7 @@ func NewInputConverter( defaultEnableMachineImageVersionAutoUpdate: defaultEnableMachineImageVersionAutoUpdate, defaultProvisioningShootNetworkingFilterDisabled: true, defaultEuAccess: false, + defaultEnableIMDSv2: defaultEnableIMDSv2, } } @@ -47,6 +49,7 @@ type converter struct { defaultEnableMachineImageVersionAutoUpdate bool defaultProvisioningShootNetworkingFilterDisabled bool defaultEuAccess bool + defaultEnableIMDSv2 bool } func (c converter) ProvisioningInputToCluster(runtimeID string, input gqlschema.ProvisionRuntimeInput, tenant, subAccountId string) (model.Cluster, apperrors.AppError) { @@ -229,6 +232,9 @@ func (c converter) providerSpecificConfigFromInput(input *gqlschema.ProviderSpec return model.NewAzureGardenerConfig(input.AzureConfig) } if input.AwsConfig != nil { + if input.AwsConfig.EnableIMDSv2 == nil { + input.AwsConfig.EnableIMDSv2 = util.PtrTo(c.defaultEnableIMDSv2) + } return model.NewAWSGardenerConfig(input.AwsConfig) } if input.OpenStackConfig != nil { diff --git a/components/provisioner/internal/provisioning/input_converter_test.go b/components/provisioner/internal/provisioning/input_converter_test.go index c2f74411ca..281a26287c 100644 --- a/components/provisioner/internal/provisioning/input_converter_test.go +++ b/components/provisioner/internal/provisioning/input_converter_test.go @@ -22,6 +22,7 @@ const ( gardenerProject = "gardener-project" defaultEnableKubernetesVersionAutoUpdate = false defaultEnableMachineImageVersionAutoUpdate = false + defaultEnableIMDSv2 = true ) func Test_ProvisioningInputToCluster(t *testing.T) { @@ -269,7 +270,9 @@ func Test_ProvisioningInputToCluster(t *testing.T) { KymaConfig: fixKymaGraphQLConfigInput(&gqlEvaluationProfile), } - expectedAWSProviderCfg, err := model.NewAWSGardenerConfig(awsGardenerProvider) + expectedAWSGardenerProviderInput := *awsGardenerProvider + expectedAWSGardenerProviderInput.EnableIMDSv2 = util.PtrTo(true) + expectedAWSProviderCfg, err := model.NewAWSGardenerConfig(&expectedAWSGardenerProviderInput) require.NoError(t, err) expectedGardenerAWSRuntimeConfig := model.Cluster{ @@ -448,7 +451,8 @@ func Test_ProvisioningInputToCluster(t *testing.T) { uuidGeneratorMock, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, - defaultEnableMachineImageVersionAutoUpdate) + defaultEnableMachineImageVersionAutoUpdate, + defaultEnableIMDSv2) // when runtimeConfig, err := inputConverter.ProvisioningInputToCluster("runtimeID", testCase.input, tenant, subAccountId) @@ -550,7 +554,8 @@ func TestConverter_ParseInput(t *testing.T) { uuidGeneratorMock, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, - defaultEnableMachineImageVersionAutoUpdate) + defaultEnableMachineImageVersionAutoUpdate, + defaultEnableIMDSv2) // when output, err := inputConverter.KymaConfigFromInput("runtimeID", input) @@ -573,7 +578,9 @@ func TestConverter_ProvisioningInputToCluster_Error(t *testing.T) { nil, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, - defaultEnableMachineImageVersionAutoUpdate) + defaultEnableMachineImageVersionAutoUpdate, + defaultEnableIMDSv2, + ) // when _, err := inputConverter.ProvisioningInputToCluster("runtimeID", input, tenant, subAccountId) @@ -596,7 +603,8 @@ func TestConverter_ProvisioningInputToCluster_Error(t *testing.T) { nil, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, - defaultEnableMachineImageVersionAutoUpdate) + defaultEnableMachineImageVersionAutoUpdate, + defaultEnableIMDSv2) // when _, err := inputConverter.ProvisioningInputToCluster("runtimeID", input, tenant, subAccountId) @@ -622,7 +630,8 @@ func TestConverter_ProvisioningInputToCluster_Error(t *testing.T) { uuidGeneratorMock, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, - defaultEnableMachineImageVersionAutoUpdate) + defaultEnableMachineImageVersionAutoUpdate, + defaultEnableIMDSv2) // when _, err := inputConverter.ProvisioningInputToCluster("runtimeID", input, tenant, subAccountId) @@ -865,6 +874,7 @@ func Test_UpgradeShootInputToGardenerConfig(t *testing.T) { gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, + defaultEnableIMDSv2, ) // when @@ -886,6 +896,7 @@ func Test_UpgradeShootInputToGardenerConfig(t *testing.T) { gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, + defaultEnableIMDSv2, ) // when diff --git a/components/provisioner/internal/provisioning/service_test.go b/components/provisioner/internal/provisioning/service_test.go index 168a298e79..4af389ff6a 100644 --- a/components/provisioner/internal/provisioning/service_test.go +++ b/components/provisioner/internal/provisioning/service_test.go @@ -61,7 +61,7 @@ func kubeconfigProviderMock() *queue_mock.KubeconfigProvider { } func TestService_ProvisionRuntime(t *testing.T) { - inputConverter := NewInputConverter(uuid.NewUUIDGenerator(), gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate) + inputConverter := NewInputConverter(uuid.NewUUIDGenerator(), gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, defaultEnableIMDSv2) graphQLConverter := NewGraphQLConverter() uuidGenerator := uuid.NewUUIDGenerator() @@ -302,7 +302,7 @@ func TestService_ProvisionRuntime(t *testing.T) { } func TestService_DeprovisionRuntime(t *testing.T) { - inputConverter := NewInputConverter(uuid.NewUUIDGenerator(), gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate) + inputConverter := NewInputConverter(uuid.NewUUIDGenerator(), gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, defaultEnableIMDSv2) graphQLConverter := NewGraphQLConverter() lastOperation := model.Operation{State: model.Succeeded} mockedKubeconfig := kubeconfig @@ -504,7 +504,7 @@ func TestService_DeprovisionRuntime(t *testing.T) { func TestService_RuntimeOperationStatus(t *testing.T) { uuidGenerator := &uuidMocks.UUIDGenerator{} - inputConverter := NewInputConverter(uuidGenerator, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate) + inputConverter := NewInputConverter(uuidGenerator, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, defaultEnableIMDSv2) graphQLConverter := NewGraphQLConverter() operation := model.Operation{ @@ -560,7 +560,7 @@ func TestService_RuntimeOperationStatus(t *testing.T) { func TestService_RuntimeStatus(t *testing.T) { uuidGenerator := &uuidMocks.UUIDGenerator{} - inputConverter := NewInputConverter(uuidGenerator, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate) + inputConverter := NewInputConverter(uuidGenerator, gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, defaultEnableIMDSv2) graphQLConverter := NewGraphQLConverter() operation := model.Operation{ @@ -641,7 +641,7 @@ func TestService_RuntimeStatus(t *testing.T) { } func TestService_UpgradeGardenerShoot(t *testing.T) { - inputConverter := NewInputConverter(uuid.NewUUIDGenerator(), gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate) + inputConverter := NewInputConverter(uuid.NewUUIDGenerator(), gardenerProject, defaultEnableKubernetesVersionAutoUpdate, defaultEnableMachineImageVersionAutoUpdate, defaultEnableIMDSv2) graphQLConverter := NewGraphQLConverter() uuidGenerator := uuid.NewUUIDGenerator() diff --git a/components/provisioner/pkg/gqlschema/models_gen.go b/components/provisioner/pkg/gqlschema/models_gen.go index 2f5a6589fc..64e50fe324 100644 --- a/components/provisioner/pkg/gqlschema/models_gen.go +++ b/components/provisioner/pkg/gqlschema/models_gen.go @@ -13,15 +13,17 @@ type ProviderSpecificConfig interface { } type AWSProviderConfig struct { - AwsZones []*AWSZone `json:"awsZones"` - VpcCidr *string `json:"vpcCidr,omitempty"` + AwsZones []*AWSZone `json:"awsZones"` + VpcCidr *string `json:"vpcCidr,omitempty"` + EnableIMDSv2 *bool `json:"enableIMDSv2,omitempty"` } func (AWSProviderConfig) IsProviderSpecificConfig() {} type AWSProviderConfigInput struct { - VpcCidr string `json:"vpcCidr"` - AwsZones []*AWSZoneInput `json:"awsZones"` + VpcCidr string `json:"vpcCidr"` + AwsZones []*AWSZoneInput `json:"awsZones"` + EnableIMDSv2 *bool `json:"enableIMDSv2,omitempty"` } type AWSZone struct { diff --git a/components/provisioner/pkg/gqlschema/schema.graphql b/components/provisioner/pkg/gqlschema/schema.graphql index d20718fd65..63735f28af 100644 --- a/components/provisioner/pkg/gqlschema/schema.graphql +++ b/components/provisioner/pkg/gqlschema/schema.graphql @@ -67,6 +67,7 @@ type AzureProviderConfig { type AWSProviderConfig { awsZones: [AWSZone]! vpcCidr: String + enableIMDSv2: Boolean } type OpenStackProviderConfig { @@ -286,6 +287,7 @@ input AzureProviderConfigInput { input AWSProviderConfigInput { vpcCidr: String! # Classless Inter-Domain Routing for the virtual public cloud awsZones: [AWSZoneInput]! # Zones, in which to create the cluster, configuration + enableIMDSv2: Boolean # Enable IMDSv2 access method only } input OpenStackProviderConfigInput { diff --git a/components/provisioner/pkg/gqlschema/schema_gen.go b/components/provisioner/pkg/gqlschema/schema_gen.go index 5609cf2777..a874272582 100644 --- a/components/provisioner/pkg/gqlschema/schema_gen.go +++ b/components/provisioner/pkg/gqlschema/schema_gen.go @@ -47,8 +47,9 @@ type DirectiveRoot struct { type ComplexityRoot struct { AWSProviderConfig struct { - AwsZones func(childComplexity int) int - VpcCidr func(childComplexity int) int + AwsZones func(childComplexity int) int + EnableIMDSv2 func(childComplexity int) int + VpcCidr func(childComplexity int) int } AWSZone struct { @@ -254,6 +255,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AWSProviderConfig.AwsZones(childComplexity), true + case "AWSProviderConfig.enableIMDSv2": + if e.complexity.AWSProviderConfig.EnableIMDSv2 == nil { + break + } + + return e.complexity.AWSProviderConfig.EnableIMDSv2(childComplexity), true + case "AWSProviderConfig.vpcCidr": if e.complexity.AWSProviderConfig.VpcCidr == nil { break @@ -1444,6 +1452,47 @@ func (ec *executionContext) fieldContext_AWSProviderConfig_vpcCidr(ctx context.C return fc, nil } +func (ec *executionContext) _AWSProviderConfig_enableIMDSv2(ctx context.Context, field graphql.CollectedField, obj *AWSProviderConfig) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_AWSProviderConfig_enableIMDSv2(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EnableIMDSv2, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_AWSProviderConfig_enableIMDSv2(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AWSProviderConfig", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _AWSZone_name(ctx context.Context, field graphql.CollectedField, obj *AWSZone) (ret graphql.Marshaler) { fc, err := ec.fieldContext_AWSZone_name(ctx, field) if err != nil { @@ -7881,7 +7930,7 @@ func (ec *executionContext) unmarshalInputAWSProviderConfigInput(ctx context.Con asMap[k] = v } - fieldsInOrder := [...]string{"vpcCidr", "awsZones"} + fieldsInOrder := [...]string{"vpcCidr", "awsZones", "enableIMDSv2"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -7902,6 +7951,13 @@ func (ec *executionContext) unmarshalInputAWSProviderConfigInput(ctx context.Con return it, err } it.AwsZones = data + case "enableIMDSv2": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("enableIMDSv2")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + it.EnableIMDSv2 = data } } @@ -9065,6 +9121,8 @@ func (ec *executionContext) _AWSProviderConfig(ctx context.Context, sel ast.Sele } case "vpcCidr": out.Values[i] = ec._AWSProviderConfig_vpcCidr(ctx, field, obj) + case "enableIMDSv2": + out.Values[i] = ec._AWSProviderConfig_enableIMDSv2(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) }