Skip to content

Commit

Permalink
feat(resource_access_policy): Add support for allowed_subnets
Browse files Browse the repository at this point in the history
  • Loading branch information
lrsmith-dev committed Jan 29, 2025
1 parent 8ad5c07 commit ac0f03c
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 5 deletions.
8 changes: 8 additions & 0 deletions docs/resources/cloud_access_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ resource "grafana_cloud_access_policy_token" "test" {

### Optional

- `conditions` (Block Set ) (see [below for nested schema](#nestedblock--conditions))
- `display_name` (String) Display name of the access policy. Defaults to the name.

### Read-Only
Expand All @@ -73,6 +74,13 @@ resource "grafana_cloud_access_policy_token" "test" {
- `policy_id` (String) ID of the access policy.
- `updated_at` (String) Last update date of the access policy.

<a id="nestedblock--conditions"></a>
### Nested Schema for `conditions`

Required:

- `allowed_subnets` (Set of String) IP range based access control for the access policy. Connections initiated from IP addresses outside of the specified ranges will be denied.

<a id="nestedblock--realm"></a>
### Nested Schema for `realm`

Expand Down
71 changes: 66 additions & 5 deletions internal/resources/cloud/resource_cloud_access_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cloud
import (
"context"
"fmt"
"net"
"strings"
"time"

Expand All @@ -22,6 +23,19 @@ var (
)

func resourceAccessPolicy() *common.Resource {
cloudAccessPolicyConditionSchema := &schema.Resource{
Schema: map[string]*schema.Schema{
"allowed_subnets": {
Type: schema.TypeSet,
Required: true,
Description: "Conditions that apply to the access policy,such as IP Allow lists.",
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateDiagFunc: validateCloudAccessPolicyAllowedSubnets,
},
},
},
}
cloudAccessPolicyRealmSchema := &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Expand Down Expand Up @@ -74,11 +88,10 @@ Required access policy scopes:

Schema: map[string]*schema.Schema{
"region": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Region where the API is deployed. Generally where the stack is deployed. Use the region list API to get the list of available regions: https://grafana.com/docs/grafana-cloud/developer-resources/api-reference/cloud-api/#list-regions.",
ValidateFunc: validation.StringIsNotEmpty,
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Region where the API is deployed. Generally where the stack is deployed. Use the region list API to get the list of available regions: https://grafana.com/docs/grafana-cloud/developer-resources/api-reference/cloud-api/#list-regions.",
},
"name": {
Type: schema.TypeString,
Expand Down Expand Up @@ -111,6 +124,12 @@ Required access policy scopes:
Required: true,
Elem: cloudAccessPolicyRealmSchema,
},
"conditions": {
Type: schema.TypeSet,
Optional: true,
Description: "Conditions for the access policy.",
Elem: cloudAccessPolicyConditionSchema,
},

// Computed
"policy_id": {
Expand Down Expand Up @@ -184,7 +203,9 @@ func createCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, client
DisplayName: &displayName,
Scopes: common.ListToStringSlice(d.Get("scopes").(*schema.Set).List()),
Realms: expandCloudAccessPolicyRealm(d.Get("realm").(*schema.Set).List()),
Conditions: expandCloudAccessPolicyConditions(d.Get("conditions").(*schema.Set).List()),
})

result, _, err := req.Execute()
if err != nil {
return apiError(err)
Expand Down Expand Up @@ -212,6 +233,7 @@ func updateCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, client
DisplayName: &displayName,
Scopes: common.ListToStringSlice(d.Get("scopes").(*schema.Set).List()),
Realms: expandCloudAccessPolicyRealm(d.Get("realm").(*schema.Set).List()),
Conditions: expandCloudAccessPolicyConditions(d.Get("conditions").(*schema.Set).List()),
})
if _, _, err = req.Execute(); err != nil {
return apiError(err)
Expand All @@ -238,6 +260,7 @@ func readCloudAccessPolicy(ctx context.Context, d *schema.ResourceData, client *
d.Set("display_name", result.DisplayName)
d.Set("scopes", result.Scopes)
d.Set("realm", flattenCloudAccessPolicyRealm(result.Realms))
d.Set("conditions", flattenCloudAccessPolicyConditions(result.Conditions))
d.Set("created_at", result.CreatedAt.Format(time.RFC3339))
if updated := result.UpdatedAt; updated != nil {
d.Set("updated_at", updated.Format(time.RFC3339))
Expand Down Expand Up @@ -266,6 +289,14 @@ func validateCloudAccessPolicyScope(v interface{}, path cty.Path) diag.Diagnosti
return nil
}

func validateCloudAccessPolicyAllowedSubnets(v interface{}, path cty.Path) diag.Diagnostics {
_, _, err := net.ParseCIDR(v.(string))
if err == nil {
return nil
}
return diag.Errorf("Invalid IP CIDR : %s.", v.(string))
}

func flattenCloudAccessPolicyRealm(realm []gcom.AuthAccessPolicyRealmsInner) []interface{} {
var result []interface{}

Expand All @@ -283,9 +314,39 @@ func flattenCloudAccessPolicyRealm(realm []gcom.AuthAccessPolicyRealmsInner) []i
"label_policy": labelPolicy,
})
}

return result
}

func flattenCloudAccessPolicyConditions(condition *gcom.AuthAccessPolicyConditions) []interface{} {
var result []interface{}
var allowedSubnets []string

for _, sn := range condition.GetAllowedSubnets() {
allowedSubnets = append(allowedSubnets, *sn.String)
}

result = append(result, map[string]interface{}{
"allowed_subnets": allowedSubnets,
})

return result
}

func expandCloudAccessPolicyConditions(condition []interface{}) *gcom.PostAccessPoliciesRequestConditions {

var result gcom.PostAccessPoliciesRequestConditions

for _, c := range condition {
c := c.(map[string]interface{})
for _, as := range c["allowed_subnets"].(*schema.Set).List() {
result.AllowedSubnets = append(result.AllowedSubnets, as.(string))
}
}

return &result
}

func expandCloudAccessPolicyRealm(realm []interface{}) []gcom.PostAccessPoliciesRequestRealmsInner {
var result []gcom.PostAccessPoliciesRequestRealmsInner

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package cloud_test

import (
"fmt"
"os"
"strings"

"testing"

"github.com/grafana/grafana-com-public-clients/go/gcom"
"github.com/grafana/terraform-provider-grafana/v3/internal/testutils"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestResourceAccessPolicy_AllowedSubnets(t *testing.T) {

t.Parallel()
testutils.CheckCloudAPITestsEnabled(t)

var policy gcom.AuthAccessPolicy

scopes := []string{
"accesspolicies:read",
}

initialAllowedSubnets := []string{
"10.0.0.29/32",
}
updatedAllowedSubnets := []string{
"10.0.0.29/32",
"10.0.0.20/32",
}

randomName := acctest.RandStringFromCharSet(6, acctest.CharSetAlpha)

resource.Test(t, resource.TestCase{
ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories,
CheckDestroy: testAccCloudAccessPolicyCheckDestroy("us", &policy),
Steps: []resource.TestStep{
{
Config: testAccCloudAccessPolicyConfigAllowedSubnets(randomName, "display name", "us", scopes, initialAllowedSubnets),
Check: resource.ComposeTestCheckFunc(
testAccCloudAccessPolicyCheckExists("grafana_cloud_access_policy.test", &policy),

resource.TestCheckResourceAttr("grafana_cloud_access_policy.test", "conditions.#", "1"),
resource.TestCheckResourceAttr("grafana_cloud_access_policy.test", "conditions.0.allowed_subnets.#", "1"),
resource.TestCheckResourceAttr("grafana_cloud_access_policy.test", "conditions.0.allowed_subnets.0", "10.0.0.29/32"),
),
},
{
Config: testAccCloudAccessPolicyConfigAllowedSubnets(randomName, "display name", "us", scopes, updatedAllowedSubnets),
Check: resource.ComposeTestCheckFunc(
testAccCloudAccessPolicyCheckExists("grafana_cloud_access_policy.test", &policy),

resource.TestCheckResourceAttr("grafana_cloud_access_policy.test", "conditions.#", "1"),
resource.TestCheckResourceAttr("grafana_cloud_access_policy.test", "conditions.0.allowed_subnets.#", "2"),
resource.TestCheckResourceAttr("grafana_cloud_access_policy.test", "conditions.0.allowed_subnets.0", "10.0.0.20/32"),
resource.TestCheckResourceAttr("grafana_cloud_access_policy.test", "conditions.0.allowed_subnets.1", "10.0.0.29/32"),
),
},
},
})
}

// Returns terraform manifests for an Cloud Access Policy with Allowed Subnets defined.
func testAccCloudAccessPolicyConfigAllowedSubnets(name, displayName, region string, scopes []string, allowedSubnets []string) string {
if displayName != "" {
displayName = fmt.Sprintf("display_name = \"%s\"", displayName)
}

return fmt.Sprintf(`
data "grafana_cloud_organization" "current" {
slug = "%[4]s"
}
resource "grafana_cloud_access_policy" "test" {
region = "%[5]s"
name = "%[1]s"
%[2]s
scopes = ["%[3]s"]
realm {
type = "org"
identifier = data.grafana_cloud_organization.current.id
}
conditions {
allowed_subnets = ["%[6]s"]
}
}
`, name, displayName, strings.Join(scopes, `","`), os.Getenv("GRAFANA_CLOUD_ORG"), region, strings.Join(allowedSubnets, `","`))
}

0 comments on commit ac0f03c

Please sign in to comment.