Skip to content

Commit

Permalink
PLT-1523: Added password_policy support in terraform.
Browse files Browse the repository at this point in the history
  • Loading branch information
SivaanandM committed Dec 18, 2024
1 parent 74f7bcc commit 55a2468
Show file tree
Hide file tree
Showing 11 changed files with 537 additions and 6 deletions.
59 changes: 59 additions & 0 deletions docs/resources/password_policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
page_title: "spectrocloud_password_policy Resource - terraform-provider-spectrocloud"
subcategory: ""
description: |-
---

# spectrocloud_password_policy (Resource)



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.

~> 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.

## Example Usage

An example of managing an password policy in Palette.

```hcl
resource "spectrocloud_password_policy" "policy_regex" {
# password_regex = "*"
password_expiry_days = 123
first_reminder_days = 5
min_digits = 1
min_lowercase_letters = 12
min_password_length = 12
min_special_characters = 1
min_uppercase_letters = 1
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- `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.
- `min_digits` (Number) The minimum number of numeric digits (0-9) required in the password. Ensures that passwords contain numerical characters.
- `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.
- `min_password_length` (Number) The minimum length required for the password. Enforces a stronger password policy by ensuring a minimum number of characters.
- `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.
- `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.
- `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.
- `password_regex` (String) A regular expression (regex) to define custom password patterns, such as enforcing specific characters or sequences in the password.
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))

### Read-Only

- `id` (String) The ID of this resource.

<a id="nestedblock--timeouts"></a>
### Nested Schema for `timeouts`

Optional:

- `create` (String)
- `delete` (String)
- `update` (String)
14 changes: 14 additions & 0 deletions examples/resources/spectrocloud_password_policy/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_providers {
spectrocloud = {
version = ">= 0.1"
source = "spectrocloud/spectrocloud"
}
}
}

provider "spectrocloud" {
host = var.sc_host
api_key = var.sc_api_key
project_name = var.sc_project_name
}
10 changes: 10 additions & 0 deletions examples/resources/spectrocloud_password_policy/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
resource "spectrocloud_password_policy" "policy_regex" {
# password_regex = "*"
password_expiry_days = 123
first_reminder_days = 5
min_digits = 1
min_lowercase_letters = 12
min_password_length = 12
min_special_characters = 1
min_uppercase_letters = 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Spectro Cloud credentials
sc_host = "{Enter Spectro Cloud API Host}" #e.g: api.spectrocloud.com (for SaaS)
sc_api_key = "{Enter Spectro Cloud API Key}"
sc_project_name = "{Enter Spectro Cloud Project Name}" #e.g: Default
18 changes: 18 additions & 0 deletions examples/resources/spectrocloud_password_policy/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
variable "sc_host" {
description = "Spectro Cloud Endpoint"
default = "api.spectrocloud.com"
}

variable "sc_api_key" {
description = "Spectro Cloud API key"
}

variable "sc_project_name" {
description = "Spectro Cloud Project (e.g: Default)"
default = "Default"
}

variable "ssh_key_value" {
description = "ssh key value"
default = "ssh-rsa ...... == [email protected]"
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,4 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)

//replace github.com/spectrocloud/palette-sdk-go => ../palette-sdk-go
replace github.com/spectrocloud/palette-sdk-go => ../palette-sdk-go
5 changes: 5 additions & 0 deletions spectrocloud/cluster_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ var (
//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"}
)

const (
tenantString = "tenant"
projectString = "project"
)

func toNtpServers(in map[string]interface{}) []string {
servers := make([]string, 0, 1)
if _, ok := in["ntp_servers"]; ok {
Expand Down
11 changes: 6 additions & 5 deletions spectrocloud/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,12 @@ func New(_ string) func() *schema.Provider {

"spectrocloud_appliance": resourceAppliance(),

"spectrocloud_workspace": resourceWorkspace(),
"spectrocloud_alert": resourceAlert(),
"spectrocloud_ssh_key": resourceSSHKey(),
"spectrocloud_user": resourceUser(),
"spectrocloud_role": resourceRole(),
"spectrocloud_workspace": resourceWorkspace(),
"spectrocloud_alert": resourceAlert(),
"spectrocloud_ssh_key": resourceSSHKey(),
"spectrocloud_user": resourceUser(),
"spectrocloud_role": resourceRole(),
"spectrocloud_password_policy": resourcePasswordPolicy(),
},
DataSourcesMap: map[string]*schema.Resource{
"spectrocloud_permission": dataSourcePermission(),
Expand Down
220 changes: 220 additions & 0 deletions spectrocloud/resource_password_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package spectrocloud

import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/spectrocloud/palette-sdk-go/api/models"
"time"
)

func resourcePasswordPolicy() *schema.Resource {
return &schema.Resource{
CreateContext: resourcePasswordPolicyCreate,
ReadContext: resourcePasswordPolicyRead,
UpdateContext: resourcePasswordPolicyUpdate,
DeleteContext: resourcePasswordPolicyDelete,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Update: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
},
SchemaVersion: 2,
Schema: map[string]*schema.Schema{
"password_regex": {
Type: schema.TypeString,
Optional: true,
Default: "",
ConflictsWith: []string{"min_password_length", "min_uppercase_letters",
"min_digits", "min_lowercase_letters", "min_special_characters"},
RequiredWith: []string{"password_expiry_days", "first_reminder_days"},
Description: "A regular expression (regex) to define custom password patterns, such as enforcing specific characters or sequences in the password.",
},
"password_expiry_days": {
Type: schema.TypeInt,
Optional: true,
Default: 999,
ValidateFunc: validation.IntBetween(1, 1000),
Description: "The number of days before the password expires. Must be between 1 and 1000 days. Defines how often passwords must be changed.",
},
"first_reminder_days": {
Type: schema.TypeInt,
Optional: true,
Default: 5,
Description: "The number of days before the password expiry to send the first reminder to the user. Default is 5 days before expiry.",
},
"min_password_length": {
Type: schema.TypeInt,
Optional: true,
Default: 12,
Description: "The minimum length required for the password. Enforces a stronger password policy by ensuring a minimum number of characters.",
},
"min_uppercase_letters": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "The minimum number of uppercase letters (A-Z) required in the password. Helps ensure password complexity with a mix of case-sensitive characters.",
},
"min_digits": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "The minimum number of numeric digits (0-9) required in the password. Ensures that passwords contain numerical characters.",
},
"min_lowercase_letters": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "The minimum number of lowercase letters (a-z) required in the password. Ensures that lowercase characters are included for password complexity.",
},
"min_special_characters": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
Description: "The minimum number of special characters (e.g., !, @, #, $, %) required in the password. This increases the password's security level by including symbols.",
},
},
}
}

