Skip to content

Commit 55a2468

Browse files
committed
PLT-1523: Added password_policy support in terraform.
1 parent 74f7bcc commit 55a2468

File tree

11 files changed

+537
-6
lines changed

11 files changed

+537
-6
lines changed

docs/resources/password_policy.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
page_title: "spectrocloud_password_policy Resource - terraform-provider-spectrocloud"
3+
subcategory: ""
4+
description: |-
5+
6+
---
7+
8+
# spectrocloud_password_policy (Resource)
9+
10+
11+
12+
You can learn more about managing password policy in Palette by reviewing the [Password Policy](https://docs.spectrocloud.com/enterprise-version/system-management/account-management/credentials/#password-requirements-and-security) guide.
13+
14+
~> The password_policy resource enforces a password compliance policy. By default, a password policy is configured in Palette with default values. Users can update the password compliance settings as per their requirements. When a spectrocloud_password_policy resource is destroyed, the password policy will revert to the Palette default settings.
15+
16+
## Example Usage
17+
18+
An example of managing an password policy in Palette.
19+
20+
```hcl
21+
resource "spectrocloud_password_policy" "policy_regex" {
22+
# password_regex = "*"
23+
password_expiry_days = 123
24+
first_reminder_days = 5
25+
min_digits = 1
26+
min_lowercase_letters = 12
27+
min_password_length = 12
28+
min_special_characters = 1
29+
min_uppercase_letters = 1
30+
}
31+
```
32+
33+
<!-- schema generated by tfplugindocs -->
34+
## Schema
35+
36+
### Optional
37+
38+
- `first_reminder_days` (Number) The number of days before the password expiry to send the first reminder to the user. Default is 5 days before expiry.
39+
- `min_digits` (Number) The minimum number of numeric digits (0-9) required in the password. Ensures that passwords contain numerical characters.
40+
- `min_lowercase_letters` (Number) The minimum number of lowercase letters (a-z) required in the password. Ensures that lowercase characters are included for password complexity.
41+
- `min_password_length` (Number) The minimum length required for the password. Enforces a stronger password policy by ensuring a minimum number of characters.
42+
- `min_special_characters` (Number) The minimum number of special characters (e.g., !, @, #, $, %) required in the password. This increases the password's security level by including symbols.
43+
- `min_uppercase_letters` (Number) The minimum number of uppercase letters (A-Z) required in the password. Helps ensure password complexity with a mix of case-sensitive characters.
44+
- `password_expiry_days` (Number) The number of days before the password expires. Must be between 1 and 1000 days. Defines how often passwords must be changed.
45+
- `password_regex` (String) A regular expression (regex) to define custom password patterns, such as enforcing specific characters or sequences in the password.
46+
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
47+
48+
### Read-Only
49+
50+
- `id` (String) The ID of this resource.
51+
52+
<a id="nestedblock--timeouts"></a>
53+
### Nested Schema for `timeouts`
54+
55+
Optional:
56+
57+
- `create` (String)
58+
- `delete` (String)
59+
- `update` (String)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
terraform {
2+
required_providers {
3+
spectrocloud = {
4+
version = ">= 0.1"
5+
source = "spectrocloud/spectrocloud"
6+
}
7+
}
8+
}
9+
10+
provider "spectrocloud" {
11+
host = var.sc_host
12+
api_key = var.sc_api_key
13+
project_name = var.sc_project_name
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
resource "spectrocloud_password_policy" "policy_regex" {
2+
# password_regex = "*"
3+
password_expiry_days = 123
4+
first_reminder_days = 5
5+
min_digits = 1
6+
min_lowercase_letters = 12
7+
min_password_length = 12
8+
min_special_characters = 1
9+
min_uppercase_letters = 1
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Spectro Cloud credentials
2+
sc_host = "{Enter Spectro Cloud API Host}" #e.g: api.spectrocloud.com (for SaaS)
3+
sc_api_key = "{Enter Spectro Cloud API Key}"
4+
sc_project_name = "{Enter Spectro Cloud Project Name}" #e.g: Default
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
variable "sc_host" {
2+
description = "Spectro Cloud Endpoint"
3+
default = "api.spectrocloud.com"
4+
}
5+
6+
variable "sc_api_key" {
7+
description = "Spectro Cloud API key"
8+
}
9+
10+
variable "sc_project_name" {
11+
description = "Spectro Cloud Project (e.g: Default)"
12+
default = "Default"
13+
}
14+
15+
variable "ssh_key_value" {
16+
description = "ssh key value"
17+
default = "ssh-rsa ...... == [email protected]"
18+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,4 @@ require (
126126
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
127127
)
128128

129-
//replace github.com/spectrocloud/palette-sdk-go => ../palette-sdk-go
129+
replace github.com/spectrocloud/palette-sdk-go => ../palette-sdk-go

spectrocloud/cluster_common.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ var (
3030
//clusterVsphereKeys = []string{"name", "context", "tags", "description", "cluster_meta_attribute", "cluster_profile", "apply_setting", "cloud_account_id", "cloud_config_id", "review_repave_state", "pause_agent_upgrades", "os_patch_on_boot", "os_patch_schedule", "os_patch_after", "kubeconfig", "admin_kube_config", "cloud_config", "machine_pool", "backup_policy", "scan_policy", "cluster_rbac_binding", "namespaces", "host_config", "location_config", "skip_completion", "force_delete", "force_delete_delay"}
3131
)
3232

33+
const (
34+
tenantString = "tenant"
35+
projectString = "project"
36+
)
37+
3338
func toNtpServers(in map[string]interface{}) []string {
3439
servers := make([]string, 0, 1)
3540
if _, ok := in["ntp_servers"]; ok {

spectrocloud/provider.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,12 @@ func New(_ string) func() *schema.Provider {
137137

138138
"spectrocloud_appliance": resourceAppliance(),
139139

140-
"spectrocloud_workspace": resourceWorkspace(),
141-
"spectrocloud_alert": resourceAlert(),
142-
"spectrocloud_ssh_key": resourceSSHKey(),
143-
"spectrocloud_user": resourceUser(),
144-
"spectrocloud_role": resourceRole(),
140+
"spectrocloud_workspace": resourceWorkspace(),
141+
"spectrocloud_alert": resourceAlert(),
142+
"spectrocloud_ssh_key": resourceSSHKey(),
143+
"spectrocloud_user": resourceUser(),
144+
"spectrocloud_role": resourceRole(),
145+
"spectrocloud_password_policy": resourcePasswordPolicy(),
145146
},
146147
DataSourcesMap: map[string]*schema.Resource{
147148
"spectrocloud_permission": dataSourcePermission(),
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package spectrocloud
2+
3+
import (
4+
"context"
5+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
8+
"github.com/spectrocloud/palette-sdk-go/api/models"
9+
"time"
10+
)
11+
12+
func resourcePasswordPolicy() *schema.Resource {
13+
return &schema.Resource{
14+
CreateContext: resourcePasswordPolicyCreate,
15+
ReadContext: resourcePasswordPolicyRead,
16+
UpdateContext: resourcePasswordPolicyUpdate,
17+
DeleteContext: resourcePasswordPolicyDelete,
18+
19+
Timeouts: &schema.ResourceTimeout{
20+
Create: schema.DefaultTimeout(10 * time.Minute),
21+
Update: schema.DefaultTimeout(10 * time.Minute),
22+
Delete: schema.DefaultTimeout(10 * time.Minute),
23+
},
24+
SchemaVersion: 2,
25+
Schema: map[string]*schema.Schema{
26+
"password_regex": {
27+
Type: schema.TypeString,
28+
Optional: true,
29+
Default: "",
30+
ConflictsWith: []string{"min_password_length", "min_uppercase_letters",
31+
"min_digits", "min_lowercase_letters", "min_special_characters"},
32+
RequiredWith: []string{"password_expiry_days", "first_reminder_days"},
33+
Description: "A regular expression (regex) to define custom password patterns, such as enforcing specific characters or sequences in the password.",
34+
},
35+
"password_expiry_days": {
36+
Type: schema.TypeInt,
37+
Optional: true,
38+
Default: 999,
39+
ValidateFunc: validation.IntBetween(1, 1000),
40+
Description: "The number of days before the password expires. Must be between 1 and 1000 days. Defines how often passwords must be changed.",
41+
},
42+
"first_reminder_days": {
43+
Type: schema.TypeInt,
44+
Optional: true,
45+
Default: 5,
46+
Description: "The number of days before the password expiry to send the first reminder to the user. Default is 5 days before expiry.",
47+
},
48+
"min_password_length": {
49+
Type: schema.TypeInt,
50+
Optional: true,
51+
Default: 12,
52+
Description: "The minimum length required for the password. Enforces a stronger password policy by ensuring a minimum number of characters.",
53+
},
54+
"min_uppercase_letters": {
55+
Type: schema.TypeInt,
56+
Optional: true,
57+
Default: 1,
58+
Description: "The minimum number of uppercase letters (A-Z) required in the password. Helps ensure password complexity with a mix of case-sensitive characters.",
59+
},
60+
"min_digits": {
61+
Type: schema.TypeInt,
62+
Optional: true,
63+
Default: 1,
64+
Description: "The minimum number of numeric digits (0-9) required in the password. Ensures that passwords contain numerical characters.",
65+
},
66+
"min_lowercase_letters": {
67+
Type: schema.TypeInt,
68+
Optional: true,
69+
Default: 1,
70+
Description: "The minimum number of lowercase letters (a-z) required in the password. Ensures that lowercase characters are included for password complexity.",
71+
},
72+
"min_special_characters": {
73+
Type: schema.TypeInt,
74+
Optional: true,
75+
Default: 1,
76+
Description: "The minimum number of special characters (e.g., !, @, #, $, %) required in the password. This increases the password's security level by including symbols.",
77+
},
78+
},
79+
}
80+
}
81+
82+
func toPasswordPolicy(d *schema.ResourceData) (*models.V1TenantPasswordPolicyEntity, error) {
83+
if d.Get("password_regex").(string) != "" {
84+
return &models.V1TenantPasswordPolicyEntity{
85+
IsRegex: true,
86+
Regex: d.Get("password_regex").(string),
87+
ExpiryDurationInDays: int64(d.Get("password_expiry_days").(int)),
88+
FirstReminderInDays: int64(d.Get("first_reminder_days").(int)),
89+
}, nil
90+
}
91+
return &models.V1TenantPasswordPolicyEntity{
92+
ExpiryDurationInDays: int64(d.Get("password_expiry_days").(int)),
93+
FirstReminderInDays: int64(d.Get("first_reminder_days").(int)),
94+
IsRegex: false,
95+
MinLength: int64(d.Get("min_password_length").(int)),
96+
MinNumOfBlockLetters: int64(d.Get("min_uppercase_letters").(int)),
97+
MinNumOfDigits: int64(d.Get("min_digits").(int)),
98+
MinNumOfSmallLetters: int64(d.Get("min_lowercase_letters").(int)),
99+
MinNumOfSpecialCharacters: int64(d.Get("min_special_characters").(int)),
100+
Regex: "",
101+
}, nil
102+
}
103+
104+
func toPasswordPolicyDefault(d *schema.ResourceData) (*models.V1TenantPasswordPolicyEntity, error) {
105+
return &models.V1TenantPasswordPolicyEntity{
106+
ExpiryDurationInDays: 999,
107+
FirstReminderInDays: 5,
108+
IsRegex: false,
109+
MinLength: 6,
110+
MinNumOfBlockLetters: 1,
111+
MinNumOfDigits: 1,
112+
MinNumOfSmallLetters: 1,
113+
MinNumOfSpecialCharacters: 1,
114+
Regex: "",
115+
}, nil
116+
}
117+
118+
func flattenPasswordPolicy(passwordPolicy *models.V1TenantPasswordPolicyEntity, d *schema.ResourceData) error {
119+
var err error
120+
err = d.Set("password_regex", passwordPolicy.Regex)
121+
if err != nil {
122+
return err
123+
}
124+
err = d.Set("password_expiry_days", passwordPolicy.ExpiryDurationInDays)
125+
if err != nil {
126+
return err
127+
}
128+
err = d.Set("first_reminder_days", passwordPolicy.FirstReminderInDays)
129+
if err != nil {
130+
return err
131+
}
132+
err = d.Set("min_password_length", passwordPolicy.MinLength)
133+
if err != nil {
134+
return err
135+
}
136+
err = d.Set("min_uppercase_letters", passwordPolicy.MinNumOfBlockLetters)
137+
if err != nil {
138+
return err
139+
}
140+
err = d.Set("min_digits", passwordPolicy.MinNumOfDigits)
141+
if err != nil {
142+
return err
143+
}
144+
err = d.Set("min_lowercase_letters", passwordPolicy.MinNumOfSmallLetters)
145+
if err != nil {
146+
return err
147+
}
148+
err = d.Set("min_special_characters", passwordPolicy.MinNumOfSpecialCharacters)
149+
if err != nil {
150+
return err
151+
}
152+
return nil
153+
}
154+
155+
func resourcePasswordPolicyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
156+
c := getV1ClientWithResourceContext(m, tenantString)
157+
var diags diag.Diagnostics
158+
passwordPolicy, err := toPasswordPolicy(d)
159+
if err != nil {
160+
return diag.FromErr(err)
161+
}
162+
tenantUID, err := c.GetTenantUID()
163+
if err != nil {
164+
return diag.FromErr(err)
165+
}
166+
// For Password Policy we don't have support for creation it's always an update
167+
err = c.UpdatePasswordPolicy(tenantUID, passwordPolicy)
168+
if err != nil {
169+
return diag.FromErr(err)
170+
}
171+
d.SetId("default-password-policy-id")
172+
return diags
173+
}
174+
175+
func resourcePasswordPolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
176+
//c := getV1ClientWithResourceContext(m, tenantString)
177+
var diags diag.Diagnostics
178+
return diags
179+
}
180+
181+
func resourcePasswordPolicyUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
182+
c := getV1ClientWithResourceContext(m, tenantString)
183+
var diags diag.Diagnostics
184+
passwordPolicy, err := toPasswordPolicy(d)
185+
if err != nil {
186+
return diag.FromErr(err)
187+
}
188+
tenantUID, err := c.GetTenantUID()
189+
if err != nil {
190+
return diag.FromErr(err)
191+
}
192+
// For Password Policy we don't have support for creation it's always an update
193+
err = c.UpdatePasswordPolicy(tenantUID, passwordPolicy)
194+
if err != nil {
195+
return diag.FromErr(err)
196+
}
197+
198+
return diags
199+
}
200+
201+
func resourcePasswordPolicyDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
202+
c := getV1ClientWithResourceContext(m, tenantString)
203+
var diags diag.Diagnostics
204+
// We can't delete the base password policy, instead
205+
passwordPolicy, err := toPasswordPolicyDefault(d)
206+
if err != nil {
207+
return diag.FromErr(err)
208+
}
209+
tenantUID, err := c.GetTenantUID()
210+
if err != nil {
211+
return diag.FromErr(err)
212+
}
213+
// For Password Policy we don't have support for creation it's always an update
214+
err = c.UpdatePasswordPolicy(tenantUID, passwordPolicy)
215+
if err != nil {
216+
return diag.FromErr(err)
217+
}
218+
d.SetId("")
219+
return diags
220+
}

0 commit comments

Comments
 (0)