diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md
index 109e4c7d2..b361fc459 100644
--- a/controllers/nginx/configuration.md
+++ b/controllers/nginx/configuration.md
@@ -37,17 +37,18 @@ The following annotations are supported:
|Name |type|
|---------------------------|------|
-|[ingress.kubernetes.io/rewrite-target](#rewrite)|URI|
|[ingress.kubernetes.io/add-base-url](#rewrite)|true or false|
+|[ingress.kubernetes.io/auth-realm](#authentication)|string|
+|[ingress.kubernetes.io/auth-secret](#authentication)|string|
+|[ingress.kubernetes.io/auth-type](#authentication)|basic or digest|
+|[ingress.kubernetes.io/auth-url](#external-authentication)|string|
|[ingress.kubernetes.io/limit-connections](#rate-limiting)|number|
|[ingress.kubernetes.io/limit-rps](#rate-limiting)|number|
-|[ingress.kubernetes.io/auth-type](#authentication)|basic or digest|
-|[ingress.kubernetes.io/auth-secret](#authentication)|string|
-|[ingress.kubernetes.io/auth-realm](#authentication)|string|
+|[ingress.kubernetes.io/rewrite-target](#rewrite)|URI|
+|[ingress.kubernetes.io/secure-backends](#secure-backends)|true or false|
|[ingress.kubernetes.io/ssl-redirect](#server-side-https-enforcement-through-redirect)|true or false|
|[ingress.kubernetes.io/upstream-max-fails](#custom-nginx-upstream-checks)|number|
|[ingress.kubernetes.io/upstream-fail-timeout](#custom-nginx-upstream-checks)|number|
-|[ingress.kubernetes.io/secure-backends](#secure-backends)|true or false|
|[ingress.kubernetes.io/whitelist-source-range](#whitelist-source-range)|CIDR|
@@ -119,6 +120,18 @@ ingress.kubernetes.io/auth-realm:"realm string"
Please check the [auth](examples/auth/README.md) example
+### External Authentication
+
+To use an existing service that provides authentication the Ingress rule can be annotated with `ingress.kubernetes.io/auth-url` to indicate the URL where the HTTP request should be sent.
+Additionally is possible to set `ingress.kubernetes.io/auth-method` to specify the HTTP method to use (GET or POST) and `ingress.kubernetes.io/auth-send-body` to true or false (default).
+
+```
+ingress.kubernetes.io/auth-url:"URL to the authentication service"
+```
+
+Please check the [external-auth](examples/external-auth/README.md) example
+
+
### Rewrite
In some scenarios the exposed URL in the backend service differs from the specified path in the Ingress rule. Without a rewrite any request will return 404.
diff --git a/controllers/nginx/controller.go b/controllers/nginx/controller.go
index c1118e569..097e458b7 100644
--- a/controllers/nginx/controller.go
+++ b/controllers/nginx/controller.go
@@ -42,6 +42,7 @@ import (
"k8s.io/contrib/ingress/controllers/nginx/nginx"
"k8s.io/contrib/ingress/controllers/nginx/nginx/auth"
+ "k8s.io/contrib/ingress/controllers/nginx/nginx/authreq"
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
"k8s.io/contrib/ingress/controllers/nginx/nginx/cors"
"k8s.io/contrib/ingress/controllers/nginx/nginx/healthcheck"
@@ -723,6 +724,12 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
glog.V(3).Infof("error reading CORS annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
}
+ ra, err := authreq.ParseAnnotations(ing)
+ glog.V(3).Infof("nginx auth request %v", ra)
+ if err != nil {
+ glog.V(3).Infof("error reading auth request annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
+ }
+
host := rule.Host
if host == "" {
host = defServerName
@@ -756,6 +763,7 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
loc.SecureUpstream = secUpstream
loc.Whitelist = *wl
loc.EnableCORS = eCORS
+ loc.ExternalAuthURL = ra
addLoc = false
continue
@@ -771,14 +779,15 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
if addLoc {
server.Locations = append(server.Locations, &ingress.Location{
- Path: nginxPath,
- Upstream: *ups,
- Auth: *nginxAuth,
- RateLimit: *rl,
- Redirect: *locRew,
- SecureUpstream: secUpstream,
- Whitelist: *wl,
- EnableCORS: eCORS,
+ Path: nginxPath,
+ Upstream: *ups,
+ Auth: *nginxAuth,
+ RateLimit: *rl,
+ Redirect: *locRew,
+ SecureUpstream: secUpstream,
+ Whitelist: *wl,
+ EnableCORS: eCORS,
+ ExternalAuthURL: ra,
})
}
}
diff --git a/controllers/nginx/examples/external-auth/README.md b/controllers/nginx/examples/external-auth/README.md
new file mode 100644
index 000000000..db522c1d2
--- /dev/null
+++ b/controllers/nginx/examples/external-auth/README.md
@@ -0,0 +1,148 @@
+# External authentication
+
+### Example 1:
+
+Use an external service (Basic Auth) located in `https://httpbin.org`
+
+```
+$ kubectl create -f ingress.yaml
+ingress "external-auth" created
+$ kubectl get ing external-auth
+NAME HOSTS ADDRESS PORTS AGE
+external-auth external-auth-01.sample.com 172.17.4.99 80 13s
+$ kubectl get ing external-auth -o yaml
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+ annotations:
+ ingress.kubernetes.io/auth-url: https://httpbin.org/basic-auth/user/passwd
+ creationTimestamp: 2016-10-03T13:50:35Z
+ generation: 1
+ name: external-auth
+ namespace: default
+ resourceVersion: "2068378"
+ selfLink: /apis/extensions/v1beta1/namespaces/default/ingresses/external-auth
+ uid: 5c388f1d-8970-11e6-9004-080027d2dc94
+spec:
+ rules:
+ - host: external-auth-01.sample.com
+ http:
+ paths:
+ - backend:
+ serviceName: echoheaders
+ servicePort: 80
+ path: /
+status:
+ loadBalancer:
+ ingress:
+ - ip: 172.17.4.99
+$
+```
+
+Test 1: no username/password (expect code 401)
+```
+$ curl -k http://172.17.4.99 -v -H 'Host: external-auth-01.sample.com'
+* Rebuilt URL to: http://172.17.4.99/
+* Trying 172.17.4.99...
+* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
+> GET / HTTP/1.1
+> Host: external-auth-01.sample.com
+> User-Agent: curl/7.50.1
+> Accept: */*
+>
+< HTTP/1.1 401 Unauthorized
+< Server: nginx/1.11.3
+< Date: Mon, 03 Oct 2016 14:52:08 GMT
+< Content-Type: text/html
+< Content-Length: 195
+< Connection: keep-alive
+< WWW-Authenticate: Basic realm="Fake Realm"
+<
+
+
401 Authorization Required
+
+401 Authorization Required
+
nginx/1.11.3
+
+
+* Connection #0 to host 172.17.4.99 left intact
+```
+
+Test 2: valid username/password (expect code 200)
+```
+$ curl -k http://172.17.4.99 -v -H 'Host: external-auth-01.sample.com' -u 'user:passwd'
+* Rebuilt URL to: http://172.17.4.99/
+* Trying 172.17.4.99...
+* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
+* Server auth using Basic with user 'user'
+> GET / HTTP/1.1
+> Host: external-auth-01.sample.com
+> Authorization: Basic dXNlcjpwYXNzd2Q=
+> User-Agent: curl/7.50.1
+> Accept: */*
+>
+< HTTP/1.1 200 OK
+< Server: nginx/1.11.3
+< Date: Mon, 03 Oct 2016 14:52:50 GMT
+< Content-Type: text/plain
+< Transfer-Encoding: chunked
+< Connection: keep-alive
+<
+CLIENT VALUES:
+client_address=10.2.60.2
+command=GET
+real path=/
+query=nil
+request_version=1.1
+request_uri=http://external-auth-01.sample.com:8080/
+
+SERVER VALUES:
+server_version=nginx: 1.9.11 - lua: 10001
+
+HEADERS RECEIVED:
+accept=*/*
+authorization=Basic dXNlcjpwYXNzd2Q=
+connection=close
+host=external-auth-01.sample.com
+user-agent=curl/7.50.1
+x-forwarded-for=10.2.60.1
+x-forwarded-host=external-auth-01.sample.com
+x-forwarded-port=80
+x-forwarded-proto=http
+x-real-ip=10.2.60.1
+BODY:
+* Connection #0 to host 172.17.4.99 left intact
+-no body in request-
+```
+
+Test 3: invalid username/password (expect code 401)
+```
+curl -k http://172.17.4.99 -v -H 'Host: external-auth-01.sample.com' -u 'user:user'
+* Rebuilt URL to: http://172.17.4.99/
+* Trying 172.17.4.99...
+* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
+* Server auth using Basic with user 'user'
+> GET / HTTP/1.1
+> Host: external-auth-01.sample.com
+> Authorization: Basic dXNlcjp1c2Vy
+> User-Agent: curl/7.50.1
+> Accept: */*
+>
+< HTTP/1.1 401 Unauthorized
+< Server: nginx/1.11.3
+< Date: Mon, 03 Oct 2016 14:53:04 GMT
+< Content-Type: text/html
+< Content-Length: 195
+< Connection: keep-alive
+* Authentication problem. Ignoring this.
+< WWW-Authenticate: Basic realm="Fake Realm"
+<
+
+401 Authorization Required
+
+401 Authorization Required
+
nginx/1.11.3
+
+
+* Connection #0 to host 172.17.4.99 left intact
+```
diff --git a/controllers/nginx/examples/external-auth/ingress.yaml b/controllers/nginx/examples/external-auth/ingress.yaml
new file mode 100644
index 000000000..1cf779ce2
--- /dev/null
+++ b/controllers/nginx/examples/external-auth/ingress.yaml
@@ -0,0 +1,15 @@
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+ annotations:
+ ingress.kubernetes.io/auth-url: "https://httpbin.org/basic-auth/user/passwd"
+ name: external-auth
+spec:
+ rules:
+ - host: external-auth-01.sample.com
+ http:
+ paths:
+ - backend:
+ serviceName: echoheaders
+ servicePort: 80
+ path: /
\ No newline at end of file
diff --git a/controllers/nginx/main.go b/controllers/nginx/main.go
index 5b0bc50c4..42d47c0e7 100644
--- a/controllers/nginx/main.go
+++ b/controllers/nginx/main.go
@@ -95,14 +95,16 @@ func main() {
glog.Fatalf("Please specify --default-backend-service")
}
- config, err := clientConfig.ClientConfig()
+ kubeClient, err := unversioned.NewInCluster()
if err != nil {
- glog.Fatalf("error connecting to the client: %v", err)
- }
- kubeClient, err := unversioned.New(config)
-
- if err != nil {
- glog.Fatalf("failed to create client: %v", err)
+ config, err := clientConfig.ClientConfig()
+ if err != nil {
+ glog.Fatalf("error configuring the client: %v", err)
+ }
+ kubeClient, err = unversioned.New(config)
+ if err != nil {
+ glog.Fatalf("failed to create client: %v", err)
+ }
}
runtimePodInfo, err := getPodDetails(kubeClient)
diff --git a/controllers/nginx/nginx.tmpl b/controllers/nginx/nginx.tmpl
index 8ee54b8b6..d543cfe99 100644
--- a/controllers/nginx/nginx.tmpl
+++ b/controllers/nginx/nginx.tmpl
@@ -90,6 +90,7 @@ http {
{{ if not (empty .defResolver) }}# Custom dns resolver.
resolver {{ .defResolver }} valid=30s;
+ resolver_timeout 10s;
{{ end }}
map $http_upgrade $connection_upgrade {
@@ -183,27 +184,47 @@ http {
server {
server_name {{ $server.Name }};
listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};
- {{ if $server.SSL }}listen 443 {{ if $cfg.useProxyProtocol }}proxy_protocol{{ end }} ssl {{ if $cfg.enableSpdy }}spdy{{ end }} {{ if $cfg.useHttp2 }}http2{{ end }};
+ {{- if $server.SSL }}listen 443 {{ if $cfg.useProxyProtocol }}proxy_protocol{{ end }} ssl {{ if $cfg.enableSpdy }}spdy{{ end }} {{ if $cfg.useHttp2 }}http2{{ end }};
{{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
# PEM sha: {{ $server.SSLPemChecksum }}
ssl_certificate {{ $server.SSLCertificate }};
ssl_certificate_key {{ $server.SSLCertificateKey }};
{{- end }}
- {{ if (and $server.SSL $cfg.hsts) -}}
+ {{- if (and $server.SSL $cfg.hsts) }}
more_set_headers "Strict-Transport-Security: max-age={{ $cfg.hstsMaxAge }}{{ if $cfg.hstsIncludeSubdomains }}; includeSubDomains{{ end }}; preload";
{{- end }}
- {{ if $cfg.enableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end }}
+ {{- if $cfg.enableVtsStatus }}vhost_traffic_status_filter_by_set_key $geoip_country_code country::$server_name;{{ end -}}
{{- range $location := $server.Locations }}
{{ $path := buildLocation $location }}
+ {{ $authPath := buildAuthLocation $location }}
+ {{- if not (empty $authPath) }}
+ location = {{ $authPath }} {
+ internal;
+ {{ if not $location.ExternalAuthURL.SendBody }}
+ proxy_pass_request_body off;
+ proxy_set_header Content-Length "";
+ {{ end -}}
+ {{ if not (empty $location.ExternalAuthURL.Method) }}
+ proxy_method {{ $location.ExternalAuthURL.Method }};
+ {{ end -}}
+ proxy_set_header Host $host;
+ proxy_pass_request_headers on;
+ proxy_pass {{ $location.ExternalAuthURL.URL }};
+ }
+ {{ end }}
location {{ $path }} {
{{ if gt (len $location.Whitelist.CIDR) 0 }}
{{- range $ip := $location.Whitelist.CIDR }}
allow {{ $ip }};{{ end }}
deny all;
{{ end -}}
+ {{ if not (empty $authPath) }}
+ # this location requires authentication
+ auth_request {{ $authPath }};
+ {{ end }}
{{ if (and $server.SSL $location.Redirect.SSLRedirect) -}}
# enforce ssl on server side
@@ -272,7 +293,7 @@ http {
{{ if eq $server.Name "_" }}
# health checks in cloud providers require the use of port 80
- location {{ $cfg.healthzUrl }} {
+ location {{ $cfg.HealthzURL }} {
access_log off;
return 200;
}
diff --git a/controllers/nginx/nginx/authreq/main.go b/controllers/nginx/nginx/authreq/main.go
new file mode 100644
index 000000000..0f53e5bd2
--- /dev/null
+++ b/controllers/nginx/nginx/authreq/main.go
@@ -0,0 +1,138 @@
+/*
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package authreq
+
+import (
+ "errors"
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "k8s.io/kubernetes/pkg/apis/extensions"
+)
+
+const (
+ // external URL that provides the authentication
+ authURL = "ingress.kubernetes.io/auth-url"
+ authMethod = "ingress.kubernetes.io/auth-method"
+ authBody = "ingress.kubernetes.io/auth-send-body"
+)
+
+var (
+ // ErrMissingAnnotations is returned when the ingress rule
+ // does not contain annotations related with authentication
+ ErrMissingAnnotations = errors.New("missing authentication annotations")
+)
+
+// Auth returns external authentication configuration for an Ingress rule
+type Auth struct {
+ URL string
+ Method string
+ SendBody bool
+}
+
+type ingAnnotations map[string]string
+
+func (a ingAnnotations) url() (string, error) {
+ val, ok := a[authURL]
+ if !ok {
+ return "", ErrMissingAnnotations
+ }
+
+ return val, nil
+}
+
+func (a ingAnnotations) method() string {
+ val, ok := a[authMethod]
+ if !ok {
+ return ""
+ }
+
+ return val
+}
+
+func (a ingAnnotations) sendBody() bool {
+ val, ok := a[authBody]
+ if ok {
+ if b, err := strconv.ParseBool(val); err == nil {
+ return b
+ }
+ }
+ return false
+}
+
+var (
+ methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
+)
+
+func validMethod(method string) bool {
+ if len(method) == 0 {
+ return false
+ }
+
+ for _, m := range methods {
+ if method == m {
+ return true
+ }
+ }
+ return false
+}
+
+// ParseAnnotations parses the annotations contained in the ingress
+// rule used to use an external URL as source for authentication
+func ParseAnnotations(ing *extensions.Ingress) (Auth, error) {
+ if ing.GetAnnotations() == nil {
+ return Auth{}, ErrMissingAnnotations
+ }
+
+ str, err := ingAnnotations(ing.GetAnnotations()).url()
+ if err != nil {
+ return Auth{}, err
+ }
+ if str == "" {
+ return Auth{}, fmt.Errorf("an empty string is not a valid URL")
+ }
+
+ ur, err := url.Parse(str)
+ if err != nil {
+ return Auth{}, err
+ }
+ if ur.Scheme == "" {
+ return Auth{}, fmt.Errorf("url scheme is empty")
+ }
+ if ur.Host == "" {
+ return Auth{}, fmt.Errorf("url host is empty")
+ }
+
+ if strings.Index(ur.Host, "..") != -1 {
+ return Auth{}, fmt.Errorf("invalid url host")
+ }
+
+ m := ingAnnotations(ing.GetAnnotations()).method()
+ if len(m) != 0 && !validMethod(m) {
+ return Auth{}, fmt.Errorf("invalid HTTP method")
+ }
+
+ sb := ingAnnotations(ing.GetAnnotations()).sendBody()
+
+ return Auth{
+ URL: str,
+ Method: m,
+ SendBody: sb,
+ }, nil
+}
diff --git a/controllers/nginx/nginx/authreq/main_test.go b/controllers/nginx/nginx/authreq/main_test.go
new file mode 100644
index 000000000..f2936f72b
--- /dev/null
+++ b/controllers/nginx/nginx/authreq/main_test.go
@@ -0,0 +1,114 @@
+/*
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package authreq
+
+import (
+ "fmt"
+ "testing"
+
+ "k8s.io/kubernetes/pkg/api"
+ "k8s.io/kubernetes/pkg/apis/extensions"
+ "k8s.io/kubernetes/pkg/util/intstr"
+)
+
+func buildIngress() *extensions.Ingress {
+ defaultBackend := extensions.IngressBackend{
+ ServiceName: "default-backend",
+ ServicePort: intstr.FromInt(80),
+ }
+
+ return &extensions.Ingress{
+ ObjectMeta: api.ObjectMeta{
+ Name: "foo",
+ Namespace: api.NamespaceDefault,
+ },
+ Spec: extensions.IngressSpec{
+ Backend: &extensions.IngressBackend{
+ ServiceName: "default-backend",
+ ServicePort: intstr.FromInt(80),
+ },
+ Rules: []extensions.IngressRule{
+ {
+ Host: "foo.bar.com",
+ IngressRuleValue: extensions.IngressRuleValue{
+ HTTP: &extensions.HTTPIngressRuleValue{
+ Paths: []extensions.HTTPIngressPath{
+ {
+ Path: "/foo",
+ Backend: defaultBackend,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func TestAnnotations(t *testing.T) {
+ ing := buildIngress()
+
+ _, err := ingAnnotations(ing.GetAnnotations()).url()
+ if err == nil {
+ t.Error("Expected a validation error")
+ }
+
+ data := map[string]string{}
+ ing.SetAnnotations(data)
+
+ tests := []struct {
+ title string
+ url string
+ method string
+ sendBody bool
+ expErr bool
+ }{
+ {"empty", "", "", false, true},
+ {"no scheme", "bar", "", false, true},
+ {"invalid host", "http://", "", false, true},
+ {"invalid host (multiple dots)", "http://foo..bar.com", "", false, true},
+ {"valid URL", "http://bar.foo.com/external-auth", "", false, false},
+ {"valid URL - send body", "http://foo.com/external-auth", "POST", true, false},
+ {"valid URL - send body", "http://foo.com/external-auth", "GET", true, false},
+ }
+
+ for _, test := range tests {
+ data[authURL] = test.url
+ data[authBody] = fmt.Sprintf("%v", test.sendBody)
+ data[authMethod] = fmt.Sprintf("%v", test.method)
+
+ u, err := ParseAnnotations(ing)
+
+ if test.expErr {
+ if err == nil {
+ t.Errorf("%v: expected error but retuned nil", test.title)
+ }
+ continue
+ }
+
+ if u.URL != test.url {
+ t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL)
+ }
+ if u.Method != test.method {
+ t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.method, u.Method)
+ }
+ if u.SendBody != test.sendBody {
+ t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.sendBody, u.SendBody)
+ }
+ }
+}
diff --git a/controllers/nginx/nginx/ingress/nginx.go b/controllers/nginx/nginx/ingress/nginx.go
index 132b7b585..5dfd7dbff 100644
--- a/controllers/nginx/nginx/ingress/nginx.go
+++ b/controllers/nginx/nginx/ingress/nginx.go
@@ -18,6 +18,7 @@ package ingress
import (
"k8s.io/contrib/ingress/controllers/nginx/nginx/auth"
+ "k8s.io/contrib/ingress/controllers/nginx/nginx/authreq"
"k8s.io/contrib/ingress/controllers/nginx/nginx/ipwhitelist"
"k8s.io/contrib/ingress/controllers/nginx/nginx/ratelimit"
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
@@ -93,15 +94,16 @@ func (c ServerByName) Less(i, j int) bool {
// Location describes an NGINX location
type Location struct {
- Path string
- IsDefBackend bool
- Upstream Upstream
- Auth auth.Nginx
- RateLimit ratelimit.RateLimit
- Redirect rewrite.Redirect
- SecureUpstream bool
- Whitelist ipwhitelist.SourceRange
- EnableCORS bool
+ Path string
+ IsDefBackend bool
+ Upstream Upstream
+ Auth auth.Nginx
+ RateLimit ratelimit.RateLimit
+ Redirect rewrite.Redirect
+ SecureUpstream bool
+ Whitelist ipwhitelist.SourceRange
+ EnableCORS bool
+ ExternalAuthURL authreq.Auth
}
// LocationByPath sorts location by path
diff --git a/controllers/nginx/nginx/main.go b/controllers/nginx/nginx/main.go
index 438e26415..944181b1d 100644
--- a/controllers/nginx/nginx/main.go
+++ b/controllers/nginx/nginx/main.go
@@ -71,17 +71,21 @@ func NewManager(kubeClient *client.Client) *Manager {
ngx := &Manager{
ConfigFile: "/etc/nginx/nginx.conf",
defCfg: config.NewDefault(),
- defResolver: strings.Join(getDNSServers(), " "),
reloadLock: &sync.Mutex{},
reloadRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.1, 1),
}
+ res, err := getDNSServers()
+ if err != nil {
+ glog.Warningf("error reading nameservers: %v", err)
+ }
+ ngx.defResolver = strings.Join(res, " ")
+
ngx.createCertsDir(config.SSLDirectory)
ngx.sslDHParam = ngx.SearchDHParamFile(config.SSLDirectory)
var onChange func()
-
onChange = func() {
template, err := ngx_template.NewTemplate(tmplPath, onChange)
if err != nil {
diff --git a/controllers/nginx/nginx/template/template.go b/controllers/nginx/nginx/template/template.go
index d4f22a371..a7e7a18a1 100644
--- a/controllers/nginx/nginx/template/template.go
+++ b/controllers/nginx/nginx/template/template.go
@@ -18,6 +18,7 @@ package template
import (
"bytes"
+ "encoding/base64"
"encoding/json"
"fmt"
"regexp"
@@ -49,6 +50,7 @@ var (
return true
},
"buildLocation": buildLocation,
+ "buildAuthLocation": buildAuthLocation,
"buildProxyPass": buildProxyPass,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
@@ -193,6 +195,22 @@ func buildLocation(input interface{}) string {
return path
}
+func buildAuthLocation(input interface{}) string {
+ location, ok := input.(*ingress.Location)
+ if !ok {
+ return ""
+ }
+
+ if location.ExternalAuthURL.URL == "" {
+ return ""
+ }
+
+ str := base64.URLEncoding.EncodeToString([]byte(location.Path))
+ // avoid locations containing the = char
+ str = strings.Replace(str, "=", "", -1)
+ return fmt.Sprintf("/_external-auth-%v", str)
+}
+
// buildProxyPass produces the proxy pass string, if the ingress has redirects
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
// If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will
diff --git a/controllers/nginx/nginx/utils.go b/controllers/nginx/nginx/utils.go
index 5bc9d3533..40a6dffd6 100644
--- a/controllers/nginx/nginx/utils.go
+++ b/controllers/nginx/nginx/utils.go
@@ -38,15 +38,14 @@ const (
)
// getDNSServers returns the list of nameservers located in the file /etc/resolv.conf
-func getDNSServers() []string {
+func getDNSServers() ([]string, error) {
+ var nameservers []string
file, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
- return []string{}
+ return nameservers, err
}
// Lines of the form "nameserver 1.2.3.4" accumulate.
- nameservers := []string{}
-
lines := strings.Split(string(file), "\n")
for l := range lines {
trimmed := strings.TrimSpace(lines[l])
@@ -63,7 +62,7 @@ func getDNSServers() []string {
}
glog.V(3).Infof("nameservers to use: %v", nameservers)
- return nameservers
+ return nameservers, nil
}
// getConfigKeyToStructKeyMap returns a map with the ConfigMapKey as key and the StructName as value.
@@ -143,6 +142,10 @@ func (ngx *Manager) ReadConfig(conf *api.ConfigMap) config.Configuration {
cfgDefault.CustomHTTPErrors = ngx.filterErrors(cErrors)
cfgDefault.SkipAccessLogURLs = cSkipUrls
+ // no custom resolver means use the system resolver
+ if cfgDefault.Resolver == "" {
+ cfgDefault.Resolver = ngx.defResolver
+ }
return cfgDefault
}
diff --git a/controllers/nginx/nginx/utils_test.go b/controllers/nginx/nginx/utils_test.go
index 294b3dd01..e9c37edd0 100644
--- a/controllers/nginx/nginx/utils_test.go
+++ b/controllers/nginx/nginx/utils_test.go
@@ -89,3 +89,13 @@ func TestManagerReadConfigStringNothing(t *testing.T) {
t.Errorf("Failed to set string value true actual='%s' expected='%s'", configNginx.SSLSessionTimeout, exp)
}
}
+
+func TestGetDNSServers(t *testing.T) {
+ s, err := getDNSServers()
+ if err != nil {
+ t.Fatalf("unexpected error reading /etc/resolv.conf file: %v", err)
+ }
+ if len(s) < 1 {
+ t.Error("expected at least 1 nameserver in /etc/resolv.conf")
+ }
+}