Skip to content

Commit

Permalink
fix: Avoids sending database user password in update request if the v…
Browse files Browse the repository at this point in the history
…alue has not changed
  • Loading branch information
AgustinBettati committed Mar 11, 2024
1 parent 2741f70 commit 436dca6
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 18 deletions.
13 changes: 9 additions & 4 deletions internal/service/databaseuser/model_database_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"go.mongodb.org/atlas-sdk/v20231115007/admin"
)

func NewMongoDBDatabaseUser(ctx context.Context, dbUserModel *TfDatabaseUserModel) (*admin.CloudDatabaseUser, diag.Diagnostics) {
func NewMongoDBDatabaseUser(ctx context.Context, statePasswordValue types.String, dbUserModel *TfDatabaseUserModel) (*admin.CloudDatabaseUser, diag.Diagnostics) {
var rolesModel []*TfRoleModel
var labelsModel []*TfLabelModel
var scopesModel []*TfScopeModel
Expand All @@ -31,10 +31,9 @@ func NewMongoDBDatabaseUser(ctx context.Context, dbUserModel *TfDatabaseUserMode
return nil, diags
}

return &admin.CloudDatabaseUser{
result := admin.CloudDatabaseUser{
GroupId: dbUserModel.ProjectID.ValueString(),
Username: dbUserModel.Username.ValueString(),
Password: dbUserModel.Password.ValueStringPointer(),
X509Type: dbUserModel.X509Type.ValueStringPointer(),
AwsIAMType: dbUserModel.AWSIAMType.ValueStringPointer(),
OidcAuthType: dbUserModel.OIDCAuthType.ValueStringPointer(),
Expand All @@ -43,7 +42,13 @@ func NewMongoDBDatabaseUser(ctx context.Context, dbUserModel *TfDatabaseUserMode
Roles: NewMongoDBAtlasRoles(rolesModel),
Labels: NewMongoDBAtlasLabels(labelsModel),
Scopes: NewMongoDBAtlasScopes(scopesModel),
}, nil
}

if statePasswordValue != dbUserModel.Password {
// Password value has been modified or no previous state was present. Password is only updated if changed in the terraform configuration CLOUDP-235738
result.Password = dbUserModel.Password.ValueStringPointer()
}
return &result, nil
}

func NewTfDatabaseUserModel(ctx context.Context, model *TfDatabaseUserModel, dbUser *admin.CloudDatabaseUser) (*TfDatabaseUserModel, diag.Diagnostics) {
Expand Down
42 changes: 35 additions & 7 deletions internal/service/databaseuser/model_database_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,41 +73,69 @@ var (
Labels: &[]admin.ComponentLabel{sdkLabel},
Scopes: &[]admin.UserScope{sdkScope},
}
cloudDatabaseUserWithoutPassword = &admin.CloudDatabaseUser{
GroupId: projectID,
DatabaseName: authDatabaseName,
Username: username,
X509Type: &x509Type,
OidcAuthType: &oidCAuthType,
LdapAuthType: &ldapAuthType,
AwsIAMType: &awsIAMType,
Roles: &[]admin.DatabaseUserRole{sdkRole},
Labels: &[]admin.ComponentLabel{sdkLabel},
Scopes: &[]admin.UserScope{sdkScope},
}
)

func TestNewMongoDBDatabaseUser(t *testing.T) {
testCases := []struct {
TfDatabaseUserModel databaseuser.TfDatabaseUserModel
tfDatabaseUserModel databaseuser.TfDatabaseUserModel
passwordStateValue types.String
expectedResult *admin.CloudDatabaseUser
name string
expectedError bool
}{
{
name: "Success CloudDatabaseUser",
TfDatabaseUserModel: *getDatabaseUserModel(rolesSet, labelsSet, scopesSet),
name: "CloudDatabaseUser for create",
tfDatabaseUserModel: *getDatabaseUserModel(rolesSet, labelsSet, scopesSet),
passwordStateValue: types.StringNull(),
expectedResult: cloudDatabaseUser,
expectedError: false,
},
{
name: "CloudDatabaseUser with new password in model as value is modified",
tfDatabaseUserModel: *getDatabaseUserModel(rolesSet, labelsSet, scopesSet),
passwordStateValue: types.StringValue("oldPassword"),
expectedResult: cloudDatabaseUser,
expectedError: false,
},
{
name: "CloudDatabaseUser with no password in model as value remains the same",
tfDatabaseUserModel: *getDatabaseUserModel(rolesSet, labelsSet, scopesSet),
passwordStateValue: types.StringValue(password),
expectedResult: cloudDatabaseUserWithoutPassword,
expectedError: false,
},
{
name: "Roles fail",
TfDatabaseUserModel: *getDatabaseUserModel(wrongRoleSet, labelsSet, scopesSet),
tfDatabaseUserModel: *getDatabaseUserModel(wrongRoleSet, labelsSet, scopesSet),
expectedError: true,
},
{
name: "Labels fail",
TfDatabaseUserModel: *getDatabaseUserModel(rolesSet, wrongLabelSet, scopesSet),
tfDatabaseUserModel: *getDatabaseUserModel(rolesSet, wrongLabelSet, scopesSet),
expectedError: true,
},
{
name: "Scopes fail",
TfDatabaseUserModel: *getDatabaseUserModel(rolesSet, labelsSet, wrongScopeSet),
tfDatabaseUserModel: *getDatabaseUserModel(rolesSet, labelsSet, wrongScopeSet),
expectedError: true,
},
}

for i, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resultModel, err := databaseuser.NewMongoDBDatabaseUser(context.Background(), &testCases[i].TfDatabaseUserModel)
resultModel, err := databaseuser.NewMongoDBDatabaseUser(context.Background(), tc.passwordStateValue, &testCases[i].tfDatabaseUserModel)

if (err != nil) != tc.expectedError {
t.Errorf("Case %s: Received unexpected error: %v", tc.name, err)
Expand Down
13 changes: 7 additions & 6 deletions internal/service/databaseuser/resource_database_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func (r *databaseUserRS) Create(ctx context.Context, req resource.CreateRequest,
return
}

dbUserReq, d := NewMongoDBDatabaseUser(ctx, databaseUserPlan)
dbUserReq, d := NewMongoDBDatabaseUser(ctx, types.StringNull(), databaseUserPlan)
resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() {
return
Expand All @@ -231,6 +231,8 @@ func (r *databaseUserRS) Create(ctx context.Context, req resource.CreateRequest,
return
}

resp.Diagnostics.AddWarning("If the password value will be managed externally it is advised to remove the attribute", "More details can be found in resource documentation under the 'password' attribute")

resp.Diagnostics.Append(resp.State.Set(ctx, &dbUserModel)...)
if resp.Diagnostics.HasError() {
return
Expand Down Expand Up @@ -278,8 +280,6 @@ func (r *databaseUserRS) Read(ctx context.Context, req resource.ReadRequest, res
return
}

resp.Diagnostics.AddWarning("If the password value will be managed externally, it is advised to remove the attribute", "More details can be found in resource documentation under the 'password' attribute")

resp.Diagnostics.Append(resp.State.Set(ctx, &dbUserModel)...)
if resp.Diagnostics.HasError() {
return
Expand All @@ -288,14 +288,15 @@ func (r *databaseUserRS) Read(ctx context.Context, req resource.ReadRequest, res

func (r *databaseUserRS) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var databaseUserPlan *TfDatabaseUserModel
var databaseUserState *TfDatabaseUserModel

diags := req.Plan.Get(ctx, &databaseUserPlan)
resp.Diagnostics.Append(diags...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &databaseUserPlan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &databaseUserState)...)
if resp.Diagnostics.HasError() {
return
}

dbUserReq, d := NewMongoDBDatabaseUser(ctx, databaseUserPlan)
dbUserReq, d := NewMongoDBDatabaseUser(ctx, databaseUserState.Password, databaseUserPlan)
resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() {
return
Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/database_user.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Accepted values include:
* `project_id` - (Required) The unique ID for the project to create the database user.
* `roles` - (Required) List of user’s roles and the databases / collections on which the roles apply. A role allows the user to perform particular actions on the specified database. A role on the admin database can include privileges that apply to the other databases as well. See [Roles](#roles) below for more details.
* `username` - (Required) Username for authenticating to MongoDB. USER_ARN or ROLE_ARN if `aws_iam_type` is USER or ROLE.
* `password` - (Required) User's initial password. A value is required to create the database user, however the argument may be removed from your Terraform configuration after user creation without impacting the user, password or Terraform management. If you do change management of the password to outside of Terraform it is advised to remove the argument from the Terraform configuration so it is not inadvertently updated to the original password. IMPORTANT --- Passwords may show up in Terraform related logs and it will be stored in the Terraform state file as plain-text. Password can be changed after creation using your preferred method, e.g. via the MongoDB Atlas UI, to ensure security.
* `password` - (Required) User's initial password. A value is required to create the database user, however the argument may be removed from your Terraform configuration after user creation without impacting the user, password or Terraform management. If you do change management of the password to outside of Terraform it is advised to remove the argument from the Terraform configuration so it is not inadvertently updated to the original password. IMPORTANT --- Passwords may show up in Terraform related logs and it will be stored in the Terraform state file as plain-text. Password can be changed after creation using your preferred method, e.g. via the MongoDB Atlas UI, to ensure security.

* `x509_type` - (Optional) X.509 method by which the provided username is authenticated. If no value is given, Atlas uses the default value of NONE. The accepted types are:
* `NONE` - The user does not use X.509 authentication.
Expand Down

0 comments on commit 436dca6

Please sign in to comment.