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

rds.cluster: add ability to auto-generate password in referenced secret #1166

Closed
wants to merge 1 commit into from
Closed
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
7 changes: 7 additions & 0 deletions apis/rds/v1beta1/zz_cluster_types.go

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

5 changes: 5 additions & 0 deletions apis/rds/v1beta1/zz_generated.deepcopy.go

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

26 changes: 26 additions & 0 deletions config/rds/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,34 @@ func Configure(p *config.Provider) {
if a, ok := attr["port"]; ok {
conn["port"] = []byte(fmt.Sprintf("%v", a))
}
if a, ok := attr["password"].(string); ok {
conn["password"] = []byte(a)
}
return conn, nil
}
desc, _ := comments.New("If true, the password will be auto-generated and"+
" stored in the Secret referenced by the masterPasswordSecretRef field.",
comments.WithTFTag("-"))
r.TerraformResource.Schema["auto_generate_password"] = &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Description: desc.String(),
}
r.InitializerFns = append(r.InitializerFns,
PasswordGenerator(
"spec.forProvider.masterPasswordSecretRef",
"spec.forProvider.autoGeneratePassword",
))
r.TerraformResource.Schema["password"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Description: "Password for the master DB user. If you set autoGeneratePassword to true, the Secret referenced here will be created or updated with generated password if it does not already contain one.",
}
r.TerraformResource.Schema["password"].Description = "Password for the " +
"master DB user. If you set autoGeneratePassword to true, the Secret" +
" referenced here will be created or updated with generated password" +
" if it does not already contain one."
})

