Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exposure analysis with pod selectors #343

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/netpol/connlist/connlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,4 +854,9 @@ var goodPathTests = []struct {
exposureAnalysis: true,
outputFormats: ExposureValidFormats,
},
{
testDirName: "test_conn_with_pod_selector_in_any_ns",
exposureAnalysis: true,
outputFormats: ExposureValidFormats,
},
}
17 changes: 7 additions & 10 deletions pkg/netpol/connlist/conns_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,8 @@ func formExposureItemAsSingleConnFiled(peerStr string, exposureItem XgressExposu
if exposureItem.IsExposedToEntireCluster() {
return formSingleExposureConn(peerStr, entireCluster, exposureItem.PotentialConnectivity(), isIngress)
}
repPeerStr := writeNsLabels(exposureItem.NamespaceLabels())
if repPeerStr != "" {
repPeerStr += "/"
}
repPeerStr += writePodLabels(exposureItem.PodLabels())
repPeerStr := getRepresentativeNamespaceString(exposureItem.NamespaceLabels()) + "/" +
getRepresentativePodString(exposureItem.PodLabels())
return formSingleExposureConn(peerStr, repPeerStr, exposureItem.PotentialConnectivity(), isIngress)
}

Expand All @@ -86,21 +83,21 @@ const (
mapClose = "}"
)

// writeNsLabels returns a string representation of a potential peer with namespace labels
func writeNsLabels(nsLabels map[string]string) string {
// getRepresentativeNamespaceString returns a string representation of a potential peer with namespace labels
func getRepresentativeNamespaceString(nsLabels map[string]string) string {
nsName, ok := nsLabels[common.K8sNsNameLabelKey]
if len(nsLabels) == 1 && ok {
return nsName
}
adisos marked this conversation as resolved.
Show resolved Hide resolved
if len(nsLabels) > 0 {
return "namespace with " + mapOpen + convertLabelsMapToString(nsLabels) + mapClose
}
return ""
return allNamespacesLbl
}

// writePodLabels returns a string representation of potential peer with pod labels
// 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 writePodLabels(podLabels map[string]string) string {
func getRepresentativePodString(podLabels map[string]string) string {
if len(podLabels) == 0 {
return allPeersLbl
}
Expand Down
14 changes: 8 additions & 6 deletions pkg/netpol/connlist/conns_formatter_dot.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
entireClusterShape = " shape=diamond"
peerLineClosing = "]"
allPeersLbl = "all pods"
allNamespacesLbl = "all namespaces"
)

var edgeLineFormat = fmt.Sprintf("\t%%q -> %%q [label=%%q color=\"gold2\" fontcolor=\"darkgreen\"]")
Expand Down Expand Up @@ -143,16 +144,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 := writeNsLabels(data.NamespaceLabels())
repPeerLabel := writePodLabels(data.PodLabels())
nsRepLabel := getRepresentativeNamespaceString(data.NamespaceLabels())
repPeerLabel := getRepresentativePodString(data.PodLabels())
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 {
dotformatting.AddPeerToNsGroup(writeNsLabels(data.NamespaceLabels()), getRepPeerLine(repPeersStr, repPeerLabel), nsPeers)
} else {
dotformatting.AddPeerToNsGroup(writeNsLabels(data.NamespaceLabels()), getRepPeerLine(repPeersStr, repPeerLabel), nsRepPeers)
if _, ok := nsPeers[nsRepLabel]; ok { // in real ns
dotformatting.AddPeerToNsGroup(getRepresentativeNamespaceString(data.NamespaceLabels()), peerLine, nsPeers)
} else { // in a representative ns
dotformatting.AddPeerToNsGroup(getRepresentativeNamespaceString(data.NamespaceLabels()), peerLine, nsRepPeers)
}
}
xgressEdges = append(xgressEdges, getExposureEdgeLine(exposedPeerStr, repPeersStr, isIngress,
Expand Down
2 changes: 1 addition & 1 deletion pkg/netpol/eval/internal/k8s/netpol.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func isRepresentativePod(peer Peer) bool {
if peer.GetPeerPod() == nil {
return false
}
return peer.GetPeerPod().FakePod && strings.HasPrefix(peer.GetPeerPod().Name, RepresentativePodName)
return peer.GetPeerPod().IsPodRepresentative()
}

// ruleConnsContain returns true if the given protocol and port are contained in connections allowed by rulePorts
Expand Down
8 changes: 7 additions & 1 deletion pkg/netpol/eval/internal/k8s/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"strings"

appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
Expand Down Expand Up @@ -306,11 +307,16 @@ func (pod *Pod) checkAndConvertNamedPortsInConnection(conns *common.ConnectionSe
return connsCopy
}

// updatePodXgressProtectedFlag updates to true the relevant ingress/egress protected flag of the pod
// UpdatePodXgressProtectedFlag updates to true the relevant ingress/egress protected flag of the pod
func (pod *Pod) UpdatePodXgressProtectedFlag(isIngress bool) {
if isIngress {
pod.IngressExposureData.IsProtected = true
} else {
pod.EgressExposureData.IsProtected = true
}
}

// IsPodRepresentative returns if the pod is a representative pod
func (pod *Pod) IsPodRepresentative() bool {
return pod.FakePod && strings.HasPrefix(pod.Name, RepresentativePodName)
}
5 changes: 2 additions & 3 deletions pkg/netpol/eval/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package eval
import (
"errors"
"fmt"
"strings"

appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
Expand Down Expand Up @@ -600,9 +599,9 @@ func (pe *PolicyEngine) AddPodByNameAndNamespace(name, ns string, objLabels *k8s
return nil, err
}
// if exposure-analysis and this is not a fake ingress-controller
if pe.exposureAnalysisFlag && strings.HasPrefix(newPod.Name, k8s.RepresentativePodName) {
if pe.exposureAnalysisFlag && newPod.IsPodRepresentative() {
// first compute a unique string from labels to be used as a map key
keyStrFromLabels := k8s.VariantFromLabelsMap(objLabels.NsLabels) + k8s.VariantFromLabelsMap(objLabels.PodLabels)
keyStrFromLabels := k8s.VariantFromLabelsMap(objLabels.NsLabels) + "/" + k8s.VariantFromLabelsMap(objLabels.PodLabels)
if _, ok := pe.representativePeersMap[keyStrFromLabels]; ok { // we already have a representative peer with same labels
return nil, nil
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
digraph {
subgraph "cluster_hello_world" {
color="black"
fontcolor="black"
"hello-world/workload-a[Deployment]" [label="workload-a[Deployment]" color="blue" fontcolor="blue"]
label="hello-world"
}
subgraph "cluster_all namespaces" {
color="red2"
fontcolor="red2"
"pod with {role=monitoring}_in_all namespaces" [label="pod with {role=monitoring}" color="red2" fontcolor="red2"]
label="all namespaces"
}
"0.0.0.0-255.255.255.255" [label="0.0.0.0-255.255.255.255" color="red2" fontcolor="red2"]
"entire-cluster" [label="entire-cluster" color="red2" fontcolor="red2" shape=diamond]
"hello-world/workload-a[Deployment]" -> "0.0.0.0-255.255.255.255" [label="All Connections" color="gold2" fontcolor="darkgreen"]
"hello-world/workload-a[Deployment]" -> "entire-cluster" [label="All Connections" color="gold2" fontcolor="darkgreen"]
"pod with {role=monitoring}_in_all namespaces" -> "hello-world/workload-a[Deployment]" [label="TCP 8050" 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,12 @@
hello-world/workload-a[Deployment] => 0.0.0.0-255.255.255.255 : All Connections

Exposure Analysis Result:
Egress Exposure:
hello-world/workload-a[Deployment] => 0.0.0.0-255.255.255.255 : All Connections
hello-world/workload-a[Deployment] => entire-cluster : All Connections

Ingress Exposure:
hello-world/workload-a[Deployment] <= all namespaces/pod with {role=monitoring} : TCP 8050

Workloads not protected by network policies:
hello-world/workload-a[Deployment] is not protected on Egress
2 changes: 1 addition & 1 deletion tests/test_conn_to_all_pods_in_a_new_ns/netpol.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-ingress-and-egress-in-entire-cluster
name: allow-ingress-to-unknown-ns
namespace: hello-world
spec:
podSelector:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-ingress-and-egress-in-entire-cluster
name: allow-ingress-ns-and-pod-selectors
namespace: hello-world
spec:
podSelector:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_conn_with_only_pod_selector/netpol.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-ingress-and-egress-in-entire-cluster
name: allow-ingress-with-pod-selector
namespace: hello-world
spec:
podSelector:
Expand Down
20 changes: 20 additions & 0 deletions tests/test_conn_with_pod_selector_in_any_ns/netpol.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-ingress-to-pod-in-any-ns
namespace: hello-world
spec:
podSelector:
matchLabels:
app: a-app
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector: {}
podSelector:
matchLabels:
role: monitoring
ports:
- port: 8050
protocol: TCP
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: hello-world
spec: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: workload-a
namespace: hello-world
labels:
app: a-app
spec:
selector:
matchLabels:
app: a-app
template:
metadata:
labels:
app: a-app
spec:
containers:
- name: hello-world
image: quay.io/shfa/hello-world:latest
ports:
- containerPort: 8000 # containerport1
- containerPort: 8050 # containerport2
- containerPort: 8090 # containerport3
---
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: same-rule-in-different-directions
name: exposure-to-unknown-peers
namespace: hello-world
spec:
podSelector:
Expand Down