github.com/looshlee/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  }