github.com/cilium/cilium@v1.16.2/pkg/k8s/network_policy_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package k8s 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 "k8s.io/apimachinery/pkg/types" 13 14 "github.com/cilium/cilium/api/v1/models" 15 "github.com/cilium/cilium/pkg/annotation" 16 "github.com/cilium/cilium/pkg/identity" 17 k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io" 18 slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" 19 slim_networkingv1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/networking/v1" 20 slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 21 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/util/intstr" 22 "github.com/cilium/cilium/pkg/labels" 23 "github.com/cilium/cilium/pkg/policy" 24 "github.com/cilium/cilium/pkg/policy/api" 25 testidentity "github.com/cilium/cilium/pkg/testutils/identity" 26 ) 27 28 var ( 29 labelsA = labels.LabelArray{ 30 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 31 labels.NewLabel("id", "a", labels.LabelSourceK8s), 32 } 33 34 labelSelectorA = slim_metav1.LabelSelector{ 35 MatchLabels: map[string]string{ 36 "id": "a", 37 }, 38 } 39 40 labelsB = labels.LabelArray{ 41 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 42 labels.NewLabel("id1", "b", labels.LabelSourceK8s), 43 labels.NewLabel("id2", "c", labels.LabelSourceK8s), 44 } 45 46 labelsC = labels.LabelArray{ 47 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 48 labels.NewLabel("id", "c", labels.LabelSourceK8s), 49 } 50 51 labelSelectorC = slim_metav1.LabelSelector{ 52 MatchLabels: map[string]string{ 53 "id": "c", 54 }, 55 } 56 57 ctxAToB = policy.SearchContext{ 58 From: labelsA, 59 To: labelsB, 60 Trace: policy.TRACE_VERBOSE, 61 } 62 63 ctxAToC = policy.SearchContext{ 64 From: labelsA, 65 To: labelsC, 66 Trace: policy.TRACE_VERBOSE, 67 } 68 69 port80 = slim_networkingv1.NetworkPolicyPort{ 70 Port: &intstr.IntOrString{ 71 Type: intstr.Int, 72 IntVal: 80, 73 }, 74 } 75 76 int8090 = int32(8090) 77 port8080to8090 = slim_networkingv1.NetworkPolicyPort{ 78 Port: &intstr.IntOrString{ 79 Type: intstr.Int, 80 IntVal: 8080, 81 }, 82 EndPort: &int8090, 83 } 84 85 dummySelectorCacheUser = &DummySelectorCacheUser{} 86 ) 87 88 type DummySelectorCacheUser struct{} 89 90 func testNewPolicyRepository() *policy.Repository { 91 repo := policy.NewPolicyRepository(nil, nil, nil) 92 repo.GetSelectorCache().SetLocalIdentityNotifier(testidentity.NewDummyIdentityNotifier()) 93 return repo 94 } 95 96 func (d *DummySelectorCacheUser) IdentitySelectionUpdated(selector policy.CachedSelector, added, deleted []identity.NumericIdentity) { 97 } 98 99 func TestParseNetworkPolicyIngress(t *testing.T) { 100 netPolicy := &slim_networkingv1.NetworkPolicy{ 101 Spec: slim_networkingv1.NetworkPolicySpec{ 102 PodSelector: slim_metav1.LabelSelector{ 103 MatchLabels: map[string]string{ 104 "foo1": "bar1", 105 "foo2": "bar2", 106 }, 107 }, 108 Ingress: []slim_networkingv1.NetworkPolicyIngressRule{ 109 { 110 From: []slim_networkingv1.NetworkPolicyPeer{ 111 { 112 PodSelector: &slim_metav1.LabelSelector{ 113 MatchLabels: map[string]string{ 114 "foo3": "bar3", 115 "foo4": "bar4", 116 }, 117 }, 118 }, 119 }, 120 Ports: []slim_networkingv1.NetworkPolicyPort{ 121 { 122 Port: &intstr.IntOrString{ 123 Type: intstr.Int, 124 IntVal: 80, 125 }, 126 }, 127 }, 128 }, 129 }, 130 }, 131 } 132 133 _, err := ParseNetworkPolicy(netPolicy) 134 require.NoError(t, err) 135 136 fromEndpoints := labels.LabelArray{ 137 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 138 labels.NewLabel("foo3", "bar3", labels.LabelSourceK8s), 139 labels.NewLabel("foo4", "bar4", labels.LabelSourceK8s), 140 } 141 142 ctx := policy.SearchContext{ 143 From: fromEndpoints, 144 To: labels.LabelArray{ 145 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 146 labels.NewLabel("foo1", "bar1", labels.LabelSourceK8s), 147 labels.NewLabel("foo2", "bar2", labels.LabelSourceK8s), 148 }, 149 Trace: policy.TRACE_VERBOSE, 150 } 151 152 rules, err := ParseNetworkPolicy(netPolicy) 153 require.NoError(t, err) 154 require.Equal(t, 1, len(rules)) 155 156 repo := testNewPolicyRepository() 157 158 repo.MustAddList(rules) 159 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctx)) 160 161 epSelector := api.NewESFromLabels(fromEndpoints...) 162 cachedEPSelector, _ := repo.GetSelectorCache().AddIdentitySelector(dummySelectorCacheUser, nil, epSelector) 163 defer func() { repo.GetSelectorCache().RemoveSelector(cachedEPSelector, dummySelectorCacheUser) }() 164 165 ingressL4Policy, err := repo.ResolveL4IngressPolicy(&ctx) 166 require.NotNil(t, ingressL4Policy) 167 require.NoError(t, err) 168 expected := policy.NewL4PolicyMapWithValues(map[string]*policy.L4Filter{ 169 "80/TCP": { 170 Port: 80, Protocol: api.ProtoTCP, U8Proto: 6, 171 L7Parser: policy.ParserTypeNone, 172 PerSelectorPolicies: policy.L7DataMap{cachedEPSelector: nil}, 173 Ingress: true, 174 RuleOrigin: map[policy.CachedSelector]labels.LabelArrayList{ 175 cachedEPSelector: {labels.ParseLabelArray( 176 "k8s:"+k8sConst.PolicyLabelName, 177 "k8s:"+k8sConst.PolicyLabelUID, 178 "k8s:"+k8sConst.PolicyLabelNamespace+"=default", 179 "k8s:"+k8sConst.PolicyLabelDerivedFrom+"="+resourceTypeNetworkPolicy, 180 )}, 181 }, 182 }, 183 }) 184 require.True(t, ingressL4Policy.Equals(t, expected), ingressL4Policy.Diff(t, expected)) 185 ingressL4Policy.Detach(repo.GetSelectorCache()) 186 187 ctx.To = labels.LabelArray{ 188 labels.NewLabel("foo2", "bar2", labels.LabelSourceK8s), 189 } 190 191 // ctx.To needs to have all labels from the policy in order to be accepted 192 require.NotEqual(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 193 194 ctx = policy.SearchContext{ 195 From: labels.LabelArray{ 196 labels.NewLabel("foo3", "bar3", labels.LabelSourceK8s), 197 }, 198 To: labels.LabelArray{ 199 labels.NewLabel("foo1", "bar1", labels.LabelSourceK8s), 200 labels.NewLabel("foo2", "bar2", labels.LabelSourceK8s), 201 }, 202 Trace: policy.TRACE_VERBOSE, 203 } 204 // ctx.From also needs to have all labels from the policy in order to be accepted 205 require.NotEqual(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 206 } 207 208 func TestParseNetworkPolicyMultipleSelectors(t *testing.T) { 209 210 // Rule with multiple selectors in egress and ingress 211 ex1 := []byte(`{ 212 "kind":"NetworkPolicy", 213 "apiVersion":"networking.k8s.io/v1", 214 "metadata":{ 215 "name":"ingress-multiple-selectors" 216 }, 217 "spec":{ 218 "podSelector":{ 219 "matchLabels":{ 220 "role":"backend" 221 } 222 }, 223 "egress":[ 224 { 225 "ports":[ 226 { 227 "protocol":"TCP", 228 "port":5432 229 } 230 ], 231 "to":[ 232 { 233 "podSelector":{ 234 "matchLabels":{ 235 "app":"db1" 236 } 237 } 238 }, 239 { 240 "podSelector":{ 241 "matchLabels":{ 242 "app":"db2" 243 } 244 } 245 } 246 ] 247 } 248 ], 249 "ingress":[ 250 { 251 "from":[ 252 { 253 "podSelector":{ 254 "matchLabels":{ 255 "role":"frontend" 256 } 257 }, 258 "namespaceSelector":{ 259 "matchLabels":{ 260 "project":"myproject" 261 } 262 } 263 }, 264 { 265 "podSelector":{ 266 "matchLabels":{ 267 "app":"inventory" 268 } 269 } 270 } 271 ] 272 } 273 ] 274 } 275 }`) 276 277 np := slim_networkingv1.NetworkPolicy{} 278 err := json.Unmarshal(ex1, &np) 279 require.NoError(t, err) 280 281 rules, err := ParseNetworkPolicy(&np) 282 require.NoError(t, err) 283 require.Equal(t, 1, len(rules)) 284 285 repo := testNewPolicyRepository() 286 repo.MustAddList(rules) 287 288 endpointLabels := labels.LabelArray{ 289 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 290 labels.NewLabel("role", "backend", labels.LabelSourceK8s), 291 } 292 293 // Ingress context 294 ctx := policy.SearchContext{ 295 From: labels.LabelArray{ 296 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 297 }, 298 To: endpointLabels, 299 Trace: policy.TRACE_VERBOSE, 300 } 301 302 // should be DENIED because ctx.From is missing the namespace selector 303 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctx)) 304 305 ctx.From = labels.LabelArray{ 306 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 307 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "project"), "myproject", labels.LabelSourceK8s), 308 } 309 310 // should be ALLOWED with the namespace label properly set 311 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 312 313 ctx.From = labels.LabelArray{ 314 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 315 labels.NewLabel("app", "inventory", labels.LabelSourceK8s), 316 } 317 318 // should be ALLOWED since all rules in From must match 319 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 320 321 // Egress context 322 ctx = policy.SearchContext{ 323 From: endpointLabels, 324 To: labels.LabelArray{ 325 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 326 labels.NewLabel("app", "db1", labels.LabelSourceK8s), 327 }, 328 Trace: policy.TRACE_VERBOSE, 329 } 330 331 // should be DENIED because DPorts are missing in context 332 require.Equal(t, api.Denied, repo.AllowsEgressRLocked(&ctx)) 333 334 ctx.DPorts = []*models.Port{{Port: 5432, Protocol: models.PortProtocolTCP}} 335 336 // should be ALLOWED with DPorts set correctly 337 require.Equal(t, api.Allowed, repo.AllowsEgressRLocked(&ctx)) 338 339 ctx.To = labels.LabelArray{ 340 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 341 labels.NewLabel("app", "db2", labels.LabelSourceK8s), 342 } 343 344 // should be ALLOWED for db2 as well 345 require.Equal(t, api.Allowed, repo.AllowsEgressRLocked(&ctx)) 346 } 347 348 func TestParseNetworkPolicyNoSelectors(t *testing.T) { 349 350 // Ingress with neither pod nor namespace selector set. 351 ex1 := []byte(`{ 352 "kind": "NetworkPolicy", 353 "apiVersion": "networking.k8s.io/v1", 354 "metadata": { 355 "name": "ingress-cidr-test", 356 "namespace": "myns", 357 "uid": "11bba160-ddca-11e8-b697-0800273b04ff" 358 }, 359 "spec": { 360 "podSelector": { 361 "matchLabels": { 362 "role": "backend" 363 } 364 }, 365 "ingress": [ 366 { 367 "from": [ 368 { 369 "ipBlock": { 370 "cidr": "10.0.0.0/8", 371 "except": [ 372 "10.96.0.0/12" 373 ] 374 } 375 } 376 ] 377 } 378 ] 379 } 380 }`) 381 382 fromEndpoints := labels.LabelArray{ 383 labels.NewLabel(k8sConst.PodNamespaceLabel, "myns", labels.LabelSourceK8s), 384 labels.NewLabel("role", "backend", labels.LabelSourceK8s), 385 } 386 387 epSelector := api.NewESFromLabels(fromEndpoints...) 388 np := slim_networkingv1.NetworkPolicy{} 389 err := json.Unmarshal(ex1, &np) 390 require.NoError(t, err) 391 392 expectedRule := api.NewRule(). 393 WithEndpointSelector(epSelector). 394 WithIngressRules([]api.IngressRule{ 395 { 396 IngressCommonRule: api.IngressCommonRule{ 397 FromCIDRSet: []api.CIDRRule{ 398 { 399 Cidr: api.CIDR("10.0.0.0/8"), 400 ExceptCIDRs: []api.CIDR{ 401 "10.96.0.0/12", 402 }, 403 }, 404 }, 405 }, 406 }, 407 }). 408 WithEgressRules([]api.EgressRule{}). 409 WithLabels(labels.ParseLabelArray( 410 "k8s:"+k8sConst.PolicyLabelName+"=ingress-cidr-test", 411 "k8s:"+k8sConst.PolicyLabelUID+"=11bba160-ddca-11e8-b697-0800273b04ff", 412 "k8s:"+k8sConst.PolicyLabelNamespace+"=myns", 413 "k8s:"+k8sConst.PolicyLabelDerivedFrom+"="+resourceTypeNetworkPolicy, 414 )) 415 416 expectedRule.Sanitize() 417 418 expectedRules := api.Rules{ 419 expectedRule, 420 } 421 422 rules, err := ParseNetworkPolicy(&np) 423 require.NoError(t, err) 424 require.NotNil(t, rules) 425 require.EqualValues(t, expectedRules, rules) 426 } 427 428 func TestParseNetworkPolicyEgress(t *testing.T) { 429 430 netPolicy := &slim_networkingv1.NetworkPolicy{ 431 Spec: slim_networkingv1.NetworkPolicySpec{ 432 PodSelector: slim_metav1.LabelSelector{ 433 MatchLabels: map[string]string{ 434 "foo1": "bar1", 435 "foo2": "bar2", 436 }, 437 }, 438 Egress: []slim_networkingv1.NetworkPolicyEgressRule{ 439 { 440 To: []slim_networkingv1.NetworkPolicyPeer{ 441 { 442 PodSelector: &slim_metav1.LabelSelector{ 443 MatchLabels: map[string]string{ 444 "foo3": "bar3", 445 "foo4": "bar4", 446 }, 447 }, 448 }, 449 }, 450 Ports: []slim_networkingv1.NetworkPolicyPort{ 451 { 452 Port: &intstr.IntOrString{ 453 Type: intstr.Int, 454 IntVal: 80, 455 }, 456 }, 457 }, 458 }, 459 }, 460 }, 461 } 462 463 rules, err := ParseNetworkPolicy(netPolicy) 464 require.NoError(t, err) 465 require.Equal(t, 1, len(rules)) 466 467 fromEndpoints := labels.LabelArray{ 468 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 469 labels.NewLabel("foo1", "bar1", labels.LabelSourceK8s), 470 labels.NewLabel("foo2", "bar2", labels.LabelSourceK8s), 471 } 472 473 toEndpoints := labels.LabelArray{ 474 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 475 labels.NewLabel("foo3", "bar3", labels.LabelSourceK8s), 476 labels.NewLabel("foo4", "bar4", labels.LabelSourceK8s), 477 } 478 479 ctx := policy.SearchContext{ 480 From: fromEndpoints, 481 To: toEndpoints, 482 Trace: policy.TRACE_VERBOSE, 483 } 484 485 repo := testNewPolicyRepository() 486 repo.MustAddList(rules) 487 // Because search context did not contain port-specific policy, deny is 488 // expected. 489 require.Equal(t, api.Denied, repo.AllowsEgressRLocked(&ctx)) 490 491 epSelector := api.NewESFromLabels(toEndpoints...) 492 cachedEPSelector, _ := repo.GetSelectorCache().AddIdentitySelector(dummySelectorCacheUser, nil, epSelector) 493 defer func() { repo.GetSelectorCache().RemoveSelector(cachedEPSelector, dummySelectorCacheUser) }() 494 495 egressL4Policy, err := repo.ResolveL4EgressPolicy(&ctx) 496 require.NotNil(t, egressL4Policy) 497 require.NoError(t, err) 498 expected := policy.NewL4PolicyMapWithValues(map[string]*policy.L4Filter{ 499 "80/TCP": { 500 Port: 80, Protocol: api.ProtoTCP, U8Proto: 6, 501 L7Parser: policy.ParserTypeNone, 502 PerSelectorPolicies: policy.L7DataMap{cachedEPSelector: nil}, 503 Ingress: false, 504 RuleOrigin: map[policy.CachedSelector]labels.LabelArrayList{ 505 cachedEPSelector: {rules[0].Labels}, 506 }, 507 }, 508 }) 509 require.True(t, egressL4Policy.Equals(t, expected), egressL4Policy.Diff(t, expected)) 510 egressL4Policy.Detach(repo.GetSelectorCache()) 511 512 ctx.From = labels.LabelArray{ 513 labels.NewLabel("foo2", "bar2", labels.LabelSourceK8s), 514 } 515 516 // ctx.From needs to have all labels from the policy in order to be accepted 517 require.NotEqual(t, api.Allowed, repo.AllowsEgressRLocked(&ctx)) 518 519 ctx = policy.SearchContext{ 520 To: labels.LabelArray{ 521 labels.NewLabel("foo3", "bar3", labels.LabelSourceK8s), 522 }, 523 From: labels.LabelArray{ 524 labels.NewLabel("foo1", "bar1", labels.LabelSourceK8s), 525 labels.NewLabel("foo2", "bar2", labels.LabelSourceK8s), 526 }, 527 Trace: policy.TRACE_VERBOSE, 528 } 529 530 // ctx.To also needs to have all labels from the policy in order to be accepted. 531 require.NotEqual(t, api.Allowed, repo.AllowsEgressRLocked(&ctx)) 532 } 533 534 func parseAndAddRules(t *testing.T, p *slim_networkingv1.NetworkPolicy) *policy.Repository { 535 repo := testNewPolicyRepository() 536 rules, err := ParseNetworkPolicy(p) 537 require.NoError(t, err) 538 rev := repo.GetRevision() 539 _, id := repo.MustAddList(rules) 540 require.Equal(t, rev+1, id) 541 542 return repo 543 } 544 545 func TestParseNetworkPolicyEgressAllowAll(t *testing.T) { 546 repo := parseAndAddRules(t, &slim_networkingv1.NetworkPolicy{ 547 Spec: slim_networkingv1.NetworkPolicySpec{ 548 PodSelector: labelSelectorA, 549 Egress: []slim_networkingv1.NetworkPolicyEgressRule{ 550 { 551 To: []slim_networkingv1.NetworkPolicyPeer{}, 552 }, 553 }, 554 }, 555 }) 556 557 require.Equal(t, api.Allowed, repo.AllowsEgressRLocked(&ctxAToB)) 558 require.Equal(t, api.Allowed, repo.AllowsEgressRLocked(&ctxAToC)) 559 560 ctxAToC80 := ctxAToC 561 ctxAToC80.DPorts = []*models.Port{{Port: 80, Protocol: models.PortProtocolTCP}} 562 require.Equal(t, api.Allowed, repo.AllowsEgressRLocked(&ctxAToC80)) 563 564 ctxAToC90 := ctxAToC 565 ctxAToC90.DPorts = []*models.Port{{Port: 90, Protocol: models.PortProtocolTCP}} 566 require.Equal(t, api.Allowed, repo.AllowsEgressRLocked(&ctxAToC90)) 567 } 568 569 func TestParseNetworkPolicyEgressL4AllowAll(t *testing.T) { 570 repo := parseAndAddRules(t, &slim_networkingv1.NetworkPolicy{ 571 Spec: slim_networkingv1.NetworkPolicySpec{ 572 PodSelector: labelSelectorA, 573 Egress: []slim_networkingv1.NetworkPolicyEgressRule{ 574 { 575 Ports: []slim_networkingv1.NetworkPolicyPort{port80}, 576 To: []slim_networkingv1.NetworkPolicyPeer{}, 577 }, 578 }, 579 }, 580 }) 581 582 ctxAToC80 := ctxAToC 583 ctxAToC80.DPorts = []*models.Port{{Port: 80, Protocol: models.PortProtocolTCP}} 584 require.Equal(t, api.Allowed, repo.AllowsEgressRLocked(&ctxAToC80)) 585 586 ctxAToC90 := ctxAToC 587 ctxAToC90.DPorts = []*models.Port{{Port: 90, Protocol: models.PortProtocolTCP}} 588 require.Equal(t, api.Denied, repo.AllowsEgressRLocked(&ctxAToC90)) 589 } 590 591 func TestParseNetworkPolicyEgressL4PortRangeAllowAll(t *testing.T) { 592 repo := parseAndAddRules(t, &slim_networkingv1.NetworkPolicy{ 593 Spec: slim_networkingv1.NetworkPolicySpec{ 594 PodSelector: labelSelectorA, 595 Egress: []slim_networkingv1.NetworkPolicyEgressRule{ 596 { 597 Ports: []slim_networkingv1.NetworkPolicyPort{port8080to8090}, 598 To: []slim_networkingv1.NetworkPolicyPeer{}, 599 }, 600 }, 601 }, 602 }) 603 604 ctxAToC8080 := ctxAToC 605 ctxAToC8080.DPorts = []*models.Port{{Port: 8080, Protocol: models.PortProtocolTCP}} 606 require.Equal(t, repo.AllowsEgressRLocked(&ctxAToC8080), api.Allowed) 607 608 ctxAToC8085 := ctxAToC 609 ctxAToC8085.DPorts = []*models.Port{{Port: 8085, Protocol: models.PortProtocolTCP}} 610 require.Equal(t, repo.AllowsEgressRLocked(&ctxAToC8085), api.Allowed) 611 612 ctxAToC8090 := ctxAToC 613 ctxAToC8090.DPorts = []*models.Port{{Port: 8090, Protocol: models.PortProtocolTCP}} 614 require.Equal(t, repo.AllowsEgressRLocked(&ctxAToC8090), api.Allowed) 615 616 ctxAToC8091 := ctxAToC 617 ctxAToC8091.DPorts = []*models.Port{{Port: 8091, Protocol: models.PortProtocolTCP}} 618 require.Equal(t, repo.AllowsEgressRLocked(&ctxAToC8091), api.Denied) 619 } 620 621 func TestParseNetworkPolicyIngressAllowAll(t *testing.T) { 622 repo := parseAndAddRules(t, &slim_networkingv1.NetworkPolicy{ 623 Spec: slim_networkingv1.NetworkPolicySpec{ 624 PodSelector: labelSelectorC, 625 Ingress: []slim_networkingv1.NetworkPolicyIngressRule{ 626 { 627 From: []slim_networkingv1.NetworkPolicyPeer{}, 628 }, 629 }, 630 }, 631 }) 632 633 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctxAToB)) 634 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctxAToC)) 635 636 ctxAToC80 := ctxAToC 637 ctxAToC80.DPorts = []*models.Port{{Port: 80, Protocol: models.PortProtocolTCP}} 638 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctxAToC80)) 639 640 ctxAToC90 := ctxAToC 641 ctxAToC90.DPorts = []*models.Port{{Port: 90, Protocol: models.PortProtocolTCP}} 642 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctxAToC90)) 643 } 644 645 func TestParseNetworkPolicyIngressL4AllowAll(t *testing.T) { 646 repo := parseAndAddRules(t, &slim_networkingv1.NetworkPolicy{ 647 Spec: slim_networkingv1.NetworkPolicySpec{ 648 PodSelector: labelSelectorC, 649 Ingress: []slim_networkingv1.NetworkPolicyIngressRule{ 650 { 651 Ports: []slim_networkingv1.NetworkPolicyPort{port80}, 652 From: []slim_networkingv1.NetworkPolicyPeer{}, 653 }, 654 }, 655 }, 656 }) 657 658 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctxAToB)) 659 660 ctxAToC80 := ctxAToC 661 ctxAToC80.DPorts = []*models.Port{{Port: 80, Protocol: models.PortProtocolTCP}} 662 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctxAToC80)) 663 664 ctxAToC90 := ctxAToC 665 ctxAToC90.DPorts = []*models.Port{{Port: 90, Protocol: models.PortProtocolTCP}} 666 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctxAToC90)) 667 } 668 669 func TestParseNetworkPolicyNamedPort(t *testing.T) { 670 netPolicy := &slim_networkingv1.NetworkPolicy{ 671 Spec: slim_networkingv1.NetworkPolicySpec{ 672 Ingress: []slim_networkingv1.NetworkPolicyIngressRule{ 673 { 674 Ports: []slim_networkingv1.NetworkPolicyPort{ 675 { 676 Port: &intstr.IntOrString{ 677 Type: intstr.String, 678 StrVal: "port-80", 679 }, 680 }, 681 }, 682 }, 683 }, 684 }, 685 } 686 687 rules, err := ParseNetworkPolicy(netPolicy) 688 require.NoError(t, err) 689 require.Equal(t, 1, len(rules)) 690 } 691 692 func TestParseNetworkPolicyEmptyPort(t *testing.T) { 693 netPolicy := &slim_networkingv1.NetworkPolicy{ 694 Spec: slim_networkingv1.NetworkPolicySpec{ 695 Ingress: []slim_networkingv1.NetworkPolicyIngressRule{ 696 { 697 Ports: []slim_networkingv1.NetworkPolicyPort{ 698 {}, 699 }, 700 }, 701 }, 702 }, 703 } 704 705 rules, err := ParseNetworkPolicy(netPolicy) 706 require.NoError(t, err) 707 require.Equal(t, 1, len(rules)) 708 require.Equal(t, 1, len(rules[0].Ingress)) 709 require.Equal(t, 1, len(rules[0].Ingress[0].ToPorts)) 710 ports := rules[0].Ingress[0].ToPorts[0].Ports 711 require.Equal(t, 1, len(ports)) 712 require.Equal(t, "0", ports[0].Port) 713 require.Equal(t, api.ProtoTCP, ports[0].Protocol) 714 } 715 716 func TestParsePorts(t *testing.T) { 717 rules := parsePorts([]slim_networkingv1.NetworkPolicyPort{ 718 {}, 719 }) 720 require.Equal(t, 1, len(rules)) 721 require.Equal(t, 1, len(rules[0].Ports)) 722 require.Equal(t, "0", rules[0].Ports[0].Port) 723 require.Equal(t, api.ProtoTCP, rules[0].Ports[0].Protocol) 724 } 725 726 func TestParseNetworkPolicyUnknownProto(t *testing.T) { 727 unknownProtocol := slim_corev1.Protocol("unknown") 728 netPolicy := &slim_networkingv1.NetworkPolicy{ 729 Spec: slim_networkingv1.NetworkPolicySpec{ 730 Ingress: []slim_networkingv1.NetworkPolicyIngressRule{ 731 { 732 Ports: []slim_networkingv1.NetworkPolicyPort{ 733 { 734 Port: &intstr.IntOrString{ 735 Type: intstr.String, 736 StrVal: "port-80", 737 }, 738 Protocol: &unknownProtocol, 739 }, 740 }, 741 }, 742 }, 743 }, 744 } 745 746 rules, err := ParseNetworkPolicy(netPolicy) 747 require.NotNil(t, err) 748 require.Equal(t, 0, len(rules)) 749 } 750 751 func TestParseNetworkPolicyEmptyFrom(t *testing.T) { 752 // From missing, all sources should be allowed 753 netPolicy1 := &slim_networkingv1.NetworkPolicy{ 754 Spec: slim_networkingv1.NetworkPolicySpec{ 755 PodSelector: slim_metav1.LabelSelector{ 756 MatchLabels: map[string]string{ 757 "foo1": "bar1", 758 }, 759 }, 760 Ingress: []slim_networkingv1.NetworkPolicyIngressRule{ 761 {}, 762 }, 763 }, 764 } 765 766 rules, err := ParseNetworkPolicy(netPolicy1) 767 require.NoError(t, err) 768 require.Equal(t, 1, len(rules)) 769 770 ctx := policy.SearchContext{ 771 From: labels.LabelArray{ 772 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 773 labels.NewLabel("foo0", "bar0", labels.LabelSourceK8s), 774 }, 775 To: labels.LabelArray{ 776 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 777 labels.NewLabel("foo1", "bar1", labels.LabelSourceK8s), 778 }, 779 Trace: policy.TRACE_VERBOSE, 780 } 781 782 repo := testNewPolicyRepository() 783 repo.MustAddList(rules) 784 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 785 786 // Empty From rules, all sources should be allowed 787 netPolicy2 := &slim_networkingv1.NetworkPolicy{ 788 Spec: slim_networkingv1.NetworkPolicySpec{ 789 PodSelector: slim_metav1.LabelSelector{ 790 MatchLabels: map[string]string{ 791 "foo1": "bar1", 792 }, 793 }, 794 Ingress: []slim_networkingv1.NetworkPolicyIngressRule{ 795 { 796 From: []slim_networkingv1.NetworkPolicyPeer{}, 797 Ports: []slim_networkingv1.NetworkPolicyPort{}, 798 }, 799 }, 800 }, 801 } 802 803 rules, err = ParseNetworkPolicy(netPolicy2) 804 require.NoError(t, err) 805 require.Equal(t, 1, len(rules)) 806 repo = testNewPolicyRepository() 807 repo.MustAddList(rules) 808 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 809 } 810 811 func TestParseNetworkPolicyDenyAll(t *testing.T) { 812 // From missing, all sources should be allowed 813 netPolicy1 := &slim_networkingv1.NetworkPolicy{ 814 Spec: slim_networkingv1.NetworkPolicySpec{ 815 PodSelector: slim_metav1.LabelSelector{ 816 MatchLabels: map[string]string{}, 817 }, 818 }, 819 } 820 821 rules, err := ParseNetworkPolicy(netPolicy1) 822 require.NoError(t, err) 823 require.Equal(t, 1, len(rules)) 824 825 ctx := policy.SearchContext{ 826 From: labels.LabelArray{ 827 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 828 labels.NewLabel("foo0", "bar0", labels.LabelSourceK8s), 829 }, 830 To: labels.LabelArray{ 831 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 832 labels.NewLabel("foo1", "bar1", labels.LabelSourceK8s), 833 }, 834 Trace: policy.TRACE_VERBOSE, 835 } 836 837 repo := testNewPolicyRepository() 838 repo.MustAddList(rules) 839 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctx)) 840 } 841 842 func TestParseNetworkPolicyNoIngress(t *testing.T) { 843 netPolicy := &slim_networkingv1.NetworkPolicy{ 844 Spec: slim_networkingv1.NetworkPolicySpec{ 845 PodSelector: slim_metav1.LabelSelector{ 846 MatchLabels: map[string]string{ 847 "foo1": "bar1", 848 "foo2": "bar2", 849 }, 850 }, 851 }, 852 } 853 854 rules, err := ParseNetworkPolicy(netPolicy) 855 require.NoError(t, err) 856 require.Equal(t, 1, len(rules)) 857 } 858 859 func TestNetworkPolicyExamples(t *testing.T) { 860 // Example 1a: Only allow traffic from frontend pods on TCP port 6379 to 861 // backend pods in the same namespace `myns` 862 ex1 := []byte(`{ 863 "kind": "NetworkPolicy", 864 "apiVersion": "networking.k8s.io/v1", 865 "metadata": { 866 "name": "allow-frontend", 867 "namespace": "myns" 868 }, 869 "spec": { 870 "podSelector": { 871 "matchLabels": { 872 "role": "backend" 873 } 874 }, 875 "ingress": [ 876 { 877 "from": [ 878 { 879 "podSelector": { 880 "matchLabels": { 881 "role": "frontend" 882 } 883 } 884 } 885 ], 886 "ports": [ 887 { 888 "protocol": "TCP", 889 "port": 6379 890 } 891 ] 892 } 893 ] 894 } 895 }`) 896 np := slim_networkingv1.NetworkPolicy{} 897 err := json.Unmarshal(ex1, &np) 898 require.NoError(t, err) 899 900 _, err = ParseNetworkPolicy(&np) 901 require.NoError(t, err) 902 903 // Example 1b: Only allow traffic from frontend pods to backend pods 904 // in the same namespace `myns` 905 ex1 = []byte(`{ 906 "kind": "NetworkPolicy", 907 "apiVersion": "networking.k8s.io/v1", 908 "metadata": { 909 "name": "allow-frontend", 910 "namespace": "myns" 911 }, 912 "spec": { 913 "podSelector": { 914 "matchLabels": { 915 "role": "backend" 916 } 917 }, 918 "ingress": [ 919 { 920 "from": [ 921 { 922 "podSelector": { 923 "matchLabels": { 924 "role": "frontend" 925 } 926 } 927 } 928 ] 929 },{ 930 "ports": [ 931 { 932 "protocol": "TCP", 933 "port": 6379 934 } 935 ] 936 } 937 ] 938 } 939 }`) 940 np = slim_networkingv1.NetworkPolicy{} 941 err = json.Unmarshal(ex1, &np) 942 require.NoError(t, err) 943 944 rules, err := ParseNetworkPolicy(&np) 945 require.NoError(t, err) 946 require.Equal(t, 1, len(rules)) 947 948 repo := testNewPolicyRepository() 949 repo.MustAddList(rules) 950 ctx := policy.SearchContext{ 951 From: labels.LabelArray{ 952 labels.NewLabel(k8sConst.PodNamespaceLabel, "myns", labels.LabelSourceK8s), 953 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 954 }, 955 To: labels.LabelArray{ 956 labels.NewLabel("role", "backend", labels.LabelSourceK8s), 957 }, 958 Trace: policy.TRACE_VERBOSE, 959 } 960 // Doesn't share the same namespace 961 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctx)) 962 963 ctx = policy.SearchContext{ 964 From: labels.LabelArray{ 965 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 966 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 967 }, 968 To: labels.LabelArray{ 969 labels.NewLabel("role", "backend", labels.LabelSourceK8s), 970 }, 971 Trace: policy.TRACE_VERBOSE, 972 } 973 // Doesn't share the same namespace 974 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctx)) 975 976 ctx = policy.SearchContext{ 977 From: labels.LabelArray{ 978 labels.NewLabel(k8sConst.PodNamespaceLabel, "myns", labels.LabelSourceK8s), 979 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 980 }, 981 To: labels.LabelArray{ 982 labels.NewLabel(k8sConst.PodNamespaceLabel, "myns", labels.LabelSourceK8s), 983 labels.NewLabel("role", "backend", labels.LabelSourceK8s), 984 }, 985 DPorts: []*models.Port{ 986 { 987 Port: 6379, 988 Protocol: models.PortProtocolTCP, 989 }, 990 }, 991 Trace: policy.TRACE_VERBOSE, 992 } 993 // Should be ACCEPT sense the traffic needs to come from `frontend` AND 994 // port 6379 and belong to the same namespace `myns`. 995 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 996 997 // Example 2a: Allow TCP 443 from any source in Bob's namespaces. 998 ex2 := []byte(`{ 999 "kind": "NetworkPolicy", 1000 "apiVersion": "networking.k8s.io/v1", 1001 "metadata": { 1002 "name": "allow-tcp-443" 1003 }, 1004 "spec": { 1005 "podSelector": { 1006 "matchLabels": { 1007 "role": "frontend" 1008 } 1009 }, 1010 "ingress": [ 1011 { 1012 "ports": [ 1013 { 1014 "protocol": "TCP", 1015 "port": 443 1016 } 1017 ], 1018 "from": [ 1019 { 1020 "namespaceSelector": { 1021 "matchLabels": { 1022 "user": "bob" 1023 } 1024 } 1025 } 1026 ] 1027 } 1028 ] 1029 } 1030 }`) 1031 1032 np = slim_networkingv1.NetworkPolicy{} 1033 err = json.Unmarshal(ex2, &np) 1034 require.NoError(t, err) 1035 1036 _, err = ParseNetworkPolicy(&np) 1037 require.NoError(t, err) 1038 1039 // Example 2b: Allow from any source in Bob's namespaces. 1040 ex2 = []byte(`{ 1041 "kind": "NetworkPolicy", 1042 "apiVersion": "networking.k8s.io/v1", 1043 "metadata": { 1044 "name": "allow-tcp-443" 1045 }, 1046 "spec": { 1047 "podSelector": { 1048 "matchLabels": { 1049 "role": "frontend" 1050 } 1051 }, 1052 "ingress": [ 1053 { 1054 "ports": [ 1055 { 1056 "protocol": "TCP", 1057 "port": 443 1058 } 1059 ], 1060 "from": [ 1061 { 1062 "namespaceSelector": { 1063 "matchLabels": { 1064 "user": "bob" 1065 } 1066 } 1067 } 1068 ] 1069 } 1070 ] 1071 } 1072 }`) 1073 1074 np = slim_networkingv1.NetworkPolicy{} 1075 err = json.Unmarshal(ex2, &np) 1076 require.NoError(t, err) 1077 1078 rules, err = ParseNetworkPolicy(&np) 1079 require.NoError(t, err) 1080 require.Equal(t, 1, len(rules)) 1081 1082 repo = testNewPolicyRepository() 1083 repo.MustAddList(rules) 1084 ctx = policy.SearchContext{ 1085 From: labels.LabelArray{ 1086 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1087 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "user"), "bob", labels.LabelSourceK8s), 1088 }, 1089 To: labels.LabelArray{ 1090 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1091 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 1092 }, 1093 Trace: policy.TRACE_VERBOSE, 1094 } 1095 1096 // Should be DENY sense the traffic needs to come from 1097 // namespace `user=bob` AND port 443. 1098 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctx)) 1099 1100 l4Policy, err := repo.ResolveL4IngressPolicy(&ctx) 1101 require.NotNil(t, l4Policy) 1102 require.NoError(t, err) 1103 l4Policy.Detach(repo.GetSelectorCache()) 1104 1105 ctx = policy.SearchContext{ 1106 From: labels.LabelArray{ 1107 labels.NewLabel(k8sConst.PodNamespaceLabel, "myns", labels.LabelSourceK8s), 1108 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "user"), "bob", labels.LabelSourceK8s), 1109 }, 1110 DPorts: []*models.Port{ 1111 { 1112 Port: 443, 1113 Protocol: models.PortProtocolTCP, 1114 }, 1115 }, 1116 To: labels.LabelArray{ 1117 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1118 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 1119 }, 1120 Trace: policy.TRACE_VERBOSE, 1121 } 1122 // Should be ACCEPT sense the traffic comes from Bob's namespaces 1123 // (even if it's a different namespace than `default`) AND port 443. 1124 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1125 1126 // Example 3: Allow all traffic to all pods in this namespace. 1127 ex3 := []byte(`{ 1128 "kind": "NetworkPolicy", 1129 "apiVersion": "networking.k8s.io/v1", 1130 "metadata": { 1131 "name": "allow-all" 1132 }, 1133 "spec": { 1134 "podSelector": null, 1135 "ingress": [ 1136 { 1137 } 1138 ] 1139 } 1140 }`) 1141 1142 np = slim_networkingv1.NetworkPolicy{} 1143 err = json.Unmarshal(ex3, &np) 1144 require.NoError(t, err) 1145 1146 rules, err = ParseNetworkPolicy(&np) 1147 require.NoError(t, err) 1148 require.Equal(t, 1, len(rules)) 1149 1150 repo = testNewPolicyRepository() 1151 repo.MustAddList(rules) 1152 ctx = policy.SearchContext{ 1153 From: labels.LabelArray{ 1154 labels.NewLabel(k8sConst.PodNamespaceLabel, "myns", labels.LabelSourceK8s), 1155 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "user"), "bob", labels.LabelSourceK8s), 1156 }, 1157 To: labels.LabelArray{ 1158 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1159 labels.NewLabel("role", "backend", labels.LabelSourceK8s), 1160 }, 1161 Trace: policy.TRACE_VERBOSE, 1162 } 1163 // Should be ACCEPT since it's going to `default` namespace 1164 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1165 1166 ctx = policy.SearchContext{ 1167 From: labels.LabelArray{ 1168 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1169 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "user"), "bob", labels.LabelSourceK8s), 1170 }, 1171 To: labels.LabelArray{ 1172 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1173 labels.NewLabel("role", "backend", labels.LabelSourceK8s), 1174 }, 1175 Trace: policy.TRACE_VERBOSE, 1176 } 1177 // Should be ACCEPT since it's coming from `default` and going to `default` ns 1178 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1179 1180 ctx = policy.SearchContext{ 1181 From: labels.LabelArray{ 1182 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1183 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "user"), "bob", labels.LabelSourceK8s), 1184 }, 1185 To: labels.LabelArray{ 1186 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1187 labels.NewLabel("role", "backend", labels.LabelSourceK8s), 1188 }, 1189 DPorts: []*models.Port{ 1190 { 1191 Port: 443, 1192 Protocol: models.PortProtocolTCP, 1193 }, 1194 }, 1195 Trace: policy.TRACE_VERBOSE, 1196 } 1197 // Should be ACCEPT since it's coming from `default` and going to `default` namespace. 1198 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1199 1200 // Example 4a: Example 4 is similar to example 2 but we will add both network 1201 // policies to see if the rules are additive for the same podSelector. 1202 ex4 := []byte(`{ 1203 "kind": "NetworkPolicy", 1204 "apiVersion": "networking.k8s.io/v1", 1205 "metadata": { 1206 "name": "allow-tcp-8080" 1207 }, 1208 "spec": { 1209 "podSelector": { 1210 "matchLabels": { 1211 "role": "frontend" 1212 } 1213 }, 1214 "ingress": [ 1215 { 1216 "ports": [ 1217 { 1218 "protocol": "UDP", 1219 "port": 8080 1220 } 1221 ], 1222 "from": [ 1223 { 1224 "namespaceSelector": { 1225 "matchLabels": { 1226 "user": "bob" 1227 } 1228 } 1229 } 1230 ] 1231 } 1232 ] 1233 } 1234 }`) 1235 1236 np = slim_networkingv1.NetworkPolicy{} 1237 err = json.Unmarshal(ex4, &np) 1238 require.NoError(t, err) 1239 1240 rules, err = ParseNetworkPolicy(&np) 1241 require.NoError(t, err) 1242 require.Equal(t, 1, len(rules)) 1243 1244 // Example 4b: Example 4 is similar to example 2 but we will add both network 1245 // policies to see if the rules are additive for the same podSelector. 1246 ex4 = []byte(`{ 1247 "kind": "NetworkPolicy", 1248 "apiVersion": "networking.k8s.io/v1", 1249 "metadata": { 1250 "name": "allow-tcp-8080" 1251 }, 1252 "spec": { 1253 "podSelector": { 1254 "matchLabels": { 1255 "role": "frontend" 1256 } 1257 }, 1258 "ingress": [ 1259 { 1260 "ports": [ 1261 { 1262 "protocol": "UDP", 1263 "port": 8080 1264 } 1265 ] 1266 },{ 1267 "from": [ 1268 { 1269 "namespaceSelector": { 1270 "matchLabels": { 1271 "user": "bob" 1272 } 1273 } 1274 } 1275 ] 1276 } 1277 ] 1278 } 1279 }`) 1280 1281 np = slim_networkingv1.NetworkPolicy{} 1282 err = json.Unmarshal(ex4, &np) 1283 require.NoError(t, err) 1284 1285 rules, err = ParseNetworkPolicy(&np) 1286 require.NoError(t, err) 1287 require.Equal(t, 1, len(rules)) 1288 1289 repo = testNewPolicyRepository() 1290 // add example 4 1291 repo.MustAddList(rules) 1292 1293 np = slim_networkingv1.NetworkPolicy{} 1294 err = json.Unmarshal(ex2, &np) 1295 require.NoError(t, err) 1296 1297 rules, err = ParseNetworkPolicy(&np) 1298 require.NoError(t, err) 1299 // add example 2 1300 repo.MustAddList(rules) 1301 1302 ctx = policy.SearchContext{ 1303 From: labels.LabelArray{ 1304 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1305 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "user"), "bob", labels.LabelSourceK8s), 1306 }, 1307 DPorts: []*models.Port{ 1308 { 1309 Protocol: models.PortProtocolUDP, 1310 Port: 8080, 1311 }, 1312 }, 1313 To: labels.LabelArray{ 1314 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1315 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 1316 }, 1317 Trace: policy.TRACE_VERBOSE, 1318 } 1319 // Should be ACCEPT sense traffic comes from Bob's namespaces AND port 8080 as specified in `ex4`. 1320 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1321 1322 ctx = policy.SearchContext{ 1323 From: labels.LabelArray{ 1324 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1325 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "user"), "bob", labels.LabelSourceK8s), 1326 }, 1327 DPorts: []*models.Port{ 1328 { 1329 Port: 443, 1330 Protocol: models.PortProtocolTCP, 1331 }, 1332 }, 1333 To: labels.LabelArray{ 1334 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1335 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 1336 }, 1337 Trace: policy.TRACE_VERBOSE, 1338 } 1339 // Should be ACCEPT sense traffic comes from Bob's namespaces AND port 443 as specified in `ex2`. 1340 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1341 1342 ctx = policy.SearchContext{ 1343 From: labels.LabelArray{ 1344 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1345 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "user"), "alice", labels.LabelSourceK8s), 1346 }, 1347 DPorts: []*models.Port{ 1348 { 1349 Protocol: models.PortProtocolUDP, 1350 Port: 8080, 1351 }, 1352 }, 1353 To: labels.LabelArray{ 1354 labels.NewLabel(k8sConst.PodNamespaceLabel, slim_metav1.NamespaceDefault, labels.LabelSourceK8s), 1355 labels.NewLabel("role", "frontend", labels.LabelSourceK8s), 1356 }, 1357 Trace: policy.TRACE_VERBOSE, 1358 } 1359 // Should be ACCEPT despite coming from Alice's namespaces since it's port 8080 as specified in `ex4`. 1360 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1361 1362 // Example 5: Some policies with match expressions. 1363 ex5 := []byte(`{ 1364 "kind": "NetworkPolicy", 1365 "apiVersion": "networking.k8s.io/v1", 1366 "metadata": { 1367 "name": "allow-tcp-8080", 1368 "namespace": "expressions" 1369 }, 1370 "spec": { 1371 "podSelector": { 1372 "matchLabels": { 1373 "component": "redis" 1374 }, 1375 "matchExpressions": [ 1376 { 1377 "key": "tier", 1378 "operator": "In", 1379 "values": [ 1380 "cache" 1381 ] 1382 }, 1383 { 1384 "key": "environment", 1385 "operator": "NotIn", 1386 "values": [ 1387 "dev" 1388 ] 1389 } 1390 ] 1391 }, 1392 "ingress": [ 1393 { 1394 "ports": [ 1395 { 1396 "protocol": "UDP", 1397 "port": 8080 1398 } 1399 ], 1400 "from": [ 1401 { 1402 "namespaceSelector": { 1403 "matchLabels": { 1404 "component": "redis" 1405 }, 1406 "matchExpressions": [ 1407 { 1408 "key": "tier", 1409 "operator": "In", 1410 "values": [ 1411 "cache" 1412 ] 1413 }, 1414 { 1415 "key": "environment", 1416 "operator": "NotIn", 1417 "values": [ 1418 "dev" 1419 ] 1420 } 1421 ] 1422 } 1423 } 1424 ] 1425 } 1426 ] 1427 } 1428 }`) 1429 1430 np = slim_networkingv1.NetworkPolicy{} 1431 err = json.Unmarshal(ex5, &np) 1432 require.NoError(t, err) 1433 1434 rules, err = ParseNetworkPolicy(&np) 1435 require.NoError(t, err) 1436 require.Equal(t, 1, len(rules)) 1437 repo.MustAddList(rules) 1438 1439 // A reminder: from the kubernetes network policy spec: 1440 // namespaceSelector: 1441 // Selects Namespaces using cluster scoped-labels. This 1442 // matches all pods in all namespaces selected by this label selector. 1443 // This field follows standard label selector semantics. 1444 // If omitted, this selector selects no namespaces. 1445 // If present but empty, this selector selects all namespaces. 1446 ctx = policy.SearchContext{ 1447 From: labels.LabelArray{ 1448 // doesn't matter the namespace. 1449 labels.NewLabel(k8sConst.PodNamespaceLabel, "myns", labels.LabelSourceK8s), 1450 // component==redis is in the policy 1451 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "component"), "redis", labels.LabelSourceK8s), 1452 // tier==cache is in the policy 1453 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "tier"), "cache", labels.LabelSourceK8s), 1454 // environment is not in `dev` which is in the policy 1455 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "environment"), "production", labels.LabelSourceK8s), 1456 // doesn't matter, there isn't any matchExpression denying traffic from any zone. 1457 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "zone"), "eu-1", labels.LabelSourceK8s), 1458 }, 1459 DPorts: []*models.Port{ 1460 { 1461 Port: 8080, 1462 Protocol: models.PortProtocolUDP, 1463 }, 1464 }, 1465 To: labels.LabelArray{ 1466 // Namespace needs to be in `expressions` since the policy is being enforced for that namespace. 1467 labels.NewLabel(k8sConst.PodNamespaceLabel, "expressions", labels.LabelSourceK8s), 1468 // component==redis is in the policy. 1469 labels.NewLabel("component", "redis", labels.LabelSourceK8s), 1470 // tier==cache is in the policy 1471 labels.NewLabel("tier", "cache", labels.LabelSourceK8s), 1472 }, 1473 Trace: policy.TRACE_VERBOSE, 1474 } 1475 // Should be ACCEPT since the SearchContext is being covered by the rules. 1476 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1477 1478 ctx.To = labels.LabelArray{ 1479 // Namespace needs to be in `expressions` since the policy is being enforced for that namespace. 1480 labels.NewLabel(k8sConst.PodNamespaceLabel, "myns", labels.LabelSourceK8s), 1481 // component==redis is in the policy. 1482 labels.NewLabel("component", "redis", labels.LabelSourceK8s), 1483 // tier==cache is in the policy 1484 labels.NewLabel("tier", "cache", labels.LabelSourceK8s), 1485 } 1486 // Should be DENY since the namespace doesn't belong to the policy. 1487 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctx)) 1488 1489 ctx = policy.SearchContext{ 1490 From: labels.LabelArray{ 1491 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "component"), "redis", labels.LabelSourceK8s), 1492 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "tier"), "cache", labels.LabelSourceK8s), 1493 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "environment"), "dev", labels.LabelSourceK8s), 1494 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "zone"), "eu-1", labels.LabelSourceK8s), 1495 }, 1496 DPorts: []*models.Port{ 1497 { 1498 Port: 8080, 1499 Protocol: models.PortProtocolUDP, 1500 }, 1501 }, 1502 To: labels.LabelArray{ 1503 labels.NewLabel(k8sConst.PodNamespaceLabel, "expressions", labels.LabelSourceK8s), 1504 labels.NewLabel("component", "redis", labels.LabelSourceK8s), 1505 labels.NewLabel("tier", "cache", labels.LabelSourceK8s), 1506 }, 1507 Trace: policy.TRACE_VERBOSE, 1508 } 1509 // Should be DENY since the environment is from dev. 1510 require.Equal(t, api.Denied, repo.AllowsIngressRLocked(&ctx)) 1511 1512 ctx = policy.SearchContext{ 1513 From: labels.LabelArray{ 1514 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "component"), "redis", labels.LabelSourceK8s), 1515 labels.NewLabel(policy.JoinPath(k8sConst.PodNamespaceMetaLabels, "tier"), "cache", labels.LabelSourceK8s), 1516 }, 1517 DPorts: []*models.Port{ 1518 { 1519 Port: 8080, 1520 Protocol: models.PortProtocolUDP, 1521 }, 1522 }, 1523 To: labels.LabelArray{ 1524 labels.NewLabel(k8sConst.PodNamespaceLabel, "expressions", labels.LabelSourceK8s), 1525 labels.NewLabel("component", "redis", labels.LabelSourceK8s), 1526 labels.NewLabel("tier", "cache", labels.LabelSourceK8s), 1527 }, 1528 Trace: policy.TRACE_VERBOSE, 1529 } 1530 // Should be ACCEPT since the environment is from dev. 1531 require.Equal(t, api.Allowed, repo.AllowsIngressRLocked(&ctx)) 1532 } 1533 1534 func TestCIDRPolicyExamples(t *testing.T) { 1535 ex1 := []byte(`{ 1536 "kind": "NetworkPolicy", 1537 "apiVersion": "networking.k8s.io/v1", 1538 "metadata": { 1539 "name": "ingress-cidr-test", 1540 "namespace": "myns" 1541 }, 1542 "spec": { 1543 "podSelector": { 1544 "matchLabels": { 1545 "role": "backend" 1546 } 1547 }, 1548 "ingress": [ 1549 { 1550 "from": [ 1551 { 1552 "namespaceSelector": { 1553 "matchLabels": { 1554 "user": "bob" 1555 } 1556 } 1557 } 1558 ] 1559 }, { 1560 "from": [ 1561 { 1562 "ipBlock": { 1563 "cidr": "10.0.0.0/8", 1564 "except": [ 1565 "10.96.0.0/12" 1566 ] 1567 } 1568 } 1569 ] 1570 } 1571 ] 1572 } 1573 }`) 1574 np := slim_networkingv1.NetworkPolicy{} 1575 err := json.Unmarshal(ex1, &np) 1576 require.NoError(t, err) 1577 1578 rules, err := ParseNetworkPolicy(&np) 1579 require.NoError(t, err) 1580 require.NotNil(t, rules) 1581 require.Equal(t, 1, len(rules)) 1582 require.Equal(t, 2, len(rules[0].Ingress)) 1583 1584 ex2 := []byte(`{ 1585 "kind": "NetworkPolicy", 1586 "apiVersion": "networking.k8s.io/v1", 1587 "metadata": { 1588 "name": "ingress-cidr-test", 1589 "namespace": "myns" 1590 }, 1591 "spec": { 1592 "podSelector": { 1593 "matchLabels": { 1594 "role": "backend" 1595 } 1596 }, 1597 "egress": [ 1598 { 1599 "to": [ 1600 { 1601 "ipBlock": { 1602 "cidr": "10.0.0.0/8", 1603 "except": [ 1604 "10.96.0.0/12", "10.255.255.254/32" 1605 ] 1606 } 1607 }, 1608 { 1609 "ipBlock": { 1610 "cidr": "11.0.0.0/8", 1611 "except": [ 1612 "11.96.0.0/12", "11.255.255.254/32" 1613 ] 1614 } 1615 } 1616 ] 1617 } 1618 ] 1619 } 1620 }`) 1621 1622 np = slim_networkingv1.NetworkPolicy{} 1623 err = json.Unmarshal(ex2, &np) 1624 require.NoError(t, err) 1625 1626 rules, err = ParseNetworkPolicy(&np) 1627 require.NoError(t, err) 1628 require.NotNil(t, rules) 1629 require.Equal(t, 1, len(rules)) 1630 require.Equal(t, api.CIDR("10.0.0.0/8"), rules[0].Egress[0].ToCIDRSet[0].Cidr) 1631 1632 expectedCIDRs := []api.CIDR{"10.96.0.0/12", "10.255.255.254/32"} 1633 for k, v := range rules[0].Egress[0].ToCIDRSet[0].ExceptCIDRs { 1634 require.Equal(t, expectedCIDRs[k], v) 1635 } 1636 1637 expectedCIDRs = []api.CIDR{"11.96.0.0/12", "11.255.255.254/32"} 1638 for k, v := range rules[0].Egress[1].ToCIDRSet[0].ExceptCIDRs { 1639 require.Equal(t, expectedCIDRs[k], v) 1640 } 1641 1642 require.Equal(t, 2, len(rules[0].Egress)) 1643 1644 } 1645 1646 func getSelectorPointer(sel api.EndpointSelector) *api.EndpointSelector { 1647 return &sel 1648 } 1649 1650 func Test_parseNetworkPolicyPeer(t *testing.T) { 1651 type args struct { 1652 namespace string 1653 peer *slim_networkingv1.NetworkPolicyPeer 1654 } 1655 tests := []struct { 1656 name string 1657 args args 1658 want *api.EndpointSelector 1659 }{ 1660 { 1661 name: "peer-with-pod-selector", 1662 args: args{ 1663 namespace: "foo-namespace", 1664 peer: &slim_networkingv1.NetworkPolicyPeer{ 1665 PodSelector: &slim_metav1.LabelSelector{ 1666 MatchLabels: map[string]string{ 1667 "foo": "bar", 1668 }, 1669 MatchExpressions: []slim_metav1.LabelSelectorRequirement{ 1670 { 1671 Key: "foo", 1672 Operator: slim_metav1.LabelSelectorOpIn, 1673 Values: []string{"bar", "baz"}, 1674 }, 1675 }, 1676 }, 1677 }, 1678 }, 1679 want: getSelectorPointer( 1680 api.NewESFromMatchRequirements( 1681 map[string]string{ 1682 "k8s.foo": "bar", 1683 "k8s.io.kubernetes.pod.namespace": "foo-namespace", 1684 }, 1685 []slim_metav1.LabelSelectorRequirement{ 1686 { 1687 Key: "k8s.foo", 1688 Operator: slim_metav1.LabelSelectorOpIn, 1689 Values: []string{"bar", "baz"}, 1690 }, 1691 }, 1692 ), 1693 ), 1694 }, 1695 { 1696 name: "peer-nil", 1697 args: args{ 1698 namespace: "foo-namespace", 1699 }, 1700 want: nil, 1701 }, 1702 { 1703 name: "peer-with-pod-selector-and-ns-selector", 1704 args: args{ 1705 namespace: "foo-namespace", 1706 peer: &slim_networkingv1.NetworkPolicyPeer{ 1707 PodSelector: &slim_metav1.LabelSelector{ 1708 MatchLabels: map[string]string{ 1709 "foo": "bar", 1710 }, 1711 MatchExpressions: []slim_metav1.LabelSelectorRequirement{ 1712 { 1713 Key: "foo", 1714 Operator: slim_metav1.LabelSelectorOpIn, 1715 Values: []string{"bar", "baz"}, 1716 }, 1717 }, 1718 }, 1719 NamespaceSelector: &slim_metav1.LabelSelector{ 1720 MatchLabels: map[string]string{ 1721 "ns-foo": "ns-bar", 1722 }, 1723 MatchExpressions: []slim_metav1.LabelSelectorRequirement{ 1724 { 1725 Key: "ns-foo-expression", 1726 Operator: slim_metav1.LabelSelectorOpExists, 1727 }, 1728 }, 1729 }, 1730 }, 1731 }, 1732 want: getSelectorPointer( 1733 api.NewESFromMatchRequirements( 1734 map[string]string{ 1735 "k8s.foo": "bar", 1736 "k8s.io.cilium.k8s.namespace.labels.ns-foo": "ns-bar", 1737 }, 1738 []slim_metav1.LabelSelectorRequirement{ 1739 { 1740 Key: "k8s.io.cilium.k8s.namespace.labels.ns-foo-expression", 1741 Operator: slim_metav1.LabelSelectorOpExists, 1742 }, 1743 { 1744 Key: "k8s.foo", 1745 Operator: slim_metav1.LabelSelectorOpIn, 1746 Values: []string{"bar", "baz"}, 1747 }, 1748 }, 1749 ), 1750 ), 1751 }, 1752 { 1753 name: "peer-with-ns-selector", 1754 args: args{ 1755 namespace: "foo-namespace", 1756 peer: &slim_networkingv1.NetworkPolicyPeer{ 1757 NamespaceSelector: &slim_metav1.LabelSelector{ 1758 MatchLabels: map[string]string{ 1759 "ns-foo": "ns-bar", 1760 }, 1761 MatchExpressions: []slim_metav1.LabelSelectorRequirement{ 1762 { 1763 Key: "ns-foo-expression", 1764 Operator: slim_metav1.LabelSelectorOpExists, 1765 }, 1766 }, 1767 }, 1768 }, 1769 }, 1770 want: getSelectorPointer( 1771 api.NewESFromMatchRequirements( 1772 map[string]string{ 1773 "k8s.io.cilium.k8s.namespace.labels.ns-foo": "ns-bar", 1774 }, 1775 []slim_metav1.LabelSelectorRequirement{ 1776 { 1777 Key: "k8s.io.cilium.k8s.namespace.labels.ns-foo-expression", 1778 Operator: slim_metav1.LabelSelectorOpExists, 1779 }, 1780 }, 1781 ), 1782 ), 1783 }, 1784 { 1785 name: "peer-with-allow-all-ns-selector", 1786 args: args{ 1787 namespace: "foo-namespace", 1788 peer: &slim_networkingv1.NetworkPolicyPeer{ 1789 NamespaceSelector: &slim_metav1.LabelSelector{}, 1790 }, 1791 }, 1792 want: getSelectorPointer( 1793 api.NewESFromMatchRequirements( 1794 map[string]string{}, 1795 []slim_metav1.LabelSelectorRequirement{ 1796 { 1797 Key: fmt.Sprintf("%s.%s", labels.LabelSourceK8s, k8sConst.PodNamespaceLabel), 1798 Operator: slim_metav1.LabelSelectorOpExists, 1799 }, 1800 }, 1801 ), 1802 ), 1803 }, 1804 } 1805 for _, tt := range tests { 1806 t.Run(tt.name, func(t *testing.T) { 1807 got := parseNetworkPolicyPeer(tt.args.namespace, tt.args.peer) 1808 require.EqualValues(t, tt.want, got) 1809 }) 1810 } 1811 } 1812 1813 func TestGetPolicyLabelsv1(t *testing.T) { 1814 uuid := "1bba160-ddca-11e8-b697-0800273b04ff" 1815 tests := []struct { 1816 np *slim_networkingv1.NetworkPolicy // input network policy 1817 name string // expected extracted name 1818 namespace string // expected extracted namespace 1819 uuid string // expected extracted uuid 1820 derivedFrom string // expected extracted derived 1821 }{ 1822 { 1823 np: &slim_networkingv1.NetworkPolicy{}, 1824 name: "", 1825 namespace: slim_metav1.NamespaceDefault, 1826 uuid: "", 1827 derivedFrom: resourceTypeNetworkPolicy, 1828 }, 1829 { 1830 np: &slim_networkingv1.NetworkPolicy{ 1831 ObjectMeta: slim_metav1.ObjectMeta{ 1832 Annotations: map[string]string{ 1833 annotation.PolicyName: "foo", 1834 }, 1835 }, 1836 }, 1837 name: "foo", 1838 uuid: "", 1839 namespace: slim_metav1.NamespaceDefault, 1840 derivedFrom: resourceTypeNetworkPolicy, 1841 }, 1842 { 1843 np: &slim_networkingv1.NetworkPolicy{ 1844 ObjectMeta: slim_metav1.ObjectMeta{ 1845 Name: "foo", 1846 Namespace: "bar", 1847 UID: types.UID(uuid), 1848 }, 1849 }, 1850 name: "foo", 1851 namespace: "bar", 1852 uuid: uuid, 1853 derivedFrom: resourceTypeNetworkPolicy, 1854 }, 1855 } 1856 1857 assertLabel := func(lbl labels.Label, key, value string) { 1858 require.Equal(t, key, lbl.Key) 1859 require.Equal(t, value, lbl.Value) 1860 require.Equal(t, labels.LabelSourceK8s, lbl.Source) 1861 } 1862 1863 for _, tt := range tests { 1864 lbls := GetPolicyLabelsv1(tt.np) 1865 require.NotNil(t, lbls) 1866 require.Len(t, lbls, 4, "Incorrect number of labels: Expected DerivedFrom, Name, Namespace and UID labels.") 1867 assertLabel(lbls[0], "io.cilium.k8s.policy.derived-from", tt.derivedFrom) 1868 assertLabel(lbls[1], "io.cilium.k8s.policy.name", tt.name) 1869 assertLabel(lbls[2], "io.cilium.k8s.policy.namespace", tt.namespace) 1870 assertLabel(lbls[3], "io.cilium.k8s.policy.uid", tt.uuid) 1871 } 1872 } 1873 1874 func TestIPBlockToCIDRRule(t *testing.T) { 1875 blocks := []*slim_networkingv1.IPBlock{ 1876 {}, 1877 {CIDR: "192.168.1.1/24"}, 1878 {CIDR: "192.168.1.1/24", Except: []string{}}, 1879 {CIDR: "192.168.1.1/24", Except: []string{"192.168.1.1/28"}}, 1880 { 1881 CIDR: "192.168.1.1/24", 1882 Except: []string{ 1883 "192.168.1.1/30", 1884 "192.168.1.1/26", 1885 "192.168.1.1/28", 1886 }, 1887 }, 1888 } 1889 1890 for _, block := range blocks { 1891 cidrRule := ipBlockToCIDRRule(block) 1892 1893 exceptCIDRs := make([]api.CIDR, len(block.Except)) 1894 for i, v := range block.Except { 1895 exceptCIDRs[i] = api.CIDR(v) 1896 } 1897 1898 require.Equal(t, false, cidrRule.Generated) 1899 require.Equal(t, api.CIDR(block.CIDR), cidrRule.Cidr) 1900 1901 if block.Except == nil || len(block.Except) == 0 { 1902 require.Nil(t, cidrRule.ExceptCIDRs) 1903 } else { 1904 require.EqualValues(t, exceptCIDRs, cidrRule.ExceptCIDRs) 1905 } 1906 } 1907 }