Skip to content

Commit

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

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
smira committed Nov 28, 2023
1 parent f041b26 commit 75c8ca6
Show file tree
Hide file tree
Showing 34 changed files with 1,368 additions and 192 deletions.
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
2 changes: 2 additions & 0 deletions api/resource/definitions/enums/enums.proto
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,10 @@ enum NethelpersPrimaryReselect {
// NethelpersProtocol is a inet protocol.
enum NethelpersProtocol {
NETHELPERS_PROTOCOL_UNSPECIFIED = 0;
PROTOCOL_ICMP = 1;
PROTOCOL_TCP = 6;
PROTOCOL_UDP = 17;
PROTOCOL_ICM_PV6 = 58;
}

// NethelpersRouteFlag wraps RTM_F_* constants.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (set NfTablesSet) SetElements() []nftables.SetElement {

for _, p := range set.Ports {
from := binaryutil.BigEndian.PutUint16(p[0])
to := binaryutil.BigEndian.PutUint16(p[1])
to := binaryutil.BigEndian.PutUint16(p[1] + 1)

elements = append(elements,
nftables.SetElement{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,11 @@ func (ctrl *NfTablesChainController) Run(ctx context.Context, r controller.Runti
return fmt.Errorf("error adding nftables set for chain %s: %w", nfChain.Name, err)
}

lookup.SetID = setID
lookup.SetName = setName
lookupOp := *lookup
lookupOp.SetID = setID
lookupOp.SetName = setName

compiledRule[i] = &lookupOp
}
}

Expand Down
187 changes: 187 additions & 0 deletions internal/app/machined/pkg/controllers/network/nftables_chain_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// 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 network

import (
"cmp"
"context"
"fmt"
"slices"

"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/gen/optional"
"github.com/siderolabs/gen/xslices"
"github.com/siderolabs/go-pointer"
"go.uber.org/zap"

v1alpha1runtime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
)

// NfTablesChainConfigController generates configuration for kmsg log delivery.
type NfTablesChainConfigController struct {
V1Alpha1Mode v1alpha1runtime.Mode
}

// Name implements controller.Controller interface.
func (ctrl *NfTablesChainConfigController) Name() string {
return "network.NfTablesChainConfigController"
}

// Inputs implements controller.Controller interface.
func (ctrl *NfTablesChainConfigController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: config.NamespaceName,
Type: config.MachineConfigType,
ID: optional.Some(config.V1Alpha1ID),
Kind: controller.InputWeak,
},
}
}

// Outputs implements controller.Controller interface.
func (ctrl *NfTablesChainConfigController) Outputs() []controller.Output {
return []controller.Output{
{
Type: network.NfTablesChainType,
Kind: controller.OutputShared,
},
}
}

// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *NfTablesChainConfigController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) (err error) {
for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
}

cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.V1Alpha1ID)
if err != nil && !state.IsNotFoundError(err) {
return fmt.Errorf("error getting machine config: %w", err)
}

r.StartTrackingOutputs()

