Skip to content

Commit

Permalink
Merge pull request #1254 from hashicorp/f/ip-prefix-validation
Browse files Browse the repository at this point in the history
azuread_named_location: validation for `ip_ranges` property
  • Loading branch information
manicminer authored Nov 16, 2023
2 parents cdabde1 + 3a4cbd3 commit 1734531
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs/resources/named_location.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The following arguments are supported:

`ip` block supports the following:

* `ip_ranges` - (Required) List of IP address ranges in IPv4 CIDR format (e.g. `1.2.3.4/32`) or any allowable IPv6 format from IETF RFC596.
* `ip_ranges` - (Required) List of IP address ranges in IPv4 CIDR format (e.g. `1.2.3.4/32`) or any allowable IPv6 format from IETF RFC596. Each CIDR prefix must be `/8` or larger.
* `trusted` - (Optional) Whether the named location is trusted. Defaults to `false`.

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ func namedLocationResource() *pluginsdk.Resource {
Type: pluginsdk.TypeList,
Required: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
Type: pluginsdk.TypeString,
ValidateFunc: validation.PrefixLengthAtLeast(8),
},
},

Expand All @@ -88,7 +89,8 @@ func namedLocationResource() *pluginsdk.Resource {
Type: pluginsdk.TypeList,
Required: true,
Elem: &pluginsdk.Schema{
Type: pluginsdk.TypeString,
Type: pluginsdk.TypeString,
ValidateFunc: validation.StringIsNotEmpty,
},
},

Expand Down
79 changes: 79 additions & 0 deletions internal/tf/validation/net.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package validation

import (
"fmt"
"net/netip"
)

func StringIsIpPrefix(i interface{}, k string) (warnings []string, errors []error) {
if warnings, errors = StringIsNotEmpty(i, k); len(errors) > 0 {
return warnings, errors
}

if _, err := netip.ParsePrefix(i.(string)); err != nil {
return nil, []error{fmt.Errorf("expected %q to be a valid IPv4 or IPv6 prefix", k)}
}

return
}

func PrefixLengthAtLeast(minLength int) func(interface{}, string) ([]string, []error) {
return func(i interface{}, k string) (warnings []string, errors []error) {
if warnings, errors = StringIsNotEmpty(i, k); len(errors) > 0 {
return warnings, errors
}

prefix, err := netip.ParsePrefix(i.(string))
if err != nil {
return nil, []error{fmt.Errorf("expected %q to be a valid IPv4 or IPv6 prefix", k)}
}

if prefixLength := prefix.Bits(); prefixLength < minLength {
return nil, []error{fmt.Errorf("expected %q to have a prefix length at least %d, got %d", k, minLength, prefixLength)}
}

return
}
}

func PrefixLengthAtMost(maxLength int) func(interface{}, string) ([]string, []error) {
return func(i interface{}, k string) (warnings []string, errors []error) {
if warnings, errors = StringIsNotEmpty(i, k); len(errors) > 0 {
return warnings, errors
}

prefix, err := netip.ParsePrefix(i.(string))
if err != nil {
return nil, []error{fmt.Errorf("expected %q to be a valid IPv4 or IPv6 prefix", k)}
}

if prefixLength := prefix.Bits(); prefixLength > maxLength {
return nil, []error{fmt.Errorf("expected %q to have a prefix length at most %d, got %d", k, maxLength, prefixLength)}
}

return
}
}

func PrefixLengthBetween(minLength, maxLength int) func(interface{}, string) ([]string, []error) {
return func(i interface{}, k string) (warnings []string, errors []error) {
if warnings, errors = StringIsNotEmpty(i, k); len(errors) > 0 {
return warnings, errors
}

prefix, err := netip.ParsePrefix(i.(string))
if err != nil {
return nil, []error{fmt.Errorf("expected %q to be a valid IPv4 or IPv6 prefix", k)}
}

if prefixLength := prefix.Bits(); prefixLength < minLength {
return nil, []error{fmt.Errorf("expected %q to have a prefix length at least %d, got %d", k, minLength, prefixLength)}
}

if prefixLength := prefix.Bits(); prefixLength > maxLength {
return nil, []error{fmt.Errorf("expected %q to have a prefix length at most %d, got %d", k, maxLength, prefixLength)}
}

return
}
}
193 changes: 193 additions & 0 deletions internal/tf/validation/net_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package validation

import (
"testing"
)

