Merge pull request #410 from aledbf/colemickens-signin-url

Add support for "signin url"
This commit is contained in:
Manuel Alejandro de Brito Fontes 2017-03-09 11:21:42 -03:00 committed by GitHub
commit a5f8af70bf
8 changed files with 201 additions and 24 deletions

View file

@ -6,6 +6,7 @@ BUILDTAGS=
RELEASE?=0.9.0-beta.2 RELEASE?=0.9.0-beta.2
PREFIX?=gcr.io/google_containers/nginx-ingress-controller PREFIX?=gcr.io/google_containers/nginx-ingress-controller
GOOS?=linux GOOS?=linux
DOCKER?=gcloud docker --
REPO_INFO=$(shell git config --get remote.origin.url) REPO_INFO=$(shell git config --get remote.origin.url)
@ -21,10 +22,10 @@ build: clean
-o rootfs/nginx-ingress-controller ${PKG}/pkg/cmd/controller -o rootfs/nginx-ingress-controller ${PKG}/pkg/cmd/controller
container: build container: build
docker build --pull -t $(PREFIX):$(RELEASE) rootfs $(DOCKER) build --pull -t $(PREFIX):$(RELEASE) rootfs
push: container push: container
gcloud docker -- push $(PREFIX):$(RELEASE) $(DOCKER) push $(PREFIX):$(RELEASE)
fmt: fmt:
@echo "+ $@" @echo "+ $@"

View file

