Skip to content

Commit

Permalink
sort anps only once before allowed-conns computes (#402)
Browse files Browse the repository at this point in the history
* sort anps only once before allowed-conns computes
  • Loading branch information
shireenf-ibm authored Sep 9, 2024
1 parent 65eff08 commit c90ac47
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 93 deletions.
107 changes: 21 additions & 86 deletions pkg/netpol/eval/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package eval
import (
"errors"
"net"
"sort"
"strings"

netv1 "k8s.io/api/networking/v1"
Expand Down Expand Up @@ -494,63 +493,51 @@ func updatePeerXgressClusterWideExposure(policy *k8s.NetworkPolicy, src, dst k8s
// analyzing admin-network-policies for conns between peers (object kind == AdminNetworkPolicy):

// getAllConnsFromAdminNetpols returns the connections from src to dst by analyzing admin network policies rules
func (pe *PolicyEngine) getAllConnsFromAdminNetpols(src, dst k8s.Peer) (anpsConns *k8s.PolicyConnections,
func (pe *PolicyEngine) getAllConnsFromAdminNetpols(src, dst k8s.Peer) (policiesConns *k8s.PolicyConnections,
captured bool, err error) {
// since the priority of policies is critical for computing the conns between peers, we need all admin policies capturing both peers.
// get all admin policies selecting the dst in Ingress direction
dstAdminNetpols, err := pe.getAdminNetpolsSelectingPeer(dst, true)
if err != nil {
return nil, false, err
}
// get all admin policies selecting the src in egress direction
srcAdminNetpols, err := pe.getAdminNetpolsSelectingPeer(src, false)
if err != nil {
return nil, false, err
}

if len(dstAdminNetpols) == 0 && len(srcAdminNetpols) == 0 {
// if there are no admin netpols selecting the peers, returning nil conns,
// conns will be determined by other policy objects/ default value
return nil, false, nil
}

// admin netpols may select subjects by namespaces, so an ANP may appear in both dstAminNetpols, and srcAdminNetpols.
// then merging both sets into one unique and sorted by priority list of admin-network-policies.
adminNetpols, err := getUniqueAndSortedANPsList(dstAdminNetpols, srcAdminNetpols)
if err != nil {
return nil, false, err
}

policiesConns := k8s.InitEmptyPolicyConnections()
// iterate the related sorted admin network policies in order to compute the allowed, pass, and denied connections between the peers
for _, anp := range adminNetpols {
policiesConns = k8s.InitEmptyPolicyConnections()
// iterate the sorted admin network policies in order to compute the allowed, pass, and denied connections between the peers
// from the admin netpols capturing the src / dst / both.
// connections are computed considering ANPs priorities (rules of an ANP with lower priority take precedence on other ANPs rules)
// and rules ordering in single ANP (coming first takes precedence).
for _, anp := range pe.sortedAdminNetpols {
singleANPConns := k8s.InitEmptyPolicyConnections()
// collect the allowed, pass, and denied connectivity from the relevant rules into policiesConns
// note that anp may capture both the src and dst (by namespaces field), so both ingress and egress sections might be helpful

// if the anp captures the src, get the relevant egress conns between src and dst
if srcAdminNetpols[anp] {
selectsSrc, err := anp.Selects(src, false)
if err != nil {
return nil, false, err
}
if selectsSrc {
singleANPConns, err = anp.GetEgressPolicyConns(dst)
if err != nil {
return nil, false, err
}
}
// if the anp captures the dst, get the relevant ingress conns (from src to dst)
if dstAdminNetpols[anp] {
selectsDst, err := anp.Selects(dst, true)
if err != nil {
return nil, false, err
}
if selectsDst {
ingressConns, err := anp.GetIngressPolicyConns(src, dst)
if err != nil {
return nil, false, err
}
// get the intersection of ingress and egress sections if also the src was captured
if srcAdminNetpols[anp] {
if selectsSrc {
singleANPConns.AllowedConns.Intersection(ingressConns.AllowedConns)
singleANPConns.DeniedConns.Union(ingressConns.DeniedConns)
singleANPConns.PassConns.Union(ingressConns.PassConns)
} else { // only dst is captured by anp
singleANPConns = ingressConns
}
}
policiesConns.CollectANPConns(singleANPConns)
if !singleANPConns.IsEmpty() { // the anp is relevant (captured at least one of the peers)
policiesConns.CollectANPConns(singleANPConns)
}
}

if policiesConns.IsEmpty() { // conns between src and dst were not captured by the adminNetpols, to be determined by netpols/default conns
Expand All @@ -559,55 +546,3 @@ func (pe *PolicyEngine) getAllConnsFromAdminNetpols(src, dst k8s.Peer) (anpsConn

return policiesConns, true, nil
}

// getAdminNetpolsSelectingPeer returns set of adminNetworkPolicies which select the input peer and have rules on the required direction
func (pe *PolicyEngine) getAdminNetpolsSelectingPeer(peer k8s.Peer, isIngress bool) (map[*k8s.AdminNetworkPolicy]bool, error) {
res := make(map[*k8s.AdminNetworkPolicy]bool, 0) // set
for _, anp := range pe.adminNetpolsMap {
selects, err := anp.Selects(peer, isIngress)
if err != nil {
return nil, err
}
if selects {
res[anp] = true
}
}
return res, nil
}

// getUniqueANPsList gets two sets of adminNetpols and merges them into one list with unique ANP objects
func getUniqueAndSortedANPsList(ingressAnps, egressAnps map[*k8s.AdminNetworkPolicy]bool) ([]*k8s.AdminNetworkPolicy, error) {
res := []*k8s.AdminNetworkPolicy{}
for key := range ingressAnps {
res = append(res, key)
}
for key := range egressAnps {
if !ingressAnps[key] {
res = append(res, key)
}
}
return sortAdminNetpolsByPriority(res)
}

// sortAdminNetpolsByPriority sorts the given list of admin-network-policies by priority
func sortAdminNetpolsByPriority(anpList []*k8s.AdminNetworkPolicy) ([]*k8s.AdminNetworkPolicy, error) {
var err error
sort.Slice(anpList, func(i, j int) bool {
// outcome is non-deterministic if there are two AdminNetworkPolicies at the same priority
if anpList[i].Spec.Priority == anpList[j].Spec.Priority {
err = errors.New(netpolerrors.SamePriorityErr(anpList[i].Name, anpList[j].Name))
return false
}
// priority values range is defined
if !anpList[i].HasValidPriority() {
err = errors.New(netpolerrors.PriorityValueErr(anpList[i].Name, anpList[i].Spec.Priority))
return false
}
if !anpList[j].HasValidPriority() {
err = errors.New(netpolerrors.PriorityValueErr(anpList[j].Name, anpList[j].Spec.Priority))
return false
}
return anpList[i].Spec.Priority < anpList[j].Spec.Priority
})
return anpList, err
}
53 changes: 46 additions & 7 deletions pkg/netpol/eval/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package eval
import (
"errors"
"fmt"
"sort"

appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
Expand Down Expand Up @@ -37,7 +38,9 @@ type (
netpolsMap map[string]map[string]*k8s.NetworkPolicy // map from namespace to map from netpol name to its object
podOwnersToRepresentativePodMap map[string]map[string]*k8s.Pod // map from namespace to map from pods' ownerReference name
// to its representative pod object
adminNetpolsMap map[string]*k8s.AdminNetworkPolicy // map from adminNetworkPolicy name to its object
adminNetpolsMap map[string]bool // set of input admin-network-policies names to ensure uniqueness by name
sortedAdminNetpols []*k8s.AdminNetworkPolicy // sorted by priority list of admin-network-policies;
// sorting ANPs occurs after all input k8s objects are inserted to the policy-engine
cache *evalCache
exposureAnalysisFlag bool
representativePeersMap map[string]*k8s.WorkloadPeer // map from unique labels string to representative peer object,
Expand All @@ -61,7 +64,7 @@ func NewPolicyEngine() *PolicyEngine {
podsMap: make(map[string]*k8s.Pod),
netpolsMap: make(map[string]map[string]*k8s.NetworkPolicy),
podOwnersToRepresentativePodMap: make(map[string]map[string]*k8s.Pod),
adminNetpolsMap: make(map[string]*k8s.AdminNetworkPolicy),
adminNetpolsMap: make(map[string]bool),
cache: newEvalCache(),
exposureAnalysisFlag: false,
}
Expand Down Expand Up @@ -165,12 +168,40 @@ func (pe *PolicyEngine) addObjectsByKind(objects []parser.K8sObject) error {
return err
}
}
if !pe.exposureAnalysisFlag { // for exposure analysis; this already done
return pe.resolveMissingNamespaces()
if !pe.exposureAnalysisFlag {
// @todo: put following line outside the if statement when exposure analysis is supported with (B)ANPs
if err := pe.sortAdminNetpolsByPriority(); err != nil {
return err
}
return pe.resolveMissingNamespaces() // for exposure analysis; this already done
}
return nil
}

// sortAdminNetpolsByPriority sorts all input admin-netpols by their priority;
// since the priority of policies is critical for computing the conns between peers
func (pe *PolicyEngine) sortAdminNetpolsByPriority() error {
var err error
sort.Slice(pe.sortedAdminNetpols, func(i, j int) bool {
// outcome is non-deterministic if there are two AdminNetworkPolicies at the same priority
if pe.sortedAdminNetpols[i].Spec.Priority == pe.sortedAdminNetpols[j].Spec.Priority {
err = errors.New(netpolerrors.SamePriorityErr(pe.sortedAdminNetpols[i].Name, pe.sortedAdminNetpols[j].Name))
return false
}
// priority values range is defined
if !pe.sortedAdminNetpols[i].HasValidPriority() {
err = errors.New(netpolerrors.PriorityValueErr(pe.sortedAdminNetpols[i].Name, pe.sortedAdminNetpols[i].Spec.Priority))
return false
}
if !pe.sortedAdminNetpols[j].HasValidPriority() {
err = errors.New(netpolerrors.PriorityValueErr(pe.sortedAdminNetpols[j].Name, pe.sortedAdminNetpols[j].Spec.Priority))
return false
}
return pe.sortedAdminNetpols[i].Spec.Priority < pe.sortedAdminNetpols[j].Spec.Priority
})
return err
}

func (pe *PolicyEngine) resolveMissingNamespaces() error {
for _, pod := range pe.podsMap {
ns := pod.Namespace
Expand Down Expand Up @@ -289,7 +320,7 @@ func (pe *PolicyEngine) ClearResources() {
pe.representativePeersMap = make(map[string]*k8s.WorkloadPeer)
}
pe.cache = newEvalCache()
pe.adminNetpolsMap = make(map[string]*k8s.AdminNetworkPolicy)
pe.adminNetpolsMap = make(map[string]bool)
}

func (pe *PolicyEngine) insertNamespace(ns *corev1.Namespace) error {
Expand Down Expand Up @@ -446,10 +477,11 @@ func (pe *PolicyEngine) insertAdminNetworkPolicy(anp *apisv1a.AdminNetworkPolicy
if pe.exposureAnalysisFlag {
return errors.New(netpolerrors.ExposureAnalysisDisabledWithANPs)
}
if _, ok := pe.adminNetpolsMap[anp.Name]; ok {
if pe.adminNetpolsMap[anp.Name] {
return errors.New(netpolerrors.ANPsWithSameNameErr(anp.Name))
}
pe.adminNetpolsMap[anp.Name] = (*k8s.AdminNetworkPolicy)(anp)
pe.adminNetpolsMap[anp.Name] = true
pe.sortedAdminNetpols = append(pe.sortedAdminNetpols, (*k8s.AdminNetworkPolicy)(anp))
return nil
}

Expand Down Expand Up @@ -513,6 +545,13 @@ func (pe *PolicyEngine) deleteNetworkPolicy(np *netv1.NetworkPolicy) error {

func (pe *PolicyEngine) deleteAdminNetworkPolicy(anp *apisv1a.AdminNetworkPolicy) error {
delete(pe.adminNetpolsMap, anp.Name)
// delete anp from the pe.sortedAdminNetpols list
for i, item := range pe.sortedAdminNetpols {
if item == (*k8s.AdminNetworkPolicy)(anp) {
// assign to pe.sortedAdminNetpols all ANPs except for current item
pe.sortedAdminNetpols = append(pe.sortedAdminNetpols[:i], pe.sortedAdminNetpols[i+1:]...)
}
}
return nil
}

Expand Down

0 comments on commit c90ac47

Please sign in to comment.