mirror of
https://github.com/rancher/dynamiclistener.git
synced 2025-09-02 13:44:26 +00:00
Compare commits
38 Commits
v0.2.0
...
v0.2.3-k3s
Author | SHA1 | Date | |
---|---|---|---|
|
715d540640 | ||
|
11104ec0ea | ||
|
fc8cf5f3ea | ||
|
3878ff2a1f | ||
|
1b2460c151 | ||
|
e34610a1ae | ||
|
7c224dcdfb | ||
|
53f6b38760 | ||
|
479ab335d6 | ||
|
2bfb7bd0cb | ||
|
94e23c7edb | ||
|
52ede5ec92 | ||
|
5c222d5753 | ||
|
74a61a850d | ||
|
4436fc6b48 | ||
|
4bac3f291f | ||
|
c992ce309c | ||
|
763229ddcd | ||
|
171fcf6b79 | ||
|
05d7922a86 | ||
|
1e67d402dc | ||
|
7e3fc0c594 | ||
|
111c5b43e9 | ||
|
bd73d0d4bc | ||
|
5276ad483a | ||
|
8545ce98db | ||
|
3f92468568 | ||
|
5ba69b1c5f | ||
|
6281628cd4 | ||
|
0b114dc0c2 | ||
|
ece289ed54 | ||
|
bc68bf5499 | ||
|
795bb90214 | ||
|
dcc205f52d | ||
|
4e8035fa46 | ||
|
a75e84bc81 | ||
|
ab900b5268 | ||
|
f1484a07b3 |
26
cert/cert.go
26
cert/cert.go
@@ -33,7 +33,7 @@ import (
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -45,6 +45,10 @@ const (
|
||||
duration365d = time.Hour * 24 * 365
|
||||
)
|
||||
|
||||
var (
|
||||
ErrStaticCert = errors.New("cannot renew static certificate")
|
||||
)
|
||||
|
||||
// Config contains the basic fields required for creating a certificate
|
||||
type Config struct {
|
||||
CommonName string
|
||||
@@ -119,7 +123,13 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x509.ParseCertificate(certDERBytes)
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(certDERBytes)
|
||||
if err == nil {
|
||||
logrus.Infof("certificate %s signed by %s: notBefore=%s notAfter=%s",
|
||||
parsedCert.Subject, caCert.Subject, parsedCert.NotBefore, parsedCert.NotAfter)
|
||||
}
|
||||
return parsedCert, err
|
||||
}
|
||||
|
||||
// MakeEllipticPrivateKeyPEM creates an ECDSA private key
|
||||
@@ -161,8 +171,8 @@ func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, a
|
||||
maxAge := time.Hour * 24 * 365 // one year self-signed certs
|
||||
|
||||
baseName := fmt.Sprintf("%s_%s_%s", host, strings.Join(ipsToStrings(alternateIPs), "-"), strings.Join(alternateDNS, "-"))
|
||||
certFixturePath := path.Join(fixtureDirectory, baseName+".crt")
|
||||
keyFixturePath := path.Join(fixtureDirectory, baseName+".key")
|
||||
certFixturePath := filepath.Join(fixtureDirectory, baseName+".crt")
|
||||
keyFixturePath := filepath.Join(fixtureDirectory, baseName+".key")
|
||||
if len(fixtureDirectory) > 0 {
|
||||
cert, err := ioutil.ReadFile(certFixturePath)
|
||||
if err == nil {
|
||||
@@ -271,11 +281,11 @@ func ipsToStrings(ips []net.IP) []string {
|
||||
}
|
||||
|
||||
// IsCertExpired checks if the certificate about to expire
|
||||
func IsCertExpired(cert *x509.Certificate) bool {
|
||||
func IsCertExpired(cert *x509.Certificate, days int) bool {
|
||||
expirationDate := cert.NotAfter
|
||||
diffDays := expirationDate.Sub(time.Now()).Hours() / 24.0
|
||||
if diffDays <= 90 {
|
||||
logrus.Infof("certificate will expire in %f days", diffDays)
|
||||
diffDays := time.Until(expirationDate).Hours() / 24.0
|
||||
if diffDays <= float64(days) {
|
||||
logrus.Infof("certificate %s will expire in %f days at %s", cert.Subject, diffDays, cert.NotAfter)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@@ -34,15 +34,15 @@ func CanReadCertAndKey(certPath, keyPath string) (bool, error) {
|
||||
certReadable := canReadFile(certPath)
|
||||
keyReadable := canReadFile(keyPath)
|
||||
|
||||
if certReadable == false && keyReadable == false {
|
||||
if !certReadable && !keyReadable {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if certReadable == false {
|
||||
if !certReadable {
|
||||
return false, fmt.Errorf("error reading %s, certificate and key must be supplied as a pair", certPath)
|
||||
}
|
||||
|
||||
if keyReadable == false {
|
||||
if !keyReadable {
|
||||
return false, fmt.Errorf("error reading %s, certificate and key must be supplied as a pair", keyPath)
|
||||
}
|
||||
|
||||
|
@@ -59,16 +59,7 @@ func loadCA() (*x509.Certificate, crypto.Signer, error) {
|
||||
return LoadCerts("./certs/ca.pem", "./certs/ca.key")
|
||||
}
|
||||
|
||||
func LoadCerts(certFile, keyFile string) (*x509.Certificate, crypto.Signer, error) {
|
||||
caPem, err := ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
caKey, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
func LoadCA(caPem, caKey []byte) (*x509.Certificate, crypto.Signer, error) {
|
||||
key, err := cert.ParsePrivateKeyPEM(caKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -85,3 +76,16 @@ func LoadCerts(certFile, keyFile string) (*x509.Certificate, crypto.Signer, erro
|
||||
|
||||
return cert, signer, nil
|
||||
}
|
||||
|
||||
func LoadCerts(certFile, keyFile string) (*x509.Certificate, crypto.Signer, error) {
|
||||
caPem, err := ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
caKey, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return LoadCA(caPem, caKey)
|
||||
}
|
||||
|
@@ -11,6 +11,8 @@ import (
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,6 +42,31 @@ func NewSelfSignedCACert(key crypto.Signer, cn string, org ...string) (*x509.Cer
|
||||
return x509.ParseCertificate(certDERBytes)
|
||||
}
|
||||
|
||||
func NewSignedClientCert(signer crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer, cn string) (*x509.Certificate, error) {
|
||||
serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parent := x509.Certificate{
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365).UTC(),
|
||||
NotBefore: caCert.NotBefore,
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: cn,
|
||||
},
|
||||
}
|
||||
|
||||
cert, err := x509.CreateCertificate(rand.Reader, &parent, caCert, signer.Public(), caKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(cert)
|
||||
}
|
||||
|
||||
func NewSignedCert(signer crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer, cn string, orgs []string,
|
||||
domains []string, ips []net.IP) (*x509.Certificate, error) {
|
||||
|
||||
@@ -67,7 +94,12 @@ func NewSignedCert(signer crypto.Signer, caCert *x509.Certificate, caKey crypto.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(cert)
|
||||
parsedCert, err := x509.ParseCertificate(cert)
|
||||
if err == nil {
|
||||
logrus.Infof("certificate %s signed by %s: notBefore=%s notAfter=%s",
|
||||
parsedCert.Subject, caCert.Subject, parsedCert.NotBefore, parsedCert.NotAfter)
|
||||
}
|
||||
return parsedCert, err
|
||||
}
|
||||
|
||||
func ParseCertPEM(pemCerts []byte) (*x509.Certificate, error) {
|
||||
|
122
factory/gen.go
122
factory/gen.go
@@ -5,22 +5,28 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/dynamiclistener/cert"
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
cnPrefix = "listener.cattle.io/cn-"
|
||||
static = "listener.cattle.io/static"
|
||||
hashKey = "listener.cattle.io/hash"
|
||||
cnPrefix = "listener.cattle.io/cn-"
|
||||
Static = "listener.cattle.io/static"
|
||||
fingerprint = "listener.cattle.io/fingerprint"
|
||||
)
|
||||
|
||||
var (
|
||||
cnRegexp = regexp.MustCompile("^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$")
|
||||
)
|
||||
|
||||
type TLS struct {
|
||||
@@ -28,9 +34,13 @@ type TLS struct {
|
||||
CAKey crypto.Signer
|
||||
CN string
|
||||
Organization []string
|
||||
FilterCN func(...string) []string
|
||||
}
|
||||
|
||||
func cns(secret *v1.Secret) (cns []string) {
|
||||
if secret == nil {
|
||||
return nil
|
||||
}
|
||||
for k, v := range secret.Annotations {
|
||||
if strings.HasPrefix(k, cnPrefix) {
|
||||
cns = append(cns, v)
|
||||
@@ -39,16 +49,14 @@ func cns(secret *v1.Secret) (cns []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func collectCNs(secret *v1.Secret) (domains []string, ips []net.IP, hash string, err error) {
|
||||
func collectCNs(secret *v1.Secret) (domains []string, ips []net.IP, err error) {
|
||||
var (
|
||||
cns = cns(secret)
|
||||
digest = sha256.New()
|
||||
cns = cns(secret)
|
||||
)
|
||||
|
||||
sort.Strings(cns)
|
||||
|
||||
for _, v := range cns {
|
||||
digest.Write([]byte(v))
|
||||
ip := net.ParseIP(v)
|
||||
if ip == nil {
|
||||
domains = append(domains, v)
|
||||
@@ -57,22 +65,71 @@ func collectCNs(secret *v1.Secret) (domains []string, ips []net.IP, hash string,
|
||||
}
|
||||
}
|
||||
|
||||
hash = hex.EncodeToString(digest.Sum(nil))
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TLS) Merge(secret, other *v1.Secret) (*v1.Secret, bool, error) {
|
||||
return t.AddCN(secret, cns(other)...)
|
||||
// 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) {
|
||||
secret, updated, err := t.AddCN(target, cns(additional)...)
|
||||
if !updated {
|
||||
if target.Annotations[fingerprint] != additional.Annotations[fingerprint] {
|
||||
secret = additional
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
return secret, updated, err
|
||||
}
|
||||
|
||||
func (t *TLS) AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
// Renew returns a copy of the given certificate that has been re-signed
|
||||
// to extend the NotAfter date. It is an error to attempt to renew
|
||||
// a static (user-provided) certificate.
|
||||
func (t *TLS) Renew(secret *v1.Secret) (*v1.Secret, error) {
|
||||
if IsStatic(secret) {
|
||||
return secret, cert.ErrStaticCert
|
||||
}
|
||||
cns := cns(secret)
|
||||
secret = secret.DeepCopy()
|
||||
secret.Annotations = map[string]string{}
|
||||
secret, _, err := t.generateCert(secret, cns...)
|
||||
return secret, err
|
||||
}
|
||||
|
||||
if !NeedsUpdate(secret, cn...) {
|
||||
// Filter ensures that the CNs are all valid accorting to both internal logic, and any filter callbacks.
|
||||
// The returned list will contain only approved CN entries.
|
||||
func (t *TLS) Filter(cn ...string) []string {
|
||||
if len(cn) == 0 || t.FilterCN == nil {
|
||||
return cn
|
||||
}
|
||||
return t.FilterCN(cn...)
|
||||
}
|
||||
|
||||
// AddCN attempts to add a list of CN strings to a given Secret, returning the potentially-modified
|
||||
// Secret along with a bool indicating whether or not it has been updated. The Secret will not be changed
|
||||
// if it has an attribute indicating that it is static (aka user-provided), or if no new CNs were added.
|
||||
func (t *TLS) AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) {
|
||||
cn = t.Filter(cn...)
|
||||
|
||||
if IsStatic(secret) || !NeedsUpdate(0, secret, cn...) {
|
||||
return secret, false, nil
|
||||
}
|
||||
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 {
|
||||
secret = &v1.Secret{}
|
||||
}
|
||||
|
||||
secret = populateCN(secret, cn...)
|
||||
|
||||
@@ -81,7 +138,7 @@ func (t *TLS) AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
domains, ips, hash, err := collectCNs(secret)
|
||||
domains, ips, err := collectCNs(secret)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -101,7 +158,7 @@ func (t *TLS) AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) {
|
||||
}
|
||||
secret.Data[v1.TLSCertKey] = certBytes
|
||||
secret.Data[v1.TLSPrivateKeyKey] = keyBytes
|
||||
secret.Annotations[hashKey] = hash
|
||||
secret.Annotations[fingerprint] = fmt.Sprintf("SHA1=%X", sha1.Sum(newCert.Raw))
|
||||
|
||||
return secret, true, nil
|
||||
}
|
||||
@@ -116,18 +173,35 @@ func populateCN(secret *v1.Secret, cn ...string) *v1.Secret {
|
||||
secret.Annotations = map[string]string{}
|
||||
}
|
||||
for _, cn := range cn {
|
||||
secret.Annotations[cnPrefix+cn] = cn
|
||||
if cnRegexp.MatchString(cn) {
|
||||
secret.Annotations[cnPrefix+cn] = cn
|
||||
} else {
|
||||
logrus.Errorf("dropping invalid CN: %s", cn)
|
||||
}
|
||||
}
|
||||
return secret
|
||||
}
|
||||
|
||||
func NeedsUpdate(secret *v1.Secret, cn ...string) bool {
|
||||
if secret.Annotations[static] == "true" {
|
||||
return false
|
||||
// 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"
|
||||
}
|
||||
|
||||
// NeedsUpdate returns true if any of the CNs are not currently present on the
|
||||
// secret's Certificate, as recorded in the cnPrefix annotations. It will return
|
||||
// false if all requested CNs are already present, or if maxSANs is non-zero and has
|
||||
// been exceeded.
|
||||
func NeedsUpdate(maxSANs int, secret *v1.Secret, cn ...string) bool {
|
||||
if secret == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, cn := range cn {
|
||||
if secret.Annotations[cnPrefix+cn] == "" {
|
||||
if maxSANs > 0 && len(cns(secret)) >= maxSANs {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -149,6 +223,7 @@ func getPrivateKey(secret *v1.Secret) (crypto.Signer, error) {
|
||||
return NewPrivateKey()
|
||||
}
|
||||
|
||||
// Marshal returns the given cert and key as byte slices.
|
||||
func Marshal(x509Cert *x509.Certificate, privateKey crypto.Signer) ([]byte, []byte, error) {
|
||||
certBlock := pem.Block{
|
||||
Type: CertificateBlockType,
|
||||
@@ -163,6 +238,7 @@ func Marshal(x509Cert *x509.Certificate, privateKey crypto.Signer) ([]byte, []by
|
||||
return pem.EncodeToMemory(&certBlock), keyBytes, nil
|
||||
}
|
||||
|
||||
// NewPrivateKey returnes a new ECDSA key
|
||||
func NewPrivateKey() (crypto.Signer, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@@ -6,6 +6,7 @@ require (
|
||||
github.com/rancher/wrangler v0.1.4
|
||||
github.com/rancher/wrangler-api v0.2.0
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
||||
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
|
||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
|
||||
)
|
||||
|
268
listener.go
268
listener.go
@@ -1,6 +1,7 @@
|
||||
package dynamiclistener
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
@@ -8,7 +9,9 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/dynamiclistener/cert"
|
||||
"github.com/rancher/dynamiclistener/factory"
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -19,15 +22,16 @@ type TLSStorage interface {
|
||||
Update(secret *v1.Secret) error
|
||||
}
|
||||
|
||||
type SetFactory interface {
|
||||
SetFactory(tls *factory.TLS)
|
||||
type TLSFactory interface {
|
||||
Renew(secret *v1.Secret) (*v1.Secret, error)
|
||||
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 Config struct {
|
||||
CN string
|
||||
Organization []string
|
||||
TLSConfig tls.Config
|
||||
SANs []string
|
||||
type SetFactory interface {
|
||||
SetFactory(tls TLSFactory)
|
||||
}
|
||||
|
||||
func NewListener(l net.Listener, storage TLSStorage, caCert *x509.Certificate, caKey crypto.Signer, config Config) (net.Listener, http.Handler, error) {
|
||||
@@ -37,6 +41,9 @@ func NewListener(l net.Listener, storage TLSStorage, caCert *x509.Certificate, c
|
||||
if len(config.Organization) == 0 {
|
||||
config.Organization = []string{"dynamic"}
|
||||
}
|
||||
if config.TLSConfig == nil {
|
||||
config.TLSConfig = &tls.Config{}
|
||||
}
|
||||
|
||||
dynamicListener := &listener{
|
||||
factory: &factory.TLS{
|
||||
@@ -44,34 +51,210 @@ func NewListener(l net.Listener, storage TLSStorage, caCert *x509.Certificate, c
|
||||
CAKey: caKey,
|
||||
CN: config.CN,
|
||||
Organization: config.Organization,
|
||||
FilterCN: allowDefaultSANs(config.SANs, config.FilterCN),
|
||||
},
|
||||
Listener: l,
|
||||
storage: &nonNil{storage: storage},
|
||||
sans: config.SANs,
|
||||
maxSANs: config.MaxSANs,
|
||||
tlsConfig: config.TLSConfig,
|
||||
}
|
||||
if dynamicListener.tlsConfig == nil {
|
||||
dynamicListener.tlsConfig = &tls.Config{}
|
||||
}
|
||||
dynamicListener.tlsConfig.GetCertificate = dynamicListener.getCertificate
|
||||
|
||||
if config.CloseConnOnCertChange {
|
||||
if len(dynamicListener.tlsConfig.Certificates) == 0 {
|
||||
dynamicListener.tlsConfig.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
dynamicListener.conns = map[int]*closeWrapper{}
|
||||
}
|
||||
|
||||
if setter, ok := storage.(SetFactory); ok {
|
||||
setter.SetFactory(dynamicListener.factory)
|
||||
}
|
||||
|
||||
return tls.NewListener(dynamicListener, &dynamicListener.tlsConfig), dynamicListener.cacheHandler(), nil
|
||||
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
|
||||
}
|
||||
|
||||
func allowDefaultSANs(sans []string, next func(...string) []string) func(...string) []string {
|
||||
if next == nil {
|
||||
return nil
|
||||
} else if len(sans) == 0 {
|
||||
return next
|
||||
}
|
||||
|
||||
sanMap := map[string]bool{}
|
||||
for _, san := range sans {
|
||||
sanMap[san] = true
|
||||
}
|
||||
|
||||
return func(s ...string) []string {
|
||||
var (
|
||||
good []string
|
||||
unknown []string
|
||||
)
|
||||
for _, s := range s {
|
||||
if sanMap[s] {
|
||||
good = append(good, s)
|
||||
} else {
|
||||
unknown = append(unknown, s)
|
||||
}
|
||||
}
|
||||
|
||||
return append(good, next(unknown...)...)
|
||||
}
|
||||
}
|
||||
|
||||
type cancelClose struct {
|
||||
cancel func()
|
||||
net.Listener
|
||||
}
|
||||
|
||||
func (c *cancelClose) Close() error {
|
||||
c.cancel()
|
||||
return c.Listener.Close()
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
CN string
|
||||
Organization []string
|
||||
TLSConfig *tls.Config
|
||||
SANs []string
|
||||
MaxSANs int
|
||||
ExpirationDaysCheck int
|
||||
CloseConnOnCertChange bool
|
||||
RegenerateCerts func() bool
|
||||
FilterCN func(...string) []string
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
sync.RWMutex
|
||||
net.Listener
|
||||
|
||||
factory *factory.TLS
|
||||
conns map[int]*closeWrapper
|
||||
connID int
|
||||
connLock sync.Mutex
|
||||
|
||||
factory TLSFactory
|
||||
storage TLSStorage
|
||||
version string
|
||||
tlsConfig tls.Config
|
||||
tlsConfig *tls.Config
|
||||
cert *tls.Certificate
|
||||
sans []string
|
||||
maxSANs int
|
||||
init sync.Once
|
||||
}
|
||||
|
||||
func (l *listener) WrapExpiration(days int) net.Listener {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
for {
|
||||
wait := 6 * time.Hour
|
||||
if err := l.checkExpiration(days); err != nil {
|
||||
logrus.Errorf("failed to check and renew dynamic cert: %v", err)
|
||||
// Don't go into short retry loop if we're using a static (user-provided) cert.
|
||||
// We will still check and print an error every six hours until the user updates the secret with
|
||||
// a cert that is not about to expire. Hopefully this will prompt them to take action.
|
||||
if err != cert.ErrStaticCert {
|
||||
wait = 5 * time.Minute
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(wait):
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return &cancelClose{
|
||||
cancel: cancel,
|
||||
Listener: l,
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
if days == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if l.cert == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
secret, err := l.storage.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyPair, err := tls.X509KeyPair(secret.Data[v1.TLSCertKey], secret.Data[v1.TLSPrivateKeyKey])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certParsed, err := x509.ParseCertificate(keyPair.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cert.IsCertExpired(certParsed, days) {
|
||||
secret, err := l.factory.Renew(secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.storage.Update(secret); err != nil {
|
||||
return err
|
||||
}
|
||||
// clear version to force cert reload
|
||||
l.version = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *listener) Accept() (net.Conn, error) {
|
||||
l.init.Do(func() {
|
||||
if len(l.sans) > 0 {
|
||||
@@ -97,13 +280,49 @@ 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", host)
|
||||
logrus.Infof("failed to create TLS cert for: %s, %v", host, err)
|
||||
}
|
||||
}
|
||||
|
||||
if l.conns != nil {
|
||||
conn = l.wrap(conn)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (l *listener) wrap(conn net.Conn) net.Conn {
|
||||
l.connLock.Lock()
|
||||
defer l.connLock.Unlock()
|
||||
l.connID++
|
||||
|
||||
wrapper := &closeWrapper{
|
||||
Conn: conn,
|
||||
id: l.connID,
|
||||
l: l,
|
||||
}
|
||||
l.conns[l.connID] = wrapper
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
type closeWrapper struct {
|
||||
net.Conn
|
||||
id int
|
||||
l *listener
|
||||
}
|
||||
|
||||
func (c *closeWrapper) close() error {
|
||||
delete(c.l.conns, c.id)
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *closeWrapper) Close() error {
|
||||
c.l.connLock.Lock()
|
||||
defer c.l.connLock.Unlock()
|
||||
return c.close()
|
||||
}
|
||||
|
||||
func (l *listener) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if hello.ServerName != "" {
|
||||
if err := l.updateCert(hello.ServerName); err != nil {
|
||||
@@ -115,6 +334,11 @@ func (l *listener) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
||||
}
|
||||
|
||||
func (l *listener) updateCert(cn ...string) error {
|
||||
cn = l.factory.Filter(cn...)
|
||||
if len(cn) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
@@ -123,7 +347,7 @@ func (l *listener) updateCert(cn ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !factory.NeedsUpdate(secret, cn...) {
|
||||
if !factory.IsStatic(secret) && !factory.NeedsUpdate(l.maxSANs, secret, cn...) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,7 +380,7 @@ func (l *listener) loadCert() (*tls.Certificate, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if l.cert != nil && l.version == secret.ResourceVersion {
|
||||
if l.cert != nil && l.version == secret.ResourceVersion && secret.ResourceVersion != "" {
|
||||
return l.cert, nil
|
||||
}
|
||||
|
||||
@@ -169,7 +393,7 @@ func (l *listener) loadCert() (*tls.Certificate, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if l.cert != nil && l.version == secret.ResourceVersion {
|
||||
if l.cert != nil && l.version == secret.ResourceVersion && secret.ResourceVersion != "" {
|
||||
return l.cert, nil
|
||||
}
|
||||
|
||||
@@ -178,7 +402,17 @@ func (l *listener) loadCert() (*tls.Certificate, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// cert has changed, close closeWrapper wrapped connections
|
||||
if l.conns != nil {
|
||||
l.connLock.Lock()
|
||||
for _, conn := range l.conns {
|
||||
_ = conn.close()
|
||||
}
|
||||
l.connLock.Unlock()
|
||||
}
|
||||
|
||||
l.cert = &cert
|
||||
l.version = secret.ResourceVersion
|
||||
return l.cert, nil
|
||||
}
|
||||
|
||||
@@ -191,6 +425,12 @@ func (l *listener) cacheHandler() http.Handler {
|
||||
|
||||
ip := net.ParseIP(h)
|
||||
if len(ip) > 0 {
|
||||
for _, v := range req.Header["User-Agent"] {
|
||||
if strings.Contains(strings.ToLower(v), "mozilla") {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
l.updateCert(h)
|
||||
}
|
||||
})
|
||||
|
@@ -11,8 +11,10 @@ import (
|
||||
func HTTPRedirect(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("x-Forwarded-Proto") == "https" ||
|
||||
if r.TLS != nil ||
|
||||
r.Header.Get("x-Forwarded-Proto") == "https" ||
|
||||
r.Header.Get("x-Forwarded-Proto") == "wss" ||
|
||||
strings.HasPrefix(r.URL.Path, "/.well-known/") ||
|
||||
strings.HasPrefix(r.URL.Path, "/ping") ||
|
||||
strings.HasPrefix(r.URL.Path, "/health") {
|
||||
next.ServeHTTP(rw, r)
|
||||
|
176
server/server.go
176
server/server.go
@@ -2,45 +2,75 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/rancher/dynamiclistener"
|
||||
"github.com/rancher/dynamiclistener/factory"
|
||||
"github.com/rancher/dynamiclistener/storage/file"
|
||||
"github.com/rancher/dynamiclistener/storage/kubernetes"
|
||||
"github.com/rancher/dynamiclistener/storage/memory"
|
||||
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.Handler) error {
|
||||
var (
|
||||
// https listener will change this if http is enabled
|
||||
targetHandler = handler
|
||||
)
|
||||
type ListenOpts struct {
|
||||
CA *x509.Certificate
|
||||
CAKey crypto.Signer
|
||||
Storage dynamiclistener.TLSStorage
|
||||
Secrets v1.SecretController
|
||||
CertNamespace string
|
||||
CertName string
|
||||
CANamespace string
|
||||
CAName string
|
||||
CertBackup string
|
||||
AcmeDomains []string
|
||||
BindHost string
|
||||
NoRedirect bool
|
||||
TLSListenerConfig dynamiclistener.Config
|
||||
}
|
||||
|
||||
func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.Handler, opts *ListenOpts) error {
|
||||
if opts == nil {
|
||||
opts = &ListenOpts{}
|
||||
}
|
||||
|
||||
if opts.TLSListenerConfig.TLSConfig == nil {
|
||||
opts.TLSListenerConfig.TLSConfig = &tls.Config{}
|
||||
}
|
||||
|
||||
logger := logrus.StandardLogger()
|
||||
errorLog := log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags)
|
||||
|
||||
if httpsPort > 0 {
|
||||
caCert, caKey, err := factory.LoadOrGenCA()
|
||||
tlsTCPListener, err := dynamiclistener.NewTCPListener(opts.BindHost, httpsPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsTCPListener, err := dynamiclistener.NewTCPListener("0.0.0.0", httpsPort)
|
||||
tlsTCPListener, handler, err = getTLSListener(ctx, tlsTCPListener, handler, *opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dynListener, dynHandler, err := dynamiclistener.NewListener(tlsTCPListener, memory.New(), caCert, caKey, dynamiclistener.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
if !opts.NoRedirect {
|
||||
handler = dynamiclistener.HTTPRedirect(handler)
|
||||
}
|
||||
|
||||
targetHandler = wrapHandler(dynHandler, handler)
|
||||
tlsServer := http.Server{
|
||||
Handler: targetHandler,
|
||||
Handler: handler,
|
||||
ErrorLog: errorLog,
|
||||
}
|
||||
targetHandler = dynamiclistener.HTTPRedirect(targetHandler)
|
||||
|
||||
go func() {
|
||||
logrus.Infof("Listening on 0.0.0.0:%d", httpsPort)
|
||||
err := tlsServer.Serve(dynListener)
|
||||
logrus.Infof("Listening on %s:%d", opts.BindHost, httpsPort)
|
||||
err := tlsServer.Serve(tlsTCPListener)
|
||||
if err != http.ErrServerClosed && err != nil {
|
||||
logrus.Fatalf("https server failed: %v", err)
|
||||
}
|
||||
@@ -53,11 +83,12 @@ func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.H
|
||||
|
||||
if httpPort > 0 {
|
||||
httpServer := http.Server{
|
||||
Addr: fmt.Sprintf("0.0.0.0:%d", httpPort),
|
||||
Handler: targetHandler,
|
||||
Addr: fmt.Sprintf("%s:%d", opts.BindHost, httpPort),
|
||||
Handler: handler,
|
||||
ErrorLog: errorLog,
|
||||
}
|
||||
go func() {
|
||||
logrus.Infof("Listening on 0.0.0.0:%d", httpPort)
|
||||
logrus.Infof("Listening on %s:%d", opts.BindHost, httpPort)
|
||||
err := httpServer.ListenAndServe()
|
||||
if err != http.ErrServerClosed && err != nil {
|
||||
logrus.Fatalf("http server failed: %v", err)
|
||||
@@ -72,9 +103,118 @@ func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.H
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTLSListener(ctx context.Context, tcp net.Listener, handler http.Handler, opts ListenOpts) (net.Listener, http.Handler, error) {
|
||||
if len(opts.TLSListenerConfig.TLSConfig.NextProtos) == 0 {
|
||||
opts.TLSListenerConfig.TLSConfig.NextProtos = []string{"h2", "http/1.1"}
|
||||
}
|
||||
|
||||
if len(opts.TLSListenerConfig.TLSConfig.Certificates) > 0 {
|
||||
return tls.NewListener(tcp, opts.TLSListenerConfig.TLSConfig), handler, nil
|
||||
}
|
||||
|
||||
if len(opts.AcmeDomains) > 0 {
|
||||
return acmeListener(tcp, handler, opts)
|
||||
}
|
||||
|
||||
storage := opts.Storage
|
||||
if storage == nil {
|
||||
storage = newStorage(ctx, opts)
|
||||
}
|
||||
|
||||
caCert, caKey, err := getCA(opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
listener, dynHandler, err := dynamiclistener.NewListener(tcp, storage, caCert, caKey, opts.TLSListenerConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return listener, wrapHandler(dynHandler, handler), nil
|
||||
}
|
||||
|
||||
func getCA(opts ListenOpts) (*x509.Certificate, crypto.Signer, error) {
|
||||
if opts.CA != nil && opts.CAKey != nil {
|
||||
return opts.CA, opts.CAKey, nil
|
||||
}
|
||||
|
||||
if opts.Secrets == nil {
|
||||
return factory.LoadOrGenCA()
|
||||
}
|
||||
|
||||
if opts.CAName == "" {
|
||||
opts.CAName = "serving-ca"
|
||||
}
|
||||
|
||||
if opts.CANamespace == "" {
|
||||
opts.CANamespace = opts.CertNamespace
|
||||
}
|
||||
|
||||
if opts.CANamespace == "" {
|
||||
opts.CANamespace = "kube-system"
|
||||
}
|
||||
|
||||
return kubernetes.LoadOrGenCA(opts.Secrets, opts.CANamespace, opts.CAName)
|
||||
}
|
||||
|
||||
func newStorage(ctx context.Context, opts ListenOpts) dynamiclistener.TLSStorage {
|
||||
var result dynamiclistener.TLSStorage
|
||||
if opts.CertBackup == "" {
|
||||
result = memory.New()
|
||||
} else {
|
||||
result = memory.NewBacked(file.New(opts.CertBackup))
|
||||
}
|
||||
|
||||
if opts.Secrets == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
if opts.CertName == "" {
|
||||
opts.CertName = "serving-cert"
|
||||
}
|
||||
|
||||
if opts.CertNamespace == "" {
|
||||
opts.CertNamespace = "kube-system"
|
||||
}
|
||||
|
||||
return kubernetes.Load(ctx, opts.Secrets, opts.CertNamespace, opts.CertName, result)
|
||||
}
|
||||
|
||||
func wrapHandler(handler http.Handler, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
handler.ServeHTTP(rw, req)
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
func acmeListener(tcp net.Listener, handler http.Handler, opts ListenOpts) (net.Listener, http.Handler, error) {
|
||||
hosts := map[string]bool{}
|
||||
for _, domain := range opts.AcmeDomains {
|
||||
hosts[domain] = true
|
||||
}
|
||||
|
||||
manager := autocert.Manager{
|
||||
Cache: autocert.DirCache("certs-cache"),
|
||||
Prompt: func(tosURL string) bool {
|
||||
return true
|
||||
},
|
||||
HostPolicy: func(ctx context.Context, host string) error {
|
||||
if !hosts[host] {
|
||||
return fmt.Errorf("host %s is not configured", host)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
opts.TLSListenerConfig.TLSConfig.GetCertificate = func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if hello.ServerName == "localhost" || hello.ServerName == "" {
|
||||
newHello := *hello
|
||||
newHello.ServerName = opts.AcmeDomains[0]
|
||||
return manager.GetCertificate(&newHello)
|
||||
}
|
||||
return manager.GetCertificate(hello)
|
||||
}
|
||||
|
||||
return tls.NewListener(tcp, opts.TLSListenerConfig.TLSConfig), manager.HTTPHandler(handler), nil
|
||||
}
|
||||
|
@@ -39,4 +39,3 @@ func (s *storage) Update(secret *v1.Secret) error {
|
||||
|
||||
return json.NewEncoder(f).Encode(secret)
|
||||
}
|
||||
|
||||
|
103
storage/kubernetes/ca.go
Normal file
103
storage/kubernetes/ca.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/rancher/dynamiclistener/factory"
|
||||
v1controller "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func LoadOrGenCA(secrets v1controller.SecretClient, namespace, name string) (*x509.Certificate, crypto.Signer, error) {
|
||||
secret, err := getSecret(secrets, namespace, name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return factory.LoadCA(secret.Data[v1.TLSCertKey], secret.Data[v1.TLSPrivateKeyKey])
|
||||
}
|
||||
|
||||
func LoadOrGenClient(secrets v1controller.SecretClient, namespace, name, cn string, ca *x509.Certificate, key crypto.Signer) (*x509.Certificate, crypto.Signer, error) {
|
||||
secret, err := getClientSecret(secrets, namespace, name, cn, ca, key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return factory.LoadCA(secret.Data[v1.TLSCertKey], secret.Data[v1.TLSPrivateKeyKey])
|
||||
}
|
||||
|
||||
func getClientSecret(secrets v1controller.SecretClient, namespace, name, cn string, caCert *x509.Certificate, caKey crypto.Signer) (*v1.Secret, error) {
|
||||
s, err := secrets.Get(namespace, name, metav1.GetOptions{})
|
||||
if !errors.IsNotFound(err) {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return createAndStoreClientCert(secrets, namespace, name, cn, caCert, caKey)
|
||||
}
|
||||
|
||||
func getSecret(secrets v1controller.SecretClient, namespace, name string) (*v1.Secret, error) {
|
||||
s, err := secrets.Get(namespace, name, metav1.GetOptions{})
|
||||
if !errors.IsNotFound(err) {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return createAndStore(secrets, namespace, name)
|
||||
}
|
||||
|
||||
func createAndStoreClientCert(secrets v1controller.SecretClient, namespace string, name, cn string, caCert *x509.Certificate, caKey crypto.Signer) (*v1.Secret, error) {
|
||||
key, err := factory.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := factory.NewSignedClientCert(key, caCert, caKey, cn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPem, keyPem, err := factory.Marshal(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
v1.TLSCertKey: certPem,
|
||||
v1.TLSPrivateKeyKey: keyPem,
|
||||
},
|
||||
Type: v1.SecretTypeTLS,
|
||||
}
|
||||
|
||||
return secrets.Create(secret)
|
||||
}
|
||||
|
||||
func createAndStore(secrets v1controller.SecretClient, namespace string, name string) (*v1.Secret, error) {
|
||||
ca, cert, err := factory.GenCA()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPem, keyPem, err := factory.Marshal(ca, cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
v1.TLSCertKey: certPem,
|
||||
v1.TLSPrivateKeyKey: keyPem,
|
||||
},
|
||||
Type: v1.SecretTypeTLS,
|
||||
}
|
||||
|
||||
return secrets.Create(secret)
|
||||
}
|
@@ -6,19 +6,28 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/rancher/dynamiclistener"
|
||||
"github.com/rancher/dynamiclistener/factory"
|
||||
"github.com/rancher/wrangler-api/pkg/generated/controllers/core"
|
||||
v1controller "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||
"github.com/rancher/wrangler/pkg/start"
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type CoreGetter func() *core.Factory
|
||||
|
||||
func Load(ctx context.Context, secrets v1controller.SecretController, namespace, name string, backing dynamiclistener.TLSStorage) dynamiclistener.TLSStorage {
|
||||
storage := &storage{
|
||||
name: name,
|
||||
namespace: namespace,
|
||||
storage: backing,
|
||||
ctx: ctx,
|
||||
}
|
||||
storage.init(secrets)
|
||||
return storage
|
||||
}
|
||||
|
||||
func New(ctx context.Context, core CoreGetter, namespace, name string, backing dynamiclistener.TLSStorage) dynamiclistener.TLSStorage {
|
||||
storage := &storage{
|
||||
name: name,
|
||||
@@ -55,10 +64,10 @@ type storage struct {
|
||||
storage dynamiclistener.TLSStorage
|
||||
secrets v1controller.SecretClient
|
||||
ctx context.Context
|
||||
tls *factory.TLS
|
||||
tls dynamiclistener.TLSFactory
|
||||
}
|
||||
|
||||
func (s *storage) SetFactory(tls *factory.TLS) {
|
||||
func (s *storage) SetFactory(tls dynamiclistener.TLSFactory) {
|
||||
s.tls = tls
|
||||
}
|
||||
|
||||
@@ -122,7 +131,7 @@ func (s *storage) saveInK8s(secret *v1.Secret) (*v1.Secret, error) {
|
||||
}
|
||||
|
||||
if existing, err := s.storage.Get(); err == nil && s.tls != nil {
|
||||
if newSecret, updated, err := s.tls.Merge(secret, existing); err == nil && updated {
|
||||
if newSecret, updated, err := s.tls.Merge(existing, secret); err == nil && updated {
|
||||
secret = newSecret
|
||||
}
|
||||
}
|
||||
@@ -132,9 +141,12 @@ func (s *storage) saveInK8s(secret *v1.Secret) (*v1.Secret, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if equality.Semantic.DeepEqual(targetSecret.Annotations, secret.Annotations) &&
|
||||
equality.Semantic.DeepEqual(targetSecret.Data, secret.Data) {
|
||||
return secret, 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
|
||||
@@ -144,10 +156,9 @@ func (s *storage) saveInK8s(secret *v1.Secret) (*v1.Secret, error) {
|
||||
if targetSecret.UID == "" {
|
||||
logrus.Infof("Creating new TLS secret for %v (count: %d): %v", targetSecret.Name, len(targetSecret.Annotations)-1, targetSecret.Annotations)
|
||||
return s.secrets.Create(targetSecret)
|
||||
} else {
|
||||
logrus.Infof("Updating TLS secret for %v (count: %d): %v", targetSecret.Name, len(targetSecret.Annotations)-1, targetSecret.Annotations)
|
||||
return s.secrets.Update(targetSecret)
|
||||
}
|
||||
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) (err error) {
|
||||
|
@@ -32,13 +32,15 @@ func (m *memory) Get() (*v1.Secret, error) {
|
||||
}
|
||||
|
||||
func (m *memory) Update(secret *v1.Secret) error {
|
||||
if m.storage != nil {
|
||||
if err := m.storage.Update(secret); err != nil {
|
||||
return err
|
||||
if m.secret == nil || m.secret.ResourceVersion == "" || m.secret.ResourceVersion != secret.ResourceVersion {
|
||||
if m.storage != nil {
|
||||
if err := m.storage.Update(secret); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Infof("Active TLS secret %s (ver=%s) (count %d): %v", secret.Name, secret.ResourceVersion, len(secret.Annotations)-1, secret.Annotations)
|
||||
m.secret = secret
|
||||
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
|
||||
}
|
||||
|
36
storage/static/static.go
Normal file
36
storage/static/static.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"github.com/rancher/dynamiclistener/factory"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
Secret *v1.Secret
|
||||
}
|
||||
|
||||
func New(certPem, keyPem []byte) *Storage {
|
||||
return &Storage{
|
||||
Secret: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
factory.Static: "true",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
v1.TLSCertKey: certPem,
|
||||
v1.TLSPrivateKeyKey: keyPem,
|
||||
},
|
||||
Type: v1.SecretTypeTLS,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Storage) Get() (*v1.Secret, error) {
|
||||
return s.Secret, nil
|
||||
}
|
||||
|
||||
func (s *Storage) Update(_ *v1.Secret) error {
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user