if cfg != nil {
if err = safe.WriterModify(ctx, r, network.NewNfTablesChain(network.NamespaceName, "ingress"),
func(chain *network.NfTablesChain) error {
spec := chain.TypedSpec()

spec.Type = nethelpers.ChainTypeFilter
spec.Hook = nethelpers.ChainHookInput
spec.Priority = nethelpers.ChainPriorityFilter

// preamble
spec.Rules = []network.NfTablesRule{
// trusted interfaces: loopback, siderolink and kubespan
{
MatchIIfName: &network.NfTablesIfNameMatch{
InterfaceName: "lo",
Operator: nethelpers.OperatorEqual,
},
Verdict: pointer.To(nethelpers.VerdictAccept),
},
{
MatchIIfName: &network.NfTablesIfNameMatch{
InterfaceName: constants.SideroLinkName,
Operator: nethelpers.OperatorEqual,
},
Verdict: pointer.To(nethelpers.VerdictAccept),
},
{
MatchIIfName: &network.NfTablesIfNameMatch{
InterfaceName: constants.KubeSpanLinkName,
Operator: nethelpers.OperatorEqual,
},
Verdict: pointer.To(nethelpers.VerdictAccept),
},
}

defaultAction := cfg.Config().NetworkRules().DefaultAction()

if defaultAction == nethelpers.DefaultActionBlock {
// allow ICMP and ICMPv6 explicitly
spec.Rules = append(spec.Rules,
network.NfTablesRule{
MatchLayer4: &network.NfTablesLayer4Match{
Protocol: nethelpers.ProtocolICMP,
},
Verdict: pointer.To(nethelpers.VerdictAccept),
},
network.NfTablesRule{
MatchLayer4: &network.NfTablesLayer4Match{
Protocol: nethelpers.ProtocolICMPv6,
},
Verdict: pointer.To(nethelpers.VerdictAccept),
},
)
}

for _, rule := range cfg.Config().NetworkRules().Rules() {
portRanges := rule.PortRanges()

// sort port ranges, machine config validation ensures that there are no overlaps
slices.SortFunc(portRanges, func(a, b [2]uint16) int {
return cmp.Compare(a[0], b[0])
})

// if default accept, drop anything that doesn't match the rule
verdict := nethelpers.VerdictDrop

if defaultAction == nethelpers.DefaultActionBlock {
verdict = nethelpers.VerdictAccept
}

spec.Rules = append(spec.Rules,
network.NfTablesRule{
MatchSourceAddress: &network.NfTablesAddressMatch{
IncludeSubnets: rule.Subnets(),
ExcludeSubnets: rule.ExceptSubnets(),
Invert: defaultAction == nethelpers.DefaultActionAccept,
},
MatchLayer4: &network.NfTablesLayer4Match{
Protocol: rule.Protocol(),
MatchDestinationPort: &network.NfTablesPortMatch{
Ranges: xslices.Map(portRanges, func(pr [2]uint16) network.PortRange {
return network.PortRange{Lo: pr[0], Hi: pr[1]}
}),
},
},
Verdict: pointer.To(verdict),
},
)
}

if defaultAction == nethelpers.DefaultActionBlock {
// drop everything else
spec.Rules = append(spec.Rules,
network.NfTablesRule{
Verdict: pointer.To(nethelpers.VerdictDrop),
},
)
}

return nil
}); err != nil {
return err
}
}

if err = safe.CleanupOutputs[*network.NfTablesChain](ctx, r); err != nil {
return err
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,51 @@ func (s *NfTablesChainSuite) TestL4Match() {
s.checkNftOutput(`table inet talos-test {
chain test-tcp {
type filter hook input priority filter; policy accept;
ip daddr { 10.0.0.0/8 } tcp dport { 1023-1024, 1027-1028 } drop
ip6 daddr { 2001::/16 } tcp dport { 1023-1024, 1027-1028 } drop
ip daddr { 10.0.0.0/8 } tcp dport { 1023-1025, 1027-1029 } drop
ip6 daddr { 2001::/16 } tcp dport { 1023-1025, 1027-1029 } drop
}
}`)
}

func (s *NfTablesChainSuite) TestL4Match2() {
chain := network.NewNfTablesChain(network.NamespaceName, "test-tcp")
chain.TypedSpec().Type = nethelpers.ChainTypeFilter
chain.TypedSpec().Hook = nethelpers.ChainHookInput
chain.TypedSpec().Priority = nethelpers.ChainPriorityFilter
chain.TypedSpec().Rules = []network.NfTablesRule{
{
MatchSourceAddress: &network.NfTablesAddressMatch{
IncludeSubnets: []netip.Prefix{
netip.MustParsePrefix("10.0.0.0/8"),
},
Invert: true,
},
MatchLayer4: &network.NfTablesLayer4Match{
Protocol: nethelpers.ProtocolTCP,
MatchDestinationPort: &network.NfTablesPortMatch{
Ranges: []network.PortRange{
{
Lo: 1023,
Hi: 1023,
},
{
Lo: 1024,
Hi: 1024,
},
},
},
},
Verdict: pointer.To(nethelpers.VerdictDrop),
},
}

s.Require().NoError(s.State().Create(s.Ctx(), chain))

s.checkNftOutput(`table inet talos-test {
chain test-tcp {
type filter hook input priority filter; policy accept;
ip saddr != { 10.0.0.0/8 } tcp dport { 1023, 1024 } drop
meta nfproto ipv6 tcp dport { 1023, 1024 } drop
}
}`)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
&network.LinkMergeController{},
&network.LinkSpecController{},
&network.LinkStatusController{},
&network.NfTablesChainConfigController{},
&network.NfTablesChainController{},
&network.NodeAddressController{},
&network.OperatorConfigController{
Expand Down
Loading

0 comments on commit 75c8ca6

Please sign in to comment.