From a249c46258211741372eff4e82b615961a760af7 Mon Sep 17 00:00:00 2001 From: mreiger Date: Tue, 20 Dec 2022 19:08:52 +0100 Subject: [PATCH] Escape dns queries from snat when dns proxy is in effect --- pkg/nftables/snat.go | 13 +++++- pkg/nftables/snat_test.go | 86 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/pkg/nftables/snat.go b/pkg/nftables/snat.go index 62a8112f..06ba3dd3 100644 --- a/pkg/nftables/snat.go +++ b/pkg/nftables/snat.go @@ -24,6 +24,7 @@ func snatRules(f *Firewall) (nftablesRules, error) { if f.primaryPrivateNet == nil { return nil, fmt.Errorf("no primary private network found") } + sourceNetworks := strings.Join(f.primaryPrivateNet.Prefixes, ", ") rules := nftablesRules{} for _, s := range f.firewall.Spec.EgressRules { @@ -73,11 +74,21 @@ func snatRules(f *Firewall) (nftablesRules, error) { snatRule := snatRule{ comment: fmt.Sprintf("snat for %s", s.NetworkID), - sourceNetworks: strings.Join(f.primaryPrivateNet.Prefixes, ", "), + sourceNetworks: sourceNetworks, oifname: fmt.Sprintf("vlan%d", *n.Vrf), to: to, } rules = append(rules, snatRule.String()) } + + enableDNS := len(f.clusterwideNetworkPolicies.GetFQDNs()) > 0 + if enableDNS { + escapeDNSRules := []string{ + fmt.Sprintf(`ip saddr { %s } tcp dport { 53 } accept comment "escape snat for dns proxy tcp"`, sourceNetworks), + fmt.Sprintf(`ip saddr { %s } udp dport { 53 } accept comment "escape snat for dns proxy udp"`, sourceNetworks), + } + return append(escapeDNSRules, uniqueSorted(rules)...), nil + } + return uniqueSorted(rules), nil } diff --git a/pkg/nftables/snat_test.go b/pkg/nftables/snat_test.go index e012a5b0..a9e4fa7c 100644 --- a/pkg/nftables/snat_test.go +++ b/pkg/nftables/snat_test.go @@ -7,11 +7,15 @@ import ( "github.com/go-logr/logr" "github.com/google/go-cmp/cmp" mn "github.com/metal-stack/metal-lib/pkg/net" + corev1 "k8s.io/api/core/v1" + networking "k8s.io/api/networking/v1" firewallv1 "github.com/metal-stack/firewall-controller/api/v1" ) func TestSnatRules(t *testing.T) { + tcp := corev1.ProtocolTCP + udp := corev1.ProtocolUDP private := "private" internet := "internet" mpls := "mpls" @@ -24,6 +28,7 @@ func TestSnatRules(t *testing.T) { tests := []struct { name string input firewallv1.FirewallSpec + cwnps firewallv1.ClusterwideNetworkPolicyList want nftablesRules wantErr bool err error @@ -65,11 +70,86 @@ func TestSnatRules(t *testing.T) { }, }, }, + cwnps: firewallv1.ClusterwideNetworkPolicyList{}, want: nftablesRules{ `ip saddr { 10.0.1.0/24 } oifname "vlan1" counter snat to jhash ip daddr . tcp sport mod 2 map { 0 : 185.0.0.2, 1 : 185.0.0.3 } comment "snat for internet"`, `ip saddr { 10.0.1.0/24 } oifname "vlan2" counter snat 100.0.0.2 comment "snat for mpls"`, }, }, + { + name: "escape DNS for dns-based CWNPs", + input: firewallv1.FirewallSpec{ + Data: firewallv1.Data{ + FirewallNetworks: []firewallv1.FirewallNetwork{ + { + Networkid: &private, + Prefixes: []string{"10.0.1.0/24"}, + Ips: []string{"10.0.1.1"}, + Networktype: &privatePrimary, + }, + { + Networkid: &internet, + Prefixes: []string{"185.0.0.0/24"}, + Ips: []string{"185.0.0.1"}, + Vrf: &vrf1, + Networktype: &external, + }, + { + Networkid: &mpls, + Prefixes: []string{"100.0.0.0/24"}, + Ips: []string{"100.0.0.1"}, + Vrf: &vrf2, + Networktype: &external, + }, + }, + EgressRules: []firewallv1.EgressRuleSNAT{ + { + NetworkID: "internet", + IPs: []string{"185.0.0.2", "185.0.0.3"}, + }, { + NetworkID: "mpls", + IPs: []string{"100.0.0.2"}, + }, + }, + }, + }, + cwnps: firewallv1.ClusterwideNetworkPolicyList{ + Items: []firewallv1.ClusterwideNetworkPolicy{ + { + Spec: firewallv1.PolicySpec{ + Egress: []firewallv1.EgressRule{ + { + ToFQDNs: []firewallv1.FQDNSelector{ + { + MatchName: "test.com", + }, + { + MatchPattern: "*.test.com", + }, + }, + Ports: []networking.NetworkPolicyPort{ + { + Protocol: &tcp, + Port: port(53), + }, + { + Protocol: &udp, + Port: port(53), + }, + }, + }, + }, + }, + }, + }, + }, + want: nftablesRules{ + `ip saddr { 10.0.1.0/24 } tcp dport { 53 } accept comment "escape snat for dns proxy tcp"`, + `ip saddr { 10.0.1.0/24 } udp dport { 53 } accept comment "escape snat for dns proxy udp"`, + `ip saddr { 10.0.1.0/24 } oifname "vlan1" counter snat to jhash ip daddr . tcp sport mod 2 map { 0 : 185.0.0.2, 1 : 185.0.0.3 } comment "snat for internet"`, + `ip saddr { 10.0.1.0/24 } oifname "vlan2" counter snat 100.0.0.2 comment "snat for mpls"`, + }, + }, { name: "empty snat rules", input: firewallv1.FirewallSpec{ @@ -86,7 +166,8 @@ func TestSnatRules(t *testing.T) { EgressRules: []firewallv1.EgressRuleSNAT{}, }, }, - want: nftablesRules{}, + cwnps: firewallv1.ClusterwideNetworkPolicyList{}, + want: nftablesRules{}, }, { name: "no primary network", @@ -102,6 +183,7 @@ func TestSnatRules(t *testing.T) { }, }, }, + cwnps: firewallv1.ClusterwideNetworkPolicyList{}, wantErr: true, err: errors.New("no primary private network found"), }, @@ -109,7 +191,7 @@ func TestSnatRules(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - f := NewFirewall(firewallv1.Firewall{Spec: tt.input}, &firewallv1.ClusterwideNetworkPolicyList{}, nil, nil, logr.Discard()) + f := NewFirewall(firewallv1.Firewall{Spec: tt.input}, &tt.cwnps, nil, nil, logr.Discard()) got, err := snatRules(f) if (err != nil) != tt.wantErr { t.Errorf("snatRules() error = %v, wantErr %v", err, tt.err)