...

Source file src/github.com/linkerd/linkerd2/cli/cmd/identity.go

Documentation: github.com/linkerd/linkerd2/cli/cmd

     1  package cmd
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"fmt"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/grantae/certinfo"
    12  	pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
    13  	"github.com/linkerd/linkerd2/pkg/k8s"
    14  	"github.com/spf13/cobra"
    15  	corev1 "k8s.io/api/core/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/client-go/kubernetes"
    18  )
    19  
    20  var emitLog bool
    21  
    22  type certificate struct {
    23  	pod         string
    24  	container   string
    25  	Certificate []*x509.Certificate
    26  	err         error
    27  }
    28  
    29  type identityOptions struct {
    30  	pod       string
    31  	namespace string
    32  	selector  string
    33  }
    34  
    35  func newIdentityOptions() *identityOptions {
    36  	return &identityOptions{
    37  		pod:      "",
    38  		selector: "",
    39  	}
    40  }
    41  
    42  func newCmdIdentity() *cobra.Command {
    43  	emitLog = false
    44  	options := newIdentityOptions()
    45  
    46  	cmd := &cobra.Command{
    47  		Use:   "identity [flags] (PODS)",
    48  		Short: "Display the certificate(s) of one or more selected pod(s)",
    49  		Long: `Display the certificate(s) of one or more selected pod(s).
    50  
    51  This command initiates a port-forward to a given pod or a set of pods and fetches the TLS certificate.
    52  		`,
    53  		Example: `
    54   # Get certificate from pod foo-bar in the default namespace.
    55   linkerd identity foo-bar
    56  
    57   # Get certificate from all pods with the label name=nginx
    58   linkerd identity -l name=nginx
    59  		`,
    60  		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    61  			k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
    62  			if err != nil {
    63  				return nil, cobra.ShellCompDirectiveError
    64  			}
    65  
    66  			if options.namespace == "" {
    67  				options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
    68  			}
    69  
    70  			cc := k8s.NewCommandCompletion(k8sAPI, options.namespace)
    71  
    72  			// insert pod resource type as first argument to suggest
    73  			// pod resources only
    74  			args = append([]string{k8s.Pod}, args...)
    75  			results, err := cc.Complete(args, toComplete)
    76  			if err != nil {
    77  				return nil, cobra.ShellCompDirectiveError
    78  			}
    79  
    80  			return results, cobra.ShellCompDirectiveDefault
    81  		},
    82  		RunE: func(cmd *cobra.Command, args []string) error {
    83  			if options.namespace == "" {
    84  				options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
    85  			}
    86  			k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
    87  			if err != nil {
    88  				return err
    89  			}
    90  
    91  			if len(args) == 0 && options.selector == "" {
    92  				return fmt.Errorf("Provide the pod name argument or use the selector flag")
    93  			}
    94  
    95  			pods, err := getPods(cmd.Context(), k8sAPI, options.namespace, options.selector, args)
    96  			if err != nil {
    97  				return err
    98  			}
    99  
   100  			resultCerts := getCertificate(k8sAPI, pods, k8s.ProxyAdminPortName, emitLog)
   101  			if len(resultCerts) == 0 {
   102  				fmt.Print("Could not fetch Certificate. Ensure that the pod(s) are meshed by running `linkerd inject`\n")
   103  				return nil
   104  			}
   105  			for i, resultCert := range resultCerts {
   106  				fmt.Printf("\nPOD %s (%d of %d)\n\n", resultCert.pod, i+1, len(resultCerts))
   107  				if resultCert.err != nil {
   108  					fmt.Printf("\n%s\n", resultCert.err)
   109  					return nil
   110  				}
   111  				for _, cert := range resultCert.Certificate {
   112  					if cert.IsCA {
   113  						continue
   114  					}
   115  					result, err := certinfo.CertificateText(cert)
   116  					if err != nil {
   117  						fmt.Printf("\n%s\n", err)
   118  						return nil
   119  					}
   120  					fmt.Print(result)
   121  				}
   122  			}
   123  			return nil
   124  		},
   125  	}
   126  
   127  	cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace of the pod")
   128  	cmd.PersistentFlags().StringVarP(&options.selector, "selector", "l", options.selector, "Selector (label query) to filter on, supports ‘=’, ‘==’, and ‘!=’ ")
   129  
   130  	pkgcmd.ConfigureNamespaceFlagCompletion(cmd, []string{"namespace"},
   131  		kubeconfigPath, impersonate, impersonateGroup, kubeContext)
   132  
   133  	return cmd
   134  }
   135  
   136  func getCertificate(k8sAPI *k8s.KubernetesAPI, pods []corev1.Pod, portName string, emitLog bool) []certificate {
   137  	var certificates []certificate
   138  	for _, pod := range pods {
   139  		container, err := getContainerWithPort(pod, portName)
   140  		if err != nil {
   141  			certificates = append(certificates, certificate{
   142  				pod: pod.GetName(),
   143  				err: err,
   144  			})
   145  			return certificates
   146  		}
   147  		cert, err := getContainerCertificate(k8sAPI, pod, container, portName, emitLog)
   148  		certificates = append(certificates, certificate{
   149  			pod:         pod.GetName(),
   150  			container:   container.Name,
   151  			Certificate: cert,
   152  			err:         err,
   153  		})
   154  	}
   155  	return certificates
   156  }
   157  
   158  func getContainerWithPort(pod corev1.Pod, portName string) (corev1.Container, error) {
   159  	var container corev1.Container
   160  	if pod.Status.Phase != corev1.PodRunning {
   161  		return container, fmt.Errorf("pod not running: %s", pod.GetName())
   162  	}
   163  
   164  	containers := append(pod.Spec.InitContainers, pod.Spec.Containers...)
   165  	for _, c := range containers {
   166  		if c.Name != k8s.ProxyContainerName {
   167  			continue
   168  		}
   169  		for _, p := range c.Ports {
   170  			if p.Name == portName {
   171  				return c, nil
   172  			}
   173  		}
   174  	}
   175  	return container, fmt.Errorf("failed to find %s port in %s container for given pod spec", portName, k8s.ProxyContainerName)
   176  }
   177  
   178  func getContainerCertificate(k8sAPI *k8s.KubernetesAPI, pod corev1.Pod, container corev1.Container, portName string, emitLog bool) ([]*x509.Certificate, error) {
   179  	portForward, err := k8s.NewContainerMetricsForward(k8sAPI, pod, container, emitLog, portName)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	defer portForward.Stop()
   185  	if err = portForward.Init(); err != nil {
   186  		fmt.Fprintf(os.Stderr, "Error running port-forward: %s", err)
   187  		return nil, err
   188  	}
   189  
   190  	certURL := portForward.URLFor("")
   191  	return getCertResponse(certURL, pod)
   192  }
   193  
   194  func getCertResponse(url string, pod corev1.Pod) ([]*x509.Certificate, error) {
   195  	serverName, err := k8s.PodIdentity(&pod)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	connURL := strings.TrimPrefix(url, "http://")
   200  	conn, err := tls.Dial("tcp", connURL, &tls.Config{
   201  		// We want to connect directly to a proxy port to dump its certificate. We don't necessarily
   202  		// want to verify the server's certificate, since this is purely for diagnostics and may be
   203  		// used when a proxy's issuer doesn't match the control plane's trust root.
   204  		//nolint:gosec
   205  		InsecureSkipVerify: true,
   206  		ServerName:         serverName,
   207  	})
   208  
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	cert := conn.ConnectionState().PeerCertificates
   214  	return cert, nil
   215  }
   216  
   217  func getPods(ctx context.Context, clientset kubernetes.Interface, namespace string, selector string, args []string) ([]corev1.Pod, error) {
   218  	if len(args) > 0 {
   219  		var pods []corev1.Pod
   220  		for _, arg := range args {
   221  			pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, arg, metav1.GetOptions{})
   222  			if err != nil {
   223  				return nil, err
   224  			}
   225  			pods = append(pods, *pod)
   226  		}
   227  		return pods, nil
   228  	}
   229  
   230  	podList, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
   231  		LabelSelector: selector,
   232  	})
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	return podList.Items, nil
   238  }
   239  

View as plain text