p.AddResourceConfigurator("aws_rds_cluster_instance", func(r *config.Resource) {
Expand Down
182 changes: 182 additions & 0 deletions config/rds/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ func TestPasswordGenerator(t *testing.T) {
err: errors.Wrap(errBoom, errGetPasswordSecret),
},
},
"CannotGetClusterSecret": {
reason: "An error should be returned if the referenced secret cannot be retrieved.",
args: args{
kube: &test.MockClient{
MockGet: test.NewMockGetFn(errBoom),
},
secretRefFieldPath: "",
toggleFieldPath: "",
mg: &fake.Managed{},
},
want: want{
err: errors.Wrap(errBoom, errGetPasswordSecret),
},
},
"SecretAlreadyFull": {
reason: "Should be no-op if the Secret already has password.",
args: args{
Expand Down Expand Up @@ -85,6 +99,35 @@ func TestPasswordGenerator(t *testing.T) {
},
},
},
"ClusterSecretAlreadyFull": {
reason: "Should be no-op if the Secret already has password.",
args: args{
kube: &test.MockClient{
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
s, ok := obj.(*corev1.Secret)
if !ok {
return errors.New("needs to be secret")
}
s.Data = map[string][]byte{
"password": []byte("foo"),
}
return nil
},
},
secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef",
mg: &ujfake.Terraformed{
Parameterizable: ujfake.Parameterizable{
Parameters: map[string]any{
"masterPasswordSecretRef": map[string]any{
"name": "foo",
"namespace": "bar",
"key": "password",
},
},
},
},
},
},
"NoSecretReference": {
reason: "Should be no-op if the secret reference is not given.",
args: args{
Expand All @@ -98,6 +141,19 @@ func TestPasswordGenerator(t *testing.T) {
},
},
},
"NoClusterSecretReference": {
reason: "Should be no-op if the secret reference is not given.",
args: args{
secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef",
mg: &ujfake.Terraformed{
Parameterizable: ujfake.Parameterizable{
Parameters: map[string]any{
"another": "field",
},
},
},
},
},
"ToggleNotSet": {
reason: "Should be no-op if the toggle is not set at all.",
args: args{
Expand All @@ -119,6 +175,27 @@ func TestPasswordGenerator(t *testing.T) {
},
},
},
"ClusterToggleNotSet": {
reason: "Should be no-op if the toggle is not set at all.",
args: args{
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
},
secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef",
toggleFieldPath: "parameterizable.parameters.autoGeneratePassword",
mg: &ujfake.Terraformed{
Parameterizable: ujfake.Parameterizable{
Parameters: map[string]any{
"masterPasswordSecretRef": map[string]any{
"name": "foo",
"namespace": "bar",
"key": "password",
},
},
},
},
},
},
"ToggleFalse": {
reason: "Should be no-op if the toggle is set to false.",
args: args{
Expand All @@ -141,6 +218,28 @@ func TestPasswordGenerator(t *testing.T) {
},
},
},
"ClusterToggleFalse": {
reason: "Should be no-op if the toggle is set to false.",
args: args{
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
},
secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef",
toggleFieldPath: "parameterizable.parameters.autoGeneratePassword",
mg: &ujfake.Terraformed{
Parameterizable: ujfake.Parameterizable{
Parameters: map[string]any{
"masterPasswordSecretRef": map[string]any{
"name": "foo",
"namespace": "bar",
"key": "password",
},
"autoGeneratePassword": false,
},
},
},
},
},
"GenerateAndApply": {
reason: "Should apply if we generate, set the content of an already existing secret.",
args: args{
Expand Down Expand Up @@ -183,6 +282,48 @@ func TestPasswordGenerator(t *testing.T) {
},
},
},
"ClusterSecretGenerateAndApply": {
reason: "Should apply if we generate, set the content of an already existing secret.",
args: args{
kube: &test.MockClient{
MockGet: func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
s, ok := obj.(*corev1.Secret)
if !ok {
return errors.New("needs to be secret")
}
s.CreationTimestamp = metav1.Time{Time: time.Now()}
return nil
},
MockPatch: func(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
s, ok := obj.(*corev1.Secret)
if !ok {
return errors.New("needs to be secret")
}
if len(s.Data["password"]) == 0 {
return errors.New("password is not set")
}
if len(s.OwnerReferences) != 0 {
return errors.New("owner references should not be set if secret already exists")
}
return nil
},
},
secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef",
toggleFieldPath: "parameterizable.parameters.autoGeneratePassword",
mg: &ujfake.Terraformed{
Parameterizable: ujfake.Parameterizable{
Parameters: map[string]any{
"masterPasswordSecretRef": map[string]any{
"name": "foo",
"namespace": "bar",
"key": "password",
},
"autoGeneratePassword": true,
},
},
},
},
},
"GenerateAndCreate": {
reason: "Should create if we generate, set the content and there is no secret in place.",
args: args{
Expand Down Expand Up @@ -224,6 +365,47 @@ func TestPasswordGenerator(t *testing.T) {
},
},
},
"ClusterSecretGenerateAndCreate": {
reason: "Should create if we generate, set the content and there is no secret in place.",
args: args{
kube: &test.MockClient{
MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, "")),
MockCreate: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
s, ok := obj.(*corev1.Secret)
if !ok {
return errors.New("needs to be secret")
}
if len(s.Data["password"]) == 0 {
return errors.New("password is not set")
}
if len(s.OwnerReferences) == 1 &&
s.OwnerReferences[0].Name == "foo-mgd" {
return nil
}
return errors.New("owner references should be set if secret is created")
},
},
secretRefFieldPath: "parameterizable.parameters.masterPasswordSecretRef",
toggleFieldPath: "parameterizable.parameters.autoGeneratePassword",
mg: &ujfake.Terraformed{
Managed: fake.Managed{
ObjectMeta: metav1.ObjectMeta{
Name: "foo-mgd",
},
},
Parameterizable: ujfake.Parameterizable{
Parameters: map[string]any{
"masterPasswordSecretRef": map[string]any{
"name": "foo",
"namespace": "bar",
"key": "password",
},
"autoGeneratePassword": true,
},
},
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
Expand Down
10 changes: 1 addition & 9 deletions examples/rds/cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ spec:
region: us-west-1
engine: aurora-postgresql
masterUsername: cpadmin
autoGeneratePassword: true
masterPasswordSecretRef:
name: sample-cluster-password
namespace: upbound-system
Expand All @@ -17,12 +18,3 @@ spec:
writeConnectionSecretToRef:
name: sample-rds-cluster-secret
namespace: upbound-system
---
apiVersion: v1
kind: Secret
metadata:
name: sample-cluster-password
namespace: upbound-system
type: Opaque
stringData:
password: TestPass0!
15 changes: 12 additions & 3 deletions package/crds/rds.aws.upbound.io_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ spec:
immediately, or during the next maintenance window. Default
is false. See Amazon RDS Documentation for more information.
type: boolean
autoGeneratePassword:
description: Password for the master DB user. Note that this may
show up in logs, and it will be stored in the state file. Cannot
be set if manage_master_user_password is set to true. If true,
the password will be auto-generated and stored in the Secret
referenced by the masterPasswordSecretRef field.
type: boolean
availabilityZones:
description: List of EC2 Availability Zones for the DB cluster
storage where DB cluster instances can be created. We recommend
Expand Down Expand Up @@ -355,9 +362,11 @@ spec:
type: boolean
masterPasswordSecretRef:
description: Password for the master DB user. Note that this may
show up in logs, and it will be stored in the state file. Please
refer to the RDS Naming Constraints. Cannot be set if manage_master_user_password
is set to true.
show up in logs, and it will be stored in the state file. Cannot
be set if manage_master_user_password is set to true. Password
for the master DB user. If you set autoGeneratePassword to true,
the Secret referenced here will be created or updated with generated
password if it does not already contain one.
properties:
key:
description: The key to select.
Expand Down