Skip to content

Commit

Permalink
feat: implement ingress firewall rules
Browse files Browse the repository at this point in the history
Fixes siderolabs#4421

See documentation for details on how to use the feature.

With `talosctl cluster create`, firewall can be easily test with
`--with-firewall=accept|block` (default mode).

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
smira committed Nov 30, 2023
1 parent 0b111ec commit 0af04a0
Show file tree
Hide file tree
Showing 32 changed files with 1,858 additions and 50 deletions.
9 changes: 8 additions & 1 deletion .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,12 @@ local integration_cilium_strict = Step('e2e-cilium-strict', target='e2e-qemu', p
IMAGE_REGISTRY: local_registry,
});

local integration_network_chaos = Step('e2e-network-chaos', target='e2e-qemu', privileged=true, depends_on=[load_artifacts], environment={
local integration_firewall = Step('e2e-network-chaos', target='e2e-qemu', privileged=true, depends_on=[load_artifacts], environment={
SHORT_INTEGRATION_TEST: 'yes',
WITH_FIREWALL: 'block',
REGISTRY: local_registry,
});
local integration_network_chaos = Step('e2e-network-chaos', target='e2e-qemu', privileged=true, depends_on=[integration_firewall], environment={
SHORT_INTEGRATION_TEST: 'yes',
WITH_NETWORK_CHAOS: 'true',
REGISTRY: local_registry,
Expand Down Expand Up @@ -605,6 +610,7 @@ local integration_pipelines = [
Pipeline('integration-provision-1', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_1]) + integration_trigger(['integration-provision', 'integration-provision-1']),
Pipeline('integration-provision-2', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_2]) + integration_trigger(['integration-provision', 'integration-provision-2']),
Pipeline('integration-misc', default_pipeline_steps + [
integration_firewall,
integration_network_chaos,
integration_canal_reset,
integration_bios_cgroupsv1,
Expand All @@ -629,6 +635,7 @@ local integration_pipelines = [
Pipeline('cron-integration-provision-1', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_1], [default_cron_pipeline]) + cron_trigger(['thrice-daily', 'nightly']),
Pipeline('cron-integration-provision-2', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_2], [default_cron_pipeline]) + cron_trigger(['thrice-daily', 'nightly']),
Pipeline('cron-integration-misc', default_pipeline_steps + [
integration_firewall,
integration_network_chaos,
integration_canal_reset,
integration_bios_cgroupsv1,
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ COPY --from=generate-build /api/inspect/*.pb.go /pkg/machinery/api/inspect/
COPY --from=go-generate /src/pkg/flannel/ /pkg/flannel/
COPY --from=go-generate /src/pkg/imager/profile/ /pkg/imager/profile/
COPY --from=go-generate /src/pkg/machinery/resources/ /pkg/machinery/resources/
COPY --from=go-generate /src/pkg/machinery/config/types/v1alpha1/ /pkg/machinery/config/types/v1alpha1/
COPY --from=go-generate /src/pkg/machinery/config/types/ /pkg/machinery/config/types/
COPY --from=go-generate /src/pkg/machinery/nethelpers/ /pkg/machinery/nethelpers/
COPY --from=go-generate /src/pkg/machinery/extensions/ /pkg/machinery/extensions/
COPY --from=embed-abbrev / /
Expand Down
25 changes: 25 additions & 0 deletions cmd/talosctl/cmd/mgmt/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/spf13/pflag"
"k8s.io/client-go/tools/clientcmd"

"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/internal/firewallpatch"
"github.com/siderolabs/talos/cmd/talosctl/pkg/mgmt/helpers"
"github.com/siderolabs/talos/pkg/cli"
"github.com/siderolabs/talos/pkg/cluster/check"
Expand Down Expand Up @@ -84,6 +85,7 @@ const (
kubePrismFlag = "kubeprism-port"
tpm2EnabledFlag = "with-tpm2"
diskEncryptionKeyTypesFlag = "disk-encryption-key-types"
firewallFlag = "with-firewall"
)

var (
Expand Down Expand Up @@ -161,6 +163,7 @@ var (
packetCorrupt float64
bandwidth int
diskEncryptionKeyTypes []string
withFirewall string
)

// createCmd represents the cluster up command.
Expand Down Expand Up @@ -595,6 +598,26 @@ func create(ctx context.Context, flags *pflag.FlagSet) (err error) {
return err
}

if withFirewall != "" {
var defaultAction nethelpers.DefaultAction

defaultAction, err = nethelpers.DefaultActionString(withFirewall)
if err != nil {
return err
}

var controlplaneIPs []netip.Addr

for i := range ips {
controlplaneIPs = append(controlplaneIPs, ips[i][:controlplanes]...)
}

configBundleOpts = append(configBundleOpts,
bundle.WithPatchControlPlane([]configpatcher.Patch{firewallpatch.ControlPlane(defaultAction, cidrs, gatewayIPs, controlplaneIPs)}),
bundle.WithPatchWorker([]configpatcher.Patch{firewallpatch.Worker(defaultAction, cidrs, gatewayIPs)}),
)
}

configBundle, err := bundle.NewBundle(configBundleOpts...)
if err != nil {
return err
Expand Down Expand Up @@ -1010,6 +1033,7 @@ func init() {
createCmd.Flags().Float64Var(&packetCorrupt, "with-network-packet-corrupt", 0.0,
"specify percent of corrupt packets on the bridge interface when creating a qemu cluster. e.g. 50% = 0.50 (default: 0.0)")
createCmd.Flags().IntVar(&bandwidth, "with-network-bandwidth", 0, "specify bandwidth restriction (in kbps) on the bridge interface when creating a qemu cluster")
createCmd.Flags().StringVar(&withFirewall, firewallFlag, "", "inject firewall rules into the cluster, value is default policy - accept/block (QEMU only)")

Cmd.AddCommand(createCmd)
}
Expand Down Expand Up @@ -1040,6 +1064,7 @@ func checkForDefinedGenFlag(flags *pflag.FlagSet) string {
forceEndpointFlag,
controlPlanePortFlag,
kubePrismFlag,
firewallFlag,
}
for _, genFlag := range genOptionFlags {
if flags.Lookup(genFlag).Changed {
Expand Down
191 changes: 191 additions & 0 deletions cmd/talosctl/cmd/mgmt/cluster/internal/firewallpatch/firewallpatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package firewallpatch provides a set of default config patches to enable firewall.
package firewallpatch

import (
"net/netip"

"github.com/siderolabs/gen/xslices"

"github.com/siderolabs/talos/pkg/machinery/config/configpatcher"
"github.com/siderolabs/talos/pkg/machinery/config/container"
"github.com/siderolabs/talos/pkg/machinery/config/types/network"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
)

func ingressRuleWithinCluster(cidrs []netip.Prefix, gateways []netip.Addr) []network.IngressRule {
rules := make([]network.IngressRule, 0, len(cidrs))

for i := range cidrs {
rules = append(rules,
network.IngressRule{
Subnet: cidrs[i],
Except: netip.PrefixFrom(gateways[i], gateways[i].BitLen()),
},
)
}

return rules
}

func ingressRuleWideOpen() []network.IngressRule {
return []network.IngressRule{
{
Subnet: netip.MustParsePrefix("0.0.0.0/0"),
},
{
Subnet: netip.MustParsePrefix("::/0"),
},
}
}

func ingressOnly(ips []netip.Addr) []network.IngressRule {
return xslices.Map(ips, func(ip netip.Addr) network.IngressRule {
return network.IngressRule{
Subnet: netip.PrefixFrom(ip, ip.BitLen()),
}
})
}

// ControlPlane generates a default firewall for a controlplane node.
//
// Kubelet and Trustd are only available within the cluster.
// Apid & Kubernetes API is wide open.
// Etcd is only available within the controlplanes.
func ControlPlane(defaultAction nethelpers.DefaultAction, cidrs []netip.Prefix, gateways []netip.Addr, controlplanes []netip.Addr) configpatcher.Patch {
def := network.NewDefaultActionConfigV1Alpha1()
def.Ingress = defaultAction

kubeletRule := network.NewRuleConfigV1Alpha1()
kubeletRule.MetaName = "kubelet-ingress"
kubeletRule.PortSelector.Ports = []network.PortRange{
{
Lo: constants.KubeletPort,
Hi: constants.KubeletPort,
},
}
kubeletRule.PortSelector.Protocol = nethelpers.ProtocolTCP
kubeletRule.Ingress = ingressRuleWithinCluster(cidrs, gateways)

apidRule := network.NewRuleConfigV1Alpha1()
apidRule.MetaName = "apid-ingress"
apidRule.PortSelector.Ports = []network.PortRange{
{
Lo: constants.ApidPort,
Hi: constants.ApidPort,
},
}
apidRule.PortSelector.Protocol = nethelpers.ProtocolTCP
apidRule.Ingress = ingressRuleWideOpen()

trustdRule := network.NewRuleConfigV1Alpha1()
trustdRule.MetaName = "trustd-ingress"
trustdRule.PortSelector.Ports = []network.PortRange{
{
Lo: constants.TrustdPort,
Hi: constants.TrustdPort,
},
}
trustdRule.PortSelector.Protocol = nethelpers.ProtocolTCP
trustdRule.Ingress = ingressRuleWithinCluster(cidrs, gateways)

kubeAPIRule := network.NewRuleConfigV1Alpha1()
kubeAPIRule.MetaName = "kubernetes-api-ingress"
kubeAPIRule.PortSelector.Ports = []network.PortRange{
{
Lo: constants.DefaultControlPlanePort,
Hi: constants.DefaultControlPlanePort,
},
}
kubeAPIRule.PortSelector.Protocol = nethelpers.ProtocolTCP
kubeAPIRule.Ingress = ingressRuleWideOpen()

etcdRule := network.NewRuleConfigV1Alpha1()
etcdRule.MetaName = "etcd-ingress"
etcdRule.PortSelector.Ports = []network.PortRange{
{
Lo: constants.EtcdClientPort,
Hi: constants.EtcdPeerPort,
},
}
etcdRule.PortSelector.Protocol = nethelpers.ProtocolTCP
etcdRule.Ingress = ingressOnly(controlplanes)

vxlanRule := network.NewRuleConfigV1Alpha1()
vxlanRule.MetaName = "cni-vxlan"
vxlanRule.PortSelector.Ports = []network.PortRange{
{
Lo: 4789, // Flannel, Calico VXLAN
Hi: 4789,
},
{
Lo: 8472, // Cilium VXLAN
Hi: 8472,
},
}
vxlanRule.PortSelector.Protocol = nethelpers.ProtocolUDP
vxlanRule.Ingress = ingressRuleWithinCluster(cidrs, gateways)

provider, err := container.New(def, kubeletRule, apidRule, trustdRule, kubeAPIRule, etcdRule, vxlanRule)
if err != nil { // should not fail
panic(err)
}

return configpatcher.StrategicMergePatch{Provider: provider}
}

// Worker generates a default firewall for a worker node.
//
// Kubelet & apid are only available within the cluster.
func Worker(defaultAction nethelpers.DefaultAction, cidrs []netip.Prefix, gateways []netip.Addr) configpatcher.Patch {
def := network.NewDefaultActionConfigV1Alpha1()
def.Ingress = defaultAction

kubeletRule := network.NewRuleConfigV1Alpha1()
kubeletRule.MetaName = "kubelet-ingress"
kubeletRule.PortSelector.Ports = []network.PortRange{
{
Lo: constants.KubeletPort,
Hi: constants.KubeletPort,
},
}
kubeletRule.PortSelector.Protocol = nethelpers.ProtocolTCP
kubeletRule.Ingress = ingressRuleWithinCluster(cidrs, gateways)

apidRule := network.NewRuleConfigV1Alpha1()
apidRule.MetaName = "apid-ingress"
apidRule.PortSelector.Ports = []network.PortRange{
{
Lo: constants.ApidPort,
Hi: constants.ApidPort,
},
}
apidRule.PortSelector.Protocol = nethelpers.ProtocolTCP
apidRule.Ingress = ingressRuleWithinCluster(cidrs, gateways)

vxlanRule := network.NewRuleConfigV1Alpha1()
vxlanRule.MetaName = "cni-vxlan"
vxlanRule.PortSelector.Ports = []network.PortRange{
{
Lo: 4789, // Flannel, Calico VXLAN
Hi: 4789,
},
{
Lo: 8472, // Cilium VXLAN
Hi: 8472,
},
}
vxlanRule.PortSelector.Protocol = nethelpers.ProtocolUDP
vxlanRule.Ingress = ingressRuleWithinCluster(cidrs, gateways)

provider, err := container.New(def, kubeletRule, apidRule, vxlanRule)
if err != nil { // should not fail
panic(err)
}

return configpatcher.StrategicMergePatch{Provider: provider}
}
6 changes: 6 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ machine:
title = "OAuth2 Machine Config Flow"
description = """\
Talos Linux when running on the `metal` platform can be configured to authenticate the machine configuration download using [OAuth2 device flow](https://www.talos.dev/v1.6/advanced/machine-config-oauth/).
"""

[note.ingress]
title = "Ingress Firewall"
description = """\
Talos Linux now supports configuring the [ingress firewall rules](https://talos.dev/v1.6/talos-guides/network/ingress-firewall/).
"""

[make_deps]
Expand Down
8 changes: 8 additions & 0 deletions hack/test/e2e-qemu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ case "${WITH_NETWORK_CHAOS:-false}" in
;;
esac

case "${WITH_FIREWALL:-false}" in
false)
;;
*)
QEMU_FLAGS+=("--with-firewall=${WITH_FIREWALL}")
;;
esac

case "${USE_DISK_IMAGE:-false}" in
false)
;;
Expand Down
Loading

0 comments on commit 0af04a0

Please sign in to comment.