diff --git a/internal/provider/resource_hvn.go b/internal/provider/resource_hvn.go index bbf8420c4..858f570c4 100644 --- a/internal/provider/resource_hvn.go +++ b/internal/provider/resource_hvn.go @@ -12,7 +12,6 @@ import ( networkmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-network/preview/2020-09-07/models" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-hcp/internal/clients" ) @@ -71,12 +70,12 @@ func resourceHvn() *schema.Resource { }, // Optional inputs "cidr_block": { - Description: "The CIDR range of the HVN. If this is not provided, the service will provide a default value.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.IsCIDR, - Computed: true, + Description: "The CIDR range of the HVN. If this is not provided, the service will provide a default value.", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateDiagFunc: validateCIDRBlock, + Computed: true, }, // Computed outputs "organization_id": { diff --git a/internal/provider/resource_hvn_route.go b/internal/provider/resource_hvn_route.go index 22e797819..a16d0c3c7 100644 --- a/internal/provider/resource_hvn_route.go +++ b/internal/provider/resource_hvn_route.go @@ -11,7 +11,6 @@ import ( sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-hcp/internal/clients" ) @@ -52,11 +51,11 @@ func resourceHvnRoute() *schema.Resource { ValidateDiagFunc: validateSlugID, }, "destination_cidr": { - Description: "The destination CIDR of the HVN route.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.IsCIDR, + Description: "The destination CIDR of the HVN route.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validateCIDRBlock, }, "target_link": { Description: "A unique URL identifying the target of the HVN route. Examples of the target: [`aws_network_peering`](aws_network_peering.md), [`aws_transit_gateway_attachment`](aws_transit_gateway_attachment.md)", diff --git a/internal/provider/validators.go b/internal/provider/validators.go index 0c0e76f3d..3c7dc979d 100644 --- a/internal/provider/validators.go +++ b/internal/provider/validators.go @@ -2,6 +2,7 @@ package provider import ( "fmt" + "net" "regexp" "strings" @@ -195,3 +196,80 @@ func validateVaultClusterTier(v interface{}, path cty.Path) diag.Diagnostics { return diagnostics } + +func validateCIDRBlock(v interface{}, path cty.Path) diag.Diagnostics { + var diagnostics diag.Diagnostics + + // validRanges contains the set of IP ranges considered valid. + var validRanges = []net.IPNet{ + { + // 10.*.*.* + IP: net.IPv4(10, 0, 0, 0), + Mask: net.IPv4Mask(255, 0, 0, 0), + }, + { + // 192.168.*.* + IP: net.IPv4(192, 168, 0, 0), + Mask: net.IPv4Mask(255, 255, 0, 0), + }, + { + // 172.[16-31].*.* + IP: net.IPv4(172, 16, 0, 0), + Mask: net.IPv4Mask(255, 240, 0, 0), + }, + } + + // parse the string as CIDR notation IP address and prefix length. + ip, net, err := net.ParseCIDR(v.(string)) + if err != nil { + msg := "unable to parse string as CIDR notation IP address" + diagnostics = append(diagnostics, diag.Diagnostic{ + Severity: diag.Error, + Summary: msg, + Detail: msg, + AttributePath: path, + }) + + return diagnostics + } + + // validate if the IP address is contained in one of the expected ranges. + valid := false + for _, validRange := range validRanges { + valueSize, _ := net.Mask.Size() + validRangeSize, _ := validRange.Mask.Size() + if validRange.Contains(ip) && valueSize >= validRangeSize { + // Flip flag if IP is found within any 1 of 3 ranges. + valid = true + } + } + + // Check flag and return an error if the IP address is not contained within + // any of the expected ranges. + if !valid { + msg := fmt.Sprintf("must match pattern of 10.*.*.* with prefix greater than /8," + + "or 172.[16-31].*.* with prefix greater than /12, or " + + "192.168.*.* with prefix greater than /16; where * is any number from [0-255]") + diagnostics = append(diagnostics, diag.Diagnostic{ + Severity: diag.Error, + Summary: msg, + Detail: msg, + AttributePath: path, + }) + } + + // Validate the address passed is the start of the CIDR range. + // This happens after we verify the IP address is a valid RFC 1819 + // range to avoid causing confusion with a misguiding error message. + if !ip.Equal(net.IP) { + msg := fmt.Sprintf("invalid CIDR range start %s, should have been %s", ip, net.IP) + diagnostics = append(diagnostics, diag.Diagnostic{ + Severity: diag.Error, + Summary: msg, + Detail: msg, + AttributePath: path, + }) + } + + return diagnostics +} diff --git a/internal/provider/validators_test.go b/internal/provider/validators_test.go index 1b2ba1885..0b40154f2 100644 --- a/internal/provider/validators_test.go +++ b/internal/provider/validators_test.go @@ -391,3 +391,245 @@ func Test_validateVaultClusterTier(t *testing.T) { }) } } + +func Test_validateCIDRBlock(t *testing.T) { + tcs := map[string]struct { + input string + expected diag.Diagnostics + }{ + "blank input string": { + input: "", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse string as CIDR notation IP address", + Detail: "unable to parse string as CIDR notation IP address", + AttributePath: nil, + }, + }, + }, + "invalid cidr notation": { + input: "192.168.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse string as CIDR notation IP address", + Detail: "unable to parse string as CIDR notation IP address", + AttributePath: nil, + }, + }, + }, + "invalid characters": { + input: "someString", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse string as CIDR notation IP address", + Detail: "unable to parse string as CIDR notation IP address", + AttributePath: nil, + }, + }, + }, + "valid cidr block": { + input: "10.0.0.0/24", + expected: diag.Diagnostics(nil), + }, + "valid cidr block 2": { + input: "10.255.255.0/24", + expected: diag.Diagnostics(nil), + }, + "invalid cidr notation 2": { + input: "10.256.0.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse string as CIDR notation IP address", + Detail: "unable to parse string as CIDR notation IP address", + AttributePath: nil, + }, + }, + }, + "invalid cidr notation 3": { + input: "10.0.256.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse string as CIDR notation IP address", + Detail: "unable to parse string as CIDR notation IP address", + AttributePath: nil, + }, + }, + }, + "invalid characters 2": { + input: "10.255.255asdfasdfaqsd.250", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse string as CIDR notation IP address", + Detail: "unable to parse string as CIDR notation IP address", + AttributePath: nil, + }, + }, + }, + "valid cidr block 3": { + input: "192.168.0.0/24", + expected: diag.Diagnostics(nil), + }, + "valid cidr block 4": { + input: "192.168.255.0/24", + expected: diag.Diagnostics(nil), + }, + "invalid cidr notation 4": { + input: "192.168.256.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse string as CIDR notation IP address", + Detail: "unable to parse string as CIDR notation IP address", + AttributePath: nil, + }, + }, + }, + "invalid pattern": { + input: "192.0.0.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + Detail: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + AttributePath: nil, + }, + }, + }, + "valid cidr block 5": { + input: "172.16.0.0/24", + expected: diag.Diagnostics(nil), + }, + "valid cidr block 6": { + input: "172.17.0.0/24", + expected: diag.Diagnostics(nil), + }, + "valid cidr block 7": { + input: "172.18.0.0/24", + expected: diag.Diagnostics(nil), + }, + "valid cidr block 8": { + input: "172.30.0.0/24", + expected: diag.Diagnostics(nil), + }, + "valid cidr block 9": { + input: "172.20.0.0/24", + expected: diag.Diagnostics(nil), + }, + "valid cidr block 10": { + input: "172.31.0.0/24", + expected: diag.Diagnostics(nil), + }, + "invalid pattern 2": { + input: "172.15.0.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + Detail: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + AttributePath: nil, + }, + }, + }, + "invalid pattern 3": { + input: "172.32.0.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + Detail: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + AttributePath: nil, + }, + }, + }, + "invalid pattern 4": { + input: "172.192.0.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + Detail: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + AttributePath: nil, + }, + }, + }, + "invalid pattern 5": { + input: "172.255.0.0/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + Detail: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + AttributePath: nil, + }, + }, + }, + "invalid pattern 6": { + input: "10.0.0.0/7", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + Detail: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + AttributePath: nil, + }, + }, + }, + "invalid pattern 7": { + input: "192.168.0.0/15", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + Detail: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + AttributePath: nil, + }, + }, + }, + "invalid pattern and invalid range": { + input: "87.70.141.1/22", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + Detail: "must match pattern of 10.*.*.* with prefix greater than /8,or 172.[16-31].*.* with prefix greater than /12, or 192.168.*.* with prefix greater than /16; where * is any number from [0-255]", + AttributePath: nil, + }, + diag.Diagnostic{ + Severity: diag.Error, + Summary: "invalid CIDR range start 87.70.141.1, should have been 87.70.140.0", + Detail: "invalid CIDR range start 87.70.141.1, should have been 87.70.140.0", + AttributePath: nil, + }, + }, + }, + "invalid range": { + input: "192.168.255.255/24", + expected: diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "invalid CIDR range start 192.168.255.255, should have been 192.168.255.0", + Detail: "invalid CIDR range start 192.168.255.255, should have been 192.168.255.0", + AttributePath: nil, + }, + }, + }, + "valid cidr block 11": { + input: "172.25.16.0/24", + expected: diag.Diagnostics(nil), + }, + } + + for n, tc := range tcs { + t.Run(n, func(t *testing.T) { + r := require.New(t) + result := validateCIDRBlock(tc.input, nil) + r.Equal(tc.expected, result) + }) + } +}