File: acl_firewall.go

package info (click to toggle)
incus 6.0.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 24,392 kB
  • sloc: sh: 16,313; ansic: 3,121; python: 457; makefile: 337; ruby: 51; sql: 50; lisp: 6
file content (146 lines) | stat: -rw-r--r-- 5,022 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package acl

import (
	"context"
	"fmt"

	"github.com/lxc/incus/v6/internal/server/db"
	dbCluster "github.com/lxc/incus/v6/internal/server/db/cluster"
	firewallDrivers "github.com/lxc/incus/v6/internal/server/firewall/drivers"
	"github.com/lxc/incus/v6/internal/server/state"
	"github.com/lxc/incus/v6/shared/api"
	"github.com/lxc/incus/v6/shared/logger"
	"github.com/lxc/incus/v6/shared/util"
)

// FirewallApplyACLRules applies ACL rules to network firewall.
func FirewallApplyACLRules(s *state.State, logger logger.Logger, aclProjectName string, aclNet NetworkACLUsage) error {
	rules, err := FirewallACLRules(s, aclNet.Name, aclProjectName, aclNet.Config)
	if err != nil {
		return err
	}

	return s.Firewall.NetworkApplyACLRules(aclNet.Name, rules)
}

// FirewallACLRules returns ACL rules for network firewall.
func FirewallACLRules(s *state.State, aclDeviceName string, aclProjectName string, config map[string]string) ([]firewallDrivers.ACLRule, error) {
	var dropRules []firewallDrivers.ACLRule
	var rejectRules []firewallDrivers.ACLRule
	var allowRules []firewallDrivers.ACLRule
	var allowStatelessRules []firewallDrivers.ACLRule

	// convertACLRules converts the ACL rules to Firewall ACL rules.
	convertACLRules := func(direction string, logPrefix string, rules ...api.NetworkACLRule) error {
		for ruleIndex, rule := range rules {
			if rule.State == "disabled" {
				continue
			}

			firewallACLRule := firewallDrivers.ACLRule{
				Direction:       direction,
				Action:          rule.Action,
				Source:          rule.Source,
				Destination:     rule.Destination,
				Protocol:        rule.Protocol,
				SourcePort:      rule.SourcePort,
				DestinationPort: rule.DestinationPort,
				ICMPType:        rule.ICMPType,
				ICMPCode:        rule.ICMPCode,
			}

			if rule.State == "logged" {
				firewallACLRule.Log = true
				// Max 29 chars.
				firewallACLRule.LogName = fmt.Sprintf("%s-%s-%d", logPrefix, direction, ruleIndex)
			}

			switch {
			case rule.Action == "drop":
				dropRules = append(dropRules, firewallACLRule)
			case rule.Action == "reject":
				rejectRules = append(rejectRules, firewallACLRule)
			case rule.Action == "allow":
				allowRules = append(allowRules, firewallACLRule)
			case rule.Action == "allow-stateless": // TODO: add NOTRACK support
				allowStatelessRules = append(allowStatelessRules, firewallACLRule)
			default:
				return fmt.Errorf("Unrecognised action %q", rule.Action)
			}
		}

		return nil
	}

	logPrefix := aclDeviceName

	// Load ACLs specified by network.
	for _, aclName := range util.SplitNTrimSpace(config["security.acls"], ",", -1, true) {
		var aclInfo *api.NetworkACL

		err := s.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
			var err error

			_, aclInfo, err = dbCluster.GetNetworkACLAPI(ctx, tx.Tx(), aclProjectName, aclName)

			return err
		})
		if err != nil {
			return nil, fmt.Errorf("Failed loading ACL %q for network %q: %w", aclName, aclDeviceName, err)
		}

		err = convertACLRules("ingress", logPrefix, aclInfo.Ingress...)
		if err != nil {
			return nil, fmt.Errorf("Failed converting ACL %q ingress rules for network %q: %w", aclInfo.Name, aclDeviceName, err)
		}

		err = convertACLRules("egress", logPrefix, aclInfo.Egress...)
		if err != nil {
			return nil, fmt.Errorf("Failed converting ACL %q egress rules for network %q: %w", aclInfo.Name, aclDeviceName, err)
		}
	}

	var rules []firewallDrivers.ACLRule
	rules = append(rules, dropRules...)
	rules = append(rules, rejectRules...)
	rules = append(rules, allowRules...)
	rules = append(rules, allowStatelessRules...)

	// Add the automatic default ACL rule for the network.
	egressAction, egressLogged := firewallACLDefaults(config, "egress")
	ingressAction, ingressLogged := firewallACLDefaults(config, "ingress")

	rules = append(rules, firewallDrivers.ACLRule{
		Direction: "egress",
		Action:    egressAction,
		Log:       egressLogged,
		LogName:   fmt.Sprintf("%s-egress", logPrefix),
	})

	rules = append(rules, firewallDrivers.ACLRule{
		Direction: "ingress",
		Action:    ingressAction,
		Log:       ingressLogged,
		LogName:   fmt.Sprintf("%s-ingress", logPrefix),
	})

	return rules, nil
}

// firewallACLDefaults returns the action and logging mode to use for the specified direction's default rule.
// If the security.acls.default.{in,e}gress.action or security.acls.default.{in,e}gress.logged settings are not
// specified in the network config, then it returns "reject" and false respectively.
func firewallACLDefaults(netConfig map[string]string, direction string) (string, bool) {
	defaults := map[string]string{
		fmt.Sprintf("security.acls.default.%s.action", direction): "reject",
		fmt.Sprintf("security.acls.default.%s.logged", direction): "false",
	}

	for k := range defaults {
		if netConfig[k] != "" {
			defaults[k] = netConfig[k]
		}
	}

	return defaults[fmt.Sprintf("security.acls.default.%s.action", direction)], util.IsTrue(defaults[fmt.Sprintf("security.acls.default.%s.logged", direction)])
}