mirror of https://github.com/linkerd/linkerd2.git
228 lines
6.0 KiB
Go
228 lines
6.0 KiB
Go
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
|
||
}
|