diff --git a/cyral/internal/policy/v2/model.go b/cyral/internal/policy/v2/model.go index 01d07330..3ad70f19 100644 --- a/cyral/internal/policy/v2/model.go +++ b/cyral/internal/policy/v2/model.go @@ -13,13 +13,6 @@ import ( "github.com/cyralinc/terraform-provider-cyral/cyral/utils" ) -// ChangeInfo represents information about changes to the policy -type ChangeInfo struct { - Actor string `json:"actor,omitempty"` - ActorType string `json:"actorType,omitempty"` - Timestamp string `json:"timestamp,omitempty"` -} - // changeInfoToMap converts ChangeInfo to a map func changeInfoToMap(c *msg.ChangeInfo) map[string]interface{} { return map[string]interface{}{ diff --git a/cyral/internal/policyset/constants.go b/cyral/internal/policyset/constants.go new file mode 100644 index 00000000..cceefe56 --- /dev/null +++ b/cyral/internal/policyset/constants.go @@ -0,0 +1,6 @@ +package policyset + +const ( + resourceName = "cyral_policy_set" + dataSourceName = resourceName +) diff --git a/cyral/internal/policyset/datasource.go b/cyral/internal/policyset/datasource.go new file mode 100644 index 00000000..844c5077 --- /dev/null +++ b/cyral/internal/policyset/datasource.go @@ -0,0 +1,105 @@ +package policyset + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" +) + +var dsContextHandler = core.ContextHandler{ + ResourceName: dataSourceName, + ResourceType: resourcetype.DataSource, + Read: readPolicySet, +} + +func dataSourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "This data source provides information about a policy set.", + ReadContext: dsContextHandler.ReadContext, + Schema: map[string]*schema.Schema{ + "id": { + Description: "Identifier for the policy set.", + Type: schema.TypeString, + Required: true, + }, + "wizard_id": { + Description: "The ID of the policy wizard used to create this policy set.", + Type: schema.TypeString, + Computed: true, + }, + "name": { + Description: "Name of the policy set.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Description of the policy set.", + Type: schema.TypeString, + Computed: true, + }, + "tags": { + Description: "Tags associated with the policy set.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "scope": { + Description: "Scope of the policy set.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "repo_ids": { + Description: "List of repository IDs that are in scope. Empty list means all repositories are in scope.", + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + }, + }, + }, + "wizard_parameters": { + Description: "Parameters passed to the wizard while creating the policy set.", + Type: schema.TypeString, + Computed: true, + }, + "enabled": { + Description: "Indicates if the policy set is enabled.", + Type: schema.TypeBool, + Computed: true, + }, + "policies": { + Description: "List of policies that comprise the policy set.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Description: "Type of the policy.", + Type: schema.TypeString, + Computed: true, + }, + "id": { + Description: "ID of the policy.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "last_updated": { + Description: "Information about when and by whom the policy set was last updated.", + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "created": { + Description: "Information about when and by whom the policy set was created.", + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} diff --git a/cyral/internal/policyset/model.go b/cyral/internal/policyset/model.go new file mode 100644 index 00000000..fc1941f8 --- /dev/null +++ b/cyral/internal/policyset/model.go @@ -0,0 +1,168 @@ +package policyset + +import ( + "context" + "fmt" + "time" + + methods "buf.build/gen/go/cyral/policy/grpc/go/policy/v1/policyv1grpc" + msg "buf.build/gen/go/cyral/policy/protocolbuffers/go/policy/v1" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/cyralinc/terraform-provider-cyral/cyral/client" + "github.com/cyralinc/terraform-provider-cyral/cyral/utils" +) + +// ToMap converts PolicySetPolicy to a map +func policySetPolicyToMap(p *msg.PolicySetPolicy) map[string]interface{} { + return map[string]interface{}{ + "type": p.GetType().String(), + "id": p.GetId(), + } +} + +func policiesToMaps(policies []*msg.PolicySetPolicy) []map[string]interface{} { + var result []map[string]interface{} + for _, policy := range policies { + result = append(result, policySetPolicyToMap(policy)) + } + return result +} + +// changeInfoToMap converts ChangeInfo to a map +func changeInfoToMap(c *msg.ChangeInfo) map[string]interface{} { + return map[string]interface{}{ + "actor": c.GetActor(), + "actor_type": c.GetActorType().String(), + "timestamp": c.GetTimestamp().AsTime().Format(time.RFC3339), + } +} + +// scopeToMap converts Scope to a list of maps +func scopeToMap(s *msg.Scope) []map[string]interface{} { + return []map[string]interface{}{ + { + "repo_ids": s.GetRepoIds(), + }, + } +} + +// updateSchema writes the policy set data to the schema +func updateSchema(ps *msg.PolicySet, d *schema.ResourceData) error { + if err := d.Set("id", ps.GetId()); err != nil { + return fmt.Errorf("error setting 'id' field: %w", err) + } + if err := d.Set("wizard_id", ps.GetWizardId()); err != nil { + return fmt.Errorf("error setting 'id' field: %w", err) + } + if err := d.Set("name", ps.GetName()); err != nil { + return fmt.Errorf("error setting 'name' field: %w", err) + } + if err := d.Set("description", ps.GetDescription()); err != nil { + return fmt.Errorf("error setting 'description' field: %w", err) + } + if err := d.Set("enabled", ps.GetEnabled()); err != nil { + return fmt.Errorf("error setting 'enabled' field: %w", err) + } + if err := d.Set("tags", ps.GetTags()); err != nil { + return fmt.Errorf("error setting 'tags' field: %w", err) + } + if err := d.Set("wizard_parameters", ps.GetWizardParameters()); err != nil { + return fmt.Errorf("error setting 'document' field: %w", err) + } + + if err := d.Set("policies", policiesToMaps(ps.GetPolicies())); err != nil { + return fmt.Errorf("error setting 'policies' field: %w", err) + } + if ps.GetScope() != nil { + if err := d.Set("scope", scopeToMap(ps.GetScope())); err != nil { + return fmt.Errorf("error setting 'scope' field: %w", err) + } + } + // Use the changeInfoToMap method to set the last_updated and created fields + if err := d.Set("last_updated", changeInfoToMap(ps.GetLastUpdated())); err != nil { + return fmt.Errorf("error setting 'last_updated' field: %w", err) + } + if err := d.Set("created", changeInfoToMap(ps.GetCreated())); err != nil { + return fmt.Errorf("error setting 'created' field: %w", err) + } + d.SetId(ps.GetId()) + return nil +} + +func policySetFromSchema(d *schema.ResourceData) *msg.PolicySet { + p := &msg.PolicySet{ + Id: d.Get("id").(string), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Enabled: d.Get("enabled").(bool), + Tags: utils.ConvertFromInterfaceList[string](d.Get("tags").([]interface{})), + WizardId: d.Get("wizard_id").(string), + WizardParameters: d.Get("wizard_parameters").(string), + } + + if v, ok := d.GetOk("scope"); ok { + p.Scope = scopeFromInterface(v.([]interface{})) + } + return p +} + +// scopeFromInterface converts the map to a Scope struct +func scopeFromInterface(s []interface{}) *msg.Scope { + if len(s) == 0 || s[0] == nil { + return nil + } + m := s[0].(map[string]interface{}) + return &msg.Scope{ + RepoIds: utils.ConvertFromInterfaceList[string](m["repo_ids"].([]interface{})), + } +} + +func createPolicySet(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error { + ps := policySetFromSchema(rd) + req := &msg.CreatePolicySetRequest{ + PolicySet: ps, + } + grpcClient := methods.NewPolicyWizardServiceClient(cl.GRPCClient()) + resp, err := grpcClient.CreatePolicySet(ctx, req) + if err != nil { + return err + } + rd.SetId(resp.GetPolicySet().GetId()) + return nil +} + +func readPolicySet(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error { + req := &msg.ReadPolicySetRequest{ + Id: rd.Get("id").(string), + } + grpcClient := methods.NewPolicyWizardServiceClient(cl.GRPCClient()) + resp, err := grpcClient.ReadPolicySet(ctx, req) + if err != nil { + return err + } + return updateSchema(resp.GetPolicySet(), rd) +} + +func updatePolicySet(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error { + ps := policySetFromSchema(rd) + req := &msg.UpdatePolicySetRequest{ + Id: ps.GetId(), + PolicySet: ps, + } + grpcClient := methods.NewPolicyWizardServiceClient(cl.GRPCClient()) + resp, err := grpcClient.UpdatePolicySet(ctx, req) + if err != nil { + return err + } + return updateSchema(resp.GetPolicySet(), rd) +} + +func deletePolicySet(ctx context.Context, cl *client.Client, rd *schema.ResourceData) error { + req := &msg.DeletePolicySetRequest{ + Id: rd.Get("id").(string), + } + grpcClient := methods.NewPolicyWizardServiceClient(cl.GRPCClient()) + _, err := grpcClient.DeletePolicySet(ctx, req) + return err +} diff --git a/cyral/internal/policyset/resource.go b/cyral/internal/policyset/resource.go new file mode 100644 index 00000000..459d0a1c --- /dev/null +++ b/cyral/internal/policyset/resource.go @@ -0,0 +1,121 @@ +package policyset + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/cyralinc/terraform-provider-cyral/cyral/core" + "github.com/cyralinc/terraform-provider-cyral/cyral/core/types/resourcetype" +) + +var resourceContextHandler = core.ContextHandler{ + ResourceName: resourceName, + ResourceType: resourcetype.Resource, + Create: createPolicySet, + Read: readPolicySet, + Update: updatePolicySet, + Delete: deletePolicySet, +} + +func resourceSchema() *schema.Resource { + return &schema.Resource{ + Description: "This resource allows management of policy sets in the Cyral platform.", + CreateContext: resourceContextHandler.CreateContext, + ReadContext: resourceContextHandler.ReadContext, + UpdateContext: resourceContextHandler.UpdateContext, + DeleteContext: resourceContextHandler.DeleteContext, + Importer: &schema.ResourceImporter{ + StateContext: importPolicySetStateContext, + }, + Schema: map[string]*schema.Schema{ + "id": { + Description: "Identifier for the policy set.", + Type: schema.TypeString, + Computed: true, + }, + "wizard_id": { + Description: "The ID of the policy wizard used to create this policy set.", + Type: schema.TypeString, + Required: true, + }, + "name": { + Description: "Name of the policy set.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "Description of the policy set.", + Type: schema.TypeString, + Optional: true, + }, + "tags": { + Description: "Tags associated with the policy set.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "scope": { + Description: "Scope of the policy set.", + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "repo_ids": { + Description: "List of repository IDs that are in scope. Empty list means all repositories are in scope.", + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + }, + }, + }, + "wizard_parameters": { + Description: "Parameters passed to the wizard while creating the policy set.", + Type: schema.TypeString, + Required: true, + }, + "enabled": { + Description: "Indicates if the policy set is enabled.", + Type: schema.TypeBool, + Optional: true, + }, + "policies": { + Description: "List of policies that comprise the policy set.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Description: "Type of the policy.", + Type: schema.TypeString, + Computed: true, + }, + "id": { + Description: "ID of the policy.", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "last_updated": { + Description: "Information about when and by whom the policy set was last updated.", + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "created": { + Description: "Information about when and by whom the policy set was created.", + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func importPolicySetStateContext(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { + return []*schema.ResourceData{d}, nil +} diff --git a/cyral/internal/policyset/resource_test.go b/cyral/internal/policyset/resource_test.go new file mode 100644 index 00000000..44a155c7 --- /dev/null +++ b/cyral/internal/policyset/resource_test.go @@ -0,0 +1,162 @@ +package policyset_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/cyralinc/terraform-provider-cyral/cyral/provider" +) + +func initialPolicySetConfig() map[string]interface{} { + wizardParameters := `{"failClosed":true,"denyByDefault":true}` + + return map[string]interface{}{ + "wizard_id": "repo-lockdown", + "name": "Lockdown all repos", + "description": "Policy set created via wizard", + "tags": []string{"default", "block", "fail closed"}, + "scope": map[string][]string{"repo_ids": {}}, // Empty scope means to target all repos + "wizard_parameters": wizardParameters, + "enabled": true, + } +} + +func updatedPolicySetConfig() map[string]interface{} { + wizardParameters := `{"failClosed":false,"denyByDefault":true}` + + return map[string]interface{}{ + "wizard_id": "repo-lockdown", + "name": "Lockdown all repos", + "description": "Updated policy set created via wizard", + "tags": []string{"default", "block", "fail open"}, + "scope": map[string][]string{"repo_ids": {}}, + "wizard_parameters": wizardParameters, + "enabled": true, + } +} + +func TestAccPolicyWizardV1Resource(t *testing.T) { + testInitialConfig, testInitialFunc := setupPolicySetTest("main_test", initialPolicySetConfig()) + testUpdatedConfig, testUpdatedFunc := setupPolicySetTest("main_test", updatedPolicySetConfig()) + resource.Test(t, resource.TestCase{ + ProviderFactories: provider.ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testInitialConfig, + Check: testInitialFunc, + }, + { + Config: testUpdatedConfig, + Check: testUpdatedFunc, + }, + { + // Test importing the resource + ResourceName: "cyral_policy_set.main_test", + ImportState: true, + ImportStateIdFunc: testImportStateIdFunc("cyral_policy_set.main_test"), + ImportStateVerify: true, + }, + }, + }) +} + +func setupPolicySetTest(resName string, policySet map[string]interface{}) (string, resource.TestCheckFunc) { + config := formatPolicySetIntoConfig(resName, policySet) + resourceFullName := fmt.Sprintf("cyral_policy_set.%s", resName) + + testFunction := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceFullName, "wizard_id", policySet["wizard_id"].(string)), + resource.TestCheckResourceAttr(resourceFullName, "name", policySet["name"].(string)), + resource.TestCheckResourceAttr(resourceFullName, "description", policySet["description"].(string)), + resource.TestCheckResourceAttr(resourceFullName, "wizard_parameters", policySet["wizard_parameters"].(string)), + resource.TestCheckResourceAttr(resourceFullName, "enabled", fmt.Sprintf("%v", policySet["enabled"])), + ) + + // Check tags + if tags, ok := policySet["tags"].([]string); ok && len(tags) > 0 { + for i, tag := range tags { + key := fmt.Sprintf("tags.%d", i) + testFunction = resource.ComposeTestCheckFunc( + testFunction, + resource.TestCheckResourceAttr(resourceFullName, key, tag), + ) + } + } + + // Check scope + if scope, ok := policySet["scope"].(map[string][]string); ok { + if repoIds, ok := scope["repo_ids"]; ok && len(repoIds) > 0 { + for i, repoId := range repoIds { + key := fmt.Sprintf("scope.0.repo_ids.%d", i) + testFunction = resource.ComposeTestCheckFunc( + testFunction, + resource.TestCheckResourceAttr(resourceFullName, key, repoId), + ) + } + } + } + + return config, testFunction +} + +func formatPolicySetIntoConfig(resName string, policySet map[string]interface{}) string { + config := fmt.Sprintf(` +resource "cyral_policy_set" "%s" { + wizard_id = "%s" + name = "%s" +`, resName, policySet["wizard_id"].(string), policySet["name"].(string)) + + if description, ok := policySet["description"].(string); ok && description != "" { + config += fmt.Sprintf(` description = "%s" +`, description) + } + + // Handle tags + if tags, ok := policySet["tags"].([]string); ok && len(tags) > 0 { + config += " tags = [\n" + for _, tag := range tags { + config += fmt.Sprintf(` "%s", +`, tag) + } + config += " ]\n" + } + + // Handle scope + if scope, ok := policySet["scope"].(map[string][]string); ok { + if repoIds, ok := scope["repo_ids"]; ok && len(repoIds) > 0 { + config += " scope {\n" + config += " repo_ids = [\n" + for _, repoId := range repoIds { + config += fmt.Sprintf(` "%s", +`, repoId) + } + config += " ]\n" + config += " }\n" + } + } + + // Properly escape wizard_parameters + config += fmt.Sprintf(" wizard_parameters = %q\n", policySet["wizard_parameters"].(string)) + + if enabled, ok := policySet["enabled"].(bool); ok { + config += fmt.Sprintf(" enabled = %v\n", enabled) + } + + config += "}\n" + + return config +} + +func testImportStateIdFunc(resourceName string) func(*terraform.State) (string, error) { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("resource %s not found in state", resourceName) + } + // The ID is the policy set ID + return rs.Primary.ID, nil + } +} diff --git a/cyral/internal/policyset/schema_loader.go b/cyral/internal/policyset/schema_loader.go new file mode 100644 index 00000000..229e9642 --- /dev/null +++ b/cyral/internal/policyset/schema_loader.go @@ -0,0 +1,31 @@ +package policyset + +import "github.com/cyralinc/terraform-provider-cyral/cyral/core" + +type packageSchema struct { +} + +func (p *packageSchema) Name() string { + return "policyset" +} + +func (p *packageSchema) Schemas() []*core.SchemaDescriptor { + return []*core.SchemaDescriptor{ + + { + Name: dataSourceName, + Type: core.DataSourceSchemaType, + Schema: dataSourceSchema, + }, + + { + Name: resourceName, + Type: core.ResourceSchemaType, + Schema: resourceSchema, + }, + } +} + +func PackageSchema() core.PackageSchema { + return &packageSchema{} +} diff --git a/cyral/provider/schema_loader.go b/cyral/provider/schema_loader.go index 0cc34034..9c01d263 100644 --- a/cyral/provider/schema_loader.go +++ b/cyral/provider/schema_loader.go @@ -16,6 +16,7 @@ import ( integration_teams "github.com/cyralinc/terraform-provider-cyral/cyral/internal/integration/teams" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/permission" policyv2 "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy/v2" + "github.com/cyralinc/terraform-provider-cyral/cyral/internal/policyset" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/regopolicy" "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository" repository_accessgateway "github.com/cyralinc/terraform-provider-cyral/cyral/internal/repository/accessgateway" @@ -56,6 +57,7 @@ func packagesSchemas() []core.PackageSchema { integration_teams.PackageSchema(), permission.PackageSchema(), policyv2.PackageSchema(), + policyset.PackageSchema(), regopolicy.PackageSchema(), repository.PackageSchema(), repository_accessgateway.PackageSchema(), diff --git a/docs/data-sources/policy_set.md b/docs/data-sources/policy_set.md new file mode 100644 index 00000000..2aab2b50 --- /dev/null +++ b/docs/data-sources/policy_set.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cyral_policy_set Data Source - terraform-provider-cyral" +subcategory: "" +description: |- + This data source provides information about a policy set. +--- + +# cyral_policy_set (Data Source) + +This data source provides information about a policy set. + + + +## Schema + +### Required + +- `id` (String) Identifier for the policy set. + +### Read-Only + +- `created` (Map of String) Information about when and by whom the policy set was created. +- `description` (String) Description of the policy set. +- `enabled` (Boolean) Indicates if the policy set is enabled. +- `last_updated` (Map of String) Information about when and by whom the policy set was last updated. +- `name` (String) Name of the policy set. +- `policies` (List of Object) List of policies that comprise the policy set. (see [below for nested schema](#nestedatt--policies)) +- `scope` (List of Object) Scope of the policy set. (see [below for nested schema](#nestedatt--scope)) +- `tags` (List of String) Tags associated with the policy set. +- `wizard_id` (String) The ID of the policy wizard used to create this policy set. +- `wizard_parameters` (String) Parameters passed to the wizard while creating the policy set. + + + +### Nested Schema for `policies` + +Read-Only: + +- `id` (String) +- `type` (String) + + + +### Nested Schema for `scope` + +Read-Only: + +- `repo_ids` (List of String) diff --git a/docs/resources/policy_set.md b/docs/resources/policy_set.md new file mode 100644 index 00000000..26c8e2e4 --- /dev/null +++ b/docs/resources/policy_set.md @@ -0,0 +1,80 @@ +# cyral_policy_set (Resource) + +This resource allows management of policy sets in the Cyral platform. + +-> Import ID syntax is `{policy_set_id}`. + +## Example Usage + +```terraform +resource "cyral_repository" "myrepo" { + type = "mongodb" + name = "myrepo" + + repo_node { + name = "node-1" + host = "mongodb.cyral.com" + port = 27017 + } + + mongodb_settings { + server_type = "standalone" + } +} + +resource "cyral_policy_set" "repo_lockdown_example" { + wizard_id = "repo-lockdown" + name = "default block with failopen" + description = "This default policy will block by default all queries for myrepo except the ones not parsed by Cyral" + enabled = true + tags = ["default block", "fail open"] + scope { + repo_ids = [cyral_repository.myrepo.id] + } + wizard_parameters = jsonencode({ + denyByDefault = true + failClosed = false + }) +} +``` + + + +## Schema + +### Required + +- `name` (String) Name of the policy set. +- `wizard_id` (String) The ID of the policy wizard used to create this policy set. +- `wizard_parameters` (String) Parameters passed to the wizard while creating the policy set. + +### Optional + +- `description` (String) Description of the policy set. +- `enabled` (Boolean) Indicates if the policy set is enabled. +- `scope` (Block List, Max: 1) Scope of the policy set. (see [below for nested schema](#nestedblock--scope)) +- `tags` (List of String) Tags associated with the policy set. + +### Read-Only + +- `created` (Map of String) Information about when and by whom the policy set was created. +- `id` (String) Identifier for the policy set. +- `last_updated` (Map of String) Information about when and by whom the policy set was last updated. +- `policies` (List of Object) List of policies that comprise the policy set. (see [below for nested schema](#nestedatt--policies)) + + + +### Nested Schema for `scope` + +Optional: + +- `repo_ids` (List of String) List of repository IDs that are in scope. Empty list means all repositories are in scope. + + + +### Nested Schema for `policies` + +Read-Only: + +- `id` (String) +- `type` (String) diff --git a/docs/resources/repository_conf_analysis.md b/docs/resources/repository_conf_analysis.md old mode 100755 new mode 100644 diff --git a/examples/resources/cyral_policy_set/resource.tf b/examples/resources/cyral_policy_set/resource.tf new file mode 100644 index 00000000..210626bb --- /dev/null +++ b/examples/resources/cyral_policy_set/resource.tf @@ -0,0 +1,29 @@ +resource "cyral_repository" "myrepo" { + type = "mongodb" + name = "myrepo" + + repo_node { + name = "node-1" + host = "mongodb.cyral.com" + port = 27017 + } + + mongodb_settings { + server_type = "standalone" + } +} + +resource "cyral_policy_set" "repo_lockdown_example" { + wizard_id = "repo-lockdown" + name = "default block with failopen" + description = "This default policy will block by default all queries for myrepo except the ones not parsed by Cyral" + enabled = true + tags = ["default block", "fail open"] + scope { + repo_ids = [cyral_repository.myrepo.id] + } + wizard_parameters = jsonencode({ + denyByDefault = true + failClosed = false + }) +} diff --git a/go.mod b/go.mod index 2f8d6af3..48e03e25 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,9 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 github.com/stretchr/testify v1.10.0 - golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f + golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d golang.org/x/oauth2 v0.24.0 - google.golang.org/grpc v1.68.0 + google.golang.org/grpc v1.68.1 google.golang.org/protobuf v1.35.2 ) @@ -80,18 +80,18 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yuin/goldmark v1.7.1 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect - github.com/zclconf/go-cty v1.15.0 // indirect + github.com/zclconf/go-cty v1.15.1 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.30.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect - golang.org/x/tools v0.27.0 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.28.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c8f11504..dc75acc8 100644 --- a/go.sum +++ b/go.sum @@ -211,8 +211,8 @@ github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= -github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= -github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.1 h1:RgQYm4j2EvoBRXOPxhUvxPzRrGDo1eCOhHXuGfrj5S0= +github.com/zclconf/go-cty v1.15.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= @@ -220,10 +220,10 @@ go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= +golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= @@ -232,15 +232,15 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -254,8 +254,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -265,24 +265,24 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/templates/resources/policy_set.md.tmpl b/templates/resources/policy_set.md.tmpl new file mode 100644 index 00000000..f1fe8d09 --- /dev/null +++ b/templates/resources/policy_set.md.tmpl @@ -0,0 +1,11 @@ +# {{ .Name | trimspace }} ({{ .Type | trimspace }}) + +{{ .Description | trimspace }} + +-> Import ID syntax is `{policy_set_id}`. + +## Example Usage + +{{ tffile "examples/resources/cyral_policy_set/resource.tf" }} + +{{ .SchemaMarkdown | trimspace }}