istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/security/security_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package security_test
    16  
    17  import (
    18  	"reflect"
    19  	"testing"
    20  
    21  	"istio.io/istio/pkg/config/security"
    22  	"istio.io/istio/pkg/test/util/assert"
    23  )
    24  
    25  func TestParseJwksURI(t *testing.T) {
    26  	cases := []struct {
    27  		in            string
    28  		expected      security.JwksInfo
    29  		expectedError bool
    30  	}{
    31  		{
    32  			in:            "foo.bar.com",
    33  			expectedError: true,
    34  		},
    35  		{
    36  			in:            "tcp://foo.bar.com:abc",
    37  			expectedError: true,
    38  		},
    39  		{
    40  			in:            "http://foo.bar.com:abc",
    41  			expectedError: true,
    42  		},
    43  		{
    44  			in: "http://foo.bar.com",
    45  			expected: security.JwksInfo{
    46  				Hostname: "foo.bar.com",
    47  				Scheme:   "http",
    48  				Port:     80,
    49  				UseSSL:   false,
    50  			},
    51  		},
    52  		{
    53  			in: "https://foo.bar.com",
    54  			expected: security.JwksInfo{
    55  				Hostname: "foo.bar.com",
    56  				Scheme:   "https",
    57  				Port:     443,
    58  				UseSSL:   true,
    59  			},
    60  		},
    61  		{
    62  			in: "http://foo.bar.com:1234",
    63  			expected: security.JwksInfo{
    64  				Hostname: "foo.bar.com",
    65  				Scheme:   "http",
    66  				Port:     1234,
    67  				UseSSL:   false,
    68  			},
    69  		},
    70  		{
    71  			in: "https://foo.bar.com:1234/secure/key",
    72  			expected: security.JwksInfo{
    73  				Hostname: "foo.bar.com",
    74  				Scheme:   "https",
    75  				Port:     1234,
    76  				UseSSL:   true,
    77  			},
    78  		},
    79  	}
    80  	for _, c := range cases {
    81  		actual, err := security.ParseJwksURI(c.in)
    82  		if c.expectedError == (err == nil) {
    83  			t.Fatalf("ParseJwksURI(%s): expected error (%v), got (%v)", c.in, c.expectedError, err)
    84  		}
    85  		if !reflect.DeepEqual(c.expected, actual) {
    86  			t.Fatalf("expected %+v, got %+v", c.expected, actual)
    87  		}
    88  	}
    89  }
    90  
    91  func TestValidateCondition(t *testing.T) {
    92  	cases := []struct {
    93  		key       string
    94  		values    []string
    95  		wantError bool
    96  	}{
    97  		{
    98  			key:       "request.headers[:authority]",
    99  			values:    []string{"productpage", ""},
   100  			wantError: true,
   101  		},
   102  		{
   103  			key:    "request.headers[:authority]",
   104  			values: []string{"productpage"},
   105  		},
   106  		{
   107  			key:       "request.headers[]",
   108  			values:    []string{"productpage"},
   109  			wantError: true,
   110  		},
   111  		{
   112  			key:    "source.ip",
   113  			values: []string{"1.2.3.4", "5.6.7.0/24"},
   114  		},
   115  		{
   116  			key:       "source.ip",
   117  			values:    []string{"a.b.c.d"},
   118  			wantError: true,
   119  		},
   120  		{
   121  			key:    "remote.ip",
   122  			values: []string{"1.2.3.4", "5.6.7.0/24"},
   123  		},
   124  		{
   125  			key:       "remote.ip",
   126  			values:    []string{"a.b.c.d"},
   127  			wantError: true,
   128  		},
   129  		{
   130  			key:    "source.namespace",
   131  			values: []string{"value"},
   132  		},
   133  		{
   134  			key:       "source.user",
   135  			values:    []string{"value"},
   136  			wantError: true,
   137  		},
   138  		{
   139  			key:    "source.principal",
   140  			values: []string{"value"},
   141  		},
   142  		{
   143  			key:    "request.auth.principal",
   144  			values: []string{"value"},
   145  		},
   146  		{
   147  			key:    "request.auth.audiences",
   148  			values: []string{"value"},
   149  		},
   150  		{
   151  			key:    "request.auth.presenter",
   152  			values: []string{"value"},
   153  		},
   154  		{
   155  			key:    "request.auth.claims[id]",
   156  			values: []string{"123"},
   157  		},
   158  		{
   159  			key:       "request.auth.claims[]",
   160  			values:    []string{"value"},
   161  			wantError: true,
   162  		},
   163  		{
   164  			key:    "destination.ip",
   165  			values: []string{"1.2.3.4", "5.6.7.0/24"},
   166  		},
   167  		{
   168  			key:       "destination.ip",
   169  			values:    []string{"a.b.c.d"},
   170  			wantError: true,
   171  		},
   172  		{
   173  			key:    "destination.port",
   174  			values: []string{"80", "90"},
   175  		},
   176  		{
   177  			key:       "destination.port",
   178  			values:    []string{"80", "x"},
   179  			wantError: true,
   180  		},
   181  		{
   182  			key:       "destination.labels[app]",
   183  			values:    []string{"value"},
   184  			wantError: true,
   185  		},
   186  		{
   187  			key:       "destination.name",
   188  			values:    []string{"value"},
   189  			wantError: true,
   190  		},
   191  		{
   192  			key:       "destination.namespace",
   193  			values:    []string{"value"},
   194  			wantError: true,
   195  		},
   196  		{
   197  			key:       "destination.user",
   198  			values:    []string{"value"},
   199  			wantError: true,
   200  		},
   201  		{
   202  			key:    "connection.sni",
   203  			values: []string{"value"},
   204  		},
   205  		{
   206  			key:    "experimental.envoy.filters.a.b[c]",
   207  			values: []string{"value"},
   208  		},
   209  		{
   210  			key:       "experimental.envoy.filters.a.b.x",
   211  			values:    []string{"value"},
   212  			wantError: true,
   213  		},
   214  	}
   215  	for _, c := range cases {
   216  		err := security.ValidateAttribute(c.key, c.values)
   217  		if c.wantError == (err == nil) {
   218  			t.Fatalf("ValidateAttribute(%s): want error (%v) but got (%v)", c.key, c.wantError, err)
   219  		}
   220  	}
   221  }
   222  
   223  func TestCheckValidPathTemplate(t *testing.T) {
   224  	cases := []struct {
   225  		name      string
   226  		values    []string
   227  		wantError bool
   228  	}{
   229  		{
   230  			name:   "valid path template - matchOneTemplate",
   231  			values: []string{"/{*}/foo"},
   232  		},
   233  		{
   234  			name:   "valid path template - matchAnyTemplate",
   235  			values: []string{"/foo/{**}/bar"},
   236  		},
   237  		{
   238  			name:   "valid path template - matchAnyTemplate at end",
   239  			values: []string{"/foo/bar/{**}"},
   240  		},
   241  		{
   242  			name:      "unsupported path template - matchAnyTemplate with additional chars",
   243  			values:    []string{"/foo/{**}buzz/bar"},
   244  			wantError: true,
   245  		},
   246  		{
   247  			name:      "unsupported path template - empty curly braces",
   248  			values:    []string{"/{*}/foo/{}/bar"},
   249  			wantError: true,
   250  		},
   251  		{
   252  			name:      "unsupported path template - matchOneTemplate with `*`",
   253  			values:    []string{"/foo/{*}/bar/*"},
   254  			wantError: true,
   255  		},
   256  		{
   257  			name:      "unsupported path template - matchOneTemplate with `**`",
   258  			values:    []string{"/foo/{*}/bar/**/buzz"},
   259  			wantError: true,
   260  		},
   261  		{
   262  			name:      "unsupported path/path template - named var: {buzz}",
   263  			values:    []string{"/foo/{buzz}/bar"},
   264  			wantError: true,
   265  		},
   266  		{
   267  			name:      "unsupported path/path template - only named var: {buzz}",
   268  			values:    []string{"/{buzz}"},
   269  			wantError: true,
   270  		},
   271  		{
   272  			name:      "unsupported path template - matchAnyTemplate with named var: {buzz}",
   273  			values:    []string{"/foo/{buzz}/bar/{**}"},
   274  			wantError: true,
   275  		},
   276  		{
   277  			name:      "unsupported path template - matchAnyTemplate with named var: {buzz=*}",
   278  			values:    []string{"/foo/{buzz=*}/bar/{**}"},
   279  			wantError: true,
   280  		},
   281  		{
   282  			name:      "unsupported path template - matchAnyTemplate with named var: {buzz=**}",
   283  			values:    []string{"/{*}/foo/{buzz=**}/bar"},
   284  			wantError: true,
   285  		},
   286  		{
   287  			name:      "unsupported path template - matchAnyTemplate with additional chars at end",
   288  			values:    []string{"/{*}/foo/{**}bar"},
   289  			wantError: true,
   290  		},
   291  		{
   292  			name:      "unsupported path template - matchOneTemplate with file extension",
   293  			values:    []string{"/{*}/foo/{*}.txt"},
   294  			wantError: true,
   295  		},
   296  		{
   297  			name:      "unsupported path template - matchOneTemplate with unmatched open curly brace",
   298  			values:    []string{"/{*}/foo/{temp"},
   299  			wantError: true,
   300  		},
   301  		{
   302  			name:      "unsupported path template - matchOneTemplate with unmatched closed curly brace",
   303  			values:    []string{"/{*}/foo/temp}/bar"},
   304  			wantError: true,
   305  		},
   306  		{
   307  			name:      "unsupported path template - matchOneTemplate with unmatched closed curly brace and `*`",
   308  			values:    []string{"/{*}/foo/temp}/*"},
   309  			wantError: true,
   310  		},
   311  	}
   312  	for _, c := range cases {
   313  		err := security.CheckValidPathTemplate(c.name, c.values)
   314  		if c.wantError == (err == nil) {
   315  			t.Fatalf("CheckValidPathTemplate(%s): want error (%v) but got (%v)", c.name, c.wantError, err)
   316  		}
   317  	}
   318  }
   319  
   320  func TestContainsPathTemplate(t *testing.T) {
   321  	testCases := []struct {
   322  		name           string
   323  		path           string
   324  		isPathTemplate bool
   325  	}{
   326  		{
   327  			name:           "matchOneOnly",
   328  			path:           "foo/bar/{*}",
   329  			isPathTemplate: true,
   330  		},
   331  		{
   332  			name:           "matchAnyOnly",
   333  			path:           "foo/{**}/bar",
   334  			isPathTemplate: true,
   335  		},
   336  		{
   337  			name:           "matchAnyAndOne",
   338  			path:           "{*}/bar/{**}",
   339  			isPathTemplate: true,
   340  		},
   341  		{
   342  			name:           "stringMatch",
   343  			path:           "foo/bar/*",
   344  			isPathTemplate: false,
   345  		},
   346  		{
   347  			name:           "namedVariable",
   348  			path:           "foo/bar/{buzz}",
   349  			isPathTemplate: false,
   350  		},
   351  	}
   352  
   353  	for _, tc := range testCases {
   354  		t.Run(tc.name, func(t *testing.T) {
   355  			pathTemplate := security.ContainsPathTemplate(tc.path)
   356  			assert.Equal(t, tc.isPathTemplate, pathTemplate)
   357  		})
   358  	}
   359  }