@ -246,6 +246,8 @@ http {
{{ end }} {{ end }}
{{ if not (empty $location.ExternalAuth.Method) }} {{ if not (empty $location.ExternalAuth.Method) }}
proxy_method {{ $location.ExternalAuth.Method }}; proxy_method {{ $location.ExternalAuth.Method }};
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Scheme $pass_access_scheme;
{{ end }} {{ end }}
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_pass_request_headers on; proxy_pass_request_headers on;
@ -271,6 +273,10 @@ http {
auth_request {{ $authPath }}; auth_request {{ $authPath }};
{{ end }} {{ end }}
{{ if not (empty $location.ExternalAuth.SigninURL) }}
error_page 401 = {{ $location.ExternalAuth.SigninURL }};
{{ end }}
{{ if (or $location.Redirect.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Redirect.SSLRedirect)) }} {{ if (or $location.Redirect.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Redirect.SSLRedirect)) }}
# enforce ssl on server side # enforce ssl on server side
if ($pass_access_scheme = http) { if ($pass_access_scheme = http) {
@ -317,6 +323,8 @@ http {
proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $pass_port; proxy_set_header X-Forwarded-Port $pass_port;
proxy_set_header X-Forwarded-Proto $pass_access_scheme; proxy_set_header X-Forwarded-Proto $pass_access_scheme;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Scheme $pass_access_scheme;
# mitigate HTTPoxy Vulnerability # mitigate HTTPoxy Vulnerability
# https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/

View file

@ -29,6 +29,7 @@ import (
const ( const (
// external URL that provides the authentication // external URL that provides the authentication
authURL = "ingress.kubernetes.io/auth-url" authURL = "ingress.kubernetes.io/auth-url"
authSigninURL = "ingress.kubernetes.io/auth-signin"
authMethod = "ingress.kubernetes.io/auth-method" authMethod = "ingress.kubernetes.io/auth-method"
authBody = "ingress.kubernetes.io/auth-send-body" authBody = "ingress.kubernetes.io/auth-send-body"
) )
@ -36,6 +37,7 @@ const (
// External returns external authentication configuration for an Ingress rule // External returns external authentication configuration for an Ingress rule
type External struct { type External struct {
URL string `json:"url"` URL string `json:"url"`
SigninURL string `json:"signinUrl"`
Method string `json:"method"` Method string `json:"method"`
SendBody bool `json:"sendBody"` SendBody bool `json:"sendBody"`
} }
@ -77,6 +79,8 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
return nil, ing_errors.NewLocationDenied("an empty string is not a valid URL") return nil, ing_errors.NewLocationDenied("an empty string is not a valid URL")
} }
signin, _ := parser.GetStringAnnotation(authSigninURL, ing)
ur, err := url.Parse(str) ur, err := url.Parse(str)
if err != nil { if err != nil {
return nil, err return nil, err
@ -101,6 +105,7 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
return &External{ return &External{
URL: str, URL: str,
SigninURL: signin,
Method: m, Method: m,
SendBody: sb, SendBody: sb,
}, nil }, nil

View file

@ -69,21 +69,23 @@ func TestAnnotations(t *testing.T) {
tests := []struct { tests := []struct {
title string title string
url string url string
signinURL string
method string method string
sendBody bool sendBody bool
expErr bool expErr bool
}{ }{
{"empty", "", "", false, true}, {"empty", "", "", "", false, true},
{"no scheme", "bar", "", false, true}, {"no scheme", "bar", "bar", "", false, true},
{"invalid host", "http://", "", false, true}, {"invalid host", "http://", "http://", "", false, true},
{"invalid host (multiple dots)", "http://foo..bar.com", "", false, true}, {"invalid host (multiple dots)", "http://foo..bar.com", "http://foo..bar.com", "", false, true},
{"valid URL", "http://bar.foo.com/external-auth", "", false, false}, {"valid URL", "http://bar.foo.com/external-auth", "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", "http://foo.com/external-auth", "POST", true, false},
{"valid URL - send body", "http://foo.com/external-auth", "GET", true, false}, {"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", true, false},
} }
for _, test := range tests { for _, test := range tests {
data[authURL] = test.url data[authURL] = test.url
data[authSigninURL] = test.signinURL
data[authBody] = fmt.Sprintf("%v", test.sendBody) data[authBody] = fmt.Sprintf("%v", test.sendBody)
data[authMethod] = fmt.Sprintf("%v", test.method) data[authMethod] = fmt.Sprintf("%v", test.method)
@ -101,6 +103,9 @@ func TestAnnotations(t *testing.T) {
if u.URL != test.url { if u.URL != test.url {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL) t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL)
} }
if u.SigninURL != test.signinURL {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.signinURL, u.SigninURL)
}
if u.Method != test.method { if u.Method != test.method {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.method, u.Method) t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.method, u.Method)
} }

View file

@ -57,7 +57,7 @@ SNI + TCP | TLS routing based on SNI hostname | nginx | Advanced
Name | Description | Platform | Complexity Level Name | Description | Platform | Complexity Level
-----| ----------- | ---------- | ---------------- -----| ----------- | ---------- | ----------------
Basic auth | password protect your website | nginx | Intermediate Basic auth | password protect your website | nginx | Intermediate
External auth plugin | defer to an external auth service | nginx | Intermediate [External auth plugin](external-auth/nginx/README.md) | defer to an external auth service | nginx | Intermediate
## Protocols ## Protocols

View file

@ -0,0 +1,64 @@
## External Authentication
### Overview
The `auth-url` and `auth-signin` annotations allow you to use an external
authentication provider to protect your Ingress resources.
(Note, this annotation requires `nginx-ingress-controller v0.9.0` or greater.)
### Key Detail
This functionality is enabled by deploying multiple Ingress objects for a single host.
One Ingress object has no special annotations and handles authentication.
Other Ingress objects can then be annotated in such a way that require the user to
authenticate against the first Ingress's endpoint, and can redirect `401`s to the
same endpoint.
Sample:
```
...
metadata:
name: application
annotations:
"ingress.kubernetes.io/auth-url": "https://$host/oauth2/auth"
"ingress.kubernetes.io/signin-url": "https://$host/oauth2/sign_in"
...
```
### Example: OAuth2 Proxy + Kubernetes-Dashboard
This example will show you how to deploy [`oauth2_proxy`](https://github.com/bitly/oauth2_proxy)
into a Kubernetes cluster and use it to protect the Kubernetes Dashboard.
#### Prepare:
1. Install the kubernetes dashboard
```console
kubectl create -f https://raw.githubusercontent.com/kubernetes/kops/master/addons/kubernetes-dashboard/v1.5.0.yaml
```
2. Create a custom Github OAuth application https://github.com/settings/applications/new
- Homepage URL is the FQDN in the Ingress rule, like `https://foo.bar.com`
- Authorization callback URL is the same as the base FQDN plus `/oauth2`, like `https://foo.bar.com/oauth2`
3. Configure oauth2_proxy values in the file oauth2-proxy.yaml with the values:
- OAUTH2_PROXY_CLIENT_ID with the github `<Client ID>`
- OAUTH2_PROXY_CLIENT_SECRET with the github `<Client Secret>`
- OAUTH2_PROXY_COOKIE_SECRET with value of `python -c 'import os,base64; print base64.b64encode(os.urandom(16))'`
4. Customize the contents of the file dashboard-ingress.yaml:
Replace `__INGRESS_HOST__` with a valid FQDN and `__INGRESS_SECRET__` with a Secret with a valid SSL certificate.
5. Deploy the oauth2 proxy and the ingress rules running:
```console
$ kubectl create -f oauth2-proxy.yaml,dashboard-ingress.yaml
```
Test the oauth integration accessing the configured URL, like `https://foo.bar.com`

View file

@ -0,0 +1,38 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/auth-signin: https://$host/oauth2/sign_in
ingress.kubernetes.io/auth-url: https://$host/oauth2/auth
name: external-auth-oauth2
namespace: kube-system
spec:
rules:
- host: __INGRESS_HOST__
http:
paths:
- backend:
serviceName: kubernetes-dashboard
servicePort: 80
path: /
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: oauth2-proxy
namespace: kube-system
spec:
rules:
- host: __INGRESS_HOST__
http:
paths:
- backend:
serviceName: oauth2-proxy
servicePort: 4180
path: /oauth2
tls:
- hosts:
- __INGRESS_HOST__
secretName: __INGRESS_SECRET__

View file

@ -0,0 +1,56 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
k8s-app: oauth2-proxy
name: oauth2-proxy
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
k8s-app: oauth2-proxy
template:
metadata:
labels:
k8s-app: oauth2-proxy
spec:
containers:
- args:
- --provider=github
- --email-domain=*
- --upstream=file:///dev/null
- --http-address=0.0.0.0:4180
# Register a new application
# https://github.com/settings/applications/new
env:
- name: OAUTH2_PROXY_CLIENT_ID
value: <Client ID>
- name: OAUTH2_PROXY_CLIENT_SECRET
value: <Client Secret>
# python -c 'import os,base64; print base64.b64encode(os.urandom(16))'
- name: OAUTH2_PROXY_COOKIE_SECRET
value: SECRET
image: docker.io/colemickens/oauth2_proxy:latest
imagePullPolicy: Always
name: oauth2-proxy
ports:
- containerPort: 4180
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: oauth2-proxy
name: oauth2-proxy
spec:
ports:
- name: http
port: 4180
protocol: TCP
targetPort: 4180
selector:
k8s-app: oauth2-proxy