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

[CC-4083] IP Allowlist Feature for Consul Cluster #455

Merged
merged 13 commits into from
Feb 17, 2023
Merged

Conversation

Achooo
Copy link
Contributor

@Achooo Achooo commented Feb 10, 2023

🛠️ Description

  • Adding a new field ip_allowlist to the hcp_consul_cluster resource to support IP Allowlisting - a Block List (list of objects).
  • Validation Rules:
    • No duplicate CIDR allowed
    • Maximum of 3 IP Allowlist entries allowed
    • Max length of 255 characters for the description field

🏗️ Acceptance tests

  • Are there any feature flags that are required to use this functionality?
  • Have you added an acceptance test for the functionality being added?
  • Have you run the acceptance tests on this branch?

Output from acceptance testing:

~/Github/HashiCorp/terraform-provider-hcp CC-4083/ip-allowlist* ⇡ 16m 2s
❯ make testacc TESTARGS='-run=TestAccConsulCluster'
==> Checking that code complies with gofmt requirements...
golangci-lint run --config ./golangci-config.yml 
TF_ACC=1 go test ./internal/... -v -run=TestAccConsulCluster -timeout 210m
testing: warning: no tests to run
PASS
ok      github.com/hashicorp/terraform-provider-hcp/internal/clients    (cached) [no tests to run]
testing: warning: no tests to run
PASS
ok      github.com/hashicorp/terraform-provider-hcp/internal/consul     (cached) [no tests to run]
testing: warning: no tests to run
PASS
ok      github.com/hashicorp/terraform-provider-hcp/internal/input      (cached) [no tests to run]
=== RUN   TestAccConsulCluster
--- PASS: TestAccConsulCluster (1421.46s)
PASS
ok      github.com/hashicorp/terraform-provider-hcp/internal/provider   1421.972s
...

End to End Tests (int)

Resource CREATE

  1. Generate the provider locally with make dev in root of repo
  2. Make a test folder test/
  3. In test folder, create a main.tf with following :
locals {
  hvn_region = "us-west-2"
  cluster_id = "consul-tf-cluster"
  hvn_id     = "consul-tf-hvn"
}

terraform {
  required_providers {
    hcp = {
      source  = "localhost/providers/hcp"
      version = "0.0.1"
    }
  }
}

resource "hcp_hvn" "main" {
  hvn_id         = local.hvn_id
  cloud_provider = "aws"
  region         = local.hvn_region
  cidr_block     = "172.25.32.0/20"
}

resource "hcp_consul_cluster" "main" {
  cluster_id      = local.cluster_id
  hvn_id          = hcp_hvn.main.hvn_id
  public_endpoint = true
  tier            = "development"

  ip_allowlist {
    address     = "192.163.1.0/24"
    description = "this is an IPV4 address"
  }
}
  1. Run terraform init
  2. Run terraform apply
  3. Results: tf apply logs

Screenshot 2023-02-15 at 4 44 57 PM

Datasource (READ ONLY)

  1. Modify main.tf with the following:
data hcp_consul_cluster "test" {
    cluster_id = local.cluster_id
}

output "verify_allowlist_output" {
    description = "output of the ALLOWLIST"
    value = data.hcp_consul_cluster.test.ip_allowlist
}
  1. run terraform apply
  2. Results:

Screenshot 2023-02-15 at 5 11 31 PM

Resource UPDATE

  1. Modify the hcp_consul_cluster resource:
resource "hcp_consul_cluster" "main" {
  cluster_id      = local.cluster_id
  hvn_id          = hcp_hvn.main.hvn_id
  public_endpoint = true
  tier            = "development"

  ip_allowlist {
    address     = "160.20.10.0/24"
    description = " modified!"
  }
}
  1. Run terraform apply
  2. Results: tf update logs

Screenshot 2023-02-15 at 4 58 02 PM

The data source output we added in the step before now shows the updated `ip_allowlist`

Screenshot 2023-02-15 at 5 12 07 PM

Validation

No more than 3 CIDRs

Screenshot 2023-02-10 at 1 30 09 AM

Description Length

Screenshot 2023-02-10 at 1 10 04 AM

Invalid CIDR

Screenshot 2023-02-10 at 1 08 36 AM

