...

Source file src/github.com/emissary-ingress/emissary/v3/cmd/apiext/ca.go

Documentation: github.com/emissary-ingress/emissary/v3/cmd/apiext

     1  package apiext
     2  
     3  import (
     4  	// stdlib
     5  	"context"
     6  	"crypto/rand"
     7  	"crypto/rsa"
     8  	"crypto/tls"
     9  	"crypto/x509"
    10  	"crypto/x509/pkix"
    11  	"encoding/pem"
    12  	"fmt"
    13  	"math/big"
    14  	"sync"
    15  	"time"
    16  
    17  	// k8s types
    18  	k8sTypesCoreV1 "k8s.io/api/core/v1"
    19  	k8sTypesMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  
    21  	// k8s clients
    22  	k8sClientCoreV1 "k8s.io/client-go/kubernetes/typed/core/v1"
    23  
    24  	// k8s utils
    25  	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
    26  	"k8s.io/client-go/rest"
    27  
    28  	// 1st-party
    29  	"github.com/datawire/dlib/dlog"
    30  )
    31  
    32  const (
    33  	certValidDuration = 365 * 24 * time.Hour
    34  	caSecretName      = "emissary-ingress-webhook-ca"
    35  )
    36  
    37  // CA is a Certificat Authority that can mint new TLS certificates.
    38  type CA struct {
    39  	Cert *x509.Certificate
    40  	Key  *rsa.PrivateKey
    41  
    42  	cacheMu sync.Mutex
    43  	cache   map[string]*tls.Certificate
    44  }
    45  
    46  // EnsureCA ensures that a Kubernetes Secret named "emissary-ingress-webhook-ca" exists in the given
    47  // namespace (creating it if it doesn't), and returns both the Secret itself and a CA using the
    48  // information from the Secret.
    49  func EnsureCA(ctx context.Context, restConfig *rest.Config, namespace string) (*CA, *k8sTypesCoreV1.Secret, error) {
    50  	coreClient, err := k8sClientCoreV1.NewForConfig(restConfig)
    51  	if err != nil {
    52  		return nil, nil, err
    53  	}
    54  	secretsClient := coreClient.Secrets(namespace)
    55  
    56  	for ctx.Err() == nil {
    57  		// Does It already exist?
    58  		caSecret, err := secretsClient.Get(ctx, caSecretName, k8sTypesMetaV1.GetOptions{})
    59  		if err == nil {
    60  			ca, err := parseCA(caSecret)
    61  			if err != nil {
    62  				return nil, nil, err
    63  			}
    64  			return ca, caSecret, nil
    65  		}
    66  		if !k8sErrors.IsNotFound(err) {
    67  			return nil, nil, err
    68  		}
    69  
    70  		// Try to create it.
    71  		caSecret, err = genCASecret(namespace)
    72  		if err != nil {
    73  			return nil, nil, err
    74  		}
    75  		caSecret, err = secretsClient.Create(ctx, caSecret, k8sTypesMetaV1.CreateOptions{})
    76  		if err == nil {
    77  			ca, err := parseCA(caSecret)
    78  			if err != nil {
    79  				return nil, nil, err
    80  			}
    81  			return ca, caSecret, nil
    82  		}
    83  		if !k8sErrors.IsAlreadyExists(err) {
    84  			return nil, nil, err
    85  		}
    86  
    87  		// Loop around, try again.
    88  	}
    89  	return nil, nil, ctx.Err()
    90  }
    91  
    92  func parseCA(caSecret *k8sTypesCoreV1.Secret) (*CA, error) {
    93  	// key
    94  	caKeyPEMBytes, ok := caSecret.Data[k8sTypesCoreV1.TLSPrivateKeyKey]
    95  	if !ok {
    96  		return nil, fmt.Errorf("no key found in CA secret")
    97  	}
    98  	caKeyBlock, _ := pem.Decode(caKeyPEMBytes)
    99  	_caKey, err := x509.ParsePKCS8PrivateKey(caKeyBlock.Bytes)
   100  	if err != nil {
   101  		return nil, fmt.Errorf("bad key loaded in CA secret: %w", err)
   102  	}
   103  	caKey, ok := _caKey.(*rsa.PrivateKey)
   104  	if !ok {
   105  		return nil, fmt.Errorf("key in CA secret is not an RSA key")
   106  	}
   107  
   108  	// cert
   109  	caCertPEMBytes, ok := caSecret.Data[k8sTypesCoreV1.TLSCertKey]
   110  	if !ok {
   111  		return nil, fmt.Errorf("no cert found in CA secret!")
   112  	}
   113  	caCertBlock, _ := pem.Decode(caCertPEMBytes)
   114  	caCert, err := x509.ParseCertificate(caCertBlock.Bytes)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	return &CA{
   120  		Cert: caCert,
   121  		Key:  caKey,
   122  	}, nil
   123  }
   124  
   125  // genKey generates an RSA key, returning both the key object, as well as a representation of it as
   126  // PEM-encoded PKCS#8 DER.
   127  func genKey() (*rsa.PrivateKey, []byte, error) {
   128  	key, err := rsa.GenerateKey(rand.Reader, 4096)
   129  	if err != nil {
   130  		return nil, nil, err
   131  	}
   132  	derBytes, err := x509.MarshalPKCS8PrivateKey(key)
   133  	if err != nil {
   134  		return nil, nil, err
   135  	}
   136  	pemBytes := pem.EncodeToMemory(&pem.Block{
   137  		Type:  "PRIVATE KEY",
   138  		Bytes: derBytes,
   139  	})
   140  	return key, pemBytes, nil
   141  }
   142  
   143  // genCACert generates a Certificate Authority's certificate, returning PEM-encoded DER.
   144  func genCACert(key *rsa.PrivateKey) ([]byte, error) {
   145  	// Generate CA Certificate and key...
   146  	notBefore := time.Now()
   147  	notAfter := notBefore.Add(certValidDuration)
   148  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   149  	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	template := &x509.Certificate{
   155  		SerialNumber: serialNumber,
   156  		Subject: pkix.Name{
   157  			Organization: []string{"Ambassador Labs"},
   158  		},
   159  		NotBefore:             notBefore,
   160  		NotAfter:              notAfter,
   161  		IsCA:                  true,
   162  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
   163  		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   164  		BasicConstraintsValid: true,
   165  	}
   166  
   167  	derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	pemBytes := pem.EncodeToMemory(&pem.Block{
   173  		Type:  "CERTIFICATE",
   174  		Bytes: derBytes,
   175  	})
   176  
   177  	return pemBytes, nil
   178  }
   179  
   180  func genCASecret(namespace string) (*k8sTypesCoreV1.Secret, error) {
   181  	key, keyPEMBytes, err := genKey()
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	certPEMBytes, err := genCACert(key)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	return &k8sTypesCoreV1.Secret{
   190  		ObjectMeta: k8sTypesMetaV1.ObjectMeta{
   191  			Name:      caSecretName,
   192  			Namespace: namespace,
   193  		},
   194  		Type: k8sTypesCoreV1.SecretTypeTLS,
   195  		Data: map[string][]byte{
   196  			k8sTypesCoreV1.TLSPrivateKeyKey: keyPEMBytes,
   197  			k8sTypesCoreV1.TLSCertKey:       certPEMBytes,
   198  		},
   199  	}, nil
   200  }
   201  
   202  func (ca *CA) GenServerCert(ctx context.Context, hostname string) (*tls.Certificate, error) {
   203  	dlog.Debugf(ctx, "GenServerCert(ctx, %q)", hostname)
   204  	ca.cacheMu.Lock()
   205  	defer ca.cacheMu.Unlock()
   206  	if ca.cache == nil {
   207  		ca.cache = make(map[string]*tls.Certificate)
   208  	}
   209  	now := time.Now()
   210  	if cached, ok := ca.cache[hostname]; ok && cached != nil && cached.Leaf != nil {
   211  		if age, lifespan := now.Sub(cached.Leaf.NotBefore), cached.Leaf.NotAfter.Sub(cached.Leaf.NotBefore); age < 2*lifespan/3 {
   212  			dlog.Debugf(ctx, "GenServerCert(ctx, %q) => from cache (age=%v lifespan=%v)", hostname, age, lifespan)
   213  			return cached, nil
   214  		} else {
   215  			dlog.Debugf(ctx, "GenServerCert(ctx, %q) => cache entry too old (age=%v lifespan=%v)", hostname, age, lifespan)
   216  		}
   217  	}
   218  	dlog.Infof(ctx, "GenServerCert(ctx, %q) => generating new cert", hostname)
   219  
   220  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   221  	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	priv, err := rsa.GenerateKey(rand.Reader, 4096)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	cert := &x509.Certificate{
   232  		SerialNumber: serialNumber,
   233  		Subject: pkix.Name{
   234  			Organization: []string{"Ambassador Labs"},
   235  			CommonName:   "Webhook API",
   236  		},
   237  		NotBefore:             now,
   238  		NotAfter:              now.Add(certValidDuration),
   239  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   240  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
   241  		BasicConstraintsValid: true,
   242  		DNSNames:              []string{hostname},
   243  	}
   244  
   245  	certPEMBytes, err := x509.CreateCertificate(
   246  		rand.Reader,
   247  		cert,
   248  		ca.Cert,
   249  		priv.Public(),
   250  		ca.Key,
   251  	)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	certChain := &tls.Certificate{
   257  		Certificate: [][]byte{certPEMBytes},
   258  		PrivateKey:  priv,
   259  		Leaf:        cert,
   260  	}
   261  
   262  	ca.cache[hostname] = certChain
   263  	return certChain, nil
   264  }
   265  

View as plain text