// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 //go:build !consulent package agent import ( "context" "testing" "github.com/miekg/dns" "github.com/stretchr/testify/require" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/testrpc" ) func TestDNS_CE_PeeredServices(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } a := StartTestAgent(t, TestAgent{HCL: ``, Overrides: `peering = { test_allow_peer_registrations = true } `}) defer a.Shutdown() testrpc.WaitForTestAgent(t, a.RPC, "dc1") makeReq := func() *structs.RegisterRequest { return &structs.RegisterRequest{ PeerName: "peer1", Datacenter: "dc1", Node: "peernode1", Address: "198.18.1.1", Service: &structs.NodeService{ PeerName: "peer1", Kind: structs.ServiceKindConnectProxy, Service: "web-proxy", Address: "199.0.0.1", Port: 12345, Proxy: structs.ConnectProxyConfig{ DestinationServiceName: "peer-web", }, EnterpriseMeta: *acl.DefaultEnterpriseMeta(), }, EnterpriseMeta: *acl.DefaultEnterpriseMeta(), } } dnsQuery := func(t *testing.T, question string, typ uint16) *dns.Msg { m := new(dns.Msg) m.SetQuestion(question, typ) c := new(dns.Client) reply, _, err := c.Exchange(m, a.DNSAddr()) require.NoError(t, err) require.Len(t, reply.Answer, 1, "zero valid records found for %q", question) return reply } assertARec := func(t *testing.T, rec dns.RR, expectName, expectIP string) { aRec, ok := rec.(*dns.A) require.True(t, ok, "Extra is not an A record: %T", rec) require.Equal(t, expectName, aRec.Hdr.Name) require.Equal(t, expectIP, aRec.A.String()) } assertSRVRec := func(t *testing.T, rec dns.RR, expectName string, expectPort uint16) { srvRec, ok := rec.(*dns.SRV) require.True(t, ok, "Answer is not a SRV record: %T", rec) require.Equal(t, expectName, srvRec.Target) require.Equal(t, expectPort, srvRec.Port) } t.Run("srv-with-addr-reply", func(t *testing.T) { require.NoError(t, a.RPC(context.Background(), "Catalog.Register", makeReq(), &struct{}{})) q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) require.Len(t, q.Answer, 1) require.Len(t, q.Extra, 1) addr := "c7000001.addr.consul." assertSRVRec(t, q.Answer[0], addr, 12345) assertARec(t, q.Extra[0], addr, "199.0.0.1") // Query the addr to make sure it's also valid. q = dnsQuery(t, addr, dns.TypeA) require.Len(t, q.Answer, 1) require.Len(t, q.Extra, 0) assertARec(t, q.Answer[0], addr, "199.0.0.1") }) t.Run("srv-with-node-reply", func(t *testing.T) { req := makeReq() // Clear service address to trigger node response req.Service.Address = "" require.NoError(t, a.RPC(context.Background(), "Catalog.Register", req, &struct{}{})) q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) require.Len(t, q.Answer, 1) require.Len(t, q.Extra, 1) nodeName := "peernode1.node.peer1.peer.consul." assertSRVRec(t, q.Answer[0], nodeName, 12345) assertARec(t, q.Extra[0], nodeName, "198.18.1.1") // Query the node to make sure it's also valid. q = dnsQuery(t, nodeName, dns.TypeA) require.Len(t, q.Answer, 1) require.Len(t, q.Extra, 0) assertARec(t, q.Answer[0], nodeName, "198.18.1.1") }) t.Run("srv-with-fqdn-reply", func(t *testing.T) { req := makeReq() // Set non-ip address to trigger external response req.Address = "localhost" req.Service.Address = "" require.NoError(t, a.RPC(context.Background(), "Catalog.Register", req, &struct{}{})) q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) require.Len(t, q.Answer, 1) require.Len(t, q.Extra, 0) assertSRVRec(t, q.Answer[0], "localhost.", 12345) }) t.Run("a-reply", func(t *testing.T) { require.NoError(t, a.RPC(context.Background(), "Catalog.Register", makeReq(), &struct{}{})) q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeA) require.Len(t, q.Answer, 1) require.Len(t, q.Extra, 0) assertARec(t, q.Answer[0], "web-proxy.service.peer1.peer.consul.", "199.0.0.1") }) } func getTestCasesParseLocality() []testCaseParseLocality { testCases := []testCaseParseLocality{ { name: "test [..dc]", labels: []string{"test-dc", "dc"}, enterpriseDNSConfig: enterpriseDNSConfig{}, expectedResult: queryLocality{ EnterpriseMeta: acl.EnterpriseMeta{}, datacenter: "test-dc", }, expectedOK: true, }, { name: "test [..peer]", labels: []string{"test-peer", "peer"}, enterpriseDNSConfig: enterpriseDNSConfig{}, expectedResult: queryLocality{ EnterpriseMeta: acl.EnterpriseMeta{}, peer: "test-peer", }, expectedOK: true, }, { name: "test 1 label", labels: []string{"test-peer"}, enterpriseDNSConfig: enterpriseDNSConfig{}, expectedResult: queryLocality{ EnterpriseMeta: acl.EnterpriseMeta{}, peerOrDatacenter: "test-peer", }, expectedOK: true, }, { name: "test 0 labels", labels: []string{}, enterpriseDNSConfig: enterpriseDNSConfig{}, expectedResult: queryLocality{}, expectedOK: true, }, { name: "test 3 labels returns not found", labels: []string{"test-dc", "dc", "test-blah"}, enterpriseDNSConfig: enterpriseDNSConfig{}, expectedResult: queryLocality{}, expectedOK: false, }, } return testCases }