From 09e6aabce414a213444c8e77424f635f62b1854f Mon Sep 17 00:00:00 2001 From: Cole Mickens Date: Thu, 2 Feb 2017 02:22:44 -0800 Subject: [PATCH 1/2] Add auth-signin annotation --- controllers/nginx/Makefile | 5 ++- .../rootfs/etc/nginx/template/nginx.tmpl | 8 ++++ core/pkg/ingress/annotations/authreq/main.go | 23 ++++++---- .../ingress/annotations/authreq/main_test.go | 29 +++++++----- examples/README.md | 2 +- examples/external-auth/README.md | 45 +++++++++++++++++++ examples/external-auth/dashboard.ingress.yaml | 27 +++++++++++ examples/external-auth/deployment.yaml | 43 ++++++++++++++++++ 8 files changed, 158 insertions(+), 24 deletions(-) create mode 100644 examples/external-auth/README.md create mode 100644 examples/external-auth/dashboard.ingress.yaml create mode 100644 examples/external-auth/deployment.yaml diff --git a/controllers/nginx/Makefile b/controllers/nginx/Makefile index 8bee35692..8805e2d2a 100644 --- a/controllers/nginx/Makefile +++ b/controllers/nginx/Makefile @@ -6,6 +6,7 @@ BUILDTAGS= RELEASE?=0.9.0-beta.2 PREFIX?=gcr.io/google_containers/nginx-ingress-controller GOOS?=linux +DOCKER?=gcloud docker -- REPO_INFO=$(shell git config --get remote.origin.url) @@ -21,10 +22,10 @@ build: clean -o rootfs/nginx-ingress-controller ${PKG}/pkg/cmd/controller container: build - docker build --pull -t $(PREFIX):$(RELEASE) rootfs + $(DOCKER) build --pull -t $(PREFIX):$(RELEASE) rootfs push: container - gcloud docker -- push $(PREFIX):$(RELEASE) + $(DOCKER) push $(PREFIX):$(RELEASE) fmt: @echo "+ $@" diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index 3ada5ba40..8a6a644a9 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -244,6 +244,8 @@ http { {{ end }} {{ if not (empty $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 }} proxy_set_header Host $host; proxy_pass_request_headers on; @@ -269,6 +271,10 @@ http { auth_request {{ $authPath }}; {{ 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)) }} # enforce ssl on server side if ($pass_access_scheme = http) { @@ -315,6 +321,8 @@ http { proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $pass_port; 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 # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ diff --git a/core/pkg/ingress/annotations/authreq/main.go b/core/pkg/ingress/annotations/authreq/main.go index 560a73868..91c56b9f6 100644 --- a/core/pkg/ingress/annotations/authreq/main.go +++ b/core/pkg/ingress/annotations/authreq/main.go @@ -28,16 +28,18 @@ import ( 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" + authURL = "ingress.kubernetes.io/auth-url" + authSigninURL = "ingress.kubernetes.io/auth-signin" + authMethod = "ingress.kubernetes.io/auth-method" + authBody = "ingress.kubernetes.io/auth-send-body" ) // External returns external authentication configuration for an Ingress rule type External struct { - URL string `json:"url"` - Method string `json:"method"` - SendBody bool `json:"sendBody"` + URL string `json:"url"` + SigninURL string `json:"signinUrl"` + Method string `json:"method"` + SendBody bool `json:"sendBody"` } var ( @@ -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") } + signin, _ := parser.GetStringAnnotation(authSigninURL, ing) + ur, err := url.Parse(str) if err != nil { return nil, err @@ -100,8 +104,9 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) { sb, _ := parser.GetBoolAnnotation(authBody, ing) return &External{ - URL: str, - Method: m, - SendBody: sb, + URL: str, + SigninURL: signin, + Method: m, + SendBody: sb, }, nil } diff --git a/core/pkg/ingress/annotations/authreq/main_test.go b/core/pkg/ingress/annotations/authreq/main_test.go index 696d8bdc0..75cd6d2b7 100644 --- a/core/pkg/ingress/annotations/authreq/main_test.go +++ b/core/pkg/ingress/annotations/authreq/main_test.go @@ -67,23 +67,25 @@ func TestAnnotations(t *testing.T) { ing.SetAnnotations(data) tests := []struct { - title string - url string - method string - sendBody bool - expErr bool + title string + url string + signinURL 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}, + {"empty", "", "", "", false, true}, + {"no scheme", "bar", "bar", "", false, true}, + {"invalid host", "http://", "http://", "", false, true}, + {"invalid host (multiple dots)", "http://foo..bar.com", "http://foo..bar.com", "", false, true}, + {"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", "http://foo.com/external-auth", "POST", true, false}, + {"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", true, false}, } for _, test := range tests { data[authURL] = test.url + data[authSigninURL] = test.signinURL data[authBody] = fmt.Sprintf("%v", test.sendBody) data[authMethod] = fmt.Sprintf("%v", test.method) @@ -101,6 +103,9 @@ func TestAnnotations(t *testing.T) { if u.URL != test.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 { t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.method, u.Method) } diff --git a/examples/README.md b/examples/README.md index 330e82a0a..3e6a8d19f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -57,7 +57,7 @@ SNI + TCP | TLS routing based on SNI hostname | nginx | Advanced Name | Description | Platform | Complexity Level -----| ----------- | ---------- | ---------------- Basic auth | password protect your website | nginx | Intermediate -External auth plugin | defer to an external auth service | nginx | Intermediate +[External auth plugin](external-auth/README.md) | defer to an external auth service | nginx | Intermediate ## Protocols diff --git a/examples/external-auth/README.md b/examples/external-auth/README.md new file mode 100644 index 000000000..3cd0da91d --- /dev/null +++ b/examples/external-auth/README.md @@ -0,0 +1,45 @@ +## 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. `export DOMAIN="somedomain.io"` +2. Install `nginx-ingress`. If you haven't already, consider using `helm`: `$ helm install stable/nginx-ingress` +3. Make sure you have a TLS cert added as a Secret named `ingress-tls` that corresponds to your `$DOMAIN`. + +### Deploy: `oauth2_proxy` + +This is the Deployment object that runs `oauth2_proxy`. + diff --git a/examples/external-auth/dashboard.ingress.yaml b/examples/external-auth/dashboard.ingress.yaml new file mode 100644 index 000000000..60ef4df84 --- /dev/null +++ b/examples/external-auth/dashboard.ingress.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: dashboard + namespace: kube-system + annotations: + "ingress.kubernetes.io/auth-url": "https://$host/oauth2/auth" + "ingress.kubernetes.io/auth-signin": "https://$host/oauth2/sign_in" +spec: + tls: + - secretName: 'foo-secret-966' + hosts: + - 'foo-966.bar.com' + rules: + - host: 'foo-966.bar.com' + http: + paths: + - path: / + backend: + serviceName: kubernetes-dashboard + servicePort: 80 + - parh: /oauth2 + backend: + serviceName: oauth2proxy + servicePort: 4180 + diff --git a/examples/external-auth/deployment.yaml b/examples/external-auth/deployment.yaml new file mode 100644 index 000000000..5329d90eb --- /dev/null +++ b/examples/external-auth/deployment.yaml @@ -0,0 +1,43 @@ + +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: oauth2-proxy + labels: + k8s-app: oauth2proxy +spec: + replicas: 1 + template: + metadata: + labels: + k8s-app: oauth2proxy + spec: + volumes: + - name: oauth2proxy-secret + secret: + secretName: oauth2proxy + containers: + - name: oauth2proxy + image: docker.io/colemickens/oauth2_proxy:latest + imagePullPolicy: Always + ports: + - containerPort: 4180 + args: + - --provider=github + - --email-domain=* +--- + +apiVersion: v1 +kind: Service +metadata: + labels: + k8s-app: oauth2proxy + name: oauth2proxy +spec: + ports: + - name: http + port: 4180 + protocol: TCP + targetPort: 4180 + selector: + k8s-app: oauth2proxy From 681af2d8d6194cafbb7c71a4c5961f85a39faad2 Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Wed, 8 Mar 2017 20:58:34 -0300 Subject: [PATCH 2/2] Add example using github oauth application --- examples/README.md | 2 +- examples/external-auth/README.md | 45 ------------- examples/external-auth/dashboard.ingress.yaml | 27 -------- examples/external-auth/deployment.yaml | 43 ------------- examples/external-auth/nginx/README.md | 64 +++++++++++++++++++ .../nginx/dashboard-ingress.yaml | 38 +++++++++++ .../external-auth/nginx/oauth2-proxy.yaml | 56 ++++++++++++++++ 7 files changed, 159 insertions(+), 116 deletions(-) delete mode 100644 examples/external-auth/README.md delete mode 100644 examples/external-auth/dashboard.ingress.yaml delete mode 100644 examples/external-auth/deployment.yaml create mode 100644 examples/external-auth/nginx/README.md create mode 100644 examples/external-auth/nginx/dashboard-ingress.yaml create mode 100644 examples/external-auth/nginx/oauth2-proxy.yaml diff --git a/examples/README.md b/examples/README.md index 3e6a8d19f..69288dd45 100644 --- a/examples/README.md +++ b/examples/README.md @@ -57,7 +57,7 @@ SNI + TCP | TLS routing based on SNI hostname | nginx | Advanced Name | Description | Platform | Complexity Level -----| ----------- | ---------- | ---------------- Basic auth | password protect your website | nginx | Intermediate -[External auth plugin](external-auth/README.md) | 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 diff --git a/examples/external-auth/README.md b/examples/external-auth/README.md deleted file mode 100644 index 3cd0da91d..000000000 --- a/examples/external-auth/README.md +++ /dev/null @@ -1,45 +0,0 @@ -## 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. `export DOMAIN="somedomain.io"` -2. Install `nginx-ingress`. If you haven't already, consider using `helm`: `$ helm install stable/nginx-ingress` -3. Make sure you have a TLS cert added as a Secret named `ingress-tls` that corresponds to your `$DOMAIN`. - -### Deploy: `oauth2_proxy` - -This is the Deployment object that runs `oauth2_proxy`. - diff --git a/examples/external-auth/dashboard.ingress.yaml b/examples/external-auth/dashboard.ingress.yaml deleted file mode 100644 index 60ef4df84..000000000 --- a/examples/external-auth/dashboard.ingress.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: dashboard - namespace: kube-system - annotations: - "ingress.kubernetes.io/auth-url": "https://$host/oauth2/auth" - "ingress.kubernetes.io/auth-signin": "https://$host/oauth2/sign_in" -spec: - tls: - - secretName: 'foo-secret-966' - hosts: - - 'foo-966.bar.com' - rules: - - host: 'foo-966.bar.com' - http: - paths: - - path: / - backend: - serviceName: kubernetes-dashboard - servicePort: 80 - - parh: /oauth2 - backend: - serviceName: oauth2proxy - servicePort: 4180 - diff --git a/examples/external-auth/deployment.yaml b/examples/external-auth/deployment.yaml deleted file mode 100644 index 5329d90eb..000000000 --- a/examples/external-auth/deployment.yaml +++ /dev/null @@ -1,43 +0,0 @@ - -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: oauth2-proxy - labels: - k8s-app: oauth2proxy -spec: - replicas: 1 - template: - metadata: - labels: - k8s-app: oauth2proxy - spec: - volumes: - - name: oauth2proxy-secret - secret: - secretName: oauth2proxy - containers: - - name: oauth2proxy - image: docker.io/colemickens/oauth2_proxy:latest - imagePullPolicy: Always - ports: - - containerPort: 4180 - args: - - --provider=github - - --email-domain=* ---- - -apiVersion: v1 -kind: Service -metadata: - labels: - k8s-app: oauth2proxy - name: oauth2proxy -spec: - ports: - - name: http - port: 4180 - protocol: TCP - targetPort: 4180 - selector: - k8s-app: oauth2proxy diff --git a/examples/external-auth/nginx/README.md b/examples/external-auth/nginx/README.md new file mode 100644 index 000000000..d61e395d4 --- /dev/null +++ b/examples/external-auth/nginx/README.md @@ -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 `` +- OAUTH2_PROXY_CLIENT_SECRET with the github `` +- 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` diff --git a/examples/external-auth/nginx/dashboard-ingress.yaml b/examples/external-auth/nginx/dashboard-ingress.yaml new file mode 100644 index 000000000..642e38f5b --- /dev/null +++ b/examples/external-auth/nginx/dashboard-ingress.yaml @@ -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__ diff --git a/examples/external-auth/nginx/oauth2-proxy.yaml b/examples/external-auth/nginx/oauth2-proxy.yaml new file mode 100644 index 000000000..1735f4690 --- /dev/null +++ b/examples/external-auth/nginx/oauth2-proxy.yaml @@ -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: + - name: OAUTH2_PROXY_CLIENT_SECRET + value: + # 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