Skip to content

Commit

Permalink
Add EnableLogging and LogLabel supports for Node NetworkPolicy
Browse files Browse the repository at this point in the history
Signed-off-by: Hongliang Liu <lhongliang@vmware.com>
  • Loading branch information
hongliangl committed Sep 2, 2024
1 parent a996421 commit a829204
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 101 deletions.
43 changes: 42 additions & 1 deletion docs/antrea-node-network-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Usage](#usage)
- [Logs](#logs)
- [Limitations](#limitations)
<!-- /toc -->

Expand Down Expand Up @@ -66,11 +67,15 @@ spec:
ports:
- protocol: TCP
port: 80
enableLogging: true
logLabel: allow-http
- name: drop-other
action: Drop
ports:
- protocol: TCP
port: 80
enableLogging: true
logLabel: default-drop-others
```
An example Node NetworkPolicy that blocks egress traffic from Nodes with label
Expand Down Expand Up @@ -105,6 +110,42 @@ spec:
port: 22
```

## Logs

The `enableLogging` and `logLabel` options provide limited support for Node NetworkPolicies. Since Node NetworkPolicies
are implemented using iptables, enabling `enableLogging` causes the Linux kernel to log information about all matching
packets via the kernel log. However, Antrea cannot process these logs directly. Instead, these logs can be accessed
through syslog, allowing you to filter and direct them to specific files using syslog syntax.

For example, consider the Node NetworkPolicy `restrict-http-to-node` above:

```text
Sep 2 10:31:07 k8s-node-control-plane kernel: [6657320.789675] Antrea:In:Allow:allow-http IN=ens224 OUT= MAC=00:50:56:a7:fb:18:00:50:56:a7:23:47:08:00 SRC=10.10.0.10 DST=192.168.240.200 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=52813 DF PROTO=TCP SPT=57658 DPT=80 WINDOW=64240 RES=0x00 SYN URGP=0
Sep 2 10:31:11 k8s-node-control-plane kernel: [6657324.899219] Antrea:In:Drop:default-drop- IN=ens224 OUT= MAC=00:50:56:a7:fb:18:00:50:56:a7:23:47:08:00 SRC=192.168.240.201 DST=192.168.240.200 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=27486 DF PROTO=TCP SPT=33152 DPT=80 WINDOW=64240 RES=0x00 SYN URGP=0
```

In these logs, `Antrea:In:Allow:allow-http` and `Antrea:In:Drop:default-drop-` and the subsequent space are prefixes added
by iptables using the `--log-prefix` parameter. The last letter of the prefix should be a space to prevent the prefix
from being linked with the subsequent log data, e.g., `Antrea:In:Allow:allow-httpIN=ens224`. The iptables log prefix is
limited to 29 characters, as described in the [iptables-extensions manual](https://ipset.netfilter.org/iptables-extensions.man.html).

The prefix format always includes `Antrea:[In|Out]:[Allow|Drop|Reject]`, followed by the user-defined `logLabel` (if it
is not empty). Due to the length restriction, any part that exceeds the 28-character limit (the last letter should be a
space) will be truncated, as seen with `default-drop-others` being shortened to `default-drop-`.

To filter these logs using rsyslog, you can use the following configuration syntax on every Node:

```bash
# Example rsyslog configuration to filter Antrea logs
:msg, contains, "Antrea:In:Allow:allow-http" /var/log/antrea-node-netpol-allow.log
:msg, contains, "Antrea:In:Drop:default-drop" /var/logantrea-node-netpol-drop.log
& stop
```

This configuration directs logs with the prefix `Antrea:In:Allow:` to `/var/log/antrea-node-netpol-allow.log` and logs
with the prefix `Antrea:In:Drop:` to `/var/logantrea-node-netpol-drop.log`. The & stop command ensures that these logs
are not processed further.

## Limitations

- This feature is currently only supported for Linux Nodes.
Expand All @@ -118,4 +159,4 @@ spec:
- FQDN is not supported for ACNPs applied to Nodes.
- Layer 7 NetworkPolicy is not supported yet.
- For UDP or SCTP, when the `Reject` action is specified in an egress rule, it behaves identical to the `Drop` action.
- Traffic logging is not supported yet for ACNPs applied to Nodes.
- Traffic logging is limited supported for ACNPs applied to Nodes.
98 changes: 77 additions & 21 deletions pkg/agent/controller/networkpolicy/node_reconciler_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ const (
ipv6Any = "::/0"
)

// The logging of Node NetworkPolicy is implemented by iptables target LOG, which turns on kernel logging of matching
// packets. The default label is useful for distinguishing Node Network logs.
const commonLogLabel = "Antrea"

var ipsetTypeHashIP = ipset.HashIP

/*
Expand Down Expand Up @@ -124,7 +128,7 @@ directly.
type coreIPTRule struct {
ruleID string
priority *types.Priority
ruleStr string
ruleStrs []string
}

type chainKey struct {
Expand Down Expand Up @@ -256,7 +260,7 @@ func (r *nodeReconciler) batchAdd(rules []*CompletedRule) error {
}

// Collect all core iptables rules.
coreIPTRule := &coreIPTRule{ruleID, iptRule.Priority, iptRule.CoreIPTRule}
coreIPTRule := &coreIPTRule{ruleID, iptRule.Priority, iptRule.CoreIPTRules}
if rule.Direction == v1beta2.DirectionIn {
ingressCoreIPTRules[ipProtocol] = append(ingressCoreIPTRules[ipProtocol], coreIPTRule)
} else {
Expand Down Expand Up @@ -322,6 +326,8 @@ func (r *nodeReconciler) GetRuleByFlowID(ruleFlowID uint32) (*types.PolicyRule,

func (r *nodeReconciler) computeIPTRules(rule *CompletedRule) (map[iptables.Protocol]*types.NodePolicyRule, *nodePolicyLastRealized) {
ruleID := rule.ID
enableLogging := rule.EnableLogging
logLabel := generateLogLabel(rule)
lastRealized := newNodePolicyLastRealized()
priority := &types.Priority{
TierPriority: *rule.TierPriority,
Expand Down Expand Up @@ -362,7 +368,12 @@ func (r *nodeReconciler) computeIPTRules(rule *CompletedRule) (map[iptables.Prot

var serviceIPTRules []string
if serviceIPTChain != "" {
serviceIPTRules = buildServiceIPTRules(ipProtocol, rule.Services, serviceIPTChain, serviceIPTRuleTarget)
serviceIPTRules = buildServiceIPTRules(ipProtocol,
rule.Services,
serviceIPTChain,
serviceIPTRuleTarget,
enableLogging,
logLabel)
}

ipnets := getIPNetsFromRule(rule, isIPv6)
Expand All @@ -383,14 +394,19 @@ func (r *nodeReconciler) computeIPTRules(rule *CompletedRule) (map[iptables.Prot
lastRealized.ipnets[ipProtocol] = ipnet
}

coreIPTRule := buildCoreIPTRule(ipProtocol,
coreIPTRules := buildCoreIPTRules(ipProtocol,
coreIPTChain,
ipset,
ipnet,
coreIPTRuleTarget,
coreIPTRuleComment,
service,
rule.Direction == v1beta2.DirectionIn)
rule.Direction == v1beta2.DirectionIn,
// If the target of a core iptables rule is not a service chain, the iptables rule for logging should be
// generated along with the core iptables rule. Otherwise, the iptables rules for logging should be generated
// along with the service iptables rules.
enableLogging && serviceIPTChain == "",
logLabel)

nodePolicyRules[ipProtocol] = &types.NodePolicyRule{
IPSet: ipset,
Expand All @@ -399,7 +415,7 @@ func (r *nodeReconciler) computeIPTRules(rule *CompletedRule) (map[iptables.Prot
ServiceIPTChain: serviceIPTChain,
ServiceIPTRules: serviceIPTRules,
CoreIPTChain: coreIPTChain,
CoreIPTRule: coreIPTRule,
CoreIPTRules: coreIPTRules,
IsIPv6: isIPv6,
}
}
Expand All @@ -422,7 +438,7 @@ func (r *nodeReconciler) add(rule *CompletedRule) error {
return err
}
}
if err := r.addOrUpdateCoreIPTRules(iptRule.CoreIPTChain, iptRule.IsIPv6, false, &coreIPTRule{ruleID, iptRule.Priority, iptRule.CoreIPTRule}); err != nil {
if err := r.addOrUpdateCoreIPTRules(iptRule.CoreIPTChain, iptRule.IsIPv6, false, &coreIPTRule{ruleID, iptRule.Priority, iptRule.CoreIPTRules}); err != nil {
return err
}
}
Expand Down Expand Up @@ -453,7 +469,7 @@ func (r *nodeReconciler) update(lastRealized *nodePolicyLastRealized, newRule *C
}
}
if prevIPSet != ipset || prevIPNet != ipnet {
if err := r.addOrUpdateCoreIPTRules(iptRule.CoreIPTChain, iptRule.IsIPv6, true, &coreIPTRule{ruleID, iptRule.Priority, iptRule.CoreIPTRule}); err != nil {
if err := r.addOrUpdateCoreIPTRules(iptRule.CoreIPTChain, iptRule.IsIPv6, true, &coreIPTRule{ruleID, iptRule.Priority, iptRule.CoreIPTRules}); err != nil {
return err
}
}
Expand Down Expand Up @@ -496,9 +512,7 @@ func (r *nodeReconciler) addOrUpdateCoreIPTRules(chain string, isIPv6 bool, isUp
// Get all iptables rules and synchronize them.
var ruleStrs []string
for _, rule := range rules {
if rule.ruleStr != "" {
ruleStrs = append(ruleStrs, rule.ruleStr)
}
ruleStrs = append(ruleStrs, rule.ruleStrs...)
}
if err := r.routeClient.AddOrUpdateNodeNetworkPolicyIPTables([]string{chain}, [][]string{ruleStrs}, isIPv6); err != nil {
return err
Expand Down Expand Up @@ -533,7 +547,7 @@ func (r *nodeReconciler) deleteCoreIPTRule(ruleID string, iptChain string, isIPv
// Get all the iptables rules and synchronize them.
var ruleStrs []string
for _, r := range rules {
ruleStrs = append(ruleStrs, r.ruleStr)
ruleStrs = append(ruleStrs, r.ruleStrs...)
}
if err := r.routeClient.AddOrUpdateNodeNetworkPolicyIPTables([]string{iptChain}, [][]string{ruleStrs}, isIPv6); err != nil {
return err
Expand Down Expand Up @@ -614,32 +628,35 @@ func getIPNetsFromRule(rule *CompletedRule, isIPv6 bool) sets.Set[string] {
return set
}

func buildCoreIPTRule(ipProtocol iptables.Protocol,
func buildCoreIPTRules(ipProtocol iptables.Protocol,
iptChain string,
ipset string,
ipnet string,
iptRuleTarget string,
iptRuleComment string,
service *v1beta2.Service,
isIngress bool) string {
isIngress bool,
enableLogging bool,
logLabel string) []string {
builder := iptables.NewRuleBuilder(iptChain)
var rules []string
if isIngress {
if ipset != "" {
builder = builder.MatchIPSetSrc(ipset, ipsetTypeHashIP)
} else if ipnet != "" {
builder = builder.MatchCIDRSrc(ipnet)
} else {
// If no source IP address is matched, return an empty string since the core iptables will never be matched.
return ""
// If no source IP address is matched, return an empty slice since the core iptables will never be matched.
return rules
}
} else {
if ipset != "" {
builder = builder.MatchIPSetDst(ipset, ipsetTypeHashIP)
} else if ipnet != "" {
builder = builder.MatchCIDRDst(ipnet)
} else {
// If no destination IP address is matched, return an empty string since the core iptables will never be matched.
return ""
// If no destination IP address is matched, return an empty slice since the core iptables will never be matched.
return rules
}
}
if service != nil {
Expand All @@ -657,13 +674,26 @@ func buildCoreIPTRule(ipProtocol iptables.Protocol,
builder = builder.MatchICMP(service.ICMPType, service.ICMPCode, ipProtocol)
}
}
return builder.SetTarget(iptRuleTarget).
if enableLogging {
rules = append(rules, builder.CopyBuilder().
SetTarget(iptables.LOGTarget).
SetLogPrefix(logLabel).
Done().
GetRule())
}
rules = append(rules, builder.SetTarget(iptRuleTarget).
SetComment(iptRuleComment).
Done().
GetRule()
GetRule())
return rules
}

func buildServiceIPTRules(ipProtocol iptables.Protocol, services []v1beta2.Service, chain string, ruleTarget string) []string {
func buildServiceIPTRules(ipProtocol iptables.Protocol,
services []v1beta2.Service,
chain string,
ruleTarget string,
enableLogging bool,
logLabel string) []string {
var rules []string
builder := iptables.NewRuleBuilder(chain)
for _, svc := range services {
Expand All @@ -681,6 +711,13 @@ func buildServiceIPTRules(ipProtocol iptables.Protocol, services []v1beta2.Servi
case "icmp":
copiedBuilder = copiedBuilder.MatchICMP(svc.ICMPType, svc.ICMPCode, ipProtocol)
}
if enableLogging {
rules = append(rules, copiedBuilder.CopyBuilder().
SetTarget(iptables.LOGTarget).
SetLogPrefix(logLabel).
Done().
GetRule())
}
rules = append(rules, copiedBuilder.SetTarget(ruleTarget).
Done().
GetRule())
Expand All @@ -707,3 +744,22 @@ func getServiceTransProtocol(protocol *v1beta2.Protocol) string {
}
return strings.ToLower(string(*protocol))
}

func generateLogLabel(rule *CompletedRule) string {
if rule.EnableLogging == false {
return ""
}
logLabel := fmt.Sprintf("%s:%s:%s", commonLogLabel, rule.Direction, *rule.Action)
if rule.LogLabel != "" {
logLabel = fmt.Sprintf("%s:%s", logLabel, rule.LogLabel)
}
// The log label is used as iptables log prefix. According to https://ipset.netfilter.org/iptables-extensions.man.html,
// the prefix is up to 29 letters long.
if len(logLabel) > 28 {
klog.InfoS("The log label is up to 29 letters long, and the part of more than 29 letters will be ignored", "logLabel", logLabel)
logLabel = logLabel[:28]
}
// The last letter must be a space.
logLabel += " "
return logLabel
}
Loading

0 comments on commit a829204

Please sign in to comment.