Skip to content

Commit 4649ace

Browse files
committed
feat(Netpol Assistant): data structures simulating connectivity matrix for ANP/BANP
1 parent 7a4cc74 commit 4649ace

23 files changed

+2410
-448
lines changed

cmd/cyclonus/README.md

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,16 @@
1-
# Cyclonus
1+
# NetworkPolicy Assistant (derived from Cyclonus)
22

3-
## Network policy explainer, prober, and test case generator
3+
Explains your configuration of (Baseline)AdminNetworkPolicy and v1 NetworkPolicy. Additionally, can test conformance of (B)ANP and v1 NetworkPolicy via a connectivity matrix. Derived from the great work of @mattfenwick et al. in [Cyclonus](https://github.com/mattfenwick/cyclonus).
44

5-
Parse, explain, and probe network policies to understand their implications and help design
6-
policies that suit your needs!
5+
More details here: [Cyclonus](https://github.com/mattfenwick/cyclonus).
76

8-
## Quickstart
7+
## Usage
98

10-
Users: check out our [Quickstart guide](./docs/quickstart.md)
9+
CLI currently under development. Will build off of `cyclonus analyze` (visualization) and `cyclonus generate` (conformance tests).
1110

12-
Developers: check out our [Developer guide](./docs/developer-guide.md)
11+
## Development
1312

14-
Cyclonus functionality:
13+
Integration tests located at *test/integration/integration_test.go*. The tests verify:
1514

16-
- [run a single network policy test on a cluster](./docs/probe.md)
17-
- [run network policy conformance tests on a cluster](./docs/generator.md)
18-
- [understand test runs](./docs/test-runs.md)
19-
- [analyze network policies](./docs/analyze.md)
20-
21-
22-
## Integrations
23-
24-
Cyclonus is available as a [**krew/kubectl plugin**](https://github.com/mattfenwick/kubectl-cyclonus):
25-
26-
- [Set up krew](https://krew.sigs.k8s.io/docs/user-guide/quickstart/)
27-
- install: `kubectl krew install cyclonus`
28-
- use: `kubectl cyclonus -h`
29-
30-
**Antrea testing**: [Cyclonus runs network policy tests for Antrea on a daily basis](https://github.com/vmware-tanzu/antrea/actions/workflows/netpol_cyclonus.yml).
31-
32-
**Cilium testing**: [Cyclonus runs network policy tests for Cilium on a daily basis](https://github.com/cilium/cilium/pull/14889).
33-
34-
**Sonobuoy plugin**: [run Cyclonus tests through Sonobuoy](./hack/sonobuoy).
35-
36-
37-
## Motivation and History
38-
39-
Testing network policies for CNI providers on Kubernetes has historically been very difficult, requiring a lot of boiler plate.
40-
This was recently improved upstream via truth table based tests
41-
([see KEP](https://github.com/kubernetes/enhancements/tree/master/keps/sig-network/1611-network-policy-validation)).
42-
Cyclonus is the next evolution of the truth table tests which are part of upstream Kubernetes.
43-
Cyclonus generates hundreds of network policies, their connectivity tables, and outputs results in the same, easy to read format.
44-
45-
## Thanks to contributors
46-
47-
- @dougsland
48-
- @jayunit100
49-
- @johnSchnake
50-
- @enhaocui
51-
- @matmerr
15+
1. Building/translating NetPol spec into interim data structures (matchers).
16+
2. Simulation of expected connectivity for ANP, BANP, and v1 NetPols.

cmd/cyclonus/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ require (
1212
github.com/pkg/errors v0.9.1
1313
github.com/sirupsen/logrus v1.9.3
1414
github.com/spf13/cobra v1.7.0
15+
github.com/stretchr/testify v1.8.2
1516
golang.org/x/exp v0.0.0-20220706164943-b4a6d9510983
1617
k8s.io/api v0.28.1
1718
k8s.io/apimachinery v0.28.1
1819
k8s.io/client-go v0.28.1
20+
sigs.k8s.io/network-policy-api v0.1.1
1921
sigs.k8s.io/yaml v1.3.0
2022
)
2123

@@ -44,6 +46,7 @@ require (
4446
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4547
github.com/modern-go/reflect2 v1.0.2 // indirect
4648
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
49+
github.com/pmezard/go-difflib v1.0.0 // indirect
4750
github.com/spf13/pflag v1.0.5 // indirect
4851
golang.org/x/net v0.14.0 // indirect
4952
golang.org/x/oauth2 v0.8.0 // indirect

cmd/cyclonus/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrC
193193
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
194194
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
195195
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
196+
sigs.k8s.io/network-policy-api v0.1.1 h1:KDW+AkvCCQI3h8yH8j0hurhvPLNtLeVvmZoqtMaG9ew=
197+
sigs.k8s.io/network-policy-api v0.1.1/go.mod h1:F7S5fsb7QEzlLjuMgTGfUT4LRHylRbx2xDDpHfJKKEs=
196198
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
197199
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
198200
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=

cmd/cyclonus/pkg/cli/analyze.go

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import (
55
"strings"
66

77
"github.com/mattfenwick/collections/pkg/json"
8-
"github.com/mattfenwick/collections/pkg/set"
98
"github.com/mattfenwick/cyclonus/pkg/connectivity/probe"
109
"github.com/mattfenwick/cyclonus/pkg/generator"
11-
"github.com/mattfenwick/cyclonus/pkg/linter"
1210

1311
"github.com/mattfenwick/cyclonus/pkg/kube"
1412
"github.com/mattfenwick/cyclonus/pkg/kube/netpol"
@@ -25,7 +23,6 @@ import (
2523
const (
2624
ParseMode = "parse"
2725
ExplainMode = "explain"
28-
LintMode = "lint"
2926
QueryTrafficMode = "query-traffic"
3027
QueryTargetMode = "query-target"
3128
ProbeMode = "probe"
@@ -34,7 +31,6 @@ const (
3431
var AllModes = []string{
3532
ParseMode,
3633
ExplainMode,
37-
LintMode,
3834
QueryTrafficMode,
3935
QueryTargetMode,
4036
ProbeMode,
@@ -135,9 +131,6 @@ func RunAnalyzeCommand(args *AnalyzeArgs) {
135131
case ExplainMode:
136132
fmt.Println("explained policies:")
137133
ExplainPolicies(policies)
138-
case LintMode:
139-
fmt.Println("policy lint:")
140-
Lint(kubePolicies)
141134
case QueryTargetMode:
142135
pods := make([]*QueryTargetPod, len(kubePods))
143136
for i, p := range kubePods {
@@ -168,11 +161,6 @@ func ExplainPolicies(explainedPolicies *matcher.Policy) {
168161
fmt.Printf("%s\n", explainedPolicies.ExplainTable())
169162
}
170163

171-
func Lint(kubePolicies []*networkingv1.NetworkPolicy) {
172-
warnings := linter.Lint(kubePolicies, set.FromSlice[linter.Check](nil))
173-
fmt.Println(linter.WarningsTable(warnings))
174-
}
175-
176164
// QueryTargetPod matches targets; targets exist in only a single namespace and can't be matched by namespace
177165
//
178166
// label, therefore we match by exact namespace and by pod labels.
@@ -199,10 +187,14 @@ func QueryTargets(explainedPolicies *matcher.Policy, podPath string, pods []*Que
199187
}
200188

201189
func QueryTargetHelper(policies *matcher.Policy, pod *QueryTargetPod) (*matcher.Policy, *matcher.Policy) {
202-
ingressTargets := policies.TargetsApplyingToPod(true, pod.Namespace, pod.Labels)
190+
podInfo := &matcher.InternalPeer{
191+
Namespace: pod.Namespace,
192+
PodLabels: pod.Labels,
193+
}
194+
ingressTargets := policies.TargetsApplyingToPod(true, podInfo)
203195
combinedIngressTarget := matcher.CombineTargetsIgnoringPrimaryKey(pod.Namespace, metav1.LabelSelector{MatchLabels: pod.Labels}, ingressTargets)
204196

205-
egressTargets := policies.TargetsApplyingToPod(false, pod.Namespace, pod.Labels)
197+
egressTargets := policies.TargetsApplyingToPod(false, podInfo)
206198
combinedEgressTarget := matcher.CombineTargetsIgnoringPrimaryKey(pod.Namespace, metav1.LabelSelector{MatchLabels: pod.Labels}, egressTargets)
207199

208200
var combinedIngresses []*matcher.Target

cmd/cyclonus/pkg/connectivity/probe/resources.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ type Resources struct {
1616
Namespaces map[string]map[string]string
1717
Pods []*Pod
1818
//ExternalIPs []string
19+
ports []int
20+
protocols []v1.Protocol
1921
}
2022

2123
func NewDefaultResources(kubernetes kube.IKubernetes, namespaces []string, podNames []string, ports []int, protocols []v1.Protocol, externalIPs []string, podCreationTimeoutSeconds int, batchJobs bool, imageRegistry string) (*Resources, error) {
@@ -24,6 +26,8 @@ func NewDefaultResources(kubernetes kube.IKubernetes, namespaces []string, podNa
2426
r := &Resources{
2527
Namespaces: map[string]map[string]string{},
2628
//ExternalIPs: externalIPs,
29+
ports: ports,
30+
protocols: protocols,
2731
}
2832

2933
for _, ns := range namespaces {

cmd/cyclonus/pkg/connectivity/probe/table.go

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package probe
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/mattfenwick/collections/pkg/slice"
58
"github.com/mattfenwick/cyclonus/pkg/utils"
69
"github.com/pkg/errors"
710
"golang.org/x/exp/maps"
8-
"strings"
11+
v1 "k8s.io/api/core/v1"
912
)
1013

1114
type Item struct {
@@ -26,6 +29,73 @@ type Table struct {
2629
Wrapped *TruthTable
2730
}
2831

32+
func NewTableWithDefaultConnectivity(r *Resources, ingress, egress Connectivity) *Table {
33+
return &Table{Wrapped: NewTruthTableFromItems(r.SortedPodNames(), func(fr, to string) interface{} {
34+
results := make(map[string]*JobResult, len(r.ports)*len(r.protocols))
35+
for _, proto := range r.protocols {
36+
for _, port := range r.ports {
37+
jr := &JobResult{
38+
Job: &Job{
39+
FromKey: fr,
40+
ToKey: to,
41+
ResolvedPort: port,
42+
ResolvedPortName: "",
43+
Protocol: proto,
44+
TimeoutSeconds: 3,
45+
},
46+
Ingress: &ingress,
47+
Egress: &egress,
48+
}
49+
50+
setCombined(jr)
51+
52+
k := fmt.Sprintf("%s/%d", proto, port)
53+
results[k] = jr
54+
}
55+
}
56+
57+
return &Item{
58+
From: fr,
59+
To: to,
60+
JobResults: results,
61+
}
62+
})}
63+
}
64+
65+
// SetEgress should be used to set connectivity for tables with connectivity already specified via NewTableWithDefaultConnectivity()
66+
func (t *Table) SetEgress(egress Connectivity, from, to string, port int, proto v1.Protocol) {
67+
portProto := fmt.Sprintf("%s/%d", proto, port)
68+
jr, ok := t.Get(from, to).JobResults[portProto]
69+
if !ok || jr.Ingress == nil || jr.Egress == nil {
70+
panic(errors.Errorf("cannot set connectivity: job result non-existent/invalid for %s/%d", proto, port))
71+
}
72+
73+
jr.Egress = &egress
74+
setCombined(jr)
75+
}
76+
77+
// SetIngress should be used to set connectivity for tables with connectivity already specified via NewTableWithDefaultConnectivity()
78+
func (t *Table) SetIngress(ingress Connectivity, from, to string, port int, proto v1.Protocol) {
79+
portProto := fmt.Sprintf("%s/%d", proto, port)
80+
jr, ok := t.Get(from, to).JobResults[portProto]
81+
if !ok || jr.Ingress == nil || jr.Egress == nil {
82+
panic(errors.Errorf("cannot set connectivity: job result non-existent/invalid for %s/%d", proto, port))
83+
}
84+
85+
jr.Ingress = &ingress
86+
setCombined(jr)
87+
}
88+
89+
func setCombined(jr *JobResult) {
90+
if *jr.Ingress == ConnectivityBlocked || *jr.Egress == ConnectivityBlocked {
91+
jr.Combined = ConnectivityBlocked
92+
}
93+
94+
if *jr.Ingress == ConnectivityAllowed && *jr.Egress == ConnectivityAllowed {
95+
jr.Combined = ConnectivityAllowed
96+
}
97+
}
98+
2999
func NewTable(items []string) *Table {
30100
return &Table{Wrapped: NewTruthTableFromItems(items, func(fr, to string) interface{} {
31101
return &Item{

cmd/cyclonus/pkg/connectivity/stepresult.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package connectivity
33
import (
44
"github.com/mattfenwick/cyclonus/pkg/connectivity/probe"
55
"github.com/mattfenwick/cyclonus/pkg/matcher"
6+
"sigs.k8s.io/network-policy-api/apis/v1alpha1"
7+
68
networkingv1 "k8s.io/api/networking/v1"
79
)
810

@@ -11,7 +13,10 @@ type StepResult struct {
1113
KubeProbes []*probe.Table
1214
Policy *matcher.Policy
1315
KubePolicies []*networkingv1.NetworkPolicy
14-
comparisons []*ComparisonTable
16+
// FIXME use ANP and BANP here
17+
ANPs []*v1alpha1.AdminNetworkPolicy
18+
BANP *v1alpha1.BaselineAdminNetworkPolicy
19+
comparisons []*ComparisonTable
1520
}
1621

1722
func NewStepResult(simulated *probe.Table, policy *matcher.Policy, kubePolicies []*networkingv1.NetworkPolicy) *StepResult {

cmd/cyclonus/pkg/kube/labelselector.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package kube
22

33
import (
44
"fmt"
5+
"strings"
6+
57
"github.com/mattfenwick/collections/pkg/slice"
68
"github.com/mattfenwick/cyclonus/pkg/utils"
79
"golang.org/x/exp/maps"
810
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9-
"strings"
1011
)
1112

1213
// IsNameMatch follows the kube pattern of "empty string means matches All"
@@ -102,7 +103,7 @@ func SerializeLabelSelector(ls metav1.LabelSelector) string {
102103

103104
func LabelSelectorTableLines(selector metav1.LabelSelector) string {
104105
if IsLabelSelectorEmpty(selector) {
105-
return "all pods"
106+
return "all"
106107
}
107108
var lines []string
108109
if len(selector.MatchLabels) > 0 {

cmd/cyclonus/pkg/kube/networkpolicy.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package kube
22

33
import (
44
"fmt"
5+
"strings"
6+
57
"github.com/olekukonko/tablewriter"
68
. "k8s.io/api/networking/v1"
79
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8-
"strings"
910
)
1011

1112
func NetworkPoliciesToTable(policies []*NetworkPolicy) string {
@@ -20,6 +21,9 @@ func NetworkPoliciesToTable(policies []*NetworkPolicy) string {
2021
name := fmt.Sprintf("%s/%s", policy.Namespace, policy.Name)
2122
//target := SerializeLabelSelector(policy.Spec.PodSelector)
2223
target := LabelSelectorTableLines(policy.Spec.PodSelector)
24+
if target == "all" {
25+
target = "all pods"
26+
}
2327

2428
for _, policyType := range policy.Spec.PolicyTypes {
2529
if policyType == PolicyTypeIngress {

0 commit comments

Comments
 (0)