k8s.io/apiserver@v0.31.1/pkg/cel/library/cidr_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 testCIDR(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 TestCIDR(t *testing.T) {
   103  	ipv4CIDR, _ := netip.ParsePrefix("192.168.0.0/24")
   104  	ipv4Addr, _ := netip.ParseAddr("192.168.0.0")
   105  
   106  	ipv6CIDR, _ := netip.ParsePrefix("2001:db8::/32")
   107  	ipv6Addr, _ := netip.ParseAddr("2001:db8::")
   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:         `cidr("192.168.0.0/24")`,
   122  			expectResult: apiservercel.CIDR{Prefix: ipv4CIDR},
   123  		},
   124  		{
   125  			name:             "parse invalid ipv4",
   126  			expr:             `cidr("192.168.0.0/")`,
   127  			expectRuntimeErr: "network address parse error during conversion from string: network address parse error during conversion from string: netip.ParsePrefix(\"192.168.0.0/\"): bad bits after slash: \"\"",
   128  		},
   129  		{
   130  			name:         "contains IP ipv4 (IP)",
   131  			expr:         `cidr("192.168.0.0/24").containsIP(ip("192.168.0.1"))`,
   132  			expectResult: trueVal,
   133  		},
   134  		{
   135  			name:         "does not contain IP ipv4 (IP)",
   136  			expr:         `cidr("192.168.0.0/24").containsIP(ip("192.168.1.1"))`,
   137  			expectResult: falseVal,
   138  		},
   139  		{
   140  			name:         "contains IP ipv4 (string)",
   141  			expr:         `cidr("192.168.0.0/24").containsIP("192.168.0.1")`,
   142  			expectResult: trueVal,
   143  		},
   144  		{
   145  			name:         "does not contain IP ipv4 (string)",
   146  			expr:         `cidr("192.168.0.0/24").containsIP("192.168.1.1")`,
   147  			expectResult: falseVal,
   148  		},
   149  		{
   150  			name:         "contains CIDR ipv4 (CIDR)",
   151  			expr:         `cidr("192.168.0.0/24").containsCIDR(cidr("192.168.0.0/25"))`,
   152  			expectResult: trueVal,
   153  		},
   154  		{
   155  			name:         "does not contain IP ipv4 (CIDR)",
   156  			expr:         `cidr("192.168.0.0/24").containsCIDR(cidr("192.168.0.0/23"))`,
   157  			expectResult: falseVal,
   158  		},
   159  		{
   160  			name:         "contains CIDR ipv4 (string)",
   161  			expr:         `cidr("192.168.0.0/24").containsCIDR("192.168.0.0/25")`,
   162  			expectResult: trueVal,
   163  		},
   164  		{
   165  			name:         "does not contain CIDR ipv4 (string)",
   166  			expr:         `cidr("192.168.0.0/24").containsCIDR("192.168.0.0/23")`,
   167  			expectResult: falseVal,
   168  		},
   169  		{
   170  			name:         "returns IP ipv4",
   171  			expr:         `cidr("192.168.0.0/24").ip()`,
   172  			expectResult: apiservercel.IP{Addr: ipv4Addr},
   173  		},
   174  		{
   175  			name:         "masks masked ipv4",
   176  			expr:         `cidr("192.168.0.0/24").masked()`,
   177  			expectResult: apiservercel.CIDR{Prefix: netip.PrefixFrom(ipv4Addr, 24)},
   178  		},
   179  		{
   180  			name:         "masks unmasked ipv4",
   181  			expr:         `cidr("192.168.0.1/24").masked()`,
   182  			expectResult: apiservercel.CIDR{Prefix: netip.PrefixFrom(ipv4Addr, 24)},
   183  		},
   184  		{
   185  			name:         "returns prefix length ipv4",
   186  			expr:         `cidr("192.168.0.0/24").prefixLength()`,
   187  			expectResult: types.Int(24),
   188  		},
   189  		{
   190  			name:         "parse ipv6",
   191  			expr:         `cidr("2001:db8::/32")`,
   192  			expectResult: apiservercel.CIDR{Prefix: ipv6CIDR},
   193  		},
   194  		{
   195  			name:             "parse invalid ipv6",
   196  			expr:             `cidr("2001:db8::/")`,
   197  			expectRuntimeErr: "network address parse error during conversion from string: network address parse error during conversion from string: netip.ParsePrefix(\"2001:db8::/\"): bad bits after slash: \"\"",
   198  		},
   199  		{
   200  			name:         "contains IP ipv6 (IP)",
   201  			expr:         `cidr("2001:db8::/32").containsIP(ip("2001:db8::1"))`,
   202  			expectResult: trueVal,
   203  		},
   204  		{
   205  			name:         "does not contain IP ipv6 (IP)",
   206  			expr:         `cidr("2001:db8::/32").containsIP(ip("2001:dc8::1"))`,
   207  			expectResult: falseVal,
   208  		},
   209  		{
   210  			name:         "contains IP ipv6 (string)",
   211  			expr:         `cidr("2001:db8::/32").containsIP("2001:db8::1")`,
   212  			expectResult: trueVal,
   213  		},
   214  		{
   215  			name:         "does not contain IP ipv6 (string)",
   216  			expr:         `cidr("2001:db8::/32").containsIP("2001:dc8::1")`,
   217  			expectResult: falseVal,
   218  		},
   219  		{
   220  			name:         "contains CIDR ipv6 (CIDR)",
   221  			expr:         `cidr("2001:db8::/32").containsCIDR(cidr("2001:db8::/33"))`,
   222  			expectResult: trueVal,
   223  		},
   224  		{
   225  			name:         "does not contain IP ipv6 (CIDR)",
   226  			expr:         `cidr("2001:db8::/32").containsCIDR(cidr("2001:db8::/31"))`,
   227  			expectResult: falseVal,
   228  		},
   229  		{
   230  			name:         "contains CIDR ipv6 (string)",
   231  			expr:         `cidr("2001:db8::/32").containsCIDR("2001:db8::/33")`,
   232  			expectResult: trueVal,
   233  		},
   234  		{
   235  			name:         "does not contain CIDR ipv6 (string)",
   236  			expr:         `cidr("2001:db8::/32").containsCIDR("2001:db8::/31")`,
   237  			expectResult: falseVal,
   238  		},
   239  		{
   240  			name:         "returns IP ipv6",
   241  			expr:         `cidr("2001:db8::/32").ip()`,
   242  			expectResult: apiservercel.IP{Addr: ipv6Addr},
   243  		},
   244  		{
   245  			name:         "masks masked ipv6",
   246  			expr:         `cidr("2001:db8::/32").masked()`,
   247  			expectResult: apiservercel.CIDR{Prefix: netip.PrefixFrom(ipv6Addr, 32)},
   248  		},
   249  		{
   250  			name:         "masks unmasked ipv6",
   251  			expr:         `cidr("2001:db8:1::/32").masked()`,
   252  			expectResult: apiservercel.CIDR{Prefix: netip.PrefixFrom(ipv6Addr, 32)},
   253  		},
   254  		{
   255  			name:         "returns prefix length ipv6",
   256  			expr:         `cidr("2001:db8::/32").prefixLength()`,
   257  			expectResult: types.Int(32),
   258  		},
   259  		{
   260  			name:         "converting a CIDR to a string",
   261  			expr:         `string(cidr("192.168.0.0/24"))`,
   262  			expectResult: types.String("192.168.0.0/24"),
   263  		},
   264  		{
   265  			name:         "type of CIDR is net.CIDR",
   266  			expr:         `type(cidr("192.168.0.0/24")) == net.CIDR`,
   267  			expectResult: trueVal,
   268  		},
   269  	}
   270  
   271  	for _, tc := range cases {
   272  		t.Run(tc.name, func(t *testing.T) {
   273  			testCIDR(t, tc.expr, tc.expectResult, tc.expectRuntimeErr, tc.expectCompileErrs)
   274  		})
   275  	}
   276  }