lang/funcs: Preserve IP address leading zero behavior from Go 1.16

Go 1.17 includes a breaking change to both net.ParseIP and net.ParseCIDR
functions to reject IPv4 address octets written with leading zeros.

Our use of these functions as part of the various CIDR functions in the
Terraform language doesn't have the same security concerns that the Go
team had in evaluating this change to the standard library, and so we
can't justify an exception to our v1.0 compatibility promises on the same
sort of security grounds that the Go team used to justify their
compatibility exception.

For that reason, we'll now use our own fork of the Go library functions
which has the new check disabled in order to preserve the prior behavior.
We're taking this path, rather than pre-normalizing the IP address before
calling into the standard library, because an additional normalization
layer would be entirely new code and additional complexity, whereas this
fork is relatively minor in terms of code size and avoids any significant
changes to our own calls to these functions.

Thanks to the Kubernetes team for their prior work on carving out a subset
of the "net" package for their similar backward-compatibility concern.
Our "ipaddr" package here is a lightly-modified fork of their fork, with
only the comments changed to talk about Terraform instead of Kubernetes.

This fork is not intended for use in any other future feature
implementations, because they wouldn't be subject to the same
compatibility constraints as our existing functions. We will use these
forked implementations for new callers only if consistency with the
behavior of the existing functions is a key requirement.
This commit is contained in:
Martin Atkins 2021-08-17 11:30:18 -07:00
parent 94f4f8e25d
commit c23a7fce4e
13 changed files with 576 additions and 6 deletions

27
internal/ipaddr/LICENSE Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
internal/ipaddr/PATENTS Normal file
View File

@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

34
internal/ipaddr/README.md Normal file
View File

