diff --git a/pkg/netpol/eval/check.go b/pkg/netpol/eval/check.go index edc06774..00b7f49e 100644 --- a/pkg/netpol/eval/check.go +++ b/pkg/netpol/eval/check.go @@ -9,7 +9,6 @@ package eval import ( "errors" "net" - "sort" "strings" netv1 "k8s.io/api/networking/v1" @@ -494,55 +493,41 @@ 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) @@ -550,7 +535,9 @@ func (pe *PolicyEngine) getAllConnsFromAdminNetpols(src, dst k8s.Peer) (anpsConn 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 @@ -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 -} diff --git a/pkg/netpol/eval/resources.go b/pkg/netpol/eval/resources.go index 327cc0d2..bd69a668 100644 --- a/pkg/netpol/eval/resources.go +++ b/pkg/netpol/eval/resources.go @@ -9,6 +9,7 @@ package eval import ( "errors" "fmt" + "sort" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" @@ -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, @@ -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, } @@ -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 @@ -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 { @@ -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 } @@ -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 }