linkerd2/cli/cmd/identity.go

228 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cmd
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"strings"
"github.com/grantae/certinfo"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
var emitLog bool
type certificate struct {
pod string
container string
Certificate []*x509.Certificate
err error
}
type identityOptions struct {
pod string
namespace string
selector string
}
func newIdentityOptions() *identityOptions {
return &identityOptions{
pod: "",
namespace: "",
selector: "",
}
}
func newCmdIdentity() *cobra.Command {
emitLog = false
options := newIdentityOptions()
cmd := &cobra.Command{
Use: "identity [flags] (PODS)",
Short: "Display the certificate(s) of one or more selected pod(s)",
Long: `Display the certificate(s) of one or more selected pod(s).
This command initiates a port-forward to a given pod or a set of pods and fetches the TLS certificate.
`,
Example: `
# Get certificate from pod foo-bar in the default namespace.
linkerd identity foo-bar
# Get certificate from all pods with the label name=nginx
linkerd identity -l name=nginx
`,
RunE: func(cmd *cobra.Command, args []string) error {
k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
if err != nil {
return err
}
if len(args) == 0 && options.selector == "" {
return fmt.Errorf("Provide the pod name argument or use the selector flag")
}
pods, err := getPods(cmd.Context(), k8sAPI, options.namespace, options.selector, args)
if err != nil {
return err
}
resultCerts := getCertificate(k8sAPI, pods, k8s.ProxyAdminPortName, emitLog)
if len(resultCerts) == 0 {
fmt.Print("Could not fetch Certificate. Ensure that the pod(s) are meshed by running `linkerd inject`\n")
return nil
}
for i, resultCert := range resultCerts {
fmt.Printf("\nPOD %s (%d of %d)\n\n", resultCert.pod, i+1, len(resultCerts))
if resultCert.err != nil {
fmt.Printf("\n%s\n", resultCert.err)
return nil
}
certChain := resultCert.Certificate
cert := certChain[len(certChain)-1]
result, err := certinfo.CertificateText(cert)
if err != nil {
fmt.Printf("\n%s\n", err)
return nil
}
fmt.Print(result)
}
return nil
},
}
cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace to use for --proxy versions (default: all namespaces)")
cmd.PersistentFlags().StringVarP(&options.selector, "selector", "l", options.selector, "Selector (label query) to filter on, supports =, ==, and != ")
return cmd
}
func getCertificate(k8sAPI *k8s.KubernetesAPI, pods []corev1.Pod, portName string, emitLog bool) []certificate {
var certificates []certificate
for _, pod := range pods {
container, err := getContainerWithPort(pod, portName)
if err != nil {
certificates = append(certificates, certificate{
pod: pod.GetName(),
err: err,
})
return certificates
}
cert, err := getContainerCertificate(k8sAPI, pod, container, portName, emitLog)
certificates = append(certificates, certificate{
pod: pod.GetName(),
container: container.Name,
Certificate: cert,
err: err,
})
}
return certificates
}
func getContainerWithPort(pod corev1.Pod, portName string) (corev1.Container, error) {
var container corev1.Container
if pod.Status.Phase != corev1.PodRunning {
return container, fmt.Errorf("pod not running: %s", pod.GetName())
}
for _, c := range pod.Spec.Containers {
if c.Name != k8s.ProxyContainerName {
continue
}
for _, p := range c.Ports {
if p.Name == portName {
return c, nil
}
}
}
return container, fmt.Errorf("failed to find %s port in %s container for given pod spec", portName, k8s.ProxyContainerName)
}
func getContainerCertificate(k8sAPI *k8s.KubernetesAPI, pod corev1.Pod, container corev1.Container, portName string, emitLog bool) ([]*x509.Certificate, error) {
portForward, err := k8s.NewContainerMetricsForward(k8sAPI, pod, container, emitLog, portName)
if err != nil {
return nil, err
}
defer portForward.Stop()
if err = portForward.Init(); err != nil {
fmt.Fprintf(os.Stderr, "Error running port-forward: %s", err)
return nil, err
}
certURL := portForward.URLFor("")
return getCertResponse(certURL, pod)
}
func getCertResponse(url string, pod corev1.Pod) ([]*x509.Certificate, error) {
serverName, err := getServerName(pod, k8s.ProxyContainerName)
if err != nil {
return nil, err
}
connURL := strings.Trim(url, "http://")
conn, err := tls.Dial("tcp", connURL, &tls.Config{
InsecureSkipVerify: true,
ServerName: serverName,
})
if err != nil {
return nil, err
}
cert := conn.ConnectionState().PeerCertificates
return cert, nil
}
func getServerName(pod corev1.Pod, containerName string) (string, error) {
if pod.Status.Phase != corev1.PodRunning {
return "", fmt.Errorf("pod not running: %s", pod.GetName())
}
var l5dns string
var l5dtrustdomain string
podsa := pod.Spec.ServiceAccountName
podns := pod.ObjectMeta.Namespace
for _, c := range pod.Spec.Containers {
if c.Name == containerName {
for _, env := range c.Env {
if env.Name == "_l5d_ns" {
l5dns = env.Value
}
if env.Name == "_l5d_trustdomain" {
l5dtrustdomain = env.Value
}
}
}
}
serverName := fmt.Sprintf("%s.%s.serviceaccount.identity.%s.%s", podsa, podns, l5dns, l5dtrustdomain)
return serverName, nil
}
func getPods(ctx context.Context, clientset kubernetes.Interface, namespace string, selector string, args []string) ([]corev1.Pod, error) {
if len(args) > 0 {
var pods []corev1.Pod
for _, arg := range args {
pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, arg, metav1.GetOptions{})
if err != nil {
return nil, err
}
pods = append(pods, *pod)
}
return pods, nil
}
podList, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
LabelSelector: selector,
})
if err != nil {
return nil, err
}
return podList.Items, nil
}