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  }