diff --git a/nsxt/gateway_common.go b/nsxt/gateway_common.go index 8e2648592..50824204f 100644 --- a/nsxt/gateway_common.go +++ b/nsxt/gateway_common.go @@ -436,6 +436,11 @@ func policyInfraPatch(context utl.SessionContext, obj model.Infra, connector cli } return infraClient.Patch(gmObj.(gm_model.Infra), &enforceRevision) + } else if context.ClientType == utl.VPC { + context = utl.SessionContext{ + ClientType: utl.Multitenancy, + ProjectID: context.ProjectID, + } } infraClient := nsx_policy.NewInfraClient(context, connector) diff --git a/nsxt/policy_common.go b/nsxt/policy_common.go index 1a32f6312..24225f83c 100644 --- a/nsxt/policy_common.go +++ b/nsxt/policy_common.go @@ -311,7 +311,7 @@ func getSecurityPolicyAndGatewayRuleSchema(scopeRequired bool, isIds bool, nsxID } func getPolicyGatewayPolicySchema() map[string]*schema.Schema { - secPolicy := getPolicySecurityPolicySchema(false, true, true) + secPolicy := getPolicySecurityPolicySchema(false, true, true, true) // GW Policies don't support scope delete(secPolicy, "scope") secPolicy["category"].ValidateFunc = validation.StringInSlice(gatewayPolicyCategoryWritableValues, false) @@ -320,7 +320,7 @@ func getPolicyGatewayPolicySchema() map[string]*schema.Schema { return secPolicy } -func getPolicySecurityPolicySchema(isIds, withContext, withRule bool) map[string]*schema.Schema { +func getPolicySecurityPolicySchema(isIds, withContext, withRule, withDomain bool) map[string]*schema.Schema { result := map[string]*schema.Schema{ "nsx_id": getNsxIDSchema(), "path": getPathSchema(), @@ -328,7 +328,7 @@ func getPolicySecurityPolicySchema(isIds, withContext, withRule bool) map[string "description": getDescriptionSchema(), "revision": getRevisionSchema(), "tag": getTagsSchema(), - "context": getContextSchema(false, false, false), + "context": getContextSchema(!withDomain, false, !withDomain), "domain": getDomainNameSchema(), "category": { Type: schema.TypeString, @@ -392,6 +392,9 @@ func getPolicySecurityPolicySchema(isIds, withContext, withRule bool) map[string if !withRule { delete(result, "rule") } + if !withDomain { + delete(result, "domain") + } return result } diff --git a/nsxt/provider.go b/nsxt/provider.go index 957b30949..c239f2ba4 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -496,6 +496,7 @@ func Provider() *schema.Provider { "nsxt_policy_gateway_flood_protection_profile_binding": resourceNsxtPolicyGatewayFloodProtectionProfileBinding(), "nsxt_policy_compute_sub_cluster": resourceNsxtPolicyComputeSubCluster(), "nsxt_policy_tier0_inter_vrf_routing": resourceNsxtPolicyTier0InterVRFRouting(), + "nsxt_vpc_security_policy": resourceNsxtVPCSecurityPolicy(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_policy_intrusion_service_policy.go b/nsxt/resource_nsxt_policy_intrusion_service_policy.go index bf760bd2a..bfddce163 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, true, true), + Schema: getPolicySecurityPolicySchema(true, true, true, true), } } diff --git a/nsxt/resource_nsxt_policy_parent_security_policy.go b/nsxt/resource_nsxt_policy_parent_security_policy.go index 4ea0439d9..2c8794c4d 100644 --- a/nsxt/resource_nsxt_policy_parent_security_policy.go +++ b/nsxt/resource_nsxt_policy_parent_security_policy.go @@ -21,7 +21,7 @@ func resourceNsxtPolicyParentSecurityPolicy() *schema.Resource { Importer: &schema.ResourceImporter{ State: nsxtDomainResourceImporter, }, - Schema: getPolicySecurityPolicySchema(false, true, false), + Schema: getPolicySecurityPolicySchema(false, true, false, true), } } @@ -55,10 +55,13 @@ func parentSecurityPolicySchemaToModel(d *schema.ResourceData, id string) model. } } -func parentSecurityPolicyModelToSchema(d *schema.ResourceData, m interface{}) (*model.SecurityPolicy, error) { +func parentSecurityPolicyModelToSchema(d *schema.ResourceData, m interface{}, withDomain bool) (*model.SecurityPolicy, error) { connector := getPolicyConnector(m) id := d.Id() - domainName := d.Get("domain").(string) + domainName := "" + if withDomain { + domainName = d.Get("domain").(string) + } if id == "" { return nil, fmt.Errorf("Error obtaining Security Policy id") } @@ -75,7 +78,9 @@ func parentSecurityPolicyModelToSchema(d *schema.ResourceData, m interface{}) (* setPolicyTagsInSchema(d, obj.Tags) d.Set("nsx_id", id) d.Set("path", obj.Path) - d.Set("domain", getDomainFromResourcePath(*obj.Path)) + if withDomain { + d.Set("domain", getDomainFromResourcePath(*obj.Path)) + } d.Set("category", obj.Category) d.Set("comments", obj.Comments) d.Set("locked", obj.Locked) @@ -92,15 +97,15 @@ func parentSecurityPolicyModelToSchema(d *schema.ResourceData, m interface{}) (* } func resourceNsxtPolicyParentSecurityPolicyCreate(d *schema.ResourceData, m interface{}) error { - return resourceNsxtPolicySecurityPolicyGeneralCreate(d, m, false) + return resourceNsxtPolicySecurityPolicyGeneralCreate(d, m, false, true) } func resourceNsxtPolicyParentSecurityPolicyRead(d *schema.ResourceData, m interface{}) error { - return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, false) + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, false, true) } func resourceNsxtPolicyParentSecurityPolicyUpdate(d *schema.ResourceData, m interface{}) error { - return resourceNsxtPolicySecurityPolicyGeneralUpdate(d, m, false) + return resourceNsxtPolicySecurityPolicyGeneralUpdate(d, m, false, true) } func resourceNsxtPolicyParentSecurityPolicyDelete(d *schema.ResourceData, m interface{}) error { diff --git a/nsxt/resource_nsxt_policy_predefined_security_policy.go b/nsxt/resource_nsxt_policy_predefined_security_policy.go index 2164cd923..97ec3363f 100644 --- a/nsxt/resource_nsxt_policy_predefined_security_policy.go +++ b/nsxt/resource_nsxt_policy_predefined_security_policy.go @@ -12,6 +12,7 @@ import ( "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + nsxt "github.com/vmware/vsphere-automation-sdk-go/services/nsxt" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" "github.com/vmware/terraform-provider-nsxt/api/infra/domains" @@ -178,6 +179,60 @@ func revertSecurityPolicyDefaultRule(rule model.Rule) model.Rule { return rule } +func strPtr(s string) *string { + v := s + return &v +} + +func createChildVPCWithSecurityPolicy(context utl.SessionContext, policyID string, policy model.SecurityPolicy) (*data.StructValue, error) { + converter := bindings.NewTypeConverter() + + childPolicy := model.ChildSecurityPolicy{ + ResourceType: "ChildSecurityPolicy", + SecurityPolicy: &policy, + } + + dataValue, errors := converter.ConvertToVapi(childPolicy, model.ChildSecurityPolicyBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + + childVPC := model.ChildResourceReference{ + Id: &context.VPCID, + ResourceType: "ChildResourceReference", + TargetType: strPtr("Vpc"), + Children: []*data.StructValue{dataValue.(*data.StructValue)}, + } + + dataValue, errors = converter.ConvertToVapi(childVPC, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + childProject := model.ChildResourceReference{ + Id: &context.ProjectID, + ResourceType: "ChildResourceReference", + TargetType: strPtr("Project"), + Children: []*data.StructValue{dataValue.(*data.StructValue)}, + } + dataValue, errors = converter.ConvertToVapi(childProject, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + + childOrg := model.ChildResourceReference{ + Id: strPtr(defaultOrgID), + ResourceType: "ChildResourceReference", + TargetType: strPtr("Org"), + Children: []*data.StructValue{dataValue.(*data.StructValue)}, + } + dataValue, errors = converter.ConvertToVapi(childOrg, model.ChildResourceReferenceBindingType()) + if len(errors) > 0 { + return nil, errors[0] + } + + return dataValue.(*data.StructValue), nil +} + func createChildDomainWithSecurityPolicy(domain string, policyID string, policy model.SecurityPolicy) (*data.StructValue, error) { converter := bindings.NewTypeConverter() @@ -409,20 +464,29 @@ func resourceNsxtPolicyPredefinedSecurityPolicyDelete(d *schema.ResourceData, m } func securityPolicyInfraPatch(context utl.SessionContext, policy model.SecurityPolicy, domain string, m interface{}) error { + connector := getPolicyConnector(m) + if context.ClientType == utl.VPC { + childVPC, err := createChildVPCWithSecurityPolicy(context, *policy.Id, policy) + if err != nil { + return fmt.Errorf("Failed to create H-API for VPC Security Policy: %s", err) + } + orgRoot := model.OrgRoot{ + ResourceType: strPtr("OrgRoot"), + Children: []*data.StructValue{childVPC}, + } + + client := nsxt.NewOrgRootClient(connector) + return client.Patch(orgRoot, nil) + } + childDomain, err := createChildDomainWithSecurityPolicy(domain, *policy.Id, policy) if err != nil { return fmt.Errorf("Failed to create H-API for Predefined Security Policy: %s", err) } - - var infraChildren []*data.StructValue - infraChildren = append(infraChildren, childDomain) - - infraType := "Infra" infraObj := model.Infra{ - Children: infraChildren, - ResourceType: &infraType, + Children: []*data.StructValue{childDomain}, + ResourceType: strPtr("Infra"), } - return policyInfraPatch(context, infraObj, getPolicyConnector(m), false) - + return policyInfraPatch(context, infraObj, connector, false) } diff --git a/nsxt/resource_nsxt_policy_security_policy.go b/nsxt/resource_nsxt_policy_security_policy.go index 71fb87d26..4383dc9e8 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, true), + Schema: getPolicySecurityPolicySchema(false, true, true, true), } } @@ -61,9 +61,12 @@ func resourceNsxtPolicySecurityPolicyExistsPartial(domainName string) func(sessi } } -func policySecurityPolicyBuildAndPatch(d *schema.ResourceData, m interface{}, id string, createFlow, withRule bool) error { +func policySecurityPolicyBuildAndPatch(d *schema.ResourceData, m interface{}, id string, createFlow, withRule, withDomain bool) error { obj := parentSecurityPolicySchemaToModel(d, id) - domain := d.Get("domain").(string) + domain := "" + if withDomain { + domain = d.Get("domain").(string) + } revision := int64(d.Get("revision").(int)) log.Printf("[INFO] Creating Security Policy with ID %s", id) @@ -92,15 +95,15 @@ func policySecurityPolicyBuildAndPatch(d *schema.ResourceData, m interface{}, id } func resourceNsxtPolicySecurityPolicyCreate(d *schema.ResourceData, m interface{}) error { - return resourceNsxtPolicySecurityPolicyGeneralCreate(d, m, true) + return resourceNsxtPolicySecurityPolicyGeneralCreate(d, m, true, true) } func resourceNsxtPolicySecurityPolicyRead(d *schema.ResourceData, m interface{}) error { - return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, true) + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, true, true) } func resourceNsxtPolicySecurityPolicyUpdate(d *schema.ResourceData, m interface{}) error { - return resourceNsxtPolicySecurityPolicyGeneralUpdate(d, m, true) + return resourceNsxtPolicySecurityPolicyGeneralUpdate(d, m, true, true) } func resourceNsxtPolicySecurityPolicyDelete(d *schema.ResourceData, m interface{}) error { @@ -124,14 +127,18 @@ func resourceNsxtPolicySecurityPolicyDelete(d *schema.ResourceData, m interface{ return nil } -func resourceNsxtPolicySecurityPolicyGeneralCreate(d *schema.ResourceData, m interface{}, withRule bool) error { +func resourceNsxtPolicySecurityPolicyGeneralCreate(d *schema.ResourceData, m interface{}, withRule, withDomain bool) error { // Initialize resource Id and verify this ID is not yet used - id, err := getOrGenerateID2(d, m, resourceNsxtPolicySecurityPolicyExistsPartial(d.Get("domain").(string))) + domain := "" + if withDomain { + domain = d.Get("domain").(string) + } + id, err := getOrGenerateID2(d, m, resourceNsxtPolicySecurityPolicyExistsPartial(domain)) if err != nil { return err } - err = policySecurityPolicyBuildAndPatch(d, m, id, true, withRule) + err = policySecurityPolicyBuildAndPatch(d, m, id, true, withRule, withDomain) if err != nil { return handleCreateError("Security Policy", id, err) @@ -140,11 +147,11 @@ func resourceNsxtPolicySecurityPolicyGeneralCreate(d *schema.ResourceData, m int d.SetId(id) d.Set("nsx_id", id) - return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, withRule) + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, withRule, withDomain) } -func resourceNsxtPolicySecurityPolicyGeneralRead(d *schema.ResourceData, m interface{}, withRule bool) error { - obj, err := parentSecurityPolicyModelToSchema(d, m) +func resourceNsxtPolicySecurityPolicyGeneralRead(d *schema.ResourceData, m interface{}, withRule, withDomain bool) error { + obj, err := parentSecurityPolicyModelToSchema(d, m, withDomain) if err != nil { return handleReadError(d, "SecurityPolicy", d.Id(), err) } @@ -154,15 +161,15 @@ func resourceNsxtPolicySecurityPolicyGeneralRead(d *schema.ResourceData, m inter return nil } -func resourceNsxtPolicySecurityPolicyGeneralUpdate(d *schema.ResourceData, m interface{}, withRule bool) error { +func resourceNsxtPolicySecurityPolicyGeneralUpdate(d *schema.ResourceData, m interface{}, withRule, withDomain bool) error { id := d.Id() if id == "" { return fmt.Errorf("Error obtaining Security Policy id") } - err := policySecurityPolicyBuildAndPatch(d, m, id, false, withRule) + err := policySecurityPolicyBuildAndPatch(d, m, id, false, withRule, withDomain) if err != nil { return handleUpdateError("Security Policy", id, err) } - return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, withRule) + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, withRule, withDomain) } diff --git a/nsxt/resource_nsxt_policy_security_policy_test.go b/nsxt/resource_nsxt_policy_security_policy_test.go index f756cff22..90d164515 100644 --- a/nsxt/resource_nsxt_policy_security_policy_test.go +++ b/nsxt/resource_nsxt_policy_security_policy_test.go @@ -27,7 +27,8 @@ func TestAccResourceNsxtPolicySecurityPolicy_multitenancy(t *testing.T) { func testAccResourceNsxtPolicySecurityPolicyBasic(t *testing.T, withContext bool, preCheck func()) { name := getAccTestResourceName() updatedName := getAccTestResourceName() - testResourceName := "nsxt_policy_security_policy.test" + resourceName := "nsxt_policy_security_policy" + testResourceName := fmt.Sprintf("%s.test", resourceName) comments1 := "Acceptance test create" comments2 := "Acceptance test update" direction1 := "IN" @@ -46,7 +47,7 @@ func testAccResourceNsxtPolicySecurityPolicyBasic(t *testing.T, withContext bool }, Steps: []resource.TestStep{ { - Config: testAccNsxtPolicySecurityPolicyBasic(name, comments1, defaultDomain, withContext), + Config: testAccNsxtPolicySecurityPolicyBasic(resourceName, name, comments1, defaultDomain, withContext), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), resource.TestCheckResourceAttr(testResourceName, "display_name", name), @@ -64,7 +65,7 @@ func testAccResourceNsxtPolicySecurityPolicyBasic(t *testing.T, withContext bool ), }, { - Config: testAccNsxtPolicySecurityPolicyBasic(updatedName, comments2, defaultDomain, withContext), + Config: testAccNsxtPolicySecurityPolicyBasic(resourceName, updatedName, comments2, defaultDomain, withContext), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), @@ -81,7 +82,7 @@ func testAccResourceNsxtPolicySecurityPolicyBasic(t *testing.T, withContext bool ), }, { - Config: testAccNsxtPolicySecurityPolicyWithRule(updatedName, direction1, proto1, tag1, defaultDomain, "", withContext), + Config: testAccNsxtPolicySecurityPolicyWithRule(resourceName, updatedName, direction1, proto1, tag1, defaultDomain, "", withContext), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), @@ -105,7 +106,7 @@ func testAccResourceNsxtPolicySecurityPolicyBasic(t *testing.T, withContext bool ), }, { - Config: testAccNsxtPolicySecurityPolicyWithRule(updatedName, direction2, proto2, tag2, defaultDomain, "", withContext), + Config: testAccNsxtPolicySecurityPolicyWithRule(resourceName, updatedName, direction2, proto2, tag2, defaultDomain, "", withContext), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), @@ -128,7 +129,7 @@ func testAccResourceNsxtPolicySecurityPolicyBasic(t *testing.T, withContext bool ), }, { - Config: testAccNsxtPolicySecurityPolicyWithProfiles(updatedName, direction2, proto2, tag2, defaultDomain, withContext), + Config: testAccNsxtPolicySecurityPolicyWithProfiles(resourceName, updatedName, direction2, proto2, tag2, defaultDomain, withContext), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), @@ -425,7 +426,8 @@ func TestAccResourceNsxtPolicySecurityPolicy_withIPCidrRange(t *testing.T) { func TestAccResourceNsxtPolicySecurityPolicy_importBasic(t *testing.T) { name := getAccTestResourceName() - testResourceName := "nsxt_policy_security_policy.test" + resourceName := "nsxt_policy_security_policy" + testResourceName := fmt.Sprintf("%s.test", resourceName) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -435,7 +437,7 @@ func TestAccResourceNsxtPolicySecurityPolicy_importBasic(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccNsxtPolicySecurityPolicyBasic(name, "import", defaultDomain, false), + Config: testAccNsxtPolicySecurityPolicyBasic(resourceName, name, "import", defaultDomain, false), }, { ResourceName: testResourceName, @@ -448,7 +450,8 @@ func TestAccResourceNsxtPolicySecurityPolicy_importBasic(t *testing.T) { func TestAccResourceNsxtPolicySecurityPolicy_importBasic_multitenancy(t *testing.T) { name := getAccTestResourceName() - testResourceName := "nsxt_policy_security_policy.test" + resourceName := "nsxt_policy_security_policy" + testResourceName := fmt.Sprintf("%s.test", resourceName) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -458,7 +461,7 @@ func TestAccResourceNsxtPolicySecurityPolicy_importBasic_multitenancy(t *testing }, Steps: []resource.TestStep{ { - Config: testAccNsxtPolicySecurityPolicyBasic(name, "import", defaultDomain, true), + Config: testAccNsxtPolicySecurityPolicyBasic(resourceName, name, "import", defaultDomain, true), }, { ResourceName: testResourceName, @@ -473,7 +476,8 @@ func TestAccResourceNsxtPolicySecurityPolicy_importBasic_multitenancy(t *testing func TestAccResourceNsxtGlobalPolicySecurityPolicy_withSite(t *testing.T) { name := getAccTestResourceName() updatedName := getAccTestResourceName() - testResourceName := "nsxt_policy_security_policy.test" + resourceName := "nsxt_policy_security_policy" + testResourceName := fmt.Sprintf("%s.test", resourceName) comments1 := "Acceptance test create" comments2 := "Acceptance test update" direction1 := "IN" @@ -496,7 +500,7 @@ func TestAccResourceNsxtGlobalPolicySecurityPolicy_withSite(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccNsxtPolicySecurityPolicyBasic(name, comments1, domain, false), + Config: testAccNsxtPolicySecurityPolicyBasic(resourceName, name, comments1, domain, false), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, domain), resource.TestCheckResourceAttr(testResourceName, "display_name", name), @@ -514,7 +518,7 @@ func TestAccResourceNsxtGlobalPolicySecurityPolicy_withSite(t *testing.T) { ), }, { - Config: testAccNsxtPolicySecurityPolicyBasic(updatedName, comments2, domain, false), + Config: testAccNsxtPolicySecurityPolicyBasic(resourceName, updatedName, comments2, domain, false), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, domain), resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), @@ -531,7 +535,7 @@ func TestAccResourceNsxtGlobalPolicySecurityPolicy_withSite(t *testing.T) { ), }, { - Config: testAccNsxtPolicySecurityPolicyWithRule(updatedName, direction1, proto1, tag1, domain, "", false), + Config: testAccNsxtPolicySecurityPolicyWithRule(resourceName, updatedName, direction1, proto1, tag1, domain, "", false), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, domain), resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), @@ -555,7 +559,7 @@ func TestAccResourceNsxtGlobalPolicySecurityPolicy_withSite(t *testing.T) { ), }, { - Config: testAccNsxtPolicySecurityPolicyWithRule(updatedName, direction2, proto2, tag2, domain, "", false), + Config: testAccNsxtPolicySecurityPolicyWithRule(resourceName, updatedName, direction2, proto2, tag2, domain, "", false), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, domain), resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), @@ -579,7 +583,7 @@ func TestAccResourceNsxtGlobalPolicySecurityPolicy_withSite(t *testing.T) { ), }, { - Config: testAccNsxtPolicySecurityPolicyWithProfiles(updatedName, direction2, proto2, tag2, domain, false), + Config: testAccNsxtPolicySecurityPolicyWithProfiles(resourceName, updatedName, direction2, proto2, tag2, domain, false), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicySecurityPolicyExists(testResourceName, domain), resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), @@ -652,14 +656,14 @@ func testAccNsxtPolicySecurityPolicyCheckDestroy(state *terraform.State, display return nil } -func testAccNsxtPolicySecurityPolicyBasic(name string, comments string, domainName string, withContext bool) string { +func testAccNsxtPolicySecurityPolicyBasic(resourceName, name, comments, domainName string, withContext bool) string { context := "" if withContext { context = testAccNsxtPolicyMultitenancyContext() } if domainName == defaultDomain { return fmt.Sprintf(` -resource "nsxt_policy_security_policy" "test" { +resource "%s" "test" { %s display_name = "%s" description = "Acceptance Test" @@ -675,10 +679,10 @@ resource "nsxt_policy_security_policy" "test" { tag = "orange" } -}`, context, name, comments) +}`, resourceName, context, name, comments) } return testAccNsxtGlobalPolicySite(domainName) + fmt.Sprintf(` -resource "nsxt_policy_security_policy" "test" { +resource "%s" "test" { %s display_name = "%s" description = "Acceptance Test" @@ -695,17 +699,17 @@ resource "nsxt_policy_security_policy" "test" { tag = "orange" } -}`, context, name, comments) +}`, resourceName, context, name, comments) } -func testAccNsxtPolicySecurityPolicyWithRule(name string, direction string, protocol string, ruleTag string, domainName string, profiles string, withContext bool) string { +func testAccNsxtPolicySecurityPolicyWithRule(resourceName, name, direction, protocol, ruleTag, domainName, profiles string, withContext bool) string { context := "" if withContext { context = testAccNsxtPolicyMultitenancyContext() } if domainName == defaultDomain { return fmt.Sprintf(` -resource "nsxt_policy_security_policy" "test" { +resource "%s" "test" { %s display_name = "%s" description = "Acceptance Test" @@ -732,10 +736,10 @@ resource "nsxt_policy_security_policy" "test" { } %s } -}`, context, name, name, direction, protocol, ruleTag, profiles) +}`, resourceName, context, name, name, direction, protocol, ruleTag, profiles) } return testAccNsxtGlobalPolicyGroupIPAddressCreateTemplate("group", domainName) + fmt.Sprintf(` -resource "nsxt_policy_security_policy" "test" { +resource "%s" "test" { %s display_name = "%s" description = "Acceptance Test" @@ -764,7 +768,7 @@ resource "nsxt_policy_security_policy" "test" { } %s } -}`, context, name, name, direction, protocol, ruleTag, profiles) +}`, resourceName, context, name, name, direction, protocol, ruleTag, profiles) } func testAccNsxtPolicySecurityPolicyWithEthernetRule(name string, direction string, protocol string, ruleTag string, domainName string, profiles string) string { @@ -989,9 +993,9 @@ func testAccNsxtPolicySecurityPolicyWithIPCidrRange(name string, destIP string, }`, name, destIP, destCidr, destIPRange, sourceIP, sourceCidr, sourceIPRange) } -func testAccNsxtPolicySecurityPolicyWithProfiles(name string, direction string, protocol string, ruleTag string, domainName string, withContext bool) string { +func testAccNsxtPolicySecurityPolicyWithProfiles(resourceName, name, direction, protocol, ruleTag, domainName string, withContext bool) string { profiles := ` profiles = [nsxt_policy_context_profile.test.path] ` - return testAccNsxtPolicyContextProfileTemplate("security-policy-test-profile", testAccNsxtPolicyContextProfileAttributeDomainNameTemplate(testSystemDomainName), withContext) + testAccNsxtPolicySecurityPolicyWithRule(name, direction, protocol, ruleTag, domainName, profiles, withContext) + return testAccNsxtPolicyContextProfileTemplate("security-policy-test-profile", testAccNsxtPolicyContextProfileAttributeDomainNameTemplate(testSystemDomainName), withContext) + testAccNsxtPolicySecurityPolicyWithRule(resourceName, name, direction, protocol, ruleTag, domainName, profiles, withContext) } diff --git a/nsxt/resource_nsxt_vpc_security_policy.go b/nsxt/resource_nsxt_vpc_security_policy.go new file mode 100644 index 000000000..2f300174d --- /dev/null +++ b/nsxt/resource_nsxt_vpc_security_policy.go @@ -0,0 +1,55 @@ +/* Copyright © 2024 Broadcom, 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/terraform-provider-nsxt/api/infra/domains" +) + +func resourceNsxtVPCSecurityPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVPCSecurityPolicyCreate, + Read: resourceNsxtVPCSecurityPolicyRead, + Update: resourceNsxtVPCSecurityPolicyUpdate, + Delete: resourceNsxtVPCSecurityPolicyDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathResourceImporter, + }, + Schema: getPolicySecurityPolicySchema(false, true, true, false), + } +} + +func resourceNsxtVPCSecurityPolicyCreate(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralCreate(d, m, true, false) +} + +func resourceNsxtVPCSecurityPolicyRead(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralRead(d, m, true, false) +} + +func resourceNsxtVPCSecurityPolicyUpdate(d *schema.ResourceData, m interface{}) error { + return resourceNsxtPolicySecurityPolicyGeneralUpdate(d, m, true, false) +} + +func resourceNsxtVPCSecurityPolicyDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VPC Security Policy id") + } + + connector := getPolicyConnector(m) + + client := domains.NewSecurityPoliciesClient(getSessionContext(d, m), connector) + err := client.Delete("", id) + + if err != nil { + return handleDeleteError("Security Policy", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_security_policy_test.go b/nsxt/resource_nsxt_vpc_security_policy_test.go new file mode 100644 index 000000000..78386c1c8 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_security_policy_test.go @@ -0,0 +1,194 @@ +/* Copyright © 2024 Broadcom, 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 TestAccResourceNsxtVPCSecurityPolicy_basic(t *testing.T) { + name := getAccTestResourceName() + updatedName := getAccTestResourceName() + resourceName := "nsxt_vpc_security_policy" + testResourceName := fmt.Sprintf("%s.test", resourceName) + comments1 := "Acceptance test create" + comments2 := "Acceptance test update" + direction1 := "IN" + direction2 := "OUT" + proto1 := "IPV4" + proto2 := "IPV4_IPV6" + defaultAction := "ALLOW" + tag1 := "abc" + tag2 := "def" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVPCSecurityPolicyCheckDestroy(state, updatedName) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicySecurityPolicyBasic(resourceName, name, comments1, defaultDomain, true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), + resource.TestCheckResourceAttr(testResourceName, "display_name", name), + resource.TestCheckResourceAttr(testResourceName, "description", "Acceptance Test"), + resource.TestCheckResourceAttr(testResourceName, "category", "Application"), + resource.TestCheckResourceAttr(testResourceName, "comments", comments1), + resource.TestCheckResourceAttr(testResourceName, "locked", "true"), + resource.TestCheckResourceAttr(testResourceName, "scope.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", "3"), + resource.TestCheckResourceAttr(testResourceName, "stateful", "true"), + resource.TestCheckResourceAttr(testResourceName, "tcp_strict", "false"), + resource.TestCheckResourceAttr(testResourceName, "rule.#", "0"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + ), + }, + { + Config: testAccNsxtPolicySecurityPolicyBasic(resourceName, updatedName, comments2, defaultDomain, true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), + resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), + resource.TestCheckResourceAttr(testResourceName, "description", "Acceptance Test"), + resource.TestCheckResourceAttr(testResourceName, "category", "Application"), + resource.TestCheckResourceAttr(testResourceName, "comments", comments2), + resource.TestCheckResourceAttr(testResourceName, "locked", "true"), + resource.TestCheckResourceAttr(testResourceName, "scope.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", "3"), + resource.TestCheckResourceAttr(testResourceName, "stateful", "true"), + resource.TestCheckResourceAttr(testResourceName, "tcp_strict", "false"), + resource.TestCheckResourceAttr(testResourceName, "rule.#", "0"), + ), + }, + { + Config: testAccNsxtPolicySecurityPolicyWithRule(resourceName, updatedName, direction1, proto1, tag1, defaultDomain, "", true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), + resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), + resource.TestCheckResourceAttr(testResourceName, "description", "Acceptance Test"), + resource.TestCheckResourceAttr(testResourceName, "category", "Application"), + resource.TestCheckResourceAttr(testResourceName, "comments", ""), + resource.TestCheckResourceAttr(testResourceName, "locked", "false"), + resource.TestCheckResourceAttr(testResourceName, "scope.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", "3"), + resource.TestCheckResourceAttr(testResourceName, "stateful", "true"), + resource.TestCheckResourceAttr(testResourceName, "tcp_strict", "false"), + resource.TestCheckResourceAttr(testResourceName, "rule.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "rule.0.display_name", updatedName), + resource.TestCheckResourceAttr(testResourceName, "rule.0.direction", direction1), + resource.TestCheckResourceAttr(testResourceName, "rule.0.ip_version", proto1), + resource.TestCheckResourceAttr(testResourceName, "rule.0.action", defaultAction), + resource.TestCheckResourceAttr(testResourceName, "rule.0.log_label", tag1), + resource.TestCheckResourceAttrSet(testResourceName, "rule.0.rule_id"), + resource.TestCheckResourceAttr(testResourceName, "rule.0.tag.#", "1"), + ), + }, + { + Config: testAccNsxtPolicySecurityPolicyWithRule(resourceName, updatedName, direction2, proto2, tag2, defaultDomain, "", true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), + resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), + resource.TestCheckResourceAttr(testResourceName, "description", "Acceptance Test"), + resource.TestCheckResourceAttr(testResourceName, "category", "Application"), + resource.TestCheckResourceAttr(testResourceName, "comments", ""), + resource.TestCheckResourceAttr(testResourceName, "locked", "false"), + resource.TestCheckResourceAttr(testResourceName, "scope.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", "3"), + resource.TestCheckResourceAttr(testResourceName, "stateful", "true"), + resource.TestCheckResourceAttr(testResourceName, "tcp_strict", "false"), + resource.TestCheckResourceAttr(testResourceName, "rule.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "rule.0.display_name", updatedName), + resource.TestCheckResourceAttr(testResourceName, "rule.0.direction", direction2), + resource.TestCheckResourceAttr(testResourceName, "rule.0.ip_version", proto2), + resource.TestCheckResourceAttr(testResourceName, "rule.0.action", defaultAction), + resource.TestCheckResourceAttr(testResourceName, "rule.0.log_label", tag2), + resource.TestCheckResourceAttr(testResourceName, "rule.0.tag.#", "1"), + ), + }, + // TODO: For now, creation od the context profile with VPC context crashes the provider. This should be addressed in the + // generated wrappers (check that there is an implementation in VPC (or whatever) context. + // Then, the context profile should be created in the project context, shared to the VPC (no sharing capability yet in TF) + // to enable testing functionality below. + // + //{ + // Config: testAccNsxtPolicySecurityPolicyWithProfiles(resourceName, updatedName, direction2, proto2, tag2, defaultDomain, true), + // Check: resource.ComposeTestCheckFunc( + // testAccNsxtPolicySecurityPolicyExists(testResourceName, defaultDomain), + // resource.TestCheckResourceAttr(testResourceName, "display_name", updatedName), + // resource.TestCheckResourceAttr(testResourceName, "description", "Acceptance Test"), + // resource.TestCheckResourceAttr(testResourceName, "category", "Application"), + // resource.TestCheckResourceAttr(testResourceName, "comments", ""), + // resource.TestCheckResourceAttr(testResourceName, "locked", "false"), + // resource.TestCheckResourceAttr(testResourceName, "scope.#", "0"), + // resource.TestCheckResourceAttr(testResourceName, "sequence_number", "3"), + // resource.TestCheckResourceAttr(testResourceName, "stateful", "true"), + // resource.TestCheckResourceAttr(testResourceName, "tcp_strict", "false"), + // resource.TestCheckResourceAttr(testResourceName, "rule.#", "1"), + // resource.TestCheckResourceAttr(testResourceName, "rule.0.display_name", updatedName), + // resource.TestCheckResourceAttr(testResourceName, "rule.0.direction", direction2), + // resource.TestCheckResourceAttr(testResourceName, "rule.0.ip_version", proto2), + // resource.TestCheckResourceAttr(testResourceName, "rule.0.action", defaultAction), + // resource.TestCheckResourceAttr(testResourceName, "rule.0.log_label", tag2), + // resource.TestCheckResourceAttr(testResourceName, "rule.0.tag.#", "1"), + // resource.TestCheckResourceAttr(testResourceName, "rule.0.profiles.#", "1"), + // ), + //}, + }, + }) +} + +func testAccNsxtVPCSecurityPolicyCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_security_policy" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtPolicySecurityPolicyExistsInDomain(testAccGetSessionContext(), resourceID, "", connector) + if err != nil { + return err + } + if exists { + return fmt.Errorf("VPC SecurityPolicy %s still exists", displayName) + } + } + return nil +} + +func TestAccResourceNsxtVPCSecurityPolicy_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_security_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicySecurityPolicyCheckDestroy(state, name, defaultDomain) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicySecurityPolicyBasic("nsxt_vpc_security_policy", name, "import", defaultDomain, true), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} diff --git a/nsxt/utils_test.go b/nsxt/utils_test.go index 9a118df83..71677eb82 100644 --- a/nsxt/utils_test.go +++ b/nsxt/utils_test.go @@ -322,6 +322,13 @@ func testAccOnlyMultitenancy(t *testing.T) { } } +func testAccOnlyVPC(t *testing.T) { + testAccNSXVersion(t, "4.1.2") + if !testAccIsVPC() { + t.Skipf("This test requires a VPC environment") + } +} + func testAccNSXGlobalManagerSitePrecheck(t *testing.T) { if testAccIsGlobalManager() && getTestSiteName() == "" { str := fmt.Sprintf("%s must be set for this acceptance test", "NSXT_TEST_SITE_NAME") diff --git a/website/docs/r/vpc_security_policy.html.markdown b/website/docs/r/vpc_security_policy.html.markdown new file mode 100644 index 000000000..e78264031 --- /dev/null +++ b/website/docs/r/vpc_security_policy.html.markdown @@ -0,0 +1,131 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_security_policy" +description: A resource to configure a Security Group and its rules within a VPC. +--- + +# nsxt_vpc_security_policy + +This resource provides a method for the management of a VPC Security Policy and rules under it. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_policy_vpc" "demovpc" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "vpc1" +} + +resource "nsxt_vpc_security_policy" "policy1" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_policy_vpc.demovpc.id + } + display_name = "policy1" + description = "Terraform provisioned Security Policy" + category = "Application" + locked = false + stateful = true + tcp_strict = false + scope = [nsxt_policy_group.pets.path] + + rule { + display_name = "block_icmp" + destination_groups = [nsxt_policy_group.cats.path, nsxt_policy_group.dogs.path] + action = "DROP" + services = [nsxt_policy_service.icmp.path] + logged = true + } + + rule { + display_name = "allow_udp" + source_groups = [nsxt_policy_group.fish.path] + sources_excluded = true + scope = [nsxt_policy_group.aquarium.path] + action = "ALLOW" + services = [nsxt_policy_service.udp.path] + logged = true + disabled = true + notes = "Disabled by starfish for debugging" + } + + lifecycle { + create_before_destroy = true + } +} +``` + +-> 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. +* `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` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + * `vpc_id` - (Required) The ID of the VPC 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. +* `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. +* `rule` - (Optional) A repeatable block to specify rules for the Security Policy. Each rule includes the following fields: + * `display_name` - (Required) Display name of the resource. + * `description` - (Optional) Description of the resource. + * `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. + * `tag` - (Optional) A list of scope + tag pairs to associate with this Rule. + * `sequence_number` - (Optional) It is recommended not to specify sequence number for rules, and rely on provider to auto-assign them. If you choose to specify sequence numbers, you must make sure the numbers are consistent with order of the rules in configuration. Please note that sequence numbers should start with 1 and not 0. To avoid confusion, either specify sequence numbers in all rules, or none at all. + + +## 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. +* `rule`: + * `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. + * `sequence_number` - Sequence number for the rule. + * `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_vpc_security_policy.policy1 PATH +``` + +The above command imports the VPC security policy named `policy1` with the NSX Policy path `PATH`.