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 #1169

Merged
merged 9 commits into from
Mar 11, 2024
6 changes: 6 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.

170 changes: 169 additions & 1 deletion config/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
"testing"
"time"

"github.com/crossplane/crossplane-runtime/pkg/errors"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/google/go-cmp/cmp"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -85,6 +85,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 +127,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 +161,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 +204,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 +268,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 +351,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
17 changes: 17 additions & 0 deletions config/rds/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ func Configure(p *config.Provider) {
"MasterUserSecretInitParameters": "ClusterMasterUserSecretInitParameters",
"MasterUserSecretObservation": "ClusterMasterUserSecretObservation",
}
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,
common.PasswordGenerator(
"spec.forProvider.masterPasswordSecretRef",
"spec.forProvider.autoGeneratePassword",
))
turkenf marked this conversation as resolved.
Show resolved Hide resolved
r.TerraformResource.Schema["master_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
10 changes: 1 addition & 9 deletions examples/rds/v1beta1/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!
12 changes: 8 additions & 4 deletions package/crds/rds.aws.upbound.io_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ spec:
immediately, or during the next maintenance window. Default
is false. See Amazon RDS Documentation for more information.
type: boolean
autoGeneratePassword:
description: 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.
Expand Down Expand Up @@ -372,10 +377,9 @@ spec:
is provided.
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.
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.
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
Loading