From 5213e9e002c56307e6c1c1c55d9c4a8ed762b760 Mon Sep 17 00:00:00 2001 From: GuessWhoSamFoo Date: Fri, 3 Apr 2020 11:36:35 -0700 Subject: [PATCH 1/2] Add network policy printer Signed-off-by: GuessWhoSamFoo --- internal/describer/objects.go | 11 + internal/gvk/gvk.go | 1 + internal/modules/overview/navigation.go | 2 + internal/modules/overview/path.go | 3 + internal/printer/handler.go | 2 + internal/printer/networkpolicy.go | 448 ++++++++++++++++++ internal/printer/networkpolicy_test.go | 163 +++++++ internal/queryer/queryer.go | 1 + internal/testutil/generator.go | 11 +- pkg/icon/icon.go | 1 + .../datagrid/datagrid.component.ts | 2 + 11 files changed, 644 insertions(+), 1 deletion(-) create mode 100644 internal/printer/networkpolicy.go create mode 100644 internal/printer/networkpolicy_test.go diff --git a/internal/describer/objects.go b/internal/describer/objects.go index 213e35be77..57997cc069 100644 --- a/internal/describer/objects.go +++ b/internal/describer/objects.go @@ -14,6 +14,7 @@ import ( batchv1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/v1" "k8s.io/api/extensions/v1beta1" + networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" "github.com/vmware-tanzu/octant/pkg/icon" @@ -164,12 +165,22 @@ func initNamespacedOverview() *Section { IconName: icon.OverviewService, }) + dlbNetworkPolicies := NewResource(ResourceOptions{ + Path: "/discovery-and-load-balancing/network-policies", + ObjectStoreKey: store.Key{APIVersion: "networking.k8s.io/v1", Kind: "NetworkPolicy"}, + ListType: &networkingv1.NetworkPolicyList{}, + ObjectType: &networkingv1.NetworkPolicy{}, + Titles: ResourceTitle{List: "Discovery & Load Balancing / Network Policies", Object: "Network Policy"}, + IconName: icon.OverviewNetworkPolicy, + }) + discoveryAndLoadBalancingDescriber := NewSection( "/discovery-and-load-balancing", "Discovery and Load Balancing", dlbHorizontalPodAutoscalers, dlbIngresses, dlbServices, + dlbNetworkPolicies, ) csConfigMaps := NewResource(ResourceOptions{ diff --git a/internal/gvk/gvk.go b/internal/gvk/gvk.go index 7066bc1bce..abe041b7f9 100644 --- a/internal/gvk/gvk.go +++ b/internal/gvk/gvk.go @@ -29,6 +29,7 @@ var ( Job = schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"} Node = schema.GroupVersionKind{Version: "v1", Kind: "Node"} Namespace = schema.GroupVersionKind{Version: "v1", Kind: "Namespace"} + NetworkPolicy = schema.GroupVersionKind{Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"} ServiceAccount = schema.GroupVersionKind{Version: "v1", Kind: "ServiceAccount"} Secret = schema.GroupVersionKind{Version: "v1", Kind: "Secret"} Service = schema.GroupVersionKind{Version: "v1", Kind: "Service"} diff --git a/internal/modules/overview/navigation.go b/internal/modules/overview/navigation.go index 7a1c545424..5363e135d9 100644 --- a/internal/modules/overview/navigation.go +++ b/internal/modules/overview/navigation.go @@ -64,6 +64,8 @@ func discoAndLBEntries(ctx context.Context, prefix, namespace string, objectStor loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.Ingress), objectStore)) neh.Add("Services", "services", loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.Service), objectStore)) + neh.Add("Network Policies", "network-policies", + loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.NetworkPolicy), objectStore)) children, err := neh.Generate(prefix, namespace, "") if err != nil { diff --git a/internal/modules/overview/path.go b/internal/modules/overview/path.go index 541856864f..63b5c2a4ff 100644 --- a/internal/modules/overview/path.go +++ b/internal/modules/overview/path.go @@ -28,6 +28,7 @@ var ( gvk.HorizontalPodAutoscaler, gvk.Ingress, gvk.Service, + gvk.NetworkPolicy, gvk.ConfigMap, gvk.Secret, gvk.PersistentVolumeClaim, @@ -86,6 +87,8 @@ func gvkPath(namespace, apiVersion, kind, name string) (string, error) { p = "/discovery-and-load-balancing/ingresses" case apiVersion == "v1" && kind == "Service": p = "/discovery-and-load-balancing/services" + case apiVersion == "networking.k8s.io/v1" && kind == "NetworkPolicy": + p = "/discovery-and-load-balancing/network-policies" case apiVersion == "rbac.authorization.k8s.io/v1" && kind == "Role": p = "/rbac/roles" case apiVersion == "rbac.authorization.k8s.io/v1" && kind == "RoleBinding": diff --git a/internal/printer/handler.go b/internal/printer/handler.go index 5f7b69a193..d112a3ec4e 100644 --- a/internal/printer/handler.go +++ b/internal/printer/handler.go @@ -37,6 +37,8 @@ func AddHandlers(p Handler) error { NodeListHandler, NamespaceHandler, NamespaceListHandler, + NetworkPolicyHandler, + NetworkPolicyListHandler, ReplicaSetHandler, ReplicaSetListHandler, ReplicationControllerHandler, diff --git a/internal/printer/networkpolicy.go b/internal/printer/networkpolicy.go new file mode 100644 index 0000000000..a34fbaee25 --- /dev/null +++ b/internal/printer/networkpolicy.go @@ -0,0 +1,448 @@ +/* +Copyright (c) 2020 the Octant contributors. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package printer + +import ( + "context" + "encoding/json" + "strings" + + "github.com/pkg/errors" + "github.com/vmware-tanzu/octant/pkg/store" + "github.com/vmware-tanzu/octant/pkg/view/component" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kLabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +// NetworkPolicyListHandler is a printFunc that prints network policies +func NetworkPolicyListHandler(_ context.Context, list *networkingv1.NetworkPolicyList, options Options) (component.Component, error) { + if list == nil { + return nil, errors.New("network policy list is nil") + } + + cols := component.NewTableCols("Name", "Labels", "Age") + table := component.NewTable("Network Policies", "We couldn't find any network policies!", cols) + + for _, networkPolicy := range list.Items { + row := component.TableRow{} + nameLink, err := options.Link.ForObject(&networkPolicy, networkPolicy.Name) + if err != nil { + return nil, err + } + + row["Name"] = nameLink + row["Labels"] = component.NewLabels(networkPolicy.Labels) + row["Age"] = component.NewTimestamp(networkPolicy.CreationTimestamp.Time) + + table.Add(row) + } + + return table, nil +} + +// NetworkPolicyHandler is a printFunc that prints NetworkPolicies +func NetworkPolicyHandler(ctx context.Context, networkPolicy *networkingv1.NetworkPolicy, options Options) (component.Component, error) { + o := NewObject(networkPolicy) + o.EnableEvents() + + np, err := newNetworkPolicyHander(networkPolicy, o) + if err != nil { + return nil, err + } + + if err := np.Config(); err != nil { + return nil, errors.Wrap(err, "print networkPolicy configuration") + } + if err := np.Status(); err != nil { + return nil, errors.Wrap(err, "print networkPolicy status") + } + + if err := np.Pods(ctx, networkPolicy, options); err != nil { + return nil, errors.Wrap(err, "print networkPolicy pods") + } + + return o.ToComponent(ctx, options) +} + +type networkPolicyObject interface { + Config() error + Status() error + Pods(ctx context.Context, networkPolicy *networkingv1.NetworkPolicy, options Options) error +} + +type networkPolicyHandler struct { + networkPolicy *networkingv1.NetworkPolicy + configFunc func(*networkingv1.NetworkPolicy) (*component.Summary, error) + summaryFunc func(*networkingv1.NetworkPolicy) (*component.Summary, error) + podFunc func(context.Context, *networkingv1.NetworkPolicy, Options) (component.Component, error) + object *Object +} + +var _ networkPolicyObject = (*networkPolicyHandler)(nil) + +func newNetworkPolicyHander(networkPolicy *networkingv1.NetworkPolicy, object *Object) (*networkPolicyHandler, error) { + if networkPolicy == nil { + return nil, errors.New("can't print a nil network policy") + } + + if object == nil { + return nil, errors.New("can't print network policy using a nil object printer") + } + + nph := &networkPolicyHandler{ + networkPolicy: networkPolicy, + configFunc: defaultNetworkPolicyConfig, + summaryFunc: defaultNetWorkPolicySummary, + podFunc: defaultNetworkPolicyPods, + object: object, + } + + return nph, nil +} + +func (n *networkPolicyHandler) Config() error { + out, err := n.configFunc(n.networkPolicy) + if err != nil { + return err + } + + n.object.RegisterConfig(out) + return nil +} + +func defaultNetworkPolicyConfig(networkPolicy *networkingv1.NetworkPolicy) (*component.Summary, error) { + return NewNetworkPolicyConfiguration(networkPolicy).Create() +} + +func (n *networkPolicyHandler) Status() error { + out, err := n.summaryFunc(n.networkPolicy) + if err != nil { + return err + } + + n.object.RegisterSummary(out) + return nil +} + +func defaultNetWorkPolicySummary(networkPolicy *networkingv1.NetworkPolicy) (*component.Summary, error) { + return createNetworkPolicySummaryStatus(networkPolicy) +} + +func (n *networkPolicyHandler) Pods(ctx context.Context, networkPolicy *networkingv1.NetworkPolicy, options Options) error { + n.object.RegisterItems(ItemDescriptor{ + Width: component.WidthFull, + Func: func() (component.Component, error) { + return n.podFunc(ctx, networkPolicy, options) + }, + }) + return nil +} + +func defaultNetworkPolicyPods(ctx context.Context, networkPolicy *networkingv1.NetworkPolicy, options Options) (component.Component, error) { + return createNetworkPodListView(ctx, networkPolicy, options) +} + +// NetworkPolicyConfiguration generates networkPolicy configuration +type NetworkPolicyConfiguration struct { + networkPolicy *networkingv1.NetworkPolicy +} + +// NewNetworkPolicyConfiguration creates an instance of NetworkPolicyConfiguration +func NewNetworkPolicyConfiguration(n *networkingv1.NetworkPolicy) *NetworkPolicyConfiguration { + return &NetworkPolicyConfiguration{ + networkPolicy: n, + } +} + +// Create creates a networkPolicy configuration summary +func (n *NetworkPolicyConfiguration) Create() (*component.Summary, error) { + if n.networkPolicy == nil { + return nil, errors.New("network policy is nil") + } + + sections := make([]component.SummarySection, 0) + + networkPolicy := n.networkPolicy + + if networkPolicy.Spec.PolicyTypes != nil { + var policyText []string + for _, policy := range networkPolicy.Spec.PolicyTypes { + policyText = append(policyText, string(policy)) + } + + sections = append(sections, component.SummarySection{ + Header: "Policy Types", + Content: component.NewText(strings.Join(policyText, ", ")), + }) + } + + if &networkPolicy.Spec.PodSelector != nil { + selectors, err := selectorToComponent(&networkPolicy.Spec.PodSelector) + if err != nil { + return nil, err + } + + sections = append(sections, component.SummarySection{ + Header: "Selectors", + Content: selectors, + }) + } + + summary := component.NewSummary("Configuration", sections...) + + return summary, nil +} + +func selectorToComponent(selector *metav1.LabelSelector) (*component.Selectors, error) { + if selector == nil { + return nil, errors.New("nil label selector") + } + + var selectors []component.Selector + + for _, lsr := range selector.MatchExpressions { + o, err := component.MatchOperator(string(lsr.Operator)) + if err != nil { + return nil, err + } + + es := component.NewExpressionSelector(lsr.Key, o, lsr.Values) + selectors = append(selectors, es) + } + + for k, v := range selector.MatchLabels { + ls := component.NewLabelSelector(k, v) + selectors = append(selectors, ls) + } + + return component.NewSelectors(selectors), nil +} + +func createNetworkPolicySummaryStatus(networkPolicy *networkingv1.NetworkPolicy) (*component.Summary, error) { + if networkPolicy == nil { + return nil, errors.New("unable to generate status from a nil network policy") + } + + ingressTable, err := createIngressRules(networkPolicy.Spec.Ingress) + if err != nil { + return nil, errors.Wrapf(err, "create ingress rules") + } + + egressTable, err := createEgressRules(networkPolicy.Spec.Egress) + if err != nil { + return nil, errors.Wrapf(err, "create egress rules") + } + + summary := component.NewSummary("Status", []component.SummarySection{ + { + Header: "Allowing ingress traffic", + Content: ingressTable, + }, + { + Header: "Allowing egress traffic", + Content: egressTable, + }, + }...) + + return summary, nil +} + +func createIngressRules(ingressRules []networkingv1.NetworkPolicyIngressRule) (*component.Table, error) { + cols := component.NewTableCols("Policy", "Value") + ingressRuleTable := component.NewTable("", "No rules found", cols) + + if ingressRules == nil { + return ingressRuleTable, nil + } + + var portText []string + + for _, rule := range ingressRules { + if rule.Ports != nil { + for _, port := range rule.Ports { + protocol := string(*port.Protocol) + portText = append(portText, port.Port.String()+"/"+protocol) + } + + row := component.TableRow{} + row["Policy"] = component.NewText("To Port") + row["Value"] = component.NewText(strings.Join(portText, ", ")) + ingressRuleTable.Add(row) + } + if rule.From != nil { + for _, peer := range rule.From { + if peer.IPBlock != nil { + row := component.TableRow{} + row["Policy"] = component.NewText("From IP Block") + row["Value"] = component.NewText(describeIPBlock(peer.IPBlock)) + + ingressRuleTable.Add(row) + } + + if peer.NamespaceSelector != nil { + selectors, err := selectorToComponent(peer.NamespaceSelector) + if err != nil { + return nil, err + } + + row := component.TableRow{} + row["Policy"] = component.NewText("From Namespace Selector") + row["Value"] = selectors + ingressRuleTable.Add(row) + } + + if peer.PodSelector != nil { + selectors, err := selectorToComponent(peer.PodSelector) + if err != nil { + return nil, err + } + + row := component.TableRow{} + row["Policy"] = component.NewText("From Pod Selector") + row["Value"] = selectors + ingressRuleTable.Add(row) + } + } + } + } + return ingressRuleTable, nil +} + +func createEgressRules(egressRules []networkingv1.NetworkPolicyEgressRule) (*component.Table, error) { + cols := component.NewTableCols("Policy", "Value") + egressRuleTable := component.NewTable("", "No rules found", cols) + + if egressRules == nil { + return egressRuleTable, nil + } + + var portText []string + + for _, rule := range egressRules { + if rule.Ports != nil { + for _, port := range rule.Ports { + protocol := string(*port.Protocol) + portText = append(portText, port.Port.String()+"/"+protocol) + } + + row := component.TableRow{} + row["Policy"] = component.NewText("To Port") + row["Value"] = component.NewText(strings.Join(portText, ", ")) + egressRuleTable.Add(row) + } + if rule.To != nil { + for _, peer := range rule.To { + if peer.IPBlock != nil { + row := component.TableRow{} + row["Policy"] = component.NewText("From IP Block") + row["Value"] = component.NewText(describeIPBlock(peer.IPBlock)) + egressRuleTable.Add(row) + } + + if peer.NamespaceSelector != nil { + selectors, err := selectorToComponent(peer.NamespaceSelector) + if err != nil { + return nil, err + } + + row := component.TableRow{} + row["Policy"] = component.NewText("From Namespace Selector") + row["Value"] = selectors + egressRuleTable.Add(row) + } + + if peer.PodSelector != nil { + selectors, err := selectorToComponent(peer.PodSelector) + if err != nil { + return nil, err + } + + row := component.TableRow{} + row["Policy"] = component.NewText("From Pod Selector") + row["Value"] = selectors + egressRuleTable.Add(row) + } + } + } + } + return egressRuleTable, nil +} + +func createNetworkPodListView(ctx context.Context, networkPolicy *networkingv1.NetworkPolicy, options Options) (component.Component, error) { + options.DisableLabels = true + podList := &corev1.PodList{} + + objectStore := options.DashConfig.ObjectStore() + + podSelectorList := []*metav1.LabelSelector{} + selectorsList := []*metav1.LabelSelector{} + keyList := []store.Key{} + + if networkPolicy.Spec.Ingress != nil { + for _, rule := range networkPolicy.Spec.Ingress { + if rule.From != nil { + for _, peer := range rule.From { + if peer.NamespaceSelector != nil { + selectorsList = append(selectorsList, peer.NamespaceSelector) + } + + if peer.PodSelector != nil { + podSelectorList = append(podSelectorList, peer.NamespaceSelector) + } + } + } + } + } + + // Check for matching namespaces + for _, selector := range selectorsList { + s, err := metav1.LabelSelectorAsMap(selector) + if err != nil { + return nil, err + } + + labelMap := kLabels.Set(s) + + ul, _, err := objectStore.List(ctx, store.Key{APIVersion: "v1", Kind: "Namespace", Selector: &labelMap}) + if err != nil { + return nil, err + } + + for i := range ul.Items { + namespace := &corev1.Namespace{} + err := runtime.DefaultUnstructuredConverter.FromUnstructured(ul.Items[i].Object, namespace) + if err != nil { + return nil, err + } + + keyList = append(keyList, store.Key{Namespace: namespace.Name, APIVersion: "v1", Kind: "Pod"}) + } + } + + // Get pods from namespaces filtering by pod selector + for _, key := range keyList { + for _, podSelector := range podSelectorList { + pods, err := loadPods(ctx, key, objectStore, podSelector) + if err != nil { + return nil, err + } + + for _, pod := range pods { + podList.Items = append(podList.Items, *pod) + } + } + } + + return PodListHandler(ctx, podList, options) +} + +func describeIPBlock(source interface{}) string { + data, _ := json.Marshal(source) + return string(data) +} diff --git a/internal/printer/networkpolicy_test.go b/internal/printer/networkpolicy_test.go new file mode 100644 index 0000000000..615b73c320 --- /dev/null +++ b/internal/printer/networkpolicy_test.go @@ -0,0 +1,163 @@ +/* +Copyright (c) 2020 the Octant contributors. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ + +package printer + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/vmware-tanzu/octant/internal/testutil" + "github.com/vmware-tanzu/octant/pkg/view/component" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_NetworkPolicyListHandler(t *testing.T) { + cols := component.NewTableCols("Name", "Labels", "Age") + now := testutil.Time() + + labels := map[string]string{ + "app": "testing", + } + + object := testutil.CreateNetworkPolicy("networkPolicy") + object.Labels = labels + object.CreationTimestamp = metav1.Time{Time: now} + + list := &networkingv1.NetworkPolicyList{ + Items: []networkingv1.NetworkPolicy{*object}, + } + + cases := []struct { + name string + list *networkingv1.NetworkPolicyList + expected *component.Table + isErr bool + }{ + { + name: "in general", + list: list, + expected: component.NewTableWithRows("Network Policies", "We couldn't find any network policies!", cols, + []component.TableRow{ + { + "Name": component.NewLink("", "networkPolicy", "/networkPolicy"), + "Labels": component.NewLabels(labels), + "Age": component.NewTimestamp(now), + }, + }), + }, + { + name: "list is nil", + list: nil, + isErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + tpo := newTestPrinterOptions(controller) + printOptions := tpo.ToOptions() + + ctx := context.Background() + + if tc.list != nil { + tpo.PathForObject(&tc.list.Items[0], tc.list.Items[0].Name, "/networkPolicy") + } + + got, err := NetworkPolicyListHandler(ctx, tc.list, printOptions) + if tc.isErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + component.AssertEqual(t, tc.expected, got) + }) + } +} + +func Test_NetworkPolicyConfiguration(t *testing.T) { + selector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "my_app", + }, + } + + networkPolicy := testutil.CreateNetworkPolicy("networkPolicy") + networkPolicy.Spec.PodSelector = selector + networkPolicy.Spec.PolicyTypes = []networkingv1.PolicyType{ + networkingv1.PolicyTypeIngress, + networkingv1.PolicyTypeEgress, + } + + cases := []struct { + name string + networkPolicy *networkingv1.NetworkPolicy + expected component.Component + isErr bool + }{ + { + name: "general", + networkPolicy: networkPolicy, + expected: component.NewSummary("Configuration", []component.SummarySection{ + { + Header: "Policy Types", + Content: component.NewText("Ingress, Egress"), + }, + { + Header: "Selectors", + Content: component.NewSelectors([]component.Selector{component.NewLabelSelector("app", "my_app")}), + }, + }...), + }, + { + name: "nil networkPolicy", + networkPolicy: nil, + isErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + npc := NewNetworkPolicyConfiguration(tc.networkPolicy) + summary, err := npc.Create() + if tc.isErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + component.AssertEqual(t, tc.expected, summary) + }) + } +} + +func Test_NetworkPolicySummaryStatus(t *testing.T) { + networkPolicy := testutil.CreateNetworkPolicy("networkPolicy") + + got, err := createNetworkPolicySummaryStatus(networkPolicy) + require.NoError(t, err) + + ingressTable, err := createIngressRules(networkPolicy.Spec.Ingress) + require.NoError(t, err) + + egressTable, err := createEgressRules(networkPolicy.Spec.Egress) + require.NoError(t, err) + + sections := component.SummarySections{ + {Header: "Allowing ingress traffic", Content: ingressTable}, + {Header: "Allowing egress traffic", Content: egressTable}, + } + + expected := component.NewSummary("Status", sections...) + assert.Equal(t, expected, got) +} diff --git a/internal/queryer/queryer.go b/internal/queryer/queryer.go index 6c5d1cbce6..153a0c58e7 100644 --- a/internal/queryer/queryer.go +++ b/internal/queryer/queryer.go @@ -304,6 +304,7 @@ var allowed = []schema.GroupVersionKind{ gvk.HorizontalPodAutoscaler, gvk.Ingress, gvk.Service, + gvk.NetworkPolicy, gvk.ConfigMap, gvk.PersistentVolumeClaim, gvk.Secret, diff --git a/internal/testutil/generator.go b/internal/testutil/generator.go index 4e0f4990c1..3e802d79fc 100644 --- a/internal/testutil/generator.go +++ b/internal/testutil/generator.go @@ -12,6 +12,7 @@ import ( batchv1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/v1" extv1beta1 "k8s.io/api/extensions/v1beta1" + networkingv1 "k8s.io/api/networking/v1" rbacv1 "k8s.io/api/rbac/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apimachinery/pkg/api/resource" @@ -200,7 +201,7 @@ func CreateJob(name string) *batchv1.Job { } } -// CreateNode creates a namespace +// CreateNamespace creates a namespace func CreateNamespace(name string) *corev1.Namespace { return &corev1.Namespace{ TypeMeta: genTypeMeta(gvk.Namespace), @@ -208,6 +209,14 @@ func CreateNamespace(name string) *corev1.Namespace { } } +// CreateNetworkPolicy creates a network policy +func CreateNetworkPolicy(name string) *networkingv1.NetworkPolicy { + return &networkingv1.NetworkPolicy{ + TypeMeta: genTypeMeta(gvk.Namespace), + ObjectMeta: genObjectMeta(name, true), + } +} + // CreateNode creates a node func CreateNode(name string) *corev1.Node { return &corev1.Node{ diff --git a/pkg/icon/icon.go b/pkg/icon/icon.go index 09ca506771..4b91160aa2 100644 --- a/pkg/icon/icon.go +++ b/pkg/icon/icon.go @@ -48,6 +48,7 @@ const ( OverviewHorizontalPodAutoscaler = "hpa" OverviewIngress = "ing" OverviewJob = "job" + OverviewNetworkPolicy = "netpol" OverviewPersistentVolumeClaim = "pvc" OverviewPod = "pod" OverviewReplicaSet = "rs" diff --git a/web/src/app/modules/shared/components/presentation/datagrid/datagrid.component.ts b/web/src/app/modules/shared/components/presentation/datagrid/datagrid.component.ts index e339b2330f..bcbbba54e9 100644 --- a/web/src/app/modules/shared/components/presentation/datagrid/datagrid.component.ts +++ b/web/src/app/modules/shared/components/presentation/datagrid/datagrid.component.ts @@ -59,9 +59,11 @@ export class DatagridComponent implements OnChanges { const current = changes.view.currentValue as TableView; this.columns = current.config.columns.map(column => column.name); + if (current.config.rows) { this.rowsWithMetadata = this.getRowsWithMetadata(current.config.rows); } + this.placeholder = current.config.emptyContent; this.lastUpdated = new Date(); this.loading = current.config.loading; From e226426e594f8953989b93d2636cd02d30592cf5 Mon Sep 17 00:00:00 2001 From: GuessWhoSamFoo Date: Wed, 8 Apr 2020 20:14:01 -0700 Subject: [PATCH 2/2] Add describer for generalized network policy cases Signed-off-by: GuessWhoSamFoo --- internal/printer/networkpolicy.go | 92 +++++++++++++++++++++++++- internal/printer/networkpolicy_test.go | 1 + 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/internal/printer/networkpolicy.go b/internal/printer/networkpolicy.go index a34fbaee25..c30e234b36 100644 --- a/internal/printer/networkpolicy.go +++ b/internal/printer/networkpolicy.go @@ -15,6 +15,7 @@ import ( "github.com/vmware-tanzu/octant/pkg/view/component" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kLabels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -240,6 +241,10 @@ func createNetworkPolicySummaryStatus(networkPolicy *networkingv1.NetworkPolicy) } summary := component.NewSummary("Status", []component.SummarySection{ + { + Header: "Summary", + Content: policyDescriber(networkPolicy), + }, { Header: "Allowing ingress traffic", Content: ingressTable, @@ -393,14 +398,18 @@ func createNetworkPodListView(ctx context.Context, networkPolicy *networkingv1.N } if peer.PodSelector != nil { - podSelectorList = append(podSelectorList, peer.NamespaceSelector) + podSelectorList = append(podSelectorList, peer.PodSelector) } } } } } - // Check for matching namespaces + // Case with only pod selectors + if len(selectorsList) == 0 { + keyList = append(keyList, store.Key{Namespace: networkPolicy.Namespace, APIVersion: "v1", Kind: "Pod"}) + } + // Case with namespace and pod selectors for _, selector := range selectorsList { s, err := metav1.LabelSelectorAsMap(selector) if err != nil { @@ -442,6 +451,85 @@ func createNetworkPodListView(ctx context.Context, networkPolicy *networkingv1.N return PodListHandler(ctx, podList, options) } +func policyDescriber(networkPolicy *networkingv1.NetworkPolicy) *component.Text { + policyMap := map[*networkingv1.NetworkPolicySpec]string{ + &networkingv1.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{}, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }: "Deny all traffic from all other namespaces", + } + + if &networkPolicy.Spec.PodSelector != nil { + podSelectorPolicyMap := map[*networkingv1.NetworkPolicySpec]string{ + &networkingv1.NetworkPolicySpec{ + PodSelector: networkPolicy.Spec.PodSelector, + Ingress: []networkingv1.NetworkPolicyIngressRule{}, + }: "Deny all traffic to application", + &networkingv1.NetworkPolicySpec{ + PodSelector: networkPolicy.Spec.PodSelector, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + {}, + }, + }: "Allow all traffic to application", + &networkingv1.NetworkPolicySpec{ + PodSelector: networkPolicy.Spec.PodSelector, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{ + { + NamespaceSelector: &metav1.LabelSelector{}, + }, + }, + }, + }, + }: "Allow traffic to application from all namespaces", + &networkingv1.NetworkPolicySpec{ + PodSelector: networkPolicy.Spec.PodSelector, + Ingress: []networkingv1.NetworkPolicyIngressRule{ + { + From: []networkingv1.NetworkPolicyPeer{}, + }, + }, + PolicyTypes: []networkingv1.PolicyType{ + networkingv1.PolicyTypeIngress, + }, + }: "Allow traffic from external clients", + } + + for k, v := range podSelectorPolicyMap { + policyMap[k] = v + } + + if networkPolicy.Spec.Egress != nil { + for _, egress := range networkPolicy.Spec.Egress { + for _, peer := range egress.To { + if peer.NamespaceSelector != nil { + denyExternalSpec := &networkingv1.NetworkPolicySpec{ + PodSelector: networkPolicy.Spec.PodSelector, + Egress: networkPolicy.Spec.Egress, + PolicyTypes: []networkingv1.PolicyType{ + networkingv1.PolicyTypeEgress, + }, + } + + policyMap[denyExternalSpec] = "Deny external egress traffic" + } + } + } + } + } + + for policySpec, policyDescription := range policyMap { + if apiequality.Semantic.DeepEqual(&networkPolicy.Spec, policySpec) { + return component.NewText(policyDescription) + } + } + + return component.NewText("---") +} + func describeIPBlock(source interface{}) string { data, _ := json.Marshal(source) return string(data) diff --git a/internal/printer/networkpolicy_test.go b/internal/printer/networkpolicy_test.go index 615b73c320..35392923d6 100644 --- a/internal/printer/networkpolicy_test.go +++ b/internal/printer/networkpolicy_test.go @@ -154,6 +154,7 @@ func Test_NetworkPolicySummaryStatus(t *testing.T) { require.NoError(t, err) sections := component.SummarySections{ + {Header: "Summary", Content: component.NewText("Deny all traffic to application")}, {Header: "Allowing ingress traffic", Content: ingressTable}, {Header: "Allowing egress traffic", Content: egressTable}, }