diff --git a/nsxt/provider.go b/nsxt/provider.go index 299f607c7..43dec2f7e 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -426,6 +426,7 @@ func Provider() *schema.Provider { "nsxt_manager_cluster": resourceNsxtManagerCluster(), "nsxt_policy_uplink_host_switch_profile": resourceNsxtUplinkHostSwitchProfile(), "nsxt_node_user": resourceNsxtUsers(), + "nsxt_principle_identity": resourceNsxtPrincipleIdentity(), "nsxt_transport_node": resourceNsxtTransportNode(), "nsxt_failure_domain": resourceNsxtFailureDomain(), "nsxt_cluster_virtual_ip": resourceNsxtClusterVirualIP(), diff --git a/nsxt/resource_nsxt_policy_role_binding.go b/nsxt/resource_nsxt_policy_role_binding.go index 41837780c..38befc77a 100644 --- a/nsxt/resource_nsxt_policy_role_binding.go +++ b/nsxt/resource_nsxt_policy_role_binding.go @@ -6,7 +6,6 @@ package nsxt import ( "fmt" "log" - "regexp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -14,12 +13,10 @@ import ( nsxModel "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" ) -// Only support local user at the moment var roleBindingUserTypes = [](string){ nsxModel.RoleBinding_TYPE_LOCAL_USER, nsxModel.RoleBinding_TYPE_REMOTE_USER, nsxModel.RoleBinding_TYPE_REMOTE_GROUP, - nsxModel.RoleBinding_TYPE_PRINCIPAL_IDENTITY, } var roleBindingIdentitySourceTypes = [](string){ @@ -71,40 +68,31 @@ func resourceNsxtPolicyUserManagementRoleBinding() *schema.Resource { Optional: true, ValidateFunc: validation.StringInSlice(roleBindingIdentitySourceTypes, false), }, - "roles_for_path": { - Type: schema.TypeList, - Description: "List of roles that are associated with the user, limiting them to a path", - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "path": { - Type: schema.TypeString, - Description: "Path of the entity in parent hierarchy.", - Required: true, - }, - "role": { - Type: schema.TypeList, - Description: "Applicable roles", - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "role": { - Type: schema.TypeString, - Description: "Short identifier for the role", - Required: true, - ValidateFunc: validation.StringMatch( - regexp.MustCompile( - `^[_a-z0-9-]+$`), - "Must be a valid role identifier matching: ^[_a-z0-9-]+$"), - }, - "role_display_name": { - Type: schema.TypeString, - Description: "Display name for role", - Computed: true, - }, - }, - }, - }, + "roles_for_path": getRolesForPathSchema(false), + }, + } +} + +// getRolesForPathSchema return schema for RolesForPath, which is shared between role bindings and PI +func getRolesForPathSchema(forceNew bool) *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Description: "List of roles that are associated with the user, limiting them to a path", + Required: true, + ForceNew: forceNew, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Description: "Path of the entity in parent hierarchy.", + Required: true, + }, + "roles": { + Type: schema.TypeSet, + Description: "Applicable roles", + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, }, }, }, @@ -121,12 +109,10 @@ func getRolesForPathFromSchema(d *schema.ResourceData) rolesForPath { for _, rolesPerPathInput := range rolesForPathInput { data := rolesPerPathInput.(map[string]interface{}) path := data["path"].(string) - roles := data["role"].([]interface{}) + roles := interface2StringList(data["roles"].(*schema.Set).List()) rolesPerPathMap := make(rolesPerPath) for _, role := range roles { - roleData := role.(map[string]interface{}) - roleInput := roleData["role"].(string) - rolesPerPathMap[roleInput] = true + rolesPerPathMap[role] = true } rolesForPathMap[path] = rolesPerPathMap } @@ -139,14 +125,11 @@ func setRolesForPathInSchema(d *schema.ResourceData, nsxRolesForPathList []nsxMo for _, nsxRolesForPath := range nsxRolesForPathList { elem := make(map[string]interface{}) elem["path"] = nsxRolesForPath.Path - var roles []map[string]interface{} + roles := make([]string, 0, len(nsxRolesForPath.Roles)) for _, nsxRole := range nsxRolesForPath.Roles { - rElem := make(map[string]interface{}) - rElem["role"] = nsxRole.Role - rElem["role_display_name"] = nsxRole.RoleDisplayName - roles = append(roles, rElem) + roles = append(roles, *nsxRole.Role) } - elem["role"] = roles + elem["roles"] = roles rolesForPathList = append(rolesForPathList, elem) } err := d.Set("roles_for_path", rolesForPathList) @@ -155,15 +138,8 @@ func setRolesForPathInSchema(d *schema.ResourceData, nsxRolesForPathList []nsxMo } } -func getRoleBindingObject(d *schema.ResourceData) *nsxModel.RoleBinding { +func getRolesForPathList(d *schema.ResourceData) []nsxModel.RolesForPath { boolTrue := true - displayName := d.Get("display_name").(string) - description := d.Get("description").(string) - tags := getPolicyTagsFromSchema(d) - name := d.Get("name").(string) - identitySrcID := d.Get("identity_source_id").(string) - identitySrcType := d.Get("identity_source_type").(string) - roleBindingType := d.Get("type").(string) rolesPerPathMap := getRolesForPathFromSchema(d) nsxRolesForPaths := make([]nsxModel.RolesForPath, 0) @@ -193,19 +169,30 @@ func getRoleBindingObject(d *schema.ResourceData) *nsxModel.RoleBinding { continue } // Add one role in the list to make NSX happy - roles := data["role"].([]interface{}) + roles := interface2StringList(data["roles"].(*schema.Set).List()) if len(roles) == 0 { continue } - roleData := roles[0].(map[string]interface{}) - roleID := roleData["role"].(string) nsxRolesForPaths = append(nsxRolesForPaths, nsxModel.RolesForPath{ Path: &path, DeletePath: &boolTrue, - Roles: []nsxModel.Role{{Role: &roleID}}, + Roles: []nsxModel.Role{{Role: &roles[0]}}, }) } } + return nsxRolesForPaths +} + +func getRoleBindingObject(d *schema.ResourceData) *nsxModel.RoleBinding { + boolTrue := true + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + name := d.Get("name").(string) + identitySrcID := d.Get("identity_source_id").(string) + identitySrcType := d.Get("identity_source_type").(string) + roleBindingType := d.Get("type").(string) + nsxRolesForPaths := getRolesForPathList(d) obj := nsxModel.RoleBinding{ DisplayName: &displayName, diff --git a/nsxt/resource_nsxt_policy_role_binding_test.go b/nsxt/resource_nsxt_policy_role_binding_test.go index ad7a2f3b9..1184fc625 100644 --- a/nsxt/resource_nsxt_policy_role_binding_test.go +++ b/nsxt/resource_nsxt_policy_role_binding_test.go @@ -50,13 +50,12 @@ func TestAccResourceNsxtPolicyRoleBinding_basic(t *testing.T) { resource.TestCheckResourceAttr(testResourceName, "identity_source_type", identType), resource.TestCheckResourceAttr(testResourceName, "roles_for_path.#", "2"), resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.path", "/"), - resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.role.#", "1"), - resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.role.0.role", "auditor"), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.roles.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.roles.0", "auditor"), resource.TestCheckResourceAttr(testResourceName, "roles_for_path.1.path", "/orgs/default"), - resource.TestCheckResourceAttr(testResourceName, "roles_for_path.1.role.#", "1"), - resource.TestCheckResourceAttr(testResourceName, "roles_for_path.1.role.0.role", "org_admin"), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.1.roles.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.1.roles.0", "org_admin"), - resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), resource.TestCheckResourceAttrSet(testResourceName, "revision"), ), }, @@ -71,10 +70,9 @@ func TestAccResourceNsxtPolicyRoleBinding_basic(t *testing.T) { resource.TestCheckResourceAttr(testResourceName, "identity_source_type", identType), resource.TestCheckResourceAttr(testResourceName, "roles_for_path.#", "1"), resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.path", "/"), - resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.role.#", "1"), - resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.role.0.role", "auditor"), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.roles.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.roles.0", "auditor"), - resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), resource.TestCheckResourceAttrSet(testResourceName, "revision"), ), }, @@ -172,19 +170,13 @@ resource "nsxt_policy_user_management_role_binding" "test" { identity_source_type = "%s" roles_for_path { - path = "/" - - role { - role = "auditor" - } + path = "/" + roles = ["auditor"] } roles_for_path { - path = "/orgs/default" - - role { - role = "org_admin" - } + path = "/orgs/default" + roles = ["org_admin"] } }`, attrMap["display_name"], attrMap["description"], user, userType, identType) } @@ -200,11 +192,8 @@ resource "nsxt_policy_user_management_role_binding" "test" { identity_source_type = "%s" roles_for_path { - path = "/" - - role { - role = "auditor" - } + path = "/" + roles = ["auditor"] } }`, attrMap["display_name"], attrMap["description"], user, userType, identType) } diff --git a/nsxt/resource_nsxt_principal_identity.go b/nsxt/resource_nsxt_principal_identity.go new file mode 100644 index 000000000..131e57c9f --- /dev/null +++ b/nsxt/resource_nsxt_principal_identity.go @@ -0,0 +1,192 @@ +/* Copyright © 2023 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + mpModel "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/trust_management" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/trust_management/principal_identities" + policyModel "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +func resourceNsxtPrincipleIdentity() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPrincipleIdentityCreate, + Read: resourceNsxtPrincipleIdentityRead, + Delete: resourceNsxtPrincipleIdentityDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "tag": getTagsSchemaForceNew(), + "is_protected": { + Type: schema.TypeBool, + Description: "Indicates whether the entities created by this principal should be protected", + Optional: true, + Default: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Description: "Name of the principal", + ValidateFunc: validatePINameOrNodeID(), + Required: true, + ForceNew: true, + }, + "node_id": { + Type: schema.TypeString, + Description: "Unique node-id of a principal", + ValidateFunc: validatePINameOrNodeID(), + Required: true, + ForceNew: true, + }, + "certificate_id": { + Type: schema.TypeString, + Description: "Id of the imported certificate pem", + Computed: true, + ForceNew: true, + }, + "certificate_pem": { + Type: schema.TypeString, + Description: "PEM encoding of the new certificate", + Required: true, + ForceNew: true, + }, + "roles_for_path": getRolesForPathSchema(true), + }, + } +} + +func validatePINameOrNodeID() schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + r := regexp.MustCompile(`^[a-zA-Z0-9]+([-._]?[a-zA-Z0-9]+)*$`) + if ok := r.MatchString(v); !ok { + es = append(es, fmt.Errorf("%s %s is invalid", k, v)) + return + } + + if len(v) < 1 || len(v) > 255 { + es = append(es, fmt.Errorf("expected length of %s to be in the range (%d - %d), got %d", k, 1, 255, len(v))) + return + } + return + } +} + +func convertToMPRolesForPath(policyRolesForPath []policyModel.RolesForPath) []mpModel.RolesForPath { + mpRolesForPath := make([]mpModel.RolesForPath, len(policyRolesForPath)) + for i, pRolesForPath := range policyRolesForPath { + mpRolesForPath[i].DeletePath = pRolesForPath.DeletePath + mpRolesForPath[i].Path = pRolesForPath.Path + mpRolesForPath[i].Roles = make([]mpModel.Role, len(pRolesForPath.Roles)) + for j, pRole := range pRolesForPath.Roles { + mpRolesForPath[i].Roles[j].Role = pRole.Role + mpRolesForPath[i].Roles[j].RoleDisplayName = pRole.RoleDisplayName + } + } + return mpRolesForPath +} + +func convertToPolicyRolesForPath(mpRolesForPath []mpModel.RolesForPath) []policyModel.RolesForPath { + pRolesForPath := make([]policyModel.RolesForPath, len(mpRolesForPath)) + for i, mRolesForPath := range mpRolesForPath { + pRolesForPath[i].DeletePath = mRolesForPath.DeletePath + pRolesForPath[i].Path = mRolesForPath.Path + pRolesForPath[i].Roles = make([]policyModel.Role, len(mRolesForPath.Roles)) + for j, mRole := range mRolesForPath.Roles { + pRolesForPath[i].Roles[j].Role = mRole.Role + pRolesForPath[i].Roles[j].RoleDisplayName = mRole.RoleDisplayName + } + } + return pRolesForPath +} + +func resourceNsxtPrincipleIdentityCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + client := principal_identities.NewWithCertificateClient(connector) + + tags := getMPTagsFromSchema(d) + isProtected := d.Get("is_protected").(bool) + name := d.Get("name").(string) + nodeID := d.Get("node_id").(string) + certificatePem := d.Get("certificate_pem").(string) + rolesForPaths := convertToMPRolesForPath(getRolesForPathList(d)) + + piObj := mpModel.PrincipalIdentityWithCertificate{ + Tags: tags, + IsProtected: &isProtected, + Name: &name, + NodeId: &nodeID, + CertificatePem: &certificatePem, + RolesForPaths: rolesForPaths, + } + + pi, err := client.Create(piObj) + if err != nil { + return handleCreateError("PrincipalIdentity", name, err) + } + d.SetId(*pi.Id) + + return resourceNsxtPrincipleIdentityRead(d, m) +} + +func resourceNsxtPrincipleIdentityRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + client := trust_management.NewPrincipalIdentitiesClient(connector) + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining logical object id") + } + piObj, err := client.Get(id) + if err != nil { + return handleReadError(d, "PrincipalIdentity", id, err) + } + + setMPTagsInSchema(d, piObj.Tags) + d.Set("is_protected", piObj.IsProtected) + d.Set("name", piObj.Name) + d.Set("node_id", piObj.NodeId) + d.Set("certificate_id", piObj.CertificateId) + setRolesForPathInSchema(d, convertToPolicyRolesForPath(piObj.RolesForPaths)) + return nil +} + +func resourceNsxtPrincipleIdentityDelete(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + piClient := trust_management.NewPrincipalIdentitiesClient(connector) + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining logical object id") + } + certID := d.Get("certificate_id").(string) + err := piClient.Delete(id) + if err != nil { + // In case PI is already deleted, do not return here to attempt cert deletion. + if handledErr := handleDeleteError("PrincipalIdentity", id, err); handledErr != nil { + return handledErr + } + } + + // Clean up underlying cert imported by NSX if exists + if len(certID) > 0 { + certClient := trust_management.NewCertificatesClient(connector) + err := certClient.Delete(certID) + if err != nil { + return handleDeleteError("Certificate", certID, err) + } + } + + return err +} diff --git a/nsxt/resource_nsxt_principal_identity_test.go b/nsxt/resource_nsxt_principal_identity_test.go new file mode 100644 index 000000000..7653e15a2 --- /dev/null +++ b/nsxt/resource_nsxt_principal_identity_test.go @@ -0,0 +1,157 @@ +/* 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" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/trust_management" +) + +var accTestPrincipleIdentityCreateAttributes = map[string]string{ + "is_protected": "false", + "name": getAccTestResourceName(), + "node_id": "node-2", + "role_path": "/orgs/default", + "role": "org_admin", +} + +func TestAccResourceNsxtPrincipleIdentity_basic(t *testing.T) { + testResourceName := "nsxt_principle_identity.test" + certPem, _, err := testAccGenerateTLSKeyPair() + if err != nil { + t.Fatal(err) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyLocalManager(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPrincipleIdentityCheckDestroy(state, accTestPrincipleIdentityCreateAttributes["name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPrincipleIdentityCreate(certPem), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPrincipleIdentityExists(accTestPrincipleIdentityCreateAttributes["name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "is_protected", accTestPrincipleIdentityCreateAttributes["is_protected"]), + resource.TestCheckResourceAttr(testResourceName, "name", accTestPrincipleIdentityCreateAttributes["name"]), + resource.TestCheckResourceAttr(testResourceName, "node_id", accTestPrincipleIdentityCreateAttributes["node_id"]), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.path", accTestPrincipleIdentityCreateAttributes["role_path"]), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.roles.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "roles_for_path.0.roles.0", accTestPrincipleIdentityCreateAttributes["role"]), + + resource.TestCheckResourceAttrSet(testResourceName, "certificate_id"), + resource.TestCheckResourceAttrSet(testResourceName, "certificate_pem"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPrincipleIdentity_import_basic(t *testing.T) { + testResourceName := "nsxt_principle_identity.test" + certPem, _, err := testAccGenerateTLSKeyPair() + if err != nil { + t.Fatal(err) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyLocalManager(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPrincipleIdentityCheckDestroy(state, accTestPrincipleIdentityCreateAttributes["name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPrincipleIdentityCreate(certPem), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"certificate_pem"}, + }, + }, + }) +} + +func testAccNsxtPrincipleIdentityExists(name string, 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("PrincipleIdentity resource %s not found in resources", resourceName) + } + + piID := rs.Primary.Attributes["id"] + if piID == "" { + return fmt.Errorf("PrincipleIdentity resource ID not set in resources") + } + tmClient := trust_management.NewPrincipalIdentitiesClient(connector) + _, err := tmClient.Get(piID) + if err != nil { + if isNotFoundError(err) { + return fmt.Errorf("PrincipleIdentity %s does not exist", name) + } + } + + return err + } +} + +func testAccNsxtPrincipleIdentityCheckDestroy(state *terraform.State, name string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_principle_identity" { + continue + } + + piID := rs.Primary.Attributes["id"] + if piID == "" { + return fmt.Errorf("PrincipleIdentity resource ID not set in resources") + } + tmClient := trust_management.NewPrincipalIdentitiesClient(connector) + _, err := tmClient.Get(piID) + if err != nil { + if isNotFoundError(err) { + return nil + } + return err + } + return fmt.Errorf("PrincipleIdentity %s still exists", name) + } + return nil +} + +func testAccNsxtPrincipleIdentityCreate(certPem string) string { + attrMap := accTestPrincipleIdentityCreateAttributes + return fmt.Sprintf(` +resource "nsxt_principle_identity" "test" { + certificate_pem = <<-EOT +%s + EOT + is_protected = %s + name = "%s" + node_id = "%s" + + roles_for_path { + path = "%s" + roles = ["%s"] + } +}`, certPem, attrMap["is_protected"], attrMap["name"], attrMap["node_id"], attrMap["role_path"], attrMap["role"]) +} diff --git a/nsxt/utils_test.go b/nsxt/utils_test.go index 0235fcd34..e5539dc3d 100644 --- a/nsxt/utils_test.go +++ b/nsxt/utils_test.go @@ -4,7 +4,15 @@ package nsxt import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + cryptorand "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" + "math/big" "math/rand" "net/http" "os" @@ -691,3 +699,45 @@ func testAccResourceNsxtPolicyImportIDRetriever(resourceID string) func(*terrafo return path, nil } } + +func testAccGenerateTLSKeyPair() (string, string, error) { + // Ref: https://go.dev/src/crypto/tls/generate_cert.go + var publicPem, privatePem string + priv, err := ecdsa.GenerateKey(elliptic.P384(), cryptorand.Reader) + if err != nil { + return publicPem, privatePem, err + } + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 180), + + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return publicPem, privatePem, err + } + buf := &bytes.Buffer{} + + if err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return publicPem, privatePem, err + } + publicPem = buf.String() + buf.Reset() + privBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return publicPem, privatePem, err + } + if err := pem.Encode(buf, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { + return publicPem, privatePem, err + } + privatePem = buf.String() + return publicPem, privatePem, nil +} diff --git a/website/docs/r/policy_user_management_role_binding.html.markdown b/website/docs/r/policy_user_management_role_binding.html.markdown index e7f7e47b5..619397784 100644 --- a/website/docs/r/policy_user_management_role_binding.html.markdown +++ b/website/docs/r/policy_user_management_role_binding.html.markdown @@ -19,20 +19,13 @@ resource "nsxt_policy_user_management_role_binding" "test" { identity_source_type = "LDAP" roles_for_path { - path = "/" - role { - role = "auditor" - } + path = "/" + roles = ["auditor"] } roles_for_path { - path = "/orgs/default" - role { - role = "org_admin" - } - role { - role = "vpc_admin" - } + path = "/orgs/default" + roles = ["org_admin", "vpc_admin"] } } ``` @@ -49,12 +42,11 @@ The following arguments are supported: * `remote_user` - This is a user which is external to NSX. * `remote_group` - This is a group of users which is external to NSX. * `local_user` - This is a user local to NSX. These are linux users. Note: Role bindings for local users are owned by NSX. Creation and deletion is not allowed for local users' binding. For updates, import existing bindings first. - * `principal_identity` - This is a principal identity user. * `identity_source_type` - (Optional) Identity source type. Applicable only to `remote_user` and `remote_group` user types. Valid options are: `VIDM`, `LDAP`, `OIDC`, `CSP`. Defaults to `VIDM` when applicable. * `identity_source_id` - (Optional) The ID of the external identity source that holds the referenced external entity. Currently, only external `LDAP` and `OIDC` servers are allowed. * `roles_for_path` - (Required) A list of The roles that are associated with the user, limiting them to a path. In case the path is '/', the roles apply everywhere. * `path` - (Required) Path of the entity in parent hierarchy. - * `role` - (Required) A list of identifiers for the roles to associate with the given user limited to a path. + * `roles` - (Required) A list of identifiers for the roles to associate with the given user limited to a path. ## Attributes Reference diff --git a/website/docs/r/principle_identity.html.markdown b/website/docs/r/principle_identity.html.markdown new file mode 100644 index 000000000..0f1a09aa8 --- /dev/null +++ b/website/docs/r/principle_identity.html.markdown @@ -0,0 +1,57 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_principle_identity" +description: A resource to configure principle identities. +--- + +# nsxt_principle_identity + +This resource provides a method for the management of Principle Identities. + +## Example Usage + +```hcl +resource "nsxt_principle_identity" "test" { + name = "open-stack" + node_id = "node-2" + certificate_pem = trimspace(file("cert.pem")) + roles_for_path { + path = "/orgs/default" + roles = ["org_admin"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `is_protected` - (optional) Indicates whether the entities created by this principal should be protected. +* `name` - (Required) Name of the principal. +* `node_id` - (Required) Unique node-id of a principal. This is used primarily in the case where a cluster of nodes is used to make calls to the NSX Manager and the same `name` is used so that the nodes can access and modify the same data while still accessing NSX through their individual secret (certificate or JWT). In all other cases this can be any string. +* `certificate_pem` - (Required) PEM encoding of the certificate to be associated with this principle identity. +* `roles_for_path` - (Required) A list of The roles that are associated with the user, limiting them to a path. In case the path is '/', the roles apply everywhere. + * `path` - (Required) Path of the entity in parent hierarchy. + * `roles` - (Required) A list of identifiers for the roles to associate with the given user limited to a path. + +Once a Principle Identity is created, it can't be modified. Modification of above arguments will cause the current PI on NSX to be deleted and recreated. Certificate updates is also handled in the same way. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `certificate_id` - NSX certificate ID of the imported `certificate_pem`. + + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_principle_identity.test PRINCIPLE_IDENTITY_ID +``` +The above command imports Principle Identity named `test` with the identifier `PRINCIPLE_IDENTITY_ID`.