Skip to content

Commit

Permalink
Add support for NodeNetworkPolicy datapath
Browse files Browse the repository at this point in the history
This PR introduces support for the NodeNetworkPolicy datapath, extending Antrea
ClusterNetworkPolicy (ACNP). The implementation leverages iptables and ipset for
enforcing rules, safeguarding Kubernetes Nodes.

There are four key components to implement the data path:

- Core iptables rule
  - Integrated into static chains ANTREA-POL-INGRESS-RULES (ingress) or
    ANTREA-POL-EGRESS-RULES (egress).
  - Matches an ipset that includes NodeNetworkPolicy rule source or
    destination IPs, or directly matches a single IP.
  - Targets an action or a service chain created for NodeNetworkPolicy
    rule with multiple services.
- Service iptables chain
  - Created for NodeNetworkPolicy rule with multiple services.
- Service iptables rules:
  - Added to the service chain for NodeNetworkPolicy rule, constructed from
    rule services.
- From/To ipset:
  - Created for a NodeNetworkPolicy rule, containing source (ingress) or
   destination (egress) IPs.

Example ingress or egress core iptables rules organized by priorities:

```
:ANTREA-POL-INGRESS-RULES
-A ANTREA-POL-INGRESS-RULES -m set --match-set ANTREA-POL-RULE1-4 src -j ANTREA-POL-RULE1 -m comment --comment "Antrea: for rule RULE1, policy AntreaClusterNetworkPolicy:name1"
-A ANTREA-POL-INGRESS-RULES -m set --match-set ANTREA-POL-RULE2-4 src -p tcp --dport 8080 -j ACCEPT -m comment --comment "Antrea: for rule RULE2, policy AntreaClusterNetworkPolicy:name2"
-A ANTREA-POL-INGRESS-RULES -s 3.3.3.3/32 src -j ANTREA-POL-RULE3 -m comment --comment "Antrea: for rule RULE3, policy AntreaClusterNetworkPolicy:name3"
-A ANTREA-POL-INGRESS-RULES -s 4.4.4.4/32 -p tcp --dport 80 -j ACCEPT -m comment --comment "Antrea: for rule RULE4, policy AntreaClusterNetworkPolicy:name4"
```

Example service chain (for rule with multiple services)::

```
:ANTREA-POL-RULE1
-A ANTREA-POL-RULE1 -j ACCEPT -p tcp --dport 80
-A ANTREA-POL-RULE1 -j ACCEPT -p tcp --dport 443
```

Example ipset (for rule with multiple source or destination IPs)

```
Name: ANTREA-POL-RULE1-4
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 472
References: 1
Number of entries: 2
Members:
1.1.1.1
1.1.1.2
```

Signed-off-by: Hongliang Liu <lhongliang@vmware.com>
  • Loading branch information
hongliangl committed Jan 9, 2024
1 parent a6b43db commit 45d69c4
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 33 deletions.
4 changes: 2 additions & 2 deletions docs/antrea-node-network-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
## Introduction

Node NetworkPolicy is designed to secure the Kubernetes Nodes traffic. It is supported by Antrea starting with Antrea
v1.15. This guide demonstrates how to configure Node NetworkPolicies.
v1.15. This guide demonstrates how to configure Node NetworkPolicy.

## Prerequisites

Expand Down Expand Up @@ -40,7 +40,7 @@ helm install antrea antrea/antrea --namespace kube-system --set featureGates.Nod
## Usage

Node NetworkPolicy is an extension of Antrea ClusterNetworkPolicy (ACNP). By specifying a `nodeSelector` in the
policy-level `appliedTo`, an ACNP is applied to the selected Kubernetes Nodes.
policy-level `appliedTo` without other selectors, an ACNP is applied to the selected Kubernetes Nodes.

