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 }