Skip to content

Commit

Permalink
ENG-12192: Add service account resource (#453)
Browse files Browse the repository at this point in the history
* Add client as parameter to ReadFromSchema and WriteToSchema

* Add service account resource

* Add small code enhancements

* Add resource docs

* Remove import from service account resource

* Add tests

* Add examples to docs

* Add cyral_permission data source

* Update service account resource to use permission IDs instead

* Revert changes to ReadFromSchema and WriteToSchema interfaces

* Register the data source

* Rename generic type parameter

* Refactor order of permissions

* Remove unnecessary filter from permission data source

* Change permission_ids to TypeSet and update tests

* Add tests for data source permission

* Update docs and add examples

* Update tests
  • Loading branch information
VictorGFM authored Sep 26, 2023
1 parent 634c2d3 commit eaac6bc
Show file tree
Hide file tree
Showing 19 changed files with 874 additions and 88 deletions.
77 changes: 77 additions & 0 deletions cyral/data_source_cyral_permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cyral

import (
"fmt"
"net/http"

"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/cyralinc/terraform-provider-cyral/client"
)

const (
// Schema keys
PermissionDataSourcePermissionListKey = "permission_list"
)

type PermissionDataSourceResponse struct {
// Permissions correspond to Roles in API.
Permissions []Permission `json:"roles"`
}

func (response *PermissionDataSourceResponse) WriteToSchema(d *schema.ResourceData) error {
d.SetId(uuid.New().String())
d.Set(PermissionDataSourcePermissionListKey, permissionsToInterfaceList(response.Permissions))
return nil
}

func dataSourcePermission() *schema.Resource {
return &schema.Resource{
Description: "Retrieve all Cyral permissions. See also resource " +
"[`cyral_service_account`](../resources/service_account.md).",
ReadContext: ReadResource(
ResourceOperationConfig{
Name: "PermissionDataSourceRead",
HttpMethod: http.MethodGet,
CreateURL: func(d *schema.ResourceData, c *client.Client) string {
return fmt.Sprintf("https://%s/v1/users/roles", c.ControlPlane)
},
NewResponseData: func(d *schema.ResourceData) ResponseData {
return &PermissionDataSourceResponse{}
},
},
),
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The data source identifier.",
Type: schema.TypeString,
Computed: true,
},
PermissionDataSourcePermissionListKey: {
Description: "List of all existing Cyral permissions.",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
IDKey: {
Description: "Permission identifier.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "Permission name.",
Type: schema.TypeString,
Computed: true,
},
DescriptionKey: {
Description: "Permission description.",
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
}
}
62 changes: 62 additions & 0 deletions cyral/data_source_cyral_permission_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cyral

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccPermissionDataSource(t *testing.T) {
testSteps := []resource.TestStep{}
dataSourceName1 := "permissions_1"
testSteps = append(
testSteps,
[]resource.TestStep{
accTestStepPermissionDataSource_RetrieveAllPermissions(dataSourceName1),
}...,
)

resource.ParallelTest(t, resource.TestCase{
ProviderFactories: providerFactories,
Steps: testSteps,
})
}

func accTestStepPermissionDataSource_RetrieveAllPermissions(dataSourceName string) resource.TestStep {
dataSourceFullName := fmt.Sprintf("data.cyral_permission.%s", dataSourceName)
config := fmt.Sprintf(`
data "cyral_permission" "%s" {
}
`, dataSourceName)
var checks []resource.TestCheckFunc
for index, expectedPermissionName := range allPermissionNames {
checks = append(checks,
[]resource.TestCheckFunc{
resource.TestCheckResourceAttrSet(
dataSourceFullName,
fmt.Sprintf(
"%s.%d.%s",
PermissionDataSourcePermissionListKey,
index,
IDKey,
),
),
resource.TestCheckTypeSetElemNestedAttrs(
dataSourceFullName,
fmt.Sprintf("%s.*", PermissionDataSourcePermissionListKey),
map[string]string{NameKey: expectedPermissionName},
),
resource.TestCheckTypeSetElemNestedAttrs(
dataSourceFullName,
fmt.Sprintf("%s.*", PermissionDataSourcePermissionListKey),
map[string]string{DescriptionKey: expectedPermissionName},
),
}...,
)
}
return resource.TestStep{
Config: config,
Check: resource.ComposeTestCheckFunc(checks...),
}
}
139 changes: 139 additions & 0 deletions cyral/model_permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package cyral

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type Permission struct {
Id string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}

func permissionsToInterfaceList(permissions []Permission) []any {
permissionsInterfaceList := make([]any, len(permissions))
for index, permission := range permissions {
permissionsInterfaceList[index] = map[string]any{
IDKey: permission.Id,
NameKey: permission.Name,
DescriptionKey: permission.Description,
}
}
return permissionsInterfaceList
}

var allPermissionNames = []string{
"Approval Management",
"Modify Policies",
"Modify Roles",
"Modify Sidecars and Repositories",
"Modify Users",
"Repo Crawler",
"View Audit Logs",
"View Datamaps",
"View Integrations",
"View Policies",
"View Roles",
"View Users",
"Modify Integrations",
}

const (
// Schema keys
approvalManagementPermissionKey = "approval_management"
modifyPoliciesPermissionKey = "modify_policies"
modifyRolesPermissionKey = "modify_roles"
modifySidecarAndRepositoriesPermissionKey = "modify_sidecars_and_repositories"
modifyUsersPermissionKey = "modify_users"
repoCrawlerPermissionKey = "repo_crawler"
viewAuditLogsPermissionKey = "view_audit_logs"
viewDatamapsPermissionKey = "view_datamaps"
viewIntegrationsPermissionKey = "view_integrations"
viewPoliciesPermissionKey = "view_policies"
viewRolesPermissionKey = "view_roles"
viewUsersPermissionKey = "view_users"
modifyIntegrationsPermissionKey = "modify_integrations"
)

var permissionsSchema = map[string]*schema.Schema{
approvalManagementPermissionKey: {
Description: "Allows approving or denying approval requests on Cyral Control Plane. " +
"Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
modifyPoliciesPermissionKey: {
Description: "Allows modifying policies on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
modifyRolesPermissionKey: {
Description: "Allows modifying roles on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
modifySidecarAndRepositoriesPermissionKey: {
Description: "Allows modifying sidecars and repositories on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
modifyUsersPermissionKey: {
Description: "Allows modifying users on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
repoCrawlerPermissionKey: {
Description: "Allows running the Cyral repo crawler data classifier and user discovery. " +
"Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
viewAuditLogsPermissionKey: {
Description: "Allows viewing audit logs on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
viewDatamapsPermissionKey: {
Description: "Allows viewing datamaps on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
viewIntegrationsPermissionKey: {
Description: "Allows viewing integrations on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
viewPoliciesPermissionKey: {
Description: "Allows viewing policies on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
viewRolesPermissionKey: {
Description: "Allows viewing roles on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
viewUsersPermissionKey: {
Description: "Allows viewing users on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
modifyIntegrationsPermissionKey: {
Description: "Allows modifying integrations on Cyral Control Plane. Defaults to `false`.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
}
39 changes: 39 additions & 0 deletions cyral/model_service_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cyral

import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type ServiceAccount struct {
DisplayName string `json:"displayName"`
ClientID string `json:"clientId,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
// Permissions correspond to Roles in Cyral APIs.
PermissionIDs []string `json:"roleIds"`
}

func (serviceAccount *ServiceAccount) ReadFromSchema(d *schema.ResourceData) error {
serviceAccount.DisplayName = d.Get(serviceAccountResourceDisplayNameKey).(string)
permissionIDs := convertFromInterfaceList[string](
d.Get(serviceAccountResourcePermissionIDsKey).(*schema.Set).List(),
)
if len(permissionIDs) == 0 {
return fmt.Errorf("at least one permission must be specified for the service account")
}
serviceAccount.PermissionIDs = permissionIDs
return nil
}

func (serviceAccount *ServiceAccount) WriteToSchema(d *schema.ResourceData) error {
d.SetId(serviceAccount.ClientID)
d.Set(serviceAccountResourceDisplayNameKey, serviceAccount.DisplayName)
d.Set(serviceAccountResourceClientIDKey, serviceAccount.ClientID)
isCreateResponse := serviceAccount.ClientSecret != ""
if isCreateResponse {
d.Set(serviceAccountResourceClientSecretKey, serviceAccount.ClientSecret)
}
d.Set(serviceAccountResourcePermissionIDsKey, convertToInterfaceList(serviceAccount.PermissionIDs))
return nil
}
4 changes: 3 additions & 1 deletion cyral/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func Provider() *schema.Provider {
"cyral_integration_idp": dataSourceIntegrationIdP(),
"cyral_integration_idp_saml": dataSourceIntegrationIdPSAML(),
"cyral_integration_logging": dataSourceIntegrationLogging(),
"cyral_permission": dataSourcePermission(),
"cyral_repository": dataSourceRepository(),
"cyral_role": dataSourceRole(),
"cyral_saml_certificate": dataSourceSAMLCertificate(),
Expand Down Expand Up @@ -122,6 +123,7 @@ func Provider() *schema.Provider {
"cyral_repository_access_gateway": resourceRepositoryAccessGateway(),
"cyral_role": resourceRole(),
"cyral_role_sso_groups": resourceRoleSSOGroups(),
"cyral_service_account": resourceServiceAccount(),
"cyral_sidecar": resourceSidecar(),
"cyral_sidecar_credentials": resourceSidecarCredentials(),
"cyral_sidecar_listener": resourceSidecarListener(),
Expand All @@ -130,7 +132,7 @@ func Provider() *schema.Provider {
}
}

func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
func providerConfigure(ctx context.Context, d *schema.ResourceData) (any, diag.Diagnostics) {
log.Printf("[DEBUG] Init providerConfigure")

clientID, clientSecret, diags := getCredentials(d)
Expand Down
6 changes: 3 additions & 3 deletions cyral/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type ResourceOperationConfig struct {
NewResponseData func(d *schema.ResourceData) ResponseData
}

func CRUDResources(resourceOperations []ResourceOperation) func(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics {
func CRUDResources(resourceOperations []ResourceOperation) func(context.Context, *schema.ResourceData, any) diag.Diagnostics {
return HandleRequests(resourceOperations)
}

Expand Down Expand Up @@ -106,8 +106,8 @@ func DeleteResource(deleteConfig ResourceOperationConfig) schema.DeleteContextFu

func HandleRequests(
resourceOperations []ResourceOperation,
) func(context.Context, *schema.ResourceData, interface{}) diag.Diagnostics {
return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
) func(context.Context, *schema.ResourceData, any) diag.Diagnostics {
return func(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
for _, operation := range resourceOperations {
log.Printf("[DEBUG] Init %s", operation.Config.Name)
c := m.(*client.Client)
Expand Down
Loading

0 comments on commit eaac6bc

Please sign in to comment.