From afa657aaea7bc17e75d2da61920bf39935fda616 Mon Sep 17 00:00:00 2001 From: Darren <75614232+dmurray-lacework@users.noreply.github.com> Date: Thu, 13 Jan 2022 14:36:02 +0000 Subject: [PATCH] feat(resource): New lacework_vulnerability_exception_host (#248) --- .../main.tf | 79 +++ go.mod | 2 +- go.sum | 13 +- integration/integration.go | 11 + ...ework_vulnerability_exception_host_test.go | 67 +++ lacework/casting.go | 11 + lacework/provider.go | 1 + ...e_lacework_vulnerability_exception_host.go | 421 ++++++++++++++++ .../github.com/hashicorp/hcl/v2/CHANGELOG.md | 17 + .../hashicorp/hcl/v2/hclsyntax/expression.go | 17 + .../hashicorp/hcl/v2/hclsyntax/parser.go | 151 ++++-- .../hcl/v2/hclsyntax/parser_template.go | 34 +- .../hashicorp/hcl/v2/hclsyntax/spec.md | 4 +- .../hashicorp/hcl/v2/hclsyntax/structure.go | 15 +- vendor/github.com/lacework/go-sdk/api/api.go | 3 + .../github.com/lacework/go-sdk/api/schemas.go | 3 +- vendor/github.com/lacework/go-sdk/api/v2.go | 43 +- .../github.com/lacework/go-sdk/api/version.go | 4 +- .../go-sdk/api/vulnerability_exceptions.go | 450 ++++++++++++++++++ .../api/vulnerability_exceptions_container.go | 85 ++++ .../api/vulnerability_exceptions_host.go | 84 ++++ .../mitchellh/mapstructure/CHANGELOG.md | 4 + .../mitchellh/mapstructure/mapstructure.go | 8 +- ...vulnerability_exception_host.html.markdown | 81 ++++ website/lacework.erb | 14 + 25 files changed, 1534 insertions(+), 88 deletions(-) create mode 100644 examples/resource_lacework_vulnerability_exception_host/main.tf create mode 100644 integration/resource_lacework_vulnerability_exception_host_test.go create mode 100644 lacework/resource_lacework_vulnerability_exception_host.go create mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go create mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go create mode 100644 vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go create mode 100644 website/docs/r/vulnerability_exception_host.html.markdown diff --git a/examples/resource_lacework_vulnerability_exception_host/main.tf b/examples/resource_lacework_vulnerability_exception_host/main.tf new file mode 100644 index 00000000..f9866686 --- /dev/null +++ b/examples/resource_lacework_vulnerability_exception_host/main.tf @@ -0,0 +1,79 @@ +terraform { + required_providers { + lacework = { + source = "lacework/lacework" + } + } +} + +resource "lacework_vulnerability_exception_host" "example" { + name = var.name + description = var.description + enabled = true + reason = "Accepted Risk" + vulnerability_criteria { + severities = ["Critical"] + cves = var.cves + package { + name = var.package_name + version = var.package_version + } + package { + name = "myPackage" + version = "2.0.0" + } + package { + name = "myOtherPackage" + version = "1.0.0" + } + fixable = true + } + resource_scope { + hostnames = ["host1", "host2"] + cluster_names = ["clust-abc", "clust-xyz"] + external_ips = ["210.12.100.5"] + namespaces = ["namespace1", "namespace2"] + } + expiration_time = "2023-01-19T23:26:10Z" +} + +variable "name" { + type = string + default = "Terraform Host Vulnerability Exception" +} + +variable "description" { + type = string + default = "Host Vulnerability Exception created by Terraform" +} + +variable "package_name" { + type = string + default = "myPackage" +} + +variable "package_version" { + type = string + default = "1.0.0" +} + +variable "cves" { + type = list(string) + default = ["CVE-2016-9840", "cve-2018-14599", "CVE-2018-6942"] +} + +output "name" { + value = lacework_vulnerability_exception_host.example.name +} + +output "description" { + value = lacework_vulnerability_exception_host.example.description +} + +output "cves" { + value = lacework_vulnerability_exception_host.example.vulnerability_criteria.0.cves +} + +output "packages" { + value = lacework_vulnerability_exception_host.example.vulnerability_criteria.0.package +} diff --git a/go.mod b/go.mod index 1ddc03fd..c58556fa 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/gruntwork-io/terratest v0.38.8 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1 github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect - github.com/lacework/go-sdk v0.22.1-0.20211209162214-4153f64f071b + github.com/lacework/go-sdk v0.23.1-0.20220107132053-ef65e232b7c7 github.com/oklog/run v1.1.0 // indirect github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 915625f7..b9ea80ce 100644 --- a/go.sum +++ b/go.sum @@ -558,8 +558,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= github.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= -github.com/hashicorp/hcl/v2 v2.10.1 h1:h4Xx4fsrRE26ohAk/1iGF/JBqRQbyUqu5Lvj60U54ys= -github.com/hashicorp/hcl/v2 v2.10.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc= +github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -660,8 +660,8 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LE github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= -github.com/lacework/go-sdk v0.22.1-0.20211209162214-4153f64f071b h1:7+FZmS7DszFj5to4ifA/nGyfF5F0HgHj7+7tFn14olQ= -github.com/lacework/go-sdk v0.22.1-0.20211209162214-4153f64f071b/go.mod h1:qY19L1pMZyebC17V06llwebxYfBbcEuGj9AXb2QrqWQ= +github.com/lacework/go-sdk v0.23.1-0.20220107132053-ef65e232b7c7 h1:2NNxZvpmlkAe7fkbouncWSAr4dVBa2sqcNzV0dvh6hc= +github.com/lacework/go-sdk v0.23.1-0.20220107132053-ef65e232b7c7/go.mod h1:hpRCkw5LhPqHyK7R4FJ7IJgVkG15kq86dk7CTn9Uk5o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -720,8 +720,9 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -791,7 +792,7 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/peterbourgon/diskv/v3 v3.0.0/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= +github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/integration/integration.go b/integration/integration.go index 58b58d3f..3665658d 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -204,6 +204,17 @@ func GetReportRuleProps(result string) api.ReportRuleResponse { return data } +func GetVulnerabilityExceptionProps(result string) api.VulnerabilityExceptionResponse { + id := GetSpecificIDFromTerraResults(1, result) + + var data api.VulnerabilityExceptionResponse + err := LwClient.V2.VulnerabilityExceptions.Get(id, &data) + if err != nil { + log.Fatalf("Unable to retrieve vulnerability exception with id: %s", id) + } + return data +} + // GetSpecificIDFromTerraResults returns the specific index id found in the Terraform output func GetSpecificIDFromTerraResults(i int, result string) string { re := regexp.MustCompile(`\[id=(.*?)\]`) diff --git a/integration/resource_lacework_vulnerability_exception_host_test.go b/integration/resource_lacework_vulnerability_exception_host_test.go new file mode 100644 index 00000000..2a8da794 --- /dev/null +++ b/integration/resource_lacework_vulnerability_exception_host_test.go @@ -0,0 +1,67 @@ +package integration + +import ( + "testing" + + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +// TestVulnerabilityExceptionHostCreate applies integration terraform: +// => '../examples/resource_lacework_vulnerability_exception_host' +// +// 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", + Vars: map[string]interface{}{ + "name": "Terraform Vulnerability Exception Host Test", + "description": "Vulnerability Exception Host created by Terraform", + "cves": []string{"CVE-2016-9840", "CVE-2018-14599", "CVE-2018-6942"}, + "package_name": "myPackage", + "package_version": "1.0.0", + }, + }) + defer terraform.Destroy(t, terraformOptions) + + // Create new Vulnerability Exception + create := terraform.InitAndApplyAndIdempotent(t, terraformOptions) + createProps := GetVulnerabilityExceptionProps(create) + + actualDescription := terraform.Output(t, terraformOptions, "description") + actualCves := terraform.Output(t, terraformOptions, "cves") + actualPackages := terraform.Output(t, terraformOptions, "packages") + + assert.Equal(t, "Vulnerability Exception Host created by Terraform", createProps.Data.Props.Description) + assert.Equal(t, []string{"CVE-2016-9840", "CVE-2018-14599", "CVE-2018-6942"}, createProps.Data.VulnerabilityCriteria.Cve) + assert.Equal(t, []map[string][]string{{"myPackage": {"1.0.0", "2.0.0"}}, {"myOtherPackage": {"1.0.0"}}}, createProps.Data.VulnerabilityCriteria.Package) + + assert.Equal(t, "Vulnerability Exception Host created by Terraform", actualDescription) + assert.Equal(t, "[CVE-2016-9840 CVE-2018-14599 CVE-2018-6942]", actualCves) + assert.Equal(t, "[map[name:myOtherPackage version:1.0.0] map[name:myPackage version:1.0.0] map[name:myPackage version:2.0.0]]", actualPackages) + + // Update Vulnerability Exception + terraformOptions.Vars = map[string]interface{}{ + "name": "Terraform Vulnerability Exception Host Test", + "description": "Updated Vulnerability Exception created by Terraform", + "cves": []string{"CVE-2016-9840", "CVE-2018-6940"}, + "package_name": "myUpdatedPackage", + "package_version": "1.1.0", + } + + update := terraform.ApplyAndIdempotent(t, terraformOptions) + updateProps := GetVulnerabilityExceptionProps(update) + actualDescription = terraform.Output(t, terraformOptions, "description") + actualCves = terraform.Output(t, terraformOptions, "cves") + actualPackages = terraform.Output(t, terraformOptions, "packages") + + assert.Equal(t, "Updated Vulnerability Exception created by Terraform", updateProps.Data.Props.Description) + assert.Equal(t, []string{"CVE-2016-9840", "CVE-2018-6940"}, updateProps.Data.VulnerabilityCriteria.Cve) + assert.Equal(t, []map[string][]string{{"myPackage": {"2.0.0"}}, {"myUpdatedPackage": {"1.1.0"}}, {"myOtherPackage": {"1.0.0"}}}, updateProps.Data.VulnerabilityCriteria.Package) + + assert.Equal(t, "Updated Vulnerability Exception created by Terraform", actualDescription) + assert.Equal(t, "[CVE-2016-9840 CVE-2018-6940]", actualCves) + assert.Equal(t, "[map[name:myOtherPackage version:1.0.0] map[name:myPackage version:2.0.0] map[name:myUpdatedPackage version:1.1.0]]", actualPackages) +} diff --git a/lacework/casting.go b/lacework/casting.go index d8685415..a531e910 100644 --- a/lacework/casting.go +++ b/lacework/casting.go @@ -95,6 +95,17 @@ func castAttributeToArrayOfKeyValueMap(d *schema.ResourceData, attr string) []ma return aMap } +func castAttributeToArrayOfCustomKeyValueMap(d *schema.ResourceData, attr string, key string, value string) []map[string]string { + list := d.Get(attr).(*schema.Set).List() + aMap := make([]map[string]string, len(list)) + for i, v := range list { + val := v.(map[string]interface{}) + aMap[i] = map[string]string{val[key].(string): val[value].(string)} + } + + return aMap +} + // convert an array of map of strings with string keys to a key/value TypeSet // needed for API v2 ContainerRegistry Limits // diff --git a/lacework/provider.go b/lacework/provider.go index 2730e982..71fa8030 100644 --- a/lacework/provider.go +++ b/lacework/provider.go @@ -101,6 +101,7 @@ func Provider() *schema.Provider { "lacework_resource_group_gcp": resourceLaceworkResourceGroupGcp(), "lacework_resource_group_machine": resourceLaceworkResourceGroupMachine(), "lacework_team_member": resourceLaceworkTeamMember(), + "lacework_vulnerability_exception_host": resourceLaceworkVulnerabilityExceptionHost(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/lacework/resource_lacework_vulnerability_exception_host.go b/lacework/resource_lacework_vulnerability_exception_host.go new file mode 100644 index 00000000..d5da748d --- /dev/null +++ b/lacework/resource_lacework_vulnerability_exception_host.go @@ -0,0 +1,421 @@ +package lacework + +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" +) + +func resourceLaceworkVulnerabilityExceptionHost() *schema.Resource { + return &schema.Resource{ + Create: resourceLaceworkVulnerabilityExceptionHostCreate, + Read: resourceLaceworkVulnerabilityExceptionHostRead, + Update: resourceLaceworkVulnerabilityExceptionHostUpdate, + Delete: resourceLaceworkVulnerabilityExceptionHostDelete, + + Importer: &schema.ResourceImporter{ + State: importLaceworkVulnerabilityException, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the vulnerability exception", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "The description of the vulnerability exception", + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "The state of the vulnerability exception", + }, + "reason": { + Type: schema.TypeString, + Optional: true, + Description: fmt.Sprintf("List of severities for the vulnerability exception. Valid severities are: %v", vulnerabilityExceptionReasonsSlice()), + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: func(val interface{}) string { + return strings.TrimSpace(strings.Title(strings.ToLower(val.(string)))) + }, + }, + ValidateFunc: validation.StringInSlice(vulnerabilityExceptionReasonsSlice(), true), + }, + "vulnerability_criteria": { + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Description: "The vulnerability criteria", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cves": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: func(val interface{}) string { + return strings.TrimSpace(val.(string)) + }, + }, + Description: "The list of cves", + }, + "package": { + Type: schema.TypeSet, + Optional: true, + Description: "The list of packages", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "The name of the package", + Required: true, + }, + + "version": { + Type: schema.TypeString, + Description: "The version of the package", + Required: true, + }, + }, + }, + }, + "severities": { + Type: schema.TypeList, + Optional: true, + Description: "List of severities for the vulnerability exception. Valid severities are:" + + " Critical, High, Medium, Low, Info", + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: func(val interface{}) string { + return strings.TrimSpace(strings.Title(strings.ToLower(val.(string)))) + }, + ValidateFunc: func(value interface{}, key string) ([]string, []error) { + switch strings.ToLower(value.(string)) { + case "critical", "high", "medium", "low", "info": + return nil, nil + default: + return nil, []error{ + fmt.Errorf( + "%s: can only be 'Critical', 'High', 'Medium', 'Low', 'Info'", key, + ), + } + } + }, + }, + }, + "fixable": { + Type: schema.TypeBool, + Optional: true, + Description: "The state of the vulnerability exception", + }, + }, + }, + }, + "resource_scope": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Description: "The resource scope", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hostnames": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: func(val interface{}) string { + return strings.TrimSpace(val.(string)) + }, + }, + Description: "The list of hostnames", + }, + "cluster_names": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: func(val interface{}) string { + return strings.TrimSpace(val.(string)) + }, + }, + Description: "The list of cluster names", + }, + "external_ips": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: func(val interface{}) string { + return strings.TrimSpace(val.(string)) + }, + }, + Description: "The list of external ips", + }, + "namespaces": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: func(val interface{}) string { + return strings.TrimSpace(val.(string)) + }, + }, + Description: "The list of namespaces", + }, + }, + }, + }, + "expiration_time": { + Type: schema.TypeString, + Optional: true, + Description: "The expiration date in RFC 3339 format", + ValidateFunc: func(value interface{}, key string) ([]string, []error) { + _, err := time.Parse(time.RFC3339, value.(string)) + if err != nil { + return nil, []error{ + fmt.Errorf("unable to parse expiry time '%s'. Time must be RFC3339 '2006-01-02T15:04:05Z07:00'", value), + } + } + return nil, nil + }, + }, + "guid": { + Type: schema.TypeString, + Computed: true, + }, + "created_time": { + Type: schema.TypeString, + Computed: true, + }, + "updated_time": { + Type: schema.TypeString, + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Computed: true, + }, + "updated_by": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceLaceworkVulnerabilityExceptionHostCreate(d *schema.ResourceData, meta interface{}) error { + var ( + lacework = meta.(*api.Client) + severities = castAttributeToStringSlice(d, "vulnerability_criteria.0.severities") + packages = castAttributeToArrayOfCustomKeyValueMap(d, "vulnerability_criteria.0.package", "name", "version") + ) + + // we validate the time format in the schema validate func, so we can ignore the error here + expiry, _ := time.Parse(time.RFC3339, d.Get("expiration_time").(string)) + + vulnException := api.NewVulnerabilityException(d.Get("name").(string), + api.VulnerabilityExceptionConfig{ + Description: d.Get("description").(string), + Type: api.VulnerabilityExceptionTypeHost, + ExceptionReason: api.NewVulnerabilityExceptionReason(d.Get("reason").(string)), + Severities: api.NewVulnerabilityExceptionSeverities(severities), + Cve: castAttributeToStringSlice(d, "vulnerability_criteria.0.cves"), + Package: api.NewVulnerabilityExceptionPackages(packages), + Fixable: d.Get("vulnerability_criteria.0.fixable").(bool), + ResourceScope: api.VulnerabilityExceptionHostResourceScope{ + Hostname: castAttributeToStringSlice(d, "resource_scope.0.hostnames"), + ExternalIP: castAttributeToStringSlice(d, "resource_scope.0.external_ips"), + ClusterName: castAttributeToStringSlice(d, "resource_scope.0.cluster_names"), + Namespace: castAttributeToStringSlice(d, "resource_scope.0.namespaces"), + }, + ExpiryTime: expiry.UTC(), + }, + ) + + if !d.Get("enabled").(bool) { + vulnException.Enabled = 0 + } + + log.Printf("[INFO] Creating vulnerability exception with data:\n%+v\n", vulnException) + response, err := lacework.V2.VulnerabilityExceptions.Create(vulnException) + if err != nil { + return err + } + + d.SetId(response.Data.Guid) + d.Set("name", response.Data.ExceptionName) + d.Set("guid", response.Data.Guid) + d.Set("enabled", response.Data.Enabled == 1) + d.Set("created_time", response.Data.CreatedTime) + d.Set("updated_time", response.Data.UpdatedTime) + d.Set("created_by", response.Data.Props.CreatedBy) + d.Set("updated_by", response.Data.Props.UpdatedBy) + d.Set("type", response.Data.ExceptionType) + + log.Printf("[INFO] Created vulnerability exception with guid %s\n", response.Data.Guid) + return nil +} + +func resourceLaceworkVulnerabilityExceptionHostRead(d *schema.ResourceData, meta interface{}) error { + var ( + lacework = meta.(*api.Client) + ) + + log.Printf("[INFO] Reading vulnerability exception with guid %s\n", d.Id()) + response, err := lacework.V2.VulnerabilityExceptions.GetVulnerabilityExceptionsHost(d.Id()) + if err != nil { + return err + } + + d.SetId(response.Data.Guid) + d.Set("name", response.Data.ExceptionName) + d.Set("guid", response.Data.Guid) + d.Set("description", response.Data.Props.Description) + d.Set("enabled", response.Data.Enabled == 1) + d.Set("created_time", response.Data.CreatedTime) + d.Set("updated_time", response.Data.UpdatedTime) + d.Set("created_by", response.Data.Props.CreatedBy) + d.Set("updated_by", response.Data.Props.UpdatedBy) + d.Set("type", response.Data.ExceptionType) + + resourceScope := make(map[string]interface{}) + resourceScope["namespaces"] = response.Data.ResourceScope.Namespace + resourceScope["hostnames"] = response.Data.ResourceScope.Hostname + resourceScope["external_ips"] = response.Data.ResourceScope.ExternalIP + resourceScope["cluster_names"] = response.Data.ResourceScope.ClusterName + d.Set("resource_scope", []map[string]interface{}{resourceScope}) + + vulnCriteria := make(map[string]interface{}) + vulnCriteria["severities"] = response.Data.VulnerabilityCriteria.Severity + vulnCriteria["cves"] = response.Data.VulnerabilityCriteria.Cve + vulnCriteria["package"] = vulnerabilityExceptionFlattenPackages(response.Data.VulnerabilityCriteria.Package) + vulnCriteria["fixable"] = vulnerabilityExceptionFixableEnabled(response.Data.VulnerabilityCriteria.Fixable) + d.Set("vulnerability_criteria", []map[string]interface{}{vulnCriteria}) + + d.Set("reason", response.Data.ExceptionReason) + d.Set("expiration_time", response.Data.ExpiryTime) + + log.Printf("[INFO] Read vulnerability exception with guid %s\n", response.Data.Guid) + return nil +} + +func resourceLaceworkVulnerabilityExceptionHostUpdate(d *schema.ResourceData, meta interface{}) error { + var ( + lacework = meta.(*api.Client) + severities = castAttributeToStringSlice(d, "vulnerability_criteria.0.severities") + packages = castAttributeToArrayOfCustomKeyValueMap(d, "vulnerability_criteria.0.package", "name", "version") + ) + + // we validate the time format in the schema validate func, so we can ignore the error here + expiry, _ := time.Parse(time.RFC3339, d.Get("expiration_time").(string)) + + vulnException := api.NewVulnerabilityException(d.Get("name").(string), + api.VulnerabilityExceptionConfig{ + Description: d.Get("description").(string), + Type: api.VulnerabilityExceptionTypeHost, + ExceptionReason: api.NewVulnerabilityExceptionReason(d.Get("reason").(string)), + Severities: api.NewVulnerabilityExceptionSeverities(severities), + Cve: castAttributeToStringSlice(d, "vulnerability_criteria.0.cves"), + Package: api.NewVulnerabilityExceptionPackages(packages), + Fixable: d.Get("vulnerability_criteria.0.fixable").(bool), + ResourceScope: api.VulnerabilityExceptionHostResourceScope{ + Hostname: castAttributeToStringSlice(d, "resource_scope.0.hostnames"), + ExternalIP: castAttributeToStringSlice(d, "resource_scope.0.external_ips"), + ClusterName: castAttributeToStringSlice(d, "resource_scope.0.cluster_names"), + Namespace: castAttributeToStringSlice(d, "resource_scope.0.namespaces"), + }, + ExpiryTime: expiry.UTC(), + }, + ) + + if !d.Get("enabled").(bool) { + vulnException.Enabled = 0 + } + + log.Printf("[INFO] Updating vulnerability exception with data:\n%+v\n", vulnException) + response, err := lacework.V2.VulnerabilityExceptions.UpdateVulnerabilityExceptionsHost(vulnException, d.Id()) + if err != nil { + return err + } + + d.SetId(response.Data.Guid) + d.Set("name", response.Data.ExceptionName) + d.Set("guid", response.Data.Guid) + d.Set("enabled", response.Data.Enabled == 1) + d.Set("created_time", response.Data.CreatedTime) + d.Set("updated_time", response.Data.UpdatedTime) + d.Set("created_by", response.Data.Props.CreatedBy) + d.Set("updated_by", response.Data.Props.UpdatedBy) + d.Set("type", response.Data.ExceptionType) + + log.Printf("[INFO] Updated vulnerability exception with guid %s\n", response.Data.Guid) + return nil +} + +func resourceLaceworkVulnerabilityExceptionHostDelete(d *schema.ResourceData, meta interface{}) error { + lacework := meta.(*api.Client) + + log.Printf("[INFO] Deleting vulnerability exception with guid %s\n", d.Id()) + err := lacework.V2.VulnerabilityExceptions.Delete(d.Id()) + if err != nil { + return err + } + + log.Printf("[INFO] Deleted vulnerability exception with guid %s\n", d.Id()) + return nil +} + +func vulnerabilityExceptionReasonsSlice() []string { + var reasons []string + for _, value := range api.VulnerabilityExceptionReasons { + reasons = append(reasons, value) + } + return reasons +} + +func vulnerabilityExceptionFixableEnabled(fixable []int) bool { + return fixable[0] == 1 +} + +func vulnerabilityExceptionFlattenPackages(vulnPackage []map[string][]string) []map[string]string { + var packages []map[string]string + for _, m := range vulnPackage { + for k, v := range m { + for _, version := range v { + packages = append(packages, map[string]string{"name": k, "version": version}) + } + } + } + return packages +} + +func importLaceworkVulnerabilityException(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + lacework := meta.(*api.Client) + + log.Printf("[INFO] Importing Lacework Vulnerability Exception with guid: %s\n", d.Id()) + + response, err := lacework.V2.VulnerabilityExceptions.GetVulnerabilityExceptionsHost(d.Id()) + + if err != nil { + return nil, fmt.Errorf( + "unable to import Lacework resource. Vulnerability Exception with guid '%s' was not found", + d.Id(), + ) + } + log.Printf("[INFO] Vulnerability Exception found with guid: %s\n", response.Data.Guid) + return []*schema.ResourceData{d}, nil +} diff --git a/vendor/github.com/hashicorp/hcl/v2/CHANGELOG.md b/vendor/github.com/hashicorp/hcl/v2/CHANGELOG.md index 816f2092..02b95669 100644 --- a/vendor/github.com/hashicorp/hcl/v2/CHANGELOG.md +++ b/vendor/github.com/hashicorp/hcl/v2/CHANGELOG.md @@ -1,5 +1,22 @@ # HCL Changelog +## v2.11.1 (Dec 1, 2021) + +### Bugs Fixed + +* hclsyntax: The type for an upgraded unknown value with a splat expression cannot be known ([#495](https://github.com/hashicorp/hcl/pull/495)) + + +## v2.11.0 (Dec 1, 2021) + +### Enhancements + +* hclsyntax: Various error messages related to unexpectedly reaching end of file while parsing a delimited subtree will now return specialized messages describing the opening tokens as "unclosed", instead of returning a generic diagnostic that just happens to refer to the empty source range at the end of the file. This gives better feedback when error messages are being presented alongside a source code snippet, as is common in HCL-based applications, because it shows which innermost container the parser was working on when it encountered the error. ([#492](https://github.com/hashicorp/hcl/pull/492)) + +### Bugs Fixed + +* hclsyntax: Upgrading an unknown single value to a list using a splat expression must return unknown ([#493](https://github.com/hashicorp/hcl/pull/493)) + ## v2.10.1 (July 21, 2021) * dynblock: Decode unknown dynamic blocks in order to obtain any diagnostics even though the decoded value is not used ([#476](https://github.com/hashicorp/hcl/pull/476)) diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go index df615281..07464a6b 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go @@ -1432,9 +1432,22 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { return cty.DynamicVal, diags } + upgradedUnknown := false if autoUpgrade { + // If we're upgrading an unknown value to a tuple/list, the result + // cannot be known. Otherwise a tuple containing an unknown value will + // upgrade to a different number of elements depending on whether + // sourceVal becomes null or not. + // We record this condition here so we can process any remaining + // expression after the * to verify the result of the traversal. For + // example, it is valid to use a splat on a single object to retrieve a + // list of a single attribute, but we still need to check if that + // attribute actually exists. + upgradedUnknown = !sourceVal.IsKnown() + sourceVal = cty.TupleVal([]cty.Value{sourceVal}) sourceTy = sourceVal.Type() + } // We'll compute our result type lazily if we need it. In the normal case @@ -1499,6 +1512,10 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { } e.Item.clearValue(ctx) // clean up our temporary value + if upgradedUnknown { + return cty.DynamicVal, diags + } + if !isKnown { // We'll ingore the resultTy diagnostics in this case since they // will just be the same errors we saw while iterating above. diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser.go index ff72713f..287e8293 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser.go @@ -76,14 +76,37 @@ Token: default: bad := p.Read() if !p.recovery { - if bad.Type == TokenOQuote { + switch bad.Type { + case TokenOQuote: diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid argument name", Detail: "Argument names must not be quoted.", Subject: &bad.Range, }) - } else { + case TokenEOF: + switch end { + case TokenCBrace: + // If we're looking for a closing brace then we're parsing a block + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unclosed configuration block", + Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: &startRange, + }) + default: + // The only other "end" should itself be TokenEOF (for + // the top-level body) and so we shouldn't get here, + // but we'll return a generic error message anyway to + // be resilient. + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unclosed configuration body", + Detail: "Found end of file before the end of this configuration body.", + Subject: &startRange, + }) + } + default: diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Argument or block definition required", @@ -388,12 +411,23 @@ Token: // user intent for this one, we'll skip it if we're already in // recovery mode. if !p.recovery { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid single-argument block definition", - Detail: "A single-line block definition must end with a closing brace immediately after its single argument definition.", - Subject: p.Peek().Range.Ptr(), - }) + switch p.Peek().Type { + case TokenEOF: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unclosed configuration block", + Detail: "There is no closing brace for this block before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: oBrace.Range.Ptr(), + Context: hcl.RangeBetween(ident.Range, oBrace.Range).Ptr(), + }) + default: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid single-argument block definition", + Detail: "A single-line block definition must end with a closing brace immediately after its single argument definition.", + Subject: p.Peek().Range.Ptr(), + }) + } } p.recover(TokenCBrace) } @@ -1059,12 +1093,22 @@ func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) { default: var diags hcl.Diagnostics if !p.recovery { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid expression", - Detail: "Expected the start of an expression, but found an invalid expression token.", - Subject: &start.Range, - }) + switch start.Type { + case TokenEOF: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Missing expression", + Detail: "Expected the start of an expression, but found the end of the file.", + Subject: &start.Range, + }) + default: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid expression", + Detail: "Expected the start of an expression, but found an invalid expression token.", + Subject: &start.Range, + }) + } } p.setRecovery() @@ -1163,13 +1207,23 @@ Token: } if sep.Type != TokenComma { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Missing argument separator", - Detail: "A comma is required to separate each function argument from the next.", - Subject: &sep.Range, - Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(), - }) + switch sep.Type { + case TokenEOF: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unterminated function call", + Detail: "There is no closing parenthesis for this function call before the end of the file. This may be caused by incorrect parethesis nesting elsewhere in this file.", + Subject: hcl.RangeBetween(name.Range, openTok.Range).Ptr(), + }) + default: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Missing argument separator", + Detail: "A comma is required to separate each function argument from the next.", + Subject: &sep.Range, + Context: hcl.RangeBetween(name.Range, sep.Range).Ptr(), + }) + } closeTok = p.recover(TokenCParen) break Token } @@ -1242,13 +1296,23 @@ func (p *parser) parseTupleCons() (Expression, hcl.Diagnostics) { if next.Type != TokenComma { if !p.recovery { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Missing item separator", - Detail: "Expected a comma to mark the beginning of the next item.", - Subject: &next.Range, - Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), - }) + switch next.Type { + case TokenEOF: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unterminated tuple constructor expression", + Detail: "There is no corresponding closing bracket before the end of the file. This may be caused by incorrect bracket nesting elsewhere in this file.", + Subject: open.Range.Ptr(), + }) + default: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Missing item separator", + Detail: "Expected a comma to mark the beginning of the next item.", + Subject: &next.Range, + Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), + }) + } } close = p.recover(TokenCBrack) break @@ -1359,6 +1423,13 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { Subject: &next.Range, Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), }) + case TokenEOF: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unterminated object constructor expression", + Detail: "There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: open.Range.Ptr(), + }) default: diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, @@ -1399,13 +1470,23 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { if next.Type != TokenComma && next.Type != TokenNewline { if !p.recovery { - diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Missing attribute separator", - Detail: "Expected a newline or comma to mark the beginning of the next attribute.", - Subject: &next.Range, - Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), - }) + switch next.Type { + case TokenEOF: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unterminated object constructor expression", + Detail: "There is no corresponding closing brace before the end of the file. This may be caused by incorrect brace nesting elsewhere in this file.", + Subject: open.Range.Ptr(), + }) + default: + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Missing attribute separator", + Detail: "Expected a newline or comma to mark the beginning of the next attribute.", + Subject: &next.Range, + Context: hcl.RangeBetween(open.Range, next.Range).Ptr(), + }) + } } close = p.recover(TokenCBrace) break diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser_template.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser_template.go index 02181fc7..ae880585 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser_template.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser_template.go @@ -414,22 +414,42 @@ Token: if close.Type != TokenTemplateSeqEnd { if !p.recovery { switch close.Type { - case TokenColon: + case TokenEOF: diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: "Extra characters after interpolation expression", - Detail: "Template interpolation doesn't expect a colon at this location. Did you intend this to be a literal sequence to be processed as part of another language? If so, you can escape it by starting with \"$${\" instead of just \"${\".", - Subject: &close.Range, - Context: hcl.RangeBetween(startRange, close.Range).Ptr(), + Summary: "Unclosed template interpolation sequence", + Detail: "There is no closing brace for this interpolation sequence before the end of the file. This might be caused by incorrect nesting inside the given expression.", + Subject: &startRange, }) - default: + case TokenColon: diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Extra characters after interpolation expression", - Detail: "Expected a closing brace to end the interpolation expression, but found extra characters.\n\nThis can happen when you include interpolation syntax for another language, such as shell scripting, but forget to escape the interpolation start token. If this is an embedded sequence for another language, escape it by starting with \"$${\" instead of just \"${\".", + Detail: "Template interpolation doesn't expect a colon at this location. Did you intend this to be a literal sequence to be processed as part of another language? If so, you can escape it by starting with \"$${\" instead of just \"${\".", Subject: &close.Range, Context: hcl.RangeBetween(startRange, close.Range).Ptr(), }) + default: + if (close.Type == TokenCQuote || close.Type == TokenOQuote) && end == TokenCQuote { + // We'll get here if we're processing a _quoted_ + // template and we find an errant quote inside an + // interpolation sequence, which suggests that + // the interpolation sequence is missing its terminator. + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unclosed template interpolation sequence", + Detail: "There is no closing brace for this interpolation sequence before the end of the quoted template. This might be caused by incorrect nesting inside the given expression.", + Subject: &startRange, + }) + } else { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Extra characters after interpolation expression", + Detail: "Expected a closing brace to end the interpolation expression, but found extra characters.\n\nThis can happen when you include interpolation syntax for another language, such as shell scripting, but forget to escape the interpolation start token. If this is an embedded sequence for another language, escape it by starting with \"$${\" instead of just \"${\".", + Subject: &close.Range, + Context: hcl.RangeBetween(startRange, close.Range).Ptr(), + }) + } } } p.recover(TokenTemplateSeqEnd) diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/spec.md b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/spec.md index 5543bde3..33c152da 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/spec.md +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/spec.md @@ -491,7 +491,7 @@ that were produced against each distinct key. - `[for v in ["a", "b"]: v]` returns `["a", "b"]`. - `[for i, v in ["a", "b"]: i]` returns `[0, 1]`. - `{for i, v in ["a", "b"]: v => i}` returns `{a = 0, b = 1}`. -- `{for i, v in ["a", "a", "b"]: k => v}` produces an error, because attribute +- `{for i, v in ["a", "a", "b"]: v => i}` produces an error, because attribute `a` is defined twice. - `{for i, v in ["a", "a", "b"]: v => i...}` returns `{a = [0, 1], b = [2]}`. @@ -890,7 +890,7 @@ as templates. - `hello ${true}` produces the string `"hello true"` - `${""}${true}` produces the string `"true"` because there are two interpolation sequences, even though one produces an empty result. -- `%{ for v in [true] }${v}%{ endif }` produces the string `true` because +- `%{ for v in [true] }${v}%{ endfor }` produces the string `true` because the presence of the `for` directive circumvents the unwrapping even though the final result is a single value. diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go index 2f7470c7..f42ae918 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go @@ -13,17 +13,12 @@ func (b *Block) AsHCLBlock() *hcl.Block { return nil } - lastHeaderRange := b.TypeRange - if len(b.LabelRanges) > 0 { - lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1] - } - return &hcl.Block{ Type: b.Type, Labels: b.Labels, Body: b.Body, - DefRange: hcl.RangeBetween(b.TypeRange, lastHeaderRange), + DefRange: b.DefRange(), TypeRange: b.TypeRange, LabelRanges: b.LabelRanges, } @@ -40,7 +35,7 @@ type Body struct { hiddenBlocks map[string]struct{} SrcRange hcl.Range - EndRange hcl.Range // Final token of the body, for reporting missing items + EndRange hcl.Range // Final token of the body (zero-length range) } // Assert that *Body implements hcl.Body @@ -390,5 +385,9 @@ func (b *Block) Range() hcl.Range { } func (b *Block) DefRange() hcl.Range { - return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange) + lastHeaderRange := b.TypeRange + if len(b.LabelRanges) > 0 { + lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1] + } + return hcl.RangeBetween(b.TypeRange, lastHeaderRange) } diff --git a/vendor/github.com/lacework/go-sdk/api/api.go b/vendor/github.com/lacework/go-sdk/api/api.go index f8e39a31..fa3bb9e8 100644 --- a/vendor/github.com/lacework/go-sdk/api/api.go +++ b/vendor/github.com/lacework/go-sdk/api/api.go @@ -116,6 +116,9 @@ const ( apiV2TeamMembers = "v2/TeamMembers" apiV2TeamMembersFromGUID = "v2/TeamMembers/%s" apiV2TeamMembersSearch = "v2/TeamMembers/search" + + apiV2VulnerabilityExceptions = "v2/VulnerabilityExceptions" + apiV2VulnerabilityExceptionFromGUID = "v2/VulnerabilityExceptions/%s" ) // WithApiV2 configures the client to use the API version 2 (/api/v2) diff --git a/vendor/github.com/lacework/go-sdk/api/schemas.go b/vendor/github.com/lacework/go-sdk/api/schemas.go index 53fe9abd..50f83dea 100644 --- a/vendor/github.com/lacework/go-sdk/api/schemas.go +++ b/vendor/github.com/lacework/go-sdk/api/schemas.go @@ -33,8 +33,9 @@ const ( ContainerRegistries CloudAccounts ResourceGroups - TeamMembers ReportRules + TeamMembers + VulnerabilityExceptions ) func (svc *SchemasService) GetService(schemaName integrationSchema) V2Service { diff --git a/vendor/github.com/lacework/go-sdk/api/v2.go b/vendor/github.com/lacework/go-sdk/api/v2.go index aede1975..b17d31da 100644 --- a/vendor/github.com/lacework/go-sdk/api/v2.go +++ b/vendor/github.com/lacework/go-sdk/api/v2.go @@ -24,19 +24,20 @@ type V2Endpoints struct { client *Client // Every schema must have its own service - UserProfile *UserProfileService - AlertChannels *AlertChannelsService - AlertRules *AlertRulesService - ReportRules *ReportRulesService - CloudAccounts *CloudAccountsService - ContainerRegistries *ContainerRegistriesService - ResourceGroups *ResourceGroupsService - AgentAccessTokens *AgentAccessTokensService - Query *QueryService - Policy *PolicyService - Schemas *SchemasService - Datasources *DatasourcesService - TeamMembers *TeamMembersService + UserProfile *UserProfileService + AlertChannels *AlertChannelsService + AlertRules *AlertRulesService + ReportRules *ReportRulesService + CloudAccounts *CloudAccountsService + ContainerRegistries *ContainerRegistriesService + ResourceGroups *ResourceGroupsService + AgentAccessTokens *AgentAccessTokensService + Query *QueryService + Policy *PolicyService + Schemas *SchemasService + Datasources *DatasourcesService + TeamMembers *TeamMembersService + VulnerabilityExceptions *VulnerabilityExceptionsService } func NewV2Endpoints(c *Client) *V2Endpoints { @@ -54,16 +55,18 @@ func NewV2Endpoints(c *Client) *V2Endpoints { &SchemasService{c, map[integrationSchema]V2Service{}}, &DatasourcesService{c}, &TeamMembersService{c}, + &VulnerabilityExceptionsService{c}, } v2.Schemas.Services = map[integrationSchema]V2Service{ - AlertChannels: &AlertChannelsService{c}, - AlertRules: &AlertRulesService{c}, - CloudAccounts: &CloudAccountsService{c}, - ContainerRegistries: &ContainerRegistriesService{c}, - ResourceGroups: &ResourceGroupsService{c}, - TeamMembers: &TeamMembersService{c}, - ReportRules: &ReportRulesService{c}, + AlertChannels: &AlertChannelsService{c}, + AlertRules: &AlertRulesService{c}, + CloudAccounts: &CloudAccountsService{c}, + ContainerRegistries: &ContainerRegistriesService{c}, + ResourceGroups: &ResourceGroupsService{c}, + TeamMembers: &TeamMembersService{c}, + ReportRules: &ReportRulesService{c}, + VulnerabilityExceptions: &VulnerabilityExceptionsService{c}, } return v2 } diff --git a/vendor/github.com/lacework/go-sdk/api/version.go b/vendor/github.com/lacework/go-sdk/api/version.go index 5b7a0c83..5e26efe8 100644 --- a/vendor/github.com/lacework/go-sdk/api/version.go +++ b/vendor/github.com/lacework/go-sdk/api/version.go @@ -1,5 +1,5 @@ // Code generated by: scripts/version_updater.sh -// File generated at: 20211124024508 +// File generated at: 20211210202831 // // <<< DO NOT EDIT >>> // @@ -7,4 +7,4 @@ package api // Version is the semver coming from the VERSION file -const Version = "0.22.1-dev" +const Version = "0.23.1-dev" diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go new file mode 100644 index 00000000..3a44e4f9 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions.go @@ -0,0 +1,450 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + "strings" + "time" + + "github.com/pkg/errors" + + "github.com/lacework/go-sdk/lwtime" +) + +// VulnerabilityExceptionsService is the service that interacts with +// the VulnerabilityExceptions schema from the Lacework APIv2 Server +type VulnerabilityExceptionsService struct { + client *Client +} + +// vulnerabilityExceptionResourceScope is an interface for the 2 types of vulnerability exceptions resource scopes: +// 'VulnerabilityExceptionContainerResourceScope' or 'VulnerabilityExceptionHostResourceScope' +type vulnerabilityExceptionResourceScope interface { + Type() vulnerabilityExceptionType + Scope() VulnerabilityExceptionResourceScope +} + +// vulnerabilityExceptionReason represents the types of vulnerability exceptions reasons: +//'False Positive', 'Accepted Risk', 'Compensating Controls', 'Fix Pending' or 'Other' +type vulnerabilityExceptionReason int + +const ( + VulnerabilityExceptionReasonAcceptedRisk vulnerabilityExceptionReason = iota + VulnerabilityExceptionReasonAcceptedFalsePositive + VulnerabilityExceptionReasonCompensatingControls + VulnerabilityExceptionReasonFixPending + VulnerabilityExceptionReasonOther + VulnerabilityExceptionReasonUnknown +) + +var VulnerabilityExceptionReasons = map[vulnerabilityExceptionReason]string{ + VulnerabilityExceptionReasonAcceptedRisk: "Accepted Risk", + VulnerabilityExceptionReasonAcceptedFalsePositive: "False Positive", + VulnerabilityExceptionReasonCompensatingControls: "Compensating Controls", + VulnerabilityExceptionReasonFixPending: "Fix Pending", + VulnerabilityExceptionReasonOther: "Other", + VulnerabilityExceptionReasonUnknown: "Unknown", +} + +func (i vulnerabilityExceptionReason) String() string { + return VulnerabilityExceptionReasons[i] +} + +func NewVulnerabilityExceptionReason(reason string) vulnerabilityExceptionReason { + switch reason { + case "Accepted Risk": + return VulnerabilityExceptionReasonAcceptedRisk + case "False Positive": + return VulnerabilityExceptionReasonAcceptedFalsePositive + case "Compensating Controls": + return VulnerabilityExceptionReasonCompensatingControls + case "Fix Pending": + return VulnerabilityExceptionReasonFixPending + case "Other": + return VulnerabilityExceptionReasonOther + default: + return VulnerabilityExceptionReasonUnknown + } +} + +// vulnerabilityExceptionType represents the types of vulnerability exceptions 'Host' or 'Container' +type vulnerabilityExceptionType int + +const ( + VulnerabilityExceptionTypeHost vulnerabilityExceptionType = iota + VulnerabilityExceptionTypeContainer +) + +var VulnerabilityExceptionTypes = map[vulnerabilityExceptionType]string{ + VulnerabilityExceptionTypeHost: "Host", + VulnerabilityExceptionTypeContainer: "Container", +} + +func (i vulnerabilityExceptionType) String() string { + return VulnerabilityExceptionTypes[i] +} + +// vulnerabilityExceptionSeverity represents the types of vulnerability severities: +// 'Critical', 'High', 'Medium', 'Low' or 'Info' +type vulnerabilityExceptionSeverity string + +type VulnerabilityExceptionSeverities []vulnerabilityExceptionSeverity + +func (sevs VulnerabilityExceptionSeverities) ToStringSlice() []string { + var res []string + for _, i := range sevs { + switch i { + case VulnerabilityExceptionSeverityCritical: + res = append(res, "Critical") + case VulnerabilityExceptionSeverityHigh: + res = append(res, "High") + case VulnerabilityExceptionSeverityMedium: + res = append(res, "Medium") + case VulnerabilityExceptionSeverityLow: + res = append(res, "Low") + case VulnerabilityExceptionSeverityInfo: + res = append(res, "Info") + default: + continue + } + } + return res +} + +func NewVulnerabilityExceptionSeverities(sevSlice []string) VulnerabilityExceptionSeverities { + var res VulnerabilityExceptionSeverities + for _, i := range sevSlice { + sev := convertVulnerabilityExceptionSeverity(i) + if sev != VulnerabilityExceptionSeverityUnknown { + res = append(res, sev) + } + } + return res +} + +func convertVulnerabilityExceptionSeverity(sev string) vulnerabilityExceptionSeverity { + switch strings.ToLower(sev) { + case "critical": + return VulnerabilityExceptionSeverityCritical + case "high": + return VulnerabilityExceptionSeverityHigh + case "medium": + return VulnerabilityExceptionSeverityMedium + case "low": + return VulnerabilityExceptionSeverityLow + case "info": + return VulnerabilityExceptionSeverityInfo + default: + return VulnerabilityExceptionSeverityUnknown + } +} + +const ( + VulnerabilityExceptionSeverityCritical vulnerabilityExceptionSeverity = "Critical" + VulnerabilityExceptionSeverityHigh vulnerabilityExceptionSeverity = "High" + VulnerabilityExceptionSeverityMedium vulnerabilityExceptionSeverity = "Medium" + VulnerabilityExceptionSeverityLow vulnerabilityExceptionSeverity = "Low" + VulnerabilityExceptionSeverityInfo vulnerabilityExceptionSeverity = "Info" + VulnerabilityExceptionSeverityUnknown vulnerabilityExceptionSeverity = "Unknown" +) + +// NewVulnerabilityException returns an instance of the VulnerabilityException struct +// +// Basic usage: Initialize a new VulnerabilityException struct, then +// use the new instance to do CRUD operations +// +// client, err := api.NewClient("account") +// if err != nil { +// return err +// } +// +// exception := api.VulnerabilityExceptionConfig{ +// Type: api.VulnerabilityExceptionTypeHost, +// Description: "This is a vuln exception", +// ExceptionReason: api.VulnerabilityExceptionReasonCompensatingControls, +// Severities: api.VulnerabilityExceptionSeverities{api.VulnerabilityExceptionSeverityCritical}, +// Fixable: true, +// ResourceScope: api.VulnerabilityExceptionContainerResourceScope{ +// ImageID: []string{""}, +// ImageTag: []string{""}, +// Registry: []string{""}, +// Repository: []string{""}, +// Namespace: []string{""}, +// }, +// ExpiryTime: time.Now().AddDate(0, 1, 0), +// } +// +// vulnerabilityException := api.NewVulnerabilityException("vulnerabilityException", exception) +// +// client.V2.VulnerabilityExceptions.Create(vulnerabilityException) +// +func NewVulnerabilityException(name string, exception VulnerabilityExceptionConfig) VulnerabilityException { + packages := aggregatePackages(exception.Package) + vulnException := VulnerabilityException{ + Enabled: 1, + ExceptionName: name, + ExceptionReason: exception.ExceptionReason.String(), + Props: VulnerabilityExceptionProps{Description: exception.Description}, + VulnerabilityCriteria: VulnerabilityExceptionCriteria{ + Severity: exception.Severities.ToStringSlice(), + Package: packages, + Cve: exception.Cve, + Fixable: exception.FixableEnabled(), + }, + ExpiryTime: exception.ExpiryTime.UTC().Format(lwtime.RFC3339Milli), + } + vulnException.setResourceScope(exception.ResourceScope) + return vulnException +} + +func aggregatePackages(packages []VulnerabilityExceptionPackage) []map[string][]string { + var packs []map[string][]string + for _, pck := range packages { + var packagesMap = make(map[string][]string) + //aggregate packages with same name + if len(packs) > 0 { + if _, ok := packs[0][pck.Name]; ok { + packs[0][pck.Name] = append(packs[0][pck.Name], pck.Version) + continue + } + } + packagesMap[pck.Name] = []string{pck.Version} + packs = append(packs, packagesMap) + } + return packs +} + +func (exception *VulnerabilityException) setResourceScope(scope vulnerabilityExceptionResourceScope) { + switch scope.Type() { + case VulnerabilityExceptionTypeContainer: + ctr := scope.Scope() + exception.ExceptionType = VulnerabilityExceptionTypeContainer.String() + exception.ResourceScope = VulnerabilityExceptionResourceScope{ + ImageID: ctr.ImageID, + ImageTag: ctr.ImageTag, + Registry: ctr.Registry, + Repository: ctr.Repository, + Namespace: ctr.Namespace, + } + case VulnerabilityExceptionTypeHost: + host := scope.Scope() + exception.ExceptionType = VulnerabilityExceptionTypeHost.String() + exception.ResourceScope = VulnerabilityExceptionResourceScope{ + Hostname: host.Hostname, + ClusterName: host.ClusterName, + ExternalIP: host.ExternalIP, + Namespace: host.Namespace, + } + default: + exception.ResourceScope = VulnerabilityExceptionResourceScope{} + } +} + +func (exception VulnerabilityException) Status() string { + if exception.Enabled == 1 { + return "Enabled" + } + return "Disabled" +} + +func (cfg VulnerabilityExceptionConfig) FixableEnabled() []int { + if cfg.Fixable { + return []int{1} + } + return []int{0} +} + +// List returns a list of Vulnerability Exceptions +func (svc *VulnerabilityExceptionsService) List() (response VulnerabilityExceptionsResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) + return +} + +// Create creates a single Vulnerability Exception +func (svc *VulnerabilityExceptionsService) Create(vuln VulnerabilityException) ( + response VulnerabilityExceptionResponse, + err error, +) { + err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) + return +} + +// Delete deletes a Vulnerability Exception that matches the provided guid +func (svc *VulnerabilityExceptionsService) Delete(guid string) error { + if guid == "" { + return errors.New("specify an intgGuid") + } + + return svc.client.RequestDecoder( + "DELETE", + fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid), + nil, + nil, + ) +} + +// Update updates a single Vulnerability Exception. +func (svc *VulnerabilityExceptionsService) Update(data VulnerabilityException) ( + response VulnerabilityExceptionResponse, + err error, +) { + if data.Guid == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, data.Guid) + // Request is invalid if it contains the ID field. We set the id field to empty + data.Guid = "" + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) + return +} + +// Get returns a raw response of the Vulnerability Exception with the matching guid. +func (svc *VulnerabilityExceptionsService) Get(guid string, response interface{}) error { + if guid == "" { + return errors.New("specify a Guid") + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) + return svc.client.RequestDecoder("GET", apiPath, nil, &response) +} + +type VulnerabilityExceptionConfig struct { + Description string + Type vulnerabilityExceptionType + ExceptionReason vulnerabilityExceptionReason + Severities VulnerabilityExceptionSeverities + Cve []string + Package []VulnerabilityExceptionPackage + Fixable bool + ResourceScope vulnerabilityExceptionResourceScope + ExpiryTime time.Time +} + +type VulnerabilityExceptionContainerResourceScope struct { + ImageID []string `json:"imageId,omitempty"` + ImageTag []string `json:"imageTag,omitempty"` + Registry []string `json:"registry,omitempty"` + Repository []string `json:"repository,omitempty"` + Namespace []string `json:"namespace,omitempty"` +} + +func (ctr VulnerabilityExceptionContainerResourceScope) Type() vulnerabilityExceptionType { + return VulnerabilityExceptionTypeContainer +} + +func (ctr VulnerabilityExceptionContainerResourceScope) Scope() VulnerabilityExceptionResourceScope { + return VulnerabilityExceptionResourceScope{ + ImageID: ctr.ImageID, + ImageTag: ctr.ImageTag, + Registry: ctr.Registry, + Repository: ctr.Repository, + Namespace: ctr.Namespace, + } +} + +func (host VulnerabilityExceptionHostResourceScope) Scope() VulnerabilityExceptionResourceScope { + return VulnerabilityExceptionResourceScope{ + Hostname: host.Hostname, + ExternalIP: host.ExternalIP, + ClusterName: host.ClusterName, + Namespace: host.Namespace, + } +} + +type VulnerabilityExceptionHostResourceScope struct { + Hostname []string `json:"hostname,omitempty"` + ExternalIP []string `json:"externalIp,omitempty"` + ClusterName []string `json:"clusterName,omitempty"` + Namespace []string `json:"namespace,omitempty"` +} + +func (host VulnerabilityExceptionHostResourceScope) Type() vulnerabilityExceptionType { + return VulnerabilityExceptionTypeHost +} + +type VulnerabilityException struct { + Guid string `json:"exceptionGuid,omitempty"` + Enabled int `json:"state"` + ExceptionName string `json:"exceptionName"` + ExceptionType string `json:"exceptionType"` + ExceptionReason string `json:"exceptionReason"` + Props VulnerabilityExceptionProps `json:"props"` + VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` + ResourceScope VulnerabilityExceptionResourceScope `json:"resourceScope,omitempty"` + CreatedTime string `json:"createdTime,omitempty"` + UpdatedTime string `json:"updatedTime,omitempty"` + ExpiryTime string `json:"expiryTime,omitempty"` +} + +type VulnerabilityExceptionProps struct { + Description string `json:"description,omitempty"` + CreatedBy string `json:"createdBy,omitempty"` + UpdatedBy string `json:"updatedBy,omitempty"` +} + +type VulnerabilityExceptionResourceScope struct { + // Container properties + ImageID []string `json:"imageId,omitempty"` + ImageTag []string `json:"imageTag,omitempty"` + Registry []string `json:"registry,omitempty"` + Repository []string `json:"repository,omitempty"` + + // Host properties + Hostname []string `json:"hostname,omitempty"` + ExternalIP []string `json:"externalIp,omitempty"` + ClusterName []string `json:"clusterName,omitempty"` + + // Shared properties + Namespace []string `json:"namespace,omitempty"` +} + +type VulnerabilityExceptionCriteria struct { + Cve []string `json:"cve,omitempty"` + Package []map[string][]string `json:"package,omitempty"` + Severity []string `json:"severity,omitempty"` + Fixable []int `json:"fixable,omitempty"` +} + +type VulnerabilityExceptionResponse struct { + Data VulnerabilityException `json:"data"` +} + +type VulnerabilityExceptionsResponse struct { + Data []VulnerabilityException `json:"data"` +} + +type VulnerabilityExceptionPackage struct { + Name string + Version string +} + +func NewVulnerabilityExceptionPackages(packageMap []map[string]string) []VulnerabilityExceptionPackage { + var packages []VulnerabilityExceptionPackage + for _, m := range packageMap { + for k, v := range m { + pck := VulnerabilityExceptionPackage{ + Name: k, + Version: v, + } + packages = append(packages, pck) + } + } + return packages +} diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go new file mode 100644 index 00000000..eb04ef84 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_container.go @@ -0,0 +1,85 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +func (svc *VulnerabilityExceptionsService) CreateVulnerabilityExceptionsContainer(vuln VulnerabilityException) ( + response VulnerabilityExceptionContainerResponse, err error) { + err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) + return +} + +func (svc *VulnerabilityExceptionsService) GetVulnerabilityExceptionsContainer(guid string) (response VulnerabilityExceptionContainerResponse, err error) { + if guid == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +func (svc *VulnerabilityExceptionsService) ListVulnerabilityExceptionsContainers() (response VulnerabilityExceptionContainerResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) + return +} + +func (svc *VulnerabilityExceptionsService) UpdateVulnerabilityExceptionsContainer(data VulnerabilityException, id string) ( + response VulnerabilityExceptionContainerResponse, + err error, +) { + if id == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, id) + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) + return +} + +type VulnerabilityExceptionContainerResponse struct { + Data VulnerabilityExceptionContainer `json:"data"` +} + +type VulnerabilityExceptionContainer struct { + Guid string `json:"exceptionGuid,omitempty"` + Enabled int `json:"state"` + ExceptionName string `json:"exceptionName"` + ExceptionType string `json:"exceptionType"` + ExceptionReason string `json:"exceptionReason"` + Props VulnerabilityExceptionProps `json:"props"` + VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` + ResourceScope VulnerabilityExceptionResourceScopeContainer `json:"resourceScope,omitempty"` + CreatedTime string `json:"createdTime,omitempty"` + UpdatedTime string `json:"updatedTime,omitempty"` + ExpiryTime string `json:"expiryTime,omitempty"` +} + +type VulnerabilityExceptionResourceScopeContainer struct { + ImageID []string `json:"imageId,omitempty"` + ImageTag []string `json:"imageTag,omitempty"` + Registry []string `json:"registry,omitempty"` + Repository []string `json:"repository,omitempty"` + Namespace []string `json:"namespace,omitempty"` +} diff --git a/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go new file mode 100644 index 00000000..c090d3b6 --- /dev/null +++ b/vendor/github.com/lacework/go-sdk/api/vulnerability_exceptions_host.go @@ -0,0 +1,84 @@ +// +// Author:: Darren Murray () +// Copyright:: Copyright 2021, Lacework Inc. +// License:: Apache License, Version 2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package api + +import ( + "fmt" + + "github.com/pkg/errors" +) + +func (svc *VulnerabilityExceptionsService) CreateVulnerabilityExceptionsHost(vuln VulnerabilityException) ( + response VulnerabilityExceptionHostResponse, err error) { + err = svc.client.RequestEncoderDecoder("POST", apiV2VulnerabilityExceptions, vuln, &response) + return +} + +func (svc *VulnerabilityExceptionsService) GetVulnerabilityExceptionsHost(guid string) (response VulnerabilityExceptionHostResponse, err error) { + if guid == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, guid) + err = svc.client.RequestDecoder("GET", apiPath, nil, &response) + return +} + +func (svc *VulnerabilityExceptionsService) ListVulnerabilityExceptionsHosts() (response VulnerabilityExceptionHostResponse, err error) { + err = svc.client.RequestDecoder("GET", apiV2VulnerabilityExceptions, nil, &response) + return +} + +func (svc *VulnerabilityExceptionsService) UpdateVulnerabilityExceptionsHost(data VulnerabilityException, id string) ( + response VulnerabilityExceptionHostResponse, + err error, +) { + if id == "" { + err = errors.New("specify a Guid") + return + } + apiPath := fmt.Sprintf(apiV2VulnerabilityExceptionFromGUID, id) + err = svc.client.RequestEncoderDecoder("PATCH", apiPath, data, &response) + return +} + +type VulnerabilityExceptionHostResponse struct { + Data VulnerabilityExceptionHost `json:"data"` +} + +type VulnerabilityExceptionHost struct { + Guid string `json:"exceptionGuid,omitempty"` + Enabled int `json:"state"` + ExceptionName string `json:"exceptionName"` + ExceptionType string `json:"exceptionType"` + ExceptionReason string `json:"exceptionReason"` + Props VulnerabilityExceptionProps `json:"props"` + VulnerabilityCriteria VulnerabilityExceptionCriteria `json:"vulnerabilityCriteria"` + ResourceScope VulnerabilityExceptionResourceScopeHost `json:"resourceScope,omitempty"` + CreatedTime string `json:"createdTime,omitempty"` + UpdatedTime string `json:"updatedTime,omitempty"` + ExpiryTime string `json:"expiryTime,omitempty"` +} + +type VulnerabilityExceptionResourceScopeHost struct { + Hostname []string `json:"hostname,omitempty"` + ExternalIP []string `json:"externalIp,omitempty"` + ClusterName []string `json:"clusterName,omitempty"` + Namespace []string `json:"namespace,omitempty"` +} diff --git a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md index 9fe803a5..38a09916 100644 --- a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md +++ b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.4.3 + +* Fix cases where `json.Number` didn't decode properly [GH-261] + ## 1.4.2 * Custom name matchers to support any sort of casing, formatting, etc. for diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go index dcee0f2d..6b81b006 100644 --- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go +++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go @@ -684,16 +684,12 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e } case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": jn := data.(json.Number) - i, err := jn.Int64() + i, err := strconv.ParseUint(string(jn), 0, 64) if err != nil { return fmt.Errorf( "error decoding json.Number into %s: %s", name, err) } - if i < 0 && !d.config.WeaklyTypedInput { - return fmt.Errorf("cannot parse '%s', %d overflows uint", - name, i) - } - val.SetUint(uint64(i)) + val.SetUint(i) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s', value: '%v'", diff --git a/website/docs/r/vulnerability_exception_host.html.markdown b/website/docs/r/vulnerability_exception_host.html.markdown new file mode 100644 index 00000000..5e42fe0f --- /dev/null +++ b/website/docs/r/vulnerability_exception_host.html.markdown @@ -0,0 +1,81 @@ +--- +subcategory: "Vulnerability Exceptions" +layout: "lacework" +page_title: "Lacework: lacework_vulnerability_exception_host" +description: |- Create and manage Lacework vulnerability exceptions for hosts +--- + +# lacework\_vulnerability\_exception\_host + +Use this resource to create a Lacework Host Vulnerability Exception to control and customize your alert profile for hosts + +## Example Usage + +```hcl +resource "lacework_vulnerability_exception_host" "example" { + name = "My Vuln Exception" + description = "This is a vulnerability host exception" + enabled = true + reason = "Accepted Risk" + vulnerability_criteria { + severities = ["critical"] + cves = ["cve-2021-11111", "cve-2021-22222"] + fixable = true + package { + name = "myPackage" + version = "1.0.0" + } + package { + name = "myOtherPackage" + version = "1.0.0" + } + } + resource_scope { + hostnames = ["host1", "host2"] + cluster_names = ["cluster1", "cluster2"] + namespaces = ["namespace1", "namespace2"] + } + expiration_time = "2022-01-19T23:26:10.229Z" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The vulnerability exception name. +* `vulnerability_criteria` - (Required) The vulnerability criteria. + 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`. +* `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. +* `expiration_time` - (Optional) The expiration of the vulnerability exception. + +### Vulnerability Criteria + +`vulnerability criteria` supports the following arguments: + +* `severities` - (Optional) The list of the severities. Valid severities include: + `Critical`, `High`, `Medium`, `Low` and `Info`. +* `cves` - (Optional) The list of the cves. +* `package` - (Optional) The list of the cves. See [Resource Scope](#resource-scope) below for details. +* `fixable` - (Optional) Whether to filter on fixable. Defaults to `false`. + +### Resource Scope + +`resource_scope` supports the following arguments: + +* `hostnames` - (Optional) The list of hostnames. +* `external_ips` - (Optional) The list of external ips. +* `cluster_names` - (Optional) The list of cluster names. +* `namespaces` - (Optional) The list of namespaces. + +## Import + +A Lacework vulnerability exception for hosts can be imported using a `GUID`, e.g. + +``` +$ terraform import lacework_vulnerability_exception_host.example EXAMPLE_1234BAE1E42182964D23973F44CFEA3C4AB63B99E9A1EC5 +``` diff --git a/website/lacework.erb b/website/lacework.erb index 58623d4a..af56b424 100644 --- a/website/lacework.erb +++ b/website/lacework.erb @@ -236,6 +236,20 @@ +
  • + Vulnerability Exceptions + +
  • +
  • Other Resources