Skip to content

Commit

Permalink
exposure analysis with focus-workload (#349)
Browse files Browse the repository at this point in the history
* exposure analysis with focus-workload

* focus-workload fixes

* textual output enhancement (adding [] to strings with multiple words)

* fix
  • Loading branch information
shireenf-ibm authored May 12, 2024
1 parent f98ffc9 commit 6525871
Show file tree
Hide file tree
Showing 39 changed files with 810 additions and 48 deletions.
41 changes: 27 additions & 14 deletions pkg/netpol/connlist/connlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func GetConnectionSetFromP2PConnection(c Peer2PeerConnection) *common.Connection

//////////////////////////////////////////////////////////////////////////////////////////////

func (ca *ConnlistAnalyzer) includePairOfWorkloads(pe *eval.PolicyEngine, src, dst eval.Peer) bool {
func (ca *ConnlistAnalyzer) includePairOfWorkloads(pe *eval.PolicyEngine, src, dst Peer) bool {
if src.IsPeerIPType() && dst.IsPeerIPType() {
return false
}
Expand All @@ -376,14 +376,12 @@ func (ca *ConnlistAnalyzer) includePairOfWorkloads(pe *eval.PolicyEngine, src, d
if ca.exposureAnalysis && !ca.includePairWithRepresentativePeer(pe, src, dst) {
return false
}
if ca.focusWorkload == "" {
return true
}
// at least one of src/dst should be the focus workload

// no focusworkload or at least one of src/dst should be the focus workload
return ca.isPeerFocusWorkload(src) || ca.isPeerFocusWorkload(dst)
}

func (ca *ConnlistAnalyzer) includePairWithRepresentativePeer(pe *eval.PolicyEngine, src, dst eval.Peer) bool {
func (ca *ConnlistAnalyzer) includePairWithRepresentativePeer(pe *eval.PolicyEngine, src, dst Peer) bool {
isRepSrc := pe.IsRepresentativePeer(src)
isRepDst := pe.IsRepresentativePeer(dst)
// cases when at least one of the peers is representative peer; when not to include the peers pair:
Expand All @@ -403,12 +401,15 @@ func (ca *ConnlistAnalyzer) includePairWithRepresentativePeer(pe *eval.PolicyEng
return true
}

func getPeerNsNameFormat(peer eval.Peer) string {
func getPeerNsNameFormat(peer Peer) string {
return types.NamespacedName{Namespace: peer.Namespace(), Name: peer.Name()}.String()
}

func (ca *ConnlistAnalyzer) isPeerFocusWorkload(peer eval.Peer) bool {
return !peer.IsPeerIPType() && (peer.Name() == ca.focusWorkload || getPeerNsNameFormat(peer) == ca.focusWorkload)
// isPeerFocusWorkload returns true if focus-workload flag is not used (each peer is included),
// or if the focus-workload is equal to peer's name
func (ca *ConnlistAnalyzer) isPeerFocusWorkload(peer Peer) bool {
return ca.focusWorkload == "" ||
(!peer.IsPeerIPType() && (peer.Name() == ca.focusWorkload || getPeerNsNameFormat(peer) == ca.focusWorkload))
}

func convertEvalPeersToConnlistPeer(peers []eval.Peer) []Peer {
Expand Down Expand Up @@ -500,7 +501,7 @@ func (ca *ConnlistAnalyzer) existsFocusWorkload(peers []Peer, excludeIngressAnal

// check if the focusworkload is in the peers
for _, peer := range peers {
if ca.focusWorkload == peer.Name() || ca.focusWorkload == getPeerNsNameFormat(peer) {
if ca.isPeerFocusWorkload(peer) {
return true, ""
}
}
Expand Down Expand Up @@ -531,7 +532,7 @@ func (ca *ConnlistAnalyzer) getConnectionsBetweenPeers(pe *eval.PolicyEngine, pe
return nil, nil, err
}
if ca.exposureAnalysis {
err = updatePeersGeneralExposureData(pe, srcPeer, dstPeer, ingressSet, egressSet, exposureMaps)
err = ca.updatePeersGeneralExposureData(pe, srcPeer, dstPeer, ingressSet, egressSet, exposureMaps)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -641,7 +642,8 @@ func createConnectionObject(allowedConnections common.Connection, src, dst Peer)
}

// updatePeersGeneralExposureData updates src and dst connections to entire world/cluster on the exposures map
func updatePeersGeneralExposureData(pe *eval.PolicyEngine, src, dst Peer, ingressSet, egressSet map[Peer]bool, exMaps *exposureMaps) error {
func (ca *ConnlistAnalyzer) updatePeersGeneralExposureData(pe *eval.PolicyEngine, src, dst Peer, ingressSet, egressSet map[Peer]bool,
exMaps *exposureMaps) error {
// when computing allowed conns between the peers,(even on first time)
// if a workload peer is not protected by netpols this was definitely detected;
// also exposure to entire cluster was definitely computed for src or/and dst (if its a workload peer)
Expand All @@ -652,17 +654,28 @@ func updatePeersGeneralExposureData(pe *eval.PolicyEngine, src, dst Peer, ingres
// (e.g. only one peer with one netpol exposing the peer to entire cluster, no netpols)
var err error
// 1. only on first time : add general exposure data for the src peer (on egress)
if !src.IsPeerIPType() && !pe.IsRepresentativePeer(src) && !egressSet[src] {
if ca.shouldAddPeerGeneralExposureData(pe, src, egressSet) {
err = exMaps.addPeerGeneralExposure(pe, src, false)
if err != nil {
return err
}
}
egressSet[src] = true
// 2. only on first time : add general exposure data for the dst peer (on ingress)
if !dst.IsPeerIPType() && !pe.IsRepresentativePeer(dst) && !ingressSet[dst] {
if ca.shouldAddPeerGeneralExposureData(pe, dst, ingressSet) {
err = exMaps.addPeerGeneralExposure(pe, dst, true)
}
ingressSet[dst] = true
return err
}

// shouldAddPeerGeneralExposureData returns whether should add given peer's general
// exposure data to the exposure results.
// returns true if :
// - the peer is not IP type
// - the peer is not representative peer
// - focus-workload flag is not used or the peer is the focus-workload
// - it is first time the peer is visited
func (ca *ConnlistAnalyzer) shouldAddPeerGeneralExposureData(pe *eval.PolicyEngine, peer Peer, xgressSet map[Peer]bool) bool {
return !peer.IsPeerIPType() && !pe.IsRepresentativePeer(peer) && !xgressSet[peer] && ca.isPeerFocusWorkload(peer)
}
38 changes: 38 additions & 0 deletions pkg/netpol/connlist/connlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,20 @@ var goodPathTests = []struct {
exposureAnalysis: true,
outputFormats: ExposureValidFormats,
},
{
testDirName: "acs-security-demos",
exposureAnalysis: true,
// test with focus-workload that appears in exposure-analysis result
focusWorkload: "frontend/webapp",
outputFormats: ExposureValidFormats,
},
{
testDirName: "acs-security-demos",
exposureAnalysis: true,
// test with focus-workload that does not appear in exposure-analysis result
focusWorkload: "backend/catalog",
outputFormats: ExposureValidFormats,
},
{
testDirName: "test_allow_all",
exposureAnalysis: true,
Expand All @@ -785,6 +799,12 @@ var goodPathTests = []struct {
exposureAnalysis: true,
outputFormats: ExposureValidFormats,
},
{
testDirName: "test_matched_and_unmatched_rules",
exposureAnalysis: true,
focusWorkload: "hello-world/workload-a",
outputFormats: ExposureValidFormats,
},
{
testDirName: "test_only_matched_rules",
exposureAnalysis: true,
Expand Down Expand Up @@ -870,9 +890,27 @@ var goodPathTests = []struct {
exposureAnalysis: true,
outputFormats: ExposureValidFormats,
},
{
testDirName: "onlineboutique_workloads",
exposureAnalysis: true,
focusWorkload: "default/loadgenerator",
outputFormats: ExposureValidFormats,
},
{
testDirName: "k8s_ingress_test_new",
exposureAnalysis: true,
outputFormats: ExposureValidFormats,
},
{
testDirName: "k8s_ingress_test_new",
exposureAnalysis: true,
focusWorkload: "details-v1-79f774bdb9",
outputFormats: ExposureValidFormats,
},
{
testDirName: "k8s_ingress_test",
exposureAnalysis: true,
focusWorkload: "ratings-v1-b6994bb9",
outputFormats: ExposureValidFormats,
},
}
39 changes: 27 additions & 12 deletions pkg/netpol/connlist/conns_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ func formExposureItemAsSingleConnFiled(peerStr string, exposureItem XgressExposu
if exposureItem.IsExposedToEntireCluster() {
return formSingleExposureConn(peerStr, entireCluster, exposureItem.PotentialConnectivity(), isIngress)
}
repPeerStr := getRepresentativeNamespaceString(exposureItem.NamespaceLabels()) + "/" +
getRepresentativePodString(exposureItem.PodLabels())
repPeerStr := getRepresentativeNamespaceString(exposureItem.NamespaceLabels(), true) + "/" +
getRepresentativePodString(exposureItem.PodLabels(), true)
return formSingleExposureConn(peerStr, repPeerStr, exposureItem.PotentialConnectivity(), isIngress)
}

Expand All @@ -85,27 +85,42 @@ func convertLabelsMapToString(labelsMap map[string]string) string {
}

const (
mapOpen = "{"
mapClose = "}"
stringInBrackets = "[%s]"
mapOpen = "{"
mapClose = "}"
)

// getRepresentativeNamespaceString returns a string representation of a potential peer with namespace labels
func getRepresentativeNamespaceString(nsLabels map[string]string) string {
// getRepresentativeNamespaceString returns a string representation of a potential peer with namespace labels.
// if namespace with multiple words adds [] , in case of textual (non-graphical) output
func getRepresentativeNamespaceString(nsLabels map[string]string, txtOutFlag bool) string {
nsName, ok := nsLabels[common.K8sNsNameLabelKey]
if len(nsLabels) == 1 && ok {
return nsName
}
res := ""
if len(nsLabels) > 0 {
return "namespace with " + mapOpen + convertLabelsMapToString(nsLabels) + mapClose
res += "namespace with " + mapOpen + convertLabelsMapToString(nsLabels) + mapClose
} else {
res += allNamespacesLbl
}
return allNamespacesLbl
if txtOutFlag {
return fmt.Sprintf(stringInBrackets, res)
}
return res
}

// getRepresentativePodString returns a string representation of potential peer with pod labels
// or all pods string for empty pod labels map (which indicates all pods)
func getRepresentativePodString(podLabels map[string]string) string {
// or all pods string for empty pod labels map (which indicates all pods).
// adds [] in case of textual (non-graphical) output
func getRepresentativePodString(podLabels map[string]string, txtOutFlag bool) string {
res := ""
if len(podLabels) == 0 {
return allPeersLbl
res += allPeersLbl
} else {
res += "pod with " + mapOpen + convertLabelsMapToString(podLabels) + mapClose
}
if txtOutFlag {
return fmt.Sprintf(stringInBrackets, res)
}
return "pod with " + mapOpen + convertLabelsMapToString(podLabels) + mapClose
return res
}
8 changes: 4 additions & 4 deletions pkg/netpol/connlist/conns_formatter_dot.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,17 @@ func getXgressExposureEdges(exposedPeerStr string, xgressExpData []XgressExposur
data.PotentialConnectivity().(*common.ConnectionSet)))
continue // if a data contains exposure to entire cluster it does not specify labels
}
nsRepLabel := getRepresentativeNamespaceString(data.NamespaceLabels())
repPeerLabel := getRepresentativePodString(data.PodLabels())
nsRepLabel := getRepresentativeNamespaceString(data.NamespaceLabels(), false)
repPeerLabel := getRepresentativePodString(data.PodLabels(), false)
repPeersStr := repPeerLabel + "_in_" + nsRepLabel // to get a unique string name of the peer node
if !representativeVisited[repPeersStr] {
representativeVisited[repPeersStr] = true
peerLine := getRepPeerLine(repPeersStr, repPeerLabel)
// ns label maybe a name of an existing namespace, so check where to add the peer
if _, ok := nsPeers[nsRepLabel]; ok { // in real ns
dotformatting.AddPeerToNsGroup(getRepresentativeNamespaceString(data.NamespaceLabels()), peerLine, nsPeers)
dotformatting.AddPeerToNsGroup(nsRepLabel, peerLine, nsPeers)
} else { // in a representative ns
dotformatting.AddPeerToNsGroup(getRepresentativeNamespaceString(data.NamespaceLabels()), peerLine, nsRepPeers)
dotformatting.AddPeerToNsGroup(nsRepLabel, peerLine, nsRepPeers)
}
}
xgressEdges = append(xgressEdges, getExposureEdgeLine(exposedPeerStr, repPeersStr, isIngress,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
digraph {
subgraph "cluster_backend" {
color="black"
fontcolor="black"
"backend/catalog[Deployment]" [label="catalog[Deployment]" color="blue" fontcolor="blue"]
"backend/recommendation[Deployment]" [label="recommendation[Deployment]" color="blue" fontcolor="blue"]
"backend/reports[Deployment]" [label="reports[Deployment]" color="blue" fontcolor="blue"]
label="backend"
}
"backend/recommendation[Deployment]" -> "backend/catalog[Deployment]" [label="TCP 8080" color="gold2" fontcolor="darkgreen"]
"backend/reports[Deployment]" -> "backend/catalog[Deployment]" [label="TCP 8080" color="gold2" fontcolor="darkgreen"]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
backend/recommendation[Deployment] => backend/catalog[Deployment] : TCP 8080
backend/reports[Deployment] => backend/catalog[Deployment] : TCP 8080
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
digraph {
subgraph "cluster_backend" {
color="black"
fontcolor="black"
"backend/checkout[Deployment]" [label="checkout[Deployment]" color="blue" fontcolor="blue"]
"backend/recommendation[Deployment]" [label="recommendation[Deployment]" color="blue" fontcolor="blue"]
"backend/reports[Deployment]" [label="reports[Deployment]" color="blue" fontcolor="blue"]
"backend/shipping[Deployment]" [label="shipping[Deployment]" color="blue" fontcolor="blue"]
label="backend"
}
subgraph "cluster_frontend" {
color="black"
fontcolor="black"
"frontend/webapp[Deployment]" [label="webapp[Deployment]" color="blue" fontcolor="blue"]
label="frontend"
}
"entire-cluster" [label="entire-cluster" color="red2" fontcolor="red2" shape=diamond]
"{ingress-controller}" [label="{ingress-controller}" color="blue" fontcolor="blue"]
"entire-cluster" -> "frontend/webapp[Deployment]" [label="TCP 8080" color="gold2" fontcolor="darkgreen"]
"frontend/webapp[Deployment]" -> "backend/checkout[Deployment]" [label="TCP 8080" color="gold2" fontcolor="darkgreen"]
"frontend/webapp[Deployment]" -> "backend/recommendation[Deployment]" [label="TCP 8080" color="gold2" fontcolor="darkgreen"]
"frontend/webapp[Deployment]" -> "backend/reports[Deployment]" [label="TCP 8080" color="gold2" fontcolor="darkgreen"]
"frontend/webapp[Deployment]" -> "backend/shipping[Deployment]" [label="TCP 8080" color="gold2" fontcolor="darkgreen"]
"frontend/webapp[Deployment]" -> "entire-cluster" [label="UDP 5353" color="gold2" fontcolor="darkgreen"]
"{ingress-controller}" -> "frontend/webapp[Deployment]" [label="TCP 8080" color="gold2" fontcolor="darkgreen"]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 6525871

Please sign in to comment.