@@ -296,8 +296,8 @@ func Test_validateConsulClusterTier(t *testing.T) {
expected: diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Error,
Summary: "expected dev to be one of [DEVELOPMENT STANDARD PLUS]",
Detail: "expected dev to be one of [DEVELOPMENT STANDARD PLUS] (value is case-insensitive).",
Summary: "expected dev to be one of [DEVELOPMENT STANDARD PLUS PREMIUM]",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating the go SDK (hashicorp/hcp-sdk-go#164) added this field. Had to fix this test

@Achooo Achooo force-pushed the CC-4083/ip-allowlist branch from dff3ddd to 8bb7a16 Compare February 10, 2023 16:58
}
`)

func consulClusterConfig(clusterID string, opt string) string {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created this helper function that creates the hcp_consul_cluster for the tests cases.

),
},
// Tests update failure for IP Allowlist with too duplicate CIDRs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to move all these tests in their own TestAccConsulCluster_IPAllowlist function, but this is likely going to increase CI time as resources need to be created for the tests to run. If we prefer clarity, that makes sense. I was reading this contributing guide and it seemed to prefer minimal resource creation.

@@ -178,6 +178,40 @@ func validateConsulClusterSize(v interface{}, path cty.Path) diag.Diagnostics {
return diagnostics
}

func validateConsulClusterCIDR(v interface{}, path cty.Path) diag.Diagnostics {
Copy link
Contributor Author

@Achooo Achooo Feb 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be added into the buildAllowlist validations to remove the code in this file.

However, these validations are pretty neat for terraform plan

Screenshot 2023-02-10 at 1 08 36 AM

In contrast, the duplicates will only be checked when the actual apply runs:

Screenshot 2023-02-10 at 1 28 44 AM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the more ideal user experience would be to check for duplicates at the plan stage as well. Unless that's something only the backend can validate?

Copy link
Contributor Author

@Achooo Achooo Feb 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See this comment for discussion on this. I can't add a validateFunc for the schema type (schema.TypeList not supported) to check for duplicates on all items

Copy link
Contributor Author

@Achooo Achooo Feb 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if you prefer another existing pattern to verify on plan, as I'm new to this repo!

@@ -629,9 +673,10 @@ func resourceConsulClusterUpdate(ctx context.Context, d *schema.ResourceData, me
// Confirm update fields have been changed
sizeChanged := d.HasChange("size")
versionChanged := d.HasChange("min_consul_version")
ipAllowlistChanged := d.HasChange("ip_allowlist")
Copy link
Contributor Author

@Achooo Achooo Feb 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For any updates:

  • Detect changes in ip_allowlist
  • if changes, buildIPAllowlist() to validate there are no duplicates. This will return an empty list if there are no ip_allowlist entries, and otherwise a list with consul model objects that the API
  • Send update request

@@ -329,6 +352,13 @@ func resourceConsulClusterCreate(ctx context.Context, d *schema.ResourceData, me
// The peering happens within the secondary cluster create operation.
autoHvnToHvnPeering := d.Get("auto_hvn_to_hvn_peering").(bool)

// Convert ip_allowlist to consul model.
Copy link
Contributor Author

@Achooo Achooo Feb 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For creates:

  • Get ip_allowlistvalue
    -buildIPAllowlist() to validate there are no duplicates in the entries. This will also return an empty list if there are no ip_allowlist entries, and otherwise a list with consul model objects that the API expects.
  • Send create request

@Achooo
Copy link
Contributor Author

Achooo commented Feb 10, 2023

Updates seem to be failing with the following errors from the consul service backend.

 Error: error updating Consul cluster (test-202302101209): [PATCH /consul/2021-02-04/organizations/{cluster.location.organization_id}/projects/{cluster.location.project_id}/clusters/{cluster.id}][400] Update default  &{Code:3 Details:[] Error:only the following fields can be updated at this time: [consul_version config.capacity_config.size] Message:only the following fields can be updated at this time: [consul_version config.capacity_config.size]}

I think the ip_allowlist field needs to be allowed for updates.

Edit: backend fix in the works, this client code remains 👍🏽

Ready to review, but I will update the test output and screenshots once fixed on the backend side.

@Achooo Achooo marked this pull request as ready for review February 10, 2023 17:32
@Achooo Achooo requested a review from a team as a code owner February 10, 2023 17:32
@Achooo Achooo requested a review from a team February 10, 2023 17:32
address := cidrMap["address"].(string)
description := cidrMap["description"].(string)

ip, _, _ := net.ParseCIDR(address)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validation (validators.go) occurs first , so this should be valid (no error). I omitted handling it, but happy to do it if this feels like bad practice.

@Achooo Achooo changed the title [CC4083] IP Allowlist Feature [CC-4083] IP Allowlist Feature Feb 10, 2023
@Achooo Achooo changed the title [CC-4083] IP Allowlist Feature [CC-4083] IP Allowlist Feature for Consul Cluster Feb 10, 2023
}

// Seen holds validated CIDRs.
seen := make(map[string]struct{}, len(cidrs))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a hashmap to validate duplicates. See the following comment on why I went with this approach:

https://github.com/hashicorp/terraform-provider-hcp/pull/455/files#r1102997970

Alternatives discussed in that comment:

  • use schema.TypeSet for the ip_allowlist field
  • don't do validation ( backend does it)

@@ -767,3 +832,37 @@ func resourceConsulClusterImport(ctx context.Context, d *schema.ResourceData, me

return []*schema.ResourceData{d}, nil
}

// buildIPAllowlist validates there are no duplicate CIDRs and returns a consul model.
func buildIPAllowlist(cidrs []interface{}) ([]*consulmodels.HashicorpCloudConsul20210204CidrRange, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is only used to check for duplicates, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed duplicate logic now, but it also converts the ip_allowlist from a terraform list (type []interface{}) to a model that can be used by the consul client.

Used across a few operations so it's nice to have it

@@ -534,6 +565,19 @@ func setConsulClusterResourceData(d *schema.ResourceData, cluster *consulmodels.
}
}

var ipAllowlist []interface{}
Copy link
Contributor Author

@Achooo Achooo Feb 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part in func setConsulClusterResourceData(...) is used for the read operation. See this line - nothing else is needed for read.

It's also used in to read the created/updated cluster once the API request is completed.

CC @loshz

address = "172.25.16.0/24"
description = "this is an IPV4 address"
}
`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be worthwhile to include an IPV6 address in the test case objects? Or do we currently only support IPV4 addresses?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I can do that, I believe they are allowed. I checked the original RFC for this feature, but it's not explicit. Maybe @loshz can confirm?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm looks like creation fails with IPV6...

Copy link
Contributor Author

@Achooo Achooo Feb 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JolisaBrownHashiCorp We've confirmed that IPV6 is disallowed. I've added a validation to inform the user if they try to use one in this commit: 75ddd6a

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. @Achooo Thanks for following up! May I ask what your slack handle is? I'm getting a deletion error when attempting to run the test suite and would like to inbox you the logs to discuss.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, it's @ashvitha, I've sent you a DM :)

@@ -193,7 +243,11 @@ func TestAccConsulCluster(t *testing.T) {
Config: testConfig(setTestAccConsulClusterConfig(updatedConsulCluster)),
Check: resource.ComposeTestCheckFunc(
testAccCheckConsulClusterExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "size", "SMALL"),
resource.TestCheckResourceAttr(resourceName, "size", "MEDIUM"),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to change to a STANDARD. This update test was failing with the following error:

 Error: error updating Consul cluster (test-202302131304): [PATCH /consul/2021-02-04/organizations/{cluster.location.organization_id}/projects/{cluster.location.project_id}/clusters/{cluster.id}][400] Update default  &{Code:3 Details:[] Error:Development tier clusters only have one size and cannot be changed. Message:Development tier clusters only have one size and cannot be changed.}

Development clusters cannot change sizes

@Achooo Achooo requested a review from bcmdarroch February 14, 2023 19:02
@Achooo Achooo force-pushed the CC-4083/ip-allowlist branch from 7688839 to 468c6f5 Compare February 15, 2023 21:22
@Achooo Achooo force-pushed the CC-4083/ip-allowlist branch 3 times, most recently from 569011a to 8f90e92 Compare February 15, 2023 22:45
@Achooo Achooo force-pushed the CC-4083/ip-allowlist branch from 8f90e92 to fde3e7b Compare February 15, 2023 22:46
Copy link
Contributor

@loshz loshz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! Have left a few minor comments/nits.

Planning to test this locally asap and report back.

@@ -534,6 +564,19 @@ func setConsulClusterResourceData(d *schema.ResourceData, cluster *consulmodels.
}
}

ipAllowlist := make([]map[string]interface{}, len(cluster.Config.NetworkConfig.IPAllowlist))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the nil NetworkConfig check here too?


// buildIPAllowlist returns a consul model for the IP allowlist.
func buildIPAllowlist(cidrs []interface{}) ([]*consulmodels.HashicorpCloudConsul20210204CidrRange, error) {
IPAllowList := make([]*consulmodels.HashicorpCloudConsul20210204CidrRange, len(cidrs))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor nit:

Suggested change
IPAllowList := make([]*consulmodels.HashicorpCloudConsul20210204CidrRange, len(cidrs))
ipAllowList := make([]*consulmodels.HashicorpCloudConsul20210204CidrRange, len(cidrs))

@loshz
Copy link
Contributor

loshz commented Feb 16, 2023

All of the E2E tests work for me on remote-dev. I was able to successfully create and update a cluster with an IP Allowlist, as well as load an existing cluster as a data source.

Achooo and others added 2 commits February 16, 2023 16:53
Commit formatting suggestions

Co-authored-by: Dan Bond <[email protected]>
Copy link
Contributor

@loshz loshz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid work, Ash! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants