k8s.io/apiserver@v0.31.1/pkg/cel/library/ip_test.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package library_test 18 19 import ( 20 "net/netip" 21 "regexp" 22 "testing" 23 24 "github.com/google/cel-go/cel" 25 "github.com/google/cel-go/common/types" 26 "github.com/google/cel-go/common/types/ref" 27 "github.com/stretchr/testify/require" 28 "k8s.io/apimachinery/pkg/util/sets" 29 apiservercel "k8s.io/apiserver/pkg/cel" 30 "k8s.io/apiserver/pkg/cel/library" 31 ) 32 33 func testIP(t *testing.T, expr string, expectResult ref.Val, expectRuntimeErr string, expectCompileErrs []string) { 34 env, err := cel.NewEnv( 35 library.IP(), 36 library.CIDR(), 37 ) 38 if err != nil { 39 t.Fatalf("%v", err) 40 } 41 42 compiled, issues := env.Compile(expr) 43 44 if len(expectCompileErrs) > 0 { 45 missingCompileErrs := []string{} 46 matchedCompileErrs := sets.New[int]() 47 for _, expectedCompileErr := range expectCompileErrs { 48 compiledPattern, err := regexp.Compile(expectedCompileErr) 49 if err != nil { 50 t.Fatalf("failed to compile expected err regex: %v", err) 51 } 52 53 didMatch := false 54 55 for i, compileError := range issues.Errors() { 56 if compiledPattern.Match([]byte(compileError.Message)) { 57 didMatch = true 58 matchedCompileErrs.Insert(i) 59 } 60 } 61 62 if !didMatch { 63 missingCompileErrs = append(missingCompileErrs, expectedCompileErr) 64 } else if len(matchedCompileErrs) != len(issues.Errors()) { 65 unmatchedErrs := []cel.Error{} 66 for i, issue := range issues.Errors() { 67 if !matchedCompileErrs.Has(i) { 68 unmatchedErrs = append(unmatchedErrs, *issue) 69 } 70 } 71 require.Empty(t, unmatchedErrs, "unexpected compilation errors") 72 } 73 } 74 75 require.Empty(t, missingCompileErrs, "expected compilation errors") 76 return 77 } else if len(issues.Errors()) > 0 { 78 t.Fatalf("%v", issues.Errors()) 79 } 80 81 prog, err := env.Program(compiled) 82 if err != nil { 83 t.Fatalf("%v", err) 84 } 85 res, _, err := prog.Eval(map[string]interface{}{}) 86 if len(expectRuntimeErr) > 0 { 87 if err == nil { 88 t.Fatalf("no runtime error thrown. Expected: %v", expectRuntimeErr) 89 } else if expectRuntimeErr != err.Error() { 90 t.Fatalf("unexpected err: %v", err) 91 } 92 } else if err != nil { 93 t.Fatalf("%v", err) 94 } else if expectResult != nil { 95 converted := res.Equal(expectResult).Value().(bool) 96 require.True(t, converted, "expectation not equal to output") 97 } else { 98 t.Fatal("expected result must not be nil") 99 } 100 } 101 102 func TestIP(t *testing.T) { 103 ipv4Addr, _ := netip.ParseAddr("192.168.0.1") 104 int4 := types.Int(4) 105 106 ipv6Addr, _ := netip.ParseAddr("2001:db8::68") 107 int6 := types.Int(6) 108 109 trueVal := types.Bool(true) 110 falseVal := types.Bool(false) 111 112 cases := []struct { 113 name string 114 expr string 115 expectResult ref.Val 116 expectRuntimeErr string 117 expectCompileErrs []string 118 }{ 119 { 120 name: "parse ipv4", 121 expr: `ip("192.168.0.1")`, 122 expectResult: apiservercel.IP{Addr: ipv4Addr}, 123 }, 124 { 125 name: "parse invalid ipv4", 126 expr: `ip("192.168.0.1.0")`, 127 expectRuntimeErr: "IP Address \"192.168.0.1.0\" parse error during conversion from string: ParseAddr(\"192.168.0.1.0\"): IPv4 address too long", 128 }, 129 { 130 name: "isIP valid ipv4", 131 expr: `isIP("192.168.0.1")`, 132 expectResult: trueVal, 133 }, 134 { 135 name: "isIP invalid ipv4", 136 expr: `isIP("192.168.0.1.0")`, 137 expectResult: falseVal, 138 }, 139 { 140 name: "ip.isCanonical valid ipv4", 141 expr: `ip.isCanonical("127.0.0.1")`, 142 expectResult: trueVal, 143 }, 144 { 145 name: "ip.isCanonical invalid ipv4", 146 expr: `ip.isCanonical("127.0.0.1.0")`, 147 expectRuntimeErr: "IP Address \"127.0.0.1.0\" parse error during conversion from string: ParseAddr(\"127.0.0.1.0\"): IPv4 address too long", 148 }, 149 { 150 name: "ipv4 family", 151 expr: `ip("192.168.0.1").family()`, 152 expectResult: int4, 153 }, 154 { 155 name: "ipv4 isUnspecified true", 156 expr: `ip("0.0.0.0").isUnspecified()`, 157 expectResult: trueVal, 158 }, 159 { 160 name: "ipv4 isUnspecified false", 161 expr: `ip("127.0.0.1").isUnspecified()`, 162 expectResult: falseVal, 163 }, 164 { 165 name: "ipv4 isLoopback true", 166 expr: `ip("127.0.0.1").isLoopback()`, 167 expectResult: trueVal, 168 }, 169 { 170 name: "ipv4 isLoopback false", 171 expr: `ip("1.2.3.4").isLoopback()`, 172 expectResult: falseVal, 173 }, 174 { 175 name: "ipv4 isLinkLocalMulticast true", 176 expr: `ip("224.0.0.1").isLinkLocalMulticast()`, 177 expectResult: trueVal, 178 }, 179 { 180 name: "ipv4 isLinkLocalMulticast false", 181 expr: `ip("224.0.1.1").isLinkLocalMulticast()`, 182 expectResult: falseVal, 183 }, 184 { 185 name: "ipv4 isLinkLocalUnicast true", 186 expr: `ip("169.254.169.254").isLinkLocalUnicast()`, 187 expectResult: trueVal, 188 }, 189 { 190 name: "ipv4 isLinkLocalUnicast false", 191 expr: `ip("192.168.0.1").isLinkLocalUnicast()`, 192 expectResult: falseVal, 193 }, 194 { 195 name: "ipv4 isGlobalUnicast true", 196 expr: `ip("192.168.0.1").isGlobalUnicast()`, 197 expectResult: trueVal, 198 }, 199 { 200 name: "ipv4 isGlobalUnicast false", 201 expr: `ip("255.255.255.255").isGlobalUnicast()`, 202 expectResult: falseVal, 203 }, 204 { 205 name: "parse ipv6", 206 expr: `ip("2001:db8::68")`, 207 expectResult: apiservercel.IP{Addr: ipv6Addr}, 208 }, 209 { 210 name: "parse invalid ipv6", 211 expr: `ip("2001:db8:::68")`, 212 expectRuntimeErr: "IP Address \"2001:db8:::68\" parse error during conversion from string: ParseAddr(\"2001:db8:::68\"): each colon-separated field must have at least one digit (at \":68\")", 213 }, 214 { 215 name: "isIP valid ipv6", 216 expr: `isIP("2001:db8::68")`, 217 expectResult: trueVal, 218 }, 219 { 220 name: "isIP invalid ipv4", 221 expr: `isIP("2001:db8:::68")`, 222 expectResult: falseVal, 223 }, 224 { 225 name: "ip.isCanonical valid ipv6", 226 expr: `ip.isCanonical("2001:db8::68")`, 227 expectResult: trueVal, 228 }, 229 { 230 name: "ip.isCanonical non-canonical ipv6", 231 expr: `ip.isCanonical("2001:DB8::68")`, 232 expectResult: falseVal, 233 }, 234 { 235 name: "ip.isCanonical invalid ipv6", 236 expr: `ip.isCanonical("2001:db8:::68")`, 237 expectRuntimeErr: "IP Address \"2001:db8:::68\" parse error during conversion from string: ParseAddr(\"2001:db8:::68\"): each colon-separated field must have at least one digit (at \":68\")", 238 }, 239 { 240 name: "ipv6 family", 241 expr: `ip("2001:db8::68").family()`, 242 expectResult: int6, 243 }, 244 { 245 name: "ipv6 isUnspecified true", 246 expr: `ip("::").isUnspecified()`, 247 expectResult: trueVal, 248 }, 249 { 250 name: "ipv6 isUnspecified false", 251 expr: `ip("::1").isUnspecified()`, 252 expectResult: falseVal, 253 }, 254 { 255 name: "ipv6 isLoopback true", 256 expr: `ip("::1").isLoopback()`, 257 expectResult: trueVal, 258 }, 259 { 260 name: "ipv6 isLoopback false", 261 expr: `ip("2001:db8::abcd").isLoopback()`, 262 expectResult: falseVal, 263 }, 264 { 265 name: "ipv6 isLinkLocalMulticast true", 266 expr: `ip("ff02::1").isLinkLocalMulticast()`, 267 expectResult: trueVal, 268 }, 269 { 270 name: "ipv6 isLinkLocalMulticast false", 271 expr: `ip("fd00::1").isLinkLocalMulticast()`, 272 expectResult: falseVal, 273 }, 274 { 275 name: "ipv6 isLinkLocalUnicast true", 276 expr: `ip("fe80::1").isLinkLocalUnicast()`, 277 expectResult: trueVal, 278 }, 279 { 280 name: "ipv6 isLinkLocalUnicast false", 281 expr: `ip("fd80::1").isLinkLocalUnicast()`, 282 expectResult: falseVal, 283 }, 284 { 285 name: "ipv6 isGlobalUnicast true", 286 expr: `ip("2001:db8::abcd").isGlobalUnicast()`, 287 expectResult: trueVal, 288 }, 289 { 290 name: "ipv6 isGlobalUnicast false", 291 expr: `ip("ff00::1").isGlobalUnicast()`, 292 expectResult: falseVal, 293 }, 294 { 295 name: "passing cidr into isIP returns compile error", 296 expr: `isIP(cidr("192.168.0.0/24"))`, 297 expectCompileErrs: []string{"found no matching overload for 'isIP' applied to '\\(net.CIDR\\)'"}, 298 }, 299 { 300 name: "converting an IP address to a string", 301 expr: `string(ip("192.168.0.1"))`, 302 expectResult: types.String("192.168.0.1"), 303 }, 304 { 305 name: "type of IP is net.IP", 306 expr: `type(ip("192.168.0.1")) == net.IP`, 307 expectResult: trueVal, 308 }, 309 } 310 311 for _, tc := range cases { 312 t.Run(tc.name, func(t *testing.T) { 313 testIP(t, tc.expr, tc.expectResult, tc.expectRuntimeErr, tc.expectCompileErrs) 314 }) 315 } 316 }