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

connlist implementing exposure analysis #296

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
813ee12
connlist implementing exposure analysis
shireenf-ibm Jan 16, 2024
d7542ba
Update pkg/netpol/connlist/connlist.go
shireenf-ibm Jan 17, 2024
fc3e814
fix rep. pod name
shireenf-ibm Jan 17, 2024
6a2215e
Update pkg/netpol/eval/exposure.go
shireenf-ibm Jan 17, 2024
5ab1fb5
Update pkg/netpol/connlist/exposed_peer.go
shireenf-ibm Jan 17, 2024
4b1aefb
Update pkg/netpol/connlist/exposed_peer.go
shireenf-ibm Jan 17, 2024
1b295dc
Update pkg/netpol/connlist/exposure_analysis.go
shireenf-ibm Jan 17, 2024
4237e81
Update pkg/netpol/connlist/exposure_analysis.go
shireenf-ibm Jan 17, 2024
59b0477
Merge branch 'connlist_implementations' of github.com:np-guard/netpol…
shireenf-ibm Jan 17, 2024
63e8f68
add func that updates the protected flag of a pod
shireenf-ibm Jan 17, 2024
bdf6d95
return error values
shireenf-ibm Jan 17, 2024
bb07eac
avoid fields dups among types
shireenf-ibm Jan 17, 2024
cd0743a
update func doc
shireenf-ibm Jan 17, 2024
b419627
getConnectionsBetweenPeers update doc + returns the exposureMap
shireenf-ibm Jan 17, 2024
995be16
move connection interface, avoid code dup, and compare conns using Co…
shireenf-ibm Jan 17, 2024
f979481
make the func an exposureMap func
shireenf-ibm Jan 17, 2024
f9745ba
fixing issue of same string in podsOwnerMap
shireenf-ibm Jan 17, 2024
c210b05
Update pkg/netpol/connlist/exposure_analysis.go
shireenf-ibm Jan 18, 2024
77fbee6
renaming Connection interface + move PortRange
shireenf-ibm Jan 18, 2024
d6c8101
struct embedding
shireenf-ibm Jan 18, 2024
9e154f0
using connectionSet internally + move the refinement to one iter at t…
shireenf-ibm Jan 18, 2024
3f3f080
Update pkg/netpol/connection/connection.go
shireenf-ibm Jan 18, 2024
53c4d72
Update pkg/netpol/connlist/exposure_analysis.go
shireenf-ibm Jan 18, 2024
2aeb9a8
rename AllConnections
shireenf-ibm Jan 18, 2024
4f451bf
verify conversion
shireenf-ibm Jan 18, 2024
36f370d
storing the maximum entire cluster connection
shireenf-ibm Jan 18, 2024
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
142 changes: 118 additions & 24 deletions pkg/netpol/connlist/connlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ type ConnlistAnalyzer struct {

// The new interface
// ConnlistFromResourceInfos returns the allowed-connections list from input slice of resource.Info objects,
// and the list of all workloads from the parsed resources
// the list of all workloads from the parsed resources,
// and list of exposed peers in the parsed resources and their potentially allowed connections
// if exposure-analysis option is on, otherwise nil
func (ca *ConnlistAnalyzer) ConnlistFromResourceInfos(info []*resource.Info) ([]Peer2PeerConnection, []Peer, []ExposedPeer, error) {
// convert resource.Info objects to k8s resources, filter irrelevant resources
objs, fpErrs := parser.ResourceInfoListToK8sObjectsList(info, ca.logger, ca.muteErrsAndWarns)
Expand All @@ -55,7 +57,7 @@ func (ca *ConnlistAnalyzer) ConnlistFromResourceInfos(info []*resource.Info) ([]
if err := ca.hasFatalError(); err != nil {
return nil, nil, nil, err
}
return []Peer2PeerConnection{}, []Peer{}, []ExposedPeer{}, nil
return []Peer2PeerConnection{}, []Peer{}, ca.emptyExposedListOrNil(), nil
}
return ca.connslistFromParsedResources(objs)
}
Expand All @@ -66,8 +68,10 @@ func (ca *ConnlistAnalyzer) copyFpErrs(fpErrs []parser.FileProcessingError) {
}
}

