Skip to content

Commit

Permalink
incusd/server/firewall: added support for reject ACL rules
Browse files Browse the repository at this point in the history
Signed-off-by: Mike Robski <[email protected]>
  • Loading branch information
mikerobski committed Jan 17, 2025
1 parent 66b1deb commit f265910
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 56 deletions.
139 changes: 90 additions & 49 deletions internal/server/firewall/drivers/drivers_nftables.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,11 @@ func (d Nftables) InstanceSetupBridgeFilter(projectName string, instanceName str

// Set the template fields for the ACL rules.
tplFields["aclInDropRules"] = nftRules.inDropRules
tplFields["aclInRejectRules"] = nftRules.inRejectRules
tplFields["aclInRejectRulesConverted"] = nftRules.inRejectRulesConverted
tplFields["aclInAcceptRules"] = append(nftRules.inAcceptRules4, nftRules.inAcceptRules6...)
tplFields["aclInDefaultRule"] = nftRules.defaultInRule
tplFields["aclInDefaultRuleConverted"] = nftRules.defaultInRuleConverted

tplFields["aclOutDropRules"] = nftRules.outDropRules
tplFields["aclOutAcceptRules"] = nftRules.outAcceptRules
Expand Down Expand Up @@ -596,25 +599,31 @@ func (d Nftables) InstanceClearProxyNAT(projectName string, instanceName string,

// nftRulesCollection contains the ACL rules translated to NFT rules and split in groups.
type nftRulesCollection struct {
inDropRules []string
inAcceptRules4 []string
inAcceptRules6 []string
outDropRules []string
outAcceptRules []string
defaultInRule string
defaultOutRule string
inDropRules []string
inRejectRules []string
inRejectRulesConverted []string
inAcceptRules4 []string
inAcceptRules6 []string
outDropRules []string
outAcceptRules []string
defaultInRule string
defaultInRuleConverted string
defaultOutRule string
}

// aclRulesToNftRules converts ACL rules applied to the device to NFT rules.
func (d Nftables) aclRulesToNftRules(hostName string, aclRules []ACLRule) (*nftRulesCollection, error) {
nftRules := nftRulesCollection{
inDropRules: make([]string, 0),
inAcceptRules4: make([]string, 0),
inAcceptRules6: make([]string, 0),
outDropRules: make([]string, 0),
outAcceptRules: make([]string, 0),
defaultInRule: "",
defaultOutRule: "",
inDropRules: make([]string, 0),
inRejectRules: make([]string, 0),
inRejectRulesConverted: make([]string, 0), // To be used in the forward chain where reject is not supported
inAcceptRules4: make([]string, 0),
inAcceptRules6: make([]string, 0),
outDropRules: make([]string, 0),
outAcceptRules: make([]string, 0),
defaultInRule: "",
defaultInRuleConverted: "", // To be used in the forward chain where reject is not supported
defaultOutRule: "",
}

hostNameQuoted := "\"" + hostName + "\""
Expand All @@ -623,16 +632,24 @@ func (d Nftables) aclRulesToNftRules(hostName string, aclRules []ACLRule) (*nftR
for i, rule := range aclRules {
if i >= rulesCount-2 {
// The last two rules are the default ACL rules and we should keep them separate.
if rule.Action == "reject" {
// Reject is not supported in bridge filter and is converted to drop.
rule.Action = "drop"
}

var partial bool
var err error
if rule.Direction == "egress" {
nftRules.defaultInRule, partial, err = d.aclRuleCriteriaToRules(hostNameQuoted, 4, &rule)

if err == nil && !partial && rule.Action == "reject" {
// Convert egress reject rules to drop rules to address nftables limitation.
rule.Action = "drop"
nftRules.defaultInRuleConverted, partial, err = d.aclRuleCriteriaToRules(hostNameQuoted, 4, &rule)
} else {
nftRules.defaultInRuleConverted = nftRules.defaultInRule
}
} else {
if rule.Action == "reject" {
// Always convert ingress reject rules to drop rules to address nftables limitation.
rule.Action = "drop"
}

nftRules.defaultOutRule, partial, err = d.aclRuleCriteriaToRules(hostNameQuoted, 4, &rule)
}

Expand All @@ -647,47 +664,24 @@ func (d Nftables) aclRulesToNftRules(hostName string, aclRules []ACLRule) (*nftR
continue
}

nft4Rule := ""
nft6Rule := ""
if rule.Direction == "ingress" && rule.Action == "reject" {
// Convert ingress reject rules to drop rules to address nftables limitation.
rule.Action = "drop"
}

// First try generating rules with IPv4 or IP agnostic criteria.
nft4Rule, partial, err := d.aclRuleCriteriaToRules(hostNameQuoted, 4, &rule)
nft4Rule, nft6Rule, newNftRules, err := d.aclRuleToNftRules(hostNameQuoted, rule)
if err != nil {
return nil, err
}

if partial {
// If we couldn't fully generate the ruleset with only IPv4 or IP agnostic criteria, then
// fill in the remaining parts using IPv6 criteria.
nft6Rule, _, err = d.aclRuleCriteriaToRules(hostNameQuoted, 6, &rule)
if err != nil {
return nil, err
}

if nft6Rule == "" {
return nil, fmt.Errorf("Invalid empty rule generated")
}
} else if nft4Rule == "" {
return nil, fmt.Errorf("Invalid empty rule generated")
}

newNftRules := []string{}
if nft4Rule != "" {
newNftRules = append(newNftRules, nft4Rule)
}

if nft6Rule != "" {
newNftRules = append(newNftRules, nft6Rule)
}

switch rule.Direction {
case "ingress":
switch {
case rule.Action == "drop":
nftRules.outDropRules = append(nftRules.outDropRules, newNftRules...)

case rule.Action == "reject":
return nil, fmt.Errorf("Invalid action %q for bridge filter", rule.Action)
nftRules.outDropRules = append(nftRules.outDropRules, newNftRules...)

case rule.Action == "allow":
nftRules.outAcceptRules = append(nftRules.outAcceptRules, newNftRules...)
Expand All @@ -702,7 +696,17 @@ func (d Nftables) aclRulesToNftRules(hostName string, aclRules []ACLRule) (*nftR
nftRules.inDropRules = append(nftRules.inDropRules, newNftRules...)

case rule.Action == "reject":
return nil, fmt.Errorf("Invalid action %q for bridge filter", rule.Action)
nftRules.inRejectRules = append(nftRules.inRejectRules, newNftRules...)

// Generate reject rule converted to a drop rule.
rule.Action = "drop"

_, _, newNftRules, err = d.aclRuleToNftRules(hostNameQuoted, rule)
if err != nil {
return nil, err
}

nftRules.inRejectRulesConverted = append(nftRules.inRejectRulesConverted, newNftRules...)

case rule.Action == "allow":
if nft4Rule != "" {
Expand All @@ -725,6 +729,43 @@ func (d Nftables) aclRulesToNftRules(hostName string, aclRules []ACLRule) (*nftR
return &nftRules, nil
}

func (d Nftables) aclRuleToNftRules(hostNameQuoted string, rule ACLRule) (string, string, []string, error) {
nft4Rule := ""
nft6Rule := ""

// First try generating rules with IPv4 or IP agnostic criteria.
nft4Rule, partial, err := d.aclRuleCriteriaToRules(hostNameQuoted, 4, &rule)
if err != nil {
return "", "", nil, err
}

if partial {
// If we couldn't fully generate the ruleset with only IPv4 or IP agnostic criteria, then
// fill in the remaining parts using IPv6 criteria.
nft6Rule, _, err = d.aclRuleCriteriaToRules(hostNameQuoted, 6, &rule)
if err != nil {
return "", "", nil, err
}

if nft6Rule == "" {
return "", "", nil, fmt.Errorf("Invalid empty rule generated")
}
} else if nft4Rule == "" {
return "", "", nil, fmt.Errorf("Invalid empty rule generated")
}

nftRules := []string{}
if nft4Rule != "" {
nftRules = append(nftRules, nft4Rule)
}

if nft6Rule != "" {
nftRules = append(nftRules, nft6Rule)
}

return nft4Rule, nft6Rule, nftRules, nil
}

// applyNftConfig loads the specified config template and then applies it to the common template before sending to
// the nft command to be atomically applied to the system.
func (d Nftables) applyNftConfig(tpl *template.Template, tplFields map[string]any) error {
Expand Down
20 changes: 13 additions & 7 deletions internal/server/firewall/drivers/drivers_nftables_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,22 @@ chain in{{.chainSeparator}}{{.deviceLabel}} {
{{if .ipv6FilterAll -}}
iifname "{{.hostName}}" ether type ip6 drop
{{- end}}
{{- if or .aclInDropRules .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRule -}}
{{- if or .aclInDropRules .aclInRejectRules .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRule -}}
ct state established,related accept
{{- end}}
{{- range .aclInDropRules}}
{{.}}
{{- end}}
{{- range .aclInRejectRules}}
{{.}}
{{- end}}
{{- range .aclInAcceptRules}}
{{.}}
{{- end}}
{{if .filterUnwantedFrames -}}
iifname "{{.hostName}}" ether type != {arp, ip, ip6} drop
{{- end}}
{{if or .aclInDropRules .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRule -}}
{{if or .aclInDropRules .aclInRejectRules .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRule -}}
iifname "{{.hostName}}" ether type arp accept
iifname "{{.hostName}}" ip6 nexthdr ipv6-icmp icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert } accept
{{- end}}
Expand Down Expand Up @@ -251,16 +254,19 @@ chain fwd{{.chainSeparator}}{{.deviceLabel}} {
{{if .ipv6FilterAll -}}
iifname "{{.hostName}}" ether type ip6 drop
{{- end}}
{{- if or .aclInDropRules .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRule .aclOutDefaultRule -}}
{{- if or .aclInDropRules .aclInRejectRulesConverted .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRuleConverted .aclOutDefaultRule -}}
ct state established,related accept
{{- end}}
{{- range .aclInDropRules}}
{{.}}
{{- end}}
{{- range .aclInRejectRulesConverted}}
{{.}}
{{- end}}
{{- range .aclOutDropRules}}
{{.}}
{{- end}}
{{- range .aclInAcceptRules}}
{{- range .aclInAcceptRules}}
{{.}}
{{- end}}
{{- range .aclOutAcceptRules}}
Expand All @@ -269,17 +275,17 @@ chain fwd{{.chainSeparator}}{{.deviceLabel}} {
{{if .filterUnwantedFrames -}}
iifname "{{.hostName}}" ether type != {arp, ip, ip6} drop
{{- end}}
{{if or .aclInDropRules .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRule .aclOutDefaultRule -}}
{{if or .aclInDropRules .aclInRejectRulesConverted .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRuleConverted .aclOutDefaultRule -}}
iifname "{{.hostName}}" ether type arp accept
iifname "{{.hostName}}" ip6 nexthdr ipv6-icmp icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert } accept
oifname "{{.hostName}}" ether type arp accept
oifname "{{.hostName}}" ip6 nexthdr ipv6-icmp icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert } accept
{{- end}}
{{.aclInDefaultRule}}
{{.aclInDefaultRuleConverted}}
{{.aclOutDefaultRule}}
}
{{if or .aclInDropRules .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRule .aclOutDefaultRule -}}
{{if or .aclInDropRules .aclInRejectRulesConverted .aclInAcceptRules .aclOutDropRules .aclOutAcceptRules .aclInDefaultRule .aclOutDefaultRule -}}
chain out{{.chainSeparator}}{{.deviceLabel}} {
type filter hook output priority filter; policy accept;
ct state established,related accept
Expand Down

0 comments on commit f265910

Please sign in to comment.