@ -0,0 +1,34 @@
# Forked IP address parsing functions
This directory contains a subset of code from the Go project's `net` package
as of Go 1.16, used under the Go project license which we've included here
in [`LICENSE`](LICENSE) and [`PATENTS`](PATENTS), which are also copied from
the Go project.
Terraform has its own fork of these functions because Go 1.17 included a
breaking change to reject IPv4 address octets written with leading zeros.
The Go project rationale for that change was that Go historically interpreted
leading-zero octets inconsistently with many other implementations, trimming
off the zeros and still treating the rest as decimal rather than treating the
octet as octal.
The Go team made the reasonable observation that having a function that
interprets a non-normalized form in a manner inconsistent with other
implementations may cause naive validation or policy checks to produce
incorrect results, and thus it's a potential security concern. For more
information, see [Go issue #30999](https://golang.org/issue/30999).
After careful consideration, the Terraform team has concluded that Terraform's
use of these functions as part of the implementation of the `cidrhost`,
`cidrsubnet`, `cidrsubnets`, and `cidrnetmask` functions has a more limited
impact than the general availability of these functions in the Go standard
library, and so we can't justify a similar exception to our Terraform 1.0
compatibility promises as the Go team made to their Go 1.0 compatibility
promises.
If you're considering using this package for new functionality _other than_ the
built-in functions mentioned above, please do so only if consistency with the
behavior of those functions is important. Otherwise, new features are not
burdened by the same compatibility constraints and so should typically prefer
to use the stricter interpretation of the upstream parsing functions.

6
internal/ipaddr/doc.go Normal file
View File

@ -0,0 +1,6 @@
// Package ipaddr is a fork of a subset of the Go standard "net" package which
// retains parsing behaviors from Go 1.16 or earlier.
//
// Don't use this for any new code without careful consideration. See the
// README.md in the package directory for more information.
package ipaddr

226
internal/ipaddr/ip.go Normal file
View File

@ -0,0 +1,226 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// IP address manipulations
//
// IPv4 addresses are 4 bytes; IPv6 addresses are 16 bytes.
// An IPv4 address can be converted to an IPv6 address by
// adding a canonical prefix (10 zeros, 2 0xFFs).
// This library accepts either size of byte slice but always
// returns 16-byte addresses.
package ipaddr
import (
stdnet "net"
)
//
// Lean on the standard net lib as much as possible.
//
type IP = stdnet.IP
type IPNet = stdnet.IPNet
type ParseError = stdnet.ParseError
const IPv4len = stdnet.IPv4len
const IPv6len = stdnet.IPv6len
var CIDRMask = stdnet.CIDRMask
var IPv4 = stdnet.IPv4
// Parse IPv4 address (d.d.d.d).
func parseIPv4(s string) IP {
var p [IPv4len]byte
for i := 0; i < IPv4len; i++ {
if len(s) == 0 {
// Missing octets.
return nil
}
if i > 0 {
if s[0] != '.' {
return nil
}
s = s[1:]
}
n, c, ok := dtoi(s)
if !ok || n > 0xFF {
return nil
}
//
// NOTE: This correct check was added for go-1.17, but is a
// backwards-incompatible change for Terraform users, who might have
// already written modules with leading zeroes.
//
//if c > 1 && s[0] == '0' {
// // Reject non-zero components with leading zeroes.
// return nil
//}
s = s[c:]
p[i] = byte(n)
}
if len(s) != 0 {
return nil
}
return IPv4(p[0], p[1], p[2], p[3])
}
// parseIPv6 parses s as a literal IPv6 address described in RFC 4291
// and RFC 5952.
func parseIPv6(s string) (ip IP) {
ip = make(IP, IPv6len)
ellipsis := -1 // position of ellipsis in ip
// Might have leading ellipsis
if len(s) >= 2 && s[0] == ':' && s[1] == ':' {
ellipsis = 0
s = s[2:]
// Might be only ellipsis
if len(s) == 0 {
return ip
}
}
// Loop, parsing hex numbers followed by colon.
i := 0
for i < IPv6len {
// Hex number.
n, c, ok := xtoi(s)
if !ok || n > 0xFFFF {
return nil
}
// If followed by dot, might be in trailing IPv4.
if c < len(s) && s[c] == '.' {
if ellipsis < 0 && i != IPv6len-IPv4len {
// Not the right place.
return nil
}
if i+IPv4len > IPv6len {
// Not enough room.
return nil
}
ip4 := parseIPv4(s)
if ip4 == nil {
return nil
}
ip[i] = ip4[12]
ip[i+1] = ip4[13]
ip[i+2] = ip4[14]
ip[i+3] = ip4[15]
s = ""
i += IPv4len
break
}
// Save this 16-bit chunk.
ip[i] = byte(n >> 8)
ip[i+1] = byte(n)
i += 2
// Stop at end of string.
s = s[c:]
if len(s) == 0 {
break
}
// Otherwise must be followed by colon and more.
if s[0] != ':' || len(s) == 1 {
return nil
}
s = s[1:]
// Look for ellipsis.
if s[0] == ':' {
if ellipsis >= 0 { // already have one
return nil
}
ellipsis = i
s = s[1:]
if len(s) == 0 { // can be at end
break
}
}
}
// Must have used entire string.
if len(s) != 0 {
return nil
}
// If didn't parse enough, expand ellipsis.
if i < IPv6len {
if ellipsis < 0 {
return nil
}
n := IPv6len - i
for j := i - 1; j >= ellipsis; j-- {
ip[j+n] = ip[j]
}
for j := ellipsis + n - 1; j >= ellipsis; j-- {
ip[j] = 0
}
} else if ellipsis >= 0 {
// Ellipsis must represent at least one 0 group.
return nil
}
return ip
}
// ParseIP parses s as an IP address, returning the result.
// The string s can be in IPv4 dotted decimal ("192.0.2.1"), IPv6
// ("2001:db8::68"), or IPv4-mapped IPv6 ("::ffff:192.0.2.1") form.
// If s is not a valid textual representation of an IP address,
// ParseIP returns nil.
func ParseIP(s string) IP {
for i := 0; i < len(s); i++ {
switch s[i] {
case '.':
return parseIPv4(s)
case ':':
return parseIPv6(s)
}
}
return nil
}
// ParseCIDR parses s as a CIDR notation IP address and prefix length,
// like "192.0.2.0/24" or "2001:db8::/32", as defined in
// RFC 4632 and RFC 4291.
//
// It returns the IP address and the network implied by the IP and
// prefix length.
// For example, ParseCIDR("192.0.2.1/24") returns the IP address
// 192.0.2.1 and the network 192.0.2.0/24.
func ParseCIDR(s string) (IP, *IPNet, error) {
i := indexByteString(s, '/')
if i < 0 {
return nil, nil, &ParseError{Type: "CIDR address", Text: s}
}
addr, mask := s[:i], s[i+1:]
iplen := IPv4len
ip := parseIPv4(addr)
if ip == nil {
iplen = IPv6len
ip = parseIPv6(addr)
}
n, i, ok := dtoi(mask)
if ip == nil || !ok || i != len(mask) || n < 0 || n > 8*iplen {
return nil, nil, &ParseError{Type: "CIDR address", Text: s}
}
m := CIDRMask(n, 8*iplen)
return ip, &IPNet{IP: ip.Mask(m), Mask: m}, nil
}
// This is copied from go/src/internal/bytealg, which includes versions
// optimized for various platforms. Those optimizations are elided here so we
// don't have to maintain them.
func indexByteString(s string, c byte) int {
for i := 0; i < len(s); i++ {
if s[i] == c {
return i
}
}
return -1
}

126
internal/ipaddr/ip_test.go Normal file
View File

@ -0,0 +1,126 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ipaddr
import (
stdnet "net"
"reflect"
"testing"
)
//
// Lean on the standard net lib as much as possible.
//
type IPMask = stdnet.IPMask
var IPv4Mask = stdnet.IPv4Mask
var parseIPTests = []struct {
in string
out IP
}{
{"127.0.1.2", IPv4(127, 0, 1, 2)},
{"127.0.0.1", IPv4(127, 0, 0, 1)},
{"127.001.002.003", IPv4(127, 1, 2, 3)},
{"127.007.008.009", IPv4(127, 7, 8, 9)},
{"127.010.020.030", IPv4(127, 10, 20, 30)},
{"::ffff:127.1.2.3", IPv4(127, 1, 2, 3)},
{"::ffff:127.001.002.003", IPv4(127, 1, 2, 3)},
{"::ffff:127.007.008.009", IPv4(127, 7, 8, 9)},
{"::ffff:127.010.020.030", IPv4(127, 10, 20, 30)},
{"::ffff:7f01:0203", IPv4(127, 1, 2, 3)},
{"0:0:0:0:0000:ffff:127.1.2.3", IPv4(127, 1, 2, 3)},
{"0:0:0:0:000000:ffff:127.1.2.3", IPv4(127, 1, 2, 3)},
{"0:0:0:0::ffff:127.1.2.3", IPv4(127, 1, 2, 3)},
{"2001:4860:0:2001::68", IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}},
{"2001:4860:0000:2001:0000:0000:0000:0068", IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}},
{"-0.0.0.0", nil},
{"0.-1.0.0", nil},
{"0.0.-2.0", nil},
{"0.0.0.-3", nil},
{"127.0.0.256", nil},
{"abc", nil},
{"123:", nil},
{"fe80::1%lo0", nil},
{"fe80::1%911", nil},
{"", nil},
{"a1:a2:a3:a4::b1:b2:b3:b4", nil}, // Issue 6628
//
// NOTE: These correct failures were added for go-1.17, but are a
// backwards-incompatible change for Terraform users, who might have
// already written modules using leading zeroes.
//
//{"127.001.002.003", nil},
//{"::ffff:127.001.002.003", nil},
//{"123.000.000.000", nil},
//{"1.2..4", nil},
//{"0123.0.0.1", nil},
}
func TestParseIP(t *testing.T) {
for _, tt := range parseIPTests {
if out := ParseIP(tt.in); !reflect.DeepEqual(out, tt.out) {
t.Errorf("ParseIP(%q) = %v, want %v", tt.in, out, tt.out)
}
}
}
var parseCIDRTests = []struct {
in string
ip IP
net *IPNet
err error
}{
{"135.104.0.0/32", IPv4(135, 104, 0, 0), &IPNet{IP: IPv4(135, 104, 0, 0), Mask: IPv4Mask(255, 255, 255, 255)}, nil},
{"0.0.0.0/24", IPv4(0, 0, 0, 0), &IPNet{IP: IPv4(0, 0, 0, 0), Mask: IPv4Mask(255, 255, 255, 0)}, nil},
{"135.104.0.0/24", IPv4(135, 104, 0, 0), &IPNet{IP: IPv4(135, 104, 0, 0), Mask: IPv4Mask(255, 255, 255, 0)}, nil},
{"135.104.0.1/32", IPv4(135, 104, 0, 1), &IPNet{IP: IPv4(135, 104, 0, 1), Mask: IPv4Mask(255, 255, 255, 255)}, nil},
{"135.104.0.1/24", IPv4(135, 104, 0, 1), &IPNet{IP: IPv4(135, 104, 0, 0), Mask: IPv4Mask(255, 255, 255, 0)}, nil},
{"127.000.000.001/32", IPv4(127, 0, 0, 1), &IPNet{IP: IPv4(127, 0, 0, 1), Mask: IPv4Mask(255, 255, 255, 255)}, nil},
{"127.007.008.009/32", IPv4(127, 7, 8, 9), &IPNet{IP: IPv4(127, 7, 8, 9), Mask: IPv4Mask(255, 255, 255, 255)}, nil},
{"127.010.020.030/32", IPv4(127, 10, 20, 30), &IPNet{IP: IPv4(127, 10, 20, 30), Mask: IPv4Mask(255, 255, 255, 255)}, nil},
{"::1/128", ParseIP("::1"), &IPNet{IP: ParseIP("::1"), Mask: IPMask(ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"))}, nil},
{"abcd:2345::/127", ParseIP("abcd:2345::"), &IPNet{IP: ParseIP("abcd:2345::"), Mask: IPMask(ParseIP("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe"))}, nil},
{"abcd:2345::/65", ParseIP("abcd:2345::"), &IPNet{IP: ParseIP("abcd:2345::"), Mask: IPMask(ParseIP("ffff:ffff:ffff:ffff:8000::"))}, nil},
{"abcd:2345::/64", ParseIP("abcd:2345::"), &IPNet{IP: ParseIP("abcd:2345::"), Mask: IPMask(ParseIP("ffff:ffff:ffff:ffff::"))}, nil},
{"abcd:2345::/63", ParseIP("abcd:2345::"), &IPNet{IP: ParseIP("abcd:2345::"), Mask: IPMask(ParseIP("ffff:ffff:ffff:fffe::"))}, nil},
{"abcd:2345::/33", ParseIP("abcd:2345::"), &IPNet{IP: ParseIP("abcd:2345::"), Mask: IPMask(ParseIP("ffff:ffff:8000::"))}, nil},
{"abcd:2345::/32", ParseIP("abcd:2345::"), &IPNet{IP: ParseIP("abcd:2345::"), Mask: IPMask(ParseIP("ffff:ffff::"))}, nil},
{"abcd:2344::/31", ParseIP("abcd:2344::"), &IPNet{IP: ParseIP("abcd:2344::"), Mask: IPMask(ParseIP("ffff:fffe::"))}, nil},
{"abcd:2300::/24", ParseIP("abcd:2300::"), &IPNet{IP: ParseIP("abcd:2300::"), Mask: IPMask(ParseIP("ffff:ff00::"))}, nil},
{"abcd:2345::/24", ParseIP("abcd:2345::"), &IPNet{IP: ParseIP("abcd:2300::"), Mask: IPMask(ParseIP("ffff:ff00::"))}, nil},
{"2001:DB8::/48", ParseIP("2001:DB8::"), &IPNet{IP: ParseIP("2001:DB8::"), Mask: IPMask(ParseIP("ffff:ffff:ffff::"))}, nil},
{"2001:DB8::1/48", ParseIP("2001:DB8::1"), &IPNet{IP: ParseIP("2001:DB8::"), Mask: IPMask(ParseIP("ffff:ffff:ffff::"))}, nil},
{"192.168.1.1/255.255.255.0", nil, nil, &ParseError{Type: "CIDR address", Text: "192.168.1.1/255.255.255.0"}},
{"192.168.1.1/35", nil, nil, &ParseError{Type: "CIDR address", Text: "192.168.1.1/35"}},
{"2001:db8::1/-1", nil, nil, &ParseError{Type: "CIDR address", Text: "2001:db8::1/-1"}},
{"2001:db8::1/-0", nil, nil, &ParseError{Type: "CIDR address", Text: "2001:db8::1/-0"}},
{"-0.0.0.0/32", nil, nil, &ParseError{Type: "CIDR address", Text: "-0.0.0.0/32"}},
{"0.-1.0.0/32", nil, nil, &ParseError{Type: "CIDR address", Text: "0.-1.0.0/32"}},
{"0.0.-2.0/32", nil, nil, &ParseError{Type: "CIDR address", Text: "0.0.-2.0/32"}},
{"0.0.0.-3/32", nil, nil, &ParseError{Type: "CIDR address", Text: "0.0.0.-3/32"}},
{"0.0.0.0/-0", nil, nil, &ParseError{Type: "CIDR address", Text: "0.0.0.0/-0"}},
//
// NOTE: Theis correct failure was added for go-1.17, but is a
// backwards-incompatible change for Terraform users, who might have
// already written modules using leading zeroes.
//
//{"127.000.000.001/32", nil, nil, &ParseError{Type: "CIDR address", Text: "127.000.000.001/32"}},
{"", nil, nil, &ParseError{Type: "CIDR address", Text: ""}},
}
func TestParseCIDR(t *testing.T) {
for _, tt := range parseCIDRTests {
ip, net, err := ParseCIDR(tt.in)
if !reflect.DeepEqual(err, tt.err) {
t.Errorf("ParseCIDR(%q) = %v, %v; want %v, %v", tt.in, ip, net, tt.ip, tt.net)
}
if err == nil && (!tt.ip.Equal(ip) || !tt.net.IP.Equal(net.IP) || !reflect.DeepEqual(net.Mask, tt.net.Mask)) {
t.Errorf("ParseCIDR(%q) = %v, {%v, %v}; want %v, {%v, %v}", tt.in, ip, net.IP, net.Mask, tt.ip, tt.net.IP, tt.net.Mask)
}
}
}

54
internal/ipaddr/parse.go Normal file
View File

@ -0,0 +1,54 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Simple file i/o and string manipulation, to avoid
// depending on strconv and bufio and strings.
package ipaddr
// Bigger than we need, not too big to worry about overflow
const big = 0xFFFFFF
// Decimal to integer.
// Returns number, characters consumed, success.
func dtoi(s string) (n int, i int, ok bool) {
n = 0
for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
n = n*10 + int(s[i]-'0')
if n >= big {
return big, i, false
}
}
if i == 0 {
return 0, 0, false
}
return n, i, true
}
// Hexadecimal to integer.
// Returns number, characters consumed, success.
func xtoi(s string) (n int, i int, ok bool) {
n = 0
for i = 0; i < len(s); i++ {
if '0' <= s[i] && s[i] <= '9' {
n *= 16
n += int(s[i] - '0')
} else if 'a' <= s[i] && s[i] <= 'f' {
n *= 16
n += int(s[i]-'a') + 10
} else if 'A' <= s[i] && s[i] <= 'F' {
n *= 16
n += int(s[i]-'A') + 10
} else {
break
}
if n >= big {
return 0, i, false
}
}
if i == 0 {
return 0, i, false
}
return n, i, true
}

View File

@ -3,9 +3,9 @@ package funcs
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"net"
"github.com/apparentlymart/go-cidr/cidr" "github.com/apparentlymart/go-cidr/cidr"
"github.com/hashicorp/terraform/internal/ipaddr"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/gocty" "github.com/zclconf/go-cty/cty/gocty"
@ -30,7 +30,7 @@ var CidrHostFunc = function.New(&function.Spec{
if err := gocty.FromCtyValue(args[1], &hostNum); err != nil { if err := gocty.FromCtyValue(args[1], &hostNum); err != nil {
return cty.UnknownVal(cty.String), err return cty.UnknownVal(cty.String), err
} }
_, network, err := net.ParseCIDR(args[0].AsString()) _, network, err := ipaddr.ParseCIDR(args[0].AsString())
if err != nil { if err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
} }
@ -55,12 +55,12 @@ var CidrNetmaskFunc = function.New(&function.Spec{
}, },
Type: function.StaticReturnType(cty.String), Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
_, network, err := net.ParseCIDR(args[0].AsString()) _, network, err := ipaddr.ParseCIDR(args[0].AsString())
if err != nil { if err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
} }
return cty.StringVal(net.IP(network.Mask).String()), nil return cty.StringVal(ipaddr.IP(network.Mask).String()), nil
}, },
}) })
@ -92,7 +92,7 @@ var CidrSubnetFunc = function.New(&function.Spec{
return cty.UnknownVal(cty.String), err return cty.UnknownVal(cty.String), err
} }
_, network, err := net.ParseCIDR(args[0].AsString()) _, network, err := ipaddr.ParseCIDR(args[0].AsString())
if err != nil { if err != nil {
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err) return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
} }
@ -121,7 +121,7 @@ var CidrSubnetsFunc = function.New(&function.Spec{
}, },
Type: function.StaticReturnType(cty.List(cty.String)), Type: function.StaticReturnType(cty.List(cty.String)),
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
_, network, err := net.ParseCIDR(args[0].AsString()) _, network, err := ipaddr.ParseCIDR(args[0].AsString())
if err != nil { if err != nil {
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err) return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err)
} }

