...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/writer/certwriter.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/writer

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package writer
    18  
    19  import (
    20  	"crypto/tls"
    21  	"crypto/x509"
    22  	"encoding/pem"
    23  	"errors"
    24  	"time"
    25  
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  
    28  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/webhook/cert/generator"
    29  )
    30  
    31  const (
    32  	// CAKeyName is the name of the CA private key
    33  	CAKeyName = "ca-key.pem"
    34  	// CACertName is the name of the CA certificate
    35  	CACertName = "ca-cert.pem"
    36  	// ServerKeyName is the name of the server private key
    37  	ServerKeyName = "key.pem"
    38  	// ServerCertName is the name of the serving certificate
    39  	ServerCertName = "cert.pem"
    40  )
    41  
    42  // CertWriter provides method to handle webhooks.
    43  type CertWriter interface {
    44  	// EnsureCert provisions the cert for the webhookClientConfig.
    45  	EnsureCert(dnsName string) (*generator.Artifacts, bool, error)
    46  	// Inject injects the necessary information given the objects.
    47  	// It supports MutatingWebhookConfiguration and ValidatingWebhookConfiguration.
    48  	Inject(objs ...client.Object) error
    49  }
    50  
    51  // handleCommon ensures the given webhook has a proper certificate.
    52  // It uses the given certReadWriter to read and (or) write the certificate.
    53  func handleCommon(dnsName string, ch certReadWriter) (*generator.Artifacts, bool, error) {
    54  	if len(dnsName) == 0 {
    55  		return nil, false, errors.New("dnsName should not be empty")
    56  	}
    57  	if ch == nil {
    58  		return nil, false, errors.New("certReaderWriter should not be nil")
    59  	}
    60  
    61  	certs, changed, err := createIfNotExists(ch)
    62  	if err != nil {
    63  		return nil, changed, err
    64  	}
    65  
    66  	// Recreate the cert if it's invalid.
    67  	valid := validCert(certs, dnsName)
    68  	if !valid {
    69  		log.Info("cert is invalid or expiring, regenerating a new one")
    70  		certs, err = ch.overwrite()
    71  		if err != nil {
    72  			return nil, false, err
    73  		}
    74  		changed = true
    75  	}
    76  	return certs, changed, nil
    77  }
    78  
    79  func createIfNotExists(ch certReadWriter) (*generator.Artifacts, bool, error) {
    80  	// Try to read first
    81  	certs, err := ch.read()
    82  	if isNotFound(err) {
    83  		// Create if not exists
    84  		certs, err = ch.write()
    85  		switch {
    86  		// This may happen if there is another racer.
    87  		case isAlreadyExists(err):
    88  			certs, err = ch.read()
    89  			return certs, true, err
    90  		default:
    91  			return certs, true, err
    92  		}
    93  	}
    94  	return certs, false, err
    95  }
    96  
    97  // certReadWriter provides methods for reading and writing certificates.
    98  type certReadWriter interface {
    99  	// read reads a webhook name and returns the certs for it.
   100  	read() (*generator.Artifacts, error)
   101  	// write writes the certs and return the certs it wrote.
   102  	write() (*generator.Artifacts, error)
   103  	// overwrite overwrites the existing certs and return the certs it wrote.
   104  	overwrite() (*generator.Artifacts, error)
   105  }
   106  
   107  // validCert verifies if the certificate is valid, including
   108  // additional verifications to ensure compatibility with Kubernetes
   109  // and it's default HTTP client.
   110  func validCert(certs *generator.Artifacts, dnsName string) bool {
   111  	if certs == nil {
   112  		return false
   113  	}
   114  
   115  	// Verify key and cert are valid pair
   116  	_, err := tls.X509KeyPair(certs.Cert, certs.Key)
   117  	if err != nil {
   118  		return false
   119  	}
   120  
   121  	// Verify cert is good for desired DNS name and signed by CA and will be valid for desired period of time.
   122  	pool := x509.NewCertPool()
   123  	if !pool.AppendCertsFromPEM(certs.CACert) {
   124  		return false
   125  	}
   126  	block, _ := pem.Decode([]byte(certs.Cert))
   127  	if block == nil {
   128  		return false
   129  	}
   130  	cert, err := x509.ParseCertificate(block.Bytes)
   131  	if err != nil {
   132  		return false
   133  	}
   134  	ops := x509.VerifyOptions{
   135  		DNSName:     dnsName,
   136  		Roots:       pool,
   137  		CurrentTime: time.Now().AddDate(0, 6, 0),
   138  	}
   139  	_, err = cert.Verify(ops)
   140  	if err != nil {
   141  		return false
   142  	}
   143  	return DoesCertificateWorkWithK8sAPIClient(cert)
   144  }
   145  
   146  // DoesCertificateWorkWithK8sAPIClient returns false if the certificate
   147  // is not compatible with Kubernetes HTTP clients.
   148  func DoesCertificateWorkWithK8sAPIClient(cert *x509.Certificate) bool {
   149  	// check to see if the cert has a DNSName. Certificates that
   150  	// do not will not be considered valid for Kubernetes distributions built
   151  	// with go 1.15 or higher.
   152  	return len(cert.DNSNames) > 0
   153  }
   154  

View as plain text