diff --git a/go.mod b/go.mod index be94cbfa34..62c2a5be2d 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ replace ( github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.2 github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.3-0.20210316141917-a8c4a9ee0f6b github.com/rancher/k3s/pkg/data => ./pkg/data + github.com/rancher/wrangler => github.com/rancher/wrangler v0.8.10 go.etcd.io/etcd/api/v3 => github.com/k3s-io/etcd/api/v3 v3.5.0-k3s2 go.etcd.io/etcd/client/v3 => github.com/k3s-io/etcd/client/v3 v3.5.0-k3s2 go.etcd.io/etcd/etcdutl/v3 => github.com/k3s-io/etcd/etcdutl/v3 v3.5.0-k3s2 @@ -106,11 +107,11 @@ require ( github.com/otiai10/copy v1.6.0 github.com/pierrec/lz4 v2.6.0+incompatible github.com/pkg/errors v0.9.1 - github.com/rancher/dynamiclistener v0.2.7 + github.com/rancher/dynamiclistener v0.3.1 github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08 github.com/rancher/remotedialer v0.2.0 github.com/rancher/wharfie v0.3.4 - github.com/rancher/wrangler v0.8.8 + github.com/rancher/wrangler v0.8.9 github.com/robfig/cron/v3 v3.0.1 github.com/rootless-containers/rootlesskit v0.14.5 github.com/sirupsen/logrus v1.8.1 diff --git a/go.sum b/go.sum index 1c607e2b2e..ef5b77ccb8 100644 --- a/go.sum +++ b/go.sum @@ -870,17 +870,16 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/quobyte/api v0.1.8/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= -github.com/rancher/dynamiclistener v0.2.7 h1:4FTlQtmHO6cY/g4XtGNAZTTxJYEdwn7VgtunX06NFjQ= -github.com/rancher/dynamiclistener v0.2.7/go.mod h1:iXFvJLvLjmTzEJBrLFZl9UaMfDLOhv6fHp9fHQRlHGg= +github.com/rancher/dynamiclistener v0.3.1 h1:dx4r+K7uZm5jsOvkD0I+fSMAQdGUcQCGjRiR0ZJYjm8= +github.com/rancher/dynamiclistener v0.3.1/go.mod h1:k+C1+rfUr5SlIyEQnDxSpu0NaBlmTLKc1s3KmyS8gXA= 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/remotedialer v0.2.0 h1:xD7t3K6JYwTdAsxmGtTHQMkEkFgKouQ1foLxVW424Dc= github.com/rancher/remotedialer v0.2.0/go.mod h1:tkU8ZvrR5lRgaKWaX71nAy6daeqvPFx/lJEnbW7tXSI= github.com/rancher/wharfie v0.3.4 h1:wVqFW1cyRA20zY90Z56VrjG0lWg3oFmf/4ryr5Dkkis= github.com/rancher/wharfie v0.3.4/go.mod h1:cb8mSczpmw7ItbPF3K1W7crWuJLVdyV49sZZuaY4BS8= -github.com/rancher/wrangler v0.8.3/go.mod h1:dKEaHNB4izxmPUtpq1Hvr3z3Oh+9k5pCZyFO9sUhlaY= -github.com/rancher/wrangler v0.8.8 h1:3EEQmfUqZ/UG4hERHhihB+Q2F1s/W4CmfsPmXv17wS8= -github.com/rancher/wrangler v0.8.8/go.mod h1:dKEaHNB4izxmPUtpq1Hvr3z3Oh+9k5pCZyFO9sUhlaY= +github.com/rancher/wrangler v0.8.10 h1:GfM3dZyw3TconwqknRm6YO/wiKEbUIGl0/HWVqBy658= +github.com/rancher/wrangler v0.8.10/go.mod h1:Lte9WjPtGYxYacIWeiS9qawvu2R4NujFU9xuXWJvc/0= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= diff --git a/vendor/github.com/rancher/dynamiclistener/README.md b/vendor/github.com/rancher/dynamiclistener/README.md new file mode 100644 index 0000000000..5d64601a2d --- /dev/null +++ b/vendor/github.com/rancher/dynamiclistener/README.md @@ -0,0 +1,10 @@ +# [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). diff --git a/vendor/github.com/rancher/dynamiclistener/cert/cert.go b/vendor/github.com/rancher/dynamiclistener/cert/cert.go index c82e2d5d5b..2ef70da9b4 100644 --- a/vendor/github.com/rancher/dynamiclistener/cert/cert.go +++ b/vendor/github.com/rancher/dynamiclistener/cert/cert.go @@ -90,6 +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) } diff --git a/vendor/github.com/rancher/dynamiclistener/factory/cert_utils.go b/vendor/github.com/rancher/dynamiclistener/factory/cert_utils.go index 95405e6807..1f21593b75 100644 --- a/vendor/github.com/rancher/dynamiclistener/factory/cert_utils.go +++ b/vendor/github.com/rancher/dynamiclistener/factory/cert_utils.go @@ -10,13 +10,17 @@ import ( "math" "math/big" "net" + "os" + "strconv" + "strings" "time" "github.com/sirupsen/logrus" ) const ( - CertificateBlockType = "CERTIFICATE" + CertificateBlockType = "CERTIFICATE" + defaultNewSignedCertExpirationDays = 365 ) func NewSelfSignedCACert(key crypto.Signer, cn string, org ...string) (*x509.Certificate, error) { @@ -39,6 +43,9 @@ 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) } @@ -59,6 +66,12 @@ 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 @@ -75,12 +88,22 @@ 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 * 365).UTC(), + NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(expirationDays)).UTC(), NotBefore: caCert.NotBefore, SerialNumber: serialNumber, Subject: pkix.Name{ diff --git a/vendor/github.com/rancher/dynamiclistener/factory/gen.go b/vendor/github.com/rancher/dynamiclistener/factory/gen.go index 5bad1595fe..48d21d8cc9 100644 --- a/vendor/github.com/rancher/dynamiclistener/factory/gen.go +++ b/vendor/github.com/rancher/dynamiclistener/factory/gen.go @@ -6,7 +6,9 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/sha1" + "crypto/sha256" "crypto/x509" + "encoding/hex" "encoding/pem" "fmt" "net" @@ -68,20 +70,41 @@ 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 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. +// 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. func (t *TLS) Merge(target, additional *v1.Secret) (*v1.Secret, bool, error) { - secret, updated, err := t.AddCN(target, cns(additional)...) - if !updated { - if target.Annotations[fingerprint] != additional.Annotations[fingerprint] { - secret = additional - updated = true - } + // static secrets can't be altered, don't bother trying + if IsStatic(target) { + return target, false, nil } - return secret, updated, err + + 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...) } // Renew returns a copy of the given certificate that has been re-signed @@ -174,7 +197,7 @@ func populateCN(secret *v1.Secret, cn ...string) *v1.Secret { } for _, cn := range cn { if cnRegexp.MatchString(cn) { - secret.Annotations[cnPrefix+cn] = cn + secret.Annotations[getAnnotationKey(cn)] = cn } else { logrus.Errorf("dropping invalid CN: %s", cn) } @@ -185,7 +208,10 @@ 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 { - return secret.Annotations[Static] == "true" + if secret != nil && secret.Annotations != nil { + return secret.Annotations[Static] == "true" + } + return false } // NeedsUpdate returns true if any of the CNs are not currently present on the @@ -198,7 +224,7 @@ func NeedsUpdate(maxSANs int, secret *v1.Secret, cn ...string) bool { } for _, cn := range cn { - if secret.Annotations[cnPrefix+cn] == "" { + if secret.Annotations[getAnnotationKey(cn)] == "" { if maxSANs > 0 && len(cns(secret)) >= maxSANs { return false } @@ -242,3 +268,22 @@ 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] +} diff --git a/vendor/github.com/rancher/dynamiclistener/filter.go b/vendor/github.com/rancher/dynamiclistener/filter.go new file mode 100644 index 0000000000..d1789664f2 --- /dev/null +++ b/vendor/github.com/rancher/dynamiclistener/filter.go @@ -0,0 +1,12 @@ +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 + } +} diff --git a/vendor/github.com/rancher/dynamiclistener/go.mod b/vendor/github.com/rancher/dynamiclistener/go.mod index d0420c1d49..a70fff57d3 100644 --- a/vendor/github.com/rancher/dynamiclistener/go.mod +++ b/vendor/github.com/rancher/dynamiclistener/go.mod @@ -1,9 +1,9 @@ module github.com/rancher/dynamiclistener -go 1.16 +go 1.12 require ( - github.com/rancher/wrangler v0.8.3 + github.com/rancher/wrangler v0.8.9 github.com/sirupsen/logrus v1.4.2 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 k8s.io/api v0.18.8 diff --git a/vendor/github.com/rancher/dynamiclistener/go.sum b/vendor/github.com/rancher/dynamiclistener/go.sum index 9a359c67ae..38084cb921 100644 --- a/vendor/github.com/rancher/dynamiclistener/go.sum +++ b/vendor/github.com/rancher/dynamiclistener/go.sum @@ -86,6 +86,7 @@ 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= @@ -307,8 +308,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.3 h1:m3d5ChOQj2Pdozy6nkGiSzAgQxlQlXRis2zSRwaO83k= -github.com/rancher/wrangler v0.8.3/go.mod h1:dKEaHNB4izxmPUtpq1Hvr3z3Oh+9k5pCZyFO9sUhlaY= +github.com/rancher/wrangler v0.8.9 h1:qNHBUw7jHdQKBVX4ksmY9ckth6oZaL2tRUKMwtERZw8= +github.com/rancher/wrangler v0.8.9/go.mod h1:Lte9WjPtGYxYacIWeiS9qawvu2R4NujFU9xuXWJvc/0= 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= @@ -343,8 +344,9 @@ 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= @@ -534,6 +536,8 @@ 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= diff --git a/vendor/github.com/rancher/dynamiclistener/listener.go b/vendor/github.com/rancher/dynamiclistener/listener.go index 096145cf9c..072fa3e21c 100644 --- a/vendor/github.com/rancher/dynamiclistener/listener.go +++ b/vendor/github.com/rancher/dynamiclistener/listener.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "net" "net/http" + "runtime" "strings" "sync" "time" @@ -162,7 +163,10 @@ type listener struct { func (l *listener) WrapExpiration(days int) net.Listener { ctx, cancel := context.WithCancel(context.Background()) go func() { - time.Sleep(30 * time.Second) + // busy-wait for certificate preload to complete + for l.cert == nil { + runtime.Gosched() + } for { wait := 6 * time.Hour @@ -258,7 +262,13 @@ func (l *listener) checkExpiration(days int) error { func (l *listener) Accept() (net.Conn, error) { l.init.Do(func() { if len(l.sans) > 0 { - l.updateCert(l.sans...) + 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) + } } }) @@ -280,7 +290,7 @@ func (l *listener) Accept() (net.Conn, error) { if !strings.Contains(host, ":") { if err := l.updateCert(host); err != nil { - logrus.Infof("failed to create TLS cert for: %s, %v", host, err) + logrus.Errorf("failed to update cert with listener address: %v", err) } } @@ -308,8 +318,9 @@ func (l *listener) wrap(conn net.Conn) net.Conn { type closeWrapper struct { net.Conn - id int - l *listener + id int + l *listener + ready bool } func (c *closeWrapper) close() error { @@ -324,13 +335,15 @@ 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() + return l.loadCert(newConn) } func (l *listener) updateCert(cn ...string) error { @@ -347,7 +360,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 } @@ -365,14 +378,16 @@ func (l *listener) updateCert(cn ...string) error { if err := l.storage.Update(secret); err != nil { return err } - // clear version to force cert reload + // 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. l.version = "" } return nil } -func (l *listener) loadCert() (*tls.Certificate, error) { +func (l *listener) loadCert(currentConn net.Conn) (*tls.Certificate, error) { l.RLock() defer l.RUnlock() @@ -402,12 +417,17 @@ func (l *listener) loadCert() (*tls.Certificate, error) { return nil, err } - // cert has changed, close closeWrapper wrapped connections - if l.conns != nil { + // cert has changed, close closeWrapper wrapped connections if this isn't the first load + if currentConn != nil && l.conns != nil && l.cert != 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() } @@ -431,7 +451,9 @@ func (l *listener) cacheHandler() http.Handler { } } - l.updateCert(h) + if err := l.updateCert(h); err != nil { + logrus.Errorf("failed to update cert with HTTP request Host header: %v", err) + } } }) } diff --git a/vendor/github.com/rancher/dynamiclistener/storage/file/file.go b/vendor/github.com/rancher/dynamiclistener/storage/file/file.go index 06729446b2..a7f1376237 100644 --- a/vendor/github.com/rancher/dynamiclistener/storage/file/file.go +++ b/vendor/github.com/rancher/dynamiclistener/storage/file/file.go @@ -2,9 +2,10 @@ package file import ( "encoding/json" - "github.com/rancher/dynamiclistener" - "k8s.io/api/core/v1" "os" + + "github.com/rancher/dynamiclistener" + v1 "k8s.io/api/core/v1" ) func New(file string) dynamiclistener.TLSStorage { diff --git a/vendor/github.com/rancher/dynamiclistener/storage/kubernetes/controller.go b/vendor/github.com/rancher/dynamiclistener/storage/kubernetes/controller.go index 2e82e96646..b1a9e404f9 100644 --- a/vendor/github.com/rancher/dynamiclistener/storage/kubernetes/controller.go +++ b/vendor/github.com/rancher/dynamiclistener/storage/kubernetes/controller.go @@ -42,7 +42,7 @@ func New(ctx context.Context, core CoreGetter, namespace, name string, backing d core := core() if core != nil { storage.init(core.Core().V1().Secret()) - start.All(ctx, 5, core) + _ = start.All(ctx, 5, core) return } @@ -89,18 +89,31 @@ func (s *storage) init(secrets v1controller.SecretController) { }) s.secrets = secrets - 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, - }) + secret, err := s.storage.Get() + if err == nil && secret != nil && len(secret.Data) > 0 { + // 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) } } } @@ -130,23 +143,31 @@ func (s *storage) saveInK8s(secret *v1.Secret) (*v1.Secret, error) { 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 newSecret, updated, err := s.tls.Merge(targetSecret, secret); err != nil { - return nil, err - } else if !updated { - return newSecret, nil - } else { - secret = newSecret + // 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.storage.Get(); err == nil && existing != nil && len(existing.Data) > 0 { + 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 len(targetSecret.Data) > 0 { + 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 diff --git a/vendor/github.com/rancher/wrangler/pkg/apply/desiredset_process.go b/vendor/github.com/rancher/wrangler/pkg/apply/desiredset_process.go index 2386c8a089..aabea1d37a 100644 --- a/vendor/github.com/rancher/wrangler/pkg/apply/desiredset_process.go +++ b/vendor/github.com/rancher/wrangler/pkg/apply/desiredset_process.go @@ -1,17 +1,21 @@ package apply import ( + "context" "fmt" "sort" + "sync" "github.com/pkg/errors" gvk2 "github.com/rancher/wrangler/pkg/gvk" "github.com/rancher/wrangler/pkg/merr" "github.com/rancher/wrangler/pkg/objectset" "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" errors2 "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -247,7 +251,7 @@ func (o *desiredSet) process(debugID string, set labels.Selector, gvk schema.Gro reconciler := o.reconcilers[gvk] - existing, err := o.list(nsed, controller, client, set) + existing, err := o.list(nsed, controller, client, set, objs) if err != nil { o.err(errors.Wrapf(err, "failed to list %s for %s", gvk, debugID)) return @@ -334,32 +338,31 @@ func (o *desiredSet) process(debugID string, set labels.Selector, gvk schema.Gro } } -func (o *desiredSet) list(namespaced bool, informer cache.SharedIndexInformer, client dynamic.NamespaceableResourceInterface, selector labels.Selector) (map[objectset.ObjectKey]runtime.Object, error) { +func (o *desiredSet) list(namespaced bool, informer cache.SharedIndexInformer, client dynamic.NamespaceableResourceInterface, + selector labels.Selector, desiredObjects map[objectset.ObjectKey]runtime.Object) (map[objectset.ObjectKey]runtime.Object, error) { var ( errs []error objs = map[objectset.ObjectKey]runtime.Object{} ) if informer == nil { - var c dynamic.ResourceInterface + // if a lister namespace is set assume all objects belong to the listerNamespace + // otherwise use distinct namespaces from the desired objects + // (even if not namespaced as we'll get a single empty namespace in this case which is working OK) + var namespaces []string if o.listerNamespace != "" { - c = client.Namespace(o.listerNamespace) + namespaces = append(namespaces, o.listerNamespace) } else { - c = client + namespaces = getDistinctNamespaces(desiredObjects) } - list, err := c.List(o.ctx, v1.ListOptions{ - LabelSelector: selector.String(), - }) - if err != nil { - return nil, err - } - - for _, obj := range list.Items { - copy := obj - if err := addObjectToMap(objs, ©); err != nil { + err := multiNamespaceList(o.ctx, namespaces, client, selector, func(obj unstructured.Unstructured) { + if err := addObjectToMap(objs, &obj); err != nil { errs = append(errs, err) } + }) + if err != nil { + errs = append(errs, err) } return objs, merr.NewErrors(errs...) @@ -433,3 +436,52 @@ func addObjectToMap(objs map[objectset.ObjectKey]runtime.Object, obj interface{} return nil } + +// multiNamespaceList lists objects across all given namespaces, because requests are concurrent it is possible for appendFn to be called before errors are reported. +func multiNamespaceList(ctx context.Context, namespaces []string, baseClient dynamic.NamespaceableResourceInterface, selector labels.Selector, appendFn func(obj unstructured.Unstructured)) error { + var mu sync.Mutex + wg, _ctx := errgroup.WithContext(ctx) + + // list all namespaces concurrently + for _, namespace := range namespaces { + namespace := namespace + wg.Go(func() error { + list, err := baseClient.Namespace(namespace).List(_ctx, v1.ListOptions{ + LabelSelector: selector.String(), + }) + if err != nil { + return err + } + + mu.Lock() + for _, obj := range list.Items { + appendFn(obj) + } + mu.Unlock() + + return nil + }) + } + + return wg.Wait() +} + +func getDistinctNamespaces(objs map[objectset.ObjectKey]runtime.Object) (namespaces []string) { + for objKey, _ := range objs { + var duplicate bool + for i := range namespaces { + if namespaces[i] == objKey.Namespace { + duplicate = true + break + } + } + + if duplicate { + continue + } + + namespaces = append(namespaces, objKey.Namespace) + } + + return +} diff --git a/vendor/github.com/rancher/wrangler/pkg/objectset/objectset.go b/vendor/github.com/rancher/wrangler/pkg/objectset/objectset.go index 5c041dadf3..2d0724e265 100644 --- a/vendor/github.com/rancher/wrangler/pkg/objectset/objectset.go +++ b/vendor/github.com/rancher/wrangler/pkg/objectset/objectset.go @@ -167,6 +167,31 @@ func (o *ObjectSet) GVKOrder(known ...schema.GroupVersionKind) []schema.GroupVer return append(o.gvkOrder, rest...) } +// Namespaces all distinct namespaces found on the objects in this set. +func (o *ObjectSet) Namespaces() (namespaces []string) { + for _, objsByKey := range o.ObjectsByGVK() { + for objKey, _ := range objsByKey { + + // do not add duplicate namespace entries + var duplicate bool + for i := range namespaces { + if namespaces[i] == objKey.Namespace { + duplicate = true + break + } + } + + if duplicate { + continue + } + + namespaces = append(namespaces, objKey.Namespace) + } + } + + return +} + type ObjectByGK map[schema.GroupKind]map[ObjectKey]runtime.Object func (o ObjectByGK) Add(obj runtime.Object) (schema.GroupKind, error) { diff --git a/vendor/modules.txt b/vendor/modules.txt index 05eb1f3833..45f3908133 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -977,7 +977,7 @@ github.com/prometheus/common/model github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util -# github.com/rancher/dynamiclistener v0.2.7 +# github.com/rancher/dynamiclistener v0.3.1 ## explicit github.com/rancher/dynamiclistener github.com/rancher/dynamiclistener/cert @@ -999,7 +999,7 @@ github.com/rancher/remotedialer # github.com/rancher/wharfie v0.3.4 ## explicit github.com/rancher/wharfie/pkg/registries -# github.com/rancher/wrangler v0.8.8 +# github.com/rancher/wrangler v0.8.9 => github.com/rancher/wrangler v0.8.10 ## explicit github.com/rancher/wrangler/pkg/apply github.com/rancher/wrangler/pkg/apply/injectors @@ -3414,6 +3414,7 @@ sigs.k8s.io/yaml # github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.2 # github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.3-0.20210316141917-a8c4a9ee0f6b # github.com/rancher/k3s/pkg/data => ./pkg/data +# github.com/rancher/wrangler => github.com/rancher/wrangler v0.8.10 # go.etcd.io/etcd/api/v3 => github.com/k3s-io/etcd/api/v3 v3.5.0-k3s2 # go.etcd.io/etcd/client/v3 => github.com/k3s-io/etcd/client/v3 v3.5.0-k3s2 # go.etcd.io/etcd/etcdutl/v3 => github.com/k3s-io/etcd/etcdutl/v3 v3.5.0-k3s2