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

✨ ec2: Add support for userdata privacy #1490

Merged
Merged
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
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ generate: ## Generate code
$(MAKE) generate-manifests

.PHONY: generate-go
generate-go: $(CONTROLLER_GEN) $(MOCKGEN) $(CONVERSION_GEN) ## Runs Go related generate targets
go generate ./...
generate-go: $(CONTROLLER_GEN) $(CONVERSION_GEN) $(MOCKGEN) ## Runs Go related generate targets
$(CONTROLLER_GEN) \
paths=./api/... \
object:headerFile=./hack/boilerplate/boilerplate.generatego.txt
Expand All @@ -163,6 +162,7 @@ generate-go: $(CONTROLLER_GEN) $(MOCKGEN) $(CONVERSION_GEN) ## Runs Go related g
--input-dirs=./api/v1alpha2 \
--output-file-base=zz_generated.conversion \
--go-header-file=./hack/boilerplate/boilerplate.generatego.txt
go generate ./...

.PHONY: generate-manifests
generate-manifests: $(CONTROLLER_GEN) ## Generate manifests e.g. CRD, RBAC etc.
Expand Down
43 changes: 42 additions & 1 deletion api/v1alpha2/awsmachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,52 @@ package v1alpha2
import (
apiconversion "k8s.io/apimachinery/pkg/conversion"
infrav1alpha3 "sigs.k8s.io/cluster-api-provider-aws/api/v1alpha3"
utilconversion "sigs.k8s.io/cluster-api/util/conversion"
"sigs.k8s.io/controller-runtime/pkg/conversion"
)

// ConvertTo converts this AWSMachine to the Hub version (v1alpha3).
func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error { // nolint
dst := dstRaw.(*infrav1alpha3.AWSMachine)

if err := Convert_v1alpha2_AWSMachine_To_v1alpha3_AWSMachine(src, dst, nil); err != nil {
return err
}

// Manually restore data from annotations
restored := &infrav1alpha3.AWSMachine{}
if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
return err
}

restoreAWSMachineSpec(&restored.Spec, &dst.Spec)

return nil
}

randomvariable marked this conversation as resolved.
Show resolved Hide resolved
func restoreAWSMachineSpec(restored *infrav1alpha3.AWSMachineSpec, dst *infrav1alpha3.AWSMachineSpec) {
dst.ImageLookupBaseOS = restored.ImageLookupBaseOS
// Conversion for route: v1alpha3 --> management cluster running v1alpha2 on <= v0.4.8 --> v1alpha3
if !dst.CloudInit.InsecureSkipSecretsManager && dst.CloudInit.SecretARN == "" {
dst.CloudInit.InsecureSkipSecretsManager = restored.CloudInit.InsecureSkipSecretsManager
dst.CloudInit.SecretARN = restored.CloudInit.SecretARN
}
}

// ConvertFrom converts from the Hub version (v1alpha3) to this version.
func (dst *AWSMachine) ConvertFrom(srcRaw conversion.Hub) error { // nolint
src := srcRaw.(*infrav1alpha3.AWSMachine)
return Convert_v1alpha3_AWSMachine_To_v1alpha2_AWSMachine(src, dst, nil)

if err := Convert_v1alpha3_AWSMachine_To_v1alpha2_AWSMachine(src, dst, nil); err != nil {
return err
}

// Preserve Hub data on down-conversion.
if err := utilconversion.MarshalData(src, dst); err != nil {
return err
}

return nil
}

// ConvertTo converts this AWSMachineList to the Hub version (v1alpha3).
Expand Down Expand Up @@ -101,3 +130,15 @@ func Convert_v1alpha2_AWSMachineSpec_To_v1alpha3_AWSMachineSpec(in *AWSMachineSp

return nil
}

func Convert_v1alpha2_CloudInit_To_v1alpha3_CloudInit(in *CloudInit, out *infrav1alpha3.CloudInit, s apiconversion.Scope) error { // nolint
out.SecretARN = in.SecretARN
out.InsecureSkipSecretsManager = !in.EnableSecureSecretsManager
return nil
}

