Skip to content

Commit

Permalink
Create aws_security_sub_category_association implemented for controls
Browse files Browse the repository at this point in the history
  • Loading branch information
gramsa49 committed Oct 7, 2022
1 parent f6b68ab commit e95dec1
Show file tree
Hide file tree
Showing 5 changed files with 508 additions and 10 deletions.
36 changes: 36 additions & 0 deletions docs/resources/security_sub_category_association.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "wiz_security_sub_category_association Resource - terraform-provider-wiz"
subcategory: ""
description: |-
Manage associations between security sub-categories and policies. This resource can only be used with custom security sub-categories. Wiz managed or custom policies can be referenced. When the association is removed from state, all associations managed by this resource will be removed. Associations managed outside this resouce declaration will remain untouched through the lifecycle of this resource.
---

# wiz_security_sub_category_association (Resource)

Manage associations between security sub-categories and policies. This resource can only be used with custom security sub-categories. Wiz managed or custom policies can be referenced. When the association is removed from state, all associations managed by this resource will be removed. Associations managed outside this resouce declaration will remain untouched through the lifecycle of this resource.



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

### Required

- `security_sub_category_id` (String) Security sub-category ID.

### Optional

- `cloud_config_rule_ids` (List of String) List of cloud config rule IDs.
- Required exactly one of: `[cloud_config_rule_ids control_ids host_config_rule_ids]`.
- `control_ids` (List of String) List of control IDs.
- Required exactly one of: `[cloud_config_rule_ids control_ids host_config_rule_ids]`.
- `details` (String) Details of the association. This information is not used to manage resources, but can serve as notes for the associations.
- `host_config_rule_ids` (List of String) List of host config rule IDs.
- Required exactly one of: `[cloud_config_rule_ids control_ids host_config_rule_ids]`.

### Read-Only

- `id` (String) Internal identifier for the association.


21 changes: 11 additions & 10 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,16 +252,17 @@ yLyKQXhw2W2Xs0qLeC1etA+jTGDK4UfLeC0SF7FSi8o5LL21L8IzApar2pR/
"wiz_organizations": dataSourceWizOrganizations(),
},
ResourcesMap: map[string]*schema.Resource{
"wiz_automation_action": resourceWizAutomationAction(),
"wiz_automation_rule": resourceWizAutomationRule(),
"wiz_cicd_scan_policy": resourceWizCICDScanPolicy(),
"wiz_cloud_config_rule": resourceWizCloudConfigurationRule(),
"wiz_control": resourceWizControl(),
"wiz_project": resourceWizProject(),
"wiz_saml_idp": resourceWizSAMLIdP(),
"wiz_security_framework": resourceWizSecurityFramework(),
"wiz_service_account": resourceWizServiceAccount(),
"wiz_user": resourceWizUser(),
"wiz_automation_action": resourceWizAutomationAction(),
"wiz_automation_rule": resourceWizAutomationRule(),
"wiz_cicd_scan_policy": resourceWizCICDScanPolicy(),
"wiz_cloud_config_rule": resourceWizCloudConfigurationRule(),
"wiz_control": resourceWizControl(),
"wiz_project": resourceWizProject(),
"wiz_saml_idp": resourceWizSAMLIdP(),
"wiz_security_sub_category_association": resourceWizSecuritySubCategoryAssociation(),
"wiz_security_framework": resourceWizSecurityFramework(),
"wiz_service_account": resourceWizServiceAccount(),
"wiz_user": resourceWizUser(),
},
}
p.ConfigureContextFunc = configure(version, p)
Expand Down
260 changes: 260 additions & 0 deletions internal/provider/resource_security_sub_category_association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
package provider

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-log/tflog"
"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"
"wiz.io/hashicorp/terraform-provider-wiz/internal"
"wiz.io/hashicorp/terraform-provider-wiz/internal/client"
"wiz.io/hashicorp/terraform-provider-wiz/internal/utils"
"wiz.io/hashicorp/terraform-provider-wiz/internal/vendor"
)

func resourceWizSecuritySubCategoryAssociation() *schema.Resource {
return &schema.Resource{
Description: "Manage associations between security sub-categories and policies. This resource can only be used with custom security sub-categories. Wiz managed or custom policies can be referenced. When the association is removed from state, all associations managed by this resource will be removed. Associations managed outside this resouce declaration will remain untouched through the lifecycle of this resource.",
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Description: "Internal identifier for the association.",
Computed: true,
},
"details": {
Type: schema.TypeString,
Description: "Details of the association. This information is not used to manage resources, but can serve as notes for the associations.",
Optional: true,
},
"cloud_config_rule_ids": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Description: "List of cloud config rule IDs.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
ExactlyOneOf: []string{
"cloud_config_rule_ids",
"control_ids",
"host_config_rule_ids",
},
},
"control_ids": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Description: "List of control IDs.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
ExactlyOneOf: []string{
"cloud_config_rule_ids",
"control_ids",
"host_config_rule_ids",
},
},
"host_config_rule_ids": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Description: "List of host config rule IDs.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
ExactlyOneOf: []string{
"cloud_config_rule_ids",
"control_ids",
"host_config_rule_ids",
},
},
"security_sub_category_id": {
Type: schema.TypeString,
Required: true,
Description: "Security sub-category ID.",
ValidateDiagFunc: validation.ToDiagFunc(
validation.IsUUID,
),
},
},
CreateContext: resourceWizSecuritySubCategoryAssociationCreate,
ReadContext: resourceWizSecuritySubCategoryAssociationRead,
UpdateContext: resourceWizSecuritySubCategoryAssociationUpdate,
DeleteContext: resourceWizSecuritySubCategoryAssociationDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

// ReadSecurityFrameworkPayload struct -- updates
type ReadSecuritySubCategoryPayload struct {
SecuritySubCategory vendor.SecuritySubCategory `json:"securitySubCategory"`
}

// UpdateControls struct
type UpdateControls struct {
UpdateControls vendor.UpdateControlsPayload `json:"updateControls"`
}

func resourceWizSecuritySubCategoryAssociationCreate(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) {
tflog.Info(ctx, "resourceWizSecuritySubCategoryAssociationCreate called...")

// determine the policy type
var associationType string
var checkAssociationType bool

_, checkAssociationType = d.GetOk("host_config_rule_ids")
if checkAssociationType {
associationType = "host_config_rule"
}
_, checkAssociationType = d.GetOk("control_ids")
if checkAssociationType {
associationType = "control"
}
_, checkAssociationType = d.GetOk("cloud_config_rule_ids")
if checkAssociationType {
associationType = "cloud_config_rule"
}
tflog.Debug(ctx, fmt.Sprintf("Association Type: %s", associationType))

// read security sub-category associations for the policy type
switch associationType {
case "control":
tflog.Debug(ctx, fmt.Sprintf("Control IDs: %T %s", d.Get("control_ids"), utils.PrettyPrint(d.Get("control_ids"))))

// define the graphql query
query := `query Control (
$id: ID!
){
control(
id: $id
) {
id
name
description
enabled
securitySubCategories {
id
title
}
}
}`

for a, b := range d.Get("control_ids").([]interface{}) {
tflog.Debug(ctx, fmt.Sprintf("a: %T %d", a, a))
tflog.Debug(ctx, fmt.Sprintf("b: %T %s", b, b))

// populate the graphql variables
vars := &internal.QueryVariables{}
vars.ID = b.(string)

// process the request
data := &ReadControlPayload{}
requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "security_sub_category_association", "read")
diags = append(diags, requestDiags...)
if len(diags) > 0 {
tflog.Error(ctx, "Error from API call, resource not found.")
if data.Control.ID == "" {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Control not found",
Detail: fmt.Sprintf("Control ID: %s", b.(string)),
})

return diags
}
}

// Retrieve the security sub-category ids
var sscids []string
for e, f := range data.Control.SecuritySubCategories {
tflog.Debug(ctx, fmt.Sprintf("e: %T %d", e, e))
tflog.Debug(ctx, fmt.Sprintf("f: %T %s", f, utils.PrettyPrint(f)))
sscids = append(sscids, f.ID)
}

tflog.Debug(ctx, fmt.Sprintf("Existing security sub-category associations for control %s %s", b, utils.PrettyPrint(sscids)))

// determine which security sub-categories are defined in the tf resource but not in wiz
missing := utils.Missing(
sscids,
[]string{d.Get("security_sub_category_id").(string)},
)
tflog.Debug(ctx, fmt.Sprintf("Security sub-categories missing from control %s: %s", b.(string), missing))

// define the graphql query
mutation := `mutation UpdateControls(
$input: UpdateControlsInput!
) {
updateControls(
input: $input
) {
successCount
failCount
errors {
reason
control {
id
}
}
}
}`

// populate the graphql variables
mvars := &vendor.UpdateControlsInput{}
mvars.IDS = []string{b.(string)}
mvars.SecuritySubCategoriesToAdd = missing

// print the input variables
tflog.Debug(ctx, fmt.Sprintf("Updates: %S", utils.PrettyPrint(mvars)))

// process the request
mdata := &vendor.UpdateControlsPayload{}
mrequestDiags := client.ProcessRequest(ctx, m, mvars, mdata, mutation, "security_sub_category_association", "update")
diags = append(diags, mrequestDiags...)
if len(diags) > 0 {
return diags
}
}
}

// set the id
d.SetId(fmt.Sprintf("%s-%s", associationType, d.Get("security_sub_category_id").(string)))

//return resourceWizControlRead(ctx, d, m)
return diags
}

func resourceWizSecuritySubCategoryAssociationRead(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) {
tflog.Info(ctx, "resourceWizSecuritySubCategoryAssociationRead called...")

// read current security sub-categories for the control
// remove all sub-categories not defined in the tf resource
// populate the resource data

return diags
}

func resourceWizSecuritySubCategoryAssociationUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) {
tflog.Info(ctx, "resourceWizSecuritySubCategoryAssociationUpdate called...")

// read current security sub-categories for the control
// determine which security sub-categories are defined in the tf resource but not in wiz
// compute a superset of security sub-categories
// issue the update

return resourceWizSecuritySubCategoryAssociationRead(ctx, d, m)
}

func resourceWizSecuritySubCategoryAssociationDelete(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) {
tflog.Info(ctx, "resourceWizSecuritySubCategoryAssociationDelete called...")

// read current security sub-categories for the control
// strip all security sub-categories defined in the tf resource from the list
// issue the update

return diags
}
33 changes: 33 additions & 0 deletions internal/utils/helper_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,36 @@ func SliceOfStringToMDUList(input []string) string {
}
return output
}

// Missing returns the elements in a that are missing from b
func Missing(a, b []string) []string {
type void struct{}

// create map with length of the 'a' slice
ma := make(map[string]void, len(a))
diffs := []string{}
// Convert first slice to map with empty struct (0 bytes)
for _, ka := range a {
ma[ka] = void{}
}
// find missing values in a
for _, kb := range b {
if _, ok := ma[kb]; !ok {
diffs = append(diffs, kb)
}
}
return diffs
}

// Returns the unique values in a slice of strings
func Unique(s []string) []string {
inResult := make(map[string]bool)
var result []string
for _, str := range s {
if _, ok := inResult[str]; !ok {
inResult[str] = true
result = append(result, str)
}
}
return result
}
Loading

1 comment on commit e95dec1

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠ goimports failed (.)

internal/provider/resource_security_sub_category_association.go
 
 // UpdateControls struct
 type UpdateControls struct {
-        UpdateControls vendor.UpdateControlsPayload `json:"updateControls"`
+	UpdateControls vendor.UpdateControlsPayload `json:"updateControls"`
 }
 
 func resourceWizSecuritySubCategoryAssociationCreate(ctx context.Context, d *schema.ResourceData, m interface{}) (diags diag.Diagnostics) {
internal/vendor/wiz.go
 
 // UpdateControlsInput struct
 type UpdateControlsInput struct {
-	IDS                           []string            `json:"ids,omitempty"`
+	IDS                           []string             `json:"ids,omitempty"`
 	Filters                       *ControlFilters      `json:"filters,omitempty"`
 	Patch                         *UpdateControlsPatch `json:"patch,omitempty"`
-	SecuritySubCategoriesToAdd    []string            `json:"securitySubCategoriesToAdd,omitempty"`
-	SecuritySubCategoriesToRemove []string            `json:"securitySubCategoriesToRemove,omitempty"`
+	SecuritySubCategoriesToAdd    []string             `json:"securitySubCategoriesToAdd,omitempty"`
+	SecuritySubCategoriesToRemove []string             `json:"securitySubCategoriesToRemove,omitempty"`
 }
 
 // ControlFilters struct
 type ControlFilters struct {
-	ID                  []string     `json:"id,omitempty"`
-	Search              string       `json:"search,omitempty"`
-	Type                []string     `json:"type,omitempty"` // enum ControlType
-	Project             []string     `json:"project,omitempty"`
-	CreatedBy           string       `json:"createdBy,omitempty"` // enum ControlCreatorType
-	SecurityFramework   []string     `json:"securityFramework,omitempty"`
-	SecuritySubCategory []string     `json:"securitySubCategory,omitempty"`
-	SecurityCategory    []string     `json:"securityCategory,omitempty"`
-	FrameworkCategory   []string     `json:"frameworkCategory,omitempty"`
-	Tag                 string       `json:"tag,omitempty"`
-	EntityType          string       `json:"entityType,omitempty"` // scalar
-	Severity            string       `json:"severity,omitempty"`   // enum Severity
+	ID                  []string      `json:"id,omitempty"`
+	Search              string        `json:"search,omitempty"`
+	Type                []string      `json:"type,omitempty"` // enum ControlType
+	Project             []string      `json:"project,omitempty"`
+	CreatedBy           string        `json:"createdBy,omitempty"` // enum ControlCreatorType
+	SecurityFramework   []string      `json:"securityFramework,omitempty"`
+	SecuritySubCategory []string      `json:"securitySubCategory,omitempty"`
+	SecurityCategory    []string      `json:"securityCategory,omitempty"`
+	FrameworkCategory   []string      `json:"frameworkCategory,omitempty"`
+	Tag                 string        `json:"tag,omitempty"`
+	EntityType          string        `json:"entityType,omitempty"` // scalar
+	Severity            string        `json:"severity,omitempty"`   // enum Severity
 	WithIssues          *IssueFilters `json:"withIssues,omitempty"`
-	Enabled             *bool        `json:"enabled,omitempty"`
-	RiskEqualsAny       []string     `json:"riskEqualsAny,omitempty"`
-	RiskEqualsAll       []string     `json:"riskEqualsAll,omitempty"`
+	Enabled             *bool         `json:"enabled,omitempty"`
+	RiskEqualsAny       []string      `json:"riskEqualsAny,omitempty"`
+	RiskEqualsAll       []string      `json:"riskEqualsAll,omitempty"`
 }
 
 // IssueFilters struct

Please sign in to comment.