Skip to content
This repository has been archived by the owner on Apr 17, 2019. It is now read-only.

Commit

Permalink
Merge pull request #1796 from aledbf/subrequest-auth
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue

[nginx-ingress-controller] Add external authentication using auth_request

fixes #1492
  • Loading branch information
Kubernetes Submit Queue authored Oct 3, 2016
2 parents 64f13d5 + a7baeb0 commit 284bac0
Show file tree
Hide file tree
Showing 13 changed files with 537 additions and 40 deletions.
23 changes: 18 additions & 5 deletions ingress/controllers/nginx/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|


Expand Down Expand Up @@ -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.
Expand Down
25 changes: 17 additions & 8 deletions ingress/controllers/nginx/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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,
})
}
}
Expand Down
148 changes: 148 additions & 0 deletions ingress/controllers/nginx/examples/external-auth/README.md
Original file line number Diff line number Diff line change
@@ -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
```
15 changes: 15 additions & 0 deletions ingress/controllers/nginx/examples/external-auth/ingress.yaml
Original file line number Diff line number Diff line change
@@ -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: /
16 changes: 9 additions & 7 deletions ingress/controllers/nginx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 25 additions & 4 deletions ingress/controllers/nginx/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit 284bac0

Please sign in to comment.