From af0486784331e646b726b091edf061d8b15661bf Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Sat, 26 Oct 2019 23:18:58 -0700 Subject: [PATCH 1/2] Refactor to not include a server by default --- factory/ca.go | 80 +++++ factory/cert_utils.go | 105 ++++++ factory/gen.go | 164 +++++++++ go.mod | 13 +- listener.go | 198 +++++++++++ read.go | 50 --- redirect.go | 46 +++ server.go | 556 ------------------------------- server/server.go | 80 +++++ storage/file/file.go | 42 +++ storage/kubernetes/controller.go | 109 ++++++ storage/memory/memory.go | 42 +++ tcp.go | 38 +++ types.go | 63 ---- 14 files changed, 906 insertions(+), 680 deletions(-) create mode 100644 factory/ca.go create mode 100644 factory/cert_utils.go create mode 100644 factory/gen.go create mode 100644 listener.go delete mode 100644 read.go create mode 100644 redirect.go delete mode 100644 server.go create mode 100644 server/server.go create mode 100644 storage/file/file.go create mode 100644 storage/kubernetes/controller.go create mode 100644 storage/memory/memory.go create mode 100644 tcp.go delete mode 100644 types.go diff --git a/factory/ca.go b/factory/ca.go new file mode 100644 index 0000000..a35738c --- /dev/null +++ b/factory/ca.go @@ -0,0 +1,80 @@ +package factory + +import ( + "crypto/ecdsa" + "crypto/x509" + "io/ioutil" + "os" +) + +func GenCA() (*x509.Certificate, *ecdsa.PrivateKey, error) { + caKey, err := NewPrivateKey() + if err != nil { + return nil, nil, err + } + + caCert, err := NewSelfSignedCACert(caKey, "dynamiclistener-ca", "dynamiclistener-org") + if err != nil { + return nil, nil, err + } + + return caCert, caKey, nil +} + +func LoadOrGenCA() (*x509.Certificate, *ecdsa.PrivateKey, error) { + cert, key, err := loadCA() + if err == nil { + return cert, key, nil + } + + cert, key, err = GenCA() + if err != nil { + return nil, nil, err + } + + certBytes, keyBytes, err := Marshal(cert, key) + if err != nil { + return nil, nil, err + } + + if err := os.MkdirAll("./certs", 0700); err != nil { + return nil, nil, err + } + + if err := ioutil.WriteFile("./certs/ca.pem", certBytes, 0600); err != nil { + return nil, nil, err + } + + if err := ioutil.WriteFile("./certs/ca.key", keyBytes, 0600); err != nil { + return nil, nil, err + } + + return cert, key, nil +} + +func loadCA() (*x509.Certificate, *ecdsa.PrivateKey, error) { + return LoadCerts("./certs/ca.pem", "./certs/ca.key") +} + +func LoadCerts(certFile, keyFile string) (*x509.Certificate, *ecdsa.PrivateKey, 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 + } + + key, err := ParseECPrivateKeyPEM(caKey) + if err != nil { + return nil, nil, err + } + + cert, err := ParseCertPEM(caPem) + if err != nil { + return nil, nil, err + } + + return cert, key, nil +} diff --git a/factory/cert_utils.go b/factory/cert_utils.go new file mode 100644 index 0000000..459bd2e --- /dev/null +++ b/factory/cert_utils.go @@ -0,0 +1,105 @@ +package factory + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math" + "math/big" + "net" + "time" +) + +const ( + ECPrivateKeyBlockType = "EC PRIVATE KEY" + CertificateBlockType = "CERTIFICATE" +) + +func NewSelfSignedCACert(key crypto.Signer, cn string, org ...string) (*x509.Certificate, error) { + now := time.Now() + tmpl := x509.Certificate{ + BasicConstraintsValid: true, + IsCA: true, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + NotAfter: now.Add(time.Hour * 24 * 365 * 10).UTC(), + NotBefore: now.UTC(), + SerialNumber: new(big.Int).SetInt64(0), + Subject: pkix.Name{ + CommonName: cn, + Organization: org, + }, + } + + certDERBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key) + if err != nil { + return nil, err + } + + return x509.ParseCertificate(certDERBytes) +} + +func NewSignedCert(signer crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer, cn string, orgs []string, + domains []string, ips []net.IP) (*x509.Certificate, error) { + + serialNumber, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) + if err != nil { + return nil, err + } + + 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(), + NotBefore: caCert.NotBefore, + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: cn, + Organization: orgs, + }, + } + + cert, err := x509.CreateCertificate(rand.Reader, &parent, caCert, signer.Public(), caKey) + if err != nil { + return nil, err + } + + return x509.ParseCertificate(cert) +} + +func ParseECPrivateKeyPEM(keyData []byte) (*ecdsa.PrivateKey, error) { + var privateKeyPemBlock *pem.Block + for { + privateKeyPemBlock, keyData = pem.Decode(keyData) + if privateKeyPemBlock == nil { + break + } + + if privateKeyPemBlock.Type == ECPrivateKeyBlockType { + return x509.ParseECPrivateKey(privateKeyPemBlock.Bytes) + } + } + + return nil, fmt.Errorf("pem does not include a valid EC private key") +} + +func ParseCertPEM(pemCerts []byte) (*x509.Certificate, error) { + var pemBlock *pem.Block + for { + pemBlock, pemCerts = pem.Decode(pemCerts) + if pemBlock == nil { + break + } + + if pemBlock.Type == CertificateBlockType { + return x509.ParseCertificate(pemBlock.Bytes) + } + } + + return nil, fmt.Errorf("pem does not include a valid x509 cert") +} diff --git a/factory/gen.go b/factory/gen.go new file mode 100644 index 0000000..2341748 --- /dev/null +++ b/factory/gen.go @@ -0,0 +1,164 @@ +package factory + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "net" + "sort" + "strings" + + v1 "k8s.io/api/core/v1" +) + +const ( + cnPrefix = "listener.cattle.io/cn-" + static = "listener.cattle.io/static" + hashKey = "listener.cattle.io/hash" +) + +type TLS struct { + CACert *x509.Certificate + CAKey crypto.Signer + CN string + Organization []string +} + +func collectCNs(secret *v1.Secret) (domains []string, ips []net.IP, hash string, err error) { + var ( + cns []string + digest = sha256.New() + ) + for k, v := range secret.Annotations { + if strings.HasPrefix(k, cnPrefix) { + cns = append(cns, v) + } + } + + sort.Strings(cns) + + for _, v := range cns { + digest.Write([]byte(v)) + ip := net.ParseIP(v) + if ip == nil { + domains = append(domains, v) + } else { + ips = append(ips, ip) + } + } + + hash = hex.EncodeToString(digest.Sum(nil)) + return +} + +func (t *TLS) AddCN(secret *v1.Secret, cn ...string) (*v1.Secret, bool, error) { + var ( + err error + ) + + if !NeedsUpdate(secret, cn...) { + return secret, false, nil + } + + secret = populateCN(secret, cn...) + + privateKey, err := getPrivateKey(secret) + if err != nil { + return nil, false, err + } + + domains, ips, hash, err := collectCNs(secret) + if err != nil { + return nil, false, err + } + + newCert, err := t.newCert(domains, ips, privateKey) + if err != nil { + return nil, false, err + } + + certBytes, keyBytes, err := Marshal(newCert, privateKey) + if err != nil { + return nil, false, err + } + + if secret.Data == nil { + secret.Data = map[string][]byte{} + } + secret.Data[v1.TLSCertKey] = certBytes + secret.Data[v1.TLSPrivateKeyKey] = keyBytes + secret.Annotations[hashKey] = hash + + return secret, true, nil +} + +func (t *TLS) newCert(domains []string, ips []net.IP, privateKey *ecdsa.PrivateKey) (*x509.Certificate, error) { + return NewSignedCert(privateKey, t.CACert, t.CAKey, t.CN, t.Organization, domains, ips) +} + +func populateCN(secret *v1.Secret, cn ...string) *v1.Secret { + secret = secret.DeepCopy() + if secret.Annotations == nil { + secret.Annotations = map[string]string{} + } + for _, cn := range cn { + secret.Annotations[cnPrefix+cn] = cn + } + return secret +} + +func NeedsUpdate(secret *v1.Secret, cn ...string) bool { + if secret.Annotations[static] == "true" { + return false + } + + for _, cn := range cn { + if secret.Annotations[cnPrefix+cn] == "" { + return true + } + } + + return false +} + +func getPrivateKey(secret *v1.Secret) (*ecdsa.PrivateKey, error) { + keyBytes := secret.Data[v1.TLSPrivateKeyKey] + if len(keyBytes) == 0 { + return NewPrivateKey() + } + + privateKey, err := ParseECPrivateKeyPEM(keyBytes) + if err == nil { + return privateKey, nil + } + + return NewPrivateKey() +} + +func Marshal(x509Cert *x509.Certificate, privateKey *ecdsa.PrivateKey) ([]byte, []byte, error) { + certBlock := pem.Block{ + Type: CertificateBlockType, + Bytes: x509Cert.Raw, + } + + keyBytes, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return nil, nil, err + } + + keyBlock := pem.Block{ + Type: ECPrivateKeyBlockType, + Bytes: keyBytes, + } + + return pem.EncodeToMemory(&certBlock), pem.EncodeToMemory(&keyBlock), nil +} + +func NewPrivateKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +} diff --git a/go.mod b/go.mod index c1ca544..a2a54ab 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,7 @@ module github.com/rancher/dynamiclistener go 1.12 require ( - github.com/hashicorp/golang-lru v0.5.1 - github.com/kisielk/gotool v1.0.0 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/rancher/wrangler-api v0.2.0 github.com/sirupsen/logrus v1.4.1 - github.com/stretchr/testify v1.3.0 // indirect - github.com/stripe/safesql v0.2.0 // indirect - golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 - golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c // indirect - golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 // indirect - golang.org/x/text v0.3.2 // indirect - mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect - mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect + k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b ) diff --git a/listener.go b/listener.go new file mode 100644 index 0000000..7ba1a69 --- /dev/null +++ b/listener.go @@ -0,0 +1,198 @@ +package dynamiclistener + +import ( + "crypto" + "crypto/tls" + "crypto/x509" + "net" + "net/http" + "sync" + + "github.com/rancher/dynamiclistener/factory" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" +) + +type TLSStorage interface { + Get() (*v1.Secret, error) + Update(secret *v1.Secret) error +} + +type Config struct { + CN string + Organization []string + TLSConfig tls.Config + SANs []string +} + +func NewListener(l net.Listener, storage TLSStorage, caCert *x509.Certificate, caKey crypto.Signer, config Config) (net.Listener, http.Handler, error) { + if config.CN == "" { + config.CN = "dynamic" + } + if len(config.Organization) == 0 { + config.Organization = []string{"dynamic"} + } + + dynamicListener := &listener{ + factory: &factory.TLS{ + CACert: caCert, + CAKey: caKey, + CN: config.CN, + Organization: config.Organization, + }, + Listener: l, + storage: &nonNil{storage: storage}, + sans: config.SANs, + tlsConfig: config.TLSConfig, + } + dynamicListener.tlsConfig.GetCertificate = dynamicListener.getCertificate + + return tls.NewListener(dynamicListener, &dynamicListener.tlsConfig), dynamicListener.cacheHandler(), nil +} + +type listener struct { + sync.RWMutex + net.Listener + + factory *factory.TLS + storage TLSStorage + version string + tlsConfig tls.Config + cert *tls.Certificate + sans []string +} + +func (l *listener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return conn, err + } + + addr := conn.RemoteAddr() + if addr == nil { + return conn, nil + } + + host, _, err := net.SplitHostPort(addr.String()) + if err != nil { + logrus.Errorf("failed to parse network %s: %v", addr.Network(), err) + return conn, nil + } + + return conn, l.updateCert(host) +} + +func (l *listener) getCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + if hello.ServerName != "" { + if err := l.updateCert(hello.ServerName); err != nil { + return nil, err + } + } + + return l.loadCert() +} + +func (l *listener) updateCert(cn string) error { + l.RLock() + defer l.RUnlock() + + secret, err := l.storage.Get() + if err != nil { + return err + } + + if !factory.NeedsUpdate(secret, append(l.sans, cn)...) { + return nil + } + + l.RUnlock() + l.Lock() + defer l.RLock() + defer l.Unlock() + + secret, updated, err := l.factory.AddCN(secret, append(l.sans, cn)...) + if err != nil { + return err + } + + if updated { + if err := l.storage.Update(secret); err != nil { + return err + } + // clear version to force cert reload + l.version = "" + } + + return nil +} + +func (l *listener) loadCert() (*tls.Certificate, error) { + l.RLock() + defer l.RUnlock() + + secret, err := l.storage.Get() + if err != nil { + return nil, err + } + if l.cert != nil && l.version == secret.ResourceVersion { + return l.cert, nil + } + + defer l.RLock() + l.RUnlock() + l.Lock() + defer l.Unlock() + + secret, err = l.storage.Get() + if err != nil { + return nil, err + } + if l.cert != nil && l.version == secret.ResourceVersion { + return l.cert, nil + } + + cert, err := tls.X509KeyPair(secret.Data[v1.TLSCertKey], secret.Data[v1.TLSPrivateKeyKey]) + if err != nil { + return nil, err + } + + l.cert = &cert + return l.cert, nil +} + +func (l *listener) cacheHandler() http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + h, _, err := net.SplitHostPort(req.Host) + if err != nil { + h = req.Host + } + + ip := net.ParseIP(h) + if len(ip) > 0 { + l.updateCert(h) + } + }) +} + +type nonNil struct { + sync.Mutex + storage TLSStorage +} + +func (n *nonNil) Get() (*v1.Secret, error) { + n.Lock() + defer n.Unlock() + + s, err := n.storage.Get() + if err != nil || s == nil { + return &v1.Secret{}, err + } + return s, nil +} + +func (n *nonNil) Update(secret *v1.Secret) error { + n.Lock() + defer n.Unlock() + + return n.storage.Update(secret) +} diff --git a/read.go b/read.go deleted file mode 100644 index bf99514..0000000 --- a/read.go +++ /dev/null @@ -1,50 +0,0 @@ -package dynamiclistener - -import ( - "fmt" - "io/ioutil" - "path/filepath" -) - -func ReadTLSConfig(userConfig *UserConfig) error { - var err error - - path := userConfig.CertPath - - userConfig.CACerts, err = readPEM(filepath.Join(path, "cacerts.pem")) - if err != nil { - return err - } - - userConfig.Key, err = readPEM(filepath.Join(path, "key.pem")) - if err != nil { - return err - } - - userConfig.Cert, err = readPEM(filepath.Join(path, "cert.pem")) - if err != nil { - return err - } - - valid := false - if userConfig.Key != "" && userConfig.Cert != "" { - valid = true - } else if userConfig.Key == "" && userConfig.Cert == "" { - valid = true - } - - if !valid { - return fmt.Errorf("invalid SSL configuration found, please set cert/key, cert/key/cacerts, cacerts only, or none") - } - - return nil -} - -func readPEM(path string) (string, error) { - content, err := ioutil.ReadFile(path) - if err != nil { - return "", nil - } - - return string(content), nil -} diff --git a/redirect.go b/redirect.go new file mode 100644 index 0000000..264efde --- /dev/null +++ b/redirect.go @@ -0,0 +1,46 @@ +package dynamiclistener + +import ( + "fmt" + "net" + "net/http" + "strconv" + "strings" +) + +// Approach taken from letsencrypt, except manglePort is specific to us +func HTTPRedirect(next http.Handler) http.Handler { + return http.HandlerFunc( + func(rw http.ResponseWriter, r *http.Request) { + fmt.Println("!!!!!", r.URL.String(), r.Header) + if r.Header.Get("x-Forwarded-Proto") == "https" || + r.Header.Get("x-Forwarded-Proto") == "wss" || + strings.HasPrefix(r.URL.Path, "/ping") || + strings.HasPrefix(r.URL.Path, "/health") { + next.ServeHTTP(rw, r) + return + } + if r.Method != "GET" && r.Method != "HEAD" { + http.Error(rw, "Use HTTPS", http.StatusBadRequest) + return + } + target := "https://" + manglePort(r.Host) + r.URL.RequestURI() + http.Redirect(rw, r, target, http.StatusFound) + }) +} + +func manglePort(hostport string) string { + host, port, err := net.SplitHostPort(hostport) + if err != nil { + return hostport + } + + portInt, err := strconv.Atoi(port) + if err != nil { + return hostport + } + + portInt = ((portInt / 1000) * 1000) + 443 + + return net.JoinHostPort(host, strconv.Itoa(portInt)) +} diff --git a/server.go b/server.go deleted file mode 100644 index 2c01dbf..0000000 --- a/server.go +++ /dev/null @@ -1,556 +0,0 @@ -package dynamiclistener - -import ( - "context" - "crypto" - "crypto/ecdsa" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "errors" - "fmt" - "log" - "net" - "net/http" - "reflect" - "strconv" - "strings" - "sync" - "time" - - cert "github.com/rancher/dynamiclistener/cert" - "github.com/sirupsen/logrus" -) - -type server struct { - sync.Mutex - - userConfig UserConfig - listenConfigStorage ListenerConfigStorage - tlsCert *tls.Certificate - - ips map[string]bool - domains map[string]bool - cn string - - listeners []net.Listener - servers []*http.Server - - activeCA *x509.Certificate - activeCAKey crypto.Signer -} - -func NewServer(listenConfigStorage ListenerConfigStorage, config UserConfig) (ServerInterface, error) { - s := &server{ - userConfig: config, - listenConfigStorage: listenConfigStorage, - cn: "cattle", - } - - s.ips = map[string]bool{} - s.domains = map[string]bool{} - - if err := s.userConfigure(); err != nil { - return nil, err - } - - lc, err := listenConfigStorage.Get() - if err != nil { - return nil, err - } - - return s, s.Update(lc) -} - -func (s *server) CACert() (string, error) { - if s.userConfig.NoCACerts { - return "", nil - } - if s.userConfig.CACerts != "" { - return s.userConfig.CACerts, nil - } - return "", fmt.Errorf("ca cert not found") -} - -func marshalPrivateKey(privateKey crypto.Signer) (string, []byte, error) { - var ( - keyType string - bytes []byte - err error - ) - if key, ok := privateKey.(*ecdsa.PrivateKey); ok { - keyType = cert.ECPrivateKeyBlockType - bytes, err = x509.MarshalECPrivateKey(key) - } else if key, ok := privateKey.(*rsa.PrivateKey); ok { - keyType = cert.RSAPrivateKeyBlockType - bytes = x509.MarshalPKCS1PrivateKey(key) - } else { - keyType = cert.PrivateKeyBlockType - bytes, err = x509.MarshalPKCS8PrivateKey(privateKey) - } - if err != nil { - logrus.Errorf("Unable to marshal private key: %v", err) - } - return keyType, bytes, err -} - -func newPrivateKey() (crypto.Signer, error) { - caKeyBytes, err := cert.MakeEllipticPrivateKeyPEM() - if err != nil { - return nil, err - } - caKeyIFace, err := cert.ParsePrivateKeyPEM(caKeyBytes) - if err != nil { - return nil, err - } - return caKeyIFace.(crypto.Signer), nil -} - -func (s *server) save() (_err error) { - defer func() { - if _err != nil { - logrus.Errorf("Saving cert error: %s", _err) - } - }() - - certStr, err := certToString(s.tlsCert) - if err != nil { - return err - } - cfg, err := s.listenConfigStorage.Get() - if err != nil { - return err - } - cfg.GeneratedCerts = map[string]string{s.cn: certStr} - - _, err = s.listenConfigStorage.Set(cfg) - return err -} - -func (s *server) userConfigure() error { - if s.userConfig.HTTPSPort == 0 { - s.userConfig.HTTPSPort = 8443 - } - - for _, d := range s.userConfig.Domains { - s.domains[d] = true - } - - for _, ip := range s.userConfig.KnownIPs { - if netIP := net.ParseIP(ip); netIP != nil { - s.ips[ip] = true - } - } - - if bindAddress := net.ParseIP(s.userConfig.BindAddress); bindAddress != nil { - s.ips[s.userConfig.BindAddress] = true - } - - if s.activeCA == nil && s.activeCAKey == nil { - if s.userConfig.CACerts != "" && s.userConfig.CAKey != "" { - ca, err := cert.ParseCertsPEM([]byte(s.userConfig.CACerts)) - if err != nil { - return err - } - key, err := cert.ParsePrivateKeyPEM([]byte(s.userConfig.CAKey)) - if err != nil { - return err - } - s.activeCA = ca[0] - s.activeCAKey = key.(crypto.Signer) - } else { - ca, key, err := genCA() - if err != nil { - return err - } - s.activeCA = ca - s.activeCAKey = key - } - } - - return nil -} - -func genCA() (*x509.Certificate, crypto.Signer, error) { - caKey, err := newPrivateKey() - if err != nil { - return nil, nil, err - } - - caCert, err := cert.NewSelfSignedCACert(cert.Config{ - CommonName: "k3s-ca", - Organization: []string{"k3s-org"}, - }, caKey) - if err != nil { - return nil, nil, err - } - - return caCert, caKey, nil -} - -func (s *server) Update(status *ListenerStatus) (_err error) { - s.Lock() - defer func() { - s.Unlock() - if _err != nil { - logrus.Errorf("Update cert error: %s", _err) - } - if s.tlsCert == nil { - s.getCertificate(&tls.ClientHelloInfo{ServerName: "localhost"}) - } - }() - - certString := status.GeneratedCerts[s.cn] - tlsCert, err := stringToCert(certString) - if err != nil { - logrus.Errorf("Update cert unable to convert string to cert: %s", err) - s.tlsCert = nil - } - if tlsCert != nil { - s.tlsCert = tlsCert - for i, certBytes := range tlsCert.Certificate { - parsedCert, err := x509.ParseCertificate(certBytes) - if err != nil { - logrus.Errorf("Update cert %d parse error: %s", i, err) - s.tlsCert = nil - break - } - isExpired := cert.IsCertExpired(parsedCert) - if isExpired { - logrus.Infof("certificate is about to expire") - s.tlsCert = nil - break - } - ips := map[string]bool{} - for _, ip := range parsedCert.IPAddresses { - ips[ip.String()] = true - } - - domains := map[string]bool{} - for _, domain := range parsedCert.DNSNames { - domains[domain] = true - } - - if !(reflect.DeepEqual(ips, s.ips) && reflect.DeepEqual(domains, s.domains)) { - subset := true - for ip := range s.ips { - if !ips[ip] { - subset = false - break - } - } - if subset { - for domain := range s.domains { - if !domains[domain] { - subset = false - break - } - } - } - if !subset { - s.tlsCert = nil - } - for ip := range ips { - s.ips[ip] = true - } - for domain := range domains { - s.domains[domain] = true - } - } - } - } - - return s.reload() -} - -func (s *server) shutdown() error { - for _, listener := range s.listeners { - if err := listener.Close(); err != nil { - return err - } - } - s.listeners = nil - - for _, server := range s.servers { - go server.Shutdown(context.Background()) - } - s.servers = nil - - return nil -} - -func (s *server) reload() error { - if len(s.listeners) > 0 { - return nil - } - - if err := s.shutdown(); err != nil { - return err - } - - if err := s.serveHTTPS(); err != nil { - return err - } - - return nil -} - -func (s *server) getCertificate(hello *tls.ClientHelloInfo) (_servingCert *tls.Certificate, _err error) { - s.Lock() - changed := false - - defer func() { - defer s.Unlock() - - if _err != nil { - logrus.Errorf("Get certificate error: %s", _err) - return - } - - if changed { - s.save() - } - }() - - if hello.ServerName != "" && !s.domains[hello.ServerName] { - s.tlsCert = nil - s.domains[hello.ServerName] = true - } - - if s.tlsCert != nil { - return s.tlsCert, nil - } - - ips := []net.IP{} - for ipStr := range s.ips { - if ip := net.ParseIP(ipStr); ip != nil { - ips = append(ips, ip) - } - } - - dnsNames := []string{} - for domain := range s.domains { - dnsNames = append(dnsNames, domain) - } - - cfg := cert.Config{ - CommonName: s.cn, - Organization: s.activeCA.Subject.Organization, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - AltNames: cert.AltNames{ - DNSNames: dnsNames, - IPs: ips, - }, - } - - key, err := newPrivateKey() - if err != nil { - return nil, err - } - - cert, err := cert.NewSignedCert(cfg, key, s.activeCA, s.activeCAKey) - if err != nil { - return nil, err - } - - tlsCert := &tls.Certificate{ - Certificate: [][]byte{ - cert.Raw, - }, - PrivateKey: key, - } - - changed = true - s.tlsCert = tlsCert - return tlsCert, nil -} - -func (s *server) cacheHandler(handler http.Handler) http.Handler { - return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - h, _, err := net.SplitHostPort(req.Host) - if err != nil { - h = req.Host - } - - s.Lock() - if ip := net.ParseIP(h); ip != nil { - if !s.ips[h] { - s.ips[h] = true - s.tlsCert = nil - } - } else { - if !s.domains[h] { - s.domains[h] = true - s.tlsCert = nil - } - } - s.Unlock() - - handler.ServeHTTP(resp, req) - }) -} - -func (s *server) serveHTTPS() error { - conf := &tls.Config{ - ClientAuth: tls.RequestClientCert, - GetCertificate: s.getCertificate, - PreferServerCipherSuites: true, - } - - listener, err := s.newListener(s.userConfig.BindAddress, s.userConfig.HTTPSPort, conf) - if err != nil { - return err - } - - logger := logrus.StandardLogger() - server := &http.Server{ - Handler: s.cacheHandler(s.Handler()), - ErrorLog: log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags), - } - - s.servers = append(s.servers, server) - s.startServer(listener, server) - - if s.userConfig.HTTPPort > 0 { - httpListener, err := s.newListener(s.userConfig.BindAddress, s.userConfig.HTTPPort, nil) - if err != nil { - return err - } - - httpServer := &http.Server{ - Handler: s.cacheHandler(httpRedirect(s.Handler())), - ErrorLog: log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags), - } - - s.servers = append(s.servers, httpServer) - s.startServer(httpListener, httpServer) - } - - return nil -} - -// Approach taken from letsencrypt, except manglePort is specific to us -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" || - strings.HasPrefix(r.URL.Path, "/ping") || - strings.HasPrefix(r.URL.Path, "/health") { - next.ServeHTTP(rw, r) - return - } - if r.Method != "GET" && r.Method != "HEAD" { - http.Error(rw, "Use HTTPS", http.StatusBadRequest) - return - } - target := "https://" + manglePort(r.Host) + r.URL.RequestURI() - http.Redirect(rw, r, target, http.StatusFound) - }) -} - -func manglePort(hostport string) string { - host, port, err := net.SplitHostPort(hostport) - if err != nil { - return hostport - } - - portInt, err := strconv.Atoi(port) - if err != nil { - return hostport - } - - portInt = ((portInt / 1000) * 1000) + 443 - - return net.JoinHostPort(host, strconv.Itoa(portInt)) -} - -func (s *server) startServer(listener net.Listener, server *http.Server) { - go func() { - if err := server.Serve(listener); err != nil { - logrus.Errorf("server on %v returned err: %v", listener.Addr(), err) - } - }() -} - -func (s *server) Handler() http.Handler { - return s.userConfig.Handler -} - -func (s *server) newListener(ip string, port int, config *tls.Config) (net.Listener, error) { - addr := fmt.Sprintf("%s:%d", ip, port) - l, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - - l = tcpKeepAliveListener{l.(*net.TCPListener)} - - if config != nil { - l = tls.NewListener(l, config) - } - - s.listeners = append(s.listeners, l) - logrus.Info("Listening on ", addr) - return l, nil -} - -func stringToCert(certString string) (*tls.Certificate, error) { - parts := strings.Split(certString, "#") - if len(parts) != 2 { - return nil, errors.New("Unable to split cert into two parts") - } - - certPart, keyPart := parts[0], parts[1] - keyBytes, err := base64.StdEncoding.DecodeString(keyPart) - if err != nil { - return nil, err - } - - key, err := cert.ParsePrivateKeyPEM(keyBytes) - if err != nil { - return nil, err - } - - certBytes, err := base64.StdEncoding.DecodeString(certPart) - if err != nil { - return nil, err - } - - return &tls.Certificate{ - Certificate: [][]byte{certBytes}, - PrivateKey: key, - }, nil -} - -func certToString(cert *tls.Certificate) (string, error) { - keyType, keyBytes, err := marshalPrivateKey(cert.PrivateKey.(crypto.Signer)) - if err != nil { - return "", err - } - - privateKeyPemBlock := &pem.Block{ - Type: keyType, - Bytes: keyBytes, - } - pemBytes := pem.EncodeToMemory(privateKeyPemBlock) - - certString := base64.StdEncoding.EncodeToString(cert.Certificate[0]) - keyString := base64.StdEncoding.EncodeToString(pemBytes) - return certString + "#" + keyString, nil -} - -type tcpKeepAliveListener struct { - *net.TCPListener -} - -func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { - tc, err := ln.AcceptTCP() - if err != nil { - return - } - tc.SetKeepAlive(true) - tc.SetKeepAlivePeriod(3 * time.Minute) - return tc, nil -} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..cf069df --- /dev/null +++ b/server/server.go @@ -0,0 +1,80 @@ +package server + +import ( + "context" + "fmt" + "net/http" + + "github.com/rancher/dynamiclistener" + "github.com/rancher/dynamiclistener/factory" + "github.com/rancher/dynamiclistener/storage/memory" + "github.com/sirupsen/logrus" +) + +func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.Handler) error { + var ( + // https listener will change this if http is enabled + targetHandler = handler + ) + if httpsPort > 0 { + caCert, caKey, err := factory.LoadOrGenCA() + if err != nil { + return err + } + + tlsTCPListener, err := dynamiclistener.NewTCPListener("0.0.0.0", httpsPort) + if err != nil { + return err + } + + dynListener, dynHandler, err := dynamiclistener.NewListener(tlsTCPListener, memory.New(), caCert, caKey, dynamiclistener.Config{}) + if err != nil { + return err + } + + targetHandler = wrapHandler(dynHandler, handler) + tlsServer := http.Server{ + Handler: targetHandler, + } + targetHandler = dynamiclistener.HTTPRedirect(targetHandler) + + go func() { + logrus.Infof("Listening on 0.0.0.0:%d", httpsPort) + err := tlsServer.Serve(dynListener) + if err != http.ErrServerClosed && err != nil { + logrus.Fatalf("https server failed: %v", err) + } + }() + go func() { + <-ctx.Done() + tlsServer.Shutdown(context.Background()) + }() + } + + if httpPort > 0 { + httpServer := http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", httpPort), + Handler: targetHandler, + } + go func() { + logrus.Infof("Listening on 0.0.0.0:%d", httpPort) + err := httpServer.ListenAndServe() + if err != http.ErrServerClosed && err != nil { + logrus.Fatalf("http server failed: %v", err) + } + }() + go func() { + <-ctx.Done() + httpServer.Shutdown(context.Background()) + }() + } + + return nil +} + +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) + }) +} diff --git a/storage/file/file.go b/storage/file/file.go new file mode 100644 index 0000000..ba738bc --- /dev/null +++ b/storage/file/file.go @@ -0,0 +1,42 @@ +package file + +import ( + "encoding/json" + "github.com/rancher/dynamiclistener" + "k8s.io/api/core/v1" + "os" +) + +func New(file string) dynamiclistener.TLSStorage { + return &storage{ + file: file, + } +} + +type storage struct { + file string +} + +func (s *storage) Get() (*v1.Secret, error) { + f, err := os.Open(s.file) + if os.IsNotExist(err) { + return nil, nil + } else if err != nil { + return nil, err + } + defer f.Close() + + secret := v1.Secret{} + return &secret, json.NewDecoder(f).Decode(&secret) +} + +func (s *storage) Update(secret *v1.Secret) error { + f, err := os.Create(s.file) + if err != nil { + return err + } + defer f.Close() + + return json.NewEncoder(f).Encode(secret) +} + diff --git a/storage/kubernetes/controller.go b/storage/kubernetes/controller.go new file mode 100644 index 0000000..d5198a2 --- /dev/null +++ b/storage/kubernetes/controller.go @@ -0,0 +1,109 @@ +package kubernetes + +import ( + "context" + "sync" + "time" + + "github.com/rancher/dynamiclistener" + "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" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" +) + +type CoreGetter func() *core.Factory + +func New(ctx context.Context, core CoreGetter, namespace, name string, backing dynamiclistener.TLSStorage) dynamiclistener.TLSStorage { + storage := &storage{ + name: name, + namespace: namespace, + storage: backing, + ctx: ctx, + } + + // lazy init + go func() { + for { + core := core() + if core != nil { + storage.init(core.Core().V1().Secret()) + start.All(ctx, 5, core) + return + } + + select { + case <-ctx.Done(): + return + case <-time.After(time.Second): + } + } + }() + + return storage +} + +type storage struct { + sync.Mutex + + namespace, name string + storage dynamiclistener.TLSStorage + secrets v1controller.SecretClient + ctx context.Context +} + +func (s *storage) init(secrets v1controller.SecretController) { + s.Lock() + defer s.Unlock() + + secrets.OnChange(s.ctx, "tls-storage", func(key string, secret *v1.Secret) (*v1.Secret, error) { + if secret == nil { + return nil, nil + } + if secret.Namespace == s.namespace && secret.Name == s.name { + if err := s.Update(secret); err != nil { + return nil, err + } + } + + return secret, nil + }) + s.secrets = secrets +} + +func (s *storage) Get() (*v1.Secret, error) { + s.Lock() + defer s.Unlock() + + return s.storage.Get() +} + +func (s *storage) Update(secret *v1.Secret) (err error) { + s.Lock() + defer s.Unlock() + + if s.secrets != nil { + if secret.UID == "" { + secret.Name = s.name + secret.Namespace = s.namespace + secret, err = s.secrets.Create(secret) + if err != nil { + return err + } + } else { + existingSecret, err := s.storage.Get() + if err != nil { + return err + } + if !equality.Semantic.DeepEqual(secret.Data, existingSecret.Data) { + secret, err = s.secrets.Update(secret) + if err != nil { + return err + } + } + } + } + + return s.storage.Update(secret) +} diff --git a/storage/memory/memory.go b/storage/memory/memory.go new file mode 100644 index 0000000..079f180 --- /dev/null +++ b/storage/memory/memory.go @@ -0,0 +1,42 @@ +package memory + +import ( + "github.com/rancher/dynamiclistener" + v1 "k8s.io/api/core/v1" +) + +func New() dynamiclistener.TLSStorage { + return &memory{} +} + +func NewBacked(storage dynamiclistener.TLSStorage) dynamiclistener.TLSStorage { + return &memory{storage: storage} +} + +type memory struct { + storage dynamiclistener.TLSStorage + secret *v1.Secret +} + +func (m *memory) Get() (*v1.Secret, error) { + if m.secret == nil && m.storage != nil { + secret, err := m.storage.Get() + if err != nil { + return nil, err + } + m.secret = secret + } + + return m.secret, nil +} + +func (m *memory) Update(secret *v1.Secret) error { + if m.storage != nil { + if err := m.storage.Update(secret); err != nil { + return err + } + } + + m.secret = secret + return nil +} diff --git a/tcp.go b/tcp.go new file mode 100644 index 0000000..3f41588 --- /dev/null +++ b/tcp.go @@ -0,0 +1,38 @@ +package dynamiclistener + +import ( + "fmt" + "net" + "reflect" + "time" +) + +func NewTCPListener(ip string, port int) (net.Listener, error) { + l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ip, port)) + if err != nil { + return nil, err + } + + tcpListener, ok := l.(*net.TCPListener) + if !ok { + return nil, fmt.Errorf("wrong listener type: %v", reflect.TypeOf(tcpListener)) + } + + return tcpKeepAliveListener{ + TCPListener: tcpListener, + }, nil +} + +type tcpKeepAliveListener struct { + *net.TCPListener +} + +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { + tc, err := ln.AcceptTCP() + if err != nil { + return + } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil +} diff --git a/types.go b/types.go deleted file mode 100644 index 7fabf1c..0000000 --- a/types.go +++ /dev/null @@ -1,63 +0,0 @@ -package dynamiclistener - -import ( - "net/http" -) - -type ListenerConfigStorage interface { - Set(*ListenerStatus) (*ListenerStatus, error) - Get() (*ListenerStatus, error) -} - -type ServerInterface interface { - Update(status *ListenerStatus) error - CACert() (string, error) -} - -type UserConfig struct { - // Required fields - - Handler http.Handler - HTTPPort int - HTTPSPort int - CertPath string - - // Optional fields - - KnownIPs []string - Domains []string - Mode string - NoCACerts bool - CACerts string - CAKey string - Cert string - Key string - BindAddress string -} - -type ListenerStatus struct { - Revision string `json:"revision,omitempty"` - CACert string `json:"caCert,omitempty"` - CAKey string `json:"caKey,omitempty"` - GeneratedCerts map[string]string `json:"generatedCerts" norman:"nocreate,noupdate"` - KnownIPs map[string]bool `json:"knownIps" norman:"nocreate,noupdate"` -} - -func (l *ListenerStatus) DeepCopyInto(t *ListenerStatus) { - t.Revision = l.Revision - t.CACert = l.CACert - t.CAKey = l.CAKey - t.GeneratedCerts = copyMap(t.GeneratedCerts) - t.KnownIPs = map[string]bool{} - for k, v := range l.KnownIPs { - t.KnownIPs[k] = v - } -} - -func copyMap(m map[string]string) map[string]string { - ret := map[string]string{} - for k, v := range m { - ret[k] = v - } - return ret -} From 5878218dc0f0edde19029156ce71735be6f94446 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Wed, 30 Oct 2019 19:15:37 -0700 Subject: [PATCH 2/2] Update go mod --- go.mod | 2 + go.sum | 129 +++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index a2a54ab..f08bac2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/rancher/dynamiclistener go 1.12 require ( + github.com/rancher/wrangler v0.1.4 github.com/rancher/wrangler-api v0.2.0 github.com/sirupsen/logrus v1.4.1 k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b + k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d ) diff --git a/go.sum b/go.sum index 7709128..b940275 100644 --- a/go.sum +++ b/go.sum @@ -1,47 +1,132 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-containerregistry v0.0.0-20190617215043-876b8855d23c/go.mod h1:yZAFP63pRshzrEYLXLGPmUt0Ay+2zdjmMN1loCnRLUk= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jetstack/cert-manager v0.7.2/go.mod h1:nbddmhjWxYGt04bxvwVGUSeLhZ2PCyNvd7MpXdq+yWY= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knative/build v0.6.0/go.mod h1:/sU74ZQkwlYA5FwYDJhYTy61i/Kn+5eWfln2jDbw3Qo= +github.com/knative/pkg v0.0.0-20190514205332-5e4512dcb2ca/go.mod h1:7Ijfhw7rfB+H9VtosIsDYvZQ+qYTz7auK3fHW/5z4ww= +github.com/knative/serving v0.6.1/go.mod h1:ljvMfwQy2qanaM/8xnBSK4Mz3Vv2NawC2fo5kFRJS1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rancher/wrangler v0.1.4 h1:bdzBw4H9JKQhXPBPNp4eHbmrkA24+VII865VLiVWcw8= +github.com/rancher/wrangler v0.1.4/go.mod h1:EYP7cqpg42YqElaCm+U9ieSrGQKAXxUH5xsr+XGpWyE= +github.com/rancher/wrangler-api v0.2.0 h1:VR7hLNnDrKykKLqthtwZ58pDDtUa9ijSNToPaJLEkWc= +github.com/rancher/wrangler-api v0.2.0/go.mod h1:zTPdNLZO07KvRaVOx6XQbKBSV55Fnn4s7nqmrMPJqd8= github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff h1:VARhShG49tiji6mdRNp7JTNDtJ0FhuprF93GBQ37xGU= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stripe/safesql v0.2.0 h1:xiefmCDd8c35PVSGrL2FhBiaKxviXnGziBDOpOejeBE= -github.com/stripe/safesql v0.2.0/go.mod h1:q7b2n0JmzM1mVGfcYpanfVb2j23cXZeWFxcILPn3JV4= +github.com/tektoncd/pipeline v0.4.0/go.mod h1:IZzJdiX9EqEMuUcgdnElozdYYRh0/ZRC+NKMLj1K3Yw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug= -golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo= +k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= +k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA= +k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ= +k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/code-generator v0.0.0-20190311093542-50b561225d70/go.mod h1:MYiN+ZJZ9HkETbgVZdWw2AsuAi9PZ4V80cwfuf2axe8= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190327210449-e17681d19d3a/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/kube-openapi v0.0.0-20190502190224-411b2483e503/go.mod h1:iU+ZGYsNlvU9XKUSso6SQfKTCCw7lFduMZy26Mgr2Fw= +k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y= +k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +sigs.k8s.io/structured-merge-diff v0.0.0-20190426204423-ea680f03cc65/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=