Compare commits

..

4 Commits

Author SHA1 Message Date
Brad Davidson
cec44b5e30 Update wrangler to v0.8.3
Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
2021-07-13 15:16:59 -07:00
Sjoerd Simons
8056fb92e8 Accept IPv6 address as CN names
Expand the cnRegexp to also accept ipv6 addresses such as:
  * ::1
  * 2a00:1450:400e:80e::
  * 2a00:1450:400e:80e::200e

Fixes: #37

Signed-off-by: Sjoerd Simons <sjoerd@collabora.com>
(cherry picked from commit dc7452dbb8)
2021-06-14 14:43:06 -07:00
Dan Ramich
51bda41d9c Merge pull request #34 from dramich/wrangler
Update wrangler and drop wrangler-api
2021-04-23 08:46:33 -06:00
Dan Ramich
624606ae5a Update wrangler and drop wrangler-api 2021-04-22 15:44:19 -06:00
13 changed files with 91 additions and 332 deletions

View File

@@ -1,10 +0,0 @@
# [dynamiclistener](https://github.com/rancher/dynamiclistener)
This `README` is a work in progress; aimed towards providing information for navigating the contents of this repository.
## Changing the Expiration Days for Newly Signed Certificates
By default, a newly signed certificate is set to expire 365 days (1 year) after its creation time and date.
You can use the `CATTLE_NEW_SIGNED_CERT_EXPIRATION_DAYS` environment variable to change this value.
**Please note:** the value for the aforementioned variable must be a string representing an unsigned integer corresponding to the number of days until expiration (i.e. X509 "NotAfter" value).

View File

