diff --git a/cmd/policy-assistant/pkg/cli/analyze.go b/cmd/policy-assistant/pkg/cli/analyze.go index 1ec59156..b26a6801 100644 --- a/cmd/policy-assistant/pkg/cli/analyze.go +++ b/cmd/policy-assistant/pkg/cli/analyze.go @@ -4,8 +4,12 @@ import ( "fmt" "github.com/mattfenwick/cyclonus/examples" "github.com/mattfenwick/cyclonus/pkg/kube/netpol" + "golang.org/x/net/context" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/network-policy-api/apis/v1alpha1" "strings" + "time" "github.com/mattfenwick/collections/pkg/json" "github.com/mattfenwick/cyclonus/pkg/connectivity/probe" @@ -38,6 +42,8 @@ var AllModes = []string{ ProbeMode, } +const DefaultTimeout = 3 * time.Minute + type AnalyzeArgs struct { AllNamespaces bool Namespaces []string @@ -56,6 +62,8 @@ type AnalyzeArgs struct { // synthetic probe ProbePath string + + Timeout time.Duration } func SetupAnalyzeCommand() *cobra.Command { @@ -82,6 +90,7 @@ func SetupAnalyzeCommand() *cobra.Command { command.Flags().StringVar(&args.TargetPodPath, "target-pod-path", "", "path to json target pod file -- json array of dicts") command.Flags().StringVar(&args.TrafficPath, "traffic-path", "", "path to json traffic file, containing of a list of traffic objects") command.Flags().StringVar(&args.ProbePath, "probe-path", "", "path to json model file for synthetic probe") + command.Flags().DurationVar(&args.Timeout, "kube-client-timeout", DefaultTimeout, "kube client timeout") return command } @@ -90,9 +99,10 @@ func RunAnalyzeCommand(args *AnalyzeArgs) { // 1. read policies from kube var kubePolicies []*networkingv1.NetworkPolicy var kubeANPs []*v1alpha1.AdminNetworkPolicy - var kubeBANPs *v1alpha1.BaselineAdminNetworkPolicy + var kubeBANP *v1alpha1.BaselineAdminNetworkPolicy var kubePods []v1.Pod var kubeNamespaces []v1.Namespace + var netpolErr, anpErr, banpErr error if args.AllNamespaces || len(args.Namespaces) > 0 { kubeClient, err := kube.NewKubernetesForContext(args.Context) utils.DoOrDie(err) @@ -104,31 +114,48 @@ func RunAnalyzeCommand(args *AnalyzeArgs) { kubeNamespaces = nsList.Items namespaces = []string{v1.NamespaceAll} } - kubePolicies, err = kube.ReadNetworkPoliciesFromKube(kubeClient, namespaces) - if err != nil { + + includeANPS, includeBANPSs := shouldIncludeANPandBANP(kubeClient.ClientSet) + + ctx, cancel := context.WithTimeout(context.TODO(), args.Timeout) + defer cancel() + + kubePolicies, kubeANPs, kubeBANP, netpolErr, anpErr, banpErr = kube.ReadNetworkPoliciesFromKube(ctx, kubeClient, namespaces, includeANPS, includeBANPSs) + + if netpolErr != 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 { + logrus.Errorf("Unable to fetch admin network policies: %s \n", anpErr) + } + if banpErr != nil { + logrus.Errorf("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...) + if banpFromPath != nil && kubeBANP != nil { + logrus.Debugf("More that one banp parsed - setting banp from file") + } + kubeBANP = banpFromPath } // 3. read example policies if args.UseExamplePolicies { kubePolicies = append(kubePolicies, netpol.AllExamples...) - kubeANPs = examples.CoreGressRulesCombinedANB - kubeBANPs = examples.CoreGressRulesCombinedBANB + kubeANPs = append(kubeANPs, examples.CoreGressRulesCombinedANB...) + if kubeBANP != nil { + logrus.Debugf("More that onew banp parsed - setting banp from the examples") + } + kubeBANP = examples.CoreGressRulesCombinedBANB } logrus.Debugf("parsed policies:\n%s", json.MustMarshalToString(kubePolicies)) - policies := matcher.BuildV1AndV2NetPols(args.SimplifyPolicies, kubePolicies, kubeANPs, kubeBANPs) + policies := matcher.BuildV1AndV2NetPols(args.SimplifyPolicies, kubePolicies, kubeANPs, kubeBANP) for _, mode := range args.Modes { switch mode { @@ -303,3 +330,28 @@ func ProbeSyntheticConnectivity(explainedPolicies *matcher.Policy, modelPath str fmt.Printf("Egress:\n%s\n", simulatedProbe.RenderEgress()) fmt.Printf("Combined:\n%s\n\n\n", simulatedProbe.RenderTable()) } + +func shouldIncludeANPandBANP(client *kubernetes.Clientset) (bool, bool) { + var includeANP, includeBANP bool + _, resources, _, err := client.DiscoveryClient.GroupsAndMaybeResources() + if err != nil { + logrus.Errorf("Unable to fetch all registered resources: %s", err) + return includeANP, includeBANP + } + gv := schema.GroupVersion{Group: "policy.networking.k8s.io", Version: "v1alpha1"} + + if groupResources, ok := resources[gv]; ok { + for _, res := range groupResources.APIResources { + switch res.Kind { + case "AdminNetworkPolicy": + includeANP = true + case "BaselineAdminNetworkPolicy": + includeBANP = true + default: + continue + } + } + } + + return includeANP, includeBANP +} 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..393227fd 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" + "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,16 @@ type IKubernetes interface { DeleteService(namespace string, name string) error GetServicesInNamespace(namespace string) ([]v1.Service, error) + GetAdminNetworkPolicies(ctx context.Context) ([]v1alpha1.AdminNetworkPolicy, error) + CreateAdminNetworkPolicy(ctx context.Context, policy *v1alpha1.AdminNetworkPolicy) (*v1alpha1.AdminNetworkPolicy, error) + UpdateAdminNetworkPolicy(ctx context.Context, policy *v1alpha1.AdminNetworkPolicy) (*v1alpha1.AdminNetworkPolicy, error) + DeleteAdminNetworkPolicy(ctx context.Context, name string) error + + GetBaselineAdminNetworkPolicy(ctx context.Context) (*v1alpha1.BaselineAdminNetworkPolicy, error) + CreateBaselineAdminNetworkPolicy(ctx context.Context, policy *v1alpha1.BaselineAdminNetworkPolicy) (*v1alpha1.BaselineAdminNetworkPolicy, error) + UpdateBaselineAdminNetworkPolicy(ctx context.Context, policy *v1alpha1.BaselineAdminNetworkPolicy) (*v1alpha1.BaselineAdminNetworkPolicy, error) + DeleteBaselineAdminNetworkPolicy(ctx context.Context, name string) error + CreatePod(kubePod *v1.Pod) (*v1.Pod, error) GetPod(namespace string, pod string) (*v1.Pod, error) DeletePod(namespace string, pod string) error @@ -37,10 +49,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 +95,14 @@ func GetServicesInNamespaces(kubernetes IKubernetes, namespaces []string) ([]v1. return allServices, nil } +func GetAdminNetworkPolicies(ctx context.Context, kubernetes IKubernetes) ([]v1alpha1.AdminNetworkPolicy, error) { + return kubernetes.GetAdminNetworkPolicies(ctx) +} + +func GetBaselineAdminNetworkPolicy(ctx context.Context, kubernetes IKubernetes) (*v1alpha1.BaselineAdminNetworkPolicy, error) { + return kubernetes.GetBaselineAdminNetworkPolicy(ctx) +} + type MockNamespace struct { NamespaceObject *v1.Namespace Netpols map[string]*networkingv1.NetworkPolicy @@ -91,9 +111,14 @@ type MockNamespace struct { } type MockKubernetes struct { - Namespaces map[string]*MockNamespace - passRate float64 - podID int + AdminNetworkPolicies []v1alpha1.AdminNetworkPolicy + AdminNetworkPolicyError error + BaselineNetworkPolicy *v1alpha1.BaselineAdminNetworkPolicy + BaseAdminNetworkPolicyError error + Namespaces map[string]*MockNamespace + NetworkPolicyError error + passRate float64 + podID int } func NewMockKubernetes(passRate float64) *MockKubernetes { @@ -191,7 +216,11 @@ 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) { + if m.NetworkPolicyError != nil { + return nil, m.NetworkPolicyError + } + nsObject, err := m.getNamespaceObject(namespace) if err != nil { return nil, err @@ -363,3 +392,37 @@ func (m *MockKubernetes) ExecuteRemoteCommand(namespace string, pod string, cont } return "", "", nil, nil } + +func (m *MockKubernetes) GetAdminNetworkPolicies(ctx context.Context) ([]v1alpha1.AdminNetworkPolicy, error) { + return m.AdminNetworkPolicies, m.AdminNetworkPolicyError +} + +func (k *MockKubernetes) CreateAdminNetworkPolicy(ctx context.Context, policy *v1alpha1.AdminNetworkPolicy) (*v1alpha1.AdminNetworkPolicy, error) { + return nil, ErrNotImplemented +} + +func (k *MockKubernetes) UpdateAdminNetworkPolicy(ctx context.Context, policy *v1alpha1.AdminNetworkPolicy) (*v1alpha1.AdminNetworkPolicy, error) { + return nil, ErrNotImplemented +} + +func (k *MockKubernetes) DeleteAdminNetworkPolicy(ctx context.Context, name string) error { + //TODO: implement + return ErrNotImplemented +} + +func (m *MockKubernetes) GetBaselineAdminNetworkPolicy(ctx context.Context) (*v1alpha1.BaselineAdminNetworkPolicy, error) { + return m.BaselineNetworkPolicy, m.BaseAdminNetworkPolicyError +} + +func (k *MockKubernetes) CreateBaselineAdminNetworkPolicy(ctx context.Context, policy *v1alpha1.BaselineAdminNetworkPolicy) (*v1alpha1.BaselineAdminNetworkPolicy, error) { + return nil, ErrNotImplemented +} + +func (k *MockKubernetes) UpdateBaselineAdminNetworkPolicy(ctx context.Context, policy *v1alpha1.BaselineAdminNetworkPolicy) (*v1alpha1.BaselineAdminNetworkPolicy, error) { + return nil, ErrNotImplemented +} + +func (k *MockKubernetes) DeleteBaselineAdminNetworkPolicy(ctx context.Context, name string) error { + //TODO: implement + return ErrNotImplemented +} diff --git a/cmd/policy-assistant/pkg/kube/kubernetes.go b/cmd/policy-assistant/pkg/kube/kubernetes.go index a28f7dfa..d6db587e 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" @@ -18,9 +20,12 @@ import ( "k8s.io/client-go/tools/remotecommand" ) +var ErrNotImplemented = errors.New("Not implemented") + 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 +40,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 +103,60 @@ 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) GetAdminNetworkPolicies(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) CreateAdminNetworkPolicy(ctx context.Context, policy *v1alpha12.AdminNetworkPolicy) (*v1alpha12.AdminNetworkPolicy, error) { + return nil, ErrNotImplemented +} + +func (k *Kubernetes) UpdateAdminNetworkPolicy(ctx context.Context, policy *v1alpha12.AdminNetworkPolicy) (*v1alpha12.AdminNetworkPolicy, error) { + return nil, ErrNotImplemented +} + +func (k *Kubernetes) DeleteAdminNetworkPolicy(ctx context.Context, name string) error { + //TODO: implement + return ErrNotImplemented +} + +func (k *Kubernetes) GetBaselineAdminNetworkPolicy(ctx context.Context) (*v1alpha12.BaselineAdminNetworkPolicy, error) { + banps, err := k.alphaClientSet.BaselineAdminNetworkPolicies().List(ctx, metav1.ListOptions{}) + + if err != nil { + return nil, err + } + if len(banps.Items) == 1 { + return &banps.Items[0], nil + } + return nil, nil +} + +func (k *Kubernetes) CreateBaselineAdminNetworkPolicy(ctx context.Context, policy *v1alpha12.BaselineAdminNetworkPolicy) (*v1alpha12.BaselineAdminNetworkPolicy, error) { + return nil, ErrNotImplemented +} + +func (k *Kubernetes) UpdateBaselineAdminNetworkPolicy(ctx context.Context, policy *v1alpha12.BaselineAdminNetworkPolicy) (*v1alpha12.BaselineAdminNetworkPolicy, error) { + return nil, ErrNotImplemented +} + +func (k *Kubernetes) DeleteBaselineAdminNetworkPolicy(ctx context.Context, name string) error { + //TODO: implement + return ErrNotImplemented +} + 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..d3f8ff51 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,24 @@ 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" + "sync" ) -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 adminNetworkPolicies []*v1alpha12.AdminNetworkPolicy + var baselineAdminNetworkPolicy *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 +48,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 +56,100 @@ 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 { + if baselineAdminNetworkPolicy != nil { + return errors.New("baseline admin network policy already exists") + } + baselineAdminNetworkPolicy = banp + return nil + } + logrus.Debugf("unable to base admin network policies: %+v", err) + + anpList, err := utils.ParseYamlStrict[v1alpha12.AdminNetworkPolicyList](bytes) + if err == nil { + adminNetworkPolicies = append(adminNetworkPolicies, 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 { + adminNetworkPolicies = append(adminNetworkPolicies, anp) + return nil + } + logrus.Debugf("unable to single admin network policies: %+v", err) + + if len(netPolicies) == 0 && len(adminNetworkPolicies) == 0 && baselineAdminNetworkPolicy == 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, adminNetworkPolicies, baselineAdminNetworkPolicy, nil } -func ReadNetworkPoliciesFromKube(kubeClient *Kubernetes, namespaces []string) ([]*networkingv1.NetworkPolicy, error) { - netpols, err := GetNetworkPoliciesInNamespaces(kubeClient, namespaces) - if err != nil { - return nil, err - } - return refNetpolList(netpols), nil +func refList[T any](refs []T) []*T { + return slice.Map(builtin.Reference[T], refs) } -func refNetpolList(refs []networkingv1.NetworkPolicy) []*networkingv1.NetworkPolicy { - return slice.Map(builtin.Reference[networkingv1.NetworkPolicy], refs) +func ReadNetworkPoliciesFromKube(ctx context.Context, kubeClient IKubernetes, namespaces []string, includeANPs, includeBANPs bool) ([]*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 wg sync.WaitGroup + wg.Add(3) + + go func(w *sync.WaitGroup) { + defer w.Done() + netpols, netErr = GetNetworkPoliciesInNamespaces(ctx, kubeClient, namespaces) + return + }(&wg) + + go func(w *sync.WaitGroup) { + defer w.Done() + if !includeANPs { + return + } + anps, anpErr = GetAdminNetworkPolicies(ctx, kubeClient) + return + }(&wg) + + go func(w *sync.WaitGroup) { + defer w.Done() + if !includeBANPs { + return + } + banp, banpErr = GetBaselineAdminNetworkPolicy(ctx, kubeClient) + return + }(&wg) + + wg.Wait() + + return refList(netpols), refList(anps), banp, netErr, anpErr, banpErr } 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..2bcfd3ba --- /dev/null +++ b/cmd/policy-assistant/pkg/kube/read_test.go @@ -0,0 +1,112 @@ +package kube + +import ( + "context" + "errors" + v1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1alpha12 "sigs.k8s.io/network-policy-api/apis/v1alpha1" + "testing" +) + +func TestReadNetworkPoliciesFromKube(t *testing.T) { + scenarios := map[string]struct { + AdminNetworkPolicies []v1alpha12.AdminNetworkPolicy + BaselineAdminNetworkPolicies *v1alpha12.BaselineAdminNetworkPolicy + NetworkPolicies []v1.NetworkPolicy + + expectedNetErr error + expectedAnpErr error + expectedBanpErr error + }{ + "parse error on admin network policies retrieval": { + expectedAnpErr: context.DeadlineExceeded, + }, + "return admin network policies": { + AdminNetworkPolicies: []v1alpha12.AdminNetworkPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "admin-network-policy", + }, + }, + }, + }, + "parse error on base admin network policies retrieval": { + expectedAnpErr: context.DeadlineExceeded, + }, + "return base admin network policies": { + BaselineAdminNetworkPolicies: &v1alpha12.BaselineAdminNetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: "base-admin-network-policy"}, + }, + }, + "parse error on network policies retrieval": { + expectedNetErr: context.DeadlineExceeded, + }, + "return network policies": { + NetworkPolicies: []v1.NetworkPolicy{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "network_policy", + Namespace: "default", + }, + }, + }, + }, + } + + for name, scenario := range scenarios { + t.Run(name, func(t *testing.T) { + k := &MockKubernetes{ + AdminNetworkPolicies: scenario.AdminNetworkPolicies, + AdminNetworkPolicyError: scenario.expectedAnpErr, + BaselineNetworkPolicy: scenario.BaselineAdminNetworkPolicies, + BaseAdminNetworkPolicyError: scenario.expectedBanpErr, + Namespaces: map[string]*MockNamespace{}, + NetworkPolicyError: scenario.expectedNetErr, + } + + for _, np := range scenario.NetworkPolicies { + k.Namespaces[np.Namespace] = &MockNamespace{ + Netpols: map[string]*v1.NetworkPolicy{ + np.Name: &np, + }, + } + } + + netpol, anps, banp, netErr, anpErr, banpErr := ReadNetworkPoliciesFromKube(context.TODO(), k, []string{"default"}, true, true) + if scenario.expectedNetErr != nil { + if !errors.Is(netErr, scenario.expectedNetErr) { + t.Fatalf("Unexpected error: %v, expected %v", netErr, scenario.expectedNetErr) + } + } + if scenario.expectedAnpErr != nil { + if !errors.Is(anpErr, scenario.expectedAnpErr) { + t.Fatalf("Unexpected error: %v, expected %v", anpErr, scenario.expectedAnpErr) + } + } + if scenario.expectedBanpErr != nil { + if !errors.Is(banpErr, scenario.expectedBanpErr) { + t.Fatalf("Unexpected error: %v, expected %v", banpErr, scenario.expectedBanpErr) + } + } + + if len(scenario.AdminNetworkPolicies) > 0 { + if anps[0].Name != scenario.AdminNetworkPolicies[0].Name { + t.Fatalf("Unexpected ANP: %v, expected %v", anps[0].Name, scenario.AdminNetworkPolicies[0].Name) + } + } + + if scenario.BaselineAdminNetworkPolicies != nil { + if banp.Name != scenario.BaselineAdminNetworkPolicies.Name { + t.Fatalf("Unexpected BANP: %v, expected %v", banp.Name, banp.Name) + } + } + if len(scenario.NetworkPolicies) > 0 { + if netpol[0].Name != scenario.NetworkPolicies[0].Name { + t.Fatalf("Unexpected NetworkPolicy: %v, expected %v", netpol[0].Name, scenario.NetworkPolicies[0].Name) + } + } + }) + } + +} diff --git a/cmd/policy-assistant/pkg/kube/read_tests.go b/cmd/policy-assistant/pkg/kube/read_tests.go index f5b5946b..44922bad 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("../../test/example-policies/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("../../test/example-policies/networkpolicies/yaml-syntax/yaml-list.yaml") Expect(err).To(BeNil()) Expect(len(policies)).To(Equal(3)) }) @@ -28,15 +28,35 @@ func RunReadNetworkPolicyTests() { // }) It("Should read multiple policies from all files in a directory", func() { - policies, err := ReadNetworkPoliciesFromPath("../../networkpolicies/simple-example") + policies, _, _, err := ReadNetworkPoliciesFromPath("../../test/example-policies/networkpolicies/simple-example") Expect(err).To(BeNil()) Expect(len(policies)).To(Equal(7)) - policies, err = ReadNetworkPoliciesFromPath("../../networkpolicies/") + policies, _, _, err = ReadNetworkPoliciesFromPath("../../test/example-policies/networkpolicies/") Expect(err).To(BeNil()) Expect(len(policies)).To(Equal(14)) }) + It("Should read multiple admin network policies", func() { + _, anps, _, err := ReadNetworkPoliciesFromPath("../../test/example-policies/anps/") + Expect(err).To(BeNil()) + Expect(len(anps)).To(Equal(3)) + }) + + It("Should read a base admin network policy", func() { + _, _, banp, err := ReadNetworkPoliciesFromPath("../../test/example-policies/banp/") + Expect(err).To(BeNil()) + Expect(banp).ToNot(BeNil()) + }) + + It("Should parse multiple types from folder", func() { + policiies, anps, bapn, err := ReadNetworkPoliciesFromPath("../../test/example-policies/") + Expect(err).To(BeNil()) + Expect(len(policiies)).To(Equal(14)) + Expect(len(anps)).To(Equal(3)) + Expect(bapn).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" diff --git a/cmd/policy-assistant/test/example-policies/anps/anp-list.yaml b/cmd/policy-assistant/test/example-policies/anps/anp-list.yaml new file mode 100644 index 00000000..0e7271d3 --- /dev/null +++ b/cmd/policy-assistant/test/example-policies/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/test/example-policies/anps/anp.yaml b/cmd/policy-assistant/test/example-policies/anps/anp.yaml new file mode 100644 index 00000000..2ada0fd5 --- /dev/null +++ b/cmd/policy-assistant/test/example-policies/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/test/example-policies/banp/banp.yaml b/cmd/policy-assistant/test/example-policies/banp/banp.yaml new file mode 100644 index 00000000..2fd7b6d6 --- /dev/null +++ b/cmd/policy-assistant/test/example-policies/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/networkpolicies/allow-all-internal.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/allow-all-internal.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/allow-all-internal.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/allow-all-internal.yaml diff --git a/cmd/policy-assistant/networkpolicies/allow-all.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/allow-all.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/allow-all.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/allow-all.yaml diff --git a/cmd/policy-assistant/networkpolicies/features/portrange1.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/features/portrange1.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/features/portrange1.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/features/portrange1.yaml diff --git a/cmd/policy-assistant/networkpolicies/simple-example/allow-all-egress-by-label.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/allow-all-egress-by-label.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/simple-example/allow-all-egress-by-label.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/allow-all-egress-by-label.yaml diff --git a/cmd/policy-assistant/networkpolicies/simple-example/allow-all-for-label.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/allow-all-for-label.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/simple-example/allow-all-for-label.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/allow-all-for-label.yaml diff --git a/cmd/policy-assistant/networkpolicies/simple-example/allow-by-ip.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/allow-by-ip.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/simple-example/allow-by-ip.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/allow-by-ip.yaml diff --git a/cmd/policy-assistant/networkpolicies/simple-example/allow-label-to-label.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/allow-label-to-label.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/simple-example/allow-label-to-label.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/allow-label-to-label.yaml diff --git a/cmd/policy-assistant/networkpolicies/simple-example/deny-all-egress.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/deny-all-egress.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/simple-example/deny-all-egress.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/deny-all-egress.yaml diff --git a/cmd/policy-assistant/networkpolicies/simple-example/deny-all-for-label.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/deny-all-for-label.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/simple-example/deny-all-for-label.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/deny-all-for-label.yaml diff --git a/cmd/policy-assistant/networkpolicies/simple-example/deny-all.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/deny-all.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/simple-example/deny-all.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/simple-example/deny-all.yaml diff --git a/cmd/policy-assistant/networkpolicies/upstream_test_cases/allow-to-ns-y-pod-a.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/upstream_test_cases/allow-to-ns-y-pod-a.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/upstream_test_cases/allow-to-ns-y-pod-a.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/upstream_test_cases/allow-to-ns-y-pod-a.yaml diff --git a/cmd/policy-assistant/networkpolicies/yaml-syntax/yaml-list.yaml b/cmd/policy-assistant/test/example-policies/networkpolicies/yaml-syntax/yaml-list.yaml similarity index 100% rename from cmd/policy-assistant/networkpolicies/yaml-syntax/yaml-list.yaml rename to cmd/policy-assistant/test/example-policies/networkpolicies/yaml-syntax/yaml-list.yaml