Add external authentication using auth_request

This commit is contained in:
Manuel de Brito Fontes 2016-08-19 11:51:40 -03:00
parent 25bf00a1fc
commit 541928e27d
13 changed files with 537 additions and 40 deletions

View file

@ -37,17 +37,18 @@ The following annotations are supported:
|Name |type| |Name |type|
|---------------------------|------| |---------------------------|------|
|[ingress.kubernetes.io/rewrite-target](#rewrite)|URI|
|[ingress.kubernetes.io/add-base-url](#rewrite)|true or false| |[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-connections](#rate-limiting)|number|
|[ingress.kubernetes.io/limit-rps](#rate-limiting)|number| |[ingress.kubernetes.io/limit-rps](#rate-limiting)|number|
|[ingress.kubernetes.io/auth-type](#authentication)|basic or digest| |[ingress.kubernetes.io/rewrite-target](#rewrite)|URI|
|[ingress.kubernetes.io/auth-secret](#authentication)|string| |[ingress.kubernetes.io/secure-backends](#secure-backends)|true or false|
|[ingress.kubernetes.io/auth-realm](#authentication)|string|
|[ingress.kubernetes.io/ssl-redirect](#server-side-https-enforcement-through-redirect)|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-max-fails](#custom-nginx-upstream-checks)|number|
|[ingress.kubernetes.io/upstream-fail-timeout](#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| |[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 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 ### 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. 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.

View file

@ -42,6 +42,7 @@ import (
"k8s.io/contrib/ingress/controllers/nginx/nginx" "k8s.io/contrib/ingress/controllers/nginx/nginx"
"k8s.io/contrib/ingress/controllers/nginx/nginx/auth" "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/config"
"k8s.io/contrib/ingress/controllers/nginx/nginx/cors" "k8s.io/contrib/ingress/controllers/nginx/nginx/cors"
"k8s.io/contrib/ingress/controllers/nginx/nginx/healthcheck" "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) 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 host := rule.Host
if host == "" { if host == "" {
host = defServerName host = defServerName
@ -756,6 +763,7 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
loc.SecureUpstream = secUpstream loc.SecureUpstream = secUpstream
loc.Whitelist = *wl loc.Whitelist = *wl
loc.EnableCORS = eCORS loc.EnableCORS = eCORS
loc.ExternalAuthURL = ra
addLoc = false addLoc = false
continue continue
@ -771,14 +779,15 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
if addLoc { if addLoc {
server.Locations = append(server.Locations, &ingress.Location{ server.Locations = append(server.Locations, &ingress.Location{
Path: nginxPath, Path: nginxPath,
Upstream: *ups, Upstream: *ups,
Auth: *nginxAuth, Auth: *nginxAuth,
RateLimit: *rl, RateLimit: *rl,
Redirect: *locRew, Redirect: *locRew,
SecureUpstream: secUpstream, SecureUpstream: secUpstream,
Whitelist: *wl, Whitelist: *wl,
EnableCORS: eCORS, EnableCORS: eCORS,
ExternalAuthURL: ra,
}) })
} }
} }

View file

@ -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"
<
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.11.3</center>
</body>
</html>
* 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"
<
<html>
<head><title>401 Authorization Required</title></head>
<body bgcolor="white">
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.11.3</center>
</body>
</html>
* Connection #0 to host 172.17.4.99 left intact
```

View file

@ -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: /

View file

@ -95,14 +95,16 @@ func main() {
glog.Fatalf("Please specify --default-backend-service") glog.Fatalf("Please specify --default-backend-service")
} }
config, err := clientConfig.ClientConfig() kubeClient, err := unversioned.NewInCluster()
if err != nil { if err != nil {
glog.Fatalf("error connecting to the client: %v", err) config, err := clientConfig.ClientConfig()
} if err != nil {
kubeClient, err := unversioned.New(config) glog.Fatalf("error configuring the client: %v", err)
}
if err != nil { kubeClient, err = unversioned.New(config)
glog.Fatalf("failed to create client: %v", err) if err != nil {
glog.Fatalf("failed to create client: %v", err)
}
} }
runtimePodInfo, err := getPodDetails(kubeClient) runtimePodInfo, err := getPodDetails(kubeClient)

View file

@ -90,6 +90,7 @@ http {
{{ if not (empty .defResolver) }}# Custom dns resolver. {{ if not (empty .defResolver) }}# Custom dns resolver.
resolver {{ .defResolver }} valid=30s; resolver {{ .defResolver }} valid=30s;
resolver_timeout 10s;
{{ end }} {{ end }}
map $http_upgrade $connection_upgrade { map $http_upgrade $connection_upgrade {
@ -183,27 +184,47 @@ http {
server { server {
server_name {{ $server.Name }}; server_name {{ $server.Name }};
listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }}; 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 */}} {{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
# PEM sha: {{ $server.SSLPemChecksum }} # PEM sha: {{ $server.SSLPemChecksum }}
ssl_certificate {{ $server.SSLCertificate }}; ssl_certificate {{ $server.SSLCertificate }};
ssl_certificate_key {{ $server.SSLCertificateKey }}; ssl_certificate_key {{ $server.SSLCertificateKey }};
{{- end }} {{- 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"; more_set_headers "Strict-Transport-Security: max-age={{ $cfg.hstsMaxAge }}{{ if $cfg.hstsIncludeSubdomains }}; includeSubDomains{{ end }}; preload";
{{- end }} {{- 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 }} {{- range $location := $server.Locations }}
{{ $path := buildLocation $location }} {{ $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 }} { location {{ $path }} {
{{ if gt (len $location.Whitelist.CIDR) 0 }} {{ if gt (len $location.Whitelist.CIDR) 0 }}
{{- range $ip := $location.Whitelist.CIDR }} {{- range $ip := $location.Whitelist.CIDR }}
allow {{ $ip }};{{ end }} allow {{ $ip }};{{ end }}
deny all; deny all;
{{ end -}} {{ end -}}
{{ if not (empty $authPath) }}
# this location requires authentication
auth_request {{ $authPath }};
{{ end }}
{{ if (and $server.SSL $location.Redirect.SSLRedirect) -}} {{ if (and $server.SSL $location.Redirect.SSLRedirect) -}}
# enforce ssl on server side # enforce ssl on server side
@ -272,7 +293,7 @@ http {
{{ if eq $server.Name "_" }} {{ if eq $server.Name "_" }}
# health checks in cloud providers require the use of port 80 # health checks in cloud providers require the use of port 80
location {{ $cfg.healthzUrl }} { location {{ $cfg.HealthzURL }} {
access_log off; access_log off;
return 200; return 200;
} }

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -18,6 +18,7 @@ package ingress
import ( import (
"k8s.io/contrib/ingress/controllers/nginx/nginx/auth" "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/ipwhitelist"
"k8s.io/contrib/ingress/controllers/nginx/nginx/ratelimit" "k8s.io/contrib/ingress/controllers/nginx/nginx/ratelimit"
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite" "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 // Location describes an NGINX location
type Location struct { type Location struct {
Path string Path string
IsDefBackend bool IsDefBackend bool
Upstream Upstream Upstream Upstream
Auth auth.Nginx Auth auth.Nginx
RateLimit ratelimit.RateLimit RateLimit ratelimit.RateLimit
Redirect rewrite.Redirect Redirect rewrite.Redirect
SecureUpstream bool SecureUpstream bool
Whitelist ipwhitelist.SourceRange Whitelist ipwhitelist.SourceRange
EnableCORS bool EnableCORS bool
ExternalAuthURL authreq.Auth
} }
// LocationByPath sorts location by path // LocationByPath sorts location by path

View file

@ -71,17 +71,21 @@ func NewManager(kubeClient *client.Client) *Manager {
ngx := &Manager{ ngx := &Manager{
ConfigFile: "/etc/nginx/nginx.conf", ConfigFile: "/etc/nginx/nginx.conf",
defCfg: config.NewDefault(), defCfg: config.NewDefault(),
defResolver: strings.Join(getDNSServers(), " "),
reloadLock: &sync.Mutex{}, reloadLock: &sync.Mutex{},
reloadRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.1, 1), 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.createCertsDir(config.SSLDirectory)
ngx.sslDHParam = ngx.SearchDHParamFile(config.SSLDirectory) ngx.sslDHParam = ngx.SearchDHParamFile(config.SSLDirectory)
var onChange func() var onChange func()
onChange = func() { onChange = func() {
template, err := ngx_template.NewTemplate(tmplPath, onChange) template, err := ngx_template.NewTemplate(tmplPath, onChange)
if err != nil { if err != nil {

View file

@ -18,6 +18,7 @@ package template
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp" "regexp"
@ -49,6 +50,7 @@ var (
return true return true
}, },
"buildLocation": buildLocation, "buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"buildProxyPass": buildProxyPass, "buildProxyPass": buildProxyPass,
"buildRateLimitZones": buildRateLimitZones, "buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit, "buildRateLimit": buildRateLimit,
@ -193,6 +195,22 @@ func buildLocation(input interface{}) string {
return path 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 // buildProxyPass produces the proxy pass string, if the ingress has redirects
// (specified through the ingress.kubernetes.io/rewrite-to annotation) // (specified through the ingress.kubernetes.io/rewrite-to annotation)
// If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will // If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will

View file

@ -38,15 +38,14 @@ const (
) )
// getDNSServers returns the list of nameservers located in the file /etc/resolv.conf // 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") file, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil { if err != nil {
return []string{} return nameservers, err
} }
// Lines of the form "nameserver 1.2.3.4" accumulate. // Lines of the form "nameserver 1.2.3.4" accumulate.
nameservers := []string{}
lines := strings.Split(string(file), "\n") lines := strings.Split(string(file), "\n")
for l := range lines { for l := range lines {
trimmed := strings.TrimSpace(lines[l]) trimmed := strings.TrimSpace(lines[l])
@ -63,7 +62,7 @@ func getDNSServers() []string {
} }
glog.V(3).Infof("nameservers to use: %v", nameservers) 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. // 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.CustomHTTPErrors = ngx.filterErrors(cErrors)
cfgDefault.SkipAccessLogURLs = cSkipUrls cfgDefault.SkipAccessLogURLs = cSkipUrls
// no custom resolver means use the system resolver
if cfgDefault.Resolver == "" {
cfgDefault.Resolver = ngx.defResolver
}
return cfgDefault return cfgDefault
} }

View file

@ -89,3 +89,13 @@ func TestManagerReadConfigStringNothing(t *testing.T) {
t.Errorf("Failed to set string value true actual='%s' expected='%s'", configNginx.SSLSessionTimeout, exp) 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")
}
}