istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/security/authz/model/model_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 model 16 17 import ( 18 "reflect" 19 "strings" 20 "testing" 21 22 "github.com/davecgh/go-spew/spew" 23 rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" 24 matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" 25 26 authzpb "istio.io/api/security/v1beta1" 27 "istio.io/istio/pilot/pkg/security/trustdomain" 28 "istio.io/istio/pkg/util/protomarshal" 29 ) 30 31 func TestModel_MigrateTrustDomain(t *testing.T) { 32 cases := []struct { 33 name string 34 tdBundle trustdomain.Bundle 35 rule *authzpb.Rule 36 want []string 37 notWant []string 38 }{ 39 { 40 name: "no-source-principal", 41 tdBundle: trustdomain.NewBundle("td-1", []string{"td-2"}), 42 rule: yamlRule(t, ` 43 from: 44 - source: 45 requestPrincipals: ["td-1/ns/foo/sa/sleep"] 46 `), 47 want: []string{ 48 "td-1/ns/foo/sa/sleep", 49 }, 50 notWant: []string{ 51 "td-2/ns/foo/sa/sleep", 52 }, 53 }, 54 { 55 name: "source-principal-field", 56 tdBundle: trustdomain.NewBundle("td-1", []string{"td-2"}), 57 rule: yamlRule(t, ` 58 from: 59 - source: 60 principals: ["td-1/ns/foo/sa/sleep"] 61 `), 62 want: []string{ 63 "td-1/ns/foo/sa/sleep", 64 "td-2/ns/foo/sa/sleep", 65 }, 66 }, 67 { 68 name: "source-principal-attribute", 69 tdBundle: trustdomain.NewBundle("td-1", []string{"td-2"}), 70 rule: yamlRule(t, ` 71 when: 72 - key: source.principal 73 values: ["td-1/ns/foo/sa/sleep"] 74 `), 75 want: []string{ 76 "td-1/ns/foo/sa/sleep", 77 "td-2/ns/foo/sa/sleep", 78 }, 79 }, 80 } 81 82 for _, tc := range cases { 83 t.Run(tc.name, func(t *testing.T) { 84 got, err := New(tc.rule, false) 85 if err != nil { 86 t.Fatal(err) 87 } 88 got.MigrateTrustDomain(tc.tdBundle) 89 gotStr := spew.Sdump(got) 90 for _, want := range tc.want { 91 if !strings.Contains(gotStr, want) { 92 t.Errorf("got %s but not found %s", gotStr, want) 93 } 94 } 95 for _, notWant := range tc.notWant { 96 if strings.Contains(gotStr, notWant) { 97 t.Errorf("got %s but not want %s", gotStr, notWant) 98 } 99 } 100 }) 101 } 102 } 103 104 func TestModel_Generate(t *testing.T) { 105 rule := yamlRule(t, ` 106 from: 107 - source: 108 requestPrincipals: ["td-1/ns/foo/sa/sleep-1"] 109 notRequestPrincipals: ["td-1/ns/foo/sa/sleep-2"] 110 - source: 111 requestPrincipals: ["td-1/ns/foo/sa/sleep-3"] 112 notRequestPrincipals: ["td-1/ns/foo/sa/sleep-4"] 113 to: 114 - operation: 115 ports: ["8001"] 116 notPorts: ["8002"] 117 - operation: 118 ports: ["8003"] 119 notPorts: ["8004"] 120 when: 121 - key: "source.principal" 122 values: ["td-1/ns/foo/sa/httpbin-1"] 123 notValues: ["td-1/ns/foo/sa/httpbin-2"] 124 - key: "destination.ip" 125 values: ["10.0.0.1"] 126 notValues: ["10.0.0.2"] 127 `) 128 129 cases := []struct { 130 name string 131 forTCP bool 132 action rbacpb.RBAC_Action 133 rule *authzpb.Rule 134 want []string 135 notWant []string 136 }{ 137 { 138 name: "allow-http", 139 action: rbacpb.RBAC_ALLOW, 140 rule: rule, 141 want: []string{ 142 "td-1/ns/foo/sa/sleep-1", 143 "td-1/ns/foo/sa/sleep-2", 144 "td-1/ns/foo/sa/sleep-3", 145 "td-1/ns/foo/sa/sleep-4", 146 "td-1/ns/foo/sa/httpbin-1", 147 "td-1/ns/foo/sa/httpbin-2", 148 "8001", 149 "8002", 150 "8003", 151 "8004", 152 "10.0.0.1", 153 "10.0.0.2", 154 }, 155 }, 156 { 157 name: "allow-tcp", 158 action: rbacpb.RBAC_ALLOW, 159 forTCP: true, 160 rule: rule, 161 notWant: []string{ 162 "td-1/ns/foo/sa/sleep-1", 163 "td-1/ns/foo/sa/sleep-2", 164 "td-1/ns/foo/sa/sleep-3", 165 "td-1/ns/foo/sa/sleep-4", 166 "td-1/ns/foo/sa/httpbin-1", 167 "td-1/ns/foo/sa/httpbin-2", 168 "8001", 169 "8002", 170 "8003", 171 "8004", 172 "10.0.0.1", 173 "10.0.0.2", 174 }, 175 }, 176 { 177 name: "deny-http", 178 action: rbacpb.RBAC_DENY, 179 rule: rule, 180 want: []string{ 181 "td-1/ns/foo/sa/sleep-1", 182 "td-1/ns/foo/sa/sleep-2", 183 "td-1/ns/foo/sa/sleep-3", 184 "td-1/ns/foo/sa/sleep-4", 185 "td-1/ns/foo/sa/httpbin-1", 186 "td-1/ns/foo/sa/httpbin-2", 187 "8001", 188 "8002", 189 "8003", 190 "8004", 191 "10.0.0.1", 192 "10.0.0.2", 193 }, 194 }, 195 { 196 name: "deny-tcp", 197 action: rbacpb.RBAC_DENY, 198 forTCP: true, 199 rule: rule, 200 want: []string{ 201 "8001", 202 "8002", 203 "8003", 204 "8004", 205 "10.0.0.1", 206 "10.0.0.2", 207 "td-1/ns/foo/sa/httpbin-1", 208 "td-1/ns/foo/sa/httpbin-2", 209 }, 210 notWant: []string{ 211 "td-1/ns/foo/sa/sleep-1", 212 "td-1/ns/foo/sa/sleep-2", 213 "td-1/ns/foo/sa/sleep-3", 214 "td-1/ns/foo/sa/sleep-4", 215 }, 216 }, 217 { 218 name: "audit-http", 219 action: rbacpb.RBAC_LOG, 220 rule: rule, 221 want: []string{ 222 "td-1/ns/foo/sa/sleep-1", 223 "td-1/ns/foo/sa/sleep-2", 224 "td-1/ns/foo/sa/sleep-3", 225 "td-1/ns/foo/sa/sleep-4", 226 "td-1/ns/foo/sa/httpbin-1", 227 "td-1/ns/foo/sa/httpbin-2", 228 "8001", 229 "8002", 230 "8003", 231 "8004", 232 "10.0.0.1", 233 "10.0.0.2", 234 }, 235 }, 236 { 237 name: "audit-tcp", 238 action: rbacpb.RBAC_LOG, 239 forTCP: true, 240 rule: rule, 241 want: []string{ 242 "8001", 243 "8002", 244 "8003", 245 "8004", 246 "10.0.0.1", 247 "10.0.0.2", 248 "td-1/ns/foo/sa/httpbin-1", 249 "td-1/ns/foo/sa/httpbin-2", 250 }, 251 notWant: []string{ 252 "td-1/ns/foo/sa/sleep-1", 253 "td-1/ns/foo/sa/sleep-2", 254 "td-1/ns/foo/sa/sleep-3", 255 "td-1/ns/foo/sa/sleep-4", 256 }, 257 }, 258 } 259 260 for _, tc := range cases { 261 t.Run(tc.name, func(t *testing.T) { 262 m, err := New(tc.rule, false) 263 if err != nil { 264 t.Fatal(err) 265 } 266 p, _ := m.Generate(tc.forTCP, false, tc.action) 267 var gotYaml string 268 if p != nil { 269 if gotYaml, err = protomarshal.ToYAML(p); err != nil { 270 t.Fatalf("%s: failed to parse yaml: %s", tc.name, err) 271 } 272 } 273 274 for _, want := range tc.want { 275 if !strings.Contains(gotYaml, want) { 276 t.Errorf("got:\n%s but not found %s", gotYaml, want) 277 } 278 } 279 for _, notWant := range tc.notWant { 280 if strings.Contains(gotYaml, notWant) { 281 t.Errorf("got:\n%s but not want %s", gotYaml, notWant) 282 } 283 } 284 }) 285 } 286 } 287 288 func TestRule_Principal(t *testing.T) { 289 tests := []struct { 290 name string 291 r rule 292 forTCP bool 293 useAuthenticated bool 294 action rbacpb.RBAC_Action 295 want []*rbacpb.Principal 296 }{ 297 { 298 name: "useAuthenticated:true", 299 r: rule{ 300 key: "foo", 301 values: []string{"value"}, 302 notValues: []string{"notValue"}, 303 g: srcPrincipalGenerator{}, 304 }, 305 forTCP: true, 306 useAuthenticated: true, 307 action: rbacpb.RBAC_ALLOW, 308 want: []*rbacpb.Principal{ 309 { 310 Identifier: &rbacpb.Principal_OrIds{ 311 OrIds: &rbacpb.Principal_Set{ 312 Ids: []*rbacpb.Principal{ 313 { 314 Identifier: &rbacpb.Principal_Authenticated_{ 315 Authenticated: &rbacpb.Principal_Authenticated{ 316 PrincipalName: &matcherv3.StringMatcher{ 317 MatchPattern: &matcherv3.StringMatcher_Exact{ 318 Exact: "spiffe://value", 319 }, 320 }, 321 }, 322 }, 323 }, 324 }, 325 }, 326 }, 327 }, 328 { 329 Identifier: &rbacpb.Principal_NotId{ 330 NotId: &rbacpb.Principal{ 331 Identifier: &rbacpb.Principal_OrIds{ 332 OrIds: &rbacpb.Principal_Set{ 333 Ids: []*rbacpb.Principal{ 334 { 335 Identifier: &rbacpb.Principal_Authenticated_{ 336 Authenticated: &rbacpb.Principal_Authenticated{ 337 PrincipalName: &matcherv3.StringMatcher{ 338 MatchPattern: &matcherv3.StringMatcher_Exact{ 339 Exact: "spiffe://notValue", 340 }, 341 }, 342 }, 343 }, 344 }, 345 }, 346 }, 347 }, 348 }, 349 }, 350 }, 351 }, 352 }, 353 { 354 name: "useAuthenticated:false", 355 r: rule{ 356 key: "foo", 357 values: []string{"value"}, 358 notValues: []string{"notValue"}, 359 g: srcNamespaceGenerator{}, 360 }, 361 forTCP: false, 362 useAuthenticated: false, 363 action: rbacpb.RBAC_ALLOW, 364 want: []*rbacpb.Principal{ 365 { 366 Identifier: &rbacpb.Principal_OrIds{ 367 OrIds: &rbacpb.Principal_Set{ 368 Ids: []*rbacpb.Principal{ 369 { 370 Identifier: &rbacpb.Principal_FilterState{ 371 FilterState: &matcherv3.FilterStateMatcher{ 372 Key: "io.istio.peer_principal", 373 Matcher: &matcherv3.FilterStateMatcher_StringMatch{ 374 StringMatch: &matcherv3.StringMatcher{ 375 MatchPattern: &matcherv3.StringMatcher_SafeRegex{ 376 SafeRegex: &matcherv3.RegexMatcher{ 377 Regex: ".*/ns/value/.*", 378 }, 379 }, 380 }, 381 }, 382 }, 383 }, 384 }, 385 }, 386 }, 387 }, 388 }, 389 { 390 Identifier: &rbacpb.Principal_NotId{ 391 NotId: &rbacpb.Principal{ 392 Identifier: &rbacpb.Principal_OrIds{ 393 OrIds: &rbacpb.Principal_Set{ 394 Ids: []*rbacpb.Principal{ 395 { 396 Identifier: &rbacpb.Principal_FilterState{ 397 FilterState: &matcherv3.FilterStateMatcher{ 398 Key: "io.istio.peer_principal", 399 Matcher: &matcherv3.FilterStateMatcher_StringMatch{ 400 StringMatch: &matcherv3.StringMatcher{ 401 MatchPattern: &matcherv3.StringMatcher_SafeRegex{ 402 SafeRegex: &matcherv3.RegexMatcher{ 403 Regex: ".*/ns/notValue/.*", 404 }, 405 }, 406 }, 407 }, 408 }, 409 }, 410 }, 411 }, 412 }, 413 }, 414 }, 415 }, 416 }, 417 }, 418 }, 419 } 420 for _, tt := range tests { 421 t.Run(tt.name, func(t *testing.T) { 422 got, _ := tt.r.principal(tt.forTCP, tt.useAuthenticated, tt.action) 423 if !reflect.DeepEqual(got, tt.want) { 424 t.Errorf("rule.principal got %v, want %v", got, tt.want) 425 } 426 }) 427 } 428 } 429 430 func yamlRule(t *testing.T, yaml string) *authzpb.Rule { 431 t.Helper() 432 p := &authzpb.Rule{} 433 if err := protomarshal.ApplyYAML(yaml, p); err != nil { 434 t.Fatalf("failed to parse yaml: %s", err) 435 } 436 return p 437 }