View File

@ -32,6 +32,19 @@ func TestCidrHost(t *testing.T) {
cty.StringVal("192.168.1.0"), cty.StringVal("192.168.1.0"),
false, false,
}, },
{
// We inadvertently inherited a pre-Go1.17 standard library quirk
// if parsing zero-prefix parts as decimal rather than octal.
// Go 1.17 resolved that quirk by making zero-prefix invalid, but
// we've preserved our existing behavior for backward compatibility,
// on the grounds that these functions are for generating addresses
// rather than validating or processing them. We do always generate
// a canonical result regardless of the input, though.
cty.StringVal("010.001.0.0/24"),
cty.NumberIntVal(6),
cty.StringVal("10.1.0.6"),
false,
},
{ {
cty.StringVal("192.168.1.0/30"), cty.StringVal("192.168.1.0/30"),
cty.NumberIntVal(255), cty.NumberIntVal(255),
@ -110,6 +123,17 @@ func TestCidrNetmask(t *testing.T) {
cty.StringVal("ffff:ffff:ffff:ffff::"), cty.StringVal("ffff:ffff:ffff:ffff::"),
false, false,
}, },
{
// We inadvertently inherited a pre-Go1.17 standard library quirk
// if parsing zero-prefix parts as decimal rather than octal.
// Go 1.17 resolved that quirk by making zero-prefix invalid, but
// we've preserved our existing behavior for backward compatibility,
// on the grounds that these functions are for generating addresses
// rather than validating or processing them.
cty.StringVal("010.001.0.0/24"),
cty.StringVal("255.255.255.0"),
false,
},
{ {
cty.StringVal("not-a-cidr"), cty.StringVal("not-a-cidr"),
cty.UnknownVal(cty.String), cty.UnknownVal(cty.String),
@ -178,6 +202,20 @@ func TestCidrSubnet(t *testing.T) {
cty.StringVal("fe80::3:0:0:0/81"), cty.StringVal("fe80::3:0:0:0/81"),
false, false,
}, },
{
// We inadvertently inherited a pre-Go1.17 standard library quirk
// if parsing zero-prefix parts as decimal rather than octal.
// Go 1.17 resolved that quirk by making zero-prefix invalid, but
// we've preserved our existing behavior for backward compatibility,
// on the grounds that these functions are for generating addresses
// rather than validating or processing them. We do always generate
// a canonical result regardless of the input, though.
cty.StringVal("010.001.0.0/24"),
cty.NumberIntVal(4),
cty.NumberIntVal(1),
cty.StringVal("10.1.0.16/28"),
false,
},
{ // not enough bits left { // not enough bits left
cty.StringVal("192.168.0.0/30"), cty.StringVal("192.168.0.0/30"),
cty.NumberIntVal(4), cty.NumberIntVal(4),
@ -267,6 +305,23 @@ func TestCidrSubnets(t *testing.T) {
}), }),
``, ``,
}, },
{
// We inadvertently inherited a pre-Go1.17 standard library quirk
// if parsing zero-prefix parts as decimal rather than octal.
// Go 1.17 resolved that quirk by making zero-prefix invalid, but
// we've preserved our existing behavior for backward compatibility,
// on the grounds that these functions are for generating addresses
// rather than validating or processing them. We do always generate
// a canonical result regardless of the input, though.
cty.StringVal("010.0.0.0/21"),
[]cty.Value{
cty.NumberIntVal(3),
},
cty.ListVal([]cty.Value{
cty.StringVal("10.0.0.0/24"),
}),
``,
},
{ {
cty.StringVal("10.0.0.0/30"), cty.StringVal("10.0.0.0/30"),
[]cty.Value{ []cty.Value{

View File

@ -34,6 +34,11 @@ situations, such as point-to-point links.
This function accepts both IPv6 and IPv4 prefixes, and the result always uses This function accepts both IPv6 and IPv4 prefixes, and the result always uses
the same addressing scheme as the given prefix. the same addressing scheme as the given prefix.
-> **Note:** As a historical accident, this function interprets IPv4 address
octets that have leading zeros as decimal numbers, which is contrary to some
other systems which interpret them as octal. We have preserved this behavior
for backward compatibility, but recommend against relying on this behavior.
## Examples ## Examples
``` ```

View File

@ -25,6 +25,11 @@ IPv4 address syntax, as expected by some software.
CIDR notation is the only valid notation for IPv6 addresses, so `cidrnetmask` CIDR notation is the only valid notation for IPv6 addresses, so `cidrnetmask`
produces an error if given an IPv6 address. produces an error if given an IPv6 address.
-> **Note:** As a historical accident, this function interprets IPv4 address
octets that have leading zeros as decimal numbers, which is contrary to some
other systems which interpret them as octal. We have preserved this behavior
for backward compatibility, but recommend against relying on this behavior.
## Examples ## Examples
``` ```

View File

@ -34,6 +34,11 @@ allows you to give a specific network number to use. `cidrsubnets` can allocate
multiple network addresses at once, but numbers them automatically starting multiple network addresses at once, but numbers them automatically starting
with zero. with zero.
-> **Note:** As a historical accident, this function interprets IPv4 address
octets that have leading zeros as decimal numbers, which is contrary to some
other systems which interpret them as octal. We have preserved this behavior
for backward compatibility, but recommend against relying on this behavior.
## Examples ## Examples
``` ```

View File

@ -39,6 +39,11 @@ existing calls safely, as long as there is sufficient address space available.
This function accepts both IPv6 and IPv4 prefixes, and the result always uses This function accepts both IPv6 and IPv4 prefixes, and the result always uses
the same addressing scheme as the given prefix. the same addressing scheme as the given prefix.
-> **Note:** As a historical accident, this function interprets IPv4 address
octets that have leading zeros as decimal numbers, which is contrary to some
other systems which interpret them as octal. We have preserved this behavior
for backward compatibility, but recommend against relying on this behavior.
-> **Note:** [The Terraform module `hashicorp/subnets/cidr`](https://registry.terraform.io/modules/hashicorp/subnets/cidr) -> **Note:** [The Terraform module `hashicorp/subnets/cidr`](https://registry.terraform.io/modules/hashicorp/subnets/cidr)
wraps `cidrsubnets` to provide additional functionality for assigning symbolic wraps `cidrsubnets` to provide additional functionality for assigning symbolic
names to your networks and skipping prefixes for obsolete allocations. Its names to your networks and skipping prefixes for obsolete allocations. Its