Merge pull request #410 from aledbf/colemickens-signin-url
Add support for "signin url"
This commit is contained in:
commit
a5f8af70bf
8 changed files with 201 additions and 24 deletions
|
@ -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 "+ $@"
|
||||||
|
|
|
@ -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/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
64
examples/external-auth/nginx/README.md
Normal file
64
examples/external-auth/nginx/README.md
Normal 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`
|
38
examples/external-auth/nginx/dashboard-ingress.yaml
Normal file
38
examples/external-auth/nginx/dashboard-ingress.yaml
Normal 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__
|
56
examples/external-auth/nginx/oauth2-proxy.yaml
Normal file
56
examples/external-auth/nginx/oauth2-proxy.yaml
Normal 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
|
Loading…
Reference in a new issue