func toPasswordPolicy(d *schema.ResourceData) (*models.V1TenantPasswordPolicyEntity, error) {
if d.Get("password_regex").(string) != "" {
return &models.V1TenantPasswordPolicyEntity{
IsRegex: true,
Regex: d.Get("password_regex").(string),
ExpiryDurationInDays: int64(d.Get("password_expiry_days").(int)),
FirstReminderInDays: int64(d.Get("first_reminder_days").(int)),
}, nil
}
return &models.V1TenantPasswordPolicyEntity{
ExpiryDurationInDays: int64(d.Get("password_expiry_days").(int)),
FirstReminderInDays: int64(d.Get("first_reminder_days").(int)),
IsRegex: false,
MinLength: int64(d.Get("min_password_length").(int)),
MinNumOfBlockLetters: int64(d.Get("min_uppercase_letters").(int)),
MinNumOfDigits: int64(d.Get("min_digits").(int)),
MinNumOfSmallLetters: int64(d.Get("min_lowercase_letters").(int)),
MinNumOfSpecialCharacters: int64(d.Get("min_special_characters").(int)),
Regex: "",
}, nil
}

func toPasswordPolicyDefault(d *schema.ResourceData) (*models.V1TenantPasswordPolicyEntity, error) {
return &models.V1TenantPasswordPolicyEntity{
ExpiryDurationInDays: 999,
FirstReminderInDays: 5,
IsRegex: false,
MinLength: 6,
MinNumOfBlockLetters: 1,
MinNumOfDigits: 1,
MinNumOfSmallLetters: 1,
MinNumOfSpecialCharacters: 1,
Regex: "",
}, nil
}

func flattenPasswordPolicy(passwordPolicy *models.V1TenantPasswordPolicyEntity, d *schema.ResourceData) error {
var err error
err = d.Set("password_regex", passwordPolicy.Regex)
if err != nil {
return err
}
err = d.Set("password_expiry_days", passwordPolicy.ExpiryDurationInDays)
if err != nil {
return err
}
err = d.Set("first_reminder_days", passwordPolicy.FirstReminderInDays)
if err != nil {
return err
}
err = d.Set("min_password_length", passwordPolicy.MinLength)
if err != nil {
return err
}
err = d.Set("min_uppercase_letters", passwordPolicy.MinNumOfBlockLetters)
if err != nil {
return err
}
err = d.Set("min_digits", passwordPolicy.MinNumOfDigits)
if err != nil {
return err
}
err = d.Set("min_lowercase_letters", passwordPolicy.MinNumOfSmallLetters)
if err != nil {
return err
}
err = d.Set("min_special_characters", passwordPolicy.MinNumOfSpecialCharacters)
if err != nil {
return err
}
return nil
}

func resourcePasswordPolicyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := getV1ClientWithResourceContext(m, tenantString)
var diags diag.Diagnostics
passwordPolicy, err := toPasswordPolicy(d)
if err != nil {
return diag.FromErr(err)
}
tenantUID, err := c.GetTenantUID()
if err != nil {
return diag.FromErr(err)
}
// For Password Policy we don't have support for creation it's always an update
err = c.UpdatePasswordPolicy(tenantUID, passwordPolicy)
if err != nil {
return diag.FromErr(err)
}
d.SetId("default-password-policy-id")
return diags
}

func resourcePasswordPolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
//c := getV1ClientWithResourceContext(m, tenantString)
var diags diag.Diagnostics
return diags
}

func resourcePasswordPolicyUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := getV1ClientWithResourceContext(m, tenantString)
var diags diag.Diagnostics
passwordPolicy, err := toPasswordPolicy(d)
if err != nil {
return diag.FromErr(err)
}
tenantUID, err := c.GetTenantUID()
if err != nil {
return diag.FromErr(err)
}
// For Password Policy we don't have support for creation it's always an update
err = c.UpdatePasswordPolicy(tenantUID, passwordPolicy)
if err != nil {
return diag.FromErr(err)
}

return diags
}

func resourcePasswordPolicyDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := getV1ClientWithResourceContext(m, tenantString)
var diags diag.Diagnostics
// We can't delete the base password policy, instead
passwordPolicy, err := toPasswordPolicyDefault(d)
if err != nil {
return diag.FromErr(err)
}
tenantUID, err := c.GetTenantUID()
if err != nil {
return diag.FromErr(err)
}
// For Password Policy we don't have support for creation it's always an update
err = c.UpdatePasswordPolicy(tenantUID, passwordPolicy)
if err != nil {
return diag.FromErr(err)
}
d.SetId("")
return diags
}
Loading

0 comments on commit 55a2468

Please sign in to comment.