From 5ce1be674fa403e0b991b7a5ffdafd8b78bba268 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Thu, 23 Jan 2025 11:59:32 +0100 Subject: [PATCH 1/2] feat(specs): Initial panos_application codegen spec --- specs/objects/application.yaml | 822 +++++++++++++++++++++++++++++++++ 1 file changed, 822 insertions(+) create mode 100644 specs/objects/application.yaml diff --git a/specs/objects/application.yaml b/specs/objects/application.yaml new file mode 100644 index 00000000..5c0fbacf --- /dev/null +++ b/specs/objects/application.yaml @@ -0,0 +1,822 @@ +name: application +terraform_provider_config: + description: Application Object + skip_resource: false + skip_datasource: false + resource_type: entry + resource_variants: [] + suffix: application + plural_suffix: '' + plural_name: '' + plural_description: '' +go_sdk_config: + skip: false + package: + - objects + - application +xpath_suffix: +- application +locations: +- name: shared + xpath: + path: + - config + - shared + vars: [] + description: Location in Shared Panorama + devices: + - panorama + - ngfw + validators: [] + required: false + read_only: false +- name: vsys + xpath: + path: + - config + - devices + - $ngfw_device + - vsys + - $vsys + vars: + - name: ngfw_device + description: The NGFW device name + required: false + default: localhost.localdomain + validators: [] + type: entry + - name: vsys + description: The Virtual System name + required: false + default: vsys1 + validators: + - type: not-values + spec: + values: + - value: shared + error: The vsys name cannot be "shared". Use the "shared" location instead + type: entry + description: Located in a specific Virtual System + devices: + - ngfw + - panorama + validators: [] + required: false + read_only: false +- name: device-group + xpath: + path: + - config + - devices + - $panorama_device + - device-group + - $device_group + vars: + - name: panorama_device + description: Panorama device name + required: false + default: localhost.localdomain + validators: [] + type: entry + - name: device_group + description: Device Group name + required: true + validators: + - type: not-values + spec: + values: + - value: shared + error: The device group name cannot be "shared". Use the "shared" location + instead + type: entry + description: Located in a specific Device Group + devices: + - panorama + validators: [] + required: false + read_only: false +entries: +- name: name + description: '' + validators: [] +imports: [] +spec: + params: + - name: able-to-transfer-file + type: bool + profiles: + - xpath: + - able-to-transfer-file + validators: [] + spec: {} + description: '' + required: false + - name: alg-disable-capability + type: string + profiles: + - xpath: + - alg-disable-capability + validators: + - type: length + spec: + max: 127 + spec: {} + description: '' + required: false + - name: category + type: string + profiles: + - xpath: + - category + validators: [] + spec: {} + description: '' + required: false + - name: consume-big-bandwidth + type: bool + profiles: + - xpath: + - consume-big-bandwidth + validators: [] + spec: {} + description: '' + required: false + - name: data-ident + type: bool + profiles: + - xpath: + - data-ident + validators: [] + spec: {} + description: '' + required: false + - name: default + type: object + profiles: + - xpath: + - default + validators: [] + spec: + params: [] + variants: + - name: ident-by-icmp-type + type: object + profiles: + - xpath: + - ident-by-icmp-type + validators: [] + spec: + params: + - name: code + type: string + profiles: + - xpath: + - code + validators: [] + spec: {} + description: '' + required: false + - name: type + type: string + profiles: + - xpath: + - type + validators: [] + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: ident-by-icmp6-type + type: object + profiles: + - xpath: + - ident-by-icmp6-type + validators: [] + spec: + params: + - name: code + type: string + profiles: + - xpath: + - code + validators: [] + spec: {} + description: '' + required: false + - name: type + type: string + profiles: + - xpath: + - type + validators: [] + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + - name: ident-by-ip-protocol + type: string + profiles: + - xpath: + - ident-by-ip-protocol + validators: [] + spec: {} + description: '' + required: false + - name: port + type: list + profiles: + - xpath: + - port + type: member + validators: [] + spec: + type: string + items: + type: string + description: '' + required: false + description: '' + required: false + - name: description + type: string + profiles: + - xpath: + - description + validators: + - type: length + spec: + min: 0 + max: 1023 + spec: {} + description: '' + required: false + - name: disable-override + type: enum + profiles: + - xpath: + - disable-override + validators: + - type: values + spec: + values: + - 'yes' + - 'no' + spec: + default: 'no' + values: + - value: 'yes' + - value: 'no' + description: disable object override in child device groups + required: false + - name: evasive-behavior + type: bool + profiles: + - xpath: + - evasive-behavior + validators: [] + spec: {} + description: '' + required: false + - name: file-type-ident + type: bool + profiles: + - xpath: + - file-type-ident + validators: [] + spec: {} + description: '' + required: false + - name: has-known-vulnerability + type: bool + profiles: + - xpath: + - has-known-vulnerability + validators: [] + spec: {} + description: '' + required: false + - name: no-appid-caching + type: bool + profiles: + - xpath: + - no-appid-caching + validators: [] + spec: {} + description: '' + required: false + - name: parent-app + type: string + profiles: + - xpath: + - parent-app + validators: + - type: length + spec: + max: 127 + spec: {} + description: '' + required: false + - name: pervasive-use + type: bool + profiles: + - xpath: + - pervasive-use + validators: [] + spec: {} + description: '' + required: false + - name: prone-to-misuse + type: bool + profiles: + - xpath: + - prone-to-misuse + validators: [] + spec: {} + description: '' + required: false + - name: risk + type: int64 + profiles: + - xpath: + - risk + validators: + - type: length + spec: + min: 1 + max: 5 + spec: {} + description: '' + required: false + - name: signature + type: list + profiles: + - xpath: + - signature + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: comment + type: string + profiles: + - xpath: + - comment + validators: + - type: length + spec: + min: 0 + max: 256 + spec: {} + description: '' + required: false + - name: scope + type: enum + profiles: + - xpath: + - scope + validators: + - type: values + spec: + values: + - protocol-data-unit + - session + spec: + default: protocol-data-unit + values: + - value: protocol-data-unit + - value: session + description: '' + required: false + - name: order-free + type: bool + profiles: + - xpath: + - order-free + validators: [] + spec: {} + description: '' + required: false + - name: and-condition + type: list + profiles: + - xpath: + - and-condition + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: or-condition + type: list + profiles: + - xpath: + - or-condition + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: operator + type: object + profiles: + - xpath: + - operator + validators: [] + spec: + params: [] + variants: + - name: pattern-match + type: object + profiles: + - xpath: + - pattern-match + validators: [] + spec: + params: + - name: context + type: string + profiles: + - xpath: + - context + validators: + - type: length + spec: + max: 127 + spec: {} + description: '' + required: false + - name: pattern + type: string + profiles: + - xpath: + - pattern + validators: + - type: length + spec: + max: 127 + spec: {} + description: '' + required: false + - name: qualifier + type: list + profiles: + - xpath: + - qualifier + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: value + type: string + profiles: + - xpath: + - value + validators: [] + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + variants: [] + description: '' + required: false + - name: greater-than + type: object + profiles: + - xpath: + - greater-than + validators: [] + spec: + params: + - name: context + type: string + profiles: + - xpath: + - context + validators: + - type: length + spec: + max: 127 + spec: {} + description: '' + required: false + - name: value + type: int64 + profiles: + - xpath: + - value + validators: + - type: length + spec: + min: 0 + max: 4294967295 + spec: {} + description: '' + required: false + - name: qualifier + type: list + profiles: + - xpath: + - qualifier + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: value + type: string + profiles: + - xpath: + - value + validators: [] + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + variants: [] + description: '' + required: false + - name: less-than + type: object + profiles: + - xpath: + - less-than + validators: [] + spec: + params: + - name: context + type: string + profiles: + - xpath: + - context + validators: + - type: length + spec: + max: 127 + spec: {} + description: '' + required: false + - name: value + type: int64 + profiles: + - xpath: + - value + validators: + - type: length + spec: + min: 0 + max: 4294967295 + spec: {} + description: '' + required: false + - name: qualifier + type: list + profiles: + - xpath: + - qualifier + - entry + type: entry + validators: [] + spec: + type: object + items: + type: object + spec: + params: + - name: value + type: string + profiles: + - xpath: + - value + validators: [] + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + variants: [] + description: '' + required: false + - name: equal-to + type: object + profiles: + - xpath: + - equal-to + validators: [] + spec: + params: + - name: context + type: string + profiles: + - xpath: + - context + validators: [] + spec: {} + description: '' + required: false + - name: position + type: string + profiles: + - xpath: + - position + validators: + - type: length + spec: + max: 127 + spec: {} + description: '' + required: false + - name: mask + type: string + profiles: + - xpath: + - mask + validators: + - type: length + spec: + max: 10 + spec: {} + description: 4-byte hex value + required: false + - name: value + type: string + profiles: + - xpath: + - value + validators: + - type: length + spec: + max: 10 + spec: {} + description: '' + required: false + variants: [] + description: '' + required: false + description: '' + required: false + variants: [] + description: '' + required: false + variants: [] + description: '' + required: false + variants: [] + description: '' + required: false + - name: subcategory + type: string + profiles: + - xpath: + - subcategory + validators: + - type: length + spec: + max: 63 + spec: {} + description: '' + required: false + - name: tcp-half-closed-timeout + type: int64 + profiles: + - xpath: + - tcp-half-closed-timeout + validators: + - type: length + spec: + min: 1 + max: 604800 + spec: {} + description: timeout for half-close session in seconds + required: false + - name: tcp-time-wait-timeout + type: int64 + profiles: + - xpath: + - tcp-time-wait-timeout + validators: + - type: length + spec: + min: 1 + max: 600 + spec: {} + description: timeout for session in time_wait state in seconds + required: false + - name: tcp-timeout + type: int64 + profiles: + - xpath: + - tcp-timeout + validators: + - type: length + spec: + min: 0 + max: 604800 + spec: {} + description: timeout in seconds + required: false + - name: technology + type: string + profiles: + - xpath: + - technology + validators: + - type: length + spec: + max: 63 + spec: {} + description: '' + required: false + - name: timeout + type: int64 + profiles: + - xpath: + - timeout + validators: + - type: length + spec: + min: 0 + max: 604800 + spec: {} + description: timeout in seconds + required: false + - name: tunnel-applications + type: bool + profiles: + - xpath: + - tunnel-applications + validators: [] + spec: {} + description: '' + required: false + - name: tunnel-other-application + type: bool + profiles: + - xpath: + - tunnel-other-application + validators: [] + spec: {} + description: '' + required: false + - name: udp-timeout + type: int64 + profiles: + - xpath: + - udp-timeout + validators: + - type: length + spec: + min: 0 + max: 604800 + spec: {} + description: timeout in seconds + required: false + - name: used-by-malware + type: bool + profiles: + - xpath: + - used-by-malware + validators: [] + spec: {} + description: '' + required: false + - name: virus-ident + type: bool + profiles: + - xpath: + - virus-ident + validators: [] + spec: {} + description: '' + required: false + variants: [] From 3ccc0ef3f73a1f6d1eeeccdfc5ee517a119e26a2 Mon Sep 17 00:00:00 2001 From: Krzysztof Klimonda Date: Thu, 23 Jan 2025 12:53:26 +0100 Subject: [PATCH 2/2] feat(tests): Add panos_application acceptance tests --- .../test/resource_application_test.go | 349 ++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 assets/terraform/test/resource_application_test.go diff --git a/assets/terraform/test/resource_application_test.go b/assets/terraform/test/resource_application_test.go new file mode 100644 index 00000000..22bff368 --- /dev/null +++ b/assets/terraform/test/resource_application_test.go @@ -0,0 +1,349 @@ +package provider_test + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + sdkErrors "github.com/PaloAltoNetworks/pango/errors" + "github.com/PaloAltoNetworks/pango/objects/application" + + "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 TestAccApplication(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: testAccApplicationDestroy(prefix), + Steps: []resource.TestStep{ + { + Config: testApplicationTmpl, + ConfigVariables: map[string]config.Variable{ + "prefix": config.StringVariable(prefix), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("able_to_transfer_file"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("category"), + knownvalue.StringExact("general-internet"), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("consume_big_bandwidth"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("data_ident"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("description"), + knownvalue.StringExact("description"), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("disable_override"), + knownvalue.StringExact("no"), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("evasive_behavior"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("file_type_ident"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("has_known_vulnerability"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("no_appid_caching"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("parent_app"), + knownvalue.StringExact("8x8"), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("pervasive_use"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("prone_to_misuse"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("risk"), + knownvalue.Int64Exact(1), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("subcategory"), + knownvalue.StringExact("internet-utility"), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("tcp_half_closed_timeout"), + knownvalue.Int64Exact(60), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("tcp_time_wait_timeout"), + knownvalue.Int64Exact(120), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("tcp_timeout"), + knownvalue.Int64Exact(180), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("technology"), + knownvalue.StringExact("browser-based"), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("timeout"), + knownvalue.Int64Exact(240), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("tunnel_applications"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("tunnel_other_application"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("udp_timeout"), + knownvalue.Int64Exact(120), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("used_by_malware"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("virus_ident"), + knownvalue.Bool(true), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("default"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "ident_by_icmp_type": knownvalue.ObjectExact(map[string]knownvalue.Check{ + "code": knownvalue.StringExact("1"), + "type": knownvalue.StringExact("1"), + }), + "ident_by_icmp6_type": knownvalue.Null(), + "ident_by_ip_protocol": knownvalue.Null(), + "port": knownvalue.Null(), + }), + ), + statecheck.ExpectKnownValue( + "panos_application.app1", + tfjsonpath.New("signature"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "name": knownvalue.StringExact("signature"), + "comment": knownvalue.StringExact("comment"), + "scope": knownvalue.StringExact("protocol-data-unit"), + "order_free": knownvalue.Bool(true), + "and_condition": knownvalue.Null(), + }), + }), + ), + statecheck.ExpectKnownValue( + "panos_application.app2", + tfjsonpath.New("default"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "ident_by_icmp6_type": knownvalue.ObjectExact(map[string]knownvalue.Check{ + "code": knownvalue.StringExact("1"), + "type": knownvalue.StringExact("1"), + }), + "ident_by_icmp_type": knownvalue.Null(), + "ident_by_ip_protocol": knownvalue.Null(), + "port": knownvalue.Null(), + }), + ), + statecheck.ExpectKnownValue( + "panos_application.app3", + tfjsonpath.New("default"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "ident_by_icmp_type": knownvalue.Null(), + "ident_by_icmp6_type": knownvalue.Null(), + "ident_by_ip_protocol": knownvalue.StringExact("1"), + "port": knownvalue.Null(), + }), + ), + statecheck.ExpectKnownValue( + "panos_application.app4", + tfjsonpath.New("default"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "ident_by_icmp_type": knownvalue.Null(), + "ident_by_icmp6_type": knownvalue.Null(), + "ident_by_ip_protocol": knownvalue.Null(), + "port": knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("tcp/80"), + }), + }), + ), + }, + }, + }, + }) +} + +const testApplicationTmpl = ` +variable prefix { type = string } + +resource "panos_device_group" "dg" { + location = { panorama = {} } + + name = format("%s-dg", var.prefix) +} + +resource "panos_application" "app1" { + location = { device_group = { name = panos_device_group.dg.name } } + + name = format("%s-app1", var.prefix) + + able_to_transfer_file = true + #alg_disable_capability = "yes" + category = "general-internet" + consume_big_bandwidth = true + data_ident = true + description = "description" + disable_override = "no" + evasive_behavior = true + file_type_ident = true + has_known_vulnerability = true + no_appid_caching = true + parent_app = "8x8" + pervasive_use = true + prone_to_misuse = true + risk = 1 + subcategory = "internet-utility" + tcp_half_closed_timeout = 60 + tcp_time_wait_timeout = 120 + tcp_timeout = 180 + technology = "browser-based" + timeout = 240 + tunnel_applications = true + tunnel_other_application = true + udp_timeout = 120 + used_by_malware = true + virus_ident = true + default = { + ident_by_icmp_type = { + code = "1" + type = "1" + } + } + signature = [{ + name = "signature" + comment = "comment" + scope = "protocol-data-unit" + order_free = true + }] +} + +resource "panos_application" "app2" { + location = { device_group = { name = panos_device_group.dg.name } } + + name = format("%s-app2", var.prefix) + + default = { + ident_by_icmp6_type = { + code = "1" + type = "1" + } + } +} + +resource "panos_application" "app3" { + location = { device_group = { name = panos_device_group.dg.name } } + + name = format("%s-app3", var.prefix) + + default = { + ident_by_ip_protocol = "1" + } +} + +resource "panos_application" "app4" { + location = { device_group = { name = panos_device_group.dg.name } } + + name = format("%s-app4", var.prefix) + + default = { + port = ["tcp/80"] + } +} + +` + +func testAccApplicationDestroy(prefix string) func(s *terraform.State) error { + return func(s *terraform.State) error { + api := application.NewService(sdkClient) + ctx := context.TODO() + + location := application.NewSharedLocation() + + entries, err := api.List(ctx, *location, "get", "", "") + if err != nil && !sdkErrors.IsObjectNotFound(err) { + return fmt.Errorf("listing interface management entries via sdk: %v", 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 := api.Delete(ctx, *location, leftEntries...) + if delErr != nil { + return errors.Join(err, delErr) + } + } + + return nil + } +}