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)])
}
|