From ab97b85590a0a920e266a92160e788afc5ea220f Mon Sep 17 00:00:00 2001 From: Andrii Shaforostov Date: Fri, 30 Jun 2023 10:52:37 +0300 Subject: [PATCH] gitops abac rules (#119) ## What - Add Gitops ABAC Rule resource ## Why For creating rules for gitops entities ## Notes ## Checklist * [x] _I have read [CONTRIBUTING.md](https://github.com/codefresh-io/terraform-provider-codefresh/blob/master/CONTRIBUTING.md)._ * [x] _I have [allowed changes to my fork to be made](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)._ * [x] _I have added tests, assuming new tests are warranted_. * [x] _I understand that the `/test` comment will be ignored by the CI trigger [unless it is made by a repo admin or collaborator](https://codefresh.io/docs/docs/pipelines/triggers/git-triggers/#support-for-building-pull-requests-from-forks)._ --- client/client.go | 4 +- client/gitops_abac_rules.go | 206 +++++++++++++++++++ client/gql_client.go | 57 ++++++ client/user.go | 2 +- codefresh/env.go | 2 + codefresh/provider.go | 15 +- codefresh/resource_abac_rules.go | 268 +++++++++++++++++++++++++ codefresh/resource_abac_rules_test.go | 113 +++++++++++ codefresh/resource_permission.go | 2 +- docs/index.md | 1 + docs/resources/abac_rules.md | 73 +++++++ examples/abac_rules/main.tf | 21 ++ examples/abac_rules/provider.tf | 5 + examples/abac_rules/terraform.tfvars | 3 + examples/abac_rules/vars.tf | 12 ++ examples/abac_rules/versions.tf | 8 + go.sum | 4 + templates/resources/abac_rules.md.tmpl | 40 ++++ 18 files changed, 832 insertions(+), 4 deletions(-) create mode 100644 client/gitops_abac_rules.go create mode 100644 client/gql_client.go create mode 100644 codefresh/resource_abac_rules.go create mode 100644 codefresh/resource_abac_rules_test.go create mode 100644 docs/resources/abac_rules.md create mode 100644 examples/abac_rules/main.tf create mode 100644 examples/abac_rules/provider.tf create mode 100644 examples/abac_rules/terraform.tfvars create mode 100644 examples/abac_rules/vars.tf create mode 100644 examples/abac_rules/versions.tf create mode 100644 templates/resources/abac_rules.md.tmpl diff --git a/client/client.go b/client/client.go index 30abcb93..4228ae43 100644 --- a/client/client.go +++ b/client/client.go @@ -14,6 +14,7 @@ type Client struct { Token string TokenHeader string Host string + HostV2 string Client *http.Client } @@ -29,12 +30,13 @@ type RequestOptions struct { // NewClient returns a new client configured to communicate on a server with the // given hostname and to send an Authorization Header with the value of // token -func NewClient(hostname string, token string, tokenHeader string) *Client { +func NewClient(hostname string, hostnameV2 string, token string, tokenHeader string) *Client { if tokenHeader == "" { tokenHeader = "Authorization" } return &Client{ Host: hostname, + HostV2: hostnameV2, Token: token, TokenHeader: tokenHeader, Client: &http.Client{}, diff --git a/client/gitops_abac_rules.go b/client/gitops_abac_rules.go new file mode 100644 index 00000000..9ec7e5a8 --- /dev/null +++ b/client/gitops_abac_rules.go @@ -0,0 +1,206 @@ +package client + +import ( + "fmt" +) + +type EntityAbacAttribute struct { + Name string `json:"name"` + Key string `json:"key,omitempty"` + Value string `json:"value"` +} + +// GitopsAbacRule spec +type GitopsAbacRule struct { + ID string `json:"id,omitempty"` + AccountId string `json:"accountId,omitempty"` + EntityType string `json:"entityType"` + Teams []string `json:"teams"` + Tags []string `json:"tags,omitempty"` + Actions []string `json:"actions"` + Attributes []EntityAbacAttribute `json:"attributes"` +} + +type GitopsAbacRulesListResponse struct { + Data struct { + AbacRules []GitopsAbacRule `json:"abacRules"` + } `json:"data"` +} + +type GitopsAbacRuleResponse struct { + Data struct { + AbacRule GitopsAbacRule `json:"abacRule,omitempty"` + CreateAbacRule GitopsAbacRule `json:"createAbacRule,omitempty"` + RemoveAbacRule GitopsAbacRule `json:"removeAbacRule,omitempty"` + } `json:"data"` +} + +func (client *Client) GetAbacRulesList(entityType string) ([]GitopsAbacRule, error) { + request := GraphQLRequest{ + Query: ` + query AbacRules($accountId: String!, $entityType: AbacEntityValues!) { + abacRules(accountId: $accountId, entityType: $entityType) { + id + accountId + entityType + teams + tags + actions + attributes { + name + key + value + } + } + } + `, + Variables: map[string]interface{}{ + "accountId": "", + "entityType": entityType, + }, + } + + response, err := client.SendGqlRequest(request) + if err != nil { + fmt.Println("Error:", err) + return nil, err + } + + var gitopsAbacRulesResponse GitopsAbacRulesListResponse + err = DecodeGraphQLResponseInto(response, &gitopsAbacRulesResponse) + if err != nil { + return nil, err + } + + return gitopsAbacRulesResponse.Data.AbacRules, nil +} + +// GetAbacRuleByID - +func (client *Client) GetAbacRuleByID(id string) (*GitopsAbacRule, error) { + request := GraphQLRequest{ + Query: ` + query AbacRule($accountId: String!, $id: ID!) { + abacRule(accountId: $accountId, id: $id) { + id + accountId + entityType + teams + tags + actions + attributes { + name + key + value + } + } + } + `, + Variables: map[string]interface{}{ + "accountId": "", + "id": id, + }, + } + + response, err := client.SendGqlRequest(request) + if err != nil { + fmt.Println("Error:", err) + return nil, err + } + + var gitopsAbacRuleResponse GitopsAbacRuleResponse + err = DecodeGraphQLResponseInto(response, &gitopsAbacRuleResponse) + if err != nil { + return nil, err + } + + return &gitopsAbacRuleResponse.Data.AbacRule, nil +} + +func (client *Client) CreateAbacRule(gitopsAbacRule *GitopsAbacRule) (*GitopsAbacRule, error) { + + newAbacRule := &GitopsAbacRule{ + EntityType: gitopsAbacRule.EntityType, + Teams: gitopsAbacRule.Teams, + Tags: gitopsAbacRule.Tags, + Actions: gitopsAbacRule.Actions, + Attributes: gitopsAbacRule.Attributes, + } + + request := GraphQLRequest{ + Query: ` + mutation CreateAbacRule($accountId: String!, $createAbacRuleInput: CreateAbacRuleInput!) { + createAbacRule(accountId: $accountId, createAbacRuleInput: $createAbacRuleInput) { + id + accountId + entityType + teams + tags + actions + attributes { + name + key + value + } + } + } + `, + Variables: map[string]interface{}{ + "accountId": "", + "createAbacRuleInput": newAbacRule, + }, + } + + response, err := client.SendGqlRequest(request) + if err != nil { + fmt.Println("Error:", err) + return nil, err + } + + var gitopsAbacRuleResponse GitopsAbacRuleResponse + err = DecodeGraphQLResponseInto(response, &gitopsAbacRuleResponse) + if err != nil { + return nil, err + } + + return &gitopsAbacRuleResponse.Data.CreateAbacRule, nil +} + +func (client *Client) DeleteAbacRule(id string) (*GitopsAbacRule, error) { + request := GraphQLRequest{ + Query: ` + mutation RemoveAbacRule($accountId: String!, $id: ID!) { + removeAbacRule(accountId: $accountId, id: $id) { + id + accountId + entityType + teams + tags + actions + attributes { + name + key + value + } + } + } + `, + Variables: map[string]interface{}{ + "accountId": "", + "id": id, + }, + } + + response, err := client.SendGqlRequest(request) + if err != nil { + fmt.Println("Error:", err) + return nil, err + } + + var gitopsAbacRuleResponse GitopsAbacRuleResponse + err = DecodeGraphQLResponseInto(response, &gitopsAbacRuleResponse) + if err != nil { + return nil, err + } + + return &gitopsAbacRuleResponse.Data.RemoveAbacRule, nil +} diff --git a/client/gql_client.go b/client/gql_client.go new file mode 100644 index 00000000..7987a64d --- /dev/null +++ b/client/gql_client.go @@ -0,0 +1,57 @@ +package client + +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "net/http" +) + +// GraphQLRequest GraphQL query +type GraphQLRequest struct { + Query string `json:"query"` + Variables map[string]interface{} `json:"variables,omitempty"` +} + +func (client *Client) SendGqlRequest(request GraphQLRequest) ([]byte, error) { + jsonRequest, err := json.Marshal(request) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", client.HostV2, bytes.NewBuffer(jsonRequest)) + if err != nil { + return nil, err + } + + tokenHeader := client.TokenHeader + if tokenHeader == "" { + tokenHeader = "Authorization" + } + req.Header.Set(tokenHeader, client.Token) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode >= 400 { + bodyBytes, _ := ioutil.ReadAll(resp.Body) + return nil, errors.New(resp.Status + " " + string(bodyBytes)) + } + defer resp.Body.Close() + + var buf bytes.Buffer + _, err = buf.ReadFrom(resp.Body) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func DecodeGraphQLResponseInto(body []byte, target interface{}) error { + return json.Unmarshal(body, target) +} diff --git a/client/user.go b/client/user.go index ba1c6435..47b9d4d4 100644 --- a/client/user.go +++ b/client/user.go @@ -134,7 +134,7 @@ func (client *Client) AddUserToTeamByAdmin(userID string, accountID string, team return err } // new Client for accountAdmin - accountAdminClient := NewClient(client.Host, accountAdminToken, "x-access-token") + accountAdminClient := NewClient(client.Host, "", accountAdminToken, "x-access-token") usersTeam, err := accountAdminClient.GetTeamByName(team) if err != nil { return err diff --git a/codefresh/env.go b/codefresh/env.go index 8e16a185..3a53357b 100644 --- a/codefresh/env.go +++ b/codefresh/env.go @@ -4,7 +4,9 @@ const ( ENV_CODEFRESH_PLUGIN_DEBUG = "CODEFRESH_PLUGIN_DEBUG" ENV_CODEFRESH_PLUGIN_ADDR = "CODEFRESH_PLUGIN_ADDR" ENV_CODEFRESH_API_URL = "CODEFRESH_API_URL" + ENV_CODEFRESH_API2_URL = "CODEFRESH_API2_URL" ENV_CODEFRESH_API_KEY = "CODEFRESH_API_KEY" DEFAULT_CODEFRESH_API_URL = "https://g.codefresh.io/api" + DEFAULT_CODEFRESH_API2_URL = "https://g.codefresh.io/2.0/api/graphql" DEFAULT_CODEFRESH_PLUGIN_ADDR = "registry.terraform.io/-/codefresh" ) diff --git a/codefresh/provider.go b/codefresh/provider.go index fba23292..ffe6c0d1 100644 --- a/codefresh/provider.go +++ b/codefresh/provider.go @@ -23,6 +23,17 @@ func Provider() *schema.Provider { }, Description: fmt.Sprintf("The Codefresh API URL. Defaults to `%s`. Can also be set using the `%s` environment variable.", DEFAULT_CODEFRESH_API_URL, ENV_CODEFRESH_API_URL), }, + "api_url_v2": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: func() (interface{}, error) { + if url := os.Getenv(ENV_CODEFRESH_API2_URL); url != "" { + return url, nil + } + return DEFAULT_CODEFRESH_API2_URL, nil + }, + Description: fmt.Sprintf("The Codefresh gitops API URL. Defaults to `%s`. Can also be set using the `%s` environment variable.", DEFAULT_CODEFRESH_API2_URL, ENV_CODEFRESH_API2_URL), + }, "token": { Type: schema.TypeString, Optional: true, @@ -55,6 +66,7 @@ func Provider() *schema.Provider { "codefresh_step_types": resourceStepTypes(), "codefresh_user": resourceUser(), "codefresh_team": resourceTeam(), + "codefresh_abac_rules": resourceGitopsAbacRule(), }, ConfigureFunc: configureProvider, } @@ -63,9 +75,10 @@ func Provider() *schema.Provider { func configureProvider(d *schema.ResourceData) (interface{}, error) { apiURL := d.Get("api_url").(string) + apiURLV2 := d.Get("api_url_v2").(string) token := d.Get("token").(string) if token == "" { token = os.Getenv(ENV_CODEFRESH_API_KEY) } - return cfClient.NewClient(apiURL, token, ""), nil + return cfClient.NewClient(apiURL, apiURLV2, token, ""), nil } diff --git a/codefresh/resource_abac_rules.go b/codefresh/resource_abac_rules.go new file mode 100644 index 00000000..648b2564 --- /dev/null +++ b/codefresh/resource_abac_rules.go @@ -0,0 +1,268 @@ +package codefresh + +import ( + "context" + "fmt" + "log" + + cfClient "github.com/codefresh-io/terraform-provider-codefresh/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var validSetValues = []string{"REFRESH", "SYNC", "TERMINATE_SYNC", "VIEW_POD_LOGS", "APP_ROLLBACK"} + +func resourceGitopsAbacRule() *schema.Resource { + return &schema.Resource{ + Description: "Gitops Abac Rules are used to setup access control and allow to define which teams have access to which resources based on tags and attributes.", + Create: resourceGitopsAbacRuleCreate, + Read: resourceGitopsAbacRuleRead, + Update: resourceGitopsAbacRuleUpdate, + Delete: resourceGitopsAbacRuleDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "id": { + Description: "The abac rule ID.", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "entity_type": { + Description: ` +The type of resources the abac rules applies to. Possible values: + * gitopsApplications + `, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "gitopsApplications", + }, false), + }, + "teams": { + Description: "The Ids of teams the abac rules apply to.", + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": { + Description: ` +The effective tags to apply the permission. It supports 2 custom tags: + * untagged is a “tag” which refers to all resources that don't have any tag. + * (the star character) means all tags. + `, + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "actions": { + Description: ` +Action to be allowed. Possible values: + * REFRESH + * SYNC + * TERMINATE_SYNC + * VIEW_POD_LOGS + * APP_ROLLBACK + `, + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "attribute": { + Description: "Resource attribute that need to be validated", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "key": { + Type: schema.TypeString, + Optional: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + CustomizeDiff: func(ctx context.Context, diff *schema.ResourceDiff, v interface{}) error { + actions := diff.Get("actions").(*schema.Set).List() + + for _, action := range actions { + actionStr := action.(string) + if !contains(validSetValues, actionStr) { + return fmt.Errorf("Invalid action value: %s", actionStr) + } + } + + return nil + }, + } +} + +func contains(slice []string, element string) bool { + for _, item := range slice { + if item == element { + return true + } + } + return false +} + +func resourceGitopsAbacRuleCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfClient.Client) + + abacRule := *mapResourceToGitopsAbacRule(d) + + newGitopsAbacRule, err := client.CreateAbacRule(&abacRule) + if err != nil { + return err + } + if newGitopsAbacRule == nil { + return fmt.Errorf("resourceGitopsAbacRuleCreate - failed to create abac rule, empty response") + } + + d.SetId(newGitopsAbacRule.ID) + + return resourceGitopsAbacRuleRead(d, meta) +} + +func resourceGitopsAbacRuleRead(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*cfClient.Client) + + abacRuleID := d.Id() + if abacRuleID == "" { + d.SetId("") + return nil + } + + abacRule, err := client.GetAbacRuleByID(abacRuleID) + if err != nil { + return err + } + + err = mapGitopsAbacRuleToResource(abacRule, d) + if err != nil { + return err + } + + return nil +} + +func resourceGitopsAbacRuleUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfClient.Client) + + abacRule := *mapResourceToGitopsAbacRule(d) + resp, err := client.CreateAbacRule(&abacRule) + if err != nil { + return err + } + + deleteErr := resourceGitopsAbacRuleDelete(d, meta) + if deleteErr != nil { + log.Printf("[WARN] failed to delete permission %v: %v", abacRule, deleteErr) + } + d.SetId(resp.ID) + + return resourceGitopsAbacRuleRead(d, meta) +} + +func resourceGitopsAbacRuleDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cfClient.Client) + + _, err := client.DeleteAbacRule(d.Id()) + if err != nil { + return err + } + + return nil +} + +func flattenAttributes(attributes []cfClient.EntityAbacAttribute) []map[string]interface{} { + var res = make([]map[string]interface{}, len(attributes)) + for i, attribute := range attributes { + m := make(map[string]interface{}) + m["name"] = attribute.Name + m["key"] = attribute.Key + m["value"] = attribute.Value + res[i] = m + } + return res +} + +func mapGitopsAbacRuleToResource(abacRule *cfClient.GitopsAbacRule, d *schema.ResourceData) error { + + err := d.Set("id", abacRule.ID) + if err != nil { + return err + } + + err = d.Set("entity_type", abacRule.EntityType) + if err != nil { + return err + } + + err = d.Set("teams", abacRule.Teams) + if err != nil { + return err + } + + err = d.Set("tags", abacRule.Tags) + if err != nil { + return err + } + + err = d.Set("actions", abacRule.Actions) + if err != nil { + return err + } + + if len(abacRule.Attributes) > 0 { + err = d.Set("attribute", flattenAttributes(abacRule.Attributes)) + if err != nil { + return err + } + } + + return nil +} + +func mapResourceToGitopsAbacRule(d *schema.ResourceData) *cfClient.GitopsAbacRule { + + tagsI := d.Get("tags").(*schema.Set).List() + var tags []string + if len(tagsI) > 0 { + tags = convertStringArr(tagsI) + } else { + tags = []string{"*", "untagged"} + } + + abacRule := &cfClient.GitopsAbacRule{ + ID: d.Id(), + EntityType: d.Get("entity_type").(string), + Teams: convertStringArr(d.Get("teams").(*schema.Set).List()), + Tags: tags, + Actions: convertStringArr(d.Get("actions").(*schema.Set).List()), + Attributes: []cfClient.EntityAbacAttribute{}, + } + + attributes := d.Get("attribute").([]interface{}) + for idx := range attributes { + attr := cfClient.EntityAbacAttribute{ + Name: d.Get(fmt.Sprintf("attribute.%v.name", idx)).(string), + Key: d.Get(fmt.Sprintf("attribute.%v.key", idx)).(string), + Value: d.Get(fmt.Sprintf("attribute.%v.value", idx)).(string), + } + abacRule.Attributes = append(abacRule.Attributes, attr) + } + return abacRule +} diff --git a/codefresh/resource_abac_rules_test.go b/codefresh/resource_abac_rules_test.go new file mode 100644 index 00000000..b7f2e657 --- /dev/null +++ b/codefresh/resource_abac_rules_test.go @@ -0,0 +1,113 @@ +package codefresh + +import ( + "fmt" + cfClient "github.com/codefresh-io/terraform-provider-codefresh/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + funk "github.com/thoas/go-funk" +) + +func TestAccCodefreshAbacRulesConfig(t *testing.T) { + resourceName := "codefresh_abac_rules.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCodefreshContextDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCodefreshAbacRulesConfig( + "gitopsApplications", + "LABEL", + "KEY", + "VALUE", + []string{"SYNC", "REFRESH"}, + []string{"production", "*"}, + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckCodefreshAbacRulesExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "entity_type", "gitopsApplications"), + resource.TestCheckResourceAttr(resourceName, "actions.0", "REFRESH"), + resource.TestCheckResourceAttr(resourceName, "actions.1", "SYNC"), + resource.TestCheckResourceAttr(resourceName, "attribute.0.name", "LABEL"), + resource.TestCheckResourceAttr(resourceName, "attribute.0.key", "KEY"), + resource.TestCheckResourceAttr(resourceName, "attribute.0.value", "VALUE"), + resource.TestCheckResourceAttr(resourceName, "tags.0", "*"), + resource.TestCheckResourceAttr(resourceName, "tags.1", "production"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckCodefreshAbacRulesExists(resource string) resource.TestCheckFunc { + return func(state *terraform.State) error { + rs, ok := state.RootModule().Resources[resource] + if !ok { + return fmt.Errorf("Not found: %s", resource) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + + abacRuleID := rs.Primary.ID + + apiClient := testAccProvider.Meta().(*cfClient.Client) + _, err := apiClient.GetAbacRuleByID(abacRuleID) + + if err != nil { + return fmt.Errorf("error fetching abac rule with ID %s. %s", abacRuleID, err) + } + return nil + } +} + +// CONFIGS +func testAccCodefreshAbacRulesConfig(entityType, name, key, value string, actions, tags []string) string { + escapeString := func(str string) string { + if str == "null" { + return str // null means Terraform should ignore this field + } + return fmt.Sprintf(`"%s"`, str) + } + tagsEscaped := funk.Map(tags, escapeString).([]string) + actionsEscaped := funk.Map(actions, escapeString).([]string) + + attribute := "" + if name != "" && value != "" { + keyStr := "" + if key != "" { + keyStr = fmt.Sprintf(`key = %s`, escapeString(key)) + } + attribute = fmt.Sprintf(` + attribute { + name = %s + %s + value = %s + } + `, escapeString(name), keyStr, escapeString(value)) + } + + return fmt.Sprintf(` + data "codefresh_team" "users" { + name = "users" + } + + resource "codefresh_abac_rules" "test" { + teams = [data.codefresh_team.users.id] + entity_type = %s + actions = [%s] + %s + tags = [%s] + } +`, escapeString(entityType), strings.Join(actionsEscaped[:], ","), attribute, strings.Join(tagsEscaped[:], ",")) +} diff --git a/codefresh/resource_permission.go b/codefresh/resource_permission.go index e1b77f3a..82aa53cb 100644 --- a/codefresh/resource_permission.go +++ b/codefresh/resource_permission.go @@ -123,7 +123,7 @@ func resourcePermissionCreate(d *schema.ResourceData, meta interface{}) error { return err } if newPermission == nil { - return fmt.Errorf("resourcePermissionCreate - failed to create permission, empty responce") + return fmt.Errorf("resourcePermissionCreate - failed to create permission, empty response") } d.SetId(newPermission.ID) diff --git a/docs/index.md b/docs/index.md index 66b0f942..f6998389 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,6 +21,7 @@ The key can be passed either as the provider's attribute or as environment varia ### Optional - `api_url` (String) The Codefresh API URL. Defaults to `https://g.codefresh.io/api`. Can also be set using the `CODEFRESH_API_URL` environment variable. +- `api_url_v2` (String) The Codefresh gitops API URL. Defaults to `https://g.codefresh.io/2.0/api/graphql`. Can also be set using the `CODEFRESH_API2_URL` environment variable. - `token` (String) The Codefresh API token. Can also be set using the `CODEFRESH_API_KEY` environment variable. ## Managing Resources Across Different Accounts diff --git a/docs/resources/abac_rules.md b/docs/resources/abac_rules.md new file mode 100644 index 00000000..c7cdcb0a --- /dev/null +++ b/docs/resources/abac_rules.md @@ -0,0 +1,73 @@ +--- +page_title: "codefresh_abac_rules Resource - terraform-provider-codefresh" +subcategory: "" +description: |- + Gitops Abac Rules are used to setup access control and allow to define which teams have access to which resources based on tags and attributes. +--- + +# codefresh_abac_rules (Resource) + +Gitops Abac Rules are used to setup access control and allow to define which teams have access to which resources based on tags and attributes. + +See the [Access Control documentation](https://codefresh.io/docs/docs/administration/account-user-management/access-control/). + +## Example usage + +```hcl +resource "codefresh_team" "developers" { + name = "developers" + + users = [ + "5efc3cb6355c6647041b6e49", + "59009221c102763beda7cf04" + ] +} + +resource "codefresh_abac_rules" "app_rule" { + entity_type = "gitopsApplications" + teams = [data.codefresh_team.developers.id] + actions = ["REFRESH", "SYNC", "TERMINATE_SYNC", "APP_ROLLBACK"] + + attribute { + name = "LABEL" + key = "KEY" + value = "VALUE" + } +} + +``` + + +## Schema + +### Required + +- `actions` (Set of String) Action to be allowed. Possible values: + * REFRESH + * SYNC + * TERMINATE_SYNC + * VIEW_POD_LOGS + * APP_ROLLBACK +- `entity_type` (String) The type of resources the abac rules applies to. Possible values: + * gitopsApplications +- `teams` (Set of String) The Ids of teams the abac rules apply to. + +### Optional + +- `attribute` (Block List) Resource attribute that need to be validated (see [below for nested schema](#nestedblock--attribute)) +- `id` (String) The abac rule ID. +- `tags` (Set of String) The effective tags to apply the permission. It supports 2 custom tags: + * untagged is a “tag” which refers to all resources that don't have any tag. + * (the star character) means all tags. + + +### Nested Schema for `attribute` + +Required: + +- `name` (String) +- `value` (String) + +Optional: + +- `key` (String) \ No newline at end of file diff --git a/examples/abac_rules/main.tf b/examples/abac_rules/main.tf new file mode 100644 index 00000000..f60c53b4 --- /dev/null +++ b/examples/abac_rules/main.tf @@ -0,0 +1,21 @@ +data "codefresh_team" "admins" { + name = "admins" +} + +data "codefresh_team" "users" { + name = "users" +} + +resource "codefresh_abac_rules" "app_rule" { + entity_type = "gitopsApplications" + teams = [data.codefresh_team.users.id] + actions = ["REFRESH", "SYNC", "TERMINATE_SYNC", "VIEW_POD_LOGS", "APP_ROLLBACK"] + + attribute { + name = "LABEL" + key = "KEY" + value = "VALUE" + } + + tags = ["dev", "untagged"] +} diff --git a/examples/abac_rules/provider.tf b/examples/abac_rules/provider.tf new file mode 100644 index 00000000..79fc432c --- /dev/null +++ b/examples/abac_rules/provider.tf @@ -0,0 +1,5 @@ +provider "codefresh" { + api_url = var.api_url + api_url_v2 = var.api_url_v2 + token = var.token # If token isn't set the provider expects the $CODEFRESH_API_KEY env variable +} \ No newline at end of file diff --git a/examples/abac_rules/terraform.tfvars b/examples/abac_rules/terraform.tfvars new file mode 100644 index 00000000..2999903e --- /dev/null +++ b/examples/abac_rules/terraform.tfvars @@ -0,0 +1,3 @@ +api_url = "https://my-codefresh.example.com/api" +api_url_v2 = "https://my-codefresh.example.com/2.0/api/graphql" +token = "" diff --git a/examples/abac_rules/vars.tf b/examples/abac_rules/vars.tf new file mode 100644 index 00000000..578727c4 --- /dev/null +++ b/examples/abac_rules/vars.tf @@ -0,0 +1,12 @@ +variable api_url { + type = string +} + +variable api_url_v2 { + type = string +} + +variable token { + type = string + default = "" +} \ No newline at end of file diff --git a/examples/abac_rules/versions.tf b/examples/abac_rules/versions.tf new file mode 100644 index 00000000..9dee49eb --- /dev/null +++ b/examples/abac_rules/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + codefresh = { + source = "codefresh-io/codefresh" + version = "~> 0.1" + } + } +} \ No newline at end of file diff --git a/go.sum b/go.sum index 83f938a6..81d41568 100644 --- a/go.sum +++ b/go.sum @@ -117,6 +117,7 @@ github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvx github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -234,6 +235,7 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= @@ -700,6 +702,7 @@ github.com/sashamelentyev/usestdlibvars v1.23.0 h1:01h+/2Kd+NblNItNeux0veSL5cBF1 github.com/sashamelentyev/usestdlibvars v1.23.0/go.mod h1:YPwr/Y1LATzHI93CqoPUN/2BzGQ/6N/cl/KwgR0B/aU= github.com/sclevine/yj v0.0.0-20210612025309-737bdf40a5d1 h1:2cp8mZ+gRxIx/5GoLcQmmXzJuKdM1cSE2dmvFn3udJE= github.com/sclevine/yj v0.0.0-20210612025309-737bdf40a5d1/go.mod h1:PkEqqiiBYB87KgvpQj2r0wtRjDKEhhLRarGCubegp7E= +github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/securego/gosec/v2 v2.15.0 h1:v4Ym7FF58/jlykYmmhZ7mTm7FQvN/setNm++0fgIAtw= github.com/securego/gosec/v2 v2.15.0/go.mod h1:VOjTrZOkUtSDt2QLSJmQBMWnvwiQPEjg0l+5juIqGk8= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -844,6 +847,7 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= diff --git a/templates/resources/abac_rules.md.tmpl b/templates/resources/abac_rules.md.tmpl new file mode 100644 index 00000000..bbb4b9d5 --- /dev/null +++ b/templates/resources/abac_rules.md.tmpl @@ -0,0 +1,40 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +See the [Access Control documentation](https://codefresh.io/docs/docs/administration/account-user-management/access-control/). + +## Example usage + +```hcl +resource "codefresh_team" "developers" { + name = "developers" + + users = [ + "5efc3cb6355c6647041b6e49", + "59009221c102763beda7cf04" + ] +} + +resource "codefresh_abac_rules" "app_rule" { + entity_type = "gitopsApplications" + teams = [data.codefresh_team.developers.id] + actions = ["REFRESH", "SYNC", "TERMINATE_SYNC", "APP_ROLLBACK"] + + attribute { + name = "LABEL" + key = "KEY" + value = "VALUE" + } +} + +``` + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file