diff --git a/cmd/policy-assistant/anps/anp-list.yaml b/cmd/policy-assistant/anps/anp-list.yaml new file mode 100644 index 00000000..0e7271d3 --- /dev/null +++ b/cmd/policy-assistant/anps/anp-list.yaml @@ -0,0 +1,39 @@ +apiVersion: policy.networking.k8s.io/v1alpha1 +kind: AdminNetworkPolicyList +Items: + - apiVersion: policy.networking.k8s.io/v1alpha1 + kind: AdminNetworkPolicy + metadata: + name: egress-sctp + spec: + priority: 3 + subject: + namespaces: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-ravenclaw + egress: + - name: "allow-to-gryffindor-everything" + action: "Allow" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-gryffindor + - apiVersion: policy.networking.k8s.io/v1alpha1 + kind: AdminNetworkPolicy + metadata: + name: gress-rules + spec: + priority: 15 + subject: + namespaces: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-gryffindor + egress: + - name: "allow-to-ravenclaw-everything" + action: "Allow" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-ravenclaw \ No newline at end of file diff --git a/cmd/policy-assistant/anps/anp.yaml b/cmd/policy-assistant/anps/anp.yaml new file mode 100644 index 00000000..2ada0fd5 --- /dev/null +++ b/cmd/policy-assistant/anps/anp.yaml @@ -0,0 +1,72 @@ +apiVersion: policy.networking.k8s.io/v1alpha1 +kind: AdminNetworkPolicy +metadata: + name: egress-sctp +spec: + priority: 8 + subject: + namespaces: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-ravenclaw + egress: + - name: "allow-to-gryffindor-everything" + action: "Allow" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-gryffindor + - name: "deny-to-gryffindor-everything" + action: "Deny" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-gryffindor + - name: "pass-to-gryffindor-everything" + action: "Pass" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-gryffindor + - name: "deny-to-slytherin-at-port-9003" + action: "Deny" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-slytherin + ports: + - portNumber: + protocol: SCTP + port: 9003 + - name: "pass-to-slytherin-at-port-9003" + action: "Pass" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-slytherin + ports: + - portNumber: + protocol: SCTP + port: 9003 + - name: "allow-to-hufflepuff-at-port-9003" + action: "Allow" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-hufflepuff + ports: + - portNumber: + protocol: SCTP + port: 9003 + - name: "deny-to-hufflepuff-everything-else" + action: "Deny" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-hufflepuff diff --git a/cmd/policy-assistant/banp/banp.yaml b/cmd/policy-assistant/banp/banp.yaml new file mode 100644 index 00000000..2fd7b6d6 --- /dev/null +++ b/cmd/policy-assistant/banp/banp.yaml @@ -0,0 +1,53 @@ +apiVersion: policy.networking.k8s.io/v1alpha1 +kind: BaselineAdminNetworkPolicy +metadata: + name: default +spec: + subject: + namespaces: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-ravenclaw + egress: + - name: "allow-to-gryffindor-everything" + action: "Allow" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-gryffindor + - name: "deny-to-gryffindor-everything" + action: "Deny" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-gryffindor + - name: "deny-to-slytherin-at-port-9003" + action: "Deny" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-slytherin + ports: + - portNumber: + protocol: SCTP + port: 9003 + - name: "allow-to-hufflepuff-at-port-9003" + action: "Allow" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-hufflepuff + ports: + - portNumber: + protocol: SCTP + port: 9003 + - name: "deny-to-hufflepuff-everything-else" + action: "Deny" + to: + - namespaces: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: network-policy-conformance-hufflepuff diff --git a/cmd/policy-assistant/pkg/cli/analyze.go b/cmd/policy-assistant/pkg/cli/analyze.go index 1ec59156..8db87b84 100644 --- a/cmd/policy-assistant/pkg/cli/analyze.go +++ b/cmd/policy-assistant/pkg/cli/analyze.go @@ -4,8 +4,10 @@ import ( "fmt" "github.com/mattfenwick/cyclonus/examples" "github.com/mattfenwick/cyclonus/pkg/kube/netpol" + "golang.org/x/net/context" "sigs.k8s.io/network-policy-api/apis/v1alpha1" "strings" + "time" "github.com/mattfenwick/collections/pkg/json" "github.com/mattfenwick/cyclonus/pkg/connectivity/probe" @@ -93,6 +95,7 @@ func RunAnalyzeCommand(args *AnalyzeArgs) { var kubeBANPs *v1alpha1.BaselineAdminNetworkPolicy var kubePods []v1.Pod var kubeNamespaces []v1.Namespace + var netErr, anpErr, banpErr error if args.AllNamespaces || len(args.Namespaces) > 0 { kubeClient, err := kube.NewKubernetesForContext(args.Context) utils.DoOrDie(err) @@ -104,27 +107,37 @@ func RunAnalyzeCommand(args *AnalyzeArgs) { kubeNamespaces = nsList.Items namespaces = []string{v1.NamespaceAll} } - kubePolicies, err = kube.ReadNetworkPoliciesFromKube(kubeClient, namespaces) - if err != nil { + + //TODO: add a flag for the timeout + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + kubePolicies, kubeANPs, kubeBANPs, netErr, anpErr, banpErr = kube.ReadNetworkPoliciesFromKube(ctx, kubeClient, namespaces) + + if netErr != nil { logrus.Errorf("unable to read network policies from kube, ns '%s': %+v", namespaces, err) } - kubePods, err = kube.GetPodsInNamespaces(kubeClient, namespaces) - if err != nil { - logrus.Errorf("unable to read pods from kube, ns '%s': %+v", namespaces, err) + if anpErr != nil { + fmt.Printf("Unable to fetch admin network policies: %s \n", anpErr) + } + if banpErr != nil { + fmt.Printf("Unable to fetch base admin network policies: %s \n", banpErr) } } // 2. read policies from file if args.PolicyPath != "" { - policiesFromPath, err := kube.ReadNetworkPoliciesFromPath(args.PolicyPath) + policiesFromPath, anpsFromPath, banpFromPath, err := kube.ReadNetworkPoliciesFromPath(args.PolicyPath) utils.DoOrDie(err) kubePolicies = append(kubePolicies, policiesFromPath...) + kubeANPs = append(kubeANPs, anpsFromPath...) + kubeBANPs = banpFromPath } // 3. read example policies if args.UseExamplePolicies { kubePolicies = append(kubePolicies, netpol.AllExamples...) - kubeANPs = examples.CoreGressRulesCombinedANB - kubeBANPs = examples.CoreGressRulesCombinedBANB + kubeANPs = append(kubeANPs, examples.CoreGressRulesCombinedANB...) + kubeBANPs = kubeBANPs } logrus.Debugf("parsed policies:\n%s", json.MustMarshalToString(kubePolicies)) diff --git a/cmd/policy-assistant/pkg/connectivity/testcasestate.go b/cmd/policy-assistant/pkg/connectivity/testcasestate.go index 9696a050..391f07e1 100644 --- a/cmd/policy-assistant/pkg/connectivity/testcasestate.go +++ b/cmd/policy-assistant/pkg/connectivity/testcasestate.go @@ -1,6 +1,7 @@ package connectivity import ( + "context" "time" "github.com/mattfenwick/cyclonus/pkg/connectivity/probe" @@ -140,7 +141,7 @@ func (t *TestCaseState) DeletePod(ns string, pod string) error { } func (t *TestCaseState) ReadPolicies(namespaces []string) error { - policies, err := kube.GetNetworkPoliciesInNamespaces(t.Kubernetes, namespaces) + policies, err := kube.GetNetworkPoliciesInNamespaces(context.TODO(), t.Kubernetes, namespaces) if err != nil { return err } @@ -322,7 +323,7 @@ func (t *TestCaseState) VerifyClusterState() error { return err } - policies, err := kube.GetNetworkPoliciesInNamespaces(t.Kubernetes, t.Resources.NamespacesSlice()) + policies, err := kube.GetNetworkPoliciesInNamespaces(context.TODO(), t.Kubernetes, t.Resources.NamespacesSlice()) if err != nil { return err } diff --git a/cmd/policy-assistant/pkg/kube/ikubernetes.go b/cmd/policy-assistant/pkg/kube/ikubernetes.go index 497277ab..ccf5ecf0 100644 --- a/cmd/policy-assistant/pkg/kube/ikubernetes.go +++ b/cmd/policy-assistant/pkg/kube/ikubernetes.go @@ -1,6 +1,7 @@ package kube import ( + "context" "fmt" "github.com/mattfenwick/cyclonus/pkg/utils" "github.com/pkg/errors" @@ -8,6 +9,7 @@ import ( networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "math/rand" + v1alpha12 "sigs.k8s.io/network-policy-api/apis/v1alpha1" ) type IKubernetes interface { @@ -18,7 +20,7 @@ type IKubernetes interface { GetAllNamespaces() (*v1.NamespaceList, error) CreateNetworkPolicy(kubePolicy *networkingv1.NetworkPolicy) (*networkingv1.NetworkPolicy, error) - GetNetworkPoliciesInNamespace(namespace string) ([]networkingv1.NetworkPolicy, error) + GetNetworkPoliciesInNamespace(ctx context.Context, namespace string) ([]networkingv1.NetworkPolicy, error) UpdateNetworkPolicy(kubePolicy *networkingv1.NetworkPolicy) (*networkingv1.NetworkPolicy, error) DeleteNetworkPolicy(namespace string, name string) error DeleteAllNetworkPoliciesInNamespace(namespace string) error @@ -28,6 +30,9 @@ type IKubernetes interface { DeleteService(namespace string, name string) error GetServicesInNamespace(namespace string) ([]v1.Service, error) + GetAdminNetworkPoliciesInNamespace(ctx context.Context) ([]v1alpha12.AdminNetworkPolicy, error) + GetBaseAdminNetworkPoliciesInNamespace(ctx context.Context) (v1alpha12.BaselineAdminNetworkPolicy, error) + CreatePod(kubePod *v1.Pod) (*v1.Pod, error) GetPod(namespace string, pod string) (*v1.Pod, error) DeletePod(namespace string, pod string) error @@ -37,10 +42,10 @@ type IKubernetes interface { ExecuteRemoteCommand(namespace string, pod string, container string, command []string) (string, string, error, error) } -func GetNetworkPoliciesInNamespaces(kubernetes IKubernetes, namespaces []string) ([]networkingv1.NetworkPolicy, error) { +func GetNetworkPoliciesInNamespaces(ctx context.Context, kubernetes IKubernetes, namespaces []string) ([]networkingv1.NetworkPolicy, error) { var allNetpols []networkingv1.NetworkPolicy for _, ns := range namespaces { - netpols, err := kubernetes.GetNetworkPoliciesInNamespace(ns) + netpols, err := kubernetes.GetNetworkPoliciesInNamespace(ctx, ns) if err != nil { return nil, err } @@ -83,6 +88,14 @@ func GetServicesInNamespaces(kubernetes IKubernetes, namespaces []string) ([]v1. return allServices, nil } +func GetAdminNetworkPoliciesInNamespaces(ctx context.Context, kubernetes IKubernetes) ([]v1alpha12.AdminNetworkPolicy, error) { + return kubernetes.GetAdminNetworkPoliciesInNamespace(ctx) +} + +func GetBaseAdminNetworkPoliciesInNamespaces(ctx context.Context, kubernetes IKubernetes) (v1alpha12.BaselineAdminNetworkPolicy, error) { + return kubernetes.GetBaseAdminNetworkPoliciesInNamespace(ctx) +} + type MockNamespace struct { NamespaceObject *v1.Namespace Netpols map[string]*networkingv1.NetworkPolicy @@ -91,9 +104,12 @@ type MockNamespace struct { } type MockKubernetes struct { - Namespaces map[string]*MockNamespace - passRate float64 - podID int + AdminNetworkPolicies func() ([]v1alpha12.AdminNetworkPolicy, error) + BaseNetworkPolicies func() (v1alpha12.BaselineAdminNetworkPolicy, error) + NetworkPolicies func() ([]networkingv1.NetworkPolicy, error) + Namespaces map[string]*MockNamespace + passRate float64 + podID int } func NewMockKubernetes(passRate float64) *MockKubernetes { @@ -191,7 +207,17 @@ func (m *MockKubernetes) DeleteNetworkPolicy(ns string, name string) error { return nil } -func (m *MockKubernetes) GetNetworkPoliciesInNamespace(namespace string) ([]networkingv1.NetworkPolicy, error) { +func (m *MockKubernetes) GetNetworkPoliciesInNamespace(ctx context.Context, namespace string) ([]networkingv1.NetworkPolicy, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + res, err := m.NetworkPolicies() + if res != nil || err != nil { + return res, err + } + } + nsObject, err := m.getNamespaceObject(namespace) if err != nil { return nil, err @@ -363,3 +389,22 @@ func (m *MockKubernetes) ExecuteRemoteCommand(namespace string, pod string, cont } return "", "", nil, nil } + +func (m *MockKubernetes) GetAdminNetworkPoliciesInNamespace(ctx context.Context) ([]v1alpha12.AdminNetworkPolicy, error) { + select { + default: + return m.AdminNetworkPolicies() + case <-ctx.Done(): + return []v1alpha12.AdminNetworkPolicy{}, ctx.Err() + } + +} + +func (m *MockKubernetes) GetBaseAdminNetworkPoliciesInNamespace(ctx context.Context) (v1alpha12.BaselineAdminNetworkPolicy, error) { + select { + default: + return m.BaseNetworkPolicies() + case <-ctx.Done(): + return v1alpha12.BaselineAdminNetworkPolicy{}, ctx.Err() + } +} diff --git a/cmd/policy-assistant/pkg/kube/kubernetes.go b/cmd/policy-assistant/pkg/kube/kubernetes.go index a28f7dfa..b0e1a637 100644 --- a/cmd/policy-assistant/pkg/kube/kubernetes.go +++ b/cmd/policy-assistant/pkg/kube/kubernetes.go @@ -3,6 +3,8 @@ package kube import ( "bytes" "context" + v1alpha12 "sigs.k8s.io/network-policy-api/apis/v1alpha1" + "sigs.k8s.io/network-policy-api/pkg/client/clientset/versioned/typed/apis/v1alpha1" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -19,8 +21,9 @@ import ( ) type Kubernetes struct { - ClientSet *kubernetes.Clientset - RestConfig *rest.Config + ClientSet *kubernetes.Clientset + alphaClientSet *v1alpha1.PolicyV1alpha1Client + RestConfig *rest.Config } func NewKubernetesForContext(context string) (*Kubernetes, error) { @@ -35,9 +38,15 @@ func NewKubernetesForContext(context string) (*Kubernetes, error) { if err != nil { return nil, errors.Wrapf(err, "unable to instantiate Clientset") } + alphacClientset, err := v1alpha1.NewForConfig(kubeConfig) + if err != nil { + return nil, errors.Wrapf(err, "unable to instantiate alpha network client set") + } + return &Kubernetes{ - ClientSet: clientset, - RestConfig: kubeConfig, + ClientSet: clientset, + alphaClientSet: alphacClientset, + RestConfig: kubeConfig, }, nil } @@ -92,14 +101,34 @@ func (k *Kubernetes) DeleteNetworkPolicy(ns string, name string) error { return errors.Wrapf(err, "unable to delete network policy %s/%s", ns, name) } -func (k *Kubernetes) GetNetworkPoliciesInNamespace(namespace string) ([]networkingv1.NetworkPolicy, error) { - netpolList, err := k.ClientSet.NetworkingV1().NetworkPolicies(namespace).List(context.TODO(), metav1.ListOptions{}) +func (k *Kubernetes) GetNetworkPoliciesInNamespace(ctx context.Context, namespace string) ([]networkingv1.NetworkPolicy, error) { + netpolList, err := k.ClientSet.NetworkingV1().NetworkPolicies(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, errors.Wrapf(err, "unable to get netpols in namespace %s", namespace) } return netpolList.Items, nil } +func (k *Kubernetes) GetAdminNetworkPoliciesInNamespace(ctx context.Context) ([]v1alpha12.AdminNetworkPolicy, error) { + anps, err := k.alphaClientSet.AdminNetworkPolicies().List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + return anps.Items, nil +} + +func (k *Kubernetes) GetBaseAdminNetworkPoliciesInNamespace(ctx context.Context) (v1alpha12.BaselineAdminNetworkPolicy, error) { + banp, err := k.alphaClientSet.BaselineAdminNetworkPolicies().List(ctx, metav1.ListOptions{}) + if err != nil { + return v1alpha12.BaselineAdminNetworkPolicy{}, err + } + if len(banp.Items) > 0 { + return banp.Items[0], nil + } + return v1alpha12.BaselineAdminNetworkPolicy{}, errors.New("BANP not found") + +} + func (k *Kubernetes) UpdateNetworkPolicy(policy *networkingv1.NetworkPolicy) (*networkingv1.NetworkPolicy, error) { logrus.Debugf("updating network policy %s/%s", policy.Namespace, policy.Name) np, err := k.ClientSet.NetworkingV1().NetworkPolicies(policy.Namespace).Update(context.TODO(), policy, metav1.UpdateOptions{}) diff --git a/cmd/policy-assistant/pkg/kube/read.go b/cmd/policy-assistant/pkg/kube/read.go index 5cdf2167..a186df49 100644 --- a/cmd/policy-assistant/pkg/kube/read.go +++ b/cmd/policy-assistant/pkg/kube/read.go @@ -1,9 +1,7 @@ package kube import ( - "os" - "path/filepath" - + "context" "github.com/mattfenwick/collections/pkg/builtin" "github.com/mattfenwick/collections/pkg/file" "github.com/mattfenwick/collections/pkg/slice" @@ -11,10 +9,23 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" networkingv1 "k8s.io/api/networking/v1" + "os" + "path/filepath" + v1alpha12 "sigs.k8s.io/network-policy-api/apis/v1alpha1" ) -func ReadNetworkPoliciesFromPath(policyPath string) ([]*networkingv1.NetworkPolicy, error) { - var allPolicies []*networkingv1.NetworkPolicy +// ReadNetworkPoliciesFromPath walks the folder and try to parse each file in +// one of the supported types in the following manner: +// 1. NetworkPolicyList +// 2. NetworkPolicy +// 3. BaselineAdminNetworkPolicy +// 4. AdminNetworkPolicyList +// 5. AdminNetworkPolicy +func ReadNetworkPoliciesFromPath(policyPath string) ([]*networkingv1.NetworkPolicy, []*v1alpha12.AdminNetworkPolicy, *v1alpha12.BaselineAdminNetworkPolicy, error) { + var netPolicies []*networkingv1.NetworkPolicy + var adminNetPolicies []*v1alpha12.AdminNetworkPolicy + var baseAdminNetPolicies *v1alpha12.BaselineAdminNetworkPolicy + err := filepath.Walk(policyPath, func(path string, info os.FileInfo, err error) error { if err != nil { return errors.Wrapf(err, "unable to walk path %s", path) @@ -36,7 +47,7 @@ func ReadNetworkPoliciesFromPath(policyPath string) ([]*networkingv1.NetworkPoli // policies, err := yaml.ParseMany[networkingv1.NetworkPolicy](bytes) // if err == nil { // logrus.Debugf("parsed %d policies from %s", len(policies), path) - // allPolicies = append(allPolicies, refNetpolList(policies)...) + // netPolicies = append(netPolicies, refNetpolList(policies)...) // return nil // } // logrus.Errorf("unable to parse multiple policies separated by '---' lines: %+v", err) @@ -44,41 +55,135 @@ func ReadNetworkPoliciesFromPath(policyPath string) ([]*networkingv1.NetworkPoli // try parsing a NetworkPolicyList policyList, err := utils.ParseYamlStrict[networkingv1.NetworkPolicyList](bytes) if err == nil { - allPolicies = append(allPolicies, refNetpolList(policyList.Items)...) + netPolicies = append(netPolicies, refList(policyList.Items)...) return nil } - - logrus.Debugf("unable to parse list of policies: %+v", err) + logrus.Debugf("unable to parse list of network policies: %+v", err) policy, err := utils.ParseYamlStrict[networkingv1.NetworkPolicy](bytes) - if err != nil { - return errors.WithMessagef(err, "unable to parse single policy from yaml at %s", path) + if err == nil { + netPolicies = append(netPolicies, policy) + return nil + } + logrus.Debugf("unable to parse network policy: %+v", err) + + banp, err := utils.ParseYamlStrict[v1alpha12.BaselineAdminNetworkPolicy](bytes) + if err == nil { + baseAdminNetPolicies = banp + return nil + } + logrus.Debugf("unable to base admin network policies: %+v", err) + + anpList, err := utils.ParseYamlStrict[v1alpha12.AdminNetworkPolicyList](bytes) + if err == nil { + adminNetPolicies = append(adminNetPolicies, refList(anpList.Items)...) + return nil + } + logrus.Debugf("unable to parse list of admin network policies: %+v", err) + + anp, err := utils.ParseYamlStrict[v1alpha12.AdminNetworkPolicy](bytes) + if err == nil { + adminNetPolicies = append(adminNetPolicies, anp) + return nil + } + logrus.Debugf("unable to single admin network policies: %+v", err) + + if len(netPolicies) == 0 && len(adminNetPolicies) == 0 && baseAdminNetPolicies == nil { + return errors.WithMessagef(err, "unable to parse any policies from yaml at %s", path) } - logrus.Debugf("parsed single policy from %s: %+v", path, policy) - allPolicies = append(allPolicies, policy) return nil }) if err != nil { - return nil, err + return nil, nil, nil, err //return nil, errors.Wrapf(err, "unable to walk filesystem from %s", policyPath) } - for _, p := range allPolicies { - if len(p.Spec.PolicyTypes) == 0 { - return nil, errors.Errorf("missing spec.policyTypes from network policy %s/%s", p.Namespace, p.Name) + if len(netPolicies) > 0 { + for _, p := range netPolicies { + if len(p.Spec.PolicyTypes) == 0 { + return nil, nil, nil, errors.Errorf("missing spec.policyTypes from network policy %s/%s", p.Namespace, p.Name) + } } } - return allPolicies, nil + return netPolicies, adminNetPolicies, baseAdminNetPolicies, nil +} + +func ReadNetworkPoliciesFromKube(ctx context.Context, kubeClient IKubernetes, namespaces []string) ([]*networkingv1.NetworkPolicy, []*v1alpha12.AdminNetworkPolicy, *v1alpha12.BaselineAdminNetworkPolicy, error, error, error) { + var netpols []*networkingv1.NetworkPolicy + var anps []*v1alpha12.AdminNetworkPolicy + var banp *v1alpha12.BaselineAdminNetworkPolicy + var neterr, anperr, banperr error + + var netPolsCn = make(chan apiResponse[[]networkingv1.NetworkPolicy], 1) + go func(ch chan apiResponse[[]networkingv1.NetworkPolicy]) { + ch <- readNetworkPolicies(ctx, kubeClient, namespaces) + }(netPolsCn) + + var anpsCh = make(chan apiResponse[[]v1alpha12.AdminNetworkPolicy], 1) + go func(ch chan apiResponse[[]v1alpha12.AdminNetworkPolicy]) { + ch <- readAdminNetworkPolicies(ctx, kubeClient) + }(anpsCh) + + var banpsCh = make(chan apiResponse[v1alpha12.BaselineAdminNetworkPolicy], 1) + go func(ch chan apiResponse[v1alpha12.BaselineAdminNetworkPolicy]) { + ch <- readBaseAdminNetworkPolicies(ctx, kubeClient) + }(banpsCh) + + for i := 0; i <= 2; i++ { + select { + case result := <-netPolsCn: + r, err := result.response() + netpols = refList(r) + neterr = err + case result := <-anpsCh: + r, err := result.response() + anps = refList(r) + anperr = err + case result := <-banpsCh: + r, err := result.response() + if err == nil { + banp = &r + } + banperr = err + } + } + + return netpols, anps, banp, neterr, anperr, banperr +} + +func refList[T any](refs []T) []*T { + return slice.Map(builtin.Reference[T], refs) +} + +type apiResponse[T []networkingv1.NetworkPolicy | []v1alpha12.AdminNetworkPolicy | v1alpha12.BaselineAdminNetworkPolicy] struct { + data T + error error +} + +func (t apiResponse[T]) response() (T, error) { + return t.data, t.error +} + +func readNetworkPolicies(ctx context.Context, kubeClient IKubernetes, namespaces []string) apiResponse[[]networkingv1.NetworkPolicy] { + result, err := GetNetworkPoliciesInNamespaces(ctx, kubeClient, namespaces) + if err != nil { + return apiResponse[[]networkingv1.NetworkPolicy]{nil, err} + } + return apiResponse[[]networkingv1.NetworkPolicy]{result, err} } -func ReadNetworkPoliciesFromKube(kubeClient *Kubernetes, namespaces []string) ([]*networkingv1.NetworkPolicy, error) { - netpols, err := GetNetworkPoliciesInNamespaces(kubeClient, namespaces) +func readAdminNetworkPolicies(ctx context.Context, kubeClient IKubernetes) apiResponse[[]v1alpha12.AdminNetworkPolicy] { + result, err := GetAdminNetworkPoliciesInNamespaces(ctx, kubeClient) if err != nil { - return nil, err + return apiResponse[[]v1alpha12.AdminNetworkPolicy]{nil, err} } - return refNetpolList(netpols), nil + return apiResponse[[]v1alpha12.AdminNetworkPolicy]{result, err} } -func refNetpolList(refs []networkingv1.NetworkPolicy) []*networkingv1.NetworkPolicy { - return slice.Map(builtin.Reference[networkingv1.NetworkPolicy], refs) +func readBaseAdminNetworkPolicies(ctx context.Context, kubeClient IKubernetes) apiResponse[v1alpha12.BaselineAdminNetworkPolicy] { + result, err := GetBaseAdminNetworkPoliciesInNamespaces(ctx, kubeClient) + if err != nil { + return apiResponse[v1alpha12.BaselineAdminNetworkPolicy]{result, err} + } + return apiResponse[v1alpha12.BaselineAdminNetworkPolicy]{result, err} } diff --git a/cmd/policy-assistant/pkg/kube/read_test.go b/cmd/policy-assistant/pkg/kube/read_test.go new file mode 100644 index 00000000..d8918f9f --- /dev/null +++ b/cmd/policy-assistant/pkg/kube/read_test.go @@ -0,0 +1,230 @@ +package kube + +import ( + "context" + "errors" + "fmt" + v1 "k8s.io/api/networking/v1" + errors2 "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + v1alpha12 "sigs.k8s.io/network-policy-api/apis/v1alpha1" + "testing" + "time" +) + +func TestReadNetworkPoliciesFromKube(t *testing.T) { + anpNotFound := errors2.NewNotFound(schema.GroupResource{v1alpha12.GroupName, "AdminNetworkPolicy"}, "AdminNetworkPolicy") + banpNotFound := errors2.NewNotFound(schema.GroupResource{v1alpha12.GroupName, "BaselineAdminNetworkPolicy"}, "BaselineAdminNetworkPolicy") + netpolNotFound := errors2.NewNotFound(schema.GroupResource{v1.GroupName, "NetworkPolicies"}, "NetworkPolicies") + + scenarios := map[string]struct { + ctxCreator func() (context.Context, context.CancelFunc) + anpResponse func() ([]v1alpha12.AdminNetworkPolicy, error) + banpResponse func() (v1alpha12.BaselineAdminNetworkPolicy, error) + netPolResponse func() ([]v1.NetworkPolicy, error) + + expectedNetErr error + expectedAnpErr error + expectedBanpErr error + }{ + "timeout error on admin network policies retrieval": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.TODO(), 1*time.Nanosecond) + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + time.Sleep(1 * time.Millisecond) + return []v1alpha12.AdminNetworkPolicy{}, nil + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + return v1alpha12.BaselineAdminNetworkPolicy{}, nil + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + return nil, nil + }, + expectedAnpErr: context.DeadlineExceeded, + }, + "resource not found on admin network policies retrieval": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.TODO(), func() {} + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + return []v1alpha12.AdminNetworkPolicy{}, anpNotFound + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + return v1alpha12.BaselineAdminNetworkPolicy{}, nil + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + return nil, nil + }, + expectedAnpErr: anpNotFound, + }, + "return admin network policies": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.TODO(), func() {} + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + return []v1alpha12.AdminNetworkPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-network-policy", + }, + }, + }, nil + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + return v1alpha12.BaselineAdminNetworkPolicy{}, nil + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + return nil, nil + }, + }, + "timeout error on base admin network policies retrieval": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.TODO(), 1*time.Nanosecond) + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + return []v1alpha12.AdminNetworkPolicy{}, nil + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + time.Sleep(1 * time.Millisecond) + return v1alpha12.BaselineAdminNetworkPolicy{}, nil + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + return nil, nil + }, + expectedAnpErr: context.DeadlineExceeded, + }, + "resource not found on base admin network policies retrieval": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.TODO(), func() {} + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + return []v1alpha12.AdminNetworkPolicy{}, nil + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + return v1alpha12.BaselineAdminNetworkPolicy{}, banpNotFound + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + return nil, nil + }, + expectedBanpErr: banpNotFound, + }, + "return base admin network policies": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.TODO(), func() {} + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + return []v1alpha12.AdminNetworkPolicy{}, nil + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + return v1alpha12.BaselineAdminNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "base-admin-network-policy"}, + }, nil + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + return nil, nil + }, + }, + "timeout error on network policies retrieval": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.TODO(), 1*time.Nanosecond) + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + return []v1alpha12.AdminNetworkPolicy{}, nil + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + return v1alpha12.BaselineAdminNetworkPolicy{}, nil + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + time.Sleep(10 * time.Millisecond) + return nil, nil + }, + expectedNetErr: context.DeadlineExceeded, + }, + "resource not found on network policies retrieval": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.TODO(), func() {} + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + return []v1alpha12.AdminNetworkPolicy{}, nil + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + return v1alpha12.BaselineAdminNetworkPolicy{}, nil + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + return nil, netpolNotFound + }, + expectedNetErr: netpolNotFound, + }, + "return network policies": { + ctxCreator: func() (context.Context, context.CancelFunc) { + return context.TODO(), func() {} + }, + anpResponse: func() ([]v1alpha12.AdminNetworkPolicy, error) { + return []v1alpha12.AdminNetworkPolicy{}, nil + }, + banpResponse: func() (v1alpha12.BaselineAdminNetworkPolicy, error) { + return v1alpha12.BaselineAdminNetworkPolicy{}, nil + }, + netPolResponse: func() ([]v1.NetworkPolicy, error) { + return []v1.NetworkPolicy{ + { + ObjectMeta: metav1.ObjectMeta{Name: "network_policy"}, + }, + }, nil + }, + }, + } + + for name, scenario := range scenarios { + t.Run(name, func(t *testing.T) { + ctx, cancel := scenario.ctxCreator() + defer cancel() + + k := &MockKubernetes{ + AdminNetworkPolicies: scenario.anpResponse, + BaseNetworkPolicies: scenario.banpResponse, + NetworkPolicies: scenario.netPolResponse, + } + netpol, anps, banp, netErr, anpErr, banpErr := ReadNetworkPoliciesFromKube(ctx, k, []string{"test"}) + if scenario.expectedNetErr != nil { + if !errors.Is(netErr, scenario.expectedNetErr) { + t.Fatalf("Unexpected error1: %v, expected %v", netErr, scenario.expectedNetErr) + } + } + if scenario.expectedAnpErr != nil { + if !errors.Is(anpErr, scenario.expectedAnpErr) { + t.Fatalf("Unexpected error1: %v, expected %v", anpErr, scenario.expectedAnpErr) + } + } + if scenario.expectedBanpErr != nil { + if !errors.Is(banpErr, scenario.expectedBanpErr) { + t.Fatalf("Unexpected error1: %v, expected %v", banpErr, scenario.expectedBanpErr) + } + } + + if len(anps) > 0 { + expected, _ := scenario.anpResponse() + if anps[0].Name != expected[0].Name { + t.Fatalf("Unexpected ANP: %v, expected %v", anps[0].Name, expected[0].Name) + } + } + + if banp != nil { + expected, _ := scenario.banpResponse() + if banp.Name != expected.Name { + t.Fatalf("Unexpected BANP: %v, expected %v", banp.Name, banp.Name) + } + } + if len(netpol) > 0 { + expected, _ := scenario.netPolResponse() + fmt.Println(netpol[0].Name, expected[0].Name+"1") + if netpol[0].Name != expected[0].Name { + t.Fatalf("Unexpected NetworkPolicy: %v, expected %v", netpol[0].Name, expected[0].Name) + } + } + }) + } + +} diff --git a/cmd/policy-assistant/pkg/kube/read_tests.go b/cmd/policy-assistant/pkg/kube/read_tests.go index f5b5946b..fea1bd12 100644 --- a/cmd/policy-assistant/pkg/kube/read_tests.go +++ b/cmd/policy-assistant/pkg/kube/read_tests.go @@ -8,12 +8,12 @@ import ( func RunReadNetworkPolicyTests() { Describe("ReadNetworkPolicies", func() { It("Should read a single policy from a single file", func() { - policies, err := ReadNetworkPoliciesFromPath("../../networkpolicies/features/portrange1.yaml") + policies, _, _, err := ReadNetworkPoliciesFromPath("../../networkpolicies/features/portrange1.yaml") Expect(err).To(BeNil()) Expect(len(policies)).To(Equal(1)) }) It("Should read a list of policies from a single file", func() { - policies, err := ReadNetworkPoliciesFromPath("../../networkpolicies/yaml-syntax/yaml-list.yaml") + policies, _, _, err := ReadNetworkPoliciesFromPath("../../networkpolicies/yaml-syntax/yaml-list.yaml") Expect(err).To(BeNil()) Expect(len(policies)).To(Equal(3)) }) @@ -28,15 +28,27 @@ func RunReadNetworkPolicyTests() { // }) It("Should read multiple policies from all files in a directory", func() { - policies, err := ReadNetworkPoliciesFromPath("../../networkpolicies/simple-example") + policies, _, _, err := ReadNetworkPoliciesFromPath("../../networkpolicies/simple-example") Expect(err).To(BeNil()) Expect(len(policies)).To(Equal(7)) - policies, err = ReadNetworkPoliciesFromPath("../../networkpolicies/") + policies, _, _, err = ReadNetworkPoliciesFromPath("../../networkpolicies/") Expect(err).To(BeNil()) Expect(len(policies)).To(Equal(14)) }) + It("Should read multiple admin network policies", func() { + _, anps, _, err := ReadNetworkPoliciesFromPath("../../anps/") + Expect(err).To(BeNil()) + Expect(len(anps)).To(Equal(3)) + }) + + It("Should read a base admin network policy", func() { + _, _, banp, err := ReadNetworkPoliciesFromPath("../../banp/") + Expect(err).To(BeNil()) + Expect(banp).ToNot(BeNil()) + }) + // TODO test to show what happens for duplicate names }) } diff --git a/cmd/policy-assistant/pkg/utils/utils.go b/cmd/policy-assistant/pkg/utils/utils.go index 8ef86606..70434077 100644 --- a/cmd/policy-assistant/pkg/utils/utils.go +++ b/cmd/policy-assistant/pkg/utils/utils.go @@ -2,7 +2,6 @@ package utils import ( "encoding/json" - "github.com/mattfenwick/collections/pkg/file" "github.com/pkg/errors" "github.com/sirupsen/logrus"