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

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

Merged
merged 1 commit into from
Oct 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are we using ra?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

controller.go (last commit)

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