diff --git a/assets/terraform/test/resource_vulnerability_security_profile_test.go b/assets/terraform/test/resource_vulnerability_security_profile_test.go new file mode 100644 index 00000000..b3adfca8 --- /dev/null +++ b/assets/terraform/test/resource_vulnerability_security_profile_test.go @@ -0,0 +1,303 @@ +// In TestAccVulnerabilitySecurityProfile function implement all checks indicated by "MISSING" comment + +package provider_test + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + sdkerrors "github.com/PaloAltoNetworks/pango/errors" + "github.com/PaloAltoNetworks/pango/objects/profiles/vulnerability" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestAccVulnerabilitySecurityProfile(t *testing.T) { + t.Parallel() + + nameSuffix := acctest.RandStringFromCharSet(6, acctest.CharSetAlphaNum) + prefix := fmt.Sprintf("test-acc-%s", nameSuffix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders, + CheckDestroy: testAccVulnerabilitySecurityProfileDestroy(prefix), + Steps: []resource.TestStep{ + { + Config: vulnerabilitySecurityProfileTmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_vulnerability_security_profile.profile", + tfjsonpath.New("name"), + knownvalue.StringExact(fmt.Sprintf("%s-profile", prefix)), + ), + statecheck.ExpectKnownValue( + "panos_vulnerability_security_profile.profile", + tfjsonpath.New("disable_override"), + knownvalue.StringExact("yes"), + ), + statecheck.ExpectKnownValue( + "panos_vulnerability_security_profile.profile", + tfjsonpath.New("rules"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "name": knownvalue.StringExact("rule1"), + "category": knownvalue.StringExact("any"), + "cve": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("cve1"), + knownvalue.StringExact("cve2"), + knownvalue.StringExact("cve3"), + }), + "host": knownvalue.StringExact("server"), + "packet_capture": knownvalue.StringExact("single-packet"), + "severity": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("medium"), + }), + "threat_name": knownvalue.StringExact("threat"), + "vendor_id": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("any"), + }), + "action": knownvalue.ObjectExact(map[string]knownvalue.Check{ + "alert": knownvalue.Null(), + "allow": knownvalue.Null(), + "block_ip": knownvalue.ObjectExact(map[string]knownvalue.Check{ + "duration": knownvalue.Float64Exact(10), + "track_by": knownvalue.StringExact("source-and-destination"), + }), + "default": knownvalue.Null(), + "drop": knownvalue.Null(), + "reset_both": knownvalue.Null(), + "reset_client": knownvalue.Null(), + "reset_server": knownvalue.Null(), + }), + }), + }), + ), + statecheck.ExpectKnownValue( + "panos_vulnerability_security_profile.profile", + tfjsonpath.New("description"), + knownvalue.StringExact("description"), + ), + statecheck.ExpectKnownValue( + "panos_vulnerability_security_profile.profile", + tfjsonpath.New("cloud_inline_analysis"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_vulnerability_security_profile.profile", + tfjsonpath.New("inline_exception_edl_url"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact(fmt.Sprintf("%s-category", prefix)), + }), + ), + statecheck.ExpectKnownValue( + "panos_vulnerability_security_profile.profile", + tfjsonpath.New("mica_engine_vulnerability_enabled"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "name": knownvalue.StringExact("SQL Injection"), + "inline_policy_action": knownvalue.StringExact("alert"), + }), + }), + ), + statecheck.ExpectKnownValue( + "panos_vulnerability_security_profile.profile", + tfjsonpath.New("threat_exception"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "name": knownvalue.StringExact("547000"), + "packet_capture": knownvalue.StringExact("extended-capture"), + "action": knownvalue.ObjectExact(map[string]knownvalue.Check{ + "alert": knownvalue.Null(), + "allow": knownvalue.Null(), + "block_ip": knownvalue.ObjectExact(map[string]knownvalue.Check{ + "duration": knownvalue.Float64Exact(10), + "track_by": knownvalue.StringExact("source"), + }), + "default": knownvalue.Null(), + "drop": knownvalue.Null(), + "reset_both": knownvalue.Null(), + "reset_client": knownvalue.Null(), + "reset_server": knownvalue.Null(), + }), + "exempt_ip": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "name": knownvalue.StringExact("1.1.1.1"), + }), + }), + "time_attribute": knownvalue.ObjectExact(map[string]knownvalue.Check{ + "interval": knownvalue.Float64Exact(100), + "threshold": knownvalue.Float64Exact(200), + "track_by": knownvalue.StringExact("source"), + }), + }), + }), + ), + }, + }, + { + Config: vulnerabilitySecurityProfileCleanupTmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + }, + ConfigStateChecks: []statecheck.StateCheck{ + VulnerabilitySecurityProfileExpectNoEntriesInLocation(prefix), + }, + }, + }, + }) +} + +const vulnerabilitySecurityProfileTmpl = ` +variable prefix { type = string } + +resource "panos_device_group" "dg" { + location = { panorama = {} } + + name = format("%s-dg1", var.prefix) +} + +resource "panos_custom_url_category" "category" { + location = { device_group = { name = panos_device_group.dg.name } } + + name = format("%s-category", var.prefix) +} + +#resource "panos_addresses" "exceptions" { +# location = { device_group = { name = panos_device_group.dg.name } } +# +# addresses = { +# format("%s-addr", var.prefix): { ip_netmask = "10.0.0.1/32" } +# } +#} + +resource "panos_vulnerability_security_profile" "profile" { + location = { device_group = { name = panos_device_group.dg.name } } + + name = format("%s-profile", var.prefix) + description = "description" + disable_override = "yes" + + cloud_inline_analysis = true + inline_exception_edl_url = [panos_custom_url_category.category.name] + #inline_exception_ip_address = [ for name, obj in panos_addresses.exceptions.addresses : name ] + mica_engine_vulnerability_enabled = [{ + name = "SQL Injection" + inline_policy_action = "alert" + }] + rules = [{ + name = "rule1" + category = "any" + cve = ["cve1", "cve2", "cve3"] + host = "server" + packet_capture = "single-packet" + severity = ["medium"] + threat_name = "threat" + vendor_id = ["any"] + action = { + block_ip = { duration = 10, track_by = "source-and-destination" } + } + }] + threat_exception = [{ + name = "547000" + packet_capture = "extended-capture" + action = { + block_ip = { duration = 10, track_by = "source" } + } + exempt_ip = [{ + name = "1.1.1.1" + }] + time_attribute = { + interval = 100 + threshold = 200 + track_by = "source" + } + }] +} +` + +const vulnerabilitySecurityProfileCleanupTmpl = ` +variable prefix { type = string } + +resource "panos_device_group" "dg" { + location = { panorama = {} } + + name = format("%s-dg1", var.prefix) +} +` + +type vulnerabilitySecurityProfileExpectNoEntriesInLocation struct { + prefix string +} + +func VulnerabilitySecurityProfileExpectNoEntriesInLocation(prefix string) *vulnerabilitySecurityProfileExpectNoEntriesInLocation { + return &vulnerabilitySecurityProfileExpectNoEntriesInLocation{ + prefix: prefix, + } +} + +func (o *vulnerabilitySecurityProfileExpectNoEntriesInLocation) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) { + service := vulnerability.NewService(sdkClient) + location := vulnerability.NewDeviceGroupLocation() + location.DeviceGroup.DeviceGroup = fmt.Sprintf("%s-dg1", o.prefix) + objects, err := service.List(ctx, *location, "get", "", "") + if err != nil && !sdkerrors.IsObjectNotFound(err) { + resp.Error = fmt.Errorf("failed to query server for entries: %w", err) + return + } + + var dangling []string + for _, elt := range objects { + if strings.HasPrefix(elt.Name, o.prefix) { + dangling = append(dangling, elt.Name) + } + } + + if len(dangling) > 0 { + resp.Error = fmt.Errorf("delete of the resource didn't remove it from the server") + } +} + +func testAccVulnerabilitySecurityProfileDestroy(prefix string) func(s *terraform.State) error { + return func(s *terraform.State) error { + service := vulnerability.NewService(sdkClient) + + location := vulnerability.NewDeviceGroupLocation() + location.DeviceGroup.DeviceGroup = fmt.Sprintf("%s-dg1", prefix) + + ctx := context.TODO() + entries, err := service.List(ctx, *location, "get", "", "") + if err != nil && !sdkerrors.IsObjectNotFound(err) { + return fmt.Errorf("failed to list existing entries via sdk: %w", err) + } + + var leftEntries []string + for _, elt := range entries { + if strings.HasPrefix(elt.Name, prefix) { + leftEntries = append(leftEntries, elt.Name) + } + } + + if len(leftEntries) > 0 { + err := fmt.Errorf("terraform failed to remove entries from the server") + delErr := service.Delete(ctx, *location, leftEntries...) + if delErr != nil { + return errors.Join(err, delErr) + } + } + + return nil + } +}