forked from open-policy-agent/opa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cidr.go
118 lines (98 loc) · 3.16 KB
/
cidr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package topdown
import (
"fmt"
"math/big"
"net"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/topdown/builtins"
)
func getNetFromOperand(v ast.Value) (*net.IPNet, error) {
subnetStringA, err := builtins.StringOperand(v, 1)
if err != nil {
return nil, err
}
_, cidrnet, err := net.ParseCIDR(string(subnetStringA))
if err != nil {
return nil, err
}
return cidrnet, nil
}
func getLastIP(cidr *net.IPNet) (net.IP, error) {
prefixLen, bits := cidr.Mask.Size()
if prefixLen == 0 && bits == 0 {
// non-standard mask, see https://golang.org/pkg/net/#IPMask.Size
return nil, fmt.Errorf("CIDR mask is in non-standard format")
}
var lastIP []byte
if prefixLen == bits {
// Special case for single ip address ranges ex: 192.168.1.1/32
// We can just use the starting IP as the last IP
lastIP = cidr.IP
} else {
// Use big.Int's so we can handle ipv6 addresses
firstIPInt := new(big.Int)
firstIPInt.SetBytes(cidr.IP)
hostLen := uint(bits) - uint(prefixLen)
lastIPInt := big.NewInt(1)
lastIPInt.Lsh(lastIPInt, hostLen)
lastIPInt.Sub(lastIPInt, big.NewInt(1))
lastIPInt.Or(lastIPInt, firstIPInt)
ipBytes := lastIPInt.Bytes()
lastIP = make([]byte, bits/8)
// Pack our IP bytes into the end of the return array,
// since big.Int.Bytes() removes front zero padding.
for i := 1; i <= len(lastIPInt.Bytes()); i++ {
lastIP[len(lastIP)-i] = ipBytes[len(ipBytes)-i]
}
}
return lastIP, nil
}
func builtinNetCIDRIntersects(a, b ast.Value) (ast.Value, error) {
cidrnetA, err := getNetFromOperand(a)
if err != nil {
return nil, err
}
cidrnetB, err := getNetFromOperand(b)
if err != nil {
return nil, err
}
// If either net contains the others starting IP they are overlapping
cidrsOverlap := (cidrnetA.Contains(cidrnetB.IP) || cidrnetB.Contains(cidrnetA.IP))
return ast.Boolean(cidrsOverlap), nil
}
func builtinNetCIDRContains(a, b ast.Value) (ast.Value, error) {
cidrnetA, err := getNetFromOperand(a)
if err != nil {
return nil, err
}
// b could be either an IP addressor CIDR string, try to parse it as an IP first, fall back to CIDR
bStr, err := builtins.StringOperand(b, 1)
if err != nil {
return nil, err
}
ip := net.ParseIP(string(bStr))
if ip != nil {
return ast.Boolean(cidrnetA.Contains(ip)), nil
}
// It wasn't an IP, try and parse it as a CIDR
cidrnetB, err := getNetFromOperand(b)
if err != nil {
return nil, fmt.Errorf("not a valid textual representation of an IP address or CIDR: %s", string(bStr))
}
// We can determine if cidr A contains cidr B iff A contains the starting address of B and the last address in B.
cidrContained := false
if cidrnetA.Contains(cidrnetB.IP) {
// Only spend time calculating the last IP if the starting IP is already verified to be in cidr A
lastIP, err := getLastIP(cidrnetB)
if err != nil {
return nil, err
}
cidrContained = cidrnetA.Contains(lastIP)
}
return ast.Boolean(cidrContained), nil
}
func init() {
RegisterFunctionalBuiltin2(ast.NetCIDROverlap.Name, builtinNetCIDRContains)
RegisterFunctionalBuiltin2(ast.NetCIDRIntersects.Name, builtinNetCIDRIntersects)
RegisterFunctionalBuiltin2(ast.NetCIDRContains.Name, builtinNetCIDRContains)
}