@@ -45,15 +45,16 @@ const (
duration365d = time.Hour * 24 * 365
)
var ErrStaticCert = errors.New("cannot renew static certificate")
var (
ErrStaticCert = errors.New("cannot renew static certificate")
)
// Config contains the basic fields required for creating a certificate.
// Config contains the basic fields required for creating a certificate
type Config struct {
CommonName string
Organization []string
AltNames AltNames
Usages []x509.ExtKeyUsage
ExpiresAt time.Duration
}
// AltNames contains the domain names and IP addresses that will be added
@@ -89,15 +90,10 @@ func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, erro
if err != nil {
return nil, err
}
logrus.Infof("generated self-signed CA certificate %s: notBefore=%s notAfter=%s",
tmpl.Subject, tmpl.NotBefore, tmpl.NotAfter)
return x509.ParseCertificate(certDERBytes)
}
// NewSignedCert creates a signed certificate using the given CA certificate and key based
// on the given configuration.
// NewSignedCert creates a signed certificate using the given CA certificate and key
func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
if err != nil {
@@ -109,12 +105,6 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe
if len(cfg.Usages) == 0 {
return nil, errors.New("must specify at least one ExtKeyUsage")
}
var expiresAt time.Duration
if cfg.ExpiresAt > 0 {
expiresAt = time.Duration(cfg.ExpiresAt)
} else {
expiresAt = duration365d
}
certTmpl := x509.Certificate{
Subject: pkix.Name{
@@ -125,7 +115,7 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe
IPAddresses: cfg.AltNames.IPs,
SerialNumber: serial,
NotBefore: caCert.NotBefore,
NotAfter: time.Now().Add(expiresAt).UTC(),
NotAfter: time.Now().Add(duration365d).UTC(),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: cfg.Usages,
}

View File

@@ -1,16 +0,0 @@
package cert
import v1 "k8s.io/api/core/v1"
func IsValidTLSSecret(secret *v1.Secret) bool {
if secret == nil {
return false
}
if _, ok := secret.Data[v1.TLSCertKey]; !ok {
return false
}
if _, ok := secret.Data[v1.TLSPrivateKeyKey]; !ok {
return false
}
return true
}

View File

@@ -10,17 +10,13 @@ import (
"math"
"math/big"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
)
const (
CertificateBlockType = "CERTIFICATE"
defaultNewSignedCertExpirationDays = 365
CertificateBlockType = "CERTIFICATE"
)
func NewSelfSignedCACert(key crypto.Signer, cn string, org ...string) (*x509.Certificate, error) {
@@ -43,9 +39,6 @@ func NewSelfSignedCACert(key crypto.Signer, cn string, org ...string) (*x509.Cer
return nil, err
}
logrus.Infof("generated self-signed CA certificate %s: notBefore=%s notAfter=%s",
tmpl.Subject, tmpl.NotBefore, tmpl.NotAfter)
return x509.ParseCertificate(certDERBytes)
}
@@ -66,12 +59,6 @@ func NewSignedClientCert(signer crypto.Signer, caCert *x509.Certificate, caKey c
},
}
parts := strings.Split(cn, ",o=")
if len(parts) > 1 {
parent.Subject.CommonName = parts[0]
parent.Subject.Organization = parts[1:]
}
cert, err := x509.CreateCertificate(rand.Reader, &parent, caCert, signer.Public(), caKey)
if err != nil {
return nil, err
@@ -88,22 +75,12 @@ func NewSignedCert(signer crypto.Signer, caCert *x509.Certificate, caKey crypto.
return nil, err
}
expirationDays := defaultNewSignedCertExpirationDays
envExpirationDays := os.Getenv("CATTLE_NEW_SIGNED_CERT_EXPIRATION_DAYS")
if envExpirationDays != "" {
if envExpirationDaysInt, err := strconv.Atoi(envExpirationDays); err != nil {
logrus.Infof("[NewSignedCert] expiration days from ENV (%s) could not be converted to int (falling back to default value: %d)", envExpirationDays, defaultNewSignedCertExpirationDays)
} else {
expirationDays = envExpirationDaysInt
}
}
parent := x509.Certificate{
DNSNames: domains,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IPAddresses: ips,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(expirationDays)).UTC(),
NotAfter: time.Now().Add(time.Hour * 24 * 365).UTC(),
NotBefore: caCert.NotBefore,
SerialNumber: serialNumber,
Subject: pkix.Name{

View File

@@ -6,9 +6,7 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"net"
@@ -70,41 +68,20 @@ func collectCNs(secret *v1.Secret) (domains []string, ips []net.IP, err error) {
return
}
// Merge combines the SAN lists from the target and additional Secrets, and
// returns a potentially modified Secret, along with a bool indicating if the
// returned Secret is not the same as the target Secret.
//
// If the merge would not add any CNs to the additional Secret, the additional
// Secret is returned, to allow for certificate rotation/regeneration.
//
// If the merge would not add any CNs to the target Secret, the target Secret is
// returned; no merging is necessary.
//
// If neither certificate is acceptable as-is, a new certificate containing
// the union of the two lists is generated, using the private key from the
// first Secret. The returned Secret will contain the updated cert.
// Merge combines the SAN lists from the target and additional Secrets, and returns a potentially modified Secret,
// along with a bool indicating if the returned Secret has been updated or not. If the two SAN lists alread matched
// and no merging was necessary, but the Secrets' certificate fingerprints differed, the second secret is returned
// and the updated bool is set to true despite neither certificate having actually been modified. This is required
// to support handling certificate renewal within the kubernetes storage provider.
func (t *TLS) Merge(target, additional *v1.Secret) (*v1.Secret, bool, error) {
// static secrets can't be altered, don't bother trying
if IsStatic(target) {
return target, false, nil
secret, updated, err := t.AddCN(target, cns(additional)...)
if !updated {
if target.Annotations[fingerprint] != additional.Annotations[fingerprint] {
secret = additional
updated = true
}
}
mergedCNs := append(cns(target), cns(additional)...)
// if the additional secret already has all the CNs, use it in preference to the
// current one. This behavior is required to allow for renewal or regeneration.
if !NeedsUpdate(0, additional, mergedCNs...) {
return additional, true, nil
}
// if the target secret already has all the CNs, continue using it. The additional
// cert had only a subset of the current CNs, so nothing needs to be added.
if !NeedsUpdate(0, target, mergedCNs...) {
return target, false, nil
}
// neither cert currently has all the necessary CNs; generate a new one.
return t.generateCert(target, mergedCNs...)
return secret, updated, err
}
// Renew returns a copy of the given certificate that has been re-signed
@@ -142,12 +119,6 @@ func (t *TLS) AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) {
return t.generateCert(secret, cn...)
}
func (t *TLS) Regenerate(secret *v1.Secret) (*v1.Secret, error) {
cns := cns(secret)
secret, _, err := t.generateCert(nil, cns...)
return secret, err
}
func (t *TLS) generateCert(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) {
secret = secret.DeepCopy()
if secret == nil {
@@ -179,7 +150,6 @@ func (t *TLS) generateCert(secret *v1.Secret, cn ...string) (*v1.Secret, bool, e
if secret.Data == nil {
secret.Data = map[string][]byte{}
}
secret.Type = v1.SecretTypeTLS
secret.Data[v1.TLSCertKey] = certBytes
secret.Data[v1.TLSPrivateKeyKey] = keyBytes
secret.Annotations[fingerprint] = fmt.Sprintf("SHA1=%X", sha1.Sum(newCert.Raw))
@@ -198,7 +168,7 @@ func populateCN(secret *v1.Secret, cn ...string) *v1.Secret {
}
for _, cn := range cn {
if cnRegexp.MatchString(cn) {
secret.Annotations[getAnnotationKey(cn)] = cn
secret.Annotations[cnPrefix+cn] = cn
} else {
logrus.Errorf("dropping invalid CN: %s", cn)
}
@@ -209,10 +179,7 @@ func populateCN(secret *v1.Secret, cn ...string) *v1.Secret {
// IsStatic returns true if the Secret has an attribute indicating that it contains
// a static (aka user-provided) certificate, which should not be modified.
func IsStatic(secret *v1.Secret) bool {
if secret != nil && secret.Annotations != nil {
return secret.Annotations[Static] == "true"
}
return false
return secret.Annotations[Static] == "true"
}
// NeedsUpdate returns true if any of the CNs are not currently present on the
@@ -225,7 +192,7 @@ func NeedsUpdate(maxSANs int, secret *v1.Secret, cn ...string) bool {
}
for _, cn := range cn {
if secret.Annotations[getAnnotationKey(cn)] == "" {
if secret.Annotations[cnPrefix+cn] == "" {
if maxSANs > 0 && len(cns(secret)) >= maxSANs {
return false
}
@@ -269,22 +236,3 @@ func Marshal(x509Cert *x509.Certificate, privateKey crypto.Signer) ([]byte, []by
func NewPrivateKey() (crypto.Signer, error) {
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}
// getAnnotationKey return the key to use for a given CN. IPv4 addresses and short hostnames
// are safe to store as-is, but longer hostnames and IPv6 address must be truncated and/or undergo
// character replacement in order to be used as an annotation key. If the CN requires modification,
// a portion of the SHA256 sum of the original value is used as the suffix, to reduce the likelihood
// of collisions in modified values.
func getAnnotationKey(cn string) string {
cn = cnPrefix + cn
cnLen := len(cn)
if cnLen < 64 && !strings.ContainsRune(cn, ':') {
return cn
}
digest := sha256.Sum256([]byte(cn))
cn = strings.ReplaceAll(cn, ":", "_")
if cnLen > 56 {
cnLen = 56
}
return cn[0:cnLen] + "-" + hex.EncodeToString(digest[0:])[0:6]
}

View File

@@ -1,12 +0,0 @@
package dynamiclistener
func OnlyAllow(str string) func(...string) []string {
return func(s2 ...string) []string {
for _, s := range s2 {
if s == str {
return []string{s}
}
}
return nil
}
}

5
go.mod
View File

@@ -1,12 +1,11 @@
module github.com/rancher/dynamiclistener
go 1.12
go 1.16
require (
github.com/rancher/wrangler v0.8.9
github.com/rancher/wrangler v0.8.3
github.com/sirupsen/logrus v1.4.2
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
k8s.io/api v0.18.8
k8s.io/apimachinery v0.18.8
k8s.io/client-go v0.18.8
)

10
go.sum
View File

@@ -86,7 +86,6 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -308,8 +307,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA=
github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08 h1:NxR8Fh0eE7/5/5Zvlog9B5NVjWKqBSb1WYMUF7/IE5c=
github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08/go.mod h1:9qZd/S8DqWzfKtjKGgSoHqGEByYmUE3qRaBaaAHwfEM=
github.com/rancher/wrangler v0.8.9 h1:qNHBUw7jHdQKBVX4ksmY9ckth6oZaL2tRUKMwtERZw8=
github.com/rancher/wrangler v0.8.9/go.mod h1:Lte9WjPtGYxYacIWeiS9qawvu2R4NujFU9xuXWJvc/0=
github.com/rancher/wrangler v0.8.3 h1:m3d5ChOQj2Pdozy6nkGiSzAgQxlQlXRis2zSRwaO83k=
github.com/rancher/wrangler v0.8.3/go.mod h1:dKEaHNB4izxmPUtpq1Hvr3z3Oh+9k5pCZyFO9sUhlaY=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -344,9 +343,8 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -536,8 +534,6 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -7,7 +7,6 @@ import (
"crypto/x509"
"net"
"net/http"
"runtime"
"strings"
"sync"
"time"
@@ -28,7 +27,6 @@ type TLSFactory interface {
AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error)
Merge(target *v1.Secret, additional *v1.Secret) (*v1.Secret, bool, error)
Filter(cn ...string) []string
Regenerate(secret *v1.Secret) (*v1.Secret, error)
}
type SetFactory interface {
@@ -76,18 +74,11 @@ func NewListener(l net.Listener, storage TLSStorage, caCert *x509.Certificate, c
setter.SetFactory(dynamicListener.factory)
}
if config.RegenerateCerts != nil && config.RegenerateCerts() {
if err := dynamicListener.regenerateCerts(); err != nil {
return nil, nil, err
}
}
if config.ExpirationDaysCheck == 0 {
config.ExpirationDaysCheck = 30
}
tlsListener := tls.NewListener(dynamicListener.WrapExpiration(config.ExpirationDaysCheck), dynamicListener.tlsConfig)
return tlsListener, dynamicListener.cacheHandler(), nil
}
@@ -138,7 +129,6 @@ type Config struct {
MaxSANs int
ExpirationDaysCheck int
CloseConnOnCertChange bool
RegenerateCerts func() bool
FilterCN func(...string) []string
}
@@ -163,10 +153,7 @@ type listener struct {
func (l *listener) WrapExpiration(days int) net.Listener {
ctx, cancel := context.WithCancel(context.Background())
go func() {
// busy-wait for certificate preload to complete
for l.cert == nil {
runtime.Gosched()
}
time.Sleep(30 * time.Second)
for {
wait := 6 * time.Hour
@@ -193,30 +180,6 @@ func (l *listener) WrapExpiration(days int) net.Listener {
}
}
// regenerateCerts regenerates the used certificates and
// updates the secret.
func (l *listener) regenerateCerts() error {
l.Lock()
defer l.Unlock()
secret, err := l.storage.Get()
if err != nil {
return err
}
newSecret, err := l.factory.Regenerate(secret)
if err != nil {
return err
}
if err := l.storage.Update(newSecret); err != nil {
return err
}
// clear version to force cert reload
l.version = ""
return nil
}
func (l *listener) checkExpiration(days int) error {
l.Lock()
defer l.Unlock()
@@ -262,13 +225,7 @@ func (l *listener) checkExpiration(days int) error {
func (l *listener) Accept() (net.Conn, error) {
l.init.Do(func() {
if len(l.sans) > 0 {
if err := l.updateCert(l.sans...); err != nil {
logrus.Errorf("failed to update cert with configured SANs: %v", err)
return
}
if _, err := l.loadCert(nil); err != nil {
logrus.Errorf("failed to preload certificate: %v", err)
}
l.updateCert(l.sans...)
}
})
@@ -290,7 +247,7 @@ func (l *listener) Accept() (net.Conn, error) {
if !strings.Contains(host, ":") {
if err := l.updateCert(host); err != nil {
logrus.Errorf("failed to update cert with listener address: %v", err)
logrus.Infof("failed to create TLS cert for: %s, %v", host, err)
}
}
@@ -318,9 +275,8 @@ func (l *listener) wrap(conn net.Conn) net.Conn {
type closeWrapper struct {
net.Conn
id int
l *listener
ready bool
id int
l *listener
}
func (c *closeWrapper) close() error {
@@ -335,15 +291,13 @@ func (c *closeWrapper) Close() error {
}
func (l *listener) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
newConn := hello.Conn
if hello.ServerName != "" {
if err := l.updateCert(hello.ServerName); err != nil {
logrus.Errorf("failed to update cert with TLS ServerName: %v", err)
return nil, err
}
}
return l.loadCert(newConn)
return l.loadCert()
}
func (l *listener) updateCert(cn ...string) error {
@@ -360,7 +314,7 @@ func (l *listener) updateCert(cn ...string) error {
return err
}
if factory.IsStatic(secret) || !factory.NeedsUpdate(l.maxSANs, secret, cn...) {
if !factory.IsStatic(secret) && !factory.NeedsUpdate(l.maxSANs, secret, cn...) {
return nil
}
@@ -378,16 +332,14 @@ func (l *listener) updateCert(cn ...string) error {
if err := l.storage.Update(secret); err != nil {
return err
}
// Clear version to force cert reload next time loadCert is called by TLSConfig's
// GetCertificate hook to provide a certificate for a new connection. Note that this
// means the old certificate stays in l.cert until a new connection is made.
// clear version to force cert reload
l.version = ""
}
return nil
}
func (l *listener) loadCert(currentConn net.Conn) (*tls.Certificate, error) {
func (l *listener) loadCert() (*tls.Certificate, error) {
l.RLock()
defer l.RUnlock()
@@ -408,9 +360,6 @@ func (l *listener) loadCert(currentConn net.Conn) (*tls.Certificate, error) {
if err != nil {
return nil, err
}
if !cert.IsValidTLSSecret(secret) {
return l.cert, nil
}
if l.cert != nil && l.version == secret.ResourceVersion && secret.ResourceVersion != "" {
return l.cert, nil
}
@@ -420,17 +369,12 @@ func (l *listener) loadCert(currentConn net.Conn) (*tls.Certificate, error) {
return nil, err
}
// cert has changed, close closeWrapper wrapped connections if this isn't the first load
if currentConn != nil && l.conns != nil && l.cert != nil {
// cert has changed, close closeWrapper wrapped connections
if l.conns != nil {
l.connLock.Lock()
for _, conn := range l.conns {
// Don't close a connection that's in the middle of completing a TLS handshake
if !conn.ready {
continue
}
_ = conn.close()
}
l.conns[currentConn.(*closeWrapper).id].ready = true
l.connLock.Unlock()
}
@@ -454,9 +398,7 @@ func (l *listener) cacheHandler() http.Handler {
}
}
if err := l.updateCert(h); err != nil {
logrus.Errorf("failed to update cert with HTTP request Host header: %v", err)
}
l.updateCert(h)
}
})
}

View File

@@ -64,10 +64,7 @@ func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.H
}
tlsServer := http.Server{
Handler: handler,
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
Handler: handler,
ErrorLog: errorLog,
}
@@ -89,9 +86,6 @@ func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.H
Addr: fmt.Sprintf("%s:%d", opts.BindHost, httpPort),
Handler: handler,
ErrorLog: errorLog,
BaseContext: func(listener net.Listener) context.Context {
return ctx
},
}
go func() {
logrus.Infof("Listening on %s:%d", opts.BindHost, httpPort)

View File

@@ -2,10 +2,9 @@ package file
import (
"encoding/json"
"os"
"github.com/rancher/dynamiclistener"
v1 "k8s.io/api/core/v1"
"k8s.io/api/core/v1"
"os"
)
func New(file string) dynamiclistener.TLSStorage {

View File

@@ -6,7 +6,6 @@ import (
"time"
"github.com/rancher/dynamiclistener"
"github.com/rancher/dynamiclistener/cert"
"github.com/rancher/wrangler/pkg/generated/controllers/core"
v1controller "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
"github.com/rancher/wrangler/pkg/start"
@@ -14,7 +13,6 @@ import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/retry"
)
type CoreGetter func() *core.Factory
@@ -41,9 +39,10 @@ func New(ctx context.Context, core CoreGetter, namespace, name string, backing d
// lazy init
go func() {
for {
if coreFactory := core(); coreFactory != nil {
storage.init(coreFactory.Core().V1().Secret())
_ = start.All(ctx, 5, coreFactory)
core := core()
if core != nil {
storage.init(core.Core().V1().Secret())
start.All(ctx, 5, core)
return
}
@@ -59,11 +58,11 @@ func New(ctx context.Context, core CoreGetter, namespace, name string, backing d
}
type storage struct {
sync.RWMutex
sync.Mutex
namespace, name string
storage dynamiclistener.TLSStorage
secrets v1controller.SecretController
secrets v1controller.SecretClient
ctx context.Context
tls dynamiclistener.TLSFactory
}
@@ -90,46 +89,30 @@ func (s *storage) init(secrets v1controller.SecretController) {
})
s.secrets = secrets
secret, err := s.storage.Get()
if err == nil && cert.IsValidTLSSecret(secret) {
// local storage had a cached secret, ensure that it exists in Kubernetes
_, err := s.secrets.Create(&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: s.name,
Namespace: s.namespace,
Annotations: secret.Annotations,
},
Type: v1.SecretTypeTLS,
Data: secret.Data,
})
if err != nil && !errors.IsAlreadyExists(err) {
logrus.Warnf("Failed to create Kubernetes secret: %v", err)
}
} else {
// local storage was empty, try to populate it
secret, err := s.secrets.Get(s.namespace, s.name, metav1.GetOptions{})
if err != nil {
logrus.Warnf("Failed to init Kubernetes secret: %v", err)
return
}
if err := s.storage.Update(secret); err != nil {
logrus.Warnf("Failed to init backing storage secret: %v", err)
if secret, err := s.storage.Get(); err == nil && secret != nil && len(secret.Data) > 0 {
// just ensure there is a secret in k3s
if _, err := s.secrets.Get(s.namespace, s.name, metav1.GetOptions{}); errors.IsNotFound(err) {
_, _ = s.secrets.Create(&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: s.name,
Namespace: s.namespace,
Annotations: secret.Annotations,
},
Type: v1.SecretTypeTLS,
Data: secret.Data,
})
}
}
}
func (s *storage) Get() (*v1.Secret, error) {
s.RLock()
defer s.RUnlock()
s.Lock()
defer s.Unlock()
return s.storage.Get()
}
func (s *storage) targetSecret() (*v1.Secret, error) {
s.RLock()
defer s.RUnlock()
existingSecret, err := s.secrets.Get(s.namespace, s.name, metav1.GetOptions{})
if errors.IsNotFound(err) {
return &v1.Secret{
@@ -137,48 +120,33 @@ func (s *storage) targetSecret() (*v1.Secret, error) {
Name: s.name,
Namespace: s.namespace,
},
Type: v1.SecretTypeTLS,
}, nil
}
return existingSecret, err
}
func (s *storage) saveInK8s(secret *v1.Secret) (*v1.Secret, error) {
if !s.controllerHasSynced() {
if s.secrets == nil {
return secret, nil
}
if existing, err := s.storage.Get(); err == nil && s.tls != nil {
if newSecret, updated, err := s.tls.Merge(existing, secret); err == nil && updated {
secret = newSecret
}
}
targetSecret, err := s.targetSecret()
if err != nil {
return nil, err
}
// if we don't have a TLS factory we can't create certs, so don't bother trying to merge anything,
// in favor of just blindly replacing the fields on the Kubernetes secret.
if s.tls != nil {
// merge new secret with secret from backing storage, if one exists
if existing, err := s.Get(); err == nil && cert.IsValidTLSSecret(existing) {
if newSecret, updated, err := s.tls.Merge(existing, secret); err == nil && updated {
secret = newSecret
}
}
// merge new secret with existing secret from Kubernetes, if one exists
if cert.IsValidTLSSecret(targetSecret) {
if newSecret, updated, err := s.tls.Merge(targetSecret, secret); err != nil {
return nil, err
} else if !updated {
return newSecret, nil
} else {
secret = newSecret
}
}
}
// ensure that the merged secret actually contains data before overwriting the existing fields
if !cert.IsValidTLSSecret(secret) {
logrus.Warnf("Skipping save of TLS secret for %s/%s due to missing certificate data", secret.Namespace, secret.Name)
return targetSecret, nil
if newSecret, updated, err := s.tls.Merge(targetSecret, secret); err != nil {
return nil, err
} else if !updated {
return newSecret, nil
} else {
secret = newSecret
}
targetSecret.Annotations = secret.Annotations
@@ -186,47 +154,31 @@ func (s *storage) saveInK8s(secret *v1.Secret) (*v1.Secret, error) {
targetSecret.Data = secret.Data
if targetSecret.UID == "" {
logrus.Infof("Creating new TLS secret for %s/%s (count: %d): %v", targetSecret.Namespace, targetSecret.Name, len(targetSecret.Annotations)-1, targetSecret.Annotations)
logrus.Infof("Creating new TLS secret for %v (count: %d): %v", targetSecret.Name, len(targetSecret.Annotations)-1, targetSecret.Annotations)
return s.secrets.Create(targetSecret)
}
logrus.Infof("Updating TLS secret for %s/%s (count: %d): %v", targetSecret.Namespace, targetSecret.Name, len(targetSecret.Annotations)-1, targetSecret.Annotations)
logrus.Infof("Updating TLS secret for %v (count: %d): %v", targetSecret.Name, len(targetSecret.Annotations)-1, targetSecret.Annotations)
return s.secrets.Update(targetSecret)
}
func (s *storage) Update(secret *v1.Secret) error {
// Asynchronously update the Kubernetes secret, as doing so inline may block the listener from
// accepting new connections if the apiserver becomes unavailable after the Secrets controller
// has been initialized. We're not passing around any contexts here, nor does the controller
// accept any, so there's no good way to soft-fail with a reasonable timeout.
go func() {
if err := s.update(secret); err != nil {
logrus.Errorf("Failed to save TLS secret for %s/%s: %v", secret.Namespace, secret.Name, err)
}
}()
return nil
}
func (s *storage) Update(secret *v1.Secret) (err error) {
s.Lock()
defer s.Unlock()
func (s *storage) update(secret *v1.Secret) (err error) {
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
for i := 0; i < 3; i++ {
secret, err = s.saveInK8s(secret)
return err
})
if errors.IsConflict(err) {
continue
} else if err != nil {
return err
}
break
}
if err != nil {
return err
}
// Only hold the lock while updating underlying storage
s.Lock()
defer s.Unlock()
// update underlying storage
return s.storage.Update(secret)
}
func (s *storage) controllerHasSynced() bool {
s.RLock()
defer s.RUnlock()
if s.secrets == nil {
return false
}
return s.secrets.Informer().HasSynced()
}

View File

@@ -39,7 +39,7 @@ func (m *memory) Update(secret *v1.Secret) error {
}
}
logrus.Infof("Active TLS secret %s/%s (ver=%s) (count %d): %v", secret.Namespace, secret.Name, secret.ResourceVersion, len(secret.Annotations)-1, secret.Annotations)
logrus.Infof("Active TLS secret %s (ver=%s) (count %d): %v", secret.Name, secret.ResourceVersion, len(secret.Annotations)-1, secret.Annotations)
m.secret = secret
}
return nil