From e4cd5e25149203250545511e277b464a3cc0dd17 Mon Sep 17 00:00:00 2001 From: graysonwu Date: Wed, 6 Dec 2023 09:33:44 -0800 Subject: [PATCH 1/2] Separate security policy and rule Signed-off-by: graysonwu --- api/api_list.yaml | 21 ++ api/infra/domains/security_policies/rule.go | 192 ++++++++++++ nsxt/policy_common.go | 54 +++- nsxt/policy_utils.go | 19 ++ nsxt/provider.go | 2 + ...ce_nsxt_policy_intrusion_service_policy.go | 2 +- ...urce_nsxt_policy_parent_security_policy.go | 105 +++++++ ...nsxt_policy_parent_security_policy_test.go | 164 ++++++++++ nsxt/resource_nsxt_policy_security_policy.go | 143 ++++----- ...source_nsxt_policy_security_policy_rule.go | 252 ++++++++++++++++ ...e_nsxt_policy_security_policy_rule_test.go | 285 ++++++++++++++++++ nsxt/utils.go | 21 ++ website/docs/d/compute_manager.html.markdown | 2 +- ...olicy_parent_security_policy.html.markdown | 160 ++++++++++ .../policy_security_policy_rule.html.markdown | 93 ++++++ 15 files changed, 1407 insertions(+), 108 deletions(-) create mode 100644 api/infra/domains/security_policies/rule.go create mode 100644 nsxt/resource_nsxt_policy_parent_security_policy.go create mode 100644 nsxt/resource_nsxt_policy_parent_security_policy_test.go create mode 100644 nsxt/resource_nsxt_policy_security_policy_rule.go create mode 100644 nsxt/resource_nsxt_policy_security_policy_rule_test.go create mode 100644 website/docs/r/policy_parent_security_policy.html.markdown create mode 100644 website/docs/r/policy_security_policy_rule.html.markdown diff --git a/api/api_list.yaml b/api/api_list.yaml index ddfb2404c..5d6c6cdea 100644 --- a/api/api_list.yaml +++ b/api/api_list.yaml @@ -877,3 +877,24 @@ supported_method: - New - List +- api_packages: + - client: github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra/domains/security_policies + model: github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model + type: Local + - client: github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm/global_infra/domains/security_policies + model: github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm/model + type: Global + - client: github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/infra/domains/security_policies + model: github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model + type: Multitenancy + model_name: Rule + obj_name: Rule + client_name: RulesClient + list_result_name: RuleListResult + supported_method: + - New + - Get + - Delete + - Patch + - Update + - List \ No newline at end of file diff --git a/api/infra/domains/security_policies/rule.go b/api/infra/domains/security_policies/rule.go new file mode 100644 index 000000000..214820ab1 --- /dev/null +++ b/api/infra/domains/security_policies/rule.go @@ -0,0 +1,192 @@ +//nolint:revive +package securitypolicies + +// The following file has been autogenerated. Please avoid any changes! +import ( + "errors" + + vapiProtocolClient_ "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + client1 "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm/global_infra/domains/security_policies" + model1 "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm/model" + client0 "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra/domains/security_policies" + model0 "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + client2 "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/infra/domains/security_policies" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" +) + +type RuleClientContext utl.ClientContext + +func NewRulesClient(sessionContext utl.SessionContext, connector vapiProtocolClient_.Connector) *RuleClientContext { + var client interface{} + + switch sessionContext.ClientType { + + case utl.Local: + client = client0.NewRulesClient(connector) + + case utl.Global: + client = client1.NewRulesClient(connector) + + case utl.Multitenancy: + client = client2.NewRulesClient(connector) + + default: + return nil + } + return &RuleClientContext{Client: client, ClientType: sessionContext.ClientType, ProjectID: sessionContext.ProjectID} +} + +func (c RuleClientContext) Get(domainIdParam string, securityPolicyIdParam string, ruleIdParam string) (model0.Rule, error) { + var obj model0.Rule + var err error + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.RulesClient) + obj, err = client.Get(domainIdParam, securityPolicyIdParam, ruleIdParam) + if err != nil { + return obj, err + } + + case utl.Global: + client := c.Client.(client1.RulesClient) + gmObj, err1 := client.Get(domainIdParam, securityPolicyIdParam, ruleIdParam) + if err1 != nil { + return obj, err1 + } + var rawObj interface{} + rawObj, err = utl.ConvertModelBindingType(gmObj, model1.RuleBindingType(), model0.RuleBindingType()) + obj = rawObj.(model0.Rule) + + case utl.Multitenancy: + client := c.Client.(client2.RulesClient) + obj, err = client.Get(utl.DefaultOrgID, c.ProjectID, domainIdParam, securityPolicyIdParam, ruleIdParam) + if err != nil { + return obj, err + } + + default: + return obj, errors.New("invalid infrastructure for model") + } + return obj, err +} + +func (c RuleClientContext) Delete(domainIdParam string, securityPolicyIdParam string, ruleIdParam string) error { + var err error + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.RulesClient) + err = client.Delete(domainIdParam, securityPolicyIdParam, ruleIdParam) + + case utl.Global: + client := c.Client.(client1.RulesClient) + err = client.Delete(domainIdParam, securityPolicyIdParam, ruleIdParam) + + case utl.Multitenancy: + client := c.Client.(client2.RulesClient) + err = client.Delete(utl.DefaultOrgID, c.ProjectID, domainIdParam, securityPolicyIdParam, ruleIdParam) + + default: + err = errors.New("invalid infrastructure for model") + } + return err +} + +func (c RuleClientContext) Patch(domainIdParam string, securityPolicyIdParam string, ruleIdParam string, ruleParam model0.Rule) error { + var err error + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.RulesClient) + err = client.Patch(domainIdParam, securityPolicyIdParam, ruleIdParam, ruleParam) + + case utl.Global: + client := c.Client.(client1.RulesClient) + gmObj, err1 := utl.ConvertModelBindingType(ruleParam, model0.RuleBindingType(), model1.RuleBindingType()) + if err1 != nil { + return err1 + } + err = client.Patch(domainIdParam, securityPolicyIdParam, ruleIdParam, gmObj.(model1.Rule)) + + case utl.Multitenancy: + client := c.Client.(client2.RulesClient) + err = client.Patch(utl.DefaultOrgID, c.ProjectID, domainIdParam, securityPolicyIdParam, ruleIdParam, ruleParam) + + default: + err = errors.New("invalid infrastructure for model") + } + return err +} + +func (c RuleClientContext) Update(domainIdParam string, securityPolicyIdParam string, ruleIdParam string, ruleParam model0.Rule) (model0.Rule, error) { + var err error + var obj model0.Rule + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.RulesClient) + obj, err = client.Update(domainIdParam, securityPolicyIdParam, ruleIdParam, ruleParam) + + case utl.Global: + client := c.Client.(client1.RulesClient) + gmObj, err := utl.ConvertModelBindingType(ruleParam, model0.RuleBindingType(), model1.RuleBindingType()) + if err != nil { + return obj, err + } + gmObj, err = client.Update(domainIdParam, securityPolicyIdParam, ruleIdParam, gmObj.(model1.Rule)) + if err != nil { + return obj, err + } + obj1, err1 := utl.ConvertModelBindingType(gmObj, model1.RuleBindingType(), model0.RuleBindingType()) + if err1 != nil { + return obj, err1 + } + obj = obj1.(model0.Rule) + + case utl.Multitenancy: + client := c.Client.(client2.RulesClient) + obj, err = client.Update(utl.DefaultOrgID, c.ProjectID, domainIdParam, securityPolicyIdParam, ruleIdParam, ruleParam) + + default: + err = errors.New("invalid infrastructure for model") + } + return obj, err +} + +func (c RuleClientContext) List(domainIdParam string, securityPolicyIdParam string, cursorParam *string, includeMarkForDeleteObjectsParam *bool, includedFieldsParam *string, pageSizeParam *int64, sortAscendingParam *bool, sortByParam *string) (model0.RuleListResult, error) { + var err error + var obj model0.RuleListResult + + switch c.ClientType { + + case utl.Local: + client := c.Client.(client0.RulesClient) + obj, err = client.List(domainIdParam, securityPolicyIdParam, cursorParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam) + + case utl.Global: + client := c.Client.(client1.RulesClient) + gmObj, err := client.List(domainIdParam, securityPolicyIdParam, cursorParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam) + if err != nil { + return obj, err + } + obj1, err1 := utl.ConvertModelBindingType(gmObj, model1.RuleListResultBindingType(), model0.RuleListResultBindingType()) + if err1 != nil { + return obj, err1 + } + obj = obj1.(model0.RuleListResult) + + case utl.Multitenancy: + client := c.Client.(client2.RulesClient) + obj, err = client.List(utl.DefaultOrgID, c.ProjectID, domainIdParam, securityPolicyIdParam, cursorParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam) + + default: + err = errors.New("invalid infrastructure for model") + } + return obj, err +} diff --git a/nsxt/policy_common.go b/nsxt/policy_common.go index 3da84f227..961c802bb 100644 --- a/nsxt/policy_common.go +++ b/nsxt/policy_common.go @@ -175,17 +175,24 @@ func getPolicyRuleActionSchema(isIds bool) *schema.Schema { } func getSecurityPolicyAndGatewayRulesSchema(scopeRequired bool, isIds bool, nsxIDReadOnly bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Description: "List of rules in the section", + Optional: true, + MaxItems: 1000, + Elem: &schema.Resource{ + Schema: getSecurityPolicyAndGatewayRuleSchema(scopeRequired, isIds, nsxIDReadOnly, false), + }, + } +} + +func getSecurityPolicyAndGatewayRuleSchema(scopeRequired bool, isIds bool, nsxIDReadOnly bool, separated bool) map[string]*schema.Schema { ruleSchema := map[string]*schema.Schema{ "nsx_id": getFlexNsxIDSchema(nsxIDReadOnly), "display_name": getDisplayNameSchema(), "description": getDescriptionSchema(), + "path": getPathSchema(), "revision": getRevisionSchema(), - "sequence_number": { - Type: schema.TypeInt, - Description: "Sequence number of the this rule", - Optional: true, - Computed: true, - }, "destination_groups": { Type: schema.TypeSet, Description: "List of destination groups", @@ -291,19 +298,29 @@ func getSecurityPolicyAndGatewayRulesSchema(scopeRequired bool, isIds bool, nsxI if isIds { ruleSchema["ids_profiles"] = getIdsProfilesSchema() } - return &schema.Schema{ - Type: schema.TypeList, - Description: "List of rules in the section", - Optional: true, - MaxItems: 1000, - Elem: &schema.Resource{ - Schema: ruleSchema, - }, + if separated { + ruleSchema["policy_path"] = getPolicyPathSchema(true, true, "Security Policy path") + ruleSchema["sequence_number"] = &schema.Schema{ + Type: schema.TypeInt, + Description: "Sequence number of the this rule", + Required: true, + } + // Using computed context here, because context is required for consistency and + // if it's not provided it can be derived from policy_path. + ruleSchema["context"] = getComputedContextSchema() + } else { + ruleSchema["sequence_number"] = &schema.Schema{ + Type: schema.TypeInt, + Description: "Sequence number of the this rule", + Optional: true, + Computed: true, + } } + return ruleSchema } func getPolicyGatewayPolicySchema() map[string]*schema.Schema { - secPolicy := getPolicySecurityPolicySchema(false, true) + secPolicy := getPolicySecurityPolicySchema(false, true, true) // GW Policies don't support scope delete(secPolicy, "scope") secPolicy["category"].ValidateFunc = validation.StringInSlice(gatewayPolicyCategoryWritableValues, false) @@ -312,7 +329,7 @@ func getPolicyGatewayPolicySchema() map[string]*schema.Schema { return secPolicy } -func getPolicySecurityPolicySchema(isIds bool, withContext bool) map[string]*schema.Schema { +func getPolicySecurityPolicySchema(isIds, withContext, withRule bool) map[string]*schema.Schema { result := map[string]*schema.Schema{ "nsx_id": getNsxIDSchema(), "path": getPathSchema(), @@ -380,6 +397,10 @@ func getPolicySecurityPolicySchema(isIds bool, withContext bool) map[string]*sch if !withContext { delete(result, "context") } + + if !withRule { + delete(result, "rule") + } return result } @@ -389,6 +410,7 @@ func setPolicyRulesInSchema(d *schema.ResourceData, rules []model.Rule) error { elem := make(map[string]interface{}) elem["display_name"] = rule.DisplayName elem["description"] = rule.Description + elem["path"] = rule.Path elem["notes"] = rule.Notes elem["logged"] = rule.Logged elem["log_label"] = rule.Tag diff --git a/nsxt/policy_utils.go b/nsxt/policy_utils.go index 59561cd95..d5db3d923 100644 --- a/nsxt/policy_utils.go +++ b/nsxt/policy_utils.go @@ -155,10 +155,29 @@ func setPathListInMap(data map[string]interface{}, attrName string, pathList []s } } +func getPathListFromSchema(d *schema.ResourceData, schemaAttrName string) []string { + pathList := interface2StringList(d.Get(schemaAttrName).(*schema.Set).List()) + if len(pathList) == 0 { + // Convert empty value to "ANY" + pathList = append(pathList, "ANY") + } + return pathList +} + +func setPathListInSchema(d *schema.ResourceData, attrName string, pathList []string) { + if !(len(pathList) == 1 && pathList[0] == "ANY") { + d.Set(attrName, pathList) + } +} + func getDomainFromResourcePath(rPath string) string { return getResourceIDFromResourcePath(rPath, "domains") } +func getProjectIDFromResourcePath(rPath string) string { + return getResourceIDFromResourcePath(rPath, "projects") +} + func getResourceIDFromResourcePath(rPath string, rType string) string { segments := strings.Split(rPath, "/") for i, seg := range segments { diff --git a/nsxt/provider.go b/nsxt/provider.go index a9e32d188..55e1ba98d 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -439,6 +439,8 @@ func Provider() *schema.Provider { "nsxt_policy_host_transport_node_collection": resourceNsxtPolicyHostTransportNodeCollection(), "nsxt_policy_lb_client_ssl_profile": resourceNsxtPolicyLBClientSslProfile(), "nsxt_policy_lb_http_application_profile": resourceNsxtPolicyLBHttpApplicationProfile(), + "nsxt_policy_security_policy_rule": resourceNsxtPolicySecurityPolicyRule(), + "nsxt_policy_parent_security_policy": resourceNsxtPolicyParentSecurityPolicy(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_policy_intrusion_service_policy.go b/nsxt/resource_nsxt_policy_intrusion_service_policy.go index e37593620..e3b7ff895 100644 --- a/nsxt/resource_nsxt_policy_intrusion_service_policy.go +++ b/nsxt/resource_nsxt_policy_intrusion_service_policy.go @@ -29,7 +29,7 @@ func resourceNsxtPolicyIntrusionServicePolicy() *schema.Resource { Importer: &schema.ResourceImporter{ State: nsxtDomainResourceImporter, }, - Schema: getPolicySecurityPolicySchema(true, false), + Schema: getPolicySecurityPolicySchema(true, false, true), } } diff --git a/nsxt/resource_nsxt_policy_parent_security_policy.go b/nsxt/resource_nsxt_policy_parent_security_policy.go new file mode 100644 index 000000000..d68358822 --- /dev/null +++ b/nsxt/resource_nsxt_policy_parent_security_policy.go @@ -0,0 +1,105 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware/terraform-provider-nsxt/api/infra/domains" +) + +func resourceNsxtPolicyParentSecurityPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyParentSecurityPolicyCreate, + Read: resourceNsxtPolicyParentSecurityPolicyRead, + Update: resourceNsxtPolicyParentSecurityPolicyUpdate, + Delete: resourceNsxtPolicyParentSecurityPolicyDelete, + Importer: &schema.ResourceImporter{ + State: nsxtDomainResourceImporter, + }, + Schema: getPolicySecurityPolicySchema(false, true, false), + } +} + +func parentSecurityPolicySchemaToModel(d *schema.ResourceData, id string) model.SecurityPolicy { + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + category := d.Get("category").(string) + comments := d.Get("comments").(string) + locked := d.Get("locked").(bool) + + scope := getStringListFromSchemaSet(d, "scope") + sequenceNumber := int64(d.Get("sequence_number").(int)) + stateful := d.Get("stateful").(bool) + tcpStrict := d.Get("tcp_strict").(bool) + objType := "SecurityPolicy" + + return model.SecurityPolicy{ + Id: &id, + DisplayName: &displayName, + Description: &description, + Tags: tags, + Category: &category, + Comments: &comments, + Locked: &locked, + Scope: scope, + SequenceNumber: &sequenceNumber, + Stateful: &stateful, + TcpStrict: &tcpStrict, + ResourceType: &objType, + } +} + +func parentSecurityPolicyModelToSchema(d *schema.ResourceData, m interface{}) (*model.SecurityPolicy, error) { + connector := getPolicyConnector(m) + id := d.Id() + domainName := d.Get("domain").(string) + if id == "" { + return nil, fmt.Errorf("Error obtaining Security Policy id") + } + client := domains.NewSecurityPoliciesClient(getSessionContext(d, m), connector) + obj, err := client.Get(domainName, id) + if err != nil { + return nil, handleReadError(d, "SecurityPolicy", id, err) + } + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("path", obj.Path) + d.Set("domain", getDomainFromResourcePath(*obj.Path)) + d.Set("category", obj.Category) + d.Set("comments", obj.Comments) + d.Set("locked", obj.Locked) + if len(obj.Scope) == 1 && obj.Scope[0] == "ANY" { + d.Set("scope", nil) + } else { + d.Set("scope", obj.Scope) + } + d.Set("sequence_number", obj.SequenceNumber) + d.Set("stateful", obj.Stateful) + d.Set("tcp_strict", obj.TcpStrict) + d.Set("revision", obj.Revision) + return &obj, nil +} + +func resourceNsxtPolicyParentSecurityPolicyCreate(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralCreate(d, m, false) +} + +func resourceNsxtPolicyParentSecurityPolicyRead(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, false) +} + +func resourceNsxtPolicyParentSecurityPolicyUpdate(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralUpdate(d, m, false) +} + +func resourceNsxtPolicyParentSecurityPolicyDelete(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyDelete(d, m) +} diff --git a/nsxt/resource_nsxt_policy_parent_security_policy_test.go b/nsxt/resource_nsxt_policy_parent_security_policy_test.go new file mode 100644 index 000000000..c7f732de6 --- /dev/null +++ b/nsxt/resource_nsxt_policy_parent_security_policy_test.go @@ -0,0 +1,164 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccResourceNsxtPolicyParentSecurityPolicy_basic(t *testing.T) { + testAccResourceNsxtPolicyParentSecurityPolicyBasic(t, false, func() { + testAccPreCheck(t) + }) +} + +func TestAccResourceNsxtPolicyParentSecurityPolicy_multitenancy(t *testing.T) { + testAccResourceNsxtPolicyParentSecurityPolicyBasic(t, true, func() { + testAccPreCheck(t) + testAccOnlyMultitenancy(t) + }) +} + +func testAccResourceNsxtPolicyParentSecurityPolicyBasic(t *testing.T, withContext bool, preCheck func()) { + testResourceName := "nsxt_policy_parent_security_policy.test" + + name := getAccTestResourceName() + updatedName := getAccTestResourceName() + locked := "true" + updatedLocked := "false" + seqNum := "1" + updatedSeqNum := "2" + tcpStrict := "true" + updatedTCPStrict := "false" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheck, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyParentSecurityPolicyCheckDestroy(state, updatedName) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyParentSecurityPolicyTemplate(withContext, name, locked, seqNum, tcpStrict), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), + resource.TestCheckResourceAttr(testResourceName, "display_name", name), + resource.TestCheckResourceAttr(testResourceName, "locked", locked), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", seqNum), + resource.TestCheckResourceAttr(testResourceName, "tcp_strict", tcpStrict), + ), + }, + { + Config: testAccNsxtPolicyParentSecurityPolicyTemplate(withContext, updatedName, updatedLocked, updatedSeqNum, updatedTCPStrict), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), + resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), + resource.TestCheckResourceAttr(testResourceName, "locked", updatedLocked), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", updatedSeqNum), + resource.TestCheckResourceAttr(testResourceName, "tcp_strict", updatedTCPStrict), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyParentSecurityPolicy_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_parent_security_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyParentSecurityPolicyCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyParentSecurityPolicyTemplate(false, name, "true", "1", "true"), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyParentSecurityPolicy_importBasic_multitenancy(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_parent_security_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyMultitenancy(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicySecurityPolicyCheckDestroy(state, name, defaultDomain) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyParentSecurityPolicyTemplate(true, name, "true", "1", "true"), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtPolicyParentSecurityPolicyCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_parent_security_policy" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + domain := rs.Primary.Attributes["domain"] + exists, err := resourceNsxtPolicySecurityPolicyExistsInDomain(testAccGetSessionContext(), resourceID, domain, connector) + if err != nil { + return err + } + if exists { + return fmt.Errorf("Policy SecurityPolicy %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicyParentSecurityPolicyTemplate(withContext bool, name, locked, seqNum, tcpStrict string) string { + context := "" + if withContext { + context = testAccNsxtPolicyMultitenancyContext() + } + return fmt.Sprintf(` +resource "nsxt_policy_parent_security_policy" "test" { +%s + display_name = "%s" + description = "Acceptance Test" + domain = "default" + category = "Application" + locked = %s + sequence_number = %s + stateful = true + tcp_strict = %s + + tag { + scope = "color" + tag = "orange" + } +}`, context, name, locked, seqNum, tcpStrict) +} diff --git a/nsxt/resource_nsxt_policy_security_policy.go b/nsxt/resource_nsxt_policy_security_policy.go index 1ca398f6b..5d9b5816e 100644 --- a/nsxt/resource_nsxt_policy_security_policy.go +++ b/nsxt/resource_nsxt_policy_security_policy.go @@ -24,7 +24,7 @@ func resourceNsxtPolicySecurityPolicy() *schema.Resource { Importer: &schema.ResourceImporter{ State: nsxtDomainResourceImporter, }, - Schema: getPolicySecurityPolicySchema(false, true), + Schema: getPolicySecurityPolicySchema(false, true, true), } } @@ -55,53 +55,30 @@ func resourceNsxtPolicySecurityPolicyExistsPartial(domainName string) func(sessi } } -func policySecurityPolicyBuildAndPatch(d *schema.ResourceData, m interface{}, connector client.Connector, isGlobalManager bool, id string, createFlow bool) error { - +func policySecurityPolicyBuildAndPatch(d *schema.ResourceData, m interface{}, id string, createFlow, withRule bool) error { + obj := parentSecurityPolicySchemaToModel(d, id) domain := d.Get("domain").(string) - displayName := d.Get("display_name").(string) - description := d.Get("description").(string) - tags := getPolicyTagsFromSchema(d) - category := d.Get("category").(string) - comments := d.Get("comments").(string) - locked := d.Get("locked").(bool) - scope := getStringListFromSchemaSet(d, "scope") - sequenceNumber := int64(d.Get("sequence_number").(int)) - stateful := d.Get("stateful").(bool) - tcpStrict := d.Get("tcp_strict").(bool) revision := int64(d.Get("revision").(int)) - objType := "SecurityPolicy" - - obj := model.SecurityPolicy{ - Id: &id, - DisplayName: &displayName, - Description: &description, - Tags: tags, - Category: &category, - Comments: &comments, - Locked: &locked, - Scope: scope, - SequenceNumber: &sequenceNumber, - Stateful: &stateful, - TcpStrict: &tcpStrict, - ResourceType: &objType, - } log.Printf("[INFO] Creating Security Policy with ID %s", id) - if createFlow { + if createFlow && withRule { if err := validatePolicyRuleSequence(d); err != nil { return err } - } else { + } + if !createFlow { // This is update flow obj.Revision = &revision } - policyChildren, err := getUpdatedRuleChildren(d) - if err != nil { - return err - } - if len(policyChildren) > 0 { - obj.Children = policyChildren + if withRule { + policyChildren, err := getUpdatedRuleChildren(d) + if err != nil { + return err + } + if len(policyChildren) > 0 { + obj.Children = policyChildren + } } log.Printf("[INFO] Using selective H-API for policy with ID %s", id) @@ -109,15 +86,43 @@ func policySecurityPolicyBuildAndPatch(d *schema.ResourceData, m interface{}, co } func resourceNsxtPolicySecurityPolicyCreate(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralCreate(d, m, true) +} + +func resourceNsxtPolicySecurityPolicyRead(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, true) +} + +func resourceNsxtPolicySecurityPolicyUpdate(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralUpdate(d, m, true) +} + +func resourceNsxtPolicySecurityPolicyDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Security Policy id") + } + connector := getPolicyConnector(m) + client := domains.NewSecurityPoliciesClient(getSessionContext(d, m), connector) + err := client.Delete(d.Get("domain").(string), id) + + if err != nil { + return handleDeleteError("Security Policy", id, err) + } + + return nil +} + +func resourceNsxtPolicySecurityPolicyGeneralCreate(d *schema.ResourceData, m interface{}, withRule bool) error { // Initialize resource Id and verify this ID is not yet used id, err := getOrGenerateID2(d, m, resourceNsxtPolicySecurityPolicyExistsPartial(d.Get("domain").(string))) if err != nil { return err } - err = policySecurityPolicyBuildAndPatch(d, m, connector, isPolicyGlobalManager(m), id, true) + err = policySecurityPolicyBuildAndPatch(d, m, id, true, withRule) if err != nil { return handleCreateError("Security Policy", id, err) @@ -126,71 +131,29 @@ func resourceNsxtPolicySecurityPolicyCreate(d *schema.ResourceData, m interface{ d.SetId(id) d.Set("nsx_id", id) - return resourceNsxtPolicySecurityPolicyRead(d, m) + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, withRule) } -func resourceNsxtPolicySecurityPolicyRead(d *schema.ResourceData, m interface{}) error { - connector := getPolicyConnector(m) - id := d.Id() - domainName := d.Get("domain").(string) - if id == "" { - return fmt.Errorf("Error obtaining Security Policy id") - } - client := domains.NewSecurityPoliciesClient(getSessionContext(d, m), connector) - obj, err := client.Get(domainName, id) +func resourceNsxtPolicySecurityPolicyGeneralRead(d *schema.ResourceData, m interface{}, withRule bool) error { + obj, err := parentSecurityPolicyModelToSchema(d, m) if err != nil { - return handleReadError(d, "SecurityPolicy", id, err) + return err } - d.Set("display_name", obj.DisplayName) - d.Set("description", obj.Description) - setPolicyTagsInSchema(d, obj.Tags) - d.Set("nsx_id", id) - d.Set("path", obj.Path) - d.Set("domain", getDomainFromResourcePath(*obj.Path)) - d.Set("category", obj.Category) - d.Set("comments", obj.Comments) - d.Set("locked", obj.Locked) - if len(obj.Scope) == 1 && obj.Scope[0] == "ANY" { - d.Set("scope", nil) - } else { - d.Set("scope", obj.Scope) + if withRule { + return setPolicyRulesInSchema(d, obj.Rules) } - d.Set("sequence_number", obj.SequenceNumber) - d.Set("stateful", obj.Stateful) - d.Set("tcp_strict", obj.TcpStrict) - d.Set("revision", obj.Revision) - return setPolicyRulesInSchema(d, obj.Rules) + return nil } -func resourceNsxtPolicySecurityPolicyUpdate(d *schema.ResourceData, m interface{}) error { - connector := getPolicyConnector(m) - +func resourceNsxtPolicySecurityPolicyGeneralUpdate(d *schema.ResourceData, m interface{}, withRule bool) error { id := d.Id() if id == "" { return fmt.Errorf("Error obtaining Security Policy id") } - err := policySecurityPolicyBuildAndPatch(d, m, connector, isPolicyGlobalManager(m), id, false) + err := policySecurityPolicyBuildAndPatch(d, m, id, false, withRule) if err != nil { return handleUpdateError("Security Policy", id, err) } - return resourceNsxtPolicySecurityPolicyRead(d, m) -} - -func resourceNsxtPolicySecurityPolicyDelete(d *schema.ResourceData, m interface{}) error { - id := d.Id() - if id == "" { - return fmt.Errorf("Error obtaining Security Policy id") - } - - connector := getPolicyConnector(m) - - client := domains.NewSecurityPoliciesClient(getSessionContext(d, m), connector) - err := client.Delete(d.Get("domain").(string), id) - - if err != nil { - return handleDeleteError("Security Policy", id, err) - } - - return nil + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, withRule) } diff --git a/nsxt/resource_nsxt_policy_security_policy_rule.go b/nsxt/resource_nsxt_policy_security_policy_rule.go new file mode 100644 index 000000000..99c8b3905 --- /dev/null +++ b/nsxt/resource_nsxt_policy_security_policy_rule.go @@ -0,0 +1,252 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. +SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + securitypolicies "github.com/vmware/terraform-provider-nsxt/api/infra/domains/security_policies" + utl "github.com/vmware/terraform-provider-nsxt/api/utl" +) + +func resourceNsxtPolicySecurityPolicyRule() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicySecurityPolicyRuleCreate, + Read: resourceNsxtPolicySecurityPolicyRuleRead, + Update: resourceNsxtPolicySecurityPolicyRuleUpdate, + Delete: resourceNsxtPolicySecurityPolicyRuleDelete, + Importer: &schema.ResourceImporter{ + State: nsxtSecurityPolicyRuleImporter, + }, + Schema: getSecurityPolicyAndGatewayRuleSchema(false, false, true, true), + } +} + +func resourceNsxtPolicySecurityPolicyRuleCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + policyPath := d.Get("policy_path").(string) + projectID := getProjectIDFromResourcePath(policyPath) + domain := getDomainFromResourcePath(policyPath) + policyID := getPolicyIDFromPath(policyPath) + + // Initialize resource Id and verify this ID is not yet used + id, err := getOrGenerateID2(d, m, resourceNsxtPolicySecurityPolicyRuleExistsPartial(policyPath)) + if err != nil { + return err + } + + if err := setSecurityPolicyRuleContext(d, projectID); err != nil { + return handleCreateError("SecurityPolicyRule", fmt.Sprintf("%s/%s", policyPath, id), err) + } + + log.Printf("[INFO] Creating Security Policy Rule with ID %s under policy %s", id, policyPath) + client := securitypolicies.NewRulesClient(getSessionContext(d, m), connector) + rule := securityPolicyRuleSchemaToModel(d, id) + err = client.Patch(domain, policyID, id, rule) + if err != nil { + return handleCreateError("SecurityPolicyRule", fmt.Sprintf("%s/%s", policyPath, id), err) + } + + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicySecurityPolicyRuleRead(d, m) +} + +func setSecurityPolicyRuleContext(d *schema.ResourceData, projectID string) error { + providedProjectID := getProjectIDFromSchema(d) + if providedProjectID == "" { + contexts := make([]interface{}, 1) + ctxMap := make(map[string]interface{}) + ctxMap["project_id"] = projectID + contexts[0] = ctxMap + return d.Set("context", contexts) + } else if providedProjectID != projectID { + return fmt.Errorf("provided project_id in context is inconsist with the project_id in policy_path") + } + return nil +} + +func securityPolicyRuleSchemaToModel(d *schema.ResourceData, id string) model.Rule { + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + action := d.Get("action").(string) + logged := d.Get("logged").(bool) + tag := d.Get("log_label").(string) + disabled := d.Get("disabled").(bool) + sourcesExcluded := d.Get("sources_excluded").(bool) + destinationsExcluded := d.Get("destinations_excluded").(bool) + + var ipProtocol *string + ipp := d.Get("ip_version").(string) + if ipp != "NONE" { + ipProtocol = &ipp + } + direction := d.Get("direction").(string) + notes := d.Get("notes").(string) + seq := d.Get("sequence_number").(int) + sequenceNumber := int64(seq) + tagStructs := getPolicyTagsFromSet(d.Get("tag").(*schema.Set)) + + resourceType := "Rule" + return model.Rule{ + ResourceType: &resourceType, + Id: &id, + DisplayName: &displayName, + Notes: ¬es, + Description: &description, + Action: &action, + Logged: &logged, + Tag: &tag, + Tags: tagStructs, + Disabled: &disabled, + SourcesExcluded: &sourcesExcluded, + DestinationsExcluded: &destinationsExcluded, + IpProtocol: ipProtocol, + Direction: &direction, + SourceGroups: getPathListFromSchema(d, "source_groups"), + DestinationGroups: getPathListFromSchema(d, "destination_groups"), + Services: getPathListFromSchema(d, "services"), + Scope: getPathListFromSchema(d, "scope"), + Profiles: getPathListFromSchema(d, "profiles"), + SequenceNumber: &sequenceNumber, + } +} + +func resourceNsxtPolicySecurityPolicyRuleExistsPartial(policyPath string) func(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + return func(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + return resourceNsxtPolicySecurityPolicyRuleExists(sessionContext, id, policyPath, connector) + } +} + +func resourceNsxtPolicySecurityPolicyRuleExists(sessionContext utl.SessionContext, id string, policyPath string, connector client.Connector) (bool, error) { + client := securitypolicies.NewRulesClient(sessionContext, connector) + + domain := getDomainFromResourcePath(policyPath) + policyID := getPolicyIDFromPath(policyPath) + _, err := client.Get(domain, policyID, id) + + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving Security Policy Rule", err) +} + +func resourceNsxtPolicySecurityPolicyRuleRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Security Policy Rule ID") + } + + policyPath := d.Get("policy_path").(string) + projectID := getProjectIDFromResourcePath(policyPath) + domain := getDomainFromResourcePath(policyPath) + policyID := getPolicyIDFromPath(policyPath) + + if err := setSecurityPolicyRuleContext(d, projectID); err != nil { + return handleReadError(d, "SecurityPolicyRule", fmt.Sprintf("%s/%s", policyPath, id), err) + } + + client := securitypolicies.NewRulesClient(getSessionContext(d, m), connector) + rule, err := client.Get(domain, policyID, id) + if err != nil { + return handleReadError(d, "SecurityPolicyRule", fmt.Sprintf("%s/%s", policyPath, id), err) + } + + securityPolicyRuleModelToSchema(d, rule) + return nil +} + +func securityPolicyRuleModelToSchema(d *schema.ResourceData, rule model.Rule) { + d.Set("display_name", rule.DisplayName) + d.Set("description", rule.Description) + d.Set("path", rule.Path) + d.Set("notes", rule.Notes) + d.Set("logged", rule.Logged) + d.Set("log_label", rule.Tag) + d.Set("action", rule.Action) + d.Set("destinations_excluded", rule.DestinationsExcluded) + d.Set("sources_excluded", rule.SourcesExcluded) + if rule.IpProtocol == nil { + d.Set("ip_version", "NONE") + } else { + d.Set("ip_version", rule.IpProtocol) + } + d.Set("direction", rule.Direction) + d.Set("disabled", rule.Disabled) + d.Set("revision", rule.Revision) + setPathListInSchema(d, "source_groups", rule.SourceGroups) + setPathListInSchema(d, "destination_groups", rule.DestinationGroups) + setPathListInSchema(d, "profiles", rule.Profiles) + setPathListInSchema(d, "services", rule.Services) + setPathListInSchema(d, "scope", rule.Scope) + d.Set("sequence_number", rule.SequenceNumber) + d.Set("nsx_id", rule.Id) + d.Set("rule_id", rule.RuleId) + + setPolicyTagsInSchema(d, rule.Tags) +} + +func resourceNsxtPolicySecurityPolicyRuleUpdate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Security Policy Rule ID") + } + + policyPath := d.Get("policy_path").(string) + log.Printf("[INFO] Updating Security Policy Rule with ID %s under policy %s", id, policyPath) + domain := getDomainFromResourcePath(policyPath) + policyID := getPolicyIDFromPath(policyPath) + + client := securitypolicies.NewRulesClient(getSessionContext(d, m), connector) + rule := securityPolicyRuleSchemaToModel(d, id) + err := client.Patch(domain, policyID, id, rule) + if err != nil { + return handleUpdateError("SecurityPolicyRule", fmt.Sprintf("%s/%s", policyPath, id), err) + } + + return resourceNsxtPolicySecurityPolicyRuleRead(d, m) +} + +func resourceNsxtPolicySecurityPolicyRuleDelete(d *schema.ResourceData, m interface{}) error { + id := d.Get("nsx_id").(string) + if id == "" { + return fmt.Errorf("Error obtaining Security Policy Rule ID") + } + + connector := getPolicyConnector(m) + + policyPath := d.Get("policy_path").(string) + log.Printf("[INFO] Deleting Security Policy Rule with ID %s under policy %s", id, policyPath) + domain := getDomainFromResourcePath(policyPath) + policyID := getPolicyIDFromPath(policyPath) + + client := securitypolicies.NewRulesClient(getSessionContext(d, m), connector) + return client.Delete(domain, policyID, id) +} + +func nsxtSecurityPolicyRuleImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + importID := d.Id() + rd, err := nsxtPolicyPathResourceImporterHelper(d, m) + if err != nil { + return rd, err + } + d.Set("policy_path", importID[:strings.Index(importID, "rule")-1]) + return []*schema.ResourceData{d}, nil +} diff --git a/nsxt/resource_nsxt_policy_security_policy_rule_test.go b/nsxt/resource_nsxt_policy_security_policy_rule_test.go new file mode 100644 index 000000000..34bfb3ff1 --- /dev/null +++ b/nsxt/resource_nsxt_policy_security_policy_rule_test.go @@ -0,0 +1,285 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccResourceNsxtPolicySecurityPolicyRule_basic(t *testing.T) { + testAccResourceNsxtPolicySecurityPolicyRuleBasic(t, false, func() { + testAccPreCheck(t) + }) +} + +func TestAccResourceNsxtPolicySecurityPolicyRule_multitenancy(t *testing.T) { + testAccResourceNsxtPolicySecurityPolicyRuleBasic(t, true, func() { + testAccPreCheck(t) + testAccOnlyMultitenancy(t) + }) +} + +func testAccResourceNsxtPolicySecurityPolicyRuleBasic(t *testing.T, withContext bool, preCheck func()) { + policyResourceName := "nsxt_policy_parent_security_policy.policy1" + policyName := getAccTestResourceName() + updatedPolicyName := getAccTestResourceName() + locked := "true" + updatedLocked := "false" + + ruleResourceName := "nsxt_policy_security_policy_rule.test" + appendRuleResourceName := "nsxt_policy_security_policy_rule.test2" + + name := getAccTestResourceName() + updatedName := getAccTestResourceName() + appendRuleName := getAccTestResourceName() + direction := "IN" + updatedDirection := "OUT" + proto := "IPV4" + updatedProto := "IPV4_IPV6" + action := "ALLOW" + updatedAction := "DROP" + seqNum := "1" + updatedSeqNum := "2" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheck, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + if err := testAccNsxtPolicyParentSecurityPolicyCheckDestroy(state, updatedPolicyName); err != nil { + return err + } + return testAccNsxtPolicySecurityPolicyRuleCheckDestroy(state, updatedName) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicySecurityPolicyRuleDeps(withContext, policyName, locked) + + testAccNsxtPolicySecurityPolicyRuleTemplate("test", name, action, direction, proto, seqNum), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(policyResourceName, defaultDomain), + resource.TestCheckResourceAttr(policyResourceName, "display_name", policyName), + resource.TestCheckResourceAttr(policyResourceName, "locked", locked), + + testAccNsxtPolicySecurityPolicyRuleExists(ruleResourceName), + resource.TestCheckResourceAttr(ruleResourceName, "display_name", name), + resource.TestCheckResourceAttr(ruleResourceName, "action", action), + resource.TestCheckResourceAttr(ruleResourceName, "direction", direction), + resource.TestCheckResourceAttr(ruleResourceName, "ip_version", proto), + resource.TestCheckResourceAttr(ruleResourceName, "sequence_number", seqNum), + ), + }, + { + // Update Policy and Rule at the same time + Config: testAccNsxtPolicySecurityPolicyRuleDeps(withContext, updatedPolicyName, updatedLocked) + + testAccNsxtPolicySecurityPolicyRuleTemplate("test", updatedName, updatedAction, updatedDirection, updatedProto, updatedSeqNum), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(policyResourceName, defaultDomain), + resource.TestCheckResourceAttr(policyResourceName, "display_name", updatedPolicyName), + resource.TestCheckResourceAttr(policyResourceName, "locked", updatedLocked), + + testAccNsxtPolicySecurityPolicyRuleExists(ruleResourceName), + resource.TestCheckResourceAttr(ruleResourceName, "display_name", updatedName), + resource.TestCheckResourceAttr(ruleResourceName, "action", updatedAction), + resource.TestCheckResourceAttr(ruleResourceName, "direction", updatedDirection), + resource.TestCheckResourceAttr(ruleResourceName, "ip_version", updatedProto), + resource.TestCheckResourceAttr(ruleResourceName, "sequence_number", updatedSeqNum), + ), + }, + { + // Update Policy and append another Rule at the same time + Config: testAccNsxtPolicySecurityPolicyRuleDeps(withContext, policyName, locked) + + testAccNsxtPolicySecurityPolicyRuleTemplate("test", updatedName, updatedAction, updatedDirection, updatedProto, updatedSeqNum) + + testAccNsxtPolicySecurityPolicyRuleTemplate("test2", appendRuleName, action, direction, proto, seqNum), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(policyResourceName, defaultDomain), + resource.TestCheckResourceAttr(policyResourceName, "display_name", policyName), + resource.TestCheckResourceAttr(policyResourceName, "locked", locked), + + testAccNsxtPolicySecurityPolicyRuleExists(appendRuleResourceName), + resource.TestCheckResourceAttr(appendRuleResourceName, "display_name", appendRuleName), + resource.TestCheckResourceAttr(appendRuleResourceName, "action", action), + resource.TestCheckResourceAttr(appendRuleResourceName, "direction", direction), + resource.TestCheckResourceAttr(appendRuleResourceName, "ip_version", proto), + resource.TestCheckResourceAttr(appendRuleResourceName, "sequence_number", seqNum), + + testAccNsxtPolicySecurityPolicyRuleExists(ruleResourceName), + resource.TestCheckResourceAttr(ruleResourceName, "display_name", updatedName), + resource.TestCheckResourceAttr(ruleResourceName, "action", updatedAction), + resource.TestCheckResourceAttr(ruleResourceName, "direction", updatedDirection), + resource.TestCheckResourceAttr(ruleResourceName, "ip_version", updatedProto), + resource.TestCheckResourceAttr(ruleResourceName, "sequence_number", updatedSeqNum), + ), + }, + { + // Update Policy and delete one of its Rule at the same time + Config: testAccNsxtPolicySecurityPolicyRuleDeps(withContext, updatedPolicyName, updatedLocked) + + testAccNsxtPolicySecurityPolicyRuleTemplate("test", updatedName, updatedAction, updatedDirection, updatedProto, updatedSeqNum), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(policyResourceName, defaultDomain), + resource.TestCheckResourceAttr(policyResourceName, "display_name", updatedPolicyName), + resource.TestCheckResourceAttr(policyResourceName, "locked", updatedLocked), + + func(state *terraform.State) error { + return testAccNsxtPolicySecurityPolicyRuleCheckDestroy(state, appendRuleName) + }, + + testAccNsxtPolicySecurityPolicyRuleExists(ruleResourceName), + resource.TestCheckResourceAttr(ruleResourceName, "display_name", updatedName), + resource.TestCheckResourceAttr(ruleResourceName, "action", updatedAction), + resource.TestCheckResourceAttr(ruleResourceName, "direction", updatedDirection), + resource.TestCheckResourceAttr(ruleResourceName, "ip_version", updatedProto), + resource.TestCheckResourceAttr(ruleResourceName, "sequence_number", updatedSeqNum), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicySecurityPolicyRule_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_security_policy_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicySecurityPolicyRuleCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicySecurityPolicyRuleDeps(false, "policyName", "true") + + testAccNsxtPolicySecurityPolicyRuleTemplate("test", name, "ALLOW", "IN", "IPV4", "1"), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func TestAccResourceNsxtPolicySecurityPolicyRule_importBasic_multitenancy(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_security_policy_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyMultitenancy(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicySecurityPolicyCheckDestroy(state, name, defaultDomain) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicySecurityPolicyRuleDeps(true, "policyName", "true") + + testAccNsxtPolicySecurityPolicyRuleTemplate("test", name, "ALLOW", "IN", "IPV4", "1"), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtPolicySecurityPolicyRuleExists(resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy SecurityPolicyRule resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy SecurityPolicyRule resource ID not set in resources") + } + + policyPath := rs.Primary.Attributes["policy_path"] + exists, err := resourceNsxtPolicySecurityPolicyRuleExists(testAccGetSessionContext(), resourceID, policyPath, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Error while retrieving policy SecurityPolicyRule ID %s under SecurityPolicy %s", resourceID, policyPath) + } + return nil + } +} + +func testAccNsxtPolicySecurityPolicyRuleCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_security_policy_rule" { + continue + } + + if rs.Primary.Attributes["display_name"] != displayName { + continue + } + + resourceID := rs.Primary.Attributes["id"] + policyPath := rs.Primary.Attributes["policy_path"] + exists, err := resourceNsxtPolicySecurityPolicyRuleExists(testAccGetSessionContext(), resourceID, policyPath, connector) + if err != nil { + return err + } + if exists { + return fmt.Errorf("Policy SecurityPolicyRule %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicySecurityPolicyRuleDeps(withContext bool, displayName, locked string) string { + context := "" + if withContext { + context = testAccNsxtPolicyMultitenancyContext() + } + return fmt.Sprintf(` +resource "nsxt_policy_parent_security_policy" "policy1" { +%s + display_name = "%s" + description = "Acceptance Test" + category = "Application" + locked = %s + sequence_number = 3 + stateful = true + tcp_strict = false + + tag { + scope = "color" + tag = "orange" + } +}`, context, displayName, locked) +} + +func testAccNsxtPolicySecurityPolicyRuleTemplate(resourceName, displayName, action, direction, ipVersion, seqNum string) string { + return fmt.Sprintf(` +resource "nsxt_policy_security_policy_rule" "%s" { + display_name = "%s" + policy_path = nsxt_policy_parent_security_policy.policy1.path + action = "%s" + direction = "%s" + ip_version = "%s" + sequence_number = %s + + tag { + scope = "color" + tag = "orange" + } +}`, resourceName, displayName, action, direction, ipVersion, seqNum) +} diff --git a/nsxt/utils.go b/nsxt/utils.go index caedfc5be..2a11896a8 100644 --- a/nsxt/utils.go +++ b/nsxt/utils.go @@ -702,6 +702,27 @@ func getContextSchema() *schema.Schema { } } +func getComputedContextSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Description: "Resource context", + Optional: true, + Computed: true, + MaxItems: 1, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "project_id": { + Type: schema.TypeString, + Description: "Id of the project which the resource belongs to.", + Required: true, + ForceNew: true, + }, + }, + }, + } +} + func getCustomizedMPTagsFromSchema(d *schema.ResourceData, schemaName string) []mp_model.Tag { tags := d.Get(schemaName).(*schema.Set).List() tagList := make([]mp_model.Tag, 0) diff --git a/website/docs/d/compute_manager.html.markdown b/website/docs/d/compute_manager.html.markdown index b142955c7..1ccee4abc 100644 --- a/website/docs/d/compute_manager.html.markdown +++ b/website/docs/d/compute_manager.html.markdown @@ -27,4 +27,4 @@ data "nsxt_compute_manager" "test_vcenter" { In addition to arguments listed above, the following attributes are exported: * `description` - The description of the resource. -* `server` - IP address or hostname of the resource. \ No newline at end of file +* `server` - IP address or hostname of the resource. diff --git a/website/docs/r/policy_parent_security_policy.html.markdown b/website/docs/r/policy_parent_security_policy.html.markdown new file mode 100644 index 000000000..f512eb291 --- /dev/null +++ b/website/docs/r/policy_parent_security_policy.html.markdown @@ -0,0 +1,160 @@ +--- +subcategory: "Firewall" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_parent_security_policy" +description: A resource to configure a Security Policy without rules. +--- + +# nsxt_policy_parent_security_policy + +This resource provides a method for the management of Security Policy without rules. + +Note: to avoid unexpected behavior, don't use this resource and resource `nsxt_policy_security_policy` to manage the same Security Policy at the same time. +To config rules under this resource, please use resource `nsxt_policy_security_policy_rule` to manage rules separately. + +This resource is applicable to NSX Global Manager, NSX Policy Manager and VMC. + +## Example Usage + +```hcl +resource "nsxt_policy_parent_security_policy" "policy1" { + display_name = "policy1" + description = "Terraform provisioned Security Policy" + category = "Application" + locked = false + stateful = true + tcp_strict = false + scope = [nsxt_policy_group.pets.path] + + lifecycle { + create_before_destroy = true + } +} + +resource "nsxt_policy_security_policy_rule" "rule1" { + display_name = "rule1" + description = "Terraform provisioned Security Policy Rule" + policy_path = nsxt_policy_parent_security_policy.policy1.path + sequence_number = 1 + destination_groups = [nsxt_policy_group.cats.path, nsxt_policy_group.dogs.path] + action = "DROP" + services = [nsxt_policy_service.icmp.path] + logged = true + + depends_on = [nsxt_policy_parent_security_policy.policy1] +} +``` + +## Global Manager example + +```hcl +data "nsxt_policy_site" "paris" { + display_name = "Paris" +} +resource "nsxt_policy_parent_security_policy" "policy1" { + display_name = "policy1" + description = "Terraform provisioned Security Policy" + category = "Application" + locked = false + stateful = true + tcp_strict = false + scope = [nsxt_policy_group.pets.path] + domain = data.nsxt_policy_site.paris.id + + lifecycle { + create_before_destroy = true + } +} + +resource "nsxt_policy_security_policy_rule" "rule1" { + display_name = "rule1" + description = "Terraform provisioned Security Policy Rule" + policy_path = nsxt_policy_parent_security_policy.policy1.path + sequence_number = 1 + destination_groups = [nsxt_policy_group.cats.path, nsxt_policy_group.dogs.path] + action = "DROP" + services = [nsxt_policy_service.icmp.path] + logged = true + + depends_on = [nsxt_policy_parent_security_policy.policy1] +} +``` + +## Example Usage - Multi-Tenancy + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +resource "nsxt_policy_parent_security_policy" "policy1" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "policy1" + description = "Terraform provisioned Security Policy" + category = "Application" + locked = false + stateful = true + tcp_strict = false + scope = [nsxt_policy_group.pets.path] + + lifecycle { + create_before_destroy = true + } +} + +resource "nsxt_policy_security_policy_rule" "rule1" { + display_name = "rule1" + description = "Terraform provisioned Security Policy Rule" + policy_path = nsxt_policy_parent_security_policy.policy1.path + sequence_number = 1 + destination_groups = [nsxt_policy_group.cats.path, nsxt_policy_group.dogs.path] + action = "DROP" + services = [nsxt_policy_service.icmp.path] + logged = true + + depends_on = [nsxt_policy_parent_security_policy.policy1] +} +``` + +-> We recommend using `lifecycle` directive as in samples above, in order to avoid dependency issues when updating groups/services simultaneously with the rule. + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `domain` - (Optional) The domain to use for the resource. This domain must already exist. For VMware Cloud on AWS use `cgw`. For Global Manager, please use site id for this field. If not specified, this field is default to `default`. +* `tag` - (Optional) A list of scope + tag pairs to associate with this policy. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `context` - (Optional) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to +* `category` - (Required) Category of this policy. For local manager must be one of `Ethernet`, `Emergency`, `Infrastructure`, `Environment`, `Application`. For global manager must be one of: `Infrastructure`, `Environment`, `Application`. +* `comments` - (Optional) Comments for security policy lock/unlock. +* `locked` - (Optional) Indicates whether a security policy should be locked. If locked by a user, no other user would be able to modify this policy. +* `scope` - (Optional) The list of policy object paths where the rules in this policy will get applied. +* `sequence_number` - (Optional) This field is used to resolve conflicts between security policies across domains. +* `stateful` - (Optional) If true, state of the network connects are tracked and a stateful packet inspection is performed. Default is true. +* `tcp_strict` - (Optional) Ensures that a 3 way TCP handshake is done before the data packets are sent. Default is false. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the Security Policy. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing security policy can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_parent_security_policy.policy1 domain/ID +``` + +The above command imports the security policy named `policy1` under NSX domain `domain` with the NSX Policy ID `ID`. diff --git a/website/docs/r/policy_security_policy_rule.html.markdown b/website/docs/r/policy_security_policy_rule.html.markdown new file mode 100644 index 000000000..f696e6b8a --- /dev/null +++ b/website/docs/r/policy_security_policy_rule.html.markdown @@ -0,0 +1,93 @@ +--- +subcategory: "Firewall" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_security_policy_rule" +description: A resource to configure a Security Policy Rule. +--- + +# nsxt_policy_security_policy_rule + +This resource provides a method for the management of Security Policy Rule. + +Note: to avoid unexpected behavior, don't use this resource and resource `nsxt_policy_security_policy` to manage rules under a security policy at the same time. +Instead, please use this resource with resource `nsxt_policy_parent_security_policy` to manage a security policy and its rules separately, and use `nsxt_policy_security_policy` to manage a security policy and its rules in one single resource. + +This resource is applicable to NSX Global Manager, NSX Policy Manager and VMC. + +## Example Usage + +```hcl +resource "nsxt_policy_security_policy_rule" "rule1" { + display_name = "rule1" + description = "Terraform provisioned Security Policy Rule" + policy_path = nsxt_policy_parent_security_policy.policy1.path + sequence_number = 1 + destination_groups = [nsxt_policy_group.cats.path, nsxt_policy_group.dogs.path] + action = "DROP" + services = [nsxt_policy_service.icmp.path] + logged = true +} +``` + +## Example Usage - Multi-Tenancy + +```hcl +resource "nsxt_policy_security_policy_rule" "rule1" { + display_name = "rule1" + description = "Terraform provisioned Security Policy Rule" + policy_path = nsxt_policy_parent_security_policy.policy1.path # Path of a multi-tenancy policy + sequence_number = 1 + destination_groups = [nsxt_policy_group.cats.path, nsxt_policy_group.dogs.path] + action = "DROP" + services = [nsxt_policy_service.icmp.path] + logged = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this policy. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `policy_path` - (Required) The path of the Security Policy which the object belongs to +* `context` - (Optional) The context which the object belongs to. If it's not provided, it will be derived from `policy_path`. + * `project_id` - (Required) The ID of the project which the object belongs to +* `sequence_number` - (Required) This field is used to resolve conflicts between multiple Rules under Security or Gateway Policy for a Domain. Please note that sequence numbers should start with 1 and not 0 to avoid confusion. +* `action` - (Optional) Rule action, one of `ALLOW`, `DROP`, `REJECT` and `JUMP_TO_APPLICATION`. Default is `ALLOW`. `JUMP_TO_APPLICATION` is only applicable in `Environment` category. +* `destination_groups` - (Optional) Set of group paths that serve as the destination for this rule. IPs, IP ranges, or CIDRs may also be used starting in NSX-T 3.0. An empty set can be used to specify "Any". +* `source_groups` - (Optional) Set of group paths that serve as the source for this rule. IPs, IP ranges, or CIDRs may also be used starting in NSX-T 3.0. An empty set can be used to specify "Any". +* `destinations_excluded` - (Optional) A boolean value indicating negation of destination groups. +* `sources_excluded` - (Optional) A boolean value indicating negation of source groups. +* `direction` - (Optional) Traffic direction, one of `IN`, `OUT` or `IN_OUT`. Default is `IN_OUT`. +* `disabled` - (Optional) Flag to disable this rule. Default is false. +* `ip_version` - (Optional) Version of IP protocol, one of `NONE`, `IPV4`, `IPV6`, `IPV4_IPV6`. Default is `IPV4_IPV6`. For `Ethernet` category rules, use `NONE` value. +* `logged` - (Optional) Flag to enable packet logging. Default is false. +* `notes` - (Optional) Additional notes on changes. +* `profiles` - (Optional) Set of profile paths relevant for this rule. +* `scope` - (Optional) Set of policy object paths where the rule is applied. +* `services` - (Optional) Set of service paths to match. +* `log_label` - (Optional) Additional information (string) which will be propagated to the rule syslog. + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. +* `rule_id` - Unique positive number that is assigned by the system and is useful for debugging. + +## Importing + +An existing security policy can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_security_policy_rule.rule1 POLICY_PATH +``` + +The above command imports the security policy rule named `rule1` with policy path `POLICY_PATH`. From 184970408bea720e3287af5deca0a8d1398be5b6 Mon Sep 17 00:00:00 2001 From: graysonwu Date: Wed, 27 Dec 2023 20:47:26 -0700 Subject: [PATCH 2/2] Address comments Signed-off-by: graysonwu --- nsxt/resource_nsxt_policy_security_policy_rule.go | 6 +++++- website/docs/r/policy_parent_security_policy.html.markdown | 6 ------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/nsxt/resource_nsxt_policy_security_policy_rule.go b/nsxt/resource_nsxt_policy_security_policy_rule.go index 99c8b3905..2ff54f1d1 100644 --- a/nsxt/resource_nsxt_policy_security_policy_rule.go +++ b/nsxt/resource_nsxt_policy_security_policy_rule.go @@ -247,6 +247,10 @@ func nsxtSecurityPolicyRuleImporter(d *schema.ResourceData, m interface{}) ([]*s if err != nil { return rd, err } - d.Set("policy_path", importID[:strings.Index(importID, "rule")-1]) + ruleIdx := strings.Index(importID, "rule") + if ruleIdx <= 0 { + return nil, fmt.Errorf("invalid path of Security Policy Rule to import") + } + d.Set("policy_path", importID[:ruleIdx-1]) return []*schema.ResourceData{d}, nil } diff --git a/website/docs/r/policy_parent_security_policy.html.markdown b/website/docs/r/policy_parent_security_policy.html.markdown index f512eb291..9d04ad643 100644 --- a/website/docs/r/policy_parent_security_policy.html.markdown +++ b/website/docs/r/policy_parent_security_policy.html.markdown @@ -40,8 +40,6 @@ resource "nsxt_policy_security_policy_rule" "rule1" { action = "DROP" services = [nsxt_policy_service.icmp.path] logged = true - - depends_on = [nsxt_policy_parent_security_policy.policy1] } ``` @@ -75,8 +73,6 @@ resource "nsxt_policy_security_policy_rule" "rule1" { action = "DROP" services = [nsxt_policy_service.icmp.path] logged = true - - depends_on = [nsxt_policy_parent_security_policy.policy1] } ``` @@ -113,8 +109,6 @@ resource "nsxt_policy_security_policy_rule" "rule1" { action = "DROP" services = [nsxt_policy_service.icmp.path] logged = true - - depends_on = [nsxt_policy_parent_security_policy.policy1] } ```