From 196679e9ec77cb709db54879ddeddd4eaafaea01 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 17 Aug 2020 16:53:33 +0300 Subject: [PATCH] feat: move `pkg/grpc/tls` from `github.com/talos-systems/talos` as `./tls` Just moving code without any changes. Signed-off-by: Andrey Smirnov --- .dockerignore | 3 +- Dockerfile | 3 +- tls/provider.go | 186 ++++++++++++++++++++++++++++++++++++++++++++++++ tls/tls.go | 138 +++++++++++++++++++++++++++++++++++ tls/tls_test.go | 14 ++++ 5 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 tls/provider.go create mode 100644 tls/tls.go create mode 100644 tls/tls_test.go diff --git a/.dockerignore b/.dockerignore index 42b9cef..95eee5d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,9 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2020-08-13T17:40:42Z by kres f4c4987-dirty. +# Generated on 2020-08-17T13:49:58Z by kres 3d35a96-dirty. ** +!tls !x509 !go.mod !go.sum diff --git a/Dockerfile b/Dockerfile index 3b0037f..e1b62cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2020-08-13T17:38:42Z by kres f4c4987-dirty. +# Generated on 2020-08-17T13:49:58Z by kres 3d35a96-dirty. ARG TOOLCHAIN @@ -28,6 +28,7 @@ COPY ./go.mod . COPY ./go.sum . RUN go mod download RUN go mod verify +COPY ./tls ./tls COPY ./x509 ./x509 RUN go list -mod=readonly all >/dev/null diff --git a/tls/provider.go b/tls/provider.go new file mode 100644 index 0000000..6dec7b2 --- /dev/null +++ b/tls/provider.go @@ -0,0 +1,186 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package tls + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "log" + "net" + "sync" + "time" + + talosx509 "github.com/talos-systems/crypto/x509" +) + +// CertificateProvider describes an interface by which TLS certificates may be managed. +type CertificateProvider interface { + // GetCA returns the active root CA. + GetCA() ([]byte, error) + + // GetCertificate returns the current certificate matching the given client request. + GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) + + // GetClientCertificate returns the current certificate to present to the server. + GetClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) +} + +// Generator describes an interface to sign the CSR. +type Generator interface { + Identity(csr *talosx509.CertificateSigningRequest) (ca, crt []byte, err error) +} + +type certificateProvider struct { + sync.RWMutex + + generator Generator + + ca []byte + crt *tls.Certificate + + dnsNames []string + ips []net.IP +} + +// NewRenewingCertificateProvider returns a new CertificateProvider +// which manages and updates its certificates using Generator. +func NewRenewingCertificateProvider(generator Generator, dnsNames []string, ips []net.IP) (CertificateProvider, error) { + provider := &certificateProvider{ + generator: generator, + dnsNames: dnsNames, + ips: ips, + } + + var ( + ca []byte + cert tls.Certificate + err error + ) + + if ca, cert, err = provider.update(); err != nil { + return nil, fmt.Errorf("failed to create initial certificate: %w", err) + } + + provider.updateCertificates(ca, &cert) + + //nolint: errcheck + go provider.manageUpdates(context.TODO()) + + return provider, nil +} + +func (p *certificateProvider) update() (ca []byte, cert tls.Certificate, err error) { + var ( + crt []byte + csr *talosx509.CertificateSigningRequest + identity *talosx509.PEMEncodedCertificateAndKey + ) + + csr, identity, err = talosx509.NewCSRAndIdentity(p.dnsNames, p.ips) + if err != nil { + return nil, cert, err + } + + if ca, crt, err = p.generator.Identity(csr); err != nil { + return nil, cert, fmt.Errorf("failed to generate identity: %w", err) + } + + identity.Crt = crt + + cert, err = tls.X509KeyPair(identity.Crt, identity.Key) + if err != nil { + return nil, cert, fmt.Errorf("failed to parse cert and key into a TLS Certificate: %w", err) + } + + return ca, cert, nil +} + +func (p *certificateProvider) GetCA() ([]byte, error) { + if p == nil { + return nil, errors.New("no provider") + } + + p.RLock() + defer p.RUnlock() + + return p.ca, nil +} + +func (p *certificateProvider) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) { + if p == nil { + return nil, errors.New("no provider") + } + + p.RLock() + defer p.RUnlock() + + return p.crt, nil +} + +func (p *certificateProvider) GetClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + return p.GetCertificate(nil) +} + +func (p *certificateProvider) updateCertificates(ca []byte, cert *tls.Certificate) { + p.Lock() + defer p.Unlock() + + p.ca = ca + p.crt = cert +} + +func (p *certificateProvider) manageUpdates(ctx context.Context) (err error) { + nextRenewal := talosx509.DefaultCertificateValidityDuration + + for ctx.Err() == nil { + //nolint: errcheck + if c, _ := p.GetCertificate(nil); c != nil { + if len(c.Certificate) > 0 { + var crt *x509.Certificate + crt, err = x509.ParseCertificate(c.Certificate[0]) + + if err == nil { + nextRenewal = time.Until(crt.NotAfter) / 2 + } else { + log.Println("failed to parse current leaf certificate") + } + } else { + log.Println("current leaf certificate not found") + } + } else { + log.Println("certificate not found") + } + + log.Println("next renewal in", nextRenewal) + + if nextRenewal > talosx509.DefaultCertificateValidityDuration { + nextRenewal = talosx509.DefaultCertificateValidityDuration + } + + select { + case <-time.After(nextRenewal): + case <-ctx.Done(): + return nil + } + + var ( + ca []byte + cert tls.Certificate + ) + + if ca, cert, err = p.update(); err != nil { + log.Println("failed to renew certificate:", err) + + continue + } + + p.updateCertificates(ca, &cert) + } + + return errors.New("certificate update manager exited unexpectedly") +} diff --git a/tls/tls.go b/tls/tls.go new file mode 100644 index 0000000..b4a8217 --- /dev/null +++ b/tls/tls.go @@ -0,0 +1,138 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tls provides tls.Config generation and certificate management. +package tls + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" +) + +// Type represents the TLS authentication type. +type Type int + +const ( + // Mutual configures the server's policy for TLS Client Authentication to + // mutual TLS. + Mutual Type = 1 << iota + // ServerOnly configures the server's policy for TLS Client Authentication + // to server only. + ServerOnly +) + +// ConfigOptionFunc describes a configuration option function for the TLS config. +type ConfigOptionFunc func(*tls.Config) error + +// WithClientAuthType declares the server's policy regardling TLS Client Authentication. +func WithClientAuthType(t Type) func(*tls.Config) error { + return func(cfg *tls.Config) error { + switch t { + case Mutual: + cfg.ClientAuth = tls.RequireAndVerifyClientCert + case ServerOnly: + cfg.ClientAuth = tls.NoClientCert + default: + return fmt.Errorf("unhandled client auth type %+v", t) + } + + return nil + } +} + +// WithServerCertificateProvider declares a dynamic provider for the server +// certificate. +// +// NOTE: specifying this option will CLEAR any configured Certificates, since +// they would otherwise override this option. +func WithServerCertificateProvider(p CertificateProvider) func(*tls.Config) error { + return func(cfg *tls.Config) error { + if p == nil { + return errors.New("no provider") + } + + cfg.Certificates = nil + cfg.GetCertificate = p.GetCertificate + + return nil + } +} + +// WithClientCertificateProvider declares a dynamic provider for the client +// certificate. +// +// NOTE: specifying this option will CLEAR any configured Certificates, since +// they would otherwise override this option. +func WithClientCertificateProvider(p CertificateProvider) func(*tls.Config) error { + return func(cfg *tls.Config) error { + if p == nil { + return errors.New("no provider") + } + + cfg.Certificates = nil + cfg.GetClientCertificate = p.GetClientCertificate + + return nil + } +} + +// WithKeypair declares a specific TLS keypair to be used. This can be called +// multiple times to add additional keypairs. +func WithKeypair(cert tls.Certificate) func(*tls.Config) error { + return func(cfg *tls.Config) error { + cfg.Certificates = append(cfg.Certificates, cert) + + return nil + } +} + +// WithCACertPEM declares a PEM-encoded CA Certificate to be used. +func WithCACertPEM(ca []byte) func(*tls.Config) error { + return func(cfg *tls.Config) error { + if len(ca) == 0 { + return errors.New("no CA cert provided") + } + + if ok := cfg.ClientCAs.AppendCertsFromPEM(ca); !ok { + return errors.New("failed to append CA certificate to ClientCAs pool") + } + + if ok := cfg.RootCAs.AppendCertsFromPEM(ca); !ok { + return errors.New("failed to append CA certificate to RootCAs pool") + } + + return nil + } +} + +func defaultConfig() *tls.Config { + return &tls.Config{ + RootCAs: x509.NewCertPool(), + ClientCAs: x509.NewCertPool(), + // Use the X25519 elliptic curve for the ECDHE key exchange algorithm. + CurvePreferences: []tls.CurveID{tls.X25519}, + SessionTicketsDisabled: true, + // TLS protocol, ECDHE key exchange algorithm, ECDSA digital signature algorithm, AES_256_GCM bulk encryption algorithm, and SHA384 hash algorithm. + CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, + // Force the above cipher suites. + PreferServerCipherSuites: true, + // TLS 1.2 + MinVersion: tls.VersionTLS12, + } +} + +// New returns a new TLS Configuration modified by any provided configuration options. +func New(opts ...ConfigOptionFunc) (cfg *tls.Config, err error) { + cfg = defaultConfig() + + for _, f := range opts { + if err = f(cfg); err != nil { + return + } + } + + return +} diff --git a/tls/tls_test.go b/tls/tls_test.go new file mode 100644 index 0000000..285fdd8 --- /dev/null +++ b/tls/tls_test.go @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package tls_test + +import "testing" + +func TestEmpty(t *testing.T) { + // added for accurate coverage estimation + // + // please remove it once any unit-test is added + // for this package +}