// ConnlistFromDirPath returns the allowed connections list from dir path containing k8s resources
// and list of all workloads from the parsed resources
// ConnlistFromDirPath returns the allowed connections list from dir path containing k8s resources,
// list of all workloads from the parsed resources,
// and list of exposed peers in the parsed resources and their potentially allowed connections
// if exposure-analysis option is on, otherwise nil
func (ca *ConnlistAnalyzer) ConnlistFromDirPath(dirPath string) ([]Peer2PeerConnection, []Peer, []ExposedPeer, error) {
rList, errs := fsscanner.GetResourceInfosFromDirPath([]string{dirPath}, true, ca.stopOnError)
// instead of parsing the builder's string error to decide on error type (warning/error/fatal-err)
Expand Down Expand Up @@ -120,13 +124,22 @@ func WithFocusWorkload(workload string) ConnlistAnalyzerOption {
}
}

// WithExposureAnalysis is a functional option to include exposure analysis
// WithExposureAnalysis is a functional option which directs ConnlistAnalyzer to perform exposure analysis
func WithExposureAnalysis() ConnlistAnalyzerOption {
return func(c *ConnlistAnalyzer) {
c.exposureAnalysis = true
}
}

// emptyExposedListOrNil returns an empty ExposedPeer list if the exposure-analysis option is true for
// the connlist analyzer, otherwise returns nil
func (ca *ConnlistAnalyzer) emptyExposedListOrNil() []ExposedPeer {
if ca.exposureAnalysis {
return []ExposedPeer{}
}
return nil
}

