Skip to content

Commit 0ea2519

Browse files
Add support for Password Validation Policy in cloud sql instance. (#6329) (#4597)
Co-authored-by: Shuya Ma <[email protected]> Signed-off-by: Modular Magician <[email protected]> Signed-off-by: Modular Magician <[email protected]> Co-authored-by: Shuya Ma <[email protected]>
1 parent 61abbae commit 0ea2519

File tree

4 files changed

+170
-33
lines changed

4 files changed

+170
-33
lines changed

.changelog/6329.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
sql: added `password_validation_policy` field to `google_cloud_sql` resource
3+
```

google-beta/resource_sql_database_instance.go

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,48 @@ is set to true.`,
462462
},
463463
Description: `Configuration of Query Insights.`,
464464
},
465+
"password_validation_policy": {
466+
Type: schema.TypeList,
467+
Optional: true,
468+
MaxItems: 1,
469+
Elem: &schema.Resource{
470+
Schema: map[string]*schema.Schema{
471+
"min_length": {
472+
Type: schema.TypeInt,
473+
Optional: true,
474+
ValidateFunc: validation.IntBetween(0, 2147483647),
475+
Description: `Minimum number of characters allowed.`,
476+
},
477+
"complexity": {
478+
Type: schema.TypeString,
479+
Optional: true,
480+
ValidateFunc: validation.StringInSlice([]string{"COMPLEXITY_DEFAULT", "COMPLEXITY_UNSPECIFIED"}, false),
481+
Description: `Password complexity.`,
482+
},
483+
"reuse_interval": {
484+
Type: schema.TypeInt,
485+
Optional: true,
486+
ValidateFunc: validation.IntBetween(0, 2147483647),
487+
Description: `Number of previous passwords that cannot be reused.`,
488+
},
489+
"disallow_username_substring": {
490+
Type: schema.TypeBool,
491+
Optional: true,
492+
Description: `Disallow username as a part of the password.`,
493+
},
494+
"password_change_interval": {
495+
Type: schema.TypeString,
496+
Optional: true,
497+
Description: `Minimum interval after which the password can be changed. This flag is only supported for PostgresSQL.`,
498+
},
499+
"enable_password_policy": {
500+
Type: schema.TypeBool,
501+
Required: true,
502+
Description: `Whether the password policy is enabled or not.`,
503+
},
504+
},
505+
},
506+
},
465507
},
466508
},
467509
Description: `The settings to use for the database. The configuration is detailed below.`,
@@ -492,7 +534,7 @@ is set to true.`,
492534
Optional: true,
493535
ForceNew: true,
494536
Sensitive: true,
495-
Description: `Initial root password. Required for MS SQL Server, ignored by MySQL and PostgreSQL.`,
537+
Description: `Initial root password. Required for MS SQL Server.`,
496538
},
497539

498540
"ip_address": {
@@ -829,10 +871,7 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{})
829871
instance.Settings = desiredSettings
830872
}
831873

832-
// MSSQL Server require rootPassword to be set
833-
if strings.Contains(instance.DatabaseVersion, "SQLSERVER") {
834-
instance.RootPassword = d.Get("root_password").(string)
835-
}
874+
instance.RootPassword = d.Get("root_password").(string)
836875

837876
// Modifying a replica during Create can cause problems if the master is
838877
// modified at the same time. Lock the master until we're done in order
@@ -982,24 +1021,25 @@ func expandSqlDatabaseInstanceSettings(configured []interface{}) *sqladmin.Setti
9821021
_settings := configured[0].(map[string]interface{})
9831022
settings := &sqladmin.Settings{
9841023
// Version is unset in Create but is set during update
985-
SettingsVersion: int64(_settings["version"].(int)),
986-
Tier: _settings["tier"].(string),
987-
ForceSendFields: []string{"StorageAutoResize"},
988-
ActivationPolicy: _settings["activation_policy"].(string),
989-
ActiveDirectoryConfig: expandActiveDirectoryConfig(_settings["active_directory_config"].([]interface{})),
990-
SqlServerAuditConfig: expandSqlServerAuditConfig(_settings["sql_server_audit_config"].([]interface{})),
991-
AvailabilityType: _settings["availability_type"].(string),
992-
Collation: _settings["collation"].(string),
993-
DataDiskSizeGb: int64(_settings["disk_size"].(int)),
994-
DataDiskType: _settings["disk_type"].(string),
995-
PricingPlan: _settings["pricing_plan"].(string),
996-
UserLabels: convertStringMap(_settings["user_labels"].(map[string]interface{})),
997-
BackupConfiguration: expandBackupConfiguration(_settings["backup_configuration"].([]interface{})),
998-
DatabaseFlags: expandDatabaseFlags(_settings["database_flags"].([]interface{})),
999-
IpConfiguration: expandIpConfiguration(_settings["ip_configuration"].([]interface{})),
1000-
LocationPreference: expandLocationPreference(_settings["location_preference"].([]interface{})),
1001-
MaintenanceWindow: expandMaintenanceWindow(_settings["maintenance_window"].([]interface{})),
1002-
InsightsConfig: expandInsightsConfig(_settings["insights_config"].([]interface{})),
1024+
SettingsVersion: int64(_settings["version"].(int)),
1025+
Tier: _settings["tier"].(string),
1026+
ForceSendFields: []string{"StorageAutoResize"},
1027+
ActivationPolicy: _settings["activation_policy"].(string),
1028+
ActiveDirectoryConfig: expandActiveDirectoryConfig(_settings["active_directory_config"].([]interface{})),
1029+
SqlServerAuditConfig: expandSqlServerAuditConfig(_settings["sql_server_audit_config"].([]interface{})),
1030+
AvailabilityType: _settings["availability_type"].(string),
1031+
Collation: _settings["collation"].(string),
1032+
DataDiskSizeGb: int64(_settings["disk_size"].(int)),
1033+
DataDiskType: _settings["disk_type"].(string),
1034+
PricingPlan: _settings["pricing_plan"].(string),
1035+
UserLabels: convertStringMap(_settings["user_labels"].(map[string]interface{})),
1036+
BackupConfiguration: expandBackupConfiguration(_settings["backup_configuration"].([]interface{})),
1037+
DatabaseFlags: expandDatabaseFlags(_settings["database_flags"].([]interface{})),
1038+
IpConfiguration: expandIpConfiguration(_settings["ip_configuration"].([]interface{})),
1039+
LocationPreference: expandLocationPreference(_settings["location_preference"].([]interface{})),
1040+
MaintenanceWindow: expandMaintenanceWindow(_settings["maintenance_window"].([]interface{})),
1041+
InsightsConfig: expandInsightsConfig(_settings["insights_config"].([]interface{})),
1042+
PasswordValidationPolicy: expandPasswordValidationPolicy(_settings["password_validation_policy"].([]interface{})),
10031043
}
10041044

10051045
resize := _settings["disk_autoresize"].(bool)
@@ -1191,6 +1231,22 @@ func expandInsightsConfig(configured []interface{}) *sqladmin.InsightsConfig {
11911231
}
11921232
}
11931233

1234+
func expandPasswordValidationPolicy(configured []interface{}) *sqladmin.PasswordValidationPolicy {
1235+
if len(configured) == 0 || configured[0] == nil {
1236+
return nil
1237+
}
1238+
1239+
_passwordValidationPolicy := configured[0].(map[string]interface{})
1240+
return &sqladmin.PasswordValidationPolicy{
1241+
MinLength: int64(_passwordValidationPolicy["min_length"].(int)),
1242+
Complexity: _passwordValidationPolicy["complexity"].(string),
1243+
ReuseInterval: int64(_passwordValidationPolicy["reuse_interval"].(int)),
1244+
DisallowUsernameSubstring: _passwordValidationPolicy["disallow_username_substring"].(bool),
1245+
PasswordChangeInterval: _passwordValidationPolicy["password_change_interval"].(string),
1246+
EnablePasswordPolicy: _passwordValidationPolicy["enable_password_policy"].(bool),
1247+
}
1248+
}
1249+
11941250
func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) error {
11951251
config := meta.(*Config)
11961252
userAgent, err := generateUserAgentString(d, config.userAgent)
@@ -1411,15 +1467,16 @@ func resourceSqlDatabaseInstanceImport(d *schema.ResourceData, meta interface{})
14111467

14121468
func flattenSettings(settings *sqladmin.Settings) []map[string]interface{} {
14131469
data := map[string]interface{}{
1414-
"version": settings.SettingsVersion,
1415-
"tier": settings.Tier,
1416-
"activation_policy": settings.ActivationPolicy,
1417-
"availability_type": settings.AvailabilityType,
1418-
"collation": settings.Collation,
1419-
"disk_type": settings.DataDiskType,
1420-
"disk_size": settings.DataDiskSizeGb,
1421-
"pricing_plan": settings.PricingPlan,
1422-
"user_labels": settings.UserLabels,
1470+
"version": settings.SettingsVersion,
1471+
"tier": settings.Tier,
1472+
"activation_policy": settings.ActivationPolicy,
1473+
"availability_type": settings.AvailabilityType,
1474+
"collation": settings.Collation,
1475+
"disk_type": settings.DataDiskType,
1476+
"disk_size": settings.DataDiskSizeGb,
1477+
"pricing_plan": settings.PricingPlan,
1478+
"user_labels": settings.UserLabels,
1479+
"password_validation_policy": settings.PasswordValidationPolicy,
14231480
}
14241481

14251482
if settings.ActiveDirectoryConfig != nil {
@@ -1461,6 +1518,10 @@ func flattenSettings(settings *sqladmin.Settings) []map[string]interface{} {
14611518
data["user_labels"] = settings.UserLabels
14621519
}
14631520

1521+
if settings.PasswordValidationPolicy != nil {
1522+
data["password_validation_policy"] = flattenPasswordValidationPolicy(settings.PasswordValidationPolicy)
1523+
}
1524+
14641525
return []map[string]interface{}{data}
14651526
}
14661527

@@ -1655,6 +1716,18 @@ func flattenInsightsConfig(insightsConfig *sqladmin.InsightsConfig) interface{}
16551716
return []map[string]interface{}{data}
16561717
}
16571718

1719+
func flattenPasswordValidationPolicy(passwordValidationPolicy *sqladmin.PasswordValidationPolicy) interface{} {
1720+
data := map[string]interface{}{
1721+
"min_length": passwordValidationPolicy.MinLength,
1722+
"complexity": passwordValidationPolicy.Complexity,
1723+
"reuse_interval": passwordValidationPolicy.ReuseInterval,
1724+
"disallow_username_substring": passwordValidationPolicy.DisallowUsernameSubstring,
1725+
"password_change_interval": passwordValidationPolicy.PasswordChangeInterval,
1726+
"enable_password_policy": passwordValidationPolicy.EnablePasswordPolicy,
1727+
}
1728+
return []map[string]interface{}{data}
1729+
}
1730+
16581731
func instanceMutexKey(project, instance_name string) string {
16591732
return fmt.Sprintf("google-sql-database-instance-%s-%s", project, instance_name)
16601733
}

google-beta/resource_sql_database_instance_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,53 @@ func TestAccSqlDatabaseInstance_SqlServerAuditConfig(t *testing.T) {
11951195
})
11961196
}
11971197

1198+
func TestAccSqlDatabaseInstance_sqlMysqlInstancePvpExample(t *testing.T) {
1199+
t.Parallel()
1200+
1201+
context := map[string]interface{}{
1202+
"deletion_protection": false,
1203+
"random_suffix": randString(t, 10),
1204+
}
1205+
1206+
vcrTest(t, resource.TestCase{
1207+
PreCheck: func() { testAccPreCheck(t) },
1208+
Providers: testAccProviders,
1209+
Steps: []resource.TestStep{
1210+
{
1211+
Config: testAccSqlDatabaseInstance_sqlMysqlInstancePvpExample(context),
1212+
},
1213+
{
1214+
ResourceName: "google_sql_database_instance.mysql_pvp_instance_name",
1215+
ImportState: true,
1216+
ImportStateVerify: true,
1217+
ImportStateVerifyIgnore: []string{"deletion_protection", "root_password"},
1218+
},
1219+
},
1220+
})
1221+
}
1222+
1223+
func testAccSqlDatabaseInstance_sqlMysqlInstancePvpExample(context map[string]interface{}) string {
1224+
return Nprintf(`
1225+
resource "google_sql_database_instance" "mysql_pvp_instance_name" {
1226+
name = "tf-test-mysql-pvp-instance-name%{random_suffix}"
1227+
region = "asia-northeast1"
1228+
database_version = "MYSQL_8_0"
1229+
root_password = "abcABC123!"
1230+
settings {
1231+
tier = "db-f1-micro"
1232+
password_validation_policy {
1233+
min_length = 6
1234+
complexity = "COMPLEXITY_DEFAULT"
1235+
reuse_interval = 2
1236+
disallow_username_substring = true
1237+
enable_password_policy = true
1238+
}
1239+
}
1240+
deletion_protection = "%{deletion_protection}"
1241+
}
1242+
`, context)
1243+
}
1244+
11981245
var testGoogleSqlDatabaseInstance_basic2 = `
11991246
resource "google_sql_database_instance" "instance" {
12001247
region = "us-central1"

website/docs/r/sql_database_instance.html.markdown

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ includes an up-to-date reference of supported versions.
194194
* `replica_configuration` - (Optional) The configuration for replication. The
195195
configuration is detailed below. Valid only for MySQL instances.
196196

197-
* `root_password` - (Optional) Initial root password. Required for MS SQL Server, ignored by MySQL and PostgreSQL.
197+
* `root_password` - (Optional) Initial root password. Required for MS SQL Server.
198198

199199
* `encryption_key_name` - (Optional)
200200
The full path to the encryption key used for the CMEK disk encryption. Setting
@@ -349,6 +349,20 @@ The optional `settings.insights_config` subblock for instances declares [Query I
349349

350350
* `record_client_address` - True if Query Insights will record client address when enabled.
351351

352+
The optional `settings.passward_validation_policy` subblock for instances declares [Password Validation Policy](https://cloud.google.com/sql/docs/postgres/built-in-authentication) configuration. It contains:
353+
354+
* `min_length` - Specifies the minimum number of characters that the password must have.
355+
356+
* `complexity` - Checks if the password is a combination of lowercase, uppercase, numeric, and non-alphanumeric characters.
357+
358+
* `reuse_interval` - Specifies the number of previous passwords that you can't reuse.
359+
360+
* `disallow_username_substring` - Prevents the use of the username in the password.
361+
362+
* `password_change_interval` - Specifies the minimum duration after which you can change the password.
363+
364+
* `enable_password_policy` - Enables or disable the password validation policy.
365+
352366
The optional `replica_configuration` block must have `master_instance_name` set
353367
to work, cannot be updated, and supports:
354368

0 commit comments

Comments
 (0)