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

initial exposure analysis #293

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 13 additions & 0 deletions pkg/netpol/connlist/connlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"github.com/np-guard/netpol-analyzer/pkg/logger"
"github.com/np-guard/netpol-analyzer/pkg/manifests/fsscanner"
"github.com/np-guard/netpol-analyzer/pkg/manifests/parser"
"github.com/np-guard/netpol-analyzer/pkg/netpol/connlist/internal/exposureanalysis"
"github.com/np-guard/netpol-analyzer/pkg/netpol/connlist/internal/ingressanalyzer"
"github.com/np-guard/netpol-analyzer/pkg/netpol/eval"
"github.com/np-guard/netpol-analyzer/pkg/netpol/internal/common"
Expand Down Expand Up @@ -385,6 +386,18 @@
}
connsRes = peersAllowedConns

//////////////////////
// exposure analysis
if ca.focusWorkload == "" {
// TODO: get results from this, and add to return values/ write in form of connsRes
err = exposureanalysis.GetPotentialAllowedConnections(pe, peerList)
if err != nil {
// ca.errors = append(ca.errors, newResourceEvaluationError(err))

Check failure on line 395 in pkg/netpol/connlist/connlist.go

View workflow job for this annotation

GitHub Actions / golangci-lint

commentedOutCode: may want to remove commented-out code (gocritic)
return nil, nil, err
}
}
////////////////////

if excludeIngressAnalysis {
return connsRes, peers, nil
}
Expand Down
101 changes: 101 additions & 0 deletions pkg/netpol/connlist/internal/exposureanalysis/exposure_analysis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package exposureanalysis

import (
"fmt"

"github.com/np-guard/netpol-analyzer/pkg/netpol/eval"
"github.com/np-guard/netpol-analyzer/pkg/netpol/internal/common"
)

type xgressExposure struct {
protected bool
// entire namespace is exposed
namespacesExposed []string // TODO: add conns which the namespace exposed on (replace with map[string]common.Connection)

// podsExposed (TODO: to adds pods exposed in next PRs)
}

type exposureInfo struct {
ingressExposure xgressExposure
egressExposure xgressExposure
}

// in next PRs - return the potential conns (to append to output results)
func GetPotentialAllowedConnections(pe *eval.PolicyEngine, peers []eval.Peer) error {
res := map[eval.Peer]exposureInfo{} // map from peer to its exposure info
for _, peer := range peers {
if peer.IsPeerIPType() {
continue
}
// get potentially ingress exposed
ingressEx := xgressExposure{}
captured, namespaces, err := pe.GetPeerPotentiallyAllowedConns(peer, true)
if err != nil {
return err
}
if !captured {
ingressEx.protected = false
ingressEx.namespacesExposed = nil
} else {
ingressEx.protected = true
ingressEx.namespacesExposed = namespaces
}

// egress potentially
egressEx := xgressExposure{}
captured, namespaces, err = pe.GetPeerPotentiallyAllowedConns(peer, false)
if err != nil {
return err
}
// TODO : avoid code dup
if !captured {
egressEx.protected = false
egressEx.namespacesExposed = nil
} else {
egressEx.protected = true
egressEx.namespacesExposed = namespaces
}

res[peer] = exposureInfo{ingressExposure: ingressEx, egressExposure: egressEx}
}

printRes(res)
return nil
}

func printRes(exposureAnalysisRes map[eval.Peer]exposureInfo) {
fmt.Printf("\n EXPOSURE ANALYSIS: \n")
for peer, exposureDetails := range exposureAnalysisRes {
if !exposureDetails.ingressExposure.protected && !exposureDetails.egressExposure.protected {
fmt.Printf("%q : is not protected in the cluster\n", peer.String())
continue
}
if !exposureDetails.ingressExposure.protected {
fmt.Printf("%q : is not protected on Ingress\n", peer.String())
}
if !exposureDetails.egressExposure.protected {
fmt.Printf("%q : is not protected on Egress\n", peer.String())
}
if len(exposureDetails.ingressExposure.namespacesExposed) > 0 {
fmt.Printf("%q : is exposed on Ingress from:\n", peer.String())
for i := range exposureDetails.ingressExposure.namespacesExposed {
if exposureDetails.ingressExposure.namespacesExposed[i] == common.AllNamespaces {
fmt.Printf("* %s\n", common.AllNamespaces)
} else {
fmt.Printf("* any namespace with selector/s: %q \n", exposureDetails.ingressExposure.namespacesExposed[i])
}
}
}
if len(exposureDetails.egressExposure.namespacesExposed) > 0 {
fmt.Printf("%q : is exposed on Egress to:\n", peer.String())
for i := range exposureDetails.egressExposure.namespacesExposed {
if exposureDetails.egressExposure.namespacesExposed[i] == common.AllNamespaces {
fmt.Printf("* %s\n", common.AllNamespaces)

Check warning on line 93 in pkg/netpol/connlist/internal/exposureanalysis/exposure_analysis.go

View workflow job for this annotation

GitHub Actions / golangci-lint

add-constant: string literal "* %s\n" appears, at least, 2 times, create a named constant for it (revive)
} else {
fmt.Printf("* any namespace with selector/s : %q \n", exposureDetails.egressExposure.namespacesExposed[i])
}
}
}
}
fmt.Printf("\n")
}
41 changes: 41 additions & 0 deletions pkg/netpol/eval/exposure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package eval

import (
netv1 "k8s.io/api/networking/v1"

"github.com/np-guard/netpol-analyzer/pkg/netpol/eval/internal/k8s"
)

func (pe *PolicyEngine) GetPeerPotentiallyAllowedConns(checkedPeer Peer, isIngress bool) (captured bool,
netpolsExposed []string, err error) {
checkedPodPeer, err := pe.convertWorkloadPeerToPodPeer(checkedPeer)
if err != nil {
return false, nil, err
}

policyType := netv1.PolicyTypeIngress
if !isIngress {
policyType = netv1.PolicyTypeEgress
}

netpols, err := pe.getPoliciesSelectingPod(checkedPodPeer.GetPeerPod(), policyType)
if err != nil {
return false, nil, err
}

if len(netpols) == 0 {
return false, nil, nil
}
for _, policy := range netpols {
netpolsExposed = append(netpolsExposed, getPotentiallyExposedNamespaces(policy, isIngress)...)
}

return true, netpolsExposed, nil
}

func getPotentiallyExposedNamespaces(policy *k8s.NetworkPolicy, isIngress bool) []string {
if isIngress {
return policy.GetPotentialExposedNamespacesForIngress()
}
return policy.GetPotentialExposedNamespacesForEgress()
}
56 changes: 56 additions & 0 deletions pkg/netpol/eval/internal/k8s/netpol_exposure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package k8s

import (
netv1 "k8s.io/api/networking/v1"

"github.com/np-guard/netpol-analyzer/pkg/netpol/internal/common"
)

// checks rules with only namespaceSelector
func (np *NetworkPolicy) GetPotentialExposedNamespacesForIngress() []string {
// nsToConns := make(map[string]*common.ConnectionSet, 0)

Check failure on line 11 in pkg/netpol/eval/internal/k8s/netpol_exposure.go

View workflow job for this annotation

GitHub Actions / golangci-lint

commentedOutCode: may want to remove commented-out code (gocritic)
namespacesExposed := make([]string, 0)
for _, rule := range np.Spec.Ingress {
ruleFrom := rule.From
// rulePorts := rule.Ports // TODO: add on what ports (conns) the namespace is exposed
namespacesExposed = append(namespacesExposed, np.getNamespacesSelectedByRule(ruleFrom)...)
}

return namespacesExposed
}

func (np *NetworkPolicy) GetPotentialExposedNamespacesForEgress() []string {
namespacesExposed := make([]string, 0)
for _, rule := range np.Spec.Egress {
ruleTo := rule.To
namespacesExposed = append(namespacesExposed, np.getNamespacesSelectedByRule(ruleTo)...)
}

return namespacesExposed
}

func (np *NetworkPolicy) getNamespacesSelectedByRule(rulePeers []netv1.NetworkPolicyPeer) []string {
res := make([]string, 0)
if len(rulePeers) == 0 { // allow all ingress
res = append(res, common.AllNamespaces)
return res
}
for i := range rulePeers { // assumes all rules are good (since connlist analysis already returned errors)
if rulePeers[i].IPBlock != nil {
continue
}
if rulePeers[i].PodSelector != nil {
continue
}
// rule contains only namespaceSelector
selector, _ := np.parseNetpolLabelSelector(rulePeers[i].NamespaceSelector)
selectorStr := selector.String()
if selectorStr == "" {
res = append(res, common.AllNamespaces)
} else {
res = append(res, selector.String())
}
}

return res
}
2 changes: 2 additions & 0 deletions pkg/netpol/internal/common/netpol_commands_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type NetpolError interface {
Location() string
}

const AllNamespaces = "any namespace"

// Ingress Controller const - the name and namespace of an ingress-controller pod
const (
// The actual ingress controller pod is usually unknown and not available in the input resources for the analysis.
Expand Down
32 changes: 32 additions & 0 deletions tests/allow-all-test/namespace_and_deployments.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
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:
replicas: 2
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
---
16 changes: 16 additions & 0 deletions tests/allow-all-test/netpol.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-hello-world-b-to-a-app
namespace: hello-world
spec:
podSelector:
matchLabels:
app: a-app
policyTypes:
- Ingress
- Egress
ingress:
- {}
egress:
- {}
32 changes: 32 additions & 0 deletions tests/deny-all-test/namespace_and_deployments.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
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:
replicas: 2
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
---
12 changes: 12 additions & 0 deletions tests/deny-all-test/netpol.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: allow-hello-world-b-to-a-app
namespace: hello-world
spec:
podSelector:
matchLabels:
app: a-app
policyTypes:
- Ingress
- Egress
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
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:
replicas: 2
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
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: workload-b
namespace: hello-world
labels:
app: b-app
spec:
replicas: 2
selector:
matchLabels:
app: b-app
template:
metadata:
labels:
app: b-app
spec:
containers:
- name: hello-world
image: quay.io/shfa/hello-world:latest
ports:
- containerPort: 8050
Loading
Loading