Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚠️ Support storing Ignition user data in S3 bucket for AWSMachinePool #5172

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/v1beta2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const (

// DefaultIgnitionVersion represents default Ignition version generated for machine userdata.
DefaultIgnitionVersion = "2.3"

// DefaultIgnitionStorageType represents the default storage type of Ignition userdata
DefaultIgnitionStorageType = IgnitionStorageTypeOptionClusterObjectStore
)

// SecretBackend defines variants for backend secret storage.
Expand Down
7 changes: 3 additions & 4 deletions api/v1beta2/awsmachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,12 +399,11 @@ func (r *AWSMachine) Default() {
}

if r.ignitionEnabled() && r.Spec.Ignition.Version == "" {
if r.Spec.Ignition == nil {
r.Spec.Ignition = &Ignition{}
}

r.Spec.Ignition.Version = DefaultIgnitionVersion
}
if r.ignitionEnabled() && r.Spec.Ignition.StorageType == "" {
r.Spec.Ignition.StorageType = IgnitionStorageTypeOptionClusterObjectStore
}
}

func (r *AWSMachine) validateAdditionalSecurityGroups() field.ErrorList {
Expand Down
6 changes: 6 additions & 0 deletions api/v1beta2/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ const (
// of the bootstrap secret that was used to create the user data for the latest launch
// template version.
LaunchTemplateBootstrapDataSecret = NameAWSProviderPrefix + "bootstrap-data-secret"

// LaunchTemplateBootstrapDataHash is the tag we use to store the hash of the raw bootstrap data.
// If bootstrap data is stored in S3, this hash relates to that data, not to the EC2 instance
// user data which only references the S3 object. We store this tag on launch template versions
// so that S3 bootstrap data objects can be deleted when they get outdated.
LaunchTemplateBootstrapDataHash = NameAWSProviderPrefix + "bootstrap-data-hash"
)

// ClusterTagKey generates the key for resources associated with a cluster.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,13 @@ func (t Template) ControllersPolicy() *iamv1.PolicyDocument {
Action: iamv1.Actions{
"s3:CreateBucket",
"s3:DeleteBucket",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:GetObject",
"s3:ListBucket",
"s3:PutBucketPolicy",
"s3:PutBucketTagging",
"s3:PutLifecycleConfiguration",
"s3:PutObject",
},
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,13 @@ Resources:
- Action:
- s3:CreateBucket
- s3:DeleteBucket
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
- s3:GetObject
- s3:ListBucket
- s3:PutBucketPolicy
- s3:PutBucketTagging
- s3:PutLifecycleConfiguration
- s3:PutObject
Effect: Allow
Resource:
- arn:*:s3:::cluster-api-provider-aws-*
Expand Down
100 changes: 100 additions & 0 deletions config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,106 @@ spec:
after it enters the InService state.
If no value is supplied by user a default value of 300 seconds is set
type: string
ignition:
description: Ignition defined options related to the bootstrapping
systems where Ignition is used.
properties:
proxy:
description: |-
Proxy defines proxy settings for Ignition.
Only valid for Ignition versions 3.1 and above.
properties:
httpProxy:
description: |-
HTTPProxy is the HTTP proxy to use for Ignition.
A single URL that specifies the proxy server to use for HTTP and HTTPS requests,
unless overridden by the HTTPSProxy or NoProxy options.
type: string
httpsProxy:
description: |-
HTTPSProxy is the HTTPS proxy to use for Ignition.
A single URL that specifies the proxy server to use for HTTPS requests,
unless overridden by the NoProxy option.
type: string
noProxy:
description: |-
NoProxy is the list of domains to not proxy for Ignition.
Specifies a list of strings to hosts that should be excluded from proxying.


Each value is represented by:
- An IP address prefix (1.2.3.4)
- An IP address prefix in CIDR notation (1.2.3.4/8)
- A domain name
- A domain name matches that name and all subdomains
- A domain name with a leading . matches subdomains only
- A special DNS label (*), indicates that no proxying should be done


An IP address prefix and domain name can also include a literal port number (1.2.3.4:80).
items:
description: IgnitionNoProxy defines the list of domains
to not proxy for Ignition.
maxLength: 2048
type: string
maxItems: 64
type: array
type: object
storageType:
default: ClusterObjectStore
description: |-
StorageType defines how to store the boostrap user data for Ignition.
This can be used to instruct Ignition from where to fetch the user data to bootstrap an instance.


When omitted, the storage option will default to ClusterObjectStore.


When set to "ClusterObjectStore", if the capability is available and a Cluster ObjectStore configuration
is correctly provided in the Cluster object (under .spec.s3Bucket),
an object store will be used to store bootstrap user data.


When set to "UnencryptedUserData", EC2 Instance User Data will be used to store the machine bootstrap user data, unencrypted.
This option is considered less secure than others as user data may contain sensitive informations (keys, certificates, etc.)
and users with ec2:DescribeInstances permission or users running pods
that can access the ec2 metadata service have access to this sensitive information.
So this is only to be used at ones own risk, and only when other more secure options are not viable.
enum:
- ClusterObjectStore
- UnencryptedUserData
type: string
tls:
description: |-
TLS defines TLS settings for Ignition.
Only valid for Ignition versions 3.1 and above.
properties:
certificateAuthorities:
description: |-
CASources defines the list of certificate authorities to use for Ignition.
The value is the certificate bundle (in PEM format). The bundle can contain multiple concatenated certificates.
Supported schemes are http, https, tftp, s3, arn, gs, and `data` (RFC 2397) URL scheme.
items:
description: IgnitionCASource defines the source of the
certificate authority to use for Ignition.
maxLength: 65536
type: string
maxItems: 64
type: array
type: object
version:
default: "2.3"
description: Version defines which version of Ignition will be
used to generate bootstrap data.
enum:
- "2.3"
- "3.0"
- "3.1"
- "3.2"
- "3.3"
- "3.4"
type: string
type: object
maxSize:
default: 1
description: MaxSize defines the maximum size of the group.
Expand Down
6 changes: 3 additions & 3 deletions controllers/awsmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ func (r *AWSMachineReconciler) resolveUserData(machineScope *scope.MachineScope,
if machineScope.UseIgnition(userDataFormat) {
var ignitionStorageType infrav1.IgnitionStorageTypeOption
if machineScope.AWSMachine.Spec.Ignition == nil {
ignitionStorageType = infrav1.IgnitionStorageTypeOptionClusterObjectStore
ignitionStorageType = infrav1.DefaultIgnitionStorageType
} else {
ignitionStorageType = machineScope.AWSMachine.Spec.Ignition.StorageType
}
Expand Down Expand Up @@ -795,8 +795,8 @@ func (r *AWSMachineReconciler) cloudInitUserData(machineScope *scope.MachineScop
// then returns the config to instruct ignition on how to pull the user data from the bucket.
func (r *AWSMachineReconciler) generateIgnitionWithRemoteStorage(scope *scope.MachineScope, objectStoreSvc services.ObjectStoreInterface, userData []byte) ([]byte, error) {
if objectStoreSvc == nil {
return nil, errors.New("using Ignition by default requires a cluster wide object storage configured at `AWSCluster.Spec.Ignition.S3Bucket`. " +
"You must configure one or instruct Ignition to use EC2 user data instead, by setting `AWSMachine.Spec.Ignition.StorageType` to `UnencryptedUserData`")
return nil, errors.New("using Ignition by default requires a cluster wide object storage configured at `AWSCluster.spec.s3Bucket`. " +
"You must configure one or instruct Ignition to use EC2 user data instead, by setting `AWSMachine.spec.ignition.storageType` to `UnencryptedUserData`")
}

objectURL, err := objectStoreSvc.Create(scope, userData)
Expand Down
3 changes: 3 additions & 0 deletions exp/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ func (src *AWSMachinePool) ConvertTo(dstRaw conversion.Hub) error {
if restored.Spec.AvailabilityZoneSubnetType != nil {
dst.Spec.AvailabilityZoneSubnetType = restored.Spec.AvailabilityZoneSubnetType
}
if restored.Spec.Ignition != nil {
dst.Spec.Ignition = restored.Spec.Ignition
}

if restored.Spec.AWSLaunchTemplate.PrivateDNSName != nil {
dst.Spec.AWSLaunchTemplate.PrivateDNSName = restored.Spec.AWSLaunchTemplate.PrivateDNSName
Expand Down
1 change: 1 addition & 0 deletions exp/api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions exp/api/v1beta2/awsmachinepool_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ type AWSMachinePoolSpec struct {
// SuspendProcesses defines a list of processes to suspend for the given ASG. This is constantly reconciled.
// If a process is removed from this list it will automatically be resumed.
SuspendProcesses *SuspendProcessesTypes `json:"suspendProcesses,omitempty"`

// Ignition defined options related to the bootstrapping systems where Ignition is used.
// +optional
Ignition *infrav1.Ignition `json:"ignition,omitempty"`
}

// SuspendProcessesTypes contains user friendly auto-completable values for suspended process names.
Expand Down
35 changes: 30 additions & 5 deletions exp/api/v1beta2/awsmachinepool_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
"sigs.k8s.io/cluster-api-provider-aws/v2/feature"
)

var log = ctrl.Log.WithName("awsmachinepool-resource")
Expand Down Expand Up @@ -62,12 +63,12 @@ func (r *AWSMachinePool) validateRootVolume() field.ErrorList {
return allErrs
}

if v1beta2.VolumeTypesProvisioned.Has(string(r.Spec.AWSLaunchTemplate.RootVolume.Type)) && r.Spec.AWSLaunchTemplate.RootVolume.IOPS == 0 {
if infrav1.VolumeTypesProvisioned.Has(string(r.Spec.AWSLaunchTemplate.RootVolume.Type)) && r.Spec.AWSLaunchTemplate.RootVolume.IOPS == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("spec.awsLaunchTemplate.rootVolume.iops"), "iops required if type is 'io1' or 'io2'"))
}

if r.Spec.AWSLaunchTemplate.RootVolume.Throughput != nil {
if r.Spec.AWSLaunchTemplate.RootVolume.Type != v1beta2.VolumeTypeGP3 {
if r.Spec.AWSLaunchTemplate.RootVolume.Type != infrav1.VolumeTypeGP3 {
allErrs = append(allErrs, field.Required(field.NewPath("spec.awsLaunchTemplate.rootVolume.throughput"), "throughput is valid only for type 'gp3'"))
}
if *r.Spec.AWSLaunchTemplate.RootVolume.Throughput < 0 {
Expand All @@ -86,12 +87,12 @@ func (r *AWSMachinePool) validateNonRootVolumes() field.ErrorList {
var allErrs field.ErrorList

for _, volume := range r.Spec.AWSLaunchTemplate.NonRootVolumes {
if v1beta2.VolumeTypesProvisioned.Has(string(volume.Type)) && volume.IOPS == 0 {
if infrav1.VolumeTypesProvisioned.Has(string(volume.Type)) && volume.IOPS == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("spec.template.spec.nonRootVolumes.iops"), "iops required if type is 'io1' or 'io2'"))
}

if volume.Throughput != nil {
if volume.Type != v1beta2.VolumeTypeGP3 {
if volume.Type != infrav1.VolumeTypeGP3 {
allErrs = append(allErrs, field.Required(field.NewPath("spec.template.spec.nonRootVolumes.throughput"), "throughput is valid only for type 'gp3'"))
}
if *volume.Throughput < 0 {
Expand Down Expand Up @@ -162,6 +163,22 @@ func (r *AWSMachinePool) validateRefreshPreferences() field.ErrorList {
return allErrs
}

func (r *AWSMachinePool) ignitionEnabled() bool {
return r.Spec.Ignition != nil
}

func (r *AWSMachinePool) validateIgnition() field.ErrorList {
var allErrs field.ErrorList

// Feature gate is not enabled but ignition is enabled then send a forbidden error.
if !feature.Gates.Enabled(feature.BootstrapFormatIgnition) && r.ignitionEnabled() {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "ignition"),
"can be set only if the BootstrapFormatIgnition feature gate is enabled"))
}

return allErrs
}

// ValidateCreate will do any extra validation when creating a AWSMachinePool.
func (r *AWSMachinePool) ValidateCreate() (admission.Warnings, error) {
log.Info("AWSMachinePool validate create", "machine-pool", klog.KObj(r))
Expand All @@ -176,6 +193,7 @@ func (r *AWSMachinePool) ValidateCreate() (admission.Warnings, error) {
allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
allErrs = append(allErrs, r.validateSpotInstances()...)
allErrs = append(allErrs, r.validateRefreshPreferences()...)
allErrs = append(allErrs, r.validateIgnition()...)

if len(allErrs) == 0 {
return nil, nil
Expand Down Expand Up @@ -226,4 +244,11 @@ func (r *AWSMachinePool) Default() {
log.Info("DefaultInstanceWarmup is zero, setting 300 seconds as default")
r.Spec.DefaultInstanceWarmup.Duration = 300 * time.Second
}

if r.ignitionEnabled() && r.Spec.Ignition.Version == "" {
r.Spec.Ignition.Version = infrav1.DefaultIgnitionVersion
}
if r.ignitionEnabled() && r.Spec.Ignition.StorageType == "" {
r.Spec.Ignition.StorageType = infrav1.DefaultIgnitionStorageType
}
}
5 changes: 5 additions & 0 deletions exp/api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading