diff --git a/controllers/nginx-third-party/controller.go b/controllers/nginx-third-party/controller.go index c104b57d5..8dc5e75e1 100644 --- a/controllers/nginx-third-party/controller.go +++ b/controllers/nginx-third-party/controller.go @@ -243,8 +243,6 @@ func (lbc *loadBalancerController) sync() { } func (lbc *loadBalancerController) getUpstreamServers(data []interface{}) ([]nginx.Upstream, []nginx.Server) { - pems := make(map[string]string) - upstreams := make(map[string]nginx.Upstream) servers := make(map[string]nginx.Server) @@ -297,6 +295,8 @@ func (lbc *loadBalancerController) getUpstreamServers(data []interface{}) ([]ngi } } + pems := lbc.getPemsFromIngress(data) + for _, rule := range ing.Spec.Rules { var server nginx.Server if existent, ok := servers[rule.Host]; ok { @@ -317,6 +317,18 @@ func (lbc *loadBalancerController) getUpstreamServers(data []interface{}) ([]ngi loc := nginx.Location{Path: path.Path} upsName := ing.GetNamespace() + "-" + path.Backend.ServiceName + svcKey := ing.GetNamespace() + "/" + path.Backend.ServiceName + _, svcExists, err := lbc.svcLister.Store.GetByKey(svcKey) + if err != nil { + glog.Infof("error getting service %v from the cache: %v", svcKey, err) + continue + } + + if !svcExists { + glog.Warningf("service %v does no exists. skipping Ingress rule", svcKey) + continue + } + for _, ups := range upstreams { if upsName == ups.Name { loc.Upstream = ups @@ -350,6 +362,41 @@ func (lbc *loadBalancerController) getUpstreamServers(data []interface{}) ([]ngi return aUpstreams, aServers } +func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[string]string { + pems := make(map[string]string) + + for _, ingIf := range data { + ing := ingIf.(*extensions.Ingress) + + for _, tls := range ing.Spec.TLS { + secretName := tls.SecretName + secret, err := lbc.client.Secrets(ing.Namespace).Get(secretName) + if err != nil { + glog.Warningf("Error retriveing secret %v for ing %v: %v", secretName, ing.Name, err) + continue + } + cert, ok := secret.Data[api.TLSCertKey] + if !ok { + glog.Warningf("Secret %v has no private key", secretName) + continue + } + key, ok := secret.Data[api.TLSPrivateKeyKey] + if !ok { + glog.Warningf("Secret %v has no cert", secretName) + continue + } + + pemFileName := lbc.nginx.AddOrUpdateCertAndKey(secretName, string(cert), string(key)) + + for _, host := range tls.Hosts { + pems[host] = pemFileName + } + } + } + + return pems +} + // getEndpoints returns a list of : for a given service/target port combination. func (lbc *loadBalancerController) getEndpoints(s *api.Service, servicePort intstr.IntOrString) []nginx.UpstreamServer { ep, err := lbc.endpLister.GetServiceEndpoints(s) @@ -415,9 +462,7 @@ func (lbc *loadBalancerController) Run() { go lbc.svcController.Run(lbc.stopCh) // periodic check for changes in configuration - go wait.Until(lbc.sync, 5*time.Second, wait.NeverStop) - - time.Sleep(5 * time.Second) + go wait.Until(lbc.sync, 10*time.Second, wait.NeverStop) <-lbc.stopCh glog.Infof("shutting down NGINX loadbalancer controller") diff --git a/controllers/nginx-third-party/lua/error_page.lua b/controllers/nginx-third-party/lua/error_page.lua index 2c604bfe1..d5d2afc1a 100644 --- a/controllers/nginx-third-party/lua/error_page.lua +++ b/controllers/nginx-third-party/lua/error_page.lua @@ -5,7 +5,10 @@ function openURL(status, page) local res, err = httpc:request_uri(page, { path = "/", - method = "GET" + method = "GET", + headers = { + ["Content-Type"] = ngx.var.httpReturnType or "text/html", + } }) if not res then diff --git a/controllers/nginx-third-party/lua/vendor/lua-resty-http/README.md b/controllers/nginx-third-party/lua/vendor/lua-resty-http/README.md index 917811d68..9ad07f03d 100644 --- a/controllers/nginx-third-party/lua/vendor/lua-resty-http/README.md +++ b/controllers/nginx-third-party/lua/vendor/lua-resty-http/README.md @@ -56,6 +56,7 @@ server { content_by_lua ' -- For simple singleshot requests, use the URI interface. + local http = require "resty.http" local httpc = http.new() local res, err = httpc:request_uri("http://example.com/helloworld", { method = "POST", diff --git a/controllers/nginx-third-party/lua/vendor/lua-resty-http/lib/resty/http.lua b/controllers/nginx-third-party/lua/vendor/lua-resty-http/lib/resty/http.lua index 38ee5db90..3a6a01a0c 100644 --- a/controllers/nginx-third-party/lua/vendor/lua-resty-http/lib/resty/http.lua +++ b/controllers/nginx-third-party/lua/vendor/lua-resty-http/lib/resty/http.lua @@ -67,7 +67,7 @@ end local _M = { - _VERSION = '0.06', + _VERSION = '0.07', } _M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version @@ -196,7 +196,7 @@ function _M.parse_uri(self, uri) m[3] = 80 end end - if not m[4] then m[4] = "/" end + if not m[4] or "" == m[4] then m[4] = "/" end return m, nil end end @@ -805,7 +805,11 @@ function _M.proxy_response(self, response, chunksize) end if chunk then - ngx.print(chunk) + local res, err = ngx.print(chunk) + if not res then + ngx_log(ngx_ERR, err) + break + end end until not chunk end diff --git a/controllers/nginx-third-party/lua/vendor/lua-resty-http/lua-resty-http-0.06-0.rockspec b/controllers/nginx-third-party/lua/vendor/lua-resty-http/lua-resty-http-0.07-0.rockspec similarity index 57% rename from controllers/nginx-third-party/lua/vendor/lua-resty-http/lua-resty-http-0.06-0.rockspec rename to controllers/nginx-third-party/lua/vendor/lua-resty-http/lua-resty-http-0.07-0.rockspec index e79bdf72d..d8e892281 100644 --- a/controllers/nginx-third-party/lua/vendor/lua-resty-http/lua-resty-http-0.06-0.rockspec +++ b/controllers/nginx-third-party/lua/vendor/lua-resty-http/lua-resty-http-0.07-0.rockspec @@ -1,12 +1,12 @@ package = "lua-resty-http" -version = "0.06-0" +version = "0.07-0" source = { - url = "git://github.com/pintsized/lua-resty-http", - tag = "v0.06" + url = "git://github.com/pintsized/lua-resty-http", + tag = "v0.07" } description = { - summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.", - detailed = [[ + summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.", + detailed = [[ Features an HTTP 1.0 and 1.1 streaming interface to reading bodies using coroutines, for predictable memory usage in Lua land. Alternative simple interface for singleshot requests @@ -17,17 +17,17 @@ description = { Recommended by the OpenResty maintainer as a long-term replacement for internal requests through ngx.location.capture. ]], - homepage = "https://github.com/pintsized/lua-resty-http", - license = "2-clause BSD", - maintainer = "James Hurst " + homepage = "https://github.com/pintsized/lua-resty-http", + license = "2-clause BSD", + maintainer = "James Hurst " } dependencies = { - "lua >= 5.1", + "lua >= 5.1" } build = { - type = "builtin", - modules = { - ["resty.http"] = "lib/resty/http.lua", - ["resty.http_headers"] = "lib/resty/http_headers.lua" - } + type = "builtin", + modules = { + ["resty.http"] = "lib/resty/http.lua", + ["resty.http_headers"] = "lib/resty/http_headers.lua" + } } diff --git a/controllers/nginx-third-party/lua/vendor/lua-resty-http/t/13-default-path.t b/controllers/nginx-third-party/lua/vendor/lua-resty-http/t/13-default-path.t new file mode 100644 index 000000000..94f45250b --- /dev/null +++ b/controllers/nginx-third-party/lua/vendor/lua-resty-http/t/13-default-path.t @@ -0,0 +1,52 @@ +# vim:set ft= ts=4 sw=4 et: + +use Test::Nginx::Socket; +use Cwd qw(cwd); + +plan tests => repeat_each() * (blocks() * 3); + +my $pwd = cwd(); + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;;"; + error_log logs/error.log debug; +}; + +$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ +=== TEST 1: request_uri (check the default path) +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua ' + local http = require "resty.http" + local httpc = http.new() + + local res, err = httpc:request_uri("http://127.0.0.1:"..ngx.var.server_port) + + if res and 200 == res.status then + ngx.say("OK") + else + ngx.say("FAIL") + end + '; + } + + location =/ { + content_by_lua ' + ngx.print("OK") + '; + } +--- request +GET /lua +--- response_body +OK +--- no_error_log +[error] + diff --git a/controllers/nginx-third-party/nginx.tmpl b/controllers/nginx-third-party/nginx.tmpl index 57bc90b02..d55ae0e8c 100644 --- a/controllers/nginx-third-party/nginx.tmpl +++ b/controllers/nginx-third-party/nginx.tmpl @@ -1,4 +1,4 @@ -{{ $cfg := .cfg }}{{ $sslCertificates := .sslCertificates }}{{ $defErrorSvc := .defErrorSvc }}{{ $defBackend := .defBackend }} +{{ $cfg := .cfg }}{{ $defErrorSvc := .defErrorSvc }}{{ $defBackend := .defBackend }} daemon off; worker_processes {{ $cfg.WorkerProcesses }}; @@ -17,13 +17,13 @@ http { lua_package_path '.?.lua;./etc/nginx/lua/?.lua;/etc/nginx/lua/vendor/lua-resty-http/lib/?.lua;'; init_by_lua_block { + def_backend = "http://{{ $defBackend.ServiceName }}.{{ $defBackend.Namespace }}.svc.cluster.local:{{ $defBackend.ServicePort }}" + {{ if $defErrorSvc }}{{/* only if exists a custom error service */}} dev_error_url = "http://{{ $defErrorSvc.ServiceName }}.{{ $defErrorSvc.Namespace }}.svc.cluster.local:{{ $defErrorSvc.ServicePort }}" {{ else }} - dev_error_url = nil + dev_error_url = def_backend {{ end }} - local options = {} - def_backend = "http://{{ $defBackend.ServiceName }}.{{ $defBackend.Namespace }}.svc.cluster.local:{{ $defBackend.ServicePort }}" require("error_page") } @@ -178,25 +178,6 @@ http { {{ if $defErrorSvc }}{{ template "CUSTOM_ERRORS" (dict "cfg" $cfg "defErrorSvc" $defErrorSvc) }}{{ end }} } - {{ if ge (len .sslCertificates) 1 }} - # SSL - # TODO: support more than one certificate - server { - listen 443 ssl http2 default_server; - {{ range $sslCert := .sslCertificates }}{{ if $sslCert.Default }} - # default certificate in case no match - ssl_certificate "{{ $sslCert.Cert }}"; - ssl_certificate_key "{{ $sslCert.Key }}"; - {{ end }}{{ end }} - - location / { - proxy_pass http://{{ $defBackend.ServiceName }}.{{ $defBackend.Namespace }}.svc.cluster.local:{{ $defBackend.ServicePort }}; - } - - {{ if $defErrorSvc }}{{ template "CUSTOM_ERRORS" (dict "cfg" $cfg "defErrorSvc" $defErrorSvc) }}{{ end }} - } - {{ end }} - {{range $name, $upstream := .upstreams}} upstream {{$upstream.Name}} { least_conn; @@ -256,6 +237,17 @@ http { } {{ if $defErrorSvc }}{{ template "CUSTOM_ERRORS" (dict "cfg" $cfg "defErrorSvc" $defErrorSvc) }}{{ end }} } + + # default server for services without endpoints + server { + listen 8081; + + location / { + content_by_lua_block { + openURL(503, dev_error_url) + } + } + } } # TCP services diff --git a/controllers/nginx-third-party/nginx/main.go b/controllers/nginx-third-party/nginx/main.go index 1fcd5ebbf..4990424ac 100644 --- a/controllers/nginx-third-party/nginx/main.go +++ b/controllers/nginx-third-party/nginx/main.go @@ -17,6 +17,7 @@ limitations under the License. package nginx import ( + "os" "runtime" "strconv" "strings" @@ -27,7 +28,6 @@ import ( "k8s.io/contrib/ingress/controllers/nginx-third-party/ssl" - "k8s.io/kubernetes/pkg/client/record" client "k8s.io/kubernetes/pkg/client/unversioned" k8sruntime "k8s.io/kubernetes/pkg/runtime" ) @@ -220,9 +220,9 @@ type NginxManager struct { // path to the configuration file to be used by nginx ConfigFile string - sslCertificates []ssl.Certificate - sslDHParam string - servicesL4 []Service + sslDHParam string + + servicesL4 []Service client *client.Client // template loaded ready to be used to generate the nginx configuration file @@ -231,8 +231,6 @@ type NginxManager struct { // obj runtime object to be used in events obj k8sruntime.Object - recorder record.EventRecorder - reloadLock *sync.Mutex } @@ -276,17 +274,25 @@ func newDefaultNginxCfg() *nginxConfiguration { // NewManager ... func NewManager(kubeClient *client.Client, defaultSvc, customErrorSvc Service) *NginxManager { ngx := &NginxManager{ - ConfigFile: "/etc/nginx/nginx.conf", - defBackend: defaultSvc, - defCfg: newDefaultNginxCfg(), - defError: customErrorSvc, - defResolver: strings.Join(getDnsServers(), " "), - reloadLock: &sync.Mutex{}, - sslDHParam: ssl.SearchDHParamFile(sslDirectory), - sslCertificates: ssl.CreateSSLCerts(sslDirectory), + ConfigFile: "/etc/nginx/nginx.conf", + defBackend: defaultSvc, + defCfg: newDefaultNginxCfg(), + defError: customErrorSvc, + defResolver: strings.Join(getDnsServers(), " "), + reloadLock: &sync.Mutex{}, } + ngx.createCertsDir(sslDirectory) + + ngx.sslDHParam = ssl.SearchDHParamFile(sslDirectory) + ngx.loadTemplate() return ngx } + +func (nginx *NginxManager) createCertsDir(base string) { + if err := os.Mkdir(base, os.ModeDir); err != nil { + glog.Fatalf("Couldn't create directory %v: %v", base, err) + } +} diff --git a/controllers/nginx-third-party/nginx/nginx.go b/controllers/nginx-third-party/nginx/nginx.go index 46839ee12..f5dd36afc 100644 --- a/controllers/nginx-third-party/nginx/nginx.go +++ b/controllers/nginx-third-party/nginx/nginx.go @@ -16,13 +16,11 @@ limitations under the License. package nginx -// NGINXController Updates NGINX configuration, starts and reloads NGINX -type NGINXController struct { - resolver string - nginxConfdPath string - nginxCertsPath string - local bool -} +import ( + "os" + + "github.com/golang/glog" +) // IngressNGINXConfig describes an NGINX configuration type IngressNGINXConfig struct { @@ -113,3 +111,25 @@ func NewUpstream(name string) Upstream { Backends: []UpstreamServer{}, } } + +// AddOrUpdateCertAndKey creates a .pem file wth the cert and the key with the specified name +func (nginx *NginxManager) AddOrUpdateCertAndKey(name string, cert string, key string) string { + pemFileName := sslDirectory + "/" + name + ".pem" + + pem, err := os.Create(pemFileName) + if err != nil { + glog.Fatalf("Couldn't create pem file %v: %v", pemFileName, err) + } + defer pem.Close() + + _, err = pem.WriteString(string(key)) + if err != nil { + glog.Fatalf("Couldn't write to pem file %v: %v", pemFileName, err) + } + _, err = pem.WriteString(string(cert)) + if err != nil { + glog.Fatalf("Couldn't write to pem file %v: %v", pemFileName, err) + } + + return pemFileName +} diff --git a/controllers/nginx-third-party/nginx/template.go b/controllers/nginx-third-party/nginx/template.go index 242cb9a88..bd2084fbb 100644 --- a/controllers/nginx-third-party/nginx/template.go +++ b/controllers/nginx-third-party/nginx/template.go @@ -25,12 +25,9 @@ import ( "github.com/fatih/structs" "github.com/golang/glog" - - "k8s.io/contrib/ingress/controllers/nginx-third-party/ssl" ) var funcMap = template.FuncMap{ - "getSSLHost": ssl.GetSSLHost, "empty": func(input interface{}) bool { check, ok := input.(string) if ok { @@ -66,7 +63,6 @@ func (ngx *NginxManager) writeCfg(cfg *nginxConfiguration, upstreams []Upstream, curNginxCfg := merge(toMap, fromMap) conf := make(map[string]interface{}) - conf["sslCertificates"] = ngx.sslCertificates conf["upstreams"] = upstreams conf["servers"] = servers conf["tcpServices"] = servicesL4 diff --git a/controllers/nginx-third-party/ssl/main.go b/controllers/nginx-third-party/ssl/main.go index f91226454..d4af139f3 100644 --- a/controllers/nginx-third-party/ssl/main.go +++ b/controllers/nginx-third-party/ssl/main.go @@ -17,139 +17,13 @@ limitations under the License. package ssl import ( - "crypto/tls" - "crypto/x509" - "encoding/pem" "fmt" "io/ioutil" "os" - "path/filepath" - "regexp" - "strings" "github.com/golang/glog" ) -// Certificate contains the cert, key and the list of valid hostnames -type Certificate struct { - Cert string - Key string - Cname []string - Valid bool - Default bool -} - -// CreateSSLCerts reads the content of the /etc/nginx-ssl directory and -// verifies the cert and key extracting the common names for this pair -func CreateSSLCerts(baseDir string) []Certificate { - sslCerts := []Certificate{} - - glog.Infof("inspecting directory %v for SSL certificates\n", baseDir) - files, _ := ioutil.ReadDir(baseDir) - for _, file := range files { - if !file.IsDir() { - continue - } - - // the name of the secret could be different than the certificate file - cert, key, err := getCert(fmt.Sprintf("%v/%v", baseDir, file.Name())) - if err != nil { - glog.Errorf("error checking certificate: %v", err) - continue - } - - hosts, err := checkSSLCertificate(cert, key) - if err == nil { - sslCert := Certificate{ - Cert: cert, - Key: key, - Cname: hosts, - Valid: true, - } - - if file.Name() == "default" { - sslCert.Default = true - } - - sslCerts = append(sslCerts, sslCert) - } else { - glog.Errorf("error checking certificate: %v", err) - } - } - - if len(sslCerts) == 1 { - sslCerts[0].Default = true - } - - glog.Infof("ssl certificates found: %v", sslCerts) - - return sslCerts -} - -// checkSSLCertificate check if the certificate and key file are valid -// returning the result of the validation and the list of hostnames -// contained in the common name/s -func checkSSLCertificate(certFile, keyFile string) ([]string, error) { - _, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - glog.Errorf("Error checking certificate and key file %v/%v: %v", certFile, keyFile, err) - return []string{}, err - } - - pemCerts, err := ioutil.ReadFile(certFile) - if err != nil { - return []string{}, err - } - - var block *pem.Block - block, pemCerts = pem.Decode(pemCerts) - - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - glog.Errorf("Error checking certificate and key file %v/%v: %v", certFile, keyFile, err) - return []string{}, err - } - - cn := []string{cert.Subject.CommonName} - if len(cert.DNSNames) > 0 { - cn = append(cn, cert.DNSNames...) - } - - glog.Infof("DNS %v %v\n", cn, len(cn)) - return cn, nil -} - -func verifyHostname(certFile, host string) bool { - pemCerts, err := ioutil.ReadFile(certFile) - if err != nil { - return false - } - - var block *pem.Block - block, pemCerts = pem.Decode(pemCerts) - - cert, err := x509.ParseCertificate(block.Bytes) - - err = cert.VerifyHostname(host) - if err == nil { - return true - } - - return false -} - -// GetSSLHost checks if in one of the secrets that contains SSL -// certificates could be used for the specified server name -func GetSSLHost(serverName string, certs []Certificate) Certificate { - for _, sslCert := range certs { - if verifyHostname(sslCert.Cert, serverName) { - return sslCert - } - } - - return Certificate{} -} - // SearchDHParamFile iterates all the secrets mounted inside the /etc/nginx-ssl directory // in order to find a file with the name dhparam.pem. If such file exists it will // returns the path. If not it just returns an empty string @@ -170,32 +44,3 @@ func SearchDHParamFile(baseDir string) string { glog.Warning("no file dhparam.pem found in secrets") return "" } - -// getCert returns the pair cert-key if exists or an error -func getCert(certDir string) (cert string, key string, err error) { - // we search for a file with extension crt - filepath.Walk(certDir, func(path string, f os.FileInfo, _ error) error { - if !f.IsDir() { - r, err := regexp.MatchString(".crt", f.Name()) - if err == nil && r { - cert = f.Name() - return nil - } - } - - return nil - }) - - cert = fmt.Sprintf("%v/%v", certDir, cert) - if _, err := os.Stat(cert); os.IsNotExist(err) { - return "", "", fmt.Errorf("No certificate found in directory %v: %v", certDir, err) - } - - key = strings.Replace(cert, ".crt", ".key", 1) - - if _, err := os.Stat(key); os.IsNotExist(err) { - return "", "", fmt.Errorf("No certificate key found in directory %v: %v", certDir, err) - } - - return -}