An example Node NetworkPolicy that blocks ingress traffic from Pods with label `app=client` to Nodes with label
`kubernetes.io/hostname: k8s-node-control-plane`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ func (c *Controller) syncRule(key string) error {
rule, effective, realizable := c.ruleCache.GetCompletedRule(key)
if !effective {
klog.V(2).InfoS("Rule was not effective, removing it", "ruleID", key)
// Uncertain whether this rule applies to Node or Pod, but it's safe to delete it redundantly.
// Uncertain whether this rule applies to a Node or Pod, but it's safe to delete it redundantly.
if err := c.podReconciler.Forget(key); err != nil {
return err
}
Expand Down Expand Up @@ -778,7 +778,7 @@ func (c *Controller) syncRule(key string) error {

isNodeNetworkPolicy := rule.isNodeNetworkPolicyRule()
if !c.nodeNetworkPolicyEnabled && isNodeNetworkPolicy {
klog.InfoS("Feature gate NodeNetworkPolicy is not enabled, skipping", "ruleID", key)
klog.Warningf("Feature gate NodeNetworkPolicy is not enabled, skipping ruleID %s", key)
return nil
}

Expand Down Expand Up @@ -834,7 +834,7 @@ func (c *Controller) syncRules(keys []string) error {
} else {
isNodeNetworkPolicy := rule.isNodeNetworkPolicyRule()
if !c.nodeNetworkPolicyEnabled && isNodeNetworkPolicy {
klog.InfoS("Feature gate NodeNetworkPolicy is not enabled, skipping", "ruleID", key)
klog.Warningf("Feature gate NodeNetworkPolicy is not enabled, skipping ruleID %s", key)
continue
}
if c.l7NetworkPolicyEnabled && len(rule.L7Protocols) != 0 {
Expand Down
56 changes: 28 additions & 28 deletions pkg/agent/controller/networkpolicy/node_reconciler_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ func newIPTChain() *coreIPTChain {
// nodePolicyLastRealized is the struct cached by nodeReconciler. It's used to track the actual state of iptables rules
// and chains we have enforced, so that we can know how to reconcile a rule when it's updated/removed.
type nodePolicyLastRealized struct {
// ipsets tracks the last realized ipset names used in core iptables rules. It cannot coexist with ipNets.
// ipsets tracks the last realized ipset names used in core iptables rules. It cannot coexist with ipnets.
ipsets map[iptables.Protocol]string
// ipNets tracks the last realized ipNet used in core iptables rules. It cannot coexist with ipsets.
ipNets map[iptables.Protocol]string
// serviceIPTChain tracks the last realized service iptables chain if a rule have multiple services.
// ipnets tracks the last realized ip nets used in core iptables rules. It cannot coexist with ipsets.
ipnets map[iptables.Protocol]string
// serviceIPTChain tracks the last realized service iptables chain if a rule has multiple services.
serviceIPTChain string
// coreIPTChain tracks the last realized iptables chain where the core iptables rule is installed.
coreIPTChain string
Expand All @@ -155,7 +155,7 @@ type nodePolicyLastRealized struct {
func newNodePolicyLastRealized() *nodePolicyLastRealized {
return &nodePolicyLastRealized{
ipsets: make(map[iptables.Protocol]string),
ipNets: make(map[iptables.Protocol]string),
ipnets: make(map[iptables.Protocol]string),
}
}

Expand Down Expand Up @@ -348,32 +348,32 @@ func (r *nodeReconciler) computeIPTRules(rule *CompletedRule) (map[iptables.Prot
serviceIPTRules = buildServiceIPTRules(ipProtocol, rule.Services, serviceIPTChain, serviceIPTRuleTarget)
}

ipNets := getIPNetsFromRule(rule, isIPv6)
var ipNet string
ipnets := getIPNetsFromRule(rule, isIPv6)
var ipnet string
var ipset string
if ipNets.Len() > 1 {
// If a rule matches multiple source or destination ipNets, create an ipset which contains these ipnets and
if ipnets.Len() > 1 {
// If a rule matches multiple source or destination ipnets, create an ipset which contains these ipnets and
// use the ipset in core iptables rule.
ipset = genIPSetName(ruleID, isIPv6)
lastRealized.ipsets[ipProtocol] = ipset
} else if ipNets.Len() == 1 {
} else if ipnets.Len() == 1 {
// If a rule matches single source or destination, use it in core iptables rule directly.
ipNet, _ = ipNets.PopAny()
lastRealized.ipNets[ipProtocol] = ipNet
ipnet, _ = ipnets.PopAny()
lastRealized.ipnets[ipProtocol] = ipnet
}

coreIPTRule := buildCoreIPTRule(ipProtocol,
coreIPTChain,
ipset,
ipNet,
ipnet,
coreIPTRuleTarget,
coreIPTRuleComment,
service,
rule.Direction == v1beta2.DirectionIn)

nodePolicyRules[ipProtocol] = &types.NodePolicyRule{
IPSet: ipset,
IPSetMembers: ipNets,
IPSetMembers: ipnets,
Priority: priority,
ServiceIPTChain: serviceIPTChain,
ServiceIPTRules: serviceIPTRules,
Expand Down Expand Up @@ -417,8 +417,8 @@ func (r *nodeReconciler) update(lastRealized *nodePolicyLastRealized, newRule *C
for _, ipProtocol := range r.ipProtocols {
iptRule := newIPTRules[ipProtocol]

prevIPNet := lastRealized.ipNets[ipProtocol]
ipNet := newLastRealized.ipNets[ipProtocol]
prevIPNet := lastRealized.ipnets[ipProtocol]
ipnet := newLastRealized.ipnets[ipProtocol]
prevIPSet := lastRealized.ipsets[ipProtocol]
ipset := newLastRealized.ipsets[ipProtocol]

Expand All @@ -431,7 +431,7 @@ func (r *nodeReconciler) update(lastRealized *nodePolicyLastRealized, newRule *C
return err
}
}
if prevIPSet != ipset || prevIPNet != ipNet {
if prevIPSet != ipset || prevIPNet != ipnet {
if err := r.addOrUpdateCoreIPTRules(iptRule.CoreIPTChain, iptRule.IsIPv6, true, &coreIPTRule{ruleID, iptRule.Priority, iptRule.CoreIPTRule}); err != nil {
return err
}
Expand Down Expand Up @@ -532,7 +532,7 @@ func (r *nodeReconciler) getCoreIPTChain(iptChain string, isIPv6 bool) *coreIPTC
}

func groupMembersToIPNets(groups v1beta2.GroupMemberSet, isIPv6 bool) sets.Set[string] {
ipNets := sets.New[string]()
ipnets := sets.New[string]()
suffix := "/32"
if isIPv6 {
suffix = "/128"
Expand All @@ -541,15 +541,15 @@ func groupMembersToIPNets(groups v1beta2.GroupMemberSet, isIPv6 bool) sets.Set[s
for _, ip := range member.IPs {
ipAddr := net.IP(ip)
if isIPv6 == utilnet.IsIPv6(ipAddr) {
ipNets.Insert(ipAddr.String() + suffix)
ipnets.Insert(ipAddr.String() + suffix)
}
}
}
return ipNets
return ipnets
}

func ipBlocksToIPNets(ipBlocks []v1beta2.IPBlock, isIPv6 bool) []string {
var ipNets []string
var ipnets []string
for _, b := range ipBlocks {
blockCIDR := ip.IPNetToNetIPNet(&b.CIDR)
if isIPv6 != utilnet.IsIPv6CIDR(blockCIDR) {
Expand All @@ -567,10 +567,10 @@ func ipBlocksToIPNets(ipBlocks []v1beta2.IPBlock, isIPv6 bool) []string {
continue
}
for _, d := range diffCIDRs {
ipNets = append(ipNets, d.String())
ipnets = append(ipnets, d.String())
}
}
return ipNets
return ipnets
}

func getIPNetsFromRule(rule *CompletedRule, isIPv6 bool) sets.Set[string] {
Expand Down Expand Up @@ -603,7 +603,7 @@ func getCoreIPTChain(rule *CompletedRule) string {
func buildCoreIPTRule(ipProtocol iptables.Protocol,
iptChain string,
ipset string,
ipNet string,
ipnet string,
iptRuleTarget string,
iptRuleComment string,
service *v1beta2.Service,
Expand All @@ -612,17 +612,17 @@ func buildCoreIPTRule(ipProtocol iptables.Protocol,
if isIngress {
if ipset != "" {
builder = builder.MatchIPSetSrc(ipset)
} else if ipNet != "" {
builder = builder.MatchCIDRSrc(ipNet)
} 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 ""
}
} else {
if ipset != "" {
builder = builder.MatchIPSetDst(ipset)
} else if ipNet != "" {
builder = builder.MatchCIDRDst(ipNet)
} 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 ""
Expand Down

0 comments on commit 45d69c4

Please sign in to comment.