diff --git a/cyral/data_source_cyral_regopolicy_instance.go b/cyral/data_source_cyral_regopolicy_instance.go new file mode 100644 index 00000000..df9f013e --- /dev/null +++ b/cyral/data_source_cyral_regopolicy_instance.go @@ -0,0 +1,110 @@ +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" +) + +type GetRegopolicyInstancesResponse struct { + Instances []*PolicyInstance `json:"instances"` +} + +func (resp *GetRegopolicyInstancesResponse) WriteToSchema(d *schema.ResourceData) error { + if err := writeRegopolicyInstancesToDataSourceSchema(resp.Instances, d); err != nil { + return err + } + // creating a new id for data source + d.SetId(uuid.New().String()) + return nil +} + +func writeRegopolicyInstancesToDataSourceSchema(instances []*PolicyInstance, d *schema.ResourceData) error { + var instancesList []interface{} + for _, instance := range instances { + instancesList = append(instancesList, map[string]interface{}{ + "name": instance.Name, + "description": instance.Description, + "template_id": instance.TemplateId, + "tags": instance.TagsAsInterface(), + }) + } + if err := d.Set("regopolicy_instance_list", instancesList); err != nil { + return err + } + return nil +} + +func dataSourceRegopolicyInstanceReadConfig() ResourceOperationConfig { + return ResourceOperationConfig{ + Name: "DatalabelDataSourceRead", + HttpMethod: http.MethodGet, + CreateURL: func(d *schema.ResourceData, c *client.Client) string { + nameFilter := d.Get("name").(string) + typeFilter := d.Get("type").(string) + var pathParams string + if nameFilter != "" { + pathParams = fmt.Sprintf("/%s", nameFilter) + } + queryParams := urlQuery(map[string]string{ + "type": typeFilter, + }) + + return fmt.Sprintf("https://%s/v1/datalabels%s%s", c.ControlPlane, pathParams, queryParams) + }, + NewResponseData: func(d *schema.ResourceData) ResponseData { + nameFilter := d.Get("name").(string) + if nameFilter == "" { + return &GetDataLabelsResponse{} + } else { + return &GetDataLabelResponse{} + } + }, + } +} + +func dataSourceRegopolicyInstance() *schema.Resource { + return &schema.Resource{ + Description: "Retrieve and filter policy instances. See also resource [`cyral_regopolicy`](../resources/regopolicy_instance.md).", + ReadContext: ReadResource(dataSourceDatalabelReadConfig()), + Schema: map[string]*schema.Schema{ + "regopolicy_instance_list": { + Description: "List of existing regopolicy instances satisfying given filter criteria.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + + "name": { + Description: "Name of the policy instance.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "Description for the policy instance.", + Type: schema.TypeString, + Required: true, + }, + "template_id": { + Description: "Template Id on which the instance was based", + Type: schema.TypeString, + Required: true, + }, + "tags": { + Description: "Tags used to categorize policy instance.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + }, + }, + }, + }, + } +} diff --git a/cyral/model_regopolicy_instance.go b/cyral/model_regopolicy_instance.go new file mode 100644 index 00000000..d7b23310 --- /dev/null +++ b/cyral/model_regopolicy_instance.go @@ -0,0 +1,116 @@ +package cyral + +import ( + "google.golang.org/protobuf/types/known/timestamppb" +) + +type Scope struct { + RepoIds []string `json:"repoIds,omitempty"` +} + +const ( + actorUser = "USER" + actorApiClient = "API_CLIENT" +) + +func actorTypes() []string { + return []string{ + actorUser, + actorApiClient, + } +} + +const ( + categoryTypeUnknown = "UNKNOWN" + categoryTypePredefined = "SECURITY" + categoryTypeCustom = "GRANT" + categoryUserDefined = "USER_DEFINED" +) + +func categoryTypes() []string { + return []string{ + categoryTypeUnknown, + categoryTypePredefined, + categoryTypeCustom, + categoryUserDefined, + } +} + +type ChangeInfo struct { + Actor string `json:"actor,omitempty"` + ActorType string `json:"actorType,omitempty"` + Timestamp *timestamppb.Timestamp `json:"timestamp,omitempty"` +} + +type Key struct { + Id string `json:"id,omitempty"` + Category string `json:"category,omitempty"` +} + +func (pi *PolicyInstance) TagsAsInterface() []interface{} { + var tagIfaces []interface{} + for _, tag := range pi.Tags { + tagIfaces = append(tagIfaces, tag) + } + return tagIfaces +} + +type PolicyInstance struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + TemplateId string `json:"templateId,omitempty"` + Parameters string `json:"parameters,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Scope *Scope `json:"scope,omitempty"` + Tags []string `json:"tags,omitempty"` + LastUpdated *ChangeInfo `json:"lastUpdated,omitempty"` + Created *ChangeInfo `json:"created,omitempty"` +} + +// used for 'data' in requests +type PolicyInstanceDataRequest struct { + Instance *PolicyInstance `json:"instance,omitempty"` + Duration string `json:"duration,omitempty"` +} + +type ListPolicyInstancePartial struct { + Key Key `json:"key,omitempty"` + Name string `json:"name,omitempty"` + TemplateId string `json:"templateId,omitempty"` +} + +type InsertPolicyInstanceRequest PolicyInstanceDataRequest + +type UpdatePolicyInstanceRequest PolicyInstanceDataRequest + +type DeletePolicyInstanceRequest struct { + Id string `json:"id,omitempty"` + Category string `json:"category,omitempty"` +} + +type ReadPolicyInstanceRequest struct { + Id string `json:"id,omitempty"` + Category string `json:"category,omitempty"` +} + +type ReadPolicyInstancesRequest struct { + Category string `json:"category,omitempty"` +} + +type InsertPolicyInstanceResponse struct { + Id string `json:"id,omitempty"` + Category string `json:"category,omitempty"` +} + +type UpdatePolicyInstanceResponse struct { +} + +type DeletePolicyInstanceResponse struct { + Instance PolicyInstance `json:"instance,omitempty"` +} + +type ReadPolicyInstanceResponse PolicyInstance + +type ReadPolicyInstancesResponse struct { + Instances []ListPolicyInstancePartial `json:"instances,omitempty"` +} diff --git a/cyral/provider.go b/cyral/provider.go index 8590a676..9f956302 100644 --- a/cyral/provider.go +++ b/cyral/provider.go @@ -81,6 +81,7 @@ func Provider() *schema.Provider { "cyral_sidecar_cft_template": dataSourceSidecarCftTemplate(), "cyral_sidecar_id": dataSourceSidecarID(), "cyral_sidecar_instance_ids": dataSourceSidecarInstanceIDs(), + "cyral_regopolicy_instance": dataSourceRegopolicyInstance(), }, ResourcesMap: map[string]*schema.Resource{ @@ -106,6 +107,7 @@ func Provider() *schema.Provider { "cyral_integration_sumo_logic": resourceIntegrationSumoLogic(), "cyral_policy": resourcePolicy(), "cyral_policy_rule": resourcePolicyRule(), + "cyral_regopolicy_instance": resourceRegopolicyInstance(), "cyral_repository": resourceRepository(), "cyral_repository_binding": resourceRepositoryBinding(), "cyral_repository_conf_analysis": resourceRepositoryConfAnalysis(), diff --git a/cyral/resource_cyral_regopolicy_instance.go b/cyral/resource_cyral_regopolicy_instance.go new file mode 100644 index 00000000..125c6dd9 --- /dev/null +++ b/cyral/resource_cyral_regopolicy_instance.go @@ -0,0 +1,353 @@ +package cyral + +import ( + "context" + "fmt" + "net/http" + + "github.com/cyralinc/terraform-provider-cyral/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// mand +func (r *InsertPolicyInstanceRequest) ReadFromSchema(d *schema.ResourceData) error { + scope := &Scope{} + for _, scopeObj := range d.Get("scope").(*schema.Set).List() { + scopeMap := scopeObj.(map[string]interface{}) + repoIds := scopeMap["repo_ids"] + for _, repoId := range repoIds.([]interface{}) { + scope.RepoIds = append(scope.RepoIds, repoId.(string)) + } + } + + lastUpdated := &ChangeInfo{} + for _, lastUpdatedObj := range d.Get("last_updated").(*schema.Set).List() { + lastUpdatedMap := lastUpdatedObj.(map[string]interface{}) + actor := lastUpdatedMap["actor"].(string) + actorType := lastUpdatedMap["actor_type"].(string) + timestamp := lastUpdatedMap["timestamp"].(int64) + lastUpdated.Actor = actor + lastUpdated.ActorType = actorType + lastUpdated.Timestamp = ×tamppb.Timestamp{Seconds: timestamp} + } + + created := &ChangeInfo{} + for _, createdObj := range d.Get("created").(*schema.Set).List() { + createdMap := createdObj.(map[string]interface{}) + actor := createdMap["actor"].(string) + actorType := createdMap["actor_type"].(string) + timestamp := createdMap["timestamp"].(int64) + created.Actor = actor + created.ActorType = actorType + created.Timestamp = ×tamppb.Timestamp{Seconds: timestamp} + } + + duration := d.Get("duration").(string) + + r.Instance = &PolicyInstance{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TemplateId: d.Get("template_id").(string), + Parameters: d.Get("parameters").(string), + Enabled: d.Get("enabled").(bool), + Scope: scope, + LastUpdated: lastUpdated, + Created: created, + } + r.Duration = duration + return nil +} + +// mand +func (r *UpdatePolicyInstanceRequest) ReadFromSchema(d *schema.ResourceData) error { + scope := &Scope{} + for _, scopeObj := range d.Get("scope").([]interface{}) { + scopeMap := scopeObj.(map[string]interface{}) + repoIds := scopeMap["repo_ids"] + for _, repoId := range repoIds.([]interface{}) { + scope.RepoIds = append(scope.RepoIds, repoId.(string)) + } + } + + lastUpdated := &ChangeInfo{} + for _, lastUpdatedObj := range d.Get("last_updated").([]interface{}) { + lastUpdatedMap := lastUpdatedObj.(map[string]interface{}) + actor := lastUpdatedMap["actor"].(string) + actorType := lastUpdatedMap["actor_type"].(string) + timestamp := lastUpdatedMap["timestamp"].(int64) + lastUpdated.Actor = actor + lastUpdated.ActorType = actorType + lastUpdated.Timestamp = ×tamppb.Timestamp{Seconds: timestamp} + } + + created := &ChangeInfo{} + for _, createdObj := range d.Get("created").([]interface{}) { + createdMap := createdObj.(map[string]interface{}) + actor := createdMap["actor"].(string) + actorType := createdMap["actor_type"].(string) + timestamp := createdMap["timestamp"].(int64) + created.Actor = actor + created.ActorType = actorType + created.Timestamp = ×tamppb.Timestamp{Seconds: timestamp} + } + + duration := d.Get("duration").(string) + + r.Instance = &PolicyInstance{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + TemplateId: d.Get("template_id").(string), + Parameters: d.Get("parameters").(string), + Enabled: d.Get("enabled").(bool), + Scope: scope, + LastUpdated: lastUpdated, + Created: created, + } + r.Duration = duration + + return nil +} + +// mand +func (r *InsertPolicyInstanceResponse) WriteToSchema(d *schema.ResourceData) error { + regoPolicyId := r.Id + regoPolicyCategory := r.Category + d.SetId(marshalComposedID([]string{regoPolicyId, regoPolicyCategory}, "/")) + d.Set("regopolicy_id", regoPolicyId) + d.Set("category", regoPolicyCategory) + return nil +} + +// mand +func (r *ReadPolicyInstanceResponse) WriteToSchema(d *schema.ResourceData) error { + d.Set("name", r.Name) + d.Set("description", r.Description) + d.Set("template_id", r.TemplateId) + d.Set("parameters", r.Parameters) + d.Set("enabled", r.Enabled) + if r.Scope != nil { + repoIds := r.Scope.RepoIds + scope := map[string]interface{}{ + "repo_ids": repoIds, + } + d.Set("scope", scope) + } + d.Set("tags", r.Tags) + lastUpdated := map[string]interface{}{ + "actor": r.LastUpdated.Actor, + "actor_type": r.LastUpdated.ActorType, + "timestamp": r.LastUpdated.Timestamp, + } + d.Set("last_updated", lastUpdated) + created := map[string]interface{}{ + "actor": r.Created.Actor, + "actor_type": r.Created.ActorType, + "timestamp": r.Created.Timestamp, + } + d.Set("created", created) + return nil +} + +// Reading the instance +var ReadRegopolicyInstanceConfig = ResourceOperationConfig{ + Name: "RegopolicyInstanceRead", + HttpMethod: http.MethodGet, + CreateURL: func(d *schema.ResourceData, c *client.Client) string { + category := d.Get("category").(string) + id := d.Get("regopolicy_id").(string) + return fmt.Sprintf("https://%s/v1/regopolicies/instances/%s/%s", c.ControlPlane, category, id) + }, + NewResponseData: func(d *schema.ResourceData) ResponseData { + return &ReadPolicyInstanceResponse{} + }, +} + +func resourceRegopolicyInstance() *schema.Resource { + return &schema.Resource{ + Description: "Manages the regopolicy instances.", + // Creating the instance + CreateContext: CreateResource( + ResourceOperationConfig{ + Name: "RegopolicyInstanceCreate", + HttpMethod: http.MethodPost, + CreateURL: func(d *schema.ResourceData, c *client.Client) string { + category := d.Get("category").(string) + return fmt.Sprintf("https://%s/v1/regopolicies/instances/%s", c.ControlPlane, category) + }, + // payload to pass + NewResourceData: func() ResourceData { + return &InsertPolicyInstanceRequest{} + }, + // return from API + NewResponseData: func(d *schema.ResourceData) ResponseData { + return &InsertPolicyInstanceResponse{} + }, + }, + // reading to update terraform values + ReadRegopolicyInstanceConfig, + ), + // Read + ReadContext: ReadResource(ReadRegopolicyInstanceConfig), + UpdateContext: UpdateResource( + ResourceOperationConfig{ + Name: "RegopolicyInstanceUpdate", + HttpMethod: http.MethodPut, + CreateURL: func(d *schema.ResourceData, c *client.Client) string { + category := d.Get("category").(string) + id := d.Get("regopolicy_id").(string) + return fmt.Sprintf("https://%s/v1/regopolicies/instances/%s/%s", c.ControlPlane, category, id) + }, + // payload to pass + NewResourceData: func() ResourceData { + return &UpdatePolicyInstanceRequest{} + }, + }, + // reading to update terraform values + ReadRegopolicyInstanceConfig, + ), + DeleteContext: DeleteResource( + ResourceOperationConfig{ + Name: "RegopolicyInstanceDelete", + HttpMethod: http.MethodDelete, + CreateURL: func(d *schema.ResourceData, c *client.Client) string { + category := d.Get("category").(string) + id := d.Get("id").(string) + return fmt.Sprintf("https://%s/v1/regopolicies/instances/%s/%s", c.ControlPlane, category, id) + }, + }, + ), + Schema: map[string]*schema.Schema{ + "regopolicy_id": { + Description: "ID for the policy instance.", + Type: schema.TypeString, + ForceNew: true, + Computed: true, + }, + "category": { + Description: "Category of the policy instance.", + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringInSlice(append(categoryTypes(), ""), false), + }, + "name": { + Description: "Name of the policy instance.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "Description for the policy instance.", + Type: schema.TypeString, + Optional: true, + }, + "template_id": { + Description: "Template Id on which the instance was based", + Type: schema.TypeString, + Required: true, + }, + "parameters": { + Description: "Parameters for the policy instance (matches the template parameter schema)", + Type: schema.TypeString, + Optional: true, + }, + "enabled": { + Description: "Whether the policy is enabled or not", + Type: schema.TypeBool, + Optional: true, + }, + "scope": { + Description: "Object that defines the scope of the policy, i.e. where it is applicable", + Type: schema.TypeSet, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "repo_ids": { + Description: "List of repo ids where the policy is applicable", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + }, + }, + }, + "tags": { + Description: "Tags used to categorize policy instance.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + }, + "last_updated": { + Description: "Object that defines the actor and the time when the instance last update happened.", + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "actor": { + Description: "Actor identification (e.g. email)", + Type: schema.TypeString, + Computed: true, + }, + "actor_type": { + Description: "Type of actor, if it is user or api", + Type: schema.TypeString, + Computed: true, + ValidateFunc: validation.StringInSlice(append(actorTypes(), ""), false), + }, + "timestamp": { + Description: "Timestamp for action of updating", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "created": { + Description: "Object that defines the actor and the time when the instance creation happened.", + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "actor": { + Description: "Actor identification (e.g. email)", + Type: schema.TypeString, + Computed: true, + }, + "actor_type": { + Description: "Type of actor, if it is user or api", + Type: schema.TypeString, + Computed: true, + ValidateFunc: validation.StringInSlice(append(actorTypes(), ""), false), + }, + "timestamp": { + Description: "Timestamp for the action of creating", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "duration": { + Description: "Duration of the policy instance.", + Type: schema.TypeString, + Optional: true, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: func( + ctx context.Context, + d *schema.ResourceData, + m interface{}, + ) ([]*schema.ResourceData, error) { + d.Set(RepositoryIDKey, d.Id()) + return []*schema.ResourceData{d}, nil + }, + }, + } +} diff --git a/docs/resources/regopolicy_instance.md b/docs/resources/regopolicy_instance.md new file mode 100644 index 00000000..478bc1ee --- /dev/null +++ b/docs/resources/regopolicy_instance.md @@ -0,0 +1,38 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cyral_datalabel Resource - cyral" +subcategory: "" +description: |- + Manages data labels. Data labels are part of the Cyral Data Map https://cyral.com/docs/policy/datamap. +--- + +# cyral_datalabel (Resource) + +Manages data labels. Data labels are part of the Cyral [Data Map](https://cyral.com/docs/policy/datamap). + +## Example Usage + +```terraform +resource "cyral_datalabel" "NAME" { + name = "NAME" + description = "Customer name" + tags = ["PII", "SENSITIVE"] +} +``` + + + +## Schema + +### Required + +- `name` (String) Name of the data label. + +### Optional + +- `description` (String) Description of the data label. +- `tags` (List of String) Tags that can be used to categorize data labels. + +### Read-Only + +- `id` (String) The ID of this resource.