diff --git a/api/v1beta1/awscluster_conversion.go b/api/v1beta1/awscluster_conversion.go index 33aff027e5..bba3e49def 100644 --- a/api/v1beta1/awscluster_conversion.go +++ b/api/v1beta1/awscluster_conversion.go @@ -65,6 +65,7 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error { dst.Status.Bastion.MarketType = restored.Status.Bastion.MarketType dst.Status.Bastion.HostAffinity = restored.Status.Bastion.HostAffinity dst.Status.Bastion.HostID = restored.Status.Bastion.HostID + dst.Status.Bastion.CPUOptions = restored.Status.Bastion.CPUOptions } dst.Spec.Partition = restored.Spec.Partition diff --git a/api/v1beta1/awsmachine_conversion.go b/api/v1beta1/awsmachine_conversion.go index 4cd5a66850..c0b4820983 100644 --- a/api/v1beta1/awsmachine_conversion.go +++ b/api/v1beta1/awsmachine_conversion.go @@ -47,6 +47,7 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.HostID = restored.Spec.HostID dst.Spec.HostAffinity = restored.Spec.HostAffinity dst.Spec.NetworkInterfaceType = restored.Spec.NetworkInterfaceType + dst.Spec.CPUOptions = restored.Spec.CPUOptions if restored.Spec.ElasticIPPool != nil { if dst.Spec.ElasticIPPool == nil { dst.Spec.ElasticIPPool = &infrav1.ElasticIPPool{} @@ -113,6 +114,7 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.Template.Spec.HostID = restored.Spec.Template.Spec.HostID dst.Spec.Template.Spec.HostAffinity = restored.Spec.Template.Spec.HostAffinity dst.Spec.Template.Spec.NetworkInterfaceType = restored.Spec.Template.Spec.NetworkInterfaceType + dst.Spec.Template.Spec.CPUOptions = restored.Spec.Template.Spec.CPUOptions if restored.Spec.Template.Spec.ElasticIPPool != nil { if dst.Spec.Template.Spec.ElasticIPPool == nil { dst.Spec.Template.Spec.ElasticIPPool = &infrav1.ElasticIPPool{} diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index ad9cc57bea..63e54beae3 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -1451,6 +1451,7 @@ func autoConvert_v1beta2_AWSMachineSpec_To_v1beta1_AWSMachineSpec(in *v1beta2.AW // WARNING: in.MarketType requires manual conversion: does not exist in peer-type // WARNING: in.HostID requires manual conversion: does not exist in peer-type // WARNING: in.HostAffinity requires manual conversion: does not exist in peer-type + // WARNING: in.CPUOptions requires manual conversion: does not exist in peer-type return nil } @@ -2061,6 +2062,7 @@ func autoConvert_v1beta2_Instance_To_v1beta1_Instance(in *v1beta2.Instance, out // WARNING: in.MarketType requires manual conversion: does not exist in peer-type // WARNING: in.HostAffinity requires manual conversion: does not exist in peer-type // WARNING: in.HostID requires manual conversion: does not exist in peer-type + // WARNING: in.CPUOptions requires manual conversion: does not exist in peer-type return nil } diff --git a/api/v1beta2/awsmachine_types.go b/api/v1beta2/awsmachine_types.go index 93c019b1b6..4ef386c40c 100644 --- a/api/v1beta2/awsmachine_types.go +++ b/api/v1beta2/awsmachine_types.go @@ -73,6 +73,35 @@ const ( NetworkInterfaceTypeEFAWithENAInterface NetworkInterfaceType = NetworkInterfaceType("efa") ) +// AWSConfidentialComputePolicy represents the confidential compute configuration for the instance. +type AWSConfidentialComputePolicy string + +const ( + // AWSConfidentialComputePolicyDisabled disables confidential computing for the instance. + AWSConfidentialComputePolicyDisabled AWSConfidentialComputePolicy = "Disabled" + // AWSConfidentialComputePolicySEVSNP enables AMD SEV-SNP as the confidential computing technology for the instance. + AWSConfidentialComputePolicySEVSNP AWSConfidentialComputePolicy = "AMDEncrytedVirtualizationNestedPaging" +) + +// CPUOptions defines CPU-related settings for the instance, including the confidential computing policy. +// +kubebuilder:validation:MinProperties=1 +type CPUOptions struct { + // confidentialCompute specifies whether confidential computing should be enabled for the instance, + // and, if so, which confidential computing technology to use. + // Valid values are: Disabled, AMDEncrytedVirtualizationNestedPaging + // When set to Disabled, confidential computing will be disabled for the instance. + // When set to AMDEncrytedVirtualizationNestedPaging, AMD SEV-SNP will be used as the confidential computing technology for the instance. + // In this case, ensure the following conditions are met: + // 1) The selected instance type supports AMD SEV-SNP. + // 2) The selected AWS region supports AMD SEV-SNP. + // 3) The selected AMI supports AMD SEV-SNP. + // More details can be checked at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sev-snp.html + // When omitted, this means no opinion and the platform is left to choose a reasonable default, which is subject to change without notice. The current default is Disabled. + // +kubebuilder:validation:Enum=Disabled;AMDEncrytedVirtualizationNestedPaging + // +optional + ConfidentialCompute AWSConfidentialComputePolicy `json:"confidentialCompute,omitempty"` +} + // AWSMachineSpec defines the desired state of an Amazon EC2 instance. // +kubebuilder:validation:XValidation:rule="!has(self.capacityReservationId) || !has(self.marketType) || self.marketType != 'Spot'",message="capacityReservationId may not be set when marketType is Spot" // +kubebuilder:validation:XValidation:rule="!has(self.capacityReservationId) || !has(self.spotMarketOptions)",message="capacityReservationId cannot be set when spotMarketOptions is specified" @@ -245,6 +274,11 @@ type AWSMachineSpec struct { // +optional // +kubebuilder:validation:Enum:=default;host HostAffinity *string `json:"hostAffinity,omitempty"` + + // cpuOptions defines CPU-related settings for the instance, including the confidential computing policy. + // If unset, no CPU options will be passed to the AWS platform and AWS default CPU options will be applied. + // +optional + CPUOptions *CPUOptions `json:"cpuOptions,omitempty"` } // CloudInit defines options related to the bootstrapping systems where diff --git a/api/v1beta2/types.go b/api/v1beta2/types.go index 143a806861..e79c457592 100644 --- a/api/v1beta2/types.go +++ b/api/v1beta2/types.go @@ -285,6 +285,9 @@ type Instance struct { // HostID specifies the dedicated host on which the instance should be started. // +optional HostID *string `json:"hostID,omitempty"` + + // The cpu options of the instance. + CPUOptions *CPUOptions `json:"cpuOptions,omitempty"` } // MarketType describes the market type of an Instance diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 5c21fefeb9..7a4556b63e 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -781,6 +781,11 @@ func (in *AWSMachineSpec) DeepCopyInto(out *AWSMachineSpec) { *out = new(string) **out = **in } + if in.CPUOptions != nil { + in, out := &in.CPUOptions, &out.CPUOptions + *out = new(CPUOptions) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSMachineSpec. @@ -1337,6 +1342,21 @@ func (in *CNISpec) DeepCopy() *CNISpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CPUOptions) DeepCopyInto(out *CPUOptions) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CPUOptions. +func (in *CPUOptions) DeepCopy() *CPUOptions { + if in == nil { + return nil + } + out := new(CPUOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClassicELBAttributes) DeepCopyInto(out *ClassicELBAttributes) { *out = *in @@ -1720,6 +1740,11 @@ func (in *Instance) DeepCopyInto(out *Instance) { *out = new(string) **out = **in } + if in.CPUOptions != nil { + in, out := &in.CPUOptions, &out.CPUOptions + *out = new(CPUOptions) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Instance. diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml index 9365590c24..ac14dda3dc 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml @@ -1214,6 +1214,28 @@ spec: description: CapacityReservationID specifies the target Capacity Reservation into which the instance should be launched. type: string + cpuOptions: + description: The cpu options of the instance. + minProperties: 1 + properties: + confidentialCompute: + description: |- + confidentialCompute specifies whether confidential computing should be enabled for the instance, + and, if so, which confidential computing technology to use. + Valid values are: Disabled, AMDEncrytedVirtualizationNestedPaging + When set to Disabled, confidential computing will be disabled for the instance. + When set to AMDEncrytedVirtualizationNestedPaging, AMD SEV-SNP will be used as the confidential computing technology for the instance. + In this case, ensure the following conditions are met: + 1) The selected instance type supports AMD SEV-SNP. + 2) The selected AWS region supports AMD SEV-SNP. + 3) The selected AMI supports AMD SEV-SNP. + More details can be checked at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sev-snp.html + When omitted, this means no opinion and the platform is left to choose a reasonable default, which is subject to change without notice. The current default is Disabled. + enum: + - Disabled + - AMDEncrytedVirtualizationNestedPaging + type: string + type: object ebsOptimized: description: Indicates whether the instance is optimized for Amazon EBS I/O. @@ -3410,6 +3432,28 @@ spec: description: CapacityReservationID specifies the target Capacity Reservation into which the instance should be launched. type: string + cpuOptions: + description: The cpu options of the instance. + minProperties: 1 + properties: + confidentialCompute: + description: |- + confidentialCompute specifies whether confidential computing should be enabled for the instance, + and, if so, which confidential computing technology to use. + Valid values are: Disabled, AMDEncrytedVirtualizationNestedPaging + When set to Disabled, confidential computing will be disabled for the instance. + When set to AMDEncrytedVirtualizationNestedPaging, AMD SEV-SNP will be used as the confidential computing technology for the instance. + In this case, ensure the following conditions are met: + 1) The selected instance type supports AMD SEV-SNP. + 2) The selected AWS region supports AMD SEV-SNP. + 3) The selected AMI supports AMD SEV-SNP. + More details can be checked at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sev-snp.html + When omitted, this means no opinion and the platform is left to choose a reasonable default, which is subject to change without notice. The current default is Disabled. + enum: + - Disabled + - AMDEncrytedVirtualizationNestedPaging + type: string + type: object ebsOptimized: description: Indicates whether the instance is optimized for Amazon EBS I/O. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml index 534a4ebcd0..2114de0e2d 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml @@ -2197,6 +2197,28 @@ spec: description: CapacityReservationID specifies the target Capacity Reservation into which the instance should be launched. type: string + cpuOptions: + description: The cpu options of the instance. + minProperties: 1 + properties: + confidentialCompute: + description: |- + confidentialCompute specifies whether confidential computing should be enabled for the instance, + and, if so, which confidential computing technology to use. + Valid values are: Disabled, AMDEncrytedVirtualizationNestedPaging + When set to Disabled, confidential computing will be disabled for the instance. + When set to AMDEncrytedVirtualizationNestedPaging, AMD SEV-SNP will be used as the confidential computing technology for the instance. + In this case, ensure the following conditions are met: + 1) The selected instance type supports AMD SEV-SNP. + 2) The selected AWS region supports AMD SEV-SNP. + 3) The selected AMI supports AMD SEV-SNP. + More details can be checked at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sev-snp.html + When omitted, this means no opinion and the platform is left to choose a reasonable default, which is subject to change without notice. The current default is Disabled. + enum: + - Disabled + - AMDEncrytedVirtualizationNestedPaging + type: string + type: object ebsOptimized: description: Indicates whether the instance is optimized for Amazon EBS I/O. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml index 4a0b10b5a3..8ff5b97186 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml @@ -674,6 +674,30 @@ spec: - ssm-parameter-store type: string type: object + cpuOptions: + description: |- + cpuOptions defines CPU-related settings for the instance, including the confidential computing policy. + If unset, no CPU options will be passed to the AWS platform and AWS default CPU options will be applied. + minProperties: 1 + properties: + confidentialCompute: + description: |- + confidentialCompute specifies whether confidential computing should be enabled for the instance, + and, if so, which confidential computing technology to use. + Valid values are: Disabled, AMDEncrytedVirtualizationNestedPaging + When set to Disabled, confidential computing will be disabled for the instance. + When set to AMDEncrytedVirtualizationNestedPaging, AMD SEV-SNP will be used as the confidential computing technology for the instance. + In this case, ensure the following conditions are met: + 1) The selected instance type supports AMD SEV-SNP. + 2) The selected AWS region supports AMD SEV-SNP. + 3) The selected AMI supports AMD SEV-SNP. + More details can be checked at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sev-snp.html + When omitted, this means no opinion and the platform is left to choose a reasonable default, which is subject to change without notice. The current default is Disabled. + enum: + - Disabled + - AMDEncrytedVirtualizationNestedPaging + type: string + type: object elasticIpPool: description: ElasticIPPool is the configuration to allocate Public IPv4 address (Elastic IP/EIP) from user-defined pool. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml index fdac94f540..f322aac5a7 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml @@ -593,6 +593,30 @@ spec: - ssm-parameter-store type: string type: object + cpuOptions: + description: |- + cpuOptions defines CPU-related settings for the instance, including the confidential computing policy. + If unset, no CPU options will be passed to the AWS platform and AWS default CPU options will be applied. + minProperties: 1 + properties: + confidentialCompute: + description: |- + confidentialCompute specifies whether confidential computing should be enabled for the instance, + and, if so, which confidential computing technology to use. + Valid values are: Disabled, AMDEncrytedVirtualizationNestedPaging + When set to Disabled, confidential computing will be disabled for the instance. + When set to AMDEncrytedVirtualizationNestedPaging, AMD SEV-SNP will be used as the confidential computing technology for the instance. + In this case, ensure the following conditions are met: + 1) The selected instance type supports AMD SEV-SNP. + 2) The selected AWS region supports AMD SEV-SNP. + 3) The selected AMI supports AMD SEV-SNP. + More details can be checked at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sev-snp.html + When omitted, this means no opinion and the platform is left to choose a reasonable default, which is subject to change without notice. The current default is Disabled. + enum: + - Disabled + - AMDEncrytedVirtualizationNestedPaging + type: string + type: object elasticIpPool: description: ElasticIPPool is the configuration to allocate Public IPv4 address (Elastic IP/EIP) from user-defined pool. diff --git a/pkg/cloud/services/ec2/instances.go b/pkg/cloud/services/ec2/instances.go index 05175d9908..343f143551 100644 --- a/pkg/cloud/services/ec2/instances.go +++ b/pkg/cloud/services/ec2/instances.go @@ -262,6 +262,8 @@ func (s *Service) CreateInstance(ctx context.Context, scope *scope.MachineScope, input.HostAffinity = scope.AWSMachine.Spec.HostAffinity + input.CPUOptions = scope.AWSMachine.Spec.CPUOptions + s.scope.Debug("Running instance", "machine-role", scope.Role()) s.scope.Debug("Running instance with instance metadata options", "metadata options", input.InstanceMetadataOptions) out, err := s.runInstance(scope.Role(), input) @@ -660,6 +662,7 @@ func (s *Service) runInstance(role string, i *infrav1.Instance) (*infrav1.Instan input.MetadataOptions = getInstanceMetadataOptionsRequest(i.InstanceMetadataOptions) input.PrivateDnsNameOptions = getPrivateDNSNameOptionsRequest(i.PrivateDNSName) input.CapacityReservationSpecification = getCapacityReservationSpecification(i.CapacityReservationID) + input.CpuOptions = getInstanceCPUOptionsRequest(i.CPUOptions) if i.Tenancy != "" { input.Placement = &types.Placement{ @@ -1279,3 +1282,16 @@ func getPrivateDNSNameOptionsRequest(privateDNSName *infrav1.PrivateDNSName) *ty HostnameType: types.HostnameType(aws.ToString(privateDNSName.HostnameType)), } } + +func getInstanceCPUOptionsRequest(cpuOptions *infrav1.CPUOptions) *types.CpuOptionsRequest { + request := &types.CpuOptionsRequest{} + switch cpuOptions.ConfidentialCompute { + case infrav1.AWSConfidentialComputePolicySEVSNP: + request.AmdSevSnp = types.AmdSevSnpSpecificationEnabled + case infrav1.AWSConfidentialComputePolicyDisabled: + request.AmdSevSnp = types.AmdSevSnpSpecificationDisabled + default: + } + + return request +} diff --git a/pkg/cloud/services/ec2/instances_test.go b/pkg/cloud/services/ec2/instances_test.go index dfc080d5c0..deac092e2a 100644 --- a/pkg/cloud/services/ec2/instances_test.go +++ b/pkg/cloud/services/ec2/instances_test.go @@ -5653,6 +5653,130 @@ func TestCreateInstance(t *testing.T) { } }, }, + { + name: "with AMD SEV-SNP enabled", + machine: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"set": "node"}, + }, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + DataSecretName: ptr.To[string]("bootstrap-data"), + }, + }, + }, + machineConfig: &infrav1.AWSMachineSpec{ + AMI: infrav1.AMIReference{ + ID: aws.String("abc"), + }, + InstanceType: "m6a.large", + CPUOptions: &infrav1.CPUOptions{ + ConfidentialCompute: infrav1.AWSConfidentialComputePolicy("AMDEncrytedVirtualizationNestedPaging"), + }, + }, + awsCluster: &infrav1.AWSCluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: infrav1.AWSClusterSpec{ + NetworkSpec: infrav1.NetworkSpec{ + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + ID: "subnet-1", + IsPublic: false, + }, + infrav1.SubnetSpec{ + IsPublic: false, + }, + }, + VPC: infrav1.VPCSpec{ + ID: "vpc-test", + }, + }, + }, + Status: infrav1.AWSClusterStatus{ + Network: infrav1.NetworkStatus{ + SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ + infrav1.SecurityGroupControlPlane: { + ID: "1", + }, + infrav1.SecurityGroupNode: { + ID: "2", + }, + infrav1.SecurityGroupLB: { + ID: "3", + }, + }, + APIServerELB: infrav1.LoadBalancer{ + DNSName: "test-apiserver.us-east-1.aws", + }, + }, + }, + }, + expect: func(m *mocks.MockEC2APIMockRecorder) { + m. + DescribeInstanceTypes(context.TODO(), gomock.Eq(&ec2.DescribeInstanceTypesInput{ + InstanceTypes: []types.InstanceType{ + types.InstanceTypeM6aLarge, + }, + })). + Return(&ec2.DescribeInstanceTypesOutput{ + InstanceTypes: []types.InstanceTypeInfo{ + { + ProcessorInfo: &types.ProcessorInfo{ + SupportedArchitectures: []types.ArchitectureType{ + types.ArchitectureTypeX8664, + }, + }, + }, + }, + }, nil) + m. // TODO: Restore these parameters, but with the tags as well + RunInstances(context.TODO(), gomock.Any()). + DoAndReturn(func(ctx context.Context, input *ec2.RunInstancesInput, requestOptions ...request.Option) (*ec2.RunInstancesOutput, error) { + if input.CpuOptions == nil || input.CpuOptions.AmdSevSnp != types.AmdSevSnpSpecificationEnabled { + t.Fatalf("expected AMD SEV-SNP to be enabled, but got %+v", input.CpuOptions) + } + return &ec2.RunInstancesOutput{ + Instances: []types.Instance{ + { + State: &types.InstanceState{ + Name: types.InstanceStateNamePending, + }, + IamInstanceProfile: &types.IamInstanceProfile{ + Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), + }, + InstanceId: aws.String("two"), + InstanceType: types.InstanceTypeM5Large, + SubnetId: aws.String("subnet-1"), + ImageId: aws.String("ami-1"), + RootDeviceName: aws.String("device-1"), + BlockDeviceMappings: []types.InstanceBlockDeviceMapping{ + { + DeviceName: aws.String("device-1"), + Ebs: &types.EbsInstanceBlockDevice{ + VolumeId: aws.String("volume-1"), + }, + }, + }, + Placement: &types.Placement{ + AvailabilityZone: &az, + }, + }, + }, + }, nil + }) + m. + DescribeNetworkInterfaces(context.TODO(), gomock.Any()). + Return(&ec2.DescribeNetworkInterfacesOutput{ + NetworkInterfaces: []types.NetworkInterface{}, + NextToken: nil, + }, nil) + }, + check: func(instance *infrav1.Instance, err error) { + if err != nil { + t.Fatalf("did not expect error: %v", err) + } + }, + }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) {