Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENG-12949: Policy engine providers #547

Merged
merged 17 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21
go-version: 1.22
- name: Import GPG key
id: import_gpg
uses: crazy-max/[email protected]
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM hashicorp/terraform:1.7.5 as terraform
FROM hashicorp/terraform:1.9.0 as terraform

FROM golang:1.21.5-alpine3.17 AS build
FROM golang:1.22.5-alpine3.20 AS build
WORKDIR /go/src/cyral
COPY main.go go.mod go.sum ./
COPY client/ client/
Expand All @@ -12,7 +12,7 @@ RUN gofmt -w . \
&& GOOS=darwin GOARCH=amd64 go build -o out/darwin_amd64/terraform-provider-cyral . \
&& GOOS=linux GOARCH=amd64 go build -o out/linux_amd64/terraform-provider-cyral .

FROM alpine:3.20.0 as output
FROM alpine:3.20.1 as output
ARG VERSION
RUN mkdir -p /root/.terraform.d/plugins/local/terraform/cyral/${VERSION:?You must set the VERSION build argument}
COPY --from=build /go/src/cyral/out/ /root/.terraform.d/plugins/local/terraform/cyral/${VERSION}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var resourceContextHandler = core.DefaultContextHandler{

func resourceSchema() *schema.Resource {
return &schema.Resource{
DeprecationMessage: "For control planes `>= v4.15`, please use resource `cyral_policy_v2` instead.",
Description: "Manages [policies](https://cyral.com/docs/reference/policy). See also: " +
"[Policy Rule](./policy_rule.md). For more information, see the " +
"[Policy Guide](https://cyral.com/docs/policy/overview).",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"testing"

"github.com/cyralinc/terraform-provider-cyral/cyral/internal/policy"
"github.com/cyralinc/terraform-provider-cyral/cyral/internal/deprecated/policy"
"github.com/cyralinc/terraform-provider-cyral/cyral/provider"
"github.com/cyralinc/terraform-provider-cyral/cyral/utils"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
Expand Down
22 changes: 22 additions & 0 deletions cyral/internal/policy/v2/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package policyv2

const (
resourceName = "cyral_policy_v2"
dataSourceName = resourceName
apiPathLocal = "v2/policies/local"
apiPathGlobal = "v2/policies/global"
apiPathApproval = "v2/policies/approval"
)

func getAPIPath(policyType string) string {
switch policyType {
case "POLICY_TYPE_LOCAL", "local":
return apiPathLocal
case "POLICY_TYPE_GLOBAL", "global":
return apiPathGlobal
case "POLICY_TYPE_APPROVAL", "approval":
return apiPathApproval
default:
return ""
}
}
111 changes: 111 additions & 0 deletions cyral/internal/policy/v2/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package policyv2

import (
"fmt"

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

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

var dsContextHandler = core.DefaultContextHandler{
ResourceName: dataSourceName,
ResourceType: resourcetype.DataSource,
SchemaWriterFactoryGetMethod: func(_ *schema.ResourceData) core.SchemaWriter { return &PolicyV2{} },
ReadUpdateDeleteURLFactory: func(d *schema.ResourceData, c *client.Client) string {
return fmt.Sprintf("https://%s/%s/%s", c.ControlPlane, getAPIPath(d.Get("type").(string)), d.Get("id").(string))
},
}

func dataSourceSchema() *schema.Resource {
return &schema.Resource{
Description: "This data source provides information about a policy.",
ReadContext: dsContextHandler.ReadContext(),
Schema: map[string]*schema.Schema{
"id": {
Description: "Identifier for the policy, unique within the policy type.",
Type: schema.TypeString,
Required: true,
},
"type": {
Description: "Type of the policy, one of [`local`, `global`]",
Type: schema.TypeString,
Required: true,
},
"name": {
Description: "Name of the policy.",
Type: schema.TypeString,
Computed: true,
},
"description": {
Description: "Description of the policy.",
Type: schema.TypeString,
Computed: true,
},
"enabled": {
Description: "Indicates if the policy is enabled.",
Type: schema.TypeBool,
Computed: true,
},
"tags": {
Description: "Tags associated with the policy for categorization.",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"scope": {
Description: "Scope of the policy. If empty or omitted, all repositories are in scope.",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"repo_ids": {
Description: "List of repository IDs that are in scope.",
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
},
},
},
},
"valid_from": {
Description: "Time when the policy comes into effect. If omitted, the policy is in effect immediately.",
Type: schema.TypeString,
Computed: true,
},
"valid_until": {
Description: "Time after which the policy is no longer in effect. If omitted, the policy is in effect indefinitely.",
Type: schema.TypeString,
Computed: true,
},
"document": {
Description: "The actual policy document in JSON format. It must conform to the schema for the policy type.",
Type: schema.TypeString,
Computed: true,
},
"last_updated": {
Description: "Information about when and by whom the policy was last updated.",
Type: schema.TypeMap,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"created": {
Description: "Information about when and by whom the policy was created.",
Type: schema.TypeMap,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"enforced": {
Description: "Indicates if the policy is enforced. If not enforced, no action is taken based on the policy, but alerts are triggered for violations.",
Type: schema.TypeBool,
Computed: true,
},
},
}
}
140 changes: 140 additions & 0 deletions cyral/internal/policy/v2/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package policyv2

import (
"fmt"

"github.com/cyralinc/terraform-provider-cyral/cyral/utils"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// 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"`
}

// ToMap converts ChangeInfo to a map
func (c ChangeInfo) ToMap() map[string]interface{} {
return map[string]interface{}{
"actor": c.Actor,
"actor_type": c.ActorType,
"timestamp": c.Timestamp,
}
}

// PolicyV2 represents the top-level policy structure
type PolicyV2 struct {
Policy Policy `json:"policy,omitempty"`
}

type Scope struct {
RepoIds []string `json:"repoIds,omitempty"`
}

// ToMap converts Scope to a list of maps
func (s *Scope) ToMap() []map[string]interface{} {
return []map[string]interface{}{
{
"repo_ids": s.RepoIds,
},
}
}

// Policy represents the policy details
type Policy struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Enabled bool `json:"enabled,omitempty"`
Scope *Scope `json:"scope,omitempty"`
Tags []string `json:"tags,omitempty"`
ValidFrom string `json:"validFrom,omitempty"`
ValidUntil string `json:"validUntil,omitempty"`
Document string `json:"document,omitempty"`
LastUpdated ChangeInfo `json:"lastUpdated,omitempty"`
Created ChangeInfo `json:"created,omitempty"`
Enforced bool `json:"enforced,omitempty"`
Type string `json:"type,omitempty"`
}

// WriteToSchema writes the policy data to the schema
func (r PolicyV2) WriteToSchema(d *schema.ResourceData) error {
if err := d.Set("id", r.Policy.ID); err != nil {
return fmt.Errorf("error setting 'id' field: %w", err)
}
if err := d.Set("name", r.Policy.Name); err != nil {
return fmt.Errorf("error setting 'name' field: %w", err)
}
if err := d.Set("description", r.Policy.Description); err != nil {
return fmt.Errorf("error setting 'description' field: %w", err)
}
if err := d.Set("enabled", r.Policy.Enabled); err != nil {
return fmt.Errorf("error setting 'enabled' field: %w", err)
}
if err := d.Set("tags", r.Policy.Tags); err != nil {
return fmt.Errorf("error setting 'tags' field: %w", err)
}
if err := d.Set("valid_from", r.Policy.ValidFrom); err != nil {
return fmt.Errorf("error setting 'valid_from' field: %w", err)
}
if err := d.Set("valid_until", r.Policy.ValidUntil); err != nil {
return fmt.Errorf("error setting 'valid_until' field: %w", err)
}
if err := d.Set("document", r.Policy.Document); err != nil {
return fmt.Errorf("error setting 'document' field: %w", err)
}

// Use the ToMap method to set the last_updated and created fields
if err := d.Set("last_updated", r.Policy.LastUpdated.ToMap()); err != nil {
return fmt.Errorf("error setting 'last_updated' field: %w", err)
}
if err := d.Set("created", r.Policy.Created.ToMap()); err != nil {
return fmt.Errorf("error setting 'created' field: %w", err)
}
if err := d.Set("enforced", r.Policy.Enforced); err != nil {
return fmt.Errorf("error setting 'enforced' field: %w", err)
}
if r.Policy.Type != "" {
if err := d.Set("type", r.Policy.Type); err != nil {
return fmt.Errorf("error setting 'type' field: %w", err)
}
}
if r.Policy.Scope != nil {
if err := d.Set("scope", r.Policy.Scope.ToMap()); err != nil {
return fmt.Errorf("error setting 'scope' field: %w", err)
}
}
d.SetId(r.Policy.ID)
return nil
}

// ReadFromSchema reads the policy data from the schema
func (r *PolicyV2) ReadFromSchema(d *schema.ResourceData) error {
r.Policy.ID = d.Get("id").(string)
r.Policy.Name = d.Get("name").(string)
r.Policy.Description = d.Get("description").(string)
r.Policy.Enabled = d.Get("enabled").(bool)
r.Policy.Tags = utils.ConvertFromInterfaceList[string](d.Get("tags").([]interface{}))
r.Policy.ValidFrom = d.Get("valid_from").(string)
r.Policy.ValidUntil = d.Get("valid_until").(string)
r.Policy.Document = d.Get("document").(string)
r.Policy.Enforced = d.Get("enforced").(bool)
r.Policy.Type = d.Get("type").(string)
if v, ok := d.GetOk("scope"); ok {
r.Policy.Scope = scopeFromInterface(v.([]interface{}))
}
return nil
}

// scopeFromInterface converts the map to a Scope struct
func scopeFromInterface(s []interface{}) *Scope {
if len(s) == 0 || s[0] == nil {
return nil
}
m := s[0].(map[string]interface{})
scope := Scope{
RepoIds: utils.ConvertFromInterfaceList[string](m["repo_ids"].([]interface{})),
}
return &scope
}
Loading
Loading