Skip to content

Commit

Permalink
feat(custom role) add custom role resource and permissions data source (
Browse files Browse the repository at this point in the history
#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
  • Loading branch information
Shadow649 authored Jul 27, 2023
1 parent 41345d3 commit bec2891
Show file tree
Hide file tree
Showing 16 changed files with 942 additions and 13 deletions.
5 changes: 5 additions & 0 deletions sysdig/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
67 changes: 67 additions & 0 deletions sysdig/data_source_sysdig_custom_role_permissions_common.go
Original file line number Diff line number Diff line change
@@ -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
}
21 changes: 21 additions & 0 deletions sysdig/data_source_sysdig_monitor_custom_role_permissions.go
Original file line number Diff line number Diff line change
@@ -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(),
}
}
49 changes: 49 additions & 0 deletions sysdig/data_source_sysdig_monitor_custom_role_permissions_test.go
Original file line number Diff line number Diff line change
@@ -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"]
}
`
}
21 changes: 21 additions & 0 deletions sysdig/data_source_sysdig_secure_custom_role_permissions.go
Original file line number Diff line number Diff line change
@@ -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(),
}
}
43 changes: 43 additions & 0 deletions sysdig/data_source_sysdig_secure_custom_role_permissions_test.go
Original file line number Diff line number Diff line change
@@ -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"]
}
`
}
153 changes: 153 additions & 0 deletions sysdig/internal/client/v2/custom_role.go
Original file line number Diff line number Diff line change
@@ -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)
}
18 changes: 18 additions & 0 deletions sysdig/internal/client/v2/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
Loading

0 comments on commit bec2891

Please sign in to comment.