func Convert_v1alpha3_CloudInit_To_v1alpha2_CloudInit(in *infrav1alpha3.CloudInit, out *CloudInit, s apiconversion.Scope) error { // nolint
out.SecretARN = in.SecretARN
out.EnableSecureSecretsManager = !in.InsecureSkipSecretsManager
return nil
}
84 changes: 84 additions & 0 deletions api/v1alpha2/awsmachine_conversion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
"testing"

. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
infrav1alpha3 "sigs.k8s.io/cluster-api-provider-aws/api/v1alpha3"
)

func TestConvertAWSMachine(t *testing.T) {
g := NewWithT(t)

t.Run("from hub", func(t *testing.T) {
t.Run("should restore SecretARN, assuming old version of object without field", func(t *testing.T) {
src := &infrav1alpha3.AWSMachine{
ObjectMeta: metav1.ObjectMeta{},
Spec: infrav1alpha3.AWSMachineSpec{
CloudInit: infrav1alpha3.CloudInit{
InsecureSkipSecretsManager: true,
SecretARN: "something",
},
},
}
dst := &AWSMachine{}
g.Expect(dst.ConvertFrom(src)).To(Succeed())
restored := &infrav1alpha3.AWSMachine{}
g.Expect(dst.ConvertTo(restored)).To(Succeed())
g.Expect(restored.Spec.CloudInit.SecretARN).To(Equal(src.Spec.CloudInit.SecretARN))
g.Expect(restored.Spec.CloudInit.InsecureSkipSecretsManager).To(Equal(src.Spec.CloudInit.InsecureSkipSecretsManager))
})
})
t.Run("should prefer newer cloudinit data on the v1alpha2 obj", func(t *testing.T) {
src := &infrav1alpha3.AWSMachine{
ObjectMeta: metav1.ObjectMeta{},
Spec: infrav1alpha3.AWSMachineSpec{
CloudInit: infrav1alpha3.CloudInit{
SecretARN: "something",
},
},
}
dst := &AWSMachine{
Spec: AWSMachineSpec{
CloudInit: &CloudInit{
EnableSecureSecretsManager: true,
SecretARN: "something-else",
},
},
}
g.Expect(dst.ConvertFrom(src)).To(Succeed())
restored := &infrav1alpha3.AWSMachine{}
g.Expect(dst.ConvertTo(restored)).To(Succeed())
g.Expect(restored.Spec.CloudInit.SecretARN).To(Equal(src.Spec.CloudInit.SecretARN))
})
t.Run("should restore ImageLookupBaseOS", func(t *testing.T) {
src := &infrav1alpha3.AWSMachine{
ObjectMeta: metav1.ObjectMeta{},
Spec: infrav1alpha3.AWSMachineSpec{
ImageLookupBaseOS: "amazon-linux",
},
}
dst := &AWSMachine{}
g.Expect(dst.ConvertFrom(src)).To(Succeed())
restored := &infrav1alpha3.AWSMachine{}
g.Expect(dst.ConvertTo(restored)).To(Succeed())
g.Expect(restored.Spec.ImageLookupBaseOS).To(Equal(src.Spec.ImageLookupBaseOS))
})
}
21 changes: 21 additions & 0 deletions api/v1alpha2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,27 @@ type AWSMachineSpec struct {
// +optional
// +kubebuilder:validation:MaxItems=2
NetworkInterfaces []string `json:"networkInterfaces,omitempty"`

// CloudInit defines options related to the bootstrapping systems where
// CloudInit is used.
// +optional
CloudInit *CloudInit `json:"cloudInit,omitempty"`
}

// CloudInit defines options related to the bootstrapping systems where
// CloudInit is used.
type CloudInit struct {
// enableSecureSecretsManager, when set to true will use AWS Secrets Manager to ensure
// userdata privacy. A cloud-init boothook shell script is prepended to download
// the userdata from Secrets Manager and additionally delete the secret.
// +optional
EnableSecureSecretsManager bool `json:"enableSecureSecretsManager,omitempty"`

// SecretARN is the Amazon Resource Name of the secret. This is stored
// temporarily, and deleted when the machine registers as a node against
// the workload cluster.
// +optional
SecretARN string `json:"secretARN,omitempty"`
}