// WithOutputFormat is a functional option, allowing user to choose the output format txt/json/dot/csv/md.
func WithOutputFormat(outputFormat string) ConnlistAnalyzerOption {
return func(p *ConnlistAnalyzer) {
Expand Down Expand Up @@ -197,7 +210,9 @@ func (ca *ConnlistAnalyzer) connslistFromParsedResources(objectsList []parser.K8
return ca.getConnectionsList(pe, ia)
}

// ConnlistFromK8sCluster returns the allowed connections list from k8s cluster resources and a list of all peers names
// ConnlistFromK8sCluster returns the allowed connections list from k8s cluster resources, a list of all peers names,
// and list of exposed peers in the parsed resources and their potentially allowed connections
// if exposure-analysis option is on, otherwise nil
func (ca *ConnlistAnalyzer) ConnlistFromK8sCluster(clientset *kubernetes.Clientset) ([]Peer2PeerConnection, []Peer, []ExposedPeer, error) {
pe := eval.NewPolicyEngine(ca.exposureAnalysis)

Expand Down Expand Up @@ -353,21 +368,21 @@ func (ca *ConnlistAnalyzer) includePairOfWorkloads(src, dst eval.Peer) bool {
return ca.isPeerFocusWorkload(src) || ca.isPeerFocusWorkload(dst)
}

// TBD: should reveal the Fake pod flag in eval.Peer ?
// TODO : enhance this after implementing representative peers
func (ca *ConnlistAnalyzer) hasFakePodsAndIPs(src, dst eval.Peer) bool {
if src.IsPeerIPType() && dst.Name() == common.PodInExposedNs {
if src.IsPeerIPType() && dst.Name() == common.PodInRepNs {
return true
}
if src.Name() == common.PodInExposedNs && dst.IsPeerIPType() {
if src.Name() == common.PodInRepNs && dst.IsPeerIPType() {
return true
}
if src.Name() == common.PodInExposedNs && dst.Name() == common.PodInExposedNs {
if src.Name() == common.PodInRepNs && dst.Name() == common.PodInRepNs {
return true
}
if src.Name() == common.PodInExposedNs && dst.Name() == common.IngressPodName {
if src.Name() == common.PodInRepNs && dst.Name() == common.IngressPodName {
return true
}
if src.Name() == common.IngressPodName && dst.Name() == common.PodInExposedNs {
if src.Name() == common.IngressPodName && dst.Name() == common.PodInRepNs {
return true
}
return false
Expand All @@ -386,7 +401,7 @@ func (ca *ConnlistAnalyzer) getConnectionsList(pe *eval.PolicyEngine, ia *ingres
[]Peer, []ExposedPeer, error) {
connsRes := make([]Peer2PeerConnection, 0)
if !pe.HasPodPeers() {
return connsRes, []Peer{}, []ExposedPeer{}, nil
return connsRes, []Peer{}, ca.emptyExposedListOrNil(), nil
}

// get workload peers and ip blocks
Expand All @@ -412,15 +427,22 @@ func (ca *ConnlistAnalyzer) getConnectionsList(pe *eval.PolicyEngine, ia *ingres
}

// compute connections between peers based on pe analysis of network policies
peersAllowedConns, err := ca.getConnectionsBetweenPeers(pe, peers)
// if exposure-analysis is on, also compute the exposures-map
exposuresMap := exposureMap{}
adisos marked this conversation as resolved.
Show resolved Hide resolved
peersAllowedConns, err := ca.getConnectionsBetweenPeers(pe, peers, exposuresMap)
if err != nil {
ca.errors = append(ca.errors, newResourceEvaluationError(err))
return nil, nil, nil, err
}
connsRes = peersAllowedConns

exposedPeers := ca.emptyExposedListOrNil()
if ca.exposureAnalysis {
exposedPeers = buildExposedPeerListFromExposureMap(exposuresMap)
}

if excludeIngressAnalysis {
return connsRes, peers, []ExposedPeer{}, nil
return connsRes, peers, exposedPeers, nil
}

// analyze ingress connections - create connection objects for relevant ingress analyzer connections
Expand All @@ -435,7 +457,7 @@ func (ca *ConnlistAnalyzer) getConnectionsList(pe *eval.PolicyEngine, ia *ingres
ca.logWarning(netpolerrors.NoAllowedConnsWarning)
}

return connsRes, peers, []ExposedPeer{}, nil
return connsRes, peers, exposedPeers, nil
}

// existsFocusWorkload checks if the provided focus workload is ingress-controller
Expand All @@ -460,7 +482,8 @@ func (ca *ConnlistAnalyzer) existsFocusWorkload(peers []Peer, excludeIngressAnal
}

// getConnectionsBetweenPeers returns connections list from PolicyEngine object
adisos marked this conversation as resolved.
Show resolved Hide resolved
func (ca *ConnlistAnalyzer) getConnectionsBetweenPeers(pe *eval.PolicyEngine, peers []Peer) ([]Peer2PeerConnection, error) {
func (ca *ConnlistAnalyzer) getConnectionsBetweenPeers(pe *eval.PolicyEngine, peers []Peer,
expMap exposureMap) ([]Peer2PeerConnection, error) {
connsRes := make([]Peer2PeerConnection, 0)
for i := range peers {
srcPeer := peers[i]
Expand All @@ -477,16 +500,12 @@ func (ca *ConnlistAnalyzer) getConnectionsBetweenPeers(pe *eval.PolicyEngine, pe
if allowedConnections.IsEmpty() {
continue
}
p2pConnection := &connection{
src: srcPeer,
dst: dstPeer,
allConnections: allowedConnections.AllConnections(),
protocolsAndPorts: allowedConnections.ProtocolsAndPortsMap(),
p2pConnection := ca.checkIfP2PConnOrExposureConn(pe, allowedConnections, srcPeer, dstPeer, expMap)
if p2pConnection != nil {
connsRes = append(connsRes, p2pConnection)
}
connsRes = append(connsRes, p2pConnection)
}
}

return connsRes, nil
}

Expand Down Expand Up @@ -550,3 +569,78 @@ func (ca *ConnlistAnalyzer) logWarning(msg string) {
ca.logger.Warnf(msg)
}
}

// checkIfP2PConnOrExposureConn checks if the given connection is between two peers from the parsed resources, if yes returns it,
// otherwise the connection belongs to exposure-analysis, will be added to the provided map
func (ca *ConnlistAnalyzer) checkIfP2PConnOrExposureConn(pe *eval.PolicyEngine, allowedConnections common.Connection,
src, dst Peer, exposuresMap exposureMap) *connection {
if !ca.exposureAnalysis {
// if exposure analysis option is off , the connection is definitely a P2PConnection
return createConnectionObject(allowedConnections, src, dst)
}
// else exposure analysis is on
// TODO : enhance this if condition after implementing eval.RepresentativePeer
if src.Name() != common.PodInRepNs && dst.Name() != common.PodInRepNs {
// both src and dst are peers are found in the parsed resources
return createConnectionObject(allowedConnections, src, dst)
}
// else: one of the peers is inferred from a netpol-rule , and the other is a peer from the parsed resources
// an exposure analysis connection
if src.Name() != common.PodInRepNs {
// dst is the inferred from netpol peer, we have an exposed egress for the src peer
addConnToExposureMap(pe, allowedConnections, src, dst, false, exposuresMap)
} else {
// src is the inferred from netpol peer, we have an exposed ingress to the dst peer
addConnToExposureMap(pe, allowedConnections, src, dst, true, exposuresMap)
}
return nil
}

// helper function - returns a connection object from the given fields
func createConnectionObject(allowedConnections common.Connection, src, dst Peer) *connection {
return &connection{
src: src,
dst: dst,
allConnections: allowedConnections.AllConnections(),
protocolsAndPorts: allowedConnections.ProtocolsAndPortsMap(),
}
}

// helper function - adds a connection and its data to the exposure-analysis
shireenf-ibm marked this conversation as resolved.
Show resolved Hide resolved
func addConnToExposureMap(pe *eval.PolicyEngine, allowedConnections common.Connection, src, dst Peer, isIngress bool,
adisos marked this conversation as resolved.
Show resolved Hide resolved
exposuresMap exposureMap) {
peer := src // real peer
inferredPeer := dst // inferred from netpol rule
if isIngress {
peer = dst
inferredPeer = src
}
if _, ok := exposuresMap[peer]; !ok {
exposuresMap[peer] = &peerExposureData{
isIngressProtected: false,
isEgressProtected: false,
ingressExposure: make([]*xgressExposure, 0),
egressExposure: make([]*xgressExposure, 0),
}
}
if !pe.IsPeerProtected(peer, isIngress) {
return // if the peer is not protected, we don't need to store any connection data
}
// protected peer - store the data
expData := &xgressExposure{
exposedToEntireCluster: inferredPeer.Namespace() == common.AllNamespaces,
namespaceLabels: pe.GetPeerNsLabels(inferredPeer),
podLabels: map[string]string{}, // will be empty since in this branch rules with namespaceSelectors only supported
potentialConn: &common.AllowedConns{
AllConnections: allowedConnections.AllConnections(),
ProtocolsAndPortsMap: allowedConnections.ProtocolsAndPortsMap(),
},
}
if isIngress {
exposuresMap[peer].isIngressProtected = true
exposuresMap[peer].ingressExposure = append(exposuresMap[peer].ingressExposure, expData)
} else { // egress
exposuresMap[peer].isEgressProtected = true
exposuresMap[peer].egressExposure = append(exposuresMap[peer].egressExposure, expData)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of refining the list of ingressExposure/egressExposure only in a later stage, we could have here a map from "representative" pod name to its connection-set (instead of a list), and store in the map the larges connection-set object each time we assign in the same entry, so that this would prevent possible duplication that later has to be refined.

This requires changing the internal type from list to map , so should consider if we want to apply such change, in a next branch / sub task..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated a new sub-task

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which sub-task?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
13 changes: 10 additions & 3 deletions pkg/netpol/connlist/exposed_peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,18 @@ import "github.com/np-guard/netpol-analyzer/pkg/netpol/internal/common"
type ExposedPeer interface {
// ExposedPeer is a peer for which the analysis found some potential exposure info
ExposedPeer() Peer
// IsProtectedByIngressNetpols indicates if there are ingress netpols selecting the ExposedPeer
// if peer is not protected, indicates that the peer is exposed on ingress to the whole world
// if peer is not protected by ingress netpols, the IngressExposure list will be empty
// output wil display this detail
shireenf-ibm marked this conversation as resolved.
Show resolved Hide resolved
IsProtectedByIngressNetpols() bool
// IngressExposure is a list of the potential Ingress connections to the ExposedPeer
IngressExposure() []XgressExposureData
// IsProtectedByEgressNetpols indicates if there are egress netpols selecting the ExposedPeer
// if peer is not protected, indicates that the peer is exposed on egress to the whole world
// if peer is not protected by egress netpols, the EgressExposure list will be empty
// output wil display this detail
shireenf-ibm marked this conversation as resolved.
Show resolved Hide resolved
IsProtectedByEgressNetpols() bool
// EgressExposure is a list of the potential Egress connections from the ExposedPeer
EgressExposure() []XgressExposureData
}
Expand All @@ -17,9 +27,6 @@ type ExposedPeer interface {
// any pod with labels in any-namespace, or any pod with labels in a namespace with labels, or any pod with labels in a specific namespace
// TODO: add detailed documentation as to which combinations of values represent which kind of "abstract" node in the output
type XgressExposureData interface {
// IsProtectedByNetpols indicates if the exposed peer is protected by any netpol on Ingress/Egress
// if a peer is not protected by xgress netpols, it will be exposed to entire cluster with all allowed connections
IsProtectedByNetpols() bool
// IsExposedToEntireCluster indicates if the peer is exposed to all namespaces in the cluster for the relevant direction
IsExposedToEntireCluster() bool
// NamespaceLabels are matchLabels of potential namespaces which the peer might be exposed to
Expand Down
Loading
Loading