From 2db989caa6cb9a50b217c4ba82a356b356043601 Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Mon, 10 Apr 2017 00:35:59 -0300 Subject: [PATCH] Refactor ssl-passthrough --- Godeps/Godeps.json | 4 + controllers/nginx/pkg/cmd/controller/nginx.go | 11 +- controllers/nginx/pkg/cmd/controller/tcp.go | 4 +- .../rootfs/etc/nginx/template/nginx.tmpl | 15 -- core/pkg/ingress/controller/controller.go | 83 +++++----- core/pkg/ingress/types.go | 14 +- vendor/github.com/paultag/sniff/LICENSE | 17 ++ .../github.com/paultag/sniff/parser/parser.go | 147 ++++++++++++++++++ 8 files changed, 222 insertions(+), 73 deletions(-) create mode 100644 vendor/github.com/paultag/sniff/LICENSE create mode 100644 vendor/github.com/paultag/sniff/parser/parser.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 087a4bb19..4ff1e1854 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -164,6 +164,10 @@ "Comment": "v0.1.0", "Rev": "2942f905437b665326fe044c49edb2094df13b37" }, + { + "ImportPath": "github.com/paultag/sniff/parser", + "Rev": "c36b8585a41425573d9e3e1890bf3b6ac89a3828" + }, { "ImportPath": "github.com/pborman/uuid", "Rev": "ca53cad383cad2479bbba7f7a1a05797ec1386e4" diff --git a/controllers/nginx/pkg/cmd/controller/nginx.go b/controllers/nginx/pkg/cmd/controller/nginx.go index 0334726ea..afb2acdf8 100644 --- a/controllers/nginx/pkg/cmd/controller/nginx.go +++ b/controllers/nginx/pkg/cmd/controller/nginx.go @@ -80,11 +80,8 @@ func newNGINXController() ingress.Controller { binary: ngx, configmap: &api_v1.ConfigMap{}, isIPV6Enabled: isIPv6Enabled(), -<<<<<<< fc67b1d5e2a51cc0037a434583af6530efa1a59c resolver: h, -======= proxy: &proxy{}, ->>>>>>> } listener, err := net.Listen("tcp", ":443") @@ -143,7 +140,8 @@ type NGINXController struct { storeLister ingress.StoreLister - binary string + binary string + resolver []net.IP cmdArgs []string @@ -156,11 +154,7 @@ type NGINXController struct { // returns true if IPV6 is enabled in the pod isIPV6Enabled bool -<<<<<<< fc67b1d5e2a51cc0037a434583af6530efa1a59c - resolver []net.IP -======= proxy *proxy ->>>>>>> } // Start start a new NGINX master process running in foreground. @@ -476,6 +470,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) ([]byte, er for _, pb := range ingressCfg.PassthroughBackends { svc := pb.Service if svc == nil { + glog.Warningf("missing service for PassthroughBackends %v", pb.Backend) continue } port, err := strconv.Atoi(pb.Port.String()) diff --git a/controllers/nginx/pkg/cmd/controller/tcp.go b/controllers/nginx/pkg/cmd/controller/tcp.go index 754ee275a..36a42b9b3 100644 --- a/controllers/nginx/pkg/cmd/controller/tcp.go +++ b/controllers/nginx/pkg/cmd/controller/tcp.go @@ -40,14 +40,14 @@ func (p *proxy) Handle(conn net.Conn) { length, err := conn.Read(data) if err != nil { - glog.V(4).Infof("Error reading the first 4k of the connection: %s", err) + glog.V(4).Infof("error reading the first 4k of the connection: %s", err) return } var proxy *server hostname, err := parser.GetHostname(data[:]) if err == nil { - glog.V(2).Infof("Parsed hostname: %s", hostname) + glog.V(2).Infof("parsed hostname: %s", hostname) proxy = p.Get(hostname) if proxy == nil { return diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index 9ae66274c..d65381390 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -3,7 +3,6 @@ {{ $healthzURI := .HealthzURI }} {{ $backends := .Backends }} {{ $proxyHeaders := .ProxySetHeaders }} -{{ $passthroughBackends := .PassthroughBackends }} daemon off; worker_processes {{ $cfg.WorkerProcesses }}; @@ -476,20 +475,6 @@ http { } stream { -<<<<<<< fc67b1d5e2a51cc0037a434583af6530efa1a59c - {{ if gt (len $passthroughBackends) 0 }} - # map FQDN that requires SSL passthrough - map $ssl_preread_server_name $stream_upstream { - hostnames; - {{ range $i, $passthrough := .PassthroughBackends }} - {{ $passthrough.Hostname }} {{ $passthrough.Backend }}; - {{ end }} - # send SSL traffic to this nginx in a different port - default nginx-ssl-backend; - } -======= ->>>>>>> - log_format log_stream {{ $cfg.LogFormatStream }}; {{ if $cfg.DisableAccessLog }} diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index d4b350286..1e031423d 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -381,6 +381,8 @@ func (ic *GenericController) syncIngress(key interface{}) error { passUpstreams = append(passUpstreams, &ingress.SSLPassthroughBackend{ Backend: loc.Backend, Hostname: server.Hostname, + Service: loc.Service, + Port: loc.Port, }) break } @@ -620,6 +622,8 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress loc.Backend = ups.Name loc.IsDefBackend = false loc.Backend = ups.Name + loc.Port = ups.Port + loc.Service = ups.Service mergeLocationAnnotations(loc, anns) break } @@ -631,6 +635,8 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress Path: nginxPath, Backend: ups.Name, IsDefBackend: false, + Service: ups.Service, + Port: ups.Port, } mergeLocationAnnotations(loc, anns) server.Locations = append(server.Locations, loc) @@ -641,7 +647,6 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress // Configure Backends[].SSLPassthrough for _, upstream := range upstreams { - isHTTP := false isHTTPSfrom := []*ingress.Server{} for _, server := range servers { for _, location := range server.Locations { @@ -650,26 +655,18 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress if location.Path == rootLocation { if location.Backend == defUpstreamName { glog.Warningf("ignoring ssl passthrough of %v as it doesn't have a default backend (root context)", server.Hostname) - } else { - isHTTPSfrom = append(isHTTPSfrom, server) + continue } + + isHTTPSfrom = append(isHTTPSfrom, server) } - } else { - isHTTP = true + continue } } } } if len(isHTTPSfrom) > 0 { - if isHTTP { - for _, server := range isHTTPSfrom { - glog.Warningf("backend type mismatch on %v, assuming HTTP on ssl passthrough host %v", upstream.Name, server.Hostname) - // removing this server from the PassthroughBackends slice - server.SSLPassthrough = false - } - } else { - upstream.SSLPassthrough = true - } + upstream.SSLPassthrough = true } } @@ -761,42 +758,43 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing path.Backend.ServiceName, path.Backend.ServicePort.String()) - upstream, ok := upstreams[name] - isNewUpstream := !ok - - if isNewUpstream { - glog.V(3).Infof("creating upstream %v", name) - upstream = newUpstream(name) - upstreams[name] = upstream + if _, ok := upstreams[name]; ok { + continue } - if !upstream.Secure { - upstream.Secure = secUpstream + glog.V(3).Infof("creating upstream %v", name) + upstreams[name] = newUpstream(name) + if !upstreams[name].Secure { + upstreams[name].Secure = secUpstream } - - if upstream.SessionAffinity.AffinityType == "" { - upstream.SessionAffinity.AffinityType = affinity.AffinityType + if upstreams[name].SessionAffinity.AffinityType == "" { + upstreams[name].SessionAffinity.AffinityType = affinity.AffinityType if affinity.AffinityType == "cookie" { - upstream.SessionAffinity.CookieSessionAffinity.Name = affinity.CookieConfig.Name - upstream.SessionAffinity.CookieSessionAffinity.Hash = affinity.CookieConfig.Hash + upstreams[name].SessionAffinity.CookieSessionAffinity.Name = affinity.CookieConfig.Name + upstreams[name].SessionAffinity.CookieSessionAffinity.Hash = affinity.CookieConfig.Hash } } - if isNewUpstream { - svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName) - endp, err := ic.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz) - if err != nil { - glog.Warningf("error obtaining service endpoints: %v", err) - continue - } - upstream.Endpoints = endp - - s, e, _ := ic.svcLister.Store.GetByKey(svcKey) - if e { - upstream.Service = s.(*api.Service) - } - upstream.Port = path.Backend.ServicePort + svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName) + endp, err := ic.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz) + if err != nil { + glog.Warningf("error obtaining service endpoints: %v", err) + continue } + upstreams[name].Endpoints = endp + + s, exists, err := ic.svcLister.Store.GetByKey(svcKey) + if err != nil { + glog.Warningf("error obtaining service: %v", err) + continue + } + + if exists { + upstreams[name].Service = s.(*api.Service) + } else { + glog.Warningf("service %v does not exists", svcKey) + } + upstreams[name].Port = path.Backend.ServicePort } } } @@ -932,6 +930,7 @@ func (ic *GenericController) createServers(data []interface{}, // server already configured continue } + servers[host] = &ingress.Server{ Hostname: host, Locations: []*ingress.Location{ diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index 548b3b612..ec56817d5 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -147,9 +147,9 @@ type Configuration struct { // Backend describes one or more remote server/s (endpoints) associated with a service type Backend struct { // Name represents an unique api.Service name formatted as -- - Name string `json:"name"` - Service *api.Service - Port intstr.IntOrString + Name string `json:"name"` + Service *api.Service `json:"service"` + Port intstr.IntOrString `json:"port"` // This indicates if the communication protocol between the backend and the endpoint is HTTP or HTTPS // Allowing the use of HTTPS // The endpoint/s must provide a TLS connection. @@ -245,6 +245,9 @@ type Location struct { IsDefBackend bool `json:"isDefBackend"` // Backend describes the name of the backend to use. Backend string `json:"backend"` + + Service *api.Service `json:"service"` + Port intstr.IntOrString `json:"port"` // BasicDigestAuth returns authentication configuration for // an Ingress rule. // +optional @@ -292,9 +295,8 @@ type Location struct { // The endpoints must provide the TLS termination exposing the required SSL certificate. // The ingress controller only pipes the underlying TCP connection type SSLPassthroughBackend struct { - Namespace string - Service *api.Service - Port intstr.IntOrString + Service *api.Service `json:"service"` + Port intstr.IntOrString `json:"port"` // Backend describes the endpoints to use. Backend string `json:"namespace,omitempty"` // Hostname returns the FQDN of the server diff --git a/vendor/github.com/paultag/sniff/LICENSE b/vendor/github.com/paultag/sniff/LICENSE new file mode 100644 index 000000000..1e4a5f8dd --- /dev/null +++ b/vendor/github.com/paultag/sniff/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/paultag/sniff/parser/parser.go b/vendor/github.com/paultag/sniff/parser/parser.go new file mode 100644 index 000000000..535e3a5de --- /dev/null +++ b/vendor/github.com/paultag/sniff/parser/parser.go @@ -0,0 +1,147 @@ +/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. }}} */ + +package parser + +import ( + "fmt" +) + +var TLSHeaderLength = 5 + +/* This function is basically all most folks want to invoke out of this + * jumble of bits. This will take an incoming TLS Client Hello (including + * all the fuzzy bits at the beginning of it - fresh out of the socket) and + * go ahead and give us the SNI Name they want. */ +func GetHostname(data []byte) (string, error) { + if len(data) == 0 || data[0] != 0x16 { + return "", fmt.Errorf("Doesn't look like a TLS Client Hello") + } + + extensions, err := GetExtensionBlock(data) + if err != nil { + return "", err + } + sn, err := GetSNBlock(extensions) + if err != nil { + return "", err + } + sni, err := GetSNIBlock(sn) + if err != nil { + return "", err + } + return string(sni), nil +} + +/* Given a Server Name TLS Extension block, parse out and return the SNI + * (Server Name Indication) payload */ +func GetSNIBlock(data []byte) ([]byte, error) { + index := 0 + + for { + if index >= len(data) { + break + } + length := int((data[index] << 8) + data[index+1]) + endIndex := index + 2 + length + if data[index+2] == 0x00 { /* SNI */ + sni := data[index+3:] + sniLength := int((sni[0] << 8) + sni[1]) + return sni[2 : sniLength+2], nil + } + index = endIndex + } + return []byte{}, fmt.Errorf( + "Finished parsing the SN block without finding an SNI", + ) +} + +/* Given a TLS Extensions data block, go ahead and find the SN block */ +func GetSNBlock(data []byte) ([]byte, error) { + index := 0 + + if len(data) < 2 { + return []byte{}, fmt.Errorf("Not enough bytes to be an SN block") + } + + extensionLength := int((data[index] << 8) + data[index+1]) + data = data[2 : extensionLength+2] + + for { + if index >= len(data) { + break + } + length := int((data[index+2] << 8) + data[index+3]) + endIndex := index + 4 + length + if data[index] == 0x00 && data[index+1] == 0x00 { + return data[index+4 : endIndex], nil + } + + index = endIndex + } + + return []byte{}, fmt.Errorf( + "Finished parsing the Extension block without finding an SN block", + ) +} + +/* Given a raw TLS Client Hello, go ahead and find all the Extensions */ +func GetExtensionBlock(data []byte) ([]byte, error) { + /* data[0] - content type + * data[1], data[2] - major/minor version + * data[3], data[4] - total length + * data[...38+5] - start of SessionID (length bit) + * data[38+5] - length of SessionID + */ + var index = TLSHeaderLength + 38 + + if len(data) <= index+1 { + return []byte{}, fmt.Errorf("Not enough bits to be a Client Hello") + } + + /* Index is at SessionID Length bit */ + if newIndex := index + 1 + int(data[index]); (newIndex + 2) < len(data) { + index = newIndex + } else { + return []byte{}, fmt.Errorf("Not enough bytes for the SessionID") + } + + /* Index is at Cipher List Length bits */ + if newIndex := (index + 2 + int((data[index]<<8)+data[index+1])); (newIndex + 1) < len(data) { + index = newIndex + } else { + return []byte{}, fmt.Errorf("Not enough bytes for the Cipher List") + } + + /* Index is now at the compression length bit */ + if newIndex := index + 1 + int(data[index]); newIndex < len(data) { + index = newIndex + } else { + return []byte{}, fmt.Errorf("Not enough bytes for the compression length") + } + + /* Now we're at the Extension start */ + if len(data[index:]) == 0 { + return nil, fmt.Errorf("No extensions") + } + return data[index:], nil +} + +// vim: foldmethod=marker