func TestStringIsIpPrefix(t *testing.T) {
cases := []struct {
Value string
TestName string
ErrCount int
}{
{
Value: "10.0.0.0/8",
TestName: "Valid_NonRoutable1",
ErrCount: 0,
},
{
Value: "192.168.0.0/16",
TestName: "Valid_NonRoutable2",
ErrCount: 0,
},
{
Value: "172.16.20.5",
TestName: "Invalid_SingleAddress",
ErrCount: 1,
},
{
Value: "224.0.50.8",
TestName: "Invalid_MulticastAddress",
ErrCount: 1,
},
{
Value: "100.64.10.0",
TestName: "Invalid_Network",
ErrCount: 1,
},
}

for _, tc := range cases {
t.Run(tc.TestName, func(t *testing.T) {
warnings, errors := StringIsIpPrefix(tc.Value, "test")

if len(warnings) > 0 {
t.Fatalf("Expected StringIsIpPrefix to have %d not %d warnings for %q", 0, len(warnings), tc.TestName)
}
if len(errors) != tc.ErrCount {
t.Fatalf("Expected StringIsIpPrefix to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName)
}
})
}
}

func TestPrefixLengthAtLeast(t *testing.T) {
cases := []struct {
MinLength int
Value string
TestName string
ErrCount int
}{
{
MinLength: 8,
Value: "10.0.0.0/8",
TestName: "Valid_Exact",
ErrCount: 0,
},
{
MinLength: 16,
Value: "192.168.0.0/24",
TestName: "Valid_Larger",
ErrCount: 0,
},
{
MinLength: 8,
Value: "10.0.0.0/4",
TestName: "Invalid_Smaller",
ErrCount: 1,
},
}

for _, tc := range cases {
t.Run(tc.TestName, func(t *testing.T) {
warnings, errors := PrefixLengthAtLeast(tc.MinLength)(tc.Value, "test")

if len(warnings) > 0 {
t.Fatalf("Expected PrefixLengthAtLeast to have %d not %d warnings for %q", 0, len(warnings), tc.TestName)
}
if len(errors) != tc.ErrCount {
t.Fatalf("Expected PrefixLengthAtLeast to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName)
}
})
}
}

func TestPrefixLengthAtMost(t *testing.T) {
cases := []struct {
MaxLength int
Value string
TestName string
ErrCount int
}{
{
MaxLength: 24,
Value: "192.168.0.0/24",
TestName: "Valid_Exact",
ErrCount: 0,
},
{
MaxLength: 16,
Value: "10.0.0.0/8",
TestName: "Valid_Smaller",
ErrCount: 0,
},
{
MaxLength: 8,
Value: "10.0.0.0/12",
TestName: "Invalid_Larger",
ErrCount: 1,
},
}

for _, tc := range cases {
t.Run(tc.TestName, func(t *testing.T) {
warnings, errors := PrefixLengthAtMost(tc.MaxLength)(tc.Value, "test")

if len(warnings) > 0 {
t.Fatalf("Expected PrefixLengthAtMost to have %d not %d warnings for %q", 0, len(warnings), tc.TestName)
}
if len(errors) != tc.ErrCount {
t.Fatalf("Expected PrefixLengthAtMost to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName)
}
})
}
}

func TestPrefixLengthBetween(t *testing.T) {
cases := []struct {
MinLength int
MaxLength int
Value string
TestName string
ErrCount int
}{
{
MinLength: 16,
MaxLength: 24,
Value: "192.168.0.0/24",
TestName: "Valid_ExactUpper",
ErrCount: 0,
},
{
MinLength: 16,
MaxLength: 24,
Value: "172.16.0.0/16",
TestName: "Valid_ExactLower",
ErrCount: 0,
},
{
MinLength: 8,
MaxLength: 16,
Value: "10.50.0.0/12",
TestName: "Valid_InRange",
ErrCount: 0,
},
{
MinLength: 24,
MaxLength: 28,
Value: "10.0.0.0/12",
TestName: "Invalid_Smaller",
ErrCount: 1,
},
{
MinLength: 24,
MaxLength: 28,
Value: "192.168.100.0/30",
TestName: "Invalid_Larger",
ErrCount: 1,
},
}

for _, tc := range cases {
t.Run(tc.TestName, func(t *testing.T) {
warnings, errors := PrefixLengthBetween(tc.MinLength, tc.MaxLength)(tc.Value, "test")

if len(warnings) > 0 {
t.Fatalf("Expected PrefixLengthBetween to have %d not %d warnings for %q", 0, len(warnings), tc.TestName)
}
if len(errors) != tc.ErrCount {
t.Fatalf("Expected PrefixLengthBetween to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName)
}
})
}
}

0 comments on commit 1734531

Please sign in to comment.