...

Source file src/github.com/linkerd/linkerd2/pkg/issuercerts/issuercerts.go

Documentation: github.com/linkerd/linkerd2/pkg/issuercerts

     1  package issuercerts
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"crypto/rsa"
     7  	"crypto/x509"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"time"
    12  
    13  	"k8s.io/client-go/kubernetes"
    14  
    15  	"github.com/linkerd/linkerd2/pkg/k8s"
    16  	"github.com/linkerd/linkerd2/pkg/tls"
    17  	corev1 "k8s.io/api/core/v1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  )
    20  
    21  const keyMissingError = "key %s containing the %s needs to exist in secret %s if --identity-external-issuer=%v"
    22  const expirationWarningThresholdInDays = 60
    23  
    24  // IssuerCertData holds the trust anchors cert data used by the CA
    25  type IssuerCertData struct {
    26  	TrustAnchors string
    27  	IssuerCrt    string
    28  	IssuerKey    string
    29  	Expiry       *time.Time
    30  }
    31  
    32  // FetchIssuerData fetches the issuer data from the linkerd-identity-issuer secrets (used for linkerd.io/tls schemed secrets)
    33  func FetchIssuerData(ctx context.Context, api kubernetes.Interface, trustAnchors, controlPlaneNamespace string) (*IssuerCertData, error) {
    34  	secret, err := api.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, k8s.IdentityIssuerSecretName, metav1.GetOptions{})
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	crt, ok := secret.Data[k8s.IdentityIssuerCrtName]
    40  	if !ok {
    41  		return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerCrtName, "issuer certificate", k8s.IdentityIssuerSecretName, false)
    42  	}
    43  
    44  	key, ok := secret.Data[k8s.IdentityIssuerKeyName]
    45  	if !ok {
    46  		return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerKeyName, "issuer key", k8s.IdentityIssuerSecretName, true)
    47  	}
    48  
    49  	cert, err := tls.DecodePEMCrt(string(crt))
    50  	if err != nil {
    51  		return nil, fmt.Errorf("could not parse issuer certificate: %w", err)
    52  	}
    53  
    54  	return &IssuerCertData{trustAnchors, string(crt), string(key), &cert.Certificate.NotAfter}, nil
    55  }
    56  
    57  // FetchExternalIssuerData fetches the issuer data from the linkerd-identity-issuer secrets (used for kubernetes.io/tls schemed secrets)
    58  func FetchExternalIssuerData(ctx context.Context, api kubernetes.Interface, controlPlaneNamespace string) (*IssuerCertData, error) {
    59  	secret, err := api.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, k8s.IdentityIssuerSecretName, metav1.GetOptions{})
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	anchors, ok := secret.Data[k8s.IdentityIssuerTrustAnchorsNameExternal]
    65  	if !ok {
    66  		return nil, fmt.Errorf(keyMissingError, k8s.IdentityIssuerTrustAnchorsNameExternal, "trust anchors", k8s.IdentityIssuerSecretName, true)
    67  	}
    68  
    69  	crt, ok := secret.Data[corev1.TLSCertKey]
    70  	if !ok {
    71  		return nil, fmt.Errorf(keyMissingError, corev1.TLSCertKey, "issuer certificate", k8s.IdentityIssuerSecretName, true)
    72  	}
    73  
    74  	key, ok := secret.Data[corev1.TLSPrivateKeyKey]
    75  	if !ok {
    76  		return nil, fmt.Errorf(keyMissingError, corev1.TLSPrivateKeyKey, "issuer key", k8s.IdentityIssuerSecretName, true)
    77  	}
    78  
    79  	cert, err := tls.DecodePEMCrt(string(crt))
    80  	if err != nil {
    81  		return nil, fmt.Errorf("could not parse issuer certificate: %w", err)
    82  	}
    83  
    84  	return &IssuerCertData{string(anchors), string(crt), string(key), &cert.Certificate.NotAfter}, nil
    85  }
    86  
    87  // LoadIssuerCrtAndKeyFromFiles loads the issuer certificate and key from files
    88  func LoadIssuerCrtAndKeyFromFiles(keyPEMFile, crtPEMFile string) (string, string, error) {
    89  	key, err := os.ReadFile(filepath.Clean(keyPEMFile))
    90  	if err != nil {
    91  		return "", "", err
    92  	}
    93  
    94  	crt, err := os.ReadFile(filepath.Clean(crtPEMFile))
    95  	if err != nil {
    96  		return "", "", err
    97  	}
    98  
    99  	return string(key), string(crt), nil
   100  }
   101  
   102  // LoadIssuerDataFromFiles loads the issuer data from file stored on disk
   103  func LoadIssuerDataFromFiles(keyPEMFile, crtPEMFile, trustPEMFile string) (*IssuerCertData, error) {
   104  	key, crt, err := LoadIssuerCrtAndKeyFromFiles(keyPEMFile, crtPEMFile)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	anchors, err := os.ReadFile(filepath.Clean(trustPEMFile))
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	return &IssuerCertData{string(anchors), crt, key, nil}, nil
   115  }
   116  
   117  // CheckCertValidityPeriod ensures the certificate is valid time - wise
   118  func CheckCertValidityPeriod(cert *x509.Certificate) error {
   119  	if cert.NotBefore.After(time.Now()) {
   120  		return fmt.Errorf("not valid before: %s", cert.NotBefore.Format(time.RFC3339))
   121  	}
   122  
   123  	if cert.NotAfter.Before(time.Now()) {
   124  		return fmt.Errorf("not valid anymore. Expired on %s", cert.NotAfter.Format(time.RFC3339))
   125  	}
   126  	return nil
   127  }
   128  
   129  // CheckExpiringSoon returns an error if a certificate is expiring soon
   130  func CheckExpiringSoon(cert *x509.Certificate) error {
   131  	if time.Now().AddDate(0, 0, expirationWarningThresholdInDays).After(cert.NotAfter) {
   132  		return fmt.Errorf("will expire on %s", cert.NotAfter.Format(time.RFC3339))
   133  	}
   134  	return nil
   135  }
   136  
   137  // CheckIssuerCertAlgoRequirements ensures the certificate respects with the constraints
   138  // we have posed on the public key and signature algorithms. Issuer certificates can only
   139  // be signed by an ECDSA certificate.
   140  func CheckIssuerCertAlgoRequirements(cert *x509.Certificate) error {
   141  	if cert.PublicKeyAlgorithm == x509.ECDSA {
   142  		err := checkECDSACertRequirements(cert)
   143  		if err != nil {
   144  			return err
   145  		}
   146  	} else {
   147  		return fmt.Errorf("issuer certificate must use ECDSA for public key algorithm, instead %s was used", cert.PublicKeyAlgorithm)
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  // CheckTrustAnchorAlgoRequirements ensures the certificate respects with the constraints
   154  // we have posed on the public key and signature algorithms. Trust anchors can be signed by
   155  // an ECDSA or RSA certificate.
   156  func CheckTrustAnchorAlgoRequirements(cert *x509.Certificate) error {
   157  	if cert.PublicKeyAlgorithm == x509.ECDSA {
   158  		err := checkECDSACertRequirements(cert)
   159  		if err != nil {
   160  			return err
   161  		}
   162  	} else if cert.PublicKeyAlgorithm == x509.RSA {
   163  		err := checkRSACertRequirements(cert)
   164  		if err != nil {
   165  			return err
   166  		}
   167  	} else {
   168  		return fmt.Errorf("trust anchor must use ECDSA or RSA for public key algorithm, instead %s was used", cert.PublicKeyAlgorithm)
   169  	}
   170  
   171  	return nil
   172  }
   173  
   174  func checkECDSACertRequirements(cert *x509.Certificate) error {
   175  	k, ok := cert.PublicKey.(*ecdsa.PublicKey)
   176  	if !ok {
   177  		return fmt.Errorf("expected ecdsa.PublicKey but got something %v", cert.PublicKey)
   178  	}
   179  	if k.Params().BitSize != 256 {
   180  		return fmt.Errorf("must use P-256 curve for public key, instead P-%d was used", k.Params().BitSize)
   181  	}
   182  	if cert.SignatureAlgorithm != x509.ECDSAWithSHA256 &&
   183  		cert.SignatureAlgorithm != x509.SHA256WithRSA {
   184  		return fmt.Errorf("must be signed by an ECDSA P-256 key, instead %s was used", cert.SignatureAlgorithm)
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  func checkRSACertRequirements(cert *x509.Certificate) error {
   191  	k, ok := cert.PublicKey.(*rsa.PublicKey)
   192  	if !ok {
   193  		return fmt.Errorf("expected rsa.PublicKey but got something %v", cert.PublicKey)
   194  	}
   195  	if k.N.BitLen() != 2048 && k.N.BitLen() != 4096 {
   196  		return fmt.Errorf("RSA must use at least 2084 bit public key, instead %d bit public key was used", k.N.BitLen())
   197  	}
   198  	if cert.SignatureAlgorithm != x509.SHA256WithRSA {
   199  		return fmt.Errorf("must be signed by an RSA 2048/4096 bit key, instead %s was used", cert.SignatureAlgorithm)
   200  	}
   201  
   202  	return nil
   203  }
   204  
   205  // VerifyAndBuildCreds builds and validates the creds out of the data in IssuerCertData
   206  func (ic *IssuerCertData) VerifyAndBuildCreds() (*tls.Cred, error) {
   207  	creds, err := tls.ValidateAndCreateCreds(ic.IssuerCrt, ic.IssuerKey)
   208  	if err != nil {
   209  		return nil, fmt.Errorf("failed to read CA: %w", err)
   210  	}
   211  
   212  	// we check the time validity of the issuer cert
   213  	if err := CheckCertValidityPeriod(creds.Certificate); err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	// we check the algo requirements of the issuer cert
   218  	if err := CheckIssuerCertAlgoRequirements(creds.Certificate); err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	if !creds.Certificate.IsCA {
   223  		return nil, fmt.Errorf("issuer cert is not a CA")
   224  	}
   225  
   226  	anchors, err := tls.DecodePEMCertPool(ic.TrustAnchors)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	if err := creds.Verify(anchors, "", time.Time{}); err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	return creds, nil
   236  }
   237  

View as plain text