Skip to content

Commit

Permalink
feat: add expiry field for Vulnerability Exceptions (#316)
Browse files Browse the repository at this point in the history
* 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 <darren.murray@lacework.net>
  • Loading branch information
dmurray-lacework authored Jun 10, 2022
1 parent 2fa4909 commit fb654c0
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
29 changes: 15 additions & 14 deletions examples/resource_lacework_vulnerability_exception_host/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
30 changes: 29 additions & 1 deletion lacework/resource_lacework_vulnerability_exception_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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,
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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{})
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
27 changes: 25 additions & 2 deletions lacework/resource_lacework_vulnerability_exception_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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,
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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{})
Expand Down Expand Up @@ -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)),
Expand All @@ -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"),
Expand Down
20 changes: 20 additions & 0 deletions lacework/schema_validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
})
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/vulnerability_exception_host.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit fb654c0

Please sign in to comment.