github.phpd.cn/cilium/cilium@v1.6.12/test/runtime/fqdn.go (about) 1 // Copyright 2018-2019 Authors of Cilium 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 RuntimeTest 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "path/filepath" 22 "sort" 23 "strings" 24 25 "github.com/cilium/cilium/api/v1/models" 26 . "github.com/cilium/cilium/test/ginkgo-ext" 27 "github.com/cilium/cilium/test/helpers" 28 "github.com/cilium/cilium/test/helpers/constants" 29 30 . "github.com/onsi/gomega" 31 ) 32 33 var bindCiliumTestTemplate = ` 34 $TTL 3 35 $ORIGIN cilium.test. 36 37 @ IN SOA cilium.test. admin.cilium.test. ( 38 200608081 ; serial, todays date + todays serial # 39 8H ; refresh, seconds 40 2H ; retry, seconds 41 4W ; expire, seconds 42 1D ) ; minimum, seconds 43 ; 44 ; 45 @ IN NS server 46 server.cilium.test. IN A 127.0.0.1 47 48 world1.cilium.test. IN A %[1]s 49 world2.cilium.test. IN A %[2]s 50 world3.cilium.test. IN A %[3]s 51 52 roundrobin.cilium.test. 1 IN A %[1]s 53 roundrobin.cilium.test. 1 IN A %[2]s 54 roundrobin.cilium.test. 1 IN A %[3]s 55 56 level1CNAME.cilium.test. 1 IN CNAME world1 57 level2CNAME.cilium.test. 1 IN CNAME level1CNAME.cilium.test. 58 level3CNAME.cilium.test. 1 IN CNAME level2CNAME.cilium.test. 59 60 61 world1CNAME.cilium.test. 1 IN CNAME world1 62 world2CNAME.cilium.test. 1 IN CNAME world2 63 world3CNAME.cilium.test. 1 IN CNAME world3 64 ` 65 66 var bindOutsideTestTemplate = ` 67 $TTL 3 68 $ORIGIN outside.test. 69 70 @ IN SOA outside.test. admin.outside.test. ( 71 200608081 ; serial, todays date + todays serial # 72 8H ; refresh, seconds 73 2H ; retry, seconds 74 4W ; expire, seconds 75 1D ) ; minimum, seconds 76 ; 77 ; 78 @ IN NS server 79 server.outside.test. IN A 127.0.0.1 80 81 world1.outside.test. IN A %[1]s 82 world2.outside.test. IN A %[2]s 83 world3.outside.test. IN A %[3]s 84 ` 85 86 var bindDNSSECTestTemplate = ` 87 $TTL 3 88 $ORIGIN dnssec.test. 89 90 @ IN SOA dnssec.test. admin.dnssec.test. ( 91 200608081 ; serial, todays date + todays serial # 92 8H ; refresh, seconds 93 2H ; retry, seconds 94 4W ; expire, seconds 95 1D ) ; minimum, seconds 96 ; 97 ; 98 @ IN NS server 99 server.dnssec.test. IN A 127.0.0.1 100 101 world1.dnssec.test. IN A %[1]s 102 world2.dnssec.test. IN A %[2]s 103 world3.dnssec.test. IN A %[3]s 104 ` 105 106 var bind9ZoneConfig = ` 107 zone "cilium.test" { 108 type master; 109 file "/etc/bind/db.cilium.test"; 110 }; 111 112 zone "outside.test" { 113 type master; 114 file "/etc/bind/db.outside.test"; 115 }; 116 117 zone "dnssec.test" { 118 type master; 119 file "/etc/bind/db.dnssec.test"; 120 auto-dnssec maintain; 121 inline-signing yes; 122 key-directory "/etc/bind/keys"; 123 }; 124 ` 125 126 var _ = Describe("RuntimeFQDNPolicies", func() { 127 const ( 128 bindContainerName = "bind" 129 worldNetwork = "world" 130 WorldHttpd1 = "WorldHttpd1" 131 WorldHttpd2 = "WorldHttpd2" 132 WorldHttpd3 = "WorldHttpd3" 133 OutsideHttpd1 = "OutsideHttpd1" 134 OutsideHttpd2 = "OutsideHttpd2" 135 OutsideHttpd3 = "OutsideHttpd3" 136 137 bindDBCilium = "db.cilium.test" 138 bindDBOutside = "db.outside.test" 139 bindDBDNSSSEC = "db.dnssec.test" 140 bindNamedConf = "named.conf.local" 141 bindNamedOptions = "named.conf.options" 142 143 world1Domain = "world1.cilium.test" 144 world1Target = "http://world1.cilium.test" 145 world2Target = "http://world2.cilium.test" 146 147 DNSSECDomain = "dnssec.test" 148 DNSSECWorld1Target = "world1.dnssec.test" 149 DNSSECWorld2Target = "world2.dnssec.test" 150 DNSSECContainerName = "dnssec" 151 ) 152 153 var ( 154 vm *helpers.SSHMeta 155 monitorStop = func() error { return nil } 156 157 ciliumTestImages = map[string]string{ 158 WorldHttpd1: constants.HttpdImage, 159 WorldHttpd2: constants.HttpdImage, 160 WorldHttpd3: constants.HttpdImage, 161 } 162 163 ciliumOutsideImages = map[string]string{ 164 OutsideHttpd1: constants.HttpdImage, 165 OutsideHttpd2: constants.HttpdImage, 166 OutsideHttpd3: constants.HttpdImage, 167 } 168 169 worldIps = map[string]string{} 170 outsideIps = map[string]string{} 171 generatedFiles = []string{bindDBCilium, bindNamedConf, bindDBOutside, bindDBDNSSSEC} 172 DNSServerIP = "" 173 ) 174 175 BeforeAll(func() { 176 vm = helpers.InitRuntimeHelper(helpers.Runtime, logger) 177 ExpectCiliumReady(vm) 178 179 By("Create sample containers in %q docker network", worldNetwork) 180 vm.Exec(fmt.Sprintf("docker network create %s", worldNetwork)).ExpectSuccess( 181 "%q network cant be created", worldNetwork) 182 183 for name, image := range ciliumTestImages { 184 vm.ContainerCreate(name, image, worldNetwork, fmt.Sprintf("-l id.%s", name)) 185 res := vm.ContainerInspect(name) 186 res.ExpectSuccess("Container is not ready after create it") 187 ip, err := res.Filter(fmt.Sprintf(`{[0].NetworkSettings.Networks.%s.IPAddress}`, worldNetwork)) 188 Expect(err).To(BeNil(), "Cannot retrieve network info for %q", name) 189 worldIps[name] = ip.String() 190 } 191 192 bindConfig := fmt.Sprintf(bindCiliumTestTemplate, getMapValues(worldIps)...) 193 err := helpers.RenderTemplateToFile(bindDBCilium, bindConfig, os.ModePerm) 194 Expect(err).To(BeNil(), "bind file can't be created") 195 196 // // Installed DNSSEC domain 197 bindConfig = fmt.Sprintf(bindDNSSECTestTemplate, getMapValues(worldIps)...) 198 err = helpers.RenderTemplateToFile(bindDBDNSSSEC, bindConfig, os.ModePerm) 199 Expect(err).To(BeNil(), "bind file can't be created") 200 201 for name, image := range ciliumOutsideImages { 202 vm.ContainerCreate(name, image, worldNetwork, fmt.Sprintf("-l id.%s", name)) 203 res := vm.ContainerInspect(name) 204 res.ExpectSuccess("Container is not ready after create it") 205 ip, err := res.Filter(fmt.Sprintf(`{[0].NetworkSettings.Networks.%s.IPAddress}`, worldNetwork)) 206 Expect(err).To(BeNil(), "Cannot retrieve network info for %q", name) 207 outsideIps[name] = ip.String() 208 } 209 bindConfig = fmt.Sprintf(bindOutsideTestTemplate, getMapValues(outsideIps)...) 210 err = helpers.RenderTemplateToFile(bindDBOutside, bindConfig, os.ModePerm) 211 Expect(err).To(BeNil(), "bind file can't be created") 212 213 err = helpers.RenderTemplateToFile(bindNamedConf, bind9ZoneConfig, os.ModePerm) 214 Expect(err).To(BeNil(), "Bind named.conf local file can't be created") 215 216 vm.ExecWithSudo("mkdir -m777 -p /data") 217 for _, file := range generatedFiles { 218 vm.Exec(fmt.Sprintf("mv %s /data/%s", 219 filepath.Join(helpers.BasePath, file), file)).ExpectSuccess( 220 "Cannot copy %q to bind container", file) 221 } 222 223 By("Setting up bind container") 224 // Use a bridge network (The docker default) to be able to use this 225 // server from cilium agent. This should change when DNS proxy is in 226 // place. 227 res := vm.ContainerCreate( 228 bindContainerName, 229 constants.BindContainerImage, 230 "bridge", 231 fmt.Sprintf("-p 53:53/udp -p 53:53/tcp -v /data:/data -l id.bind -e DNSSEC_DOMAIN=%s", DNSSECDomain)) 232 res.ExpectSuccess("Cannot start bind container") 233 234 res = vm.ContainerInspect(bindContainerName) 235 res.ExpectSuccess("Container is not ready after create it") 236 ip, err := res.Filter(`{[0].NetworkSettings.Networks.bridge.IPAddress}`) 237 DNSServerIP = ip.String() 238 Expect(err).To(BeNil(), "Cannot retrieve network info for %q", bindContainerName) 239 240 vm.SampleContainersActions( 241 helpers.Create, 242 helpers.CiliumDockerNetwork, 243 fmt.Sprintf("--dns=%s -l app=test", DNSServerIP)) 244 245 areEndpointsReady := vm.WaitEndpointsReady() 246 Expect(areEndpointsReady).Should(BeTrue(), "Endpoints are not ready after timeout") 247 By("Update resolv.conf on host to update the poller") 248 249 // This should be disabled when DNS proxy is in place. 250 vm.ExecWithSudo(`bash -c "echo -e \"nameserver 127.0.0.1\nnameserver 1.1.1.1\" > /etc/resolv.conf"`) 251 252 // Need to restart cilium to use the latest resolv.conf info. 253 vm.ExecWithSudo("systemctl restart cilium") 254 255 areEndpointsReady = vm.WaitEndpointsReady() 256 Expect(areEndpointsReady).Should(BeTrue(), "Endpoints are not ready after timeout") 257 258 }) 259 260 AfterAll(func() { 261 // @TODO remove this one when DNS proxy is in place. 262 vm.ExecWithSudo(`bash -c 'echo -e "nameserver 8.8.8.8\nnameserver 1.1.1.1" > /etc/resolv.conf'`) 263 for name := range ciliumTestImages { 264 vm.ContainerRm(name) 265 } 266 267 for name := range ciliumOutsideImages { 268 vm.ContainerRm(name) 269 } 270 vm.SampleContainersActions(helpers.Delete, "") 271 vm.ContainerRm(bindContainerName) 272 vm.Exec(fmt.Sprintf("docker network rm %s", worldNetwork)) 273 vm.CloseSSHClient() 274 }) 275 276 JustBeforeEach(func() { 277 monitorStop = vm.MonitorStart() 278 }) 279 280 JustAfterEach(func() { 281 vm.ValidateNoErrorsInLogs(CurrentGinkgoTestDescription().Duration) 282 ExpectDockerContainersMatchCiliumEndpoints(vm) 283 monitorStop() 284 }) 285 286 AfterEach(func() { 287 vm.PolicyDelAll() 288 }) 289 290 AfterFailed(func() { 291 GinkgoPrint(vm.Exec( 292 `docker ps -q | xargs -n 1 docker inspect --format ` + 293 `'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} {{ .Name }}'` + 294 `| sed 's/ \// /'`).Output().String()) 295 vm.ReportFailed("cilium policy get") 296 }) 297 298 expectFQDNSareApplied := func(domain string, minNumIDs int) { 299 escapedDomain := strings.Replace(domain, `.`, `\\.`, -1) 300 jqfilter := fmt.Sprintf(`jq -c '.[] | select(.identities|length >= %d) | select(.users|length > 0) | .selector | match("^MatchName: (\\w+\\.%s|), MatchPattern: ([\\w*]+\\.%s|)$") | length > 0'`, minNumIDs, escapedDomain, escapedDomain) 301 body := func() bool { 302 res := vm.Exec(fmt.Sprintf(`cilium policy selectors -o json | %s`, jqfilter)) 303 return strings.HasPrefix(res.GetStdOut(), "true") 304 } 305 err := helpers.WithTimeout( 306 body, 307 "ToFQDNs did not update any Selectors", 308 &helpers.TimeoutConfig{Timeout: helpers.HelperTimeout}) 309 Expect(err).To(BeNil(), "FQDN policy didn't correctly update the policy selectors") 310 } 311 312 fqdnPolicyImport := func(fqdnPolicy string) { 313 _, err := vm.PolicyRenderAndImport(fqdnPolicy) 314 ExpectWithOffset(1, err).To(BeNil(), "Unable to import policy: %s", err) 315 } 316 317 It("Enforces ToFQDNs policy", func() { 318 By("Importing policy with ToFQDN rules") 319 // notaname.cilium.io never returns IPs, and is there to test that the 320 // other name does get populated. 321 fqdnPolicy := ` 322 [ 323 { 324 "labels": [{ 325 "key": "toFQDNs-runtime-test-policy" 326 }], 327 "endpointSelector": { 328 "matchLabels": { 329 "container:id.app1": "" 330 } 331 }, 332 "egress": [ 333 { 334 "toPorts": [{ 335 "ports":[{"port": "53", "protocol": "ANY"}] 336 }] 337 }, 338 { 339 "toFQDNs": [ 340 { 341 "matchName": "world1.cilium.test" 342 } 343 ] 344 } 345 ] 346 } 347 ]` 348 fqdnPolicyImport(fqdnPolicy) 349 expectFQDNSareApplied("cilium.test", 1) 350 351 By("Denying egress to IPs of DNS names not in ToFQDNs, and normal IPs") 352 // www.cilium.io has a different IP than cilium.io (it is CNAMEd as well!), 353 // and so should be blocked. 354 // cilium.io.cilium.io doesn't exist. 355 // 1.1.1.1, amusingly, serves HTTP. 356 for _, blockedTarget := range []string{"world2.cilium.test"} { 357 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(blockedTarget)) 358 res.ExpectFail("Curl succeeded against blocked DNS name %s" + blockedTarget) 359 } 360 361 By("Allowing egress to IPs of specified ToFQDN DNS names") 362 res := vm.ContainerExec(helpers.App1, helpers.CurlWithHTTPCode(world1Target)) 363 res.ExpectSuccess("Cannot access to allowed DNS name %q", world1Target) 364 }) 365 366 It("Validate dns-proxy monitor information", func() { 367 368 ctx, cancel := context.WithCancel(context.Background()) 369 monitorCMD := vm.ExecInBackground(ctx, "cilium monitor --type=l7") 370 defer cancel() 371 372 policy := ` 373 [ 374 { 375 "labels": [{ 376 "key": "monitor" 377 }], 378 "endpointSelector": { 379 "matchLabels": { 380 "container:id.app1": "" 381 } 382 }, 383 "egress": [ 384 { 385 "toPorts": [{ 386 "ports":[{"port": "53", "protocol": "ANY"}], 387 "rules": { 388 "dns": [ 389 {"matchPattern": "world1.cilium.test"} 390 ] 391 } 392 }] 393 }, 394 { 395 "toFQDNs": [{ 396 "matchPattern": "world1.cilium.test" 397 }] 398 } 399 ] 400 } 401 ]` 402 _, err := vm.PolicyRenderAndImport(policy) 403 Expect(err).To(BeNil(), "Policy cannot be imported") 404 405 expectFQDNSareApplied("cilium.test", 1) 406 407 allowVerdict := "verdict Forwarded DNS Query: world1.cilium.test" 408 deniedVerdict := "verdict Denied DNS Query: world2.cilium.test" 409 410 By("Testing connectivity to Cilium.test domain") 411 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(world1Target)) 412 res.ExpectSuccess("Cannot access to %q", world1Target) 413 414 _ = monitorCMD.WaitUntilMatch(allowVerdict) 415 monitorCMD.ExpectContains(allowVerdict) 416 monitorCMD.Reset() 417 418 By("Ensure connectivity to world2 is block") 419 res = vm.ContainerExec(helpers.App1, helpers.CurlFail(world2Target)) 420 res.ExpectFail("Can access to %q when it should block", world1Target) 421 monitorCMD.WaitUntilMatch(deniedVerdict) 422 monitorCMD.ExpectContains(deniedVerdict) 423 }) 424 425 It("Interaction with other ToCIDR rules", func() { 426 policy := ` 427 [ 428 { 429 "labels": [{ 430 "key": "FQDN test - interaction with other toCIDRSet rules, no poller" 431 }], 432 "endpointSelector": { 433 "matchLabels": { 434 "container:id.app1": "" 435 } 436 }, 437 "egress": [ 438 { 439 "toPorts": [{ 440 "ports":[{"port": "53", "protocol": "ANY"}], 441 "rules": { 442 "dns": [ 443 {"matchPattern": "*.cilium.test"} 444 ] 445 } 446 }] 447 }, 448 { 449 "toFQDNs": [{ 450 "matchPattern": "*.cilium.test" 451 }] 452 }, 453 { 454 "toCIDRSet": [ 455 {"cidr": "%s/32"} 456 ] 457 } 458 ] 459 } 460 ]` 461 _, err := vm.PolicyRenderAndImport(fmt.Sprintf(policy, outsideIps[OutsideHttpd1])) 462 Expect(err).To(BeNil(), "Policy cannot be imported") 463 464 expectFQDNSareApplied("cilium.test", 1) 465 466 By("Testing connectivity to Cilium.test domain") 467 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(world1Target)) 468 res.ExpectSuccess("Cannot access toCIDRSet allowed IP of DNS name %q", world1Target) 469 470 By("Testing connectivity to existing CIDR rule") 471 res = vm.ContainerExec(helpers.App1, helpers.CurlFail(outsideIps[OutsideHttpd1])) 472 res.ExpectSuccess("Cannot access to CIDR rule when should work") 473 474 By("Ensure connectivity to other domains is still block") 475 res = vm.ContainerExec(helpers.App1, helpers.CurlFail("http://world2.outside.test")) 476 res.ExpectFail("Connectivity to outside domain successfull when it should be block") 477 478 }) 479 480 It("Roundrobin DNS", func() { 481 numberOfTries := 5 482 target := "roundrobin.cilium.test" 483 policy := ` 484 [ 485 { 486 "labels": [{ 487 "key": "FQDN test - interaction with other toCIDRSet rules, no poller" 488 }], 489 "endpointSelector": { 490 "matchLabels": { 491 "container:app": "test" 492 } 493 }, 494 "egress": [ 495 { 496 "toPorts": [{ 497 "ports":[{"port": "53", "protocol": "ANY"}], 498 "rules": { 499 "dns": [ 500 {"matchPattern": "roundrobin.cilium.test"} 501 ] 502 } 503 }] 504 }, 505 { 506 "toFQDNs": [{ 507 "matchName": "roundrobin.cilium.test" 508 }] 509 } 510 ] 511 } 512 ]` 513 _, err := vm.PolicyRenderAndImport(policy) 514 Expect(err).To(BeNil(), "Policy cannot be imported") 515 516 endpoints, err := vm.GetEndpointsIds() 517 Expect(err).To(BeNil(), "Endpoints can't be retrieved") 518 519 for _, container := range []string{helpers.App1, helpers.App2} { 520 Expect(endpoints).To(HaveKey(container), 521 "Container %q is not present in the endpoints list", container) 522 ep := vm.EndpointGet(endpoints[container]) 523 Expect(ep).ShouldNot(BeNil(), 524 "Endpoint for container %q cannot be retrieved", container) 525 Expect(ep.Status.Policy.Realized.PolicyEnabled).To( 526 Equal(models.EndpointPolicyEnabledEgress), 527 "Endpoint %q does not have policy applied", container) 528 } 529 530 By("Testing %q and %q containers are allow to work with roundrobin dns", helpers.App1, helpers.App2) 531 for i := 0; i < numberOfTries; i++ { 532 for _, container := range []string{helpers.App1, helpers.App2} { 533 By("Testing connectivity to Cilium.test domain") 534 res := vm.ContainerExec(container, helpers.CurlFail(target)) 535 res.ExpectSuccess("Container %q cannot access to %q when should work", container, target) 536 } 537 } 538 }) 539 540 It("Can update L7 DNS policy rules", func() { 541 By("Importing policy with L7 DNS rules") 542 fqdnPolicy := ` 543 [ 544 { 545 "labels": [{ 546 "key": "toFQDNs-runtime-test-policy" 547 }], 548 "endpointSelector": { 549 "matchLabels": { 550 "container:id.app1": "" 551 } 552 }, 553 "egress": [ 554 { 555 "toPorts": [{ 556 "ports":[{"port": "53", "protocol": "ANY"}], 557 "rules": { 558 "dns": [{"matchPattern": "world1.cilium.test"}] 559 } 560 }] 561 }, 562 { 563 "toFQDNs": [ 564 { 565 "matchPattern": "*.cilium.test" 566 } 567 ] 568 } 569 ] 570 } 571 ]` 572 _, err := vm.PolicyRenderAndImport(fqdnPolicy) 573 Expect(err).To(BeNil(), "Policy cannot be imported") 574 expectFQDNSareApplied("cilium.test", 1) 575 576 By("Allowing egress to IPs of only the specified DNS names") 577 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(world2Target)) 578 res.ExpectFail("Curl succeeded against blocked DNS name %q", world2Target) 579 580 res = vm.ContainerExec(helpers.App1, helpers.CurlWithHTTPCode(world1Target)) 581 res.ExpectSuccess("Cannot access %q", world1Target) 582 583 By("Updating policy with L7 DNS rules") 584 fqdnPolicy = ` 585 [ 586 { 587 "labels": [{ 588 "key": "toFQDNs-runtime-test-policy" 589 }], 590 "endpointSelector": { 591 "matchLabels": { 592 "container:id.app1": "" 593 } 594 }, 595 "egress": [ 596 { 597 "toPorts": [{ 598 "ports":[{"port": "53", "protocol": "ANY"}], 599 "rules": { 600 "dns": [{"matchPattern": "world2.cilium.test"}] 601 } 602 }] 603 }, 604 { 605 "toFQDNs": [ 606 { 607 "matchPattern": "*.cilium.test" 608 } 609 ] 610 } 611 ] 612 } 613 ]` 614 _, err = vm.PolicyRenderAndImport(fqdnPolicy) 615 Expect(err).To(BeNil(), "Policy cannot be imported") 616 expectFQDNSareApplied("cilium.test", 1) 617 618 By("Allowing egress to IPs of the new DNS name") 619 res = vm.ContainerExec(helpers.App1, helpers.CurlWithHTTPCode(world2Target)) 620 res.ExpectSuccess("Cannot access %q", world2Target) 621 }) 622 623 It("CNAME follow", func() { 624 625 By("Testing one level of CNAME") 626 policy := ` 627 [ 628 { 629 "labels": [{ 630 "key": "CNAME follow one level" 631 }], 632 "endpointSelector": { 633 "matchLabels": { 634 "container:id.app1": "" 635 } 636 }, 637 "egress": [ 638 { 639 "toPorts": [{ 640 "ports":[{"port": "53", "protocol": "ANY"}], 641 "rules": { 642 "dns": [ 643 {"matchPattern": "*.cilium.test"} 644 ] 645 } 646 }] 647 }, 648 { 649 "toFQDNs": [{ 650 "matchName": "level1CNAME.cilium.test" 651 }] 652 } 653 ] 654 } 655 ]` 656 657 _, err := vm.PolicyRenderAndImport(policy) 658 Expect(err).To(BeNil(), "Policy cannot be imported") 659 660 expectFQDNSareApplied("cilium.test", 1) 661 target := "http://level1CNAME.cilium.test" 662 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(target)) 663 res.ExpectSuccess("Container %q cannot access to %q when should work", helpers.App1, target) 664 665 By("Testing three level CNAME to same target still works") 666 target = "http://level3CNAME.cilium.test" 667 res = vm.ContainerExec(helpers.App1, helpers.CurlFail(target)) 668 res.ExpectSuccess("Container %q cannot access to %q when should work", helpers.App1, target) 669 670 By("Testing other CNAME in same domain should fail") 671 target = "http://world2CNAME.cilium.test" 672 res = vm.ContainerExec(helpers.App1, helpers.CurlFail(target)) 673 res.ExpectFail("Container %q can access to %q when shouldn't work", helpers.App1, target) 674 675 By("Testing three level of CNAME") 676 policy = ` 677 [ 678 { 679 "labels": [{ 680 "key": "CNAME follow three levels" 681 }], 682 "endpointSelector": { 683 "matchLabels": { 684 "container:id.app2": "" 685 } 686 }, 687 "egress": [ 688 { 689 "toPorts": [{ 690 "ports":[{"port": "53", "protocol": "ANY"}], 691 "rules": { 692 "dns": [ 693 {"matchPattern": "*.cilium.test"} 694 ] 695 } 696 }] 697 }, 698 { 699 "toFQDNs": [{ 700 "matchName": "level3CNAME.cilium.test" 701 }] 702 } 703 ] 704 } 705 ]` 706 707 _, err = vm.PolicyRenderAndImport(policy) 708 Expect(err).To(BeNil(), "Policy cannot be imported") 709 710 expectFQDNSareApplied("cilium.test", 1) 711 target = "http://level3CNAME.cilium.test" 712 res = vm.ContainerExec(helpers.App2, helpers.CurlFail(target)) 713 res.ExpectSuccess("Container %q cannot access to %q when should work", helpers.App2, target) 714 }) 715 716 It("Enforces L3 policy even when no IPs are inserted", func() { 717 By("Importing policy with toFQDNs rules") 718 fqdnPolicy := ` 719 [ 720 { 721 "labels": [{ 722 "key": "toFQDNs-runtime-test-policy" 723 }], 724 "endpointSelector": { 725 "matchLabels": { 726 "container:id.app1": "" 727 } 728 }, 729 "egress": [ 730 { 731 "toFQDNs": [ 732 { 733 "matchPattern": "notadomain.cilium.io" 734 } 735 ] 736 } 737 ] 738 } 739 ]` 740 _, err := vm.PolicyRenderAndImport(fqdnPolicy) 741 Expect(err).To(BeNil(), "Policy cannot be imported") 742 expectFQDNSareApplied("cilium.io", 0) 743 744 By("Denying egress to any IPs or domains") 745 for _, blockedTarget := range []string{"1.1.1.1", "cilium.io", "google.com"} { 746 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(blockedTarget)) 747 res.ExpectFail("Curl to %s succeeded when in deny-all due to toFQDNs" + blockedTarget) 748 } 749 }) 750 751 It(`Implements matchPattern: "*"`, func() { 752 By(`Importing policy with matchPattern: "*" rule`) 753 fqdnPolicy := ` 754 [ 755 { 756 "labels": [{ 757 "key": "toFQDNs-runtime-test-policy" 758 }], 759 "endpointSelector": { 760 "matchLabels": { 761 "container:id.app1": "" 762 } 763 }, 764 "egress": [ 765 { 766 "toPorts": [{ 767 "ports":[{"port": "53", "protocol": "ANY"}], 768 "rules": { 769 "dns": [ 770 {"matchPattern": "*"} 771 ] 772 } 773 }] 774 }, 775 { 776 "toFQDNs": [ 777 {"matchPattern": "world1.cilium.test"}, 778 {"matchPattern": "world*.cilium.test"}, 779 {"matchPattern": "level*CNAME.cilium.test"} 780 ] 781 } 782 ] 783 } 784 ]` 785 _, err := vm.PolicyRenderAndImport(fqdnPolicy) 786 Expect(err).To(BeNil(), "Policy cannot be imported") 787 expectFQDNSareApplied("cilium.test", 1) 788 789 By("Denying egress to any IPs or domains") 790 for _, allowedTarget := range []string{"world1.cilium.test", "world2.cilium.test", "world3.cilium.test", "level1CNAME.cilium.test", "level2CNAME.cilium.test"} { 791 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(allowedTarget)) 792 res.ExpectSuccess("Curl to %s failed when in deny-all due to toFQDNs", allowedTarget) 793 } 794 for _, blockedTarget := range []string{"1.1.1.1", "cilium.io", "google.com"} { 795 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(blockedTarget)) 796 res.ExpectFail("Curl to %s succeeded when in allow-all DNS but limited toFQDNs", blockedTarget) 797 } 798 }) 799 800 It("Validates DNSSEC responses", func() { 801 policy := ` 802 [ 803 { 804 "labels": [{ 805 "key": "FQDN test - DNSSEC domain" 806 }], 807 "endpointSelector": { 808 "matchLabels": { 809 "container:id.dnssec": "" 810 } 811 }, 812 "egress": [ 813 { 814 "toPorts": [{ 815 "ports":[{"port": "53", "protocol": "ANY"}], 816 "rules": { 817 "dns": [ 818 {"matchPattern": "world1.dnssec.test"} 819 ] 820 } 821 }] 822 }, 823 { 824 "toFQDNs": [{ 825 "matchPattern": "world1.dnssec.test" 826 }] 827 } 828 ] 829 } 830 ]` 831 _, err := vm.PolicyRenderAndImport(policy) 832 Expect(err).To(BeNil(), "Policy cannot be imported") 833 834 // Selector cache is populated when a policy is applied on an endpoint. 835 // DNSSEC container is not running yet, so we can't expect the FQDNs to be applied yet. 836 // expectFQDNSareApplied("dnssec.test", 1) 837 838 By("Validate that allow target is working correctly") 839 res := vm.ContainerRun( 840 DNSSECContainerName, 841 constants.DNSSECContainerImage, 842 helpers.CiliumDockerNetwork, 843 fmt.Sprintf("-l id.%s --dns=%s --rm", DNSSECContainerName, DNSServerIP), 844 DNSSECWorld1Target) 845 res.ExpectSuccess("Cannot connect to %q when it should work", DNSSECContainerName) 846 847 By("Validate that disallow target is working correctly") 848 res = vm.ContainerRun( 849 DNSSECContainerName, 850 constants.DNSSECContainerImage, 851 helpers.CiliumDockerNetwork, 852 fmt.Sprintf("-l id.%s --dns=%s --rm", DNSSECContainerName, DNSServerIP), 853 DNSSECWorld2Target) 854 res.ExpectFail("Can connect to %q when it should not work", DNSSECContainerName) 855 }) 856 857 Context("toFQDNs populates toCIDRSet when poller is disabled (data from proxy)", func() { 858 var config = ` 859 PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin 860 CILIUM_OPTS=--kvstore consul --kvstore-opt consul.address=127.0.0.1:8500 --debug --pprof=true --log-system-load --tofqdns-enable-poller=false 861 INITSYSTEM=SYSTEMD` 862 BeforeAll(func() { 863 vm.SetUpCiliumWithOptions(config) 864 865 ExpectCiliumReady(vm) 866 areEndpointsReady := vm.WaitEndpointsReady() 867 Expect(areEndpointsReady).Should(BeTrue(), "Endpoints are not ready after timeout") 868 }) 869 870 BeforeEach(func() { 871 By("Clearing fqdn cache: %s", vm.Exec("cilium fqdn cache clean -f").CombineOutput().String()) 872 }) 873 874 AfterAll(func() { 875 vm.SetUpCilium() 876 _ = vm.WaitEndpointsReady() // Don't assert because don't want to block all AfterAll. 877 }) 878 879 It("Policy addition after DNS lookup", func() { 880 ctx, cancel := context.WithCancel(context.Background()) 881 monitorCMD := vm.ExecInBackground(ctx, "cilium monitor") 882 defer cancel() 883 884 policy := ` 885 [ 886 { 887 "labels": [{ 888 "key": "Policy addition after DNS lookup" 889 }], 890 "endpointSelector": { 891 "matchLabels": { 892 "container:id.app1": "" 893 } 894 }, 895 "egress": [ 896 { 897 "toPorts": [{ 898 "ports":[{"port": "53", "protocol": "ANY"}], 899 "rules": { 900 "dns": [ 901 {"matchName": "world1.cilium.test"}, 902 {"matchPattern": "*.cilium.test"} 903 ] 904 } 905 }] 906 }, 907 { 908 "toFQDNs": [ 909 {"matchName": "world1.cilium.test"}, 910 {"matchPattern": "*.cilium.test"} 911 ] 912 } 913 ] 914 } 915 ]` 916 917 By("Testing connectivity to %q", world1Target) 918 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(world1Target)) 919 res.ExpectSuccess("Cannot access %q", world1Target) 920 921 By("Importing the policy") 922 _, err := vm.PolicyRenderAndImport(policy) 923 Expect(err).To(BeNil(), "Policy cannot be imported") 924 925 By("Trying curl connection to %q without DNS request", world1Target) 926 // The --resolve below suppresses further lookups 927 curlCmd := helpers.CurlFail(fmt.Sprintf("%s:80/ --resolve %s:80:%s", world1Target, world1Domain, worldIps[WorldHttpd1])) 928 monitorCMD.Reset() 929 res = vm.ContainerExec(helpers.App1, curlCmd) 930 res.ExpectFail("Can access to %q when should not (No DNS request to allow the IP)", world1Target) 931 monitorCMD.ExpectContains("xx drop (Policy denied (L3))") 932 933 By("Testing connectivity to %q", world1Target) 934 res = vm.ContainerExec(helpers.App1, helpers.CurlFail(world1Target)) 935 res.ExpectSuccess("Cannot access to %q when it should work", world1Target) 936 }) 937 938 It("L3-dependent L7/HTTP with toFQDN updates proxy policy", func() { 939 ctx, cancel := context.WithCancel(context.Background()) 940 monitorCMD := vm.ExecInBackground(ctx, "cilium monitor") 941 defer cancel() 942 943 policy := ` 944 [ 945 { 946 "labels": [{ 947 "key": "L3-dependent L7 with toFQDN" 948 }], 949 "endpointSelector": { 950 "matchLabels": { 951 "container:id.app1": "" 952 } 953 }, 954 "egress": [ 955 { 956 "toPorts": [{ 957 "ports":[{"port": "53", "protocol": "ANY"}], 958 "rules": { 959 "dns": [ 960 {"matchName": "world1.cilium.test"}, 961 {"matchPattern": "*.cilium.test"} 962 ] 963 } 964 }] 965 }, 966 { 967 "toPorts": [{ 968 "ports":[{"port": "80", "protocol": "TCP"}], 969 "rules": { 970 "http": [ 971 {"method": "GET"} 972 ] 973 } 974 }], 975 "toFQDNs": [ 976 {"matchName": "world1.cilium.test"}, 977 {"matchPattern": "*.cilium.test"} 978 ] 979 } 980 ] 981 } 982 ]` 983 By("Testing connectivity to %q", world1Target) 984 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(world1Target)) 985 res.ExpectSuccess("Cannot access %q", world1Target) 986 987 By("Importing the policy") 988 _, err := vm.PolicyRenderAndImport(policy) 989 Expect(err).To(BeNil(), "Policy cannot be imported") 990 991 By("Trying curl connection to %q without DNS request", world1Target) 992 // The --resolve below suppresses further lookups 993 curlCmd := helpers.CurlFail(fmt.Sprintf("%s:80/ --resolve %s:80:%s", world1Target, world1Domain, worldIps[WorldHttpd1])) 994 monitorCMD.Reset() 995 res = vm.ContainerExec(helpers.App1, curlCmd) 996 res.ExpectFail("Can access to %q when should not (No DNS request to allow the IP)", world1Target) 997 monitorCMD.ExpectContains("xx drop (Policy denied (L3))") 998 999 By("Testing connectivity to %q", world1Target) 1000 monitorCMD.Reset() 1001 res = vm.ContainerExec(helpers.App1, helpers.CurlFail(world1Target)) 1002 res.ExpectSuccess("Cannot access to %q when it should work", world1Target) 1003 monitorCMD.ExpectContains("verdict Forwarded GET http://world1.cilium.test/ => 200") 1004 }) 1005 }) 1006 1007 It("DNS proxy policy works if Cilium stops", func() { 1008 targetURL := "http://world1.cilium.test" 1009 targetIP := worldIps[WorldHttpd1] 1010 invalidURL := "http://world1.outside.test" 1011 invalidIP := outsideIps[OutsideHttpd1] 1012 1013 policy := ` 1014 [ 1015 { 1016 "labels": [{ 1017 "key": "dns-proxy" 1018 }], 1019 "endpointSelector": { 1020 "matchLabels": { 1021 "container:id.app1": "" 1022 } 1023 }, 1024 "egress": [ 1025 { 1026 "toPorts": [{ 1027 "ports":[{"port": "53", "protocol": "ANY"}], 1028 "rules": { 1029 "dns": [ 1030 {"matchPattern": "*.cilium.test"} 1031 ] 1032 } 1033 }] 1034 }, 1035 { 1036 "toFQDNs": [{ 1037 "matchName": "world1.cilium.test" 1038 }] 1039 } 1040 ] 1041 } 1042 ]` 1043 _, err := vm.PolicyRenderAndImport(policy) 1044 Expect(err).To(BeNil(), "Policy cannot be imported") 1045 1046 expectFQDNSareApplied("cilium.test", 1) 1047 1048 By("Curl from %q to %q", helpers.App1, targetURL) 1049 res := vm.ContainerExec(helpers.App1, helpers.CurlFail(targetURL)) 1050 res.ExpectSuccess("Cannot connect from app1") 1051 1052 By("Curl from %q to %q should fail", helpers.App1, invalidURL) 1053 res = vm.ContainerExec(helpers.App1, helpers.CurlFail(invalidURL)) 1054 res.ExpectFail("Can connect from app1 when it should not work") 1055 1056 By("Stopping Cilium") 1057 1058 defer func() { 1059 // Defer a Cilium restart to make sure that keep started when test finished. 1060 _ = vm.ExecWithSudo("systemctl start cilium") 1061 vm.WaitEndpointsReady() 1062 }() 1063 1064 res = vm.ExecWithSudo("systemctl stop cilium") 1065 res.ExpectSuccess("Failed trying to stop cilium via systemctl") 1066 ExpectCiliumNotRunning(vm) 1067 1068 By("Testing connectivity from %q to the IP %q without DNS request", helpers.App1, targetIP) 1069 res = vm.ContainerExec(helpers.App1, helpers.CurlFail("http://%s", targetIP)) 1070 res.ExpectSuccess("Cannot connect to %q", targetIP) 1071 1072 By("Curl from %q to %q with Cilium down", helpers.App1, targetURL) 1073 // When Cilium is down the DNS-proxy is also down. The Endpoint has a 1074 // redirect to use the DNS-proxy, so new DNS request are redirected 1075 // incorrectly. 1076 // Future Cilium versions will fix this behaviour 1077 res = vm.ContainerExec(helpers.App1, helpers.CurlFail(targetURL)) 1078 res.ExpectFail("This request should fail because no dns-proxy when cilium is stopped") 1079 1080 By("Testing that invalid traffic is still block when Cilium is down", helpers.App1, invalidIP) 1081 res = vm.ContainerExec(helpers.App1, helpers.CurlFail("http://%s", invalidIP)) 1082 res.ExpectFail("Can connect from app1 when it should not work") 1083 1084 By("Starting Cilium again") 1085 Expect(vm.RestartCilium()).To(BeNil(), "Cilium cannot be started correctly") 1086 1087 // Policies on docker are not persistant, so the restart connectivity is not tested at all 1088 }) 1089 }) 1090 1091 // getMapValues retuns an array of interfaces with the map values. 1092 // returned array will be sorted by map keys, the reason is that Golang does 1093 // not support ordered maps and for DNS-config the values need to be always 1094 // sorted. 1095 func getMapValues(m map[string]string) []interface{} { 1096 1097 values := make([]interface{}, len(m)) 1098 var keys []string 1099 for k := range m { 1100 keys = append(keys, k) 1101 } 1102 sort.Strings(keys) 1103 for i, k := range keys { 1104 values[i] = m[k] 1105 } 1106 return values 1107 }