github.com/fafucoder/cilium@v1.6.11/cilium/cmd/policy_trace.go (about) 1 // Copyright 2017-2019 Authors of Cilium 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 cmd 16 17 import ( 18 "fmt" 19 "os" 20 "strconv" 21 "strings" 22 23 . "github.com/cilium/cilium/api/v1/client/policy" 24 "github.com/cilium/cilium/api/v1/models" 25 "github.com/cilium/cilium/pkg/api" 26 "github.com/cilium/cilium/pkg/command" 27 endpointid "github.com/cilium/cilium/pkg/endpoint/id" 28 "github.com/cilium/cilium/pkg/identity" 29 "github.com/cilium/cilium/pkg/k8s" 30 clientset "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned" 31 "github.com/cilium/cilium/pkg/policy/trace" 32 33 "github.com/spf13/cobra" 34 meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 ) 36 37 const ( 38 defaultSecurityID = -1 39 ) 40 41 type supportedKinds string 42 43 var src, dst, dports []string 44 var srcIdentity, dstIdentity int64 45 var srcEndpoint, dstEndpoint, srcK8sPod, dstK8sPod, srcK8sYaml, dstK8sYaml string 46 47 // policyTraceCmd represents the policy_trace command 48 var policyTraceCmd = &cobra.Command{ 49 Use: "trace ( -s <label context> | --src-identity <security identity> | --src-endpoint <endpoint ID> | --src-k8s-pod <namespace:pod-name> | --src-k8s-yaml <path to YAML file> ) ( -d <label context> | --dst-identity <security identity> | --dst-endpoint <endpoint ID> | --dst-k8s-pod <namespace:pod-name> | --dst-k8s-yaml <path to YAML file>) [--dport <port>[/<protocol>]", 50 Short: "Trace a policy decision", 51 Long: `Verifies if the source is allowed to consume 52 destination. Source / destination can be provided as endpoint ID, security ID, Kubernetes Pod, YAML file, set of LABELs. LABEL is represented as 53 SOURCE:KEY[=VALUE]. 54 dports can be can be for example: 80/tcp, 53 or 23/udp. 55 If multiple sources and / or destinations are provided, each source is tested whether there is a policy allowing traffic between it and each destination. 56 --src-k8s-pod and --dst-k8s-pod requires cilium-agent to be running with disable-endpoint-crd option set to "false".`, 57 Run: func(cmd *cobra.Command, args []string) { 58 59 srcSlices := [][]string{} 60 dstSlices := [][]string{} 61 var srcSlice, dstSlice []string 62 var dPorts []*models.Port 63 var err error 64 65 if len(src) == 0 && srcIdentity == defaultSecurityID && srcEndpoint == "" && srcK8sPod == "" && srcK8sYaml == "" { 66 Usagef(cmd, "Missing source argument") 67 } 68 69 if len(dst) == 0 && dstIdentity == defaultSecurityID && dstEndpoint == "" && dstK8sPod == "" && dstK8sYaml == "" { 70 Usagef(cmd, "Missing destination argument") 71 } 72 73 // Parse provided labels 74 if len(src) > 0 { 75 srcSlice, err = parseLabels(src) 76 if err != nil { 77 Fatalf("Invalid source: %s", err) 78 } 79 80 srcSlices = append(srcSlices, srcSlice) 81 } 82 83 if len(dst) > 0 { 84 dstSlice, err = parseLabels(dst) 85 if err != nil { 86 Fatalf("Invalid destination: %s", err) 87 } 88 89 dstSlices = append(dstSlices, dstSlice) 90 } 91 92 if len(dports) > 0 { 93 dPorts, err = parseL4PortsSlice(dports) 94 if err != nil { 95 Fatalf("Invalid destination port: %s", err) 96 } 97 } 98 99 // Parse security identities. 100 if srcIdentity != defaultSecurityID { 101 srcSlice = appendIdentityLabelsToSlice(srcSlice, identity.NumericIdentity(srcIdentity).StringID()) 102 srcSlices = append(srcSlices, srcSlice) 103 } 104 105 if dstIdentity != defaultSecurityID { 106 dstSlice = appendIdentityLabelsToSlice(dstSlice, identity.NumericIdentity(dstIdentity).StringID()) 107 dstSlices = append(dstSlices, dstSlice) 108 } 109 110 // Parse endpoint IDs. 111 if srcEndpoint != "" { 112 srcSlice = appendEpLabelsToSlice(srcSlice, srcEndpoint) 113 srcSlices = append(srcSlices, srcSlice) 114 } 115 116 if dstEndpoint != "" { 117 dstSlice = appendEpLabelsToSlice(dstSlice, dstEndpoint) 118 dstSlices = append(dstSlices, dstSlice) 119 } 120 121 // Parse pod names. 122 if srcK8sPod != "" { 123 id, err := getSecIDFromK8s(srcK8sPod) 124 if err != nil { 125 Fatalf("Cannot get security id from k8s pod name: %s", err) 126 } 127 srcSlice = appendIdentityLabelsToSlice(srcSlice, id) 128 srcSlices = append(srcSlices, srcSlice) 129 } 130 131 if dstK8sPod != "" { 132 id, err := getSecIDFromK8s(dstK8sPod) 133 if err != nil { 134 Fatalf("Cannot get security id from k8s pod name: %s", err) 135 } 136 dstSlice = appendIdentityLabelsToSlice(dstSlice, id) 137 dstSlices = append(dstSlices, dstSlice) 138 } 139 140 // Parse provided YAML files. 141 if srcK8sYaml != "" { 142 srcYamlSlices, err := trace.GetLabelsFromYaml(srcK8sYaml) 143 if err != nil { 144 Fatalf("%s", err) 145 } 146 for _, v := range srcYamlSlices { 147 srcSlices = append(srcSlices, v) 148 } 149 } 150 151 if dstK8sYaml != "" { 152 dstYamlSlices, err := trace.GetLabelsFromYaml(dstK8sYaml) 153 if err != nil { 154 Fatalf("%s", err) 155 } 156 for _, v := range dstYamlSlices { 157 dstSlices = append(dstSlices, v) 158 } 159 } 160 161 for _, v := range srcSlices { 162 for _, w := range dstSlices { 163 search := models.TraceSelector{ 164 From: &models.TraceFrom{ 165 Labels: v, 166 }, 167 To: &models.TraceTo{ 168 Labels: w, 169 Dports: dPorts, 170 }, 171 Verbose: verbose, 172 } 173 174 params := NewGetPolicyResolveParams().WithTraceSelector(&search).WithTimeout(api.ClientTimeout) 175 if scr, err := client.Policy.GetPolicyResolve(params); err != nil { 176 Fatalf("Error while retrieving policy assessment result: %s\n", err) 177 } else if command.OutputJSON() { 178 if err := command.PrintOutput(scr); err != nil { 179 os.Exit(1) 180 } 181 } else if scr != nil && scr.Payload != nil { 182 fmt.Println("----------------------------------------------------------------") 183 fmt.Printf("%s\n", scr.Payload.Log) 184 fmt.Printf("Final verdict: %s\n", strings.ToUpper(scr.Payload.Verdict)) 185 } 186 } 187 } 188 }, 189 } 190 191 func init() { 192 policyCmd.AddCommand(policyTraceCmd) 193 policyTraceCmd.Flags().StringSliceVarP(&src, "src", "s", []string{}, "Source label context") 194 policyTraceCmd.Flags().StringSliceVarP(&dst, "dst", "d", []string{}, "Destination label context") 195 policyTraceCmd.Flags().StringSliceVarP(&dports, "dport", "", []string{}, "L4 destination port to search on outgoing traffic of the source label context and on incoming traffic of the destination label context") 196 policyTraceCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Set tracing to TRACE_VERBOSE") 197 policyTraceCmd.Flags().Int64VarP(&srcIdentity, "src-identity", "", defaultSecurityID, "Source identity") 198 policyTraceCmd.Flags().Int64VarP(&dstIdentity, "dst-identity", "", defaultSecurityID, "Destination identity") 199 policyTraceCmd.Flags().StringVarP(&srcEndpoint, "src-endpoint", "", "", "Source endpoint") 200 policyTraceCmd.Flags().StringVarP(&dstEndpoint, "dst-endpoint", "", "", "Destination endpoint") 201 policyTraceCmd.Flags().StringVarP(&srcK8sPod, "src-k8s-pod", "", "", "Source k8s pod ([namespace:]podname)") 202 policyTraceCmd.Flags().StringVarP(&dstK8sPod, "dst-k8s-pod", "", "", "Destination k8s pod ([namespace:]podname)") 203 policyTraceCmd.Flags().StringVarP(&srcK8sYaml, "src-k8s-yaml", "", "", "Path to YAML file for source") 204 policyTraceCmd.Flags().StringVarP(&dstK8sYaml, "dst-k8s-yaml", "", "", "Path to YAML file for destination") 205 command.AddJSONOutput(policyTraceCmd) 206 } 207 208 func appendIdentityLabelsToSlice(labelSlice []string, secID string) []string { 209 resp, err := client.IdentityGet(secID) 210 if err != nil { 211 Fatalf("%s", err) 212 } 213 return append(labelSlice, resp.Labels...) 214 } 215 216 func appendEpLabelsToSlice(labelSlice []string, epID string) []string { 217 ep, err := client.EndpointGet(epID) 218 if err != nil { 219 Fatalf("Cannot get endpoint corresponding to identifier %s: %s\n", epID, err) 220 } 221 222 lbls := []string{} 223 if ep.Status != nil && ep.Status.Identity != nil && ep.Status.Identity.Labels != nil { 224 lbls = ep.Status.Identity.Labels 225 } 226 227 return append(labelSlice, lbls...) 228 } 229 230 func parseLabels(slice []string) ([]string, error) { 231 if len(slice) == 0 { 232 return nil, fmt.Errorf("No labels provided") 233 } 234 return slice, nil 235 } 236 237 func getSecIDFromK8s(podName string) (string, error) { 238 fmtdPodName := endpointid.NewID(endpointid.PodNamePrefix, podName) 239 _, _, err := endpointid.Parse(fmtdPodName) 240 if err != nil { 241 Fatalf("Cannot parse pod name \"%s\": %s", fmtdPodName, err) 242 } 243 244 splitPodName := strings.Split(podName, ":") 245 if len(splitPodName) < 2 { 246 Fatalf("Improper identifier of pod provided; should be <namespace>:<pod name>") 247 } 248 namespace := splitPodName[0] 249 pod := splitPodName[1] 250 251 // The configuration for the Daemon contains the information needed to access the Kubernetes API. 252 resp, err := client.ConfigGet() 253 if err != nil { 254 Fatalf("Error while retrieving configuration: %s", err) 255 } 256 restConfig, err := k8s.CreateConfigFromAgentResponse(resp) 257 if err != nil { 258 return "", fmt.Errorf("unable to create rest configuration: %s", err) 259 } 260 ciliumK8sClient, err := clientset.NewForConfig(restConfig) 261 if err != nil { 262 return "", fmt.Errorf("unable to create k8s client: %s", err) 263 } 264 265 ep, err := ciliumK8sClient.CiliumV2().CiliumEndpoints(namespace).Get(pod, meta_v1.GetOptions{}) 266 if err != nil { 267 return "", fmt.Errorf("unable to get pod %s in namespace %s", pod, namespace) 268 } 269 270 if ep.Status.Identity == nil { 271 return "", fmt.Errorf("cilium security identity"+ 272 " not set for pod %s in namespace %s", pod, namespace) 273 } 274 275 return strconv.Itoa(int(ep.Status.Identity.ID)), nil 276 } 277 278 // parseL4PortsSlice parses a given `slice` of strings. Each string should be in 279 // the form of `<port>[/<protocol>]`, where the `<port>` is an integer and 280 // `<protocol>` is an optional layer 4 protocol `tcp` or `udp`. In case 281 // `protocol` is not present, or is set to `any`, the parsed port will be set to 282 // `models.PortProtocolAny`. 283 func parseL4PortsSlice(slice []string) ([]*models.Port, error) { 284 rules := []*models.Port{} 285 for _, v := range slice { 286 vSplit := strings.Split(v, "/") 287 var protoStr string 288 switch len(vSplit) { 289 case 1: 290 protoStr = models.PortProtocolANY 291 case 2: 292 protoStr = strings.ToUpper(vSplit[1]) 293 switch protoStr { 294 case models.PortProtocolTCP, models.PortProtocolUDP, models.PortProtocolANY: 295 default: 296 return nil, fmt.Errorf("invalid protocol %q", protoStr) 297 } 298 default: 299 return nil, fmt.Errorf("invalid format %q. Should be <port>[/<protocol>]", v) 300 } 301 portStr := vSplit[0] 302 port, err := strconv.Atoi(portStr) 303 if err != nil { 304 return nil, fmt.Errorf("invalid port %q: %s", portStr, err) 305 } 306 l4 := &models.Port{ 307 Port: uint16(port), 308 Protocol: protoStr, 309 } 310 rules = append(rules, l4) 311 } 312 return rules, nil 313 }