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
73
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
202
203
204
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