github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/advise/networkpolicy/advisor/advisor.go (about)

     1  // Copyright 2019-2021 The Inspektor Gadget authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package advisor
    16  
    17  import (
    18  	"bufio"
    19  	"bytes"
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"sort"
    24  	"strings"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	networkingv1 "k8s.io/api/networking/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/util/intstr"
    30  	k8syaml "sigs.k8s.io/yaml"
    31  
    32  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/network/types"
    33  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    34  )
    35  
    36  var defaultLabelsToIgnore = map[string]struct{}{
    37  	"controller-revision-hash": {},
    38  	"pod-template-generation":  {},
    39  	"pod-template-hash":        {},
    40  }
    41  
    42  type NetworkPolicyAdvisor struct {
    43  	Events []types.Event
    44  
    45  	LabelsToIgnore map[string]struct{}
    46  
    47  	Policies []networkingv1.NetworkPolicy
    48  }
    49  
    50  func NewAdvisor() *NetworkPolicyAdvisor {
    51  	return &NetworkPolicyAdvisor{
    52  		LabelsToIgnore: defaultLabelsToIgnore,
    53  	}
    54  }
    55  
    56  func (a *NetworkPolicyAdvisor) LoadFile(filename string) error {
    57  	buf, err := os.ReadFile(filename)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	return a.LoadBuffer(buf)
    62  }
    63  
    64  func (a *NetworkPolicyAdvisor) LoadBuffer(buf []byte) error {
    65  	/* Try to read the file as an array */
    66  	events := []types.Event{}
    67  	err := json.Unmarshal(buf, &events)
    68  	if err == nil {
    69  		a.Events = events
    70  		return nil
    71  	}
    72  
    73  	/* If it fails, read by line */
    74  	events = nil
    75  	line := 0
    76  	scanner := bufio.NewScanner(bytes.NewReader(buf))
    77  	for scanner.Scan() {
    78  		event := types.Event{}
    79  		text := strings.TrimSpace(scanner.Text())
    80  		if len(text) == 0 {
    81  			continue
    82  		}
    83  		line++
    84  		err = json.Unmarshal([]byte(text), &event)
    85  		if err != nil {
    86  			return fmt.Errorf("parsing line %d: %w", line, err)
    87  		}
    88  		events = append(events, event)
    89  	}
    90  
    91  	if err := scanner.Err(); err != nil {
    92  		return err
    93  	}
    94  
    95  	a.Events = events
    96  
    97  	return nil
    98  }
    99  
   100  /* labelFilteredKeyList returns a sorted list of label keys but without the labels to
   101   * ignore.
   102   */
   103  func (a *NetworkPolicyAdvisor) labelFilteredKeyList(labels map[string]string) []string {
   104  	keys := make([]string, 0, len(labels))
   105  	for k := range labels {
   106  		if _, ok := a.LabelsToIgnore[k]; ok {
   107  			continue
   108  		}
   109  		keys = append(keys, k)
   110  	}
   111  	sort.Strings(keys)
   112  
   113  	return keys
   114  }
   115  
   116  func (a *NetworkPolicyAdvisor) labelFilter(labels map[string]string) map[string]string {
   117  	ret := map[string]string{}
   118  	for k := range labels {
   119  		if _, ok := a.LabelsToIgnore[k]; ok {
   120  			continue
   121  		}
   122  		ret[k] = labels[k]
   123  	}
   124  	return ret
   125  }
   126  
   127  /* labelKeyString returns a sorted list of labels in a single string.
   128   * label1=value1,label2=value2
   129   */
   130  func (a *NetworkPolicyAdvisor) labelKeyString(labels map[string]string) (ret string) {
   131  	keys := a.labelFilteredKeyList(labels)
   132  
   133  	for index, k := range keys {
   134  		sep := ","
   135  		if index == 0 {
   136  			sep = ""
   137  		}
   138  		ret += fmt.Sprintf("%s%s=%s", sep, k, labels[k])
   139  	}
   140  	return
   141  }
   142  
   143  /* localPodKey returns a key that can be used to group pods together:
   144   * namespace:label1=value1,label2=value2
   145   */
   146  func (a *NetworkPolicyAdvisor) localPodKey(e types.Event) (ret string) {
   147  	return e.K8s.Namespace + ":" + a.labelKeyString(e.PodLabels)
   148  }
   149  
   150  func (a *NetworkPolicyAdvisor) networkPeerKey(e types.Event) (ret string) {
   151  	if e.DstEndpoint.Kind == eventtypes.EndpointKindPod {
   152  		ret = string(e.DstEndpoint.Kind) + ":" + e.DstEndpoint.Namespace + ":" + a.labelKeyString(e.DstEndpoint.PodLabels)
   153  	} else if e.DstEndpoint.Kind == eventtypes.EndpointKindService {
   154  		ret = string(e.DstEndpoint.Kind) + ":" + e.DstEndpoint.Namespace + ":" + a.labelKeyString(e.DstEndpoint.PodLabels)
   155  	} else if e.DstEndpoint.Kind == eventtypes.EndpointKindRaw {
   156  		ret = string(e.DstEndpoint.Kind) + ":" + e.DstEndpoint.Addr
   157  	}
   158  	return fmt.Sprintf("%s:%d", ret, e.Port)
   159  }
   160  
   161  func (a *NetworkPolicyAdvisor) eventToRule(e types.Event) (ports []networkingv1.NetworkPolicyPort, peers []networkingv1.NetworkPolicyPeer) {
   162  	port := intstr.FromInt(int(e.Port))
   163  	protocol := v1.Protocol(strings.ToUpper(e.Proto))
   164  	ports = []networkingv1.NetworkPolicyPort{
   165  		{
   166  			Port:     &port,
   167  			Protocol: &protocol,
   168  		},
   169  	}
   170  	if e.DstEndpoint.Kind == eventtypes.EndpointKindPod {
   171  		peers = []networkingv1.NetworkPolicyPeer{
   172  			{
   173  				PodSelector: &metav1.LabelSelector{MatchLabels: a.labelFilter(e.DstEndpoint.PodLabels)},
   174  			},
   175  		}
   176  		if e.K8s.Namespace != e.DstEndpoint.Namespace {
   177  			peers[0].NamespaceSelector = &metav1.LabelSelector{
   178  				MatchLabels: map[string]string{
   179  					// Kubernetes 1.22 is guaranteed to add the following label on namespaces:
   180  					// kubernetes.io/metadata.name=obj.Name
   181  					// See:
   182  					// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2161-apiserver-default-labels#proposal
   183  					"kubernetes.io/metadata.name": e.DstEndpoint.Namespace,
   184  				},
   185  			}
   186  		}
   187  	} else if e.DstEndpoint.Kind == eventtypes.EndpointKindService {
   188  		peers = []networkingv1.NetworkPolicyPeer{
   189  			{
   190  				PodSelector: &metav1.LabelSelector{MatchLabels: e.DstEndpoint.PodLabels},
   191  			},
   192  		}
   193  		if e.K8s.Namespace != e.DstEndpoint.Namespace {
   194  			peers[0].NamespaceSelector = &metav1.LabelSelector{
   195  				MatchLabels: map[string]string{
   196  					// Kubernetes 1.22 is guaranteed to add the following label on namespaces:
   197  					// kubernetes.io/metadata.name=obj.Name
   198  					// See:
   199  					// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2161-apiserver-default-labels#proposal
   200  					"kubernetes.io/metadata.name": e.DstEndpoint.Namespace,
   201  				},
   202  			}
   203  		}
   204  	} else if e.DstEndpoint.Kind == eventtypes.EndpointKindRaw {
   205  		if e.DstEndpoint.Addr == "127.0.0.1" {
   206  			// No need to generate a network policy for localhost
   207  			peers = []networkingv1.NetworkPolicyPeer{}
   208  		} else {
   209  			peers = []networkingv1.NetworkPolicyPeer{
   210  				{
   211  					IPBlock: &networkingv1.IPBlock{
   212  						CIDR: e.DstEndpoint.Addr + "/32",
   213  					},
   214  				},
   215  			}
   216  		}
   217  	} else {
   218  		panic("unknown event")
   219  	}
   220  	return
   221  }
   222  
   223  func sortIngressRules(rules []networkingv1.NetworkPolicyIngressRule) []networkingv1.NetworkPolicyIngressRule {
   224  	sort.Slice(rules, func(i, j int) bool {
   225  		ri, rj := rules[i], rules[j]
   226  
   227  		// No need to support all network policies, but only the ones
   228  		// generated by eventToRule()
   229  		if len(ri.Ports) != 1 || len(rj.Ports) != 1 {
   230  			panic("rules with multiple ports")
   231  		}
   232  		if ri.Ports[0].Protocol == nil || rj.Ports[0].Protocol == nil {
   233  			panic("rules without protocol")
   234  		}
   235  
   236  		switch {
   237  		case *ri.Ports[0].Protocol != *rj.Ports[0].Protocol:
   238  			return *ri.Ports[0].Protocol < *rj.Ports[0].Protocol
   239  		case ri.Ports[0].Port.IntVal != rj.Ports[0].Port.IntVal:
   240  			return ri.Ports[0].Port.IntVal < rj.Ports[0].Port.IntVal
   241  		default:
   242  			yamlOutput1, _ := k8syaml.Marshal(ri)
   243  			yamlOutput2, _ := k8syaml.Marshal(rj)
   244  			return string(yamlOutput1) < string(yamlOutput2)
   245  		}
   246  	})
   247  	return rules
   248  }
   249  
   250  func sortEgressRules(rules []networkingv1.NetworkPolicyEgressRule) []networkingv1.NetworkPolicyEgressRule {
   251  	sort.Slice(rules, func(i, j int) bool {
   252  		ri, rj := rules[i], rules[j]
   253  
   254  		// No need to support all network policies, but only the ones
   255  		// generated by eventToRule()
   256  		if len(ri.Ports) != 1 || len(rj.Ports) != 1 {
   257  			panic("rules with multiple ports")
   258  		}
   259  		if ri.Ports[0].Protocol == nil || rj.Ports[0].Protocol == nil {
   260  			panic("rules without protocol")
   261  		}
   262  
   263  		switch {
   264  		case *ri.Ports[0].Protocol != *rj.Ports[0].Protocol:
   265  			return *ri.Ports[0].Protocol < *rj.Ports[0].Protocol
   266  		case ri.Ports[0].Port.IntVal != rj.Ports[0].Port.IntVal:
   267  			return ri.Ports[0].Port.IntVal < rj.Ports[0].Port.IntVal
   268  		default:
   269  			yamlOutput1, _ := k8syaml.Marshal(ri)
   270  			yamlOutput2, _ := k8syaml.Marshal(rj)
   271  			return string(yamlOutput1) < string(yamlOutput2)
   272  		}
   273  	})
   274  	return rules
   275  }
   276  
   277  func (a *NetworkPolicyAdvisor) GeneratePolicies() {
   278  	eventsBySource := map[string][]types.Event{}
   279  	for _, e := range a.Events {
   280  		if e.Type != eventtypes.NORMAL {
   281  			continue
   282  		}
   283  		if e.PktType != "HOST" && e.PktType != "OUTGOING" {
   284  			continue
   285  		}
   286  		// ignore events on the host netns
   287  		if e.K8s.HostNetwork {
   288  			continue
   289  		}
   290  
   291  		// Kubernetes Network Policies can't block traffic from a pod's
   292  		// own resident node. Therefore we must not generate a network
   293  		// policy in that case.
   294  		if e.PktType == "HOST" && e.PodHostIP == e.DstEndpoint.Addr {
   295  			continue
   296  		}
   297  
   298  		key := a.localPodKey(e)
   299  		if _, ok := eventsBySource[key]; ok {
   300  			eventsBySource[key] = append(eventsBySource[key], e)
   301  		} else {
   302  			eventsBySource[key] = []types.Event{e}
   303  		}
   304  	}
   305  
   306  	for _, events := range eventsBySource {
   307  		egressNetworkPeer := map[string]types.Event{}
   308  		ingressNetworkPeer := map[string]types.Event{}
   309  		for _, e := range events {
   310  			key := a.networkPeerKey(e)
   311  			if e.PktType == "OUTGOING" {
   312  				if _, ok := egressNetworkPeer[key]; ok {
   313  					continue
   314  				}
   315  
   316  				egressNetworkPeer[key] = e
   317  			} else if e.PktType == "HOST" {
   318  				if _, ok := ingressNetworkPeer[key]; ok {
   319  					continue
   320  				}
   321  
   322  				ingressNetworkPeer[key] = e
   323  			}
   324  		}
   325  		egressPolicies := []networkingv1.NetworkPolicyEgressRule{}
   326  		for _, p := range egressNetworkPeer {
   327  			ports, peers := a.eventToRule(p)
   328  			if len(peers) > 0 {
   329  				rule := networkingv1.NetworkPolicyEgressRule{
   330  					Ports: ports,
   331  					To:    peers,
   332  				}
   333  				egressPolicies = append(egressPolicies, rule)
   334  			}
   335  		}
   336  		ingressPolicies := []networkingv1.NetworkPolicyIngressRule{}
   337  		for _, p := range ingressNetworkPeer {
   338  			ports, peers := a.eventToRule(p)
   339  			if len(peers) > 0 {
   340  				rule := networkingv1.NetworkPolicyIngressRule{
   341  					Ports: ports,
   342  					From:  peers,
   343  				}
   344  				ingressPolicies = append(ingressPolicies, rule)
   345  			}
   346  		}
   347  
   348  		name := events[0].K8s.PodName
   349  		if events[0].PodOwner != "" {
   350  			name = events[0].PodOwner
   351  		}
   352  		name += "-network"
   353  		policy := networkingv1.NetworkPolicy{
   354  			TypeMeta: metav1.TypeMeta{
   355  				APIVersion: "networking.k8s.io/v1",
   356  				Kind:       "NetworkPolicy",
   357  			},
   358  			ObjectMeta: metav1.ObjectMeta{
   359  				Name:      name,
   360  				Namespace: events[0].K8s.Namespace,
   361  				Labels:    map[string]string{},
   362  			},
   363  			Spec: networkingv1.NetworkPolicySpec{
   364  				PodSelector: metav1.LabelSelector{MatchLabels: a.labelFilter(events[0].PodLabels)},
   365  				PolicyTypes: []networkingv1.PolicyType{"Ingress", "Egress"},
   366  				Ingress:     sortIngressRules(ingressPolicies),
   367  				Egress:      sortEgressRules(egressPolicies),
   368  			},
   369  		}
   370  		a.Policies = append(a.Policies, policy)
   371  	}
   372  
   373  	sort.Slice(a.Policies, func(i, j int) bool {
   374  		return a.Policies[i].Name < a.Policies[j].Name
   375  	})
   376  }
   377  
   378  func (a *NetworkPolicyAdvisor) FormatPolicies() (out string) {
   379  	for i, p := range a.Policies {
   380  		yamlOutput, err := k8syaml.Marshal(p)
   381  		if err != nil {
   382  			continue
   383  		}
   384  		sep := "---\n"
   385  		if i == len(a.Policies)-1 {
   386  			sep = ""
   387  		}
   388  		out += fmt.Sprintf("%s%s", string(yamlOutput), sep)
   389  	}
   390  	return
   391  }