// AWSMachineStatus defines the observed state of AWSMachine
Expand Down
24 changes: 24 additions & 0 deletions api/v1alpha2/zz_generated.conversion.go

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

20 changes: 20 additions & 0 deletions api/v1alpha2/zz_generated.deepcopy.go

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

21 changes: 21 additions & 0 deletions api/v1alpha3/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ type AWSMachineSpec struct {
// +optional
// +kubebuilder:validation:MaxItems=2
NetworkInterfaces []string `json:"networkInterfaces,omitempty"`

// CloudInit defines options related to the bootstrapping systems where
// CloudInit is used.
// +optional
CloudInit CloudInit `json:"cloudInit,omitempty"`
}

// CloudInit defines options related to the bootstrapping systems where
// CloudInit is used.
type CloudInit struct {
// InsecureSkipSecretsManager, when set to true will not use AWS Secrets Manager
// to ensure privacy of userdata.
// By default, a cloud-init boothook shell script is prepended to download
// the userdata from Secrets Manager and additionally delete the secret.
InsecureSkipSecretsManager bool `json:"insecureSkipSecretsManager,omitempty"`

// SecretARN is the Amazon Resource Name of the secret. This is stored
// temporarily, and deleted when the machine registers as a node against
// the workload cluster.
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
// +optional
SecretARN string `json:"secretARN,omitempty"`
}

// AWSMachineStatus defines the observed state of AWSMachine
Expand Down
28 changes: 23 additions & 5 deletions api/v1alpha3/awsmachine_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ var _ webhook.Validator = &AWSMachine{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *AWSMachine) ValidateCreate() error {
return nil
return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, r.validateCloudInitSecret())
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
Expand All @@ -63,6 +63,8 @@ func (r *AWSMachine) ValidateUpdate(old runtime.Object) error {

var allErrs field.ErrorList

allErrs = append(allErrs, r.validateCloudInitSecret()...)

newAWSMachineSpec := newAWSMachine["spec"].(map[string]interface{})
oldAWSMachineSpec := oldAWSMachine["spec"].(map[string]interface{})

Expand All @@ -78,14 +80,30 @@ func (r *AWSMachine) ValidateUpdate(old runtime.Object) error {
delete(oldAWSMachineSpec, "additionalSecurityGroups")
delete(newAWSMachineSpec, "additionalSecurityGroups")

// allow changes to secretARN
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
if cloudInit, ok := oldAWSMachineSpec["cloudInit"].(map[string]interface{}); ok {
delete(cloudInit, "secretARN")
}

if cloudInit, ok := newAWSMachineSpec["cloudInit"].(map[string]interface{}); ok {
delete(cloudInit, "secretARN")
}

if !reflect.DeepEqual(oldAWSMachineSpec, newAWSMachineSpec) {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "cannot be modified"))
return apierrors.NewInvalid(
GroupVersion.WithKind("AWSMachine").GroupKind(),
r.Name, allErrs)
}

return nil
return aggregateObjErrors(r.GroupVersionKind().GroupKind(), r.Name, allErrs)
}

func (r *AWSMachine) validateCloudInitSecret() field.ErrorList {
var allErrs field.ErrorList

if r.Spec.CloudInit.SecretARN != "" && r.Spec.CloudInit.InsecureSkipSecretsManager {
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "cloudInit", "secretARN"), "cannot be set if spec.cloudInit.insecureSkipSecretsManager is true"))
}

return allErrs
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
Expand Down
5 changes: 4 additions & 1 deletion api/v1alpha3/awsmachine_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestAWSMachine_ValidateUpdate(t *testing.T) {
wantErr bool
}{
{
name: "change in providerid, tags and securitygroups",
name: "change in providerid, cloudinit, tags and securitygroups",
oldMachine: &AWSMachine{
Spec: AWSMachineSpec{
ProviderID: nil,
Expand All @@ -49,6 +49,9 @@ func TestAWSMachine_ValidateUpdate(t *testing.T) {
ID: pointer.StringPtr("ID"),
},
},
CloudInit: CloudInit{
SecretARN: "test",
},
},
},
wantErr: false,
Expand Down
Loading