From fb654c07a68e61b58660fa66f17b01894225c260 Mon Sep 17 00:00:00 2001 From: Darren <75614232+dmurray-lacework@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:47:25 +0100 Subject: [PATCH] feat: add expiry field for Vulnerability Exceptions (#316) * feat: add expiry time field to vulnerability exceptions * feat: add expiry time field to vulnerability exception host * fix: set expiry on host read * refactor: address code review comments Signed-off-by: Darren Murray --- .../main.tf | 6 ++++ .../main.tf | 29 +++++++++--------- ...ework_vulnerability_exception_host_test.go | 1 - ...ework_vulnerability_exception_container.go | 30 ++++++++++++++++++- ..._vulnerability_exception_container_test.go | 26 ++++++++++++++++ ...e_lacework_vulnerability_exception_host.go | 27 +++++++++++++++-- lacework/schema_validators.go | 20 +++++++++++++ .../api/cloud_accounts_aws_eks_audit.go | 1 + .../go-sdk/api/vulnerability_exceptions.go | 4 +-- ...rability_exception_container.html.markdown | 2 ++ ...vulnerability_exception_host.html.markdown | 2 ++ 11 files changed, 127 insertions(+), 21 deletions(-) create mode 100644 lacework/resource_lacework_vulnerability_exception_container_test.go diff --git a/examples/resource_lacework_vulnerability_exception_container/main.tf b/examples/resource_lacework_vulnerability_exception_container/main.tf index 578fe408..72a948c6 100644 --- a/examples/resource_lacework_vulnerability_exception_container/main.tf +++ b/examples/resource_lacework_vulnerability_exception_container/main.tf @@ -9,6 +9,7 @@ terraform { resource "lacework_vulnerability_exception_container" "example" { name = var.name description = var.description + expiry = var.expiry reason = "Accepted Risk" vulnerability_criteria { fixable = true @@ -43,6 +44,11 @@ variable "description" { default = "Container Vulnerability Exception created by Terraform" } +variable "expiry" { + type = string + default = "2023-06-06T15:55:15Z" +} + variable "package_name" { type = string default = "myPackage" diff --git a/examples/resource_lacework_vulnerability_exception_host/main.tf b/examples/resource_lacework_vulnerability_exception_host/main.tf index 058494cc..68b80843 100644 --- a/examples/resource_lacework_vulnerability_exception_host/main.tf +++ b/examples/resource_lacework_vulnerability_exception_host/main.tf @@ -7,57 +7,58 @@ terraform { } resource "lacework_vulnerability_exception_host" "example" { - name = var.name + name = var.name description = var.description - enabled = true - reason = "Accepted Risk" + enabled = true + expiry = "2023-06-06T15:55:15Z" + reason = "Accepted Risk" vulnerability_criteria { severities = ["Critical"] - cves = var.cves + cves = var.cves package { - name = var.package_name + name = var.package_name version = var.package_version } package { - name = "myPackage" + name = "myPackage" version = "2.0.0" } package { - name = "myOtherPackage" + name = "myOtherPackage" version = "1.0.0" } fixable = true } resource_scope { - hostnames = ["host1", "host2"] + hostnames = ["host1", "host2"] cluster_names = ["clust-abc", "clust-xyz"] external_ips = ["210.12.100.5"] - namespaces = ["namespace1", "namespace2"] + namespaces = ["namespace1", "namespace2"] } } variable "name" { - type = string + type = string default = "Terraform Host Vulnerability Exception" } variable "description" { - type = string + type = string default = "Host Vulnerability Exception created by Terraform" } variable "package_name" { - type = string + type = string default = "myPackage" } variable "package_version" { - type = string + type = string default = "1.0.0" } variable "cves" { - type = list(string) + type = list(string) default = ["CVE-2016-9840", "cve-2018-14599", "CVE-2018-6942"] } diff --git a/integration/resource_lacework_vulnerability_exception_host_test.go b/integration/resource_lacework_vulnerability_exception_host_test.go index 2a8da794..0f3e2f31 100644 --- a/integration/resource_lacework_vulnerability_exception_host_test.go +++ b/integration/resource_lacework_vulnerability_exception_host_test.go @@ -12,7 +12,6 @@ import ( // // It uses the go-sdk to verify the created vulnerability exception, // applies an update and destroys it -//nolint func TestVulnerabilityExceptionHostCreate(t *testing.T) { terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: "../examples/resource_lacework_vulnerability_exception_host", diff --git a/lacework/resource_lacework_vulnerability_exception_container.go b/lacework/resource_lacework_vulnerability_exception_container.go index 615acba0..8602e6a8 100644 --- a/lacework/resource_lacework_vulnerability_exception_container.go +++ b/lacework/resource_lacework_vulnerability_exception_container.go @@ -4,10 +4,12 @@ import ( "fmt" "log" "strings" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" "golang.org/x/text/cases" "golang.org/x/text/language" ) @@ -34,6 +36,12 @@ func resourceLaceworkVulnerabilityExceptionContainer() *schema.Resource { Optional: true, Description: "The description of the vulnerability exception", }, + "expiry": { + Type: schema.TypeString, + Optional: true, + Description: "The expiration date of the vulnerability exception", + ValidateDiagFunc: ValidateTimeFormat(time.RFC3339), + }, "enabled": { Type: schema.TypeBool, Optional: true, @@ -232,6 +240,14 @@ func resourceLaceworkVulnerabilityExceptionContainerCreate(d *schema.ResourceDat } ) + if d.Get("expiry") != nil { + expiryTime, err := time.Parse(time.RFC3339, d.Get("expiry").(string)) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to parse expiry time %s", d.Get("expiry").(string))) + } + vulnExCfg.ExpiryTime = expiryTime + } + if len(d.Get("resource_scope").([]interface{})) > 0 { vulnExCfg.ResourceScope = api.VulnerabilityExceptionContainerResourceScope{ ImageID: castAttributeToStringSlice(d, "resource_scope.0.image_ids"), @@ -290,6 +306,7 @@ func resourceLaceworkVulnerabilityExceptionContainerRead(d *schema.ResourceData, d.Set("created_by", response.Data.Props.CreatedBy) d.Set("updated_by", response.Data.Props.UpdatedBy) d.Set("type", response.Data.ExceptionType) + d.Set("expiry", response.Data.ExpiryTime) if len(d.Get("resource_scope").([]interface{})) > 0 { resourceScope := make(map[string]interface{}) @@ -328,6 +345,14 @@ func resourceLaceworkVulnerabilityExceptionContainerUpdate(d *schema.ResourceDat } ) + if d.Get("expiry") != nil { + expiryTime, err := time.Parse(time.RFC3339, d.Get("expiry").(string)) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to parse expiry time %s", d.Get("expiry").(string))) + } + vulnExCfg.ExpiryTime = expiryTime + } + if len(d.Get("resource_scope").([]interface{})) > 0 { vulnExCfg.ResourceScope = api.VulnerabilityExceptionContainerResourceScope{ ImageID: castAttributeToStringSlice(d, "resource_scope.0.image_ids"), @@ -402,7 +427,10 @@ func vulnerabilityExceptionReasonsSlice() []string { } func vulnerabilityExceptionFixableEnabled(fixable []int) bool { - return fixable[0] == 1 + if len(fixable) > 0 { + return fixable[0] == 1 + } + return false } func vulnerabilityExceptionFlattenPackages(vulnPackage []map[string][]string) []map[string]string { diff --git a/lacework/resource_lacework_vulnerability_exception_container_test.go b/lacework/resource_lacework_vulnerability_exception_container_test.go new file mode 100644 index 00000000..ab465a77 --- /dev/null +++ b/lacework/resource_lacework_vulnerability_exception_container_test.go @@ -0,0 +1,26 @@ +package lacework + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVulnerabilityExceptionFixable(t *testing.T) { + type fixableTests struct { + description string + input []int + expected bool + } + + for _, test := range []fixableTests{ + {"fixable array is nil", nil, false}, + {"fixable array is empty", []int{}, false}, + {"fixable array has a true value", []int{1}, true}, + {"fixable array has a false value", []int{0}, false}} { + t.Run(test.description, func(t *testing.T) { + result := vulnerabilityExceptionFixableEnabled(test.input) + assert.Equal(t, test.expected, result) + }) + } +} diff --git a/lacework/resource_lacework_vulnerability_exception_host.go b/lacework/resource_lacework_vulnerability_exception_host.go index 223220d6..f7a87d49 100644 --- a/lacework/resource_lacework_vulnerability_exception_host.go +++ b/lacework/resource_lacework_vulnerability_exception_host.go @@ -4,10 +4,12 @@ import ( "fmt" "log" "strings" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/lacework/go-sdk/api" + "github.com/pkg/errors" "golang.org/x/text/cases" "golang.org/x/text/language" ) @@ -34,6 +36,12 @@ func resourceLaceworkVulnerabilityExceptionHost() *schema.Resource { Optional: true, Description: "The description of the vulnerability exception", }, + "expiry": { + Type: schema.TypeString, + Optional: true, + Description: "The expiration date of the vulnerability exception", + ValidateDiagFunc: ValidateTimeFormat(time.RFC3339), + }, "enabled": { Type: schema.TypeBool, Optional: true, @@ -221,6 +229,13 @@ func resourceLaceworkVulnerabilityExceptionHostCreate(d *schema.ResourceData, me Fixable: d.Get("vulnerability_criteria.0.fixable").(bool), } ) + if d.Get("expiry") != nil { + expiryTime, err := time.Parse(time.RFC3339, d.Get("expiry").(string)) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to parse expiry time %s", d.Get("expiry").(string))) + } + vulnExCfg.ExpiryTime = expiryTime + } if len(d.Get("resource_scope").([]interface{})) > 0 { vulnExCfg.ResourceScope = api.VulnerabilityExceptionHostResourceScope{ @@ -278,6 +293,7 @@ func resourceLaceworkVulnerabilityExceptionHostRead(d *schema.ResourceData, meta d.Set("created_by", response.Data.Props.CreatedBy) d.Set("updated_by", response.Data.Props.UpdatedBy) d.Set("type", response.Data.ExceptionType) + d.Set("expiry", response.Data.ExpiryTime) if len(d.Get("resource_scope").([]interface{})) > 0 { resourceScope := make(map[string]interface{}) @@ -305,8 +321,7 @@ func resourceLaceworkVulnerabilityExceptionHostUpdate(d *schema.ResourceData, me lacework = meta.(*api.Client) severities = castAttributeToStringSlice(d, "vulnerability_criteria.0.severities") packages = castAttributeToArrayOfCustomKeyValueMap(d, "vulnerability_criteria.0.package", "name", "version") - - vulnExCfg = api.VulnerabilityExceptionConfig{ + vulnExCfg = api.VulnerabilityExceptionConfig{ Description: d.Get("description").(string), Type: api.VulnerabilityExceptionTypeHost, ExceptionReason: api.NewVulnerabilityExceptionReason(d.Get("reason").(string)), @@ -317,6 +332,14 @@ func resourceLaceworkVulnerabilityExceptionHostUpdate(d *schema.ResourceData, me } ) + if d.Get("expiry") != nil { + expiryTime, err := time.Parse(time.RFC3339, d.Get("expiry").(string)) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("unable to parse expiry time %s", d.Get("expiry").(string))) + } + vulnExCfg.ExpiryTime = expiryTime + } + if len(d.Get("resource_scope").([]interface{})) > 0 { vulnExCfg.ResourceScope = api.VulnerabilityExceptionHostResourceScope{ Hostname: castAttributeToStringSlice(d, "resource_scope.0.hostnames"), diff --git a/lacework/schema_validators.go b/lacework/schema_validators.go index 1cb971cd..f182cf8e 100644 --- a/lacework/schema_validators.go +++ b/lacework/schema_validators.go @@ -3,6 +3,7 @@ package lacework import ( "fmt" "strings" + "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -41,3 +42,22 @@ func StringDoesNotHavePrefix(chars string) schema.SchemaValidateDiagFunc { return warnings, errors }) } + +// ValidateTimeFormat returns a SchemaValidateFunc which validates that the +// value is in the timeformat supplied. +func ValidateTimeFormat(timeFormat string) schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc(func(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if _, err := time.Parse(timeFormat, v); err != nil { + errors = append(errors, fmt.Errorf("%s is not in format %s", v, timeFormat)) + return + } + + return + }) +} diff --git a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go index 352f6770..7e62f300 100644 --- a/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go +++ b/vendor/github.com/lacework/go-sdk/api/cloud_accounts_aws_eks_audit.go @@ -48,6 +48,7 @@ type AwsEksAuditIntegration struct { type AwsEksAuditData struct { Credentials AwsEksAuditCredentials `json:"crossAccountCredentials"` SnsArn string `json:"snsArn"` + S3BucketArn string `json:"s3BucketArn"` } type AwsEksAuditCredentials struct { diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go index 8ab153c8..06a4cda8 100644 --- a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go +++ b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go @@ -24,8 +24,6 @@ import ( "time" "github.com/pkg/errors" - - "github.com/lacework/go-sdk/lwtime" ) // VulnerabilityExceptionsService is the service that interacts with @@ -213,7 +211,7 @@ func NewVulnerabilityException(name string, exception VulnerabilityExceptionConf ) if !exception.ExpiryTime.IsZero() { - vulnException.ExpiryTime = exception.ExpiryTime.UTC().Format(lwtime.RFC3339Milli) + vulnException.ExpiryTime = exception.ExpiryTime.UTC().Format(time.RFC3339) } vulnException.ExceptionType = exception.Type.String() diff --git a/website/docs/r/vulnerability_exception_container.html.markdown b/website/docs/r/vulnerability_exception_container.html.markdown index 37eed5b5..b70f9c2c 100644 --- a/website/docs/r/vulnerability_exception_container.html.markdown +++ b/website/docs/r/vulnerability_exception_container.html.markdown @@ -16,6 +16,7 @@ Use this resource to create a Lacework Container Vulnerability Exception to cont resource "lacework_vulnerability_exception_container" "example" { name = "My Vuln Exception" description = "This is a vulnerability container exception" + expiry = "2023-06-06T15:55:15Z" enabled = true reason = "Accepted Risk" vulnerability_criteria { @@ -50,6 +51,7 @@ The following arguments are supported: See [Vulnerability Criteria](#vulnerability-criteria) below for details. * `description` - (Optional) The description of the vulnerability exception. * `enabled` - (Optional) The state of the external integration. Defaults to `true`. +* `expiry` - (Optional) The expiration date of the vulnerability exception. Example: `2022-06-01T16:35:00Z`. * `reason` - (Optional) The list of the severities that the rule will apply. Valid severities include: `Accepted Risk`, `False Positive`, `Compensating Controls`, Fix Pending` and `Other`. * `resource_scope` - (Optional) The resource scope. See [Resource Scope](#resource-scope) below for details. diff --git a/website/docs/r/vulnerability_exception_host.html.markdown b/website/docs/r/vulnerability_exception_host.html.markdown index f4bf17a1..f9af5513 100644 --- a/website/docs/r/vulnerability_exception_host.html.markdown +++ b/website/docs/r/vulnerability_exception_host.html.markdown @@ -18,6 +18,7 @@ resource "lacework_vulnerability_exception_host" "example" { description = "This is a vulnerability host exception" enabled = true reason = "Accepted Risk" + expiry = "2023-06-06T15:55:15Z" vulnerability_criteria { severities = ["critical"] cves = ["cve-2021-11111", "cve-2021-22222"] @@ -48,6 +49,7 @@ The following arguments are supported: See [Vulnerability Criteria](#vulnerability-criteria) below for details. * `description` - (Optional) The description of the vulnerability exception. * `enabled` - (Optional) The state of the external integration. Defaults to `true`. +* `expiry` - (Optional) The expiration date of the vulnerability exception. Example: `2022-06-01T16:35:00Z`. * `reason` - (Optional) The list of the severities that the rule will apply. Valid severities include: `Accepted Risk`, `False Positive`, `Compensating Controls`, Fix Pending` and `Other`. * `resource_scope` - (Optional) The resource scope. See [Resource Scope](#resource-scope) below for details.