From bec289128c24e375c576e23b5c3254101e03a247 Mon Sep 17 00:00:00 2001 From: Emanuele Date: Thu, 27 Jul 2023 11:59:22 +0200 Subject: [PATCH] feat(custom role) add custom role resource and permissions data source (#384) * add CustomRole entity * add permission entity * add entities to SysdigClient * add custom_role resource * add custom role resource to provider and AccTest * add data source * add documentation * fix lint * address code review -> make GetCustomRoleByName returns a pointer to CustomRole * address Monitor BE changes to new endpoints to respect rest standard * address code review comments * replace strings with constants --- sysdig/common.go | 5 + ...e_sysdig_custom_role_permissions_common.go | 67 ++++++ ..._sysdig_monitor_custom_role_permissions.go | 21 ++ ...ig_monitor_custom_role_permissions_test.go | 49 ++++ ...e_sysdig_secure_custom_role_permissions.go | 21 ++ ...dig_secure_custom_role_permissions_test.go | 43 ++++ sysdig/internal/client/v2/custom_role.go | 153 ++++++++++++ sysdig/internal/client/v2/model.go | 18 ++ sysdig/internal/client/v2/permission.go | 43 ++++ sysdig/internal/client/v2/sysdig.go | 2 + sysdig/provider.go | 29 ++- sysdig/resource_sysdig_custom_role.go | 227 ++++++++++++++++++ sysdig/resource_sysdig_custom_role_test.go | 119 +++++++++ .../docs/d/monitor_custom_role_permissions.md | 47 ++++ .../docs/d/secure_custom_role_permissions.md | 47 ++++ website/docs/r/custom_role.md | 64 +++++ 16 files changed, 942 insertions(+), 13 deletions(-) create mode 100644 sysdig/data_source_sysdig_custom_role_permissions_common.go create mode 100644 sysdig/data_source_sysdig_monitor_custom_role_permissions.go create mode 100644 sysdig/data_source_sysdig_monitor_custom_role_permissions_test.go create mode 100644 sysdig/data_source_sysdig_secure_custom_role_permissions.go create mode 100644 sysdig/data_source_sysdig_secure_custom_role_permissions_test.go create mode 100644 sysdig/internal/client/v2/custom_role.go create mode 100644 sysdig/internal/client/v2/permission.go create mode 100644 sysdig/resource_sysdig_custom_role.go create mode 100644 sysdig/resource_sysdig_custom_role_test.go create mode 100644 website/docs/d/monitor_custom_role_permissions.md create mode 100644 website/docs/d/secure_custom_role_permissions.md create mode 100644 website/docs/r/custom_role.md diff --git a/sysdig/common.go b/sysdig/common.go index 5cf1bba3..35b65b8e 100644 --- a/sysdig/common.go +++ b/sysdig/common.go @@ -33,4 +33,9 @@ const ( SchemaSystemRoleKey = "system_role" SchemaRulesKey = "rules" SchemaApiKeyKey = "api_key" + SchemaPermissionsKey = "permissions" + SchemaMonitorPermKey = "monitor_permissions" + SchemaSecurePermKey = "secure_permissions" + SchemaRequestedPermKey = "requested_permissions" + SchemaEnrichedPermKey = "enriched_permissions" ) diff --git a/sysdig/data_source_sysdig_custom_role_permissions_common.go b/sysdig/data_source_sysdig_custom_role_permissions_common.go new file mode 100644 index 00000000..0fc389a3 --- /dev/null +++ b/sysdig/data_source_sysdig_custom_role_permissions_common.go @@ -0,0 +1,67 @@ +package sysdig + +import ( + "context" + "crypto/sha256" + "fmt" + "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "strings" +) + +func dataSourceSysdigCustomRoleSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + SchemaRequestedPermKey: { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + SchemaEnrichedPermKey: { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + } +} + +func getDataSourceSysdigCustomRoleMonitorPermissionsRead(product v2.Product) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + + if err != nil { + return diag.FromErr(err) + } + rp := d.Get(SchemaRequestedPermKey).([]interface{}) + + rps := readPermissions(rp) + dependencies, err := client.GetPermissionsDependencies(ctx, product, rps) + + if err != nil { + return diag.FromErr(err) + } + ps := make([]string, len(dependencies)) + for i, dependency := range dependencies { + ps[i] = dependency.PermissionAuthority + ps = append(ps, dependency.Dependencies...) + } + + cdefChecksum := sha256.Sum256([]byte(strings.Join(rps, ","))) + d.SetId(fmt.Sprintf("%x", cdefChecksum)) + _ = d.Set(SchemaEnrichedPermKey, ps) + + return nil + } +} + +func readPermissions(rp []interface{}) []string { + permissions := make([]string, len(rp)) + for i, permission := range rp { + permissions[i] = permission.(string) + } + return permissions +} diff --git a/sysdig/data_source_sysdig_monitor_custom_role_permissions.go b/sysdig/data_source_sysdig_monitor_custom_role_permissions.go new file mode 100644 index 00000000..b08300eb --- /dev/null +++ b/sysdig/data_source_sysdig_monitor_custom_role_permissions.go @@ -0,0 +1,21 @@ +package sysdig + +import ( + "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "time" +) + +func dataSourceSysdigMonitorCustomRolePermissions() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + ReadContext: getDataSourceSysdigCustomRoleMonitorPermissionsRead(v2.MonitorProduct), + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(timeout), + }, + + Schema: dataSourceSysdigCustomRoleSchema(), + } +} diff --git a/sysdig/data_source_sysdig_monitor_custom_role_permissions_test.go b/sysdig/data_source_sysdig_monitor_custom_role_permissions_test.go new file mode 100644 index 00000000..781953c8 --- /dev/null +++ b/sysdig/data_source_sysdig_monitor_custom_role_permissions_test.go @@ -0,0 +1,49 @@ +//go:build tf_acc_sysdig_monitor + +package sysdig_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccMonitorCustomRolePermissionsDataSource(t *testing.T) { + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: monitorCustomRolePermissions(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckTypeSetElemAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.*", "dashboards.edit"), + resource.TestCheckTypeSetElemAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.*", "dashboards.read"), + resource.TestCheckTypeSetElemAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.*", "custom-events.read"), + resource.TestCheckTypeSetElemAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.*", "dashboard-metrics-data.read"), + resource.TestCheckTypeSetElemAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.*", "metrics-data.read"), + resource.TestCheckTypeSetElemAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.*", "alert-events.read"), + resource.TestCheckTypeSetElemAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.*", "api-token.read"), + resource.TestCheckTypeSetElemAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.*", "token.view"), + + resource.TestCheckResourceAttr("data.sysdig_monitor_custom_role_permissions.dashboard_edit", "enriched_permissions.#", "8"), + ), + }, + }, + }) +} + +func monitorCustomRolePermissions() string { + return ` +data "sysdig_monitor_custom_role_permissions" "dashboard_edit" { + requested_permissions = ["dashboards.edit", "token.view"] +} +` +} diff --git a/sysdig/data_source_sysdig_secure_custom_role_permissions.go b/sysdig/data_source_sysdig_secure_custom_role_permissions.go new file mode 100644 index 00000000..42878d44 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_custom_role_permissions.go @@ -0,0 +1,21 @@ +package sysdig + +import ( + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "time" +) + +func dataSourceSysdigSecureCustomRolePermissions() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + ReadContext: getDataSourceSysdigCustomRoleMonitorPermissionsRead(v2.SecureProduct), + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(timeout), + }, + + Schema: dataSourceSysdigCustomRoleSchema(), + } +} diff --git a/sysdig/data_source_sysdig_secure_custom_role_permissions_test.go b/sysdig/data_source_sysdig_secure_custom_role_permissions_test.go new file mode 100644 index 00000000..41247b28 --- /dev/null +++ b/sysdig/data_source_sysdig_secure_custom_role_permissions_test.go @@ -0,0 +1,43 @@ +//go:build tf_acc_sysdig_secure + +package sysdig_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccSecureCustomRolePermissionsDataSource(t *testing.T) { + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigSecureApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: secureCustomRolePermissions(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckTypeSetElemAttr("data.sysdig_secure_custom_role_permissions.images_edit", "enriched_permissions.*", "secure.blacklist.images.edit"), + resource.TestCheckTypeSetElemAttr("data.sysdig_secure_custom_role_permissions.images_edit", "enriched_permissions.*", "secure.blacklist.images.read"), + resource.TestCheckTypeSetElemAttr("data.sysdig_secure_custom_role_permissions.images_edit", "enriched_permissions.*", "scanning.read"), + resource.TestCheckResourceAttr("data.sysdig_secure_custom_role_permissions.images_edit", "enriched_permissions.#", "3"), + ), + }, + }, + }) +} + +func secureCustomRolePermissions() string { + return ` +data "sysdig_secure_custom_role_permissions" "images_edit" { + requested_permissions = ["secure.blacklist.images.edit"] +} +` +} diff --git a/sysdig/internal/client/v2/custom_role.go b/sysdig/internal/client/v2/custom_role.go new file mode 100644 index 00000000..cd84512b --- /dev/null +++ b/sysdig/internal/client/v2/custom_role.go @@ -0,0 +1,153 @@ +package v2 + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +var CustomRoleNotFound = errors.New("custom role not found") + +const ( + CustomRolesPath = "%s/api/roles" + CustomRolePath = "%s/api/roles/%d" +) + +type CustomRoleInterface interface { + Base + CreateCustomRole(ctx context.Context, cr *CustomRole) (*CustomRole, error) + UpdateCustomRole(ctx context.Context, cr *CustomRole, id int) (*CustomRole, error) + DeleteCustomRole(ctx context.Context, id int) error + GetCustomRole(ctx context.Context, id int) (*CustomRole, error) + GetCustomRoleByName(ctx context.Context, name string) (*CustomRole, error) +} + +func (client *Client) CreateCustomRole(ctx context.Context, cr *CustomRole) (*CustomRole, error) { + payload, err := Marshal(cr) + if err != nil { + return nil, err + } + response, err := client.requester.Request(ctx, http.MethodPost, client.CreateCustomRoleURL(), payload) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, client.ErrorFromResponse(response) + } + + created, err := Unmarshal[CustomRole](response.Body) + if err != nil { + return nil, err + } + + return &created, nil +} + +func (client *Client) UpdateCustomRole(ctx context.Context, cr *CustomRole, id int) (*CustomRole, error) { + payload, err := Marshal(cr) + if err != nil { + return nil, err + } + + response, err := client.requester.Request(ctx, http.MethodPut, client.UpdateCustomRoleURL(id), payload) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, client.ErrorFromResponse(response) + } + + updated, err := Unmarshal[CustomRole](response.Body) + if err != nil { + return nil, err + } + + return &updated, nil +} + +func (client *Client) DeleteCustomRole(ctx context.Context, id int) error { + response, err := client.requester.Request(ctx, http.MethodDelete, client.DeleteCustomRoleURL(id), nil) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound { + return client.ErrorFromResponse(response) + } + + return nil +} + +func (client *Client) GetCustomRole(ctx context.Context, id int) (*CustomRole, error) { + response, err := client.requester.Request(ctx, http.MethodGet, client.GetCustomRoleURL(id), nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + if response.StatusCode == http.StatusNotFound { + return nil, CustomRoleNotFound + } + return nil, client.ErrorFromResponse(response) + } + + cr, err := Unmarshal[CustomRole](response.Body) + if err != nil { + return nil, err + } + + return &cr, nil +} + +func (client *Client) GetCustomRoleByName(ctx context.Context, name string) (*CustomRole, error) { + response, err := client.requester.Request(ctx, http.MethodGet, client.GetCustomRolesURL(), nil) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, client.ErrorFromResponse(response) + } + + wrapper, err := Unmarshal[customRoleListWrapper](response.Body) + if err != nil { + return nil, err + } + + for _, customRole := range wrapper.Roles { + if customRole.Name == name { + return &customRole, nil + } + } + + return nil, fmt.Errorf("custom role with name: %s does not exist", name) + +} + +func (client *Client) CreateCustomRoleURL() string { + return fmt.Sprintf(CustomRolesPath, client.config.url) +} + +func (client *Client) UpdateCustomRoleURL(id int) string { + return fmt.Sprintf(CustomRolePath, client.config.url, id) +} + +func (client *Client) DeleteCustomRoleURL(id int) string { + return fmt.Sprintf(CustomRolePath, client.config.url, id) +} + +func (client *Client) GetCustomRoleURL(id int) string { + return fmt.Sprintf(CustomRolePath, client.config.url, id) +} + +func (client *Client) GetCustomRolesURL() string { + return fmt.Sprintf(CustomRolesPath, client.config.url) +} diff --git a/sysdig/internal/client/v2/model.go b/sysdig/internal/client/v2/model.go index e7f7301f..99760a12 100644 --- a/sysdig/internal/client/v2/model.go +++ b/sysdig/internal/client/v2/model.go @@ -38,6 +38,24 @@ type UserRoles struct { Admin bool `json:"admin,omitempty"` } +type CustomRole struct { + ID int `json:"id,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + MonitorPermissions []string `json:"monitorPermissions,omitempty"` + SecurePermissions []string `json:"securePermissions,omitempty"` +} +type customRoleListWrapper struct { + Roles []CustomRole `json:"roles"` +} + +type Dependency struct { + PermissionAuthority string `json:"permissionAuthority"` + Dependencies []string `json:"dependencies"` +} + +type Dependencies []Dependency + type TeamServiceAccount struct { ID int `json:"id,omitempty"` Name string `json:"name"` diff --git a/sysdig/internal/client/v2/permission.go b/sysdig/internal/client/v2/permission.go new file mode 100644 index 00000000..45ea067a --- /dev/null +++ b/sysdig/internal/client/v2/permission.go @@ -0,0 +1,43 @@ +package v2 + +import ( + "context" + "fmt" + "net/http" + "strings" +) + +const PermissionsURL = "%s/api/permissions/%s/dependencies?requestedPermissions=%s" + +type CustomRolePermissionInterface interface { + Base + + GetPermissionsDependencies(ctx context.Context, product Product, permissions []string) ([]Dependency, error) +} + +func (client *Client) GetPermissionsDependencies(ctx context.Context, product Product, permissions []string) ([]Dependency, error) { + segments := map[Product]string{MonitorProduct: "monitor", SecureProduct: "secure"} + url := fmt.Sprintf(PermissionsURL, client.config.url, segments[product], strings.Join(permissions, ",")) + + return client.getPermissionsDependencies(ctx, url) +} + +func (client *Client) getPermissionsDependencies(ctx context.Context, url string) ([]Dependency, error) { + response, err := client.requester.Request(ctx, http.MethodGet, url, nil) + if err != nil { + return []Dependency{}, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return []Dependency{}, client.ErrorFromResponse(response) + } + + dependencies, err := Unmarshal[Dependencies](response.Body) + + if err != nil { + return []Dependency{}, err + } + + return dependencies, nil +} diff --git a/sysdig/internal/client/v2/sysdig.go b/sysdig/internal/client/v2/sysdig.go index 2e35e9e7..75df4221 100644 --- a/sysdig/internal/client/v2/sysdig.go +++ b/sysdig/internal/client/v2/sysdig.go @@ -20,6 +20,8 @@ type SysdigCommon interface { Common GroupMappingInterface GroupMappingConfigInterface + CustomRoleInterface + CustomRolePermissionInterface TeamServiceAccountInterface } diff --git a/sysdig/provider.go b/sysdig/provider.go index 9f0b18f9..d3983c17 100644 --- a/sysdig/provider.go +++ b/sysdig/provider.go @@ -102,6 +102,7 @@ func Provider() *schema.Provider { "sysdig_user": resourceSysdigUser(), "sysdig_group_mapping": resourceSysdigGroupMapping(), "sysdig_group_mapping_config": resourceSysdigGroupMappingConfig(), + "sysdig_custom_role": resourceSysdigCustomRole(), "sysdig_team_service_account": resourceSysdigTeamServiceAccount(), "sysdig_secure_custom_policy": resourceSysdigSecureCustomPolicy(), @@ -155,19 +156,20 @@ func Provider() *schema.Provider { "sysdig_secure_posture_zone": resourceSysdigSecurePostureZone(), }, DataSourcesMap: map[string]*schema.Resource{ - "sysdig_secure_trusted_cloud_identity": dataSourceSysdigSecureTrustedCloudIdentity(), - "sysdig_secure_notification_channel": dataSourceSysdigSecureNotificationChannel(), - "sysdig_secure_custom_policy": dataSourceSysdigSecureCustomPolicy(), - "sysdig_secure_managed_policy": dataSourceSysdigSecureManagedPolicy(), - "sysdig_secure_managed_ruleset": dataSourceSysdigSecureManagedRuleset(), - "sysdig_secure_rule_container": dataSourceSysdigSecureRuleContainer(), - "sysdig_secure_rule_falco": dataSourceSysdigSecureRuleFalco(), - "sysdig_secure_rule_falco_count": dataSourceSysdigSecureRuleFalcoCount(), - "sysdig_secure_rule_filesystem": dataSourceSysdigSecureRuleFilesystem(), - "sysdig_secure_rule_network": dataSourceSysdigSecureRuleNetwork(), - "sysdig_secure_rule_process": dataSourceSysdigSecureRuleProcess(), - "sysdig_secure_rule_syscall": dataSourceSysdigSecureRuleSyscall(), - "sysdig_secure_posture_policies": dataSourceSysdigSecurePosturePolicies(), + "sysdig_secure_trusted_cloud_identity": dataSourceSysdigSecureTrustedCloudIdentity(), + "sysdig_secure_notification_channel": dataSourceSysdigSecureNotificationChannel(), + "sysdig_secure_custom_policy": dataSourceSysdigSecureCustomPolicy(), + "sysdig_secure_managed_policy": dataSourceSysdigSecureManagedPolicy(), + "sysdig_secure_managed_ruleset": dataSourceSysdigSecureManagedRuleset(), + "sysdig_secure_rule_container": dataSourceSysdigSecureRuleContainer(), + "sysdig_secure_rule_falco": dataSourceSysdigSecureRuleFalco(), + "sysdig_secure_rule_falco_count": dataSourceSysdigSecureRuleFalcoCount(), + "sysdig_secure_rule_filesystem": dataSourceSysdigSecureRuleFilesystem(), + "sysdig_secure_rule_network": dataSourceSysdigSecureRuleNetwork(), + "sysdig_secure_rule_process": dataSourceSysdigSecureRuleProcess(), + "sysdig_secure_rule_syscall": dataSourceSysdigSecureRuleSyscall(), + "sysdig_secure_posture_policies": dataSourceSysdigSecurePosturePolicies(), + "sysdig_secure_custom_role_permissions": dataSourceSysdigSecureCustomRolePermissions(), "sysdig_current_user": dataSourceSysdigCurrentUser(), "sysdig_user": dataSourceSysdigUser(), @@ -176,6 +178,7 @@ func Provider() *schema.Provider { "sysdig_fargate_workload_agent": dataSourceSysdigFargateWorkloadAgent(), "sysdig_monitor_notification_channel_pagerduty": dataSourceSysdigMonitorNotificationChannelPagerduty(), "sysdig_monitor_notification_channel_email": dataSourceSysdigMonitorNotificationChannelEmail(), + "sysdig_monitor_custom_role_permissions": dataSourceSysdigMonitorCustomRolePermissions(), }, ConfigureContextFunc: providerConfigure, } diff --git a/sysdig/resource_sysdig_custom_role.go b/sysdig/resource_sysdig_custom_role.go new file mode 100644 index 00000000..4a38ad57 --- /dev/null +++ b/sysdig/resource_sysdig_custom_role.go @@ -0,0 +1,227 @@ +package sysdig + +import ( + "context" + "fmt" + "strconv" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceSysdigCustomRole() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + ReadContext: resourceSysdigCustomRoleRead, + CreateContext: resourceSysdigCustomRoleCreate, + UpdateContext: resourceSysdigCustomRoleUpdate, + DeleteContext: resourceSysdigCustomRoleDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(timeout), + Update: schema.DefaultTimeout(timeout), + Read: schema.DefaultTimeout(timeout), + Delete: schema.DefaultTimeout(timeout), + }, + Schema: map[string]*schema.Schema{ + SchemaNameKey: { + Type: schema.TypeString, + Required: true, + }, + SchemaDescriptionKey: { + Type: schema.TypeString, + Optional: true, + }, + SchemaPermissionsKey: { + Type: schema.TypeSet, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + SchemaMonitorPermKey: { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + SchemaSecurePermKey: { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + } +} + +func resourceSysdigCustomRoleRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + customRole, err := client.GetCustomRole(ctx, id) + if err != nil { + if err == v2.CustomRoleNotFound { + d.SetId("") + return nil + } + return diag.FromErr(err) + } + + err = customRoleToResourceData(customRole, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigCustomRoleCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var err error + + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + customRole, err := customRoleFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + customRole, err = client.CreateCustomRole(ctx, customRole) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.Itoa(customRole.ID)) + + resourceSysdigCustomRoleRead(ctx, d, m) + + return nil +} + +func resourceSysdigCustomRoleUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var err error + + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + customRole, err := customRoleFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + customRole.ID = id + _, err = client.UpdateCustomRole(ctx, customRole, id) + if err != nil { + return diag.FromErr(err) + } + + resourceSysdigCustomRoleRead(ctx, d, m) + + return nil +} + +func resourceSysdigCustomRoleDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client, err := m.(SysdigClients).sysdigCommonClientV2() + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = client.DeleteCustomRole(ctx, id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func customRoleFromResourceData(d *schema.ResourceData) (*v2.CustomRole, error) { + schemaPermissions, ok := d.Get(SchemaPermissionsKey).(*schema.Set) + if !ok { + return nil, fmt.Errorf("cast permissions to set resuts in an error") + } + p := permission{ + schemaPermissions, + } + return &v2.CustomRole{ + Name: d.Get(SchemaNameKey).(string), + Description: d.Get(SchemaDescriptionKey).(string), + MonitorPermissions: p.readMonitorPermissions(), + SecurePermissions: p.readSecurePermissions(), + }, nil +} + +type permission struct { + s *schema.Set +} + +func (p *permission) readPermissions(product string) []string { + permissionsMap := p.s.List()[0].(map[string]interface{}) + permissionsInterface := permissionsMap[product].(*schema.Set).List() + permissions := make([]string, len(permissionsInterface)) + for i, permission := range permissionsInterface { + permissions[i] = permission.(string) + } + return permissions +} + +func (p *permission) readSecurePermissions() []string { + return p.readPermissions(SchemaSecurePermKey) +} + +func (p *permission) readMonitorPermissions() []string { + return p.readPermissions(SchemaMonitorPermKey) +} + +func customRoleToResourceData(customRole *v2.CustomRole, d *schema.ResourceData) error { + err := d.Set(SchemaNameKey, customRole.Name) + if err != nil { + return err + } + err = d.Set(SchemaDescriptionKey, customRole.Description) + if err != nil { + return err + } + err = d.Set(SchemaPermissionsKey, []map[string]interface{}{ + permissionsToResourceData(customRole.MonitorPermissions, customRole.SecurePermissions), + }) + if err != nil { + return err + } + return nil +} + +func permissionsToResourceData(monitorPermissions []string, securePermissions []string) map[string]interface{} { + return map[string]interface{}{ + SchemaMonitorPermKey: monitorPermissions, + SchemaSecurePermKey: securePermissions, + } +} diff --git a/sysdig/resource_sysdig_custom_role_test.go b/sysdig/resource_sysdig_custom_role_test.go new file mode 100644 index 00000000..652e036b --- /dev/null +++ b/sysdig/resource_sysdig_custom_role_test.go @@ -0,0 +1,119 @@ +//go:build tf_acc_sysdig_monitor || tf_acc_sysdig_secure + +package sysdig_test + +import ( + "fmt" + "github.com/draios/terraform-provider-sysdig/sysdig" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "testing" +) + +func TestAccCustomRoleResource(t *testing.T) { + name := randomText(10) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: customRoleTokenToken(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckTypeSetElemAttr("sysdig_custom_role.custom-role", "permissions.0.monitor_permissions.*", "token.view"), + resource.TestCheckTypeSetElemAttr("sysdig_custom_role.custom-role", "permissions.0.monitor_permissions.*", "api-token.read"), + resource.TestCheckResourceAttr("sysdig_custom_role.custom-role", "permissions.0.secure_permissions.#", "0"), + ), + }, + { + Config: customRolePermissionsUpdate(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckTypeSetElemAttr("sysdig_custom_role.custom-role", "permissions.0.monitor_permissions.*", "kubernetes-api-commands.read"), + resource.TestCheckResourceAttr("sysdig_custom_role.custom-role", "permissions.0.monitor_permissions.#", "1"), + ), + }, + { + Config: customRolePermissionsAddSecure(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckTypeSetElemAttr("sysdig_custom_role.custom-role", "permissions.0.secure_permissions.*", "scanning.read"), + resource.TestCheckResourceAttr("sysdig_custom_role.custom-role", "permissions.0.secure_permissions.#", "1"), + resource.TestCheckResourceAttr("sysdig_custom_role.custom-role", "permissions.0.monitor_permissions.#", "1"), + ), + }, + { + Config: customRolePermissionsEditName(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "sysdig_custom_role.custom-role", + "name", + fmt.Sprintf("custom-role-%s-updated", name), + ), + ), + }, + { + ResourceName: "sysdig_custom_role.custom-role", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func customRoleTokenToken(name string) string { + return fmt.Sprintf(` +resource "sysdig_custom_role" "custom-role" { + name = "custom-role-%s" + description = "test" + + permissions { + monitor_permissions = ["token.view", "api-token.read"] + } +} +`, name) + +} + +func customRolePermissionsUpdate(name string) string { + return fmt.Sprintf(` +resource "sysdig_custom_role" "custom-role" { + name = "custom-role-%s" + description = "test" + + permissions { + monitor_permissions = ["kubernetes-api-commands.read"] + } +} +`, name) +} + +func customRolePermissionsAddSecure(name string) string { + return fmt.Sprintf(` +resource "sysdig_custom_role" "custom-role" { + name = "custom-role-%s" + description = "test" + + permissions { + monitor_permissions = ["kubernetes-api-commands.read"] + secure_permissions = ["scanning.read"] + } +} +`, name) +} + +func customRolePermissionsEditName(name string) string { + return fmt.Sprintf(` +resource "sysdig_custom_role" "custom-role" { + name = "custom-role-%s-updated" + description = "test" + + permissions { + monitor_permissions = ["kubernetes-api-commands.read"] + secure_permissions = ["scanning.read"] + } +} +`, name) +} diff --git a/website/docs/d/monitor_custom_role_permissions.md b/website/docs/d/monitor_custom_role_permissions.md new file mode 100644 index 00000000..c5235d84 --- /dev/null +++ b/website/docs/d/monitor_custom_role_permissions.md @@ -0,0 +1,47 @@ +--- +subcategory: "Sysdig Platform" +layout: "sysdig" +page_title: "Sysdig: sysdig_monitor_custom_role_permissions" +description: |- + Validate and enrich with permissions on which the requested permissions depend +--- + +# Data Source: sysdig_monitor_custom_role_permissions + +Validate and enrich the requested permissions + + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +data "sysdig_monitor_custom_role_permissions" "dashboard_edit" { + requested_permissions = ["dashboards.edit"] +} +``` + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `enriched_permissions` - The minimum set of permissions enriched with permissions on which the requested permissions depend + + +## Example Usage with Custom Role + +```terraform +data "sysdig_monitor_custom_role_permissions" "dashboard_edit" { + requested_permissions = ["dashboards.edit"] +} + +resource "sysdig_custom_role" "my-custom-role" { + depends_on = [data.sysdig_secure_custom_role_permissions.images_edit] + name = "custom-role-name" + description = "Custom role to edit dashboards" + + permissions { + monitor_permissions = data.sysdig_monitor_custom_role_permissions.dashboard_edit.enriched_permissions + } +} +``` diff --git a/website/docs/d/secure_custom_role_permissions.md b/website/docs/d/secure_custom_role_permissions.md new file mode 100644 index 00000000..73a7f1c1 --- /dev/null +++ b/website/docs/d/secure_custom_role_permissions.md @@ -0,0 +1,47 @@ +--- +subcategory: "Sysdig Platform" +layout: "sysdig" +page_title: "Sysdig: sysdig_secure_custom_role_permissions" +description: |- + Validate and enrich with permissions on which the requested permissions depend +--- + +# Data Source: sysdig_secure_custom_role_permissions + +Validate and enrich the requested permissions + + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +data "sysdig_secure_custom_role_permissions" "images_edit" { + requested_permissions = ["secure.blacklist.images.edit"] +} +``` + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `enriched_permissions` - The minimum set of permissions enriched with permissions on which the requested permissions depend + + +## Example Usage with Custom Role + +```terraform +data "sysdig_secure_custom_role_permissions" "images_edit" { + requested_permissions = ["secure.blacklist.images.edit"] +} + +resource "sysdig_custom_role" "my-custom-role" { + depends_on = [data.sysdig_secure_custom_role_permissions.images_edit] + name = "custom-role-name" + description = "Custom role to edit images" + + permissions { + secure_permissions = data.sysdig_secure_custom_role_permissions.images_edit.enriched_permissions + } +} +``` diff --git a/website/docs/r/custom_role.md b/website/docs/r/custom_role.md new file mode 100644 index 00000000..ba941835 --- /dev/null +++ b/website/docs/r/custom_role.md @@ -0,0 +1,64 @@ +--- +subcategory: "Sysdig Platform" +layout: "sysdig" +page_title: "Sysdig: sysdig_custom_role" +description: |- + Creates a custom role in Sysdig. +--- + +# Resource: sysdig_custom_role + +Creates a custom role in Sysdig. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +resource "sysdig_custom_role" "my-custom-role" { + name = "custom-role-name" + description = "test" + + permissions { + monitor_permissions = ["kubernetes-api-commands.read"] + secure_permissions = ["scanning.read"] + } +} + +``` + +## Argument Reference + +* `name` - (Required) The custom role name. + +* `description` - (Optional) Additional long description. + +* `permissions` (Required) Block to define monitor and secure permissions. + +### permissions + +Permissions block is required and supports: + +* `monitor_permissions` - (Optional) Set of Monitor permissions assigned to the role. Check GET /api/permissions to get the list of available values + +* `secure_permissions` - (Optional) Set of Secure permissions assigned to the role. Check GET /api/permissions to get the list of available values. + +### Permissions data source + +Permissions can have dependencies and dependee. Since the dependencies graph can be hard to determine manually were introduced + +[`sysdig_secure_custom_role_permissions`](../d/secure_custom_role_permissions.md) and [`sysdig_monitor_custom_role_permissions`](../d/monitor_custom_role_permissions.md) + +Please check the relative documentation to see how to use them. + +## Attributes Reference + +No additional attributes are exported. + +## Import + +Sysdig group mapping can be imported using the ID, e.g. + +``` +$ terraform import sysdig_custom_role.my_custom_role 50 +```