Repository: kubernetes/ingress-nginx
Pull request #3506 (2018-12-03 16:06:19)

Improve the external authorization concept from opt-in to secure-by-default 
**What this PR does / why we need it**:
Assuming the cluster uses an external authorization service, 
currently activation of external authorization is per ingress rule, that means the annotation `auth-url` needs to be added to every service that is publicly available. We call this security by opt-in.
To improve this concept to security-by-default be propose to add a parameter to the configmap which takes care of authorizing every request automatically. 

The existing `no-auth-locations` is used to manually whitelist services, the new code creates the needed internal `location` and adds a sub-auth request to every regular location, just as happens with the `auth-url` annotation


k8s-ci-robot: Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please follow instructions at <https://git.k8s.io/community/CLA.md#the-contributor-license-agreement> to sign the CLA.**

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify.  Thanks.

---

- If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address.  Check your existing CLA data and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
- If you signed the CLA as a corporation, please sign in with your organization's credentials at <https://identity.linuxfoundation.org/projects/cncf> to be authorized.
- If you have done the above and are still having issues with the CLA being reported as unsigned, please email the CNCF helpdesk: helpdesk@rt.linuxfoundation.org

<!-- need_sender_cla -->

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
	
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/3506#" title="Author self-approved">haassConnyun</a>*
To fully approve this pull request, please assign additional approvers.
We suggest the following additional approver: **antoineco**

If they are not already assigned, you can assign the PR to them by writing `/assign @antoineco` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/ingress-nginx/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["antoineco"]} -->
haassConnyun: CLA signed
okryvoshapka-connyun: CLA signed
haassConnyun: /assign @antoineco
haassConnyun: /shrug
okryvoshapka-connyun: PR raised from issue #3276 
haassConnyun: /unassign @antoineco
/unshrug
was following the bot's advise, apparently not always wanted. sorry.
k8s-ci-robot: @haassConnyun: ¯\\\_(ツ)\_/¯

<details>

In response to [this](https://github.com/kubernetes/ingress-nginx/pull/3506#issuecomment-444410904):

>/unassign @antoineco
>/unshrug
>was following the bot's advise, apparently not always wanted. sorry.


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
haassConnyun: @aledbf sorry to bump this - is there something we should do to get a review or a comment?
ElvinEfendi: > Locations that should not get authenticated can be listed using `no-auth-locations`. See [no-auth-locations](#no-auth-locations)

@haassConnyun no-auth-locations is a global setting, it won't let users to whitelist a path for a given service. Do you have any plan to implement something so that users can opt out of this global auth setting per ingress? Maybe something like `auth-url-off` annotation where when it's set to true it will not enable external auth.
ElvinEfendi: @haassConnyun thanks for the PR! I've made few inline comments. On top of them I think Nginx template should not have separate rendering for global and annotation settings. When the setting is set you can create the annotation manually in the controller and make sure every (except the whitelisted ones) ingress has it. That way you'd not need to touch `nginx.tmpl`.
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/3506#" title="Author self-approved">haassConnyun</a>*
To fully approve this pull request, please assign additional approvers.
We suggest the following additional approver: **bowei**

If they are not already assigned, you can assign the PR to them by writing `/assign @bowei` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/ingress-nginx/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["bowei"]} -->
okryvoshapka-connyun: Hi @ElvinEfendi, 

As requested changes were made:
1. It is possible now to disable the globally configured auth with Ingress annotation `enable-global-auth: false`.
2. The proper priority is kept: `auth-url` annotation has higher priority then `global-auth-url` configration if both are set.
3. URL verification logic was extracted into separate function and used in annotation `auth-url` and controller's config `global-auth-url`.
4. Modification to the `tmpl.conf` was minimized, the existing solution for external auth was reused. 

To assist you, I have drawn 2 tables:
- When enable-global-auth is enabled (default)

A | auth-url | global-auth-url | enable-global-auth | expected behavior 
-- | - | - | - | - 
 1 | /a | /b | true | Request goes to /a 
 2 | /a | none | true | Request goes to /a 
 3 | none | /b | true | Request goes to /b 
 4 | none | none | true | No external auth at all 

- When enable-global-auth is disabled

 B | auth-url | global-auth-url | enable-global-auth | expected behavior 
-- | - | - | - | - 
 1 | /a | /b | false | Request goes to /a 
 2 | /a | none | false | Request goes to /a 
 3 | none | /b | false | No external auth at all 
 4 | none | none | false | No external auth at all 



ElvinEfendi: @okryvoshapka-connyun thanks!
Can you rebase with master?
okryvoshapka-connyun: I am working on it right now.
okryvoshapka-connyun: Hi @ElvinEfendi

Rebased, tested, green. 
Sorry for delay, faced with some e2e-test environment and go-formatting issues after rebase.
okryvoshapka-connyun: Hi @ElvinEfendi ,

I hope the change is not too dramatic.
So the solution was extended to be complete and fully mirror the existing external auth solution. 
Nothing was changed from previously implemented logic, only minor renaming.
It contains all the addons implemented in external auth via Ingress Rule, naming concept was kept:

| Annotation | ConfigMap setting |
| - | - | 
| auth-method | global-auth-method |
| auth-signin | global-auth-signin |
| auth-response-headers  | global-auth-response-headers |
| auth-request-redirect  | global-auth-request-redirect |
| auth-snippet  | global-auth-snippet |

The switch between 2 solutions is full, so or you are using location specific external auth, or go with global external auth (including all addons in both cases). For example, it is not possible to have `global-auth-url: /foo` and `global-auth-method: POST` but for one location change only the `nginx.ingress.kubernetes.io/auth-method: GET`.

Modification of `tmpl.conf` template was reduced to minimum, existing external auth solution was reused to maximum.

Can I get another feedback round?

P.S. I am working on rebase.


ElvinEfendi: @aledbf any reason why do you not wanna merge this? (I saw you added do-not-merge above)
okryvoshapka-connyun: Hi @ElvinEfendi and @aledbf 
Can we assist you somehow to speed up the te review of this PR?
aledbf: @okryvoshapka-connyun apologies for the delay. Please squash the commits.
After we merge the webhook PR I'll review this one
k8s-ci-robot: @haassConnyun: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

Patch:
diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md
old mode 100644
new mode 100755
index 0be636124..830ab4f77
--- a/docs/user-guide/nginx-configuration/annotations.md
+++ b/docs/user-guide/nginx-configuration/annotations.md
@@ -27,6 +27,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
 |[nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream](#client-certificate-authentication)|"true" or "false"|
 |[nginx.ingress.kubernetes.io/auth-url](#external-authentication)|string|
 |[nginx.ingress.kubernetes.io/auth-snippet](#external-authentication)|string|
+|[nginx.ingress.kubernetes.io/enable-global-auth](#external-authentication)|"true" or "false"|
 |[nginx.ingress.kubernetes.io/backend-protocol](#backend-protocol)|string|HTTP,HTTPS,GRPC,GRPCS,AJP|
 |[nginx.ingress.kubernetes.io/canary](#canary)|"true" or "false"|
 |[nginx.ingress.kubernetes.io/canary-by-header](#canary)|string|
@@ -389,6 +390,14 @@ nginx.ingress.kubernetes.io/auth-snippet: |
 !!! example
     Please check the [external-auth](../../examples/auth/external-auth/README.md) example.
 
+#### Global External Authentication
+
+By default the controller redirects all requests to an existing service that provides authentication if `global-auth-url` is set in the NGINX ConfigMap. If you want to disable this behavior for that ingress, you can use `enable-global-auth: "false"` in the NGINX ConfigMap.
+`nginx.ingress.kubernetes.io/enable-global-auth`:
+   indicates if GlobalExternalAuth configuration should be applied or not to this Ingress rule. Default values is set to `"true"`.
+
+!!! note For more information please see [global-auth-url](./configmap.md#global-auth-url).
+
 ### Rate limiting
 
 These annotations define a limit on the connections that can be opened by a single client IP address.
diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md
old mode 100644
new mode 100755
index d13d49149..37974a67c
--- a/docs/user-guide/nginx-configuration/configmap.md
+++ b/docs/user-guide/nginx-configuration/configmap.md
@@ -152,6 +152,12 @@ The following table shows a configuration option's name, type, and the default v
 |[limit-req-status-code](#limit-req-status-code)|int|503|
 |[limit-conn-status-code](#limit-conn-status-code)|int|503|
 |[no-tls-redirect-locations](#no-tls-redirect-locations)|string|"/.well-known/acme-challenge"|
+|[global-auth-url](#global-auth-url)|string|""|
+|[global-auth-method](#global-auth-method)|string|""|
+|[global-auth-signin](#global-auth-signin)|string|""|
+|[global-auth-response-headers](#global-auth-response-headers)|string|""|
+|[global-auth-request-redirect](#global-auth-request-redirect)|string|""|
+|[global-auth-snippet](#global-auth-snippet)|string|""|
 |[no-auth-locations](#no-auth-locations)|string|"/.well-known/acme-challenge"|
 |[block-cidrs](#block-cidrs)|[]string|""|
 |[block-user-agents](#block-user-agents)|[]string|""|
@@ -864,6 +870,45 @@ Sets the [status code to return in response to rejected connections](http://ngin
 A comma-separated list of locations on which http requests will never get redirected to their https counterpart.
 _**default:**_ "/.well-known/acme-challenge"
 
+## global-auth-url
+
+A url to an existing service that provides authentication for all the locations.
+Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-url`.
+Locations that should not get authenticated can be listed using `no-auth-locations` See [no-auth-locations](#no-auth-locations). In addition, each service can be excluded from authentication via annotation `enable-global-auth` set to "false".
+_**default:**_ ""
+
+_References:_ [https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md#external-authentication](https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md#external-authentication)
+
+## global-auth-method
+
+A HTTP method to use for an existing service that provides authentication for all the locations.
+Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-method`.
+_**default:**_ ""
+
+## global-auth-signin
+
+Sets the location of the error page for an existing service that provides authentication for all the locations.
+Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-signin`.
+_**default:**_ ""
+
+## global-auth-response-headers
+
+Sets the headers to pass to backend once authentication request completes. Applied to all the locations.
+Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-response-headers`.
+_**default:**_ ""
+
+## global-auth-request-redirect
+
+Sets the X-Auth-Request-Redirect header value. Applied to all the locations.
+Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-request-redirect`.
+_**default:**_ ""
+
+## global-auth-snippet
+
+Sets a custom snippet to use with external authentication. Applied to all the locations.
+Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-request-redirect`.
+_**default:**_ ""
+
 ## no-auth-locations
 
 A comma-separated list of locations that should not get authenticated.
diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go
old mode 100644
new mode 100755
index b803ebedc..32ebe6c05
--- a/internal/ingress/annotations/annotations.go
+++ b/internal/ingress/annotations/annotations.go
@@ -30,6 +30,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/alias"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/auth"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
+	"k8s.io/ingress-nginx/internal/ingress/annotations/authreqglobal"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/authtls"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/backendprotocol"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/clientbodybuffersize"
@@ -83,6 +84,7 @@ type Ingress struct {
 	//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
 	Denied             *string
 	ExternalAuth       authreq.Config
+	EnableGlobalAuth   bool
 	HTTP2PushPreload   bool
 	Proxy              proxy.Config
 	RateLimit          ratelimit.Config
@@ -127,6 +129,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
 			"CustomHTTPErrors":     customhttperrors.NewParser(cfg),
 			"DefaultBackend":       defaultbackend.NewParser(cfg),
 			"ExternalAuth":         authreq.NewParser(cfg),
+			"EnableGlobalAuth":     authreqglobal.NewParser(cfg),
 			"HTTP2PushPreload":     http2pushpreload.NewParser(cfg),
 			"Proxy":                proxy.NewParser(cfg),
 			"RateLimit":            ratelimit.NewParser(cfg),
diff --git a/internal/ingress/annotations/authreq/main.go b/internal/ingress/annotations/authreq/main.go
old mode 100644
new mode 100755
index 93666d1aa..4721c8d5e
--- a/internal/ingress/annotations/authreq/main.go
+++ b/internal/ingress/annotations/authreq/main.go
@@ -17,6 +17,7 @@ limitations under the License.
 package authreq
 
 import (
+	"fmt"
 	"net/url"
 	"regexp"
 	"strings"
@@ -84,7 +85,8 @@ var (
 	headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
 )
 
-func validMethod(method string) bool {
+// ValidMethod checks is the provided string a valid HTTP method
+func ValidMethod(method string) bool {
 	if len(method) == 0 {
 		return false
 	}
@@ -97,7 +99,8 @@ func validMethod(method string) bool {
 	return false
 }
 
-func validHeader(header string) bool {
+// ValidHeader checks is the provided string satisfies the header's name regex
+func ValidHeader(header string) bool {
 	return headerRegexp.Match([]byte(header))
 }
 
@@ -119,22 +122,13 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
 		return nil, err
 	}
 
-	authURL, err := url.Parse(urlString)
-	if err != nil {
-		return nil, err
-	}
-	if authURL.Scheme == "" {
-		return nil, ing_errors.NewLocationDenied("url scheme is empty")
-	}
-	if authURL.Host == "" {
-		return nil, ing_errors.NewLocationDenied("url host is empty")
-	}
-	if strings.Contains(authURL.Host, "..") {
-		return nil, ing_errors.NewLocationDenied("invalid url host")
+	authURL, message := ParseStringToURL(urlString)
+	if authURL == nil {
+		return nil, ing_errors.NewLocationDenied(message)
 	}
 
 	authMethod, _ := parser.GetStringAnnotation("auth-method", ing)
-	if len(authMethod) != 0 && !validMethod(authMethod) {
+	if len(authMethod) != 0 && !ValidMethod(authMethod) {
 		return nil, ing_errors.NewLocationDenied("invalid HTTP method")
 	}
 
@@ -156,7 +150,7 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
 		for _, header := range harr {
 			header = strings.TrimSpace(header)
 			if len(header) > 0 {
-				if !validHeader(header) {
+				if !ValidHeader(header) {
 					return nil, ing_errors.NewLocationDenied("invalid headers list")
 				}
 				responseHeaders = append(responseHeaders, header)
@@ -176,3 +170,22 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
 		AuthSnippet:     authSnippet,
 	}, nil
 }
+
+// ParseStringToURL parses the provided string into URL and returns error
+// message in case of failure
+func ParseStringToURL(input string) (*url.URL, string) {
+
+	parsedURL, err := url.Parse(input)
+	if err != nil {
+		return nil, fmt.Sprintf("%v is not a valid URL: %v", input, err)
+	}
+	if parsedURL.Scheme == "" {
+		return nil, "url scheme is empty."
+	} else if parsedURL.Host == "" {
+		return nil, "url host is empty."
+	} else if strings.Contains(parsedURL.Host, "..") {
+		return nil, "invalid url host."
+	}
+	return parsedURL, ""
+
+}
diff --git a/internal/ingress/annotations/authreqglobal/main.go b/internal/ingress/annotations/authreqglobal/main.go
new file mode 100755
index 000000000..e8c976f3c
--- /dev/null
+++ b/internal/ingress/annotations/authreqglobal/main.go
@@ -0,0 +1,45 @@
+/*
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package authreqglobal
+
+import (
+	extensions "k8s.io/api/extensions/v1beta1"
+
+	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
+	"k8s.io/ingress-nginx/internal/ingress/resolver"
+)
+
+type authReqGlobal struct {
+	r resolver.Resolver
+}
+
+// NewParser creates a new authentication request annotation parser
+func NewParser(r resolver.Resolver) parser.IngressAnnotation {
+	return authReqGlobal{r}
+}
+
+// ParseAnnotations parses the annotations contained in the ingress
+// rule used to enable or disable global external authentication
+func (a authReqGlobal) Parse(ing *extensions.Ingress) (interface{}, error) {
+
+	enableGlobalAuth, err := parser.GetBoolAnnotation("enable-global-auth", ing)
+	if err != nil {
+		enableGlobalAuth = true
+	}
+
+	return enableGlobalAuth, nil
+}
diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go
old mode 100644
new mode 100755
index 961c862bb..f06856406
--- a/internal/ingress/controller/config/config.go
+++ b/internal/ingress/controller/config/config.go
@@ -563,6 +563,11 @@ type Configuration struct {
 	// should not get authenticated
 	NoAuthLocations string `json:"no-auth-locations"`
 
+	// GlobalExternalAuth indicates the access to all locations requires
+	// authentication using an external provider
+	// +optional
+	GlobalExternalAuth GlobalExternalAuth `json:"global-external-auth"`
+
 	// DisableLuaRestyWAF disables lua-resty-waf globally regardless
 	// of whether there's an ingress that has enabled the WAF using annotation
 	DisableLuaRestyWAF bool `json:"disable-lua-resty-waf"`
@@ -592,11 +597,13 @@ func NewDefault() Configuration {
 	defBlockEntity := make([]string, 0)
 	defNginxStatusIpv4Whitelist := make([]string, 0)
 	defNginxStatusIpv6Whitelist := make([]string, 0)
+	defResponseHeaders := make([]string, 0)
 
 	defIPCIDR = append(defIPCIDR, "0.0.0.0/0")
 	defNginxStatusIpv4Whitelist = append(defNginxStatusIpv4Whitelist, "127.0.0.1")
 	defNginxStatusIpv6Whitelist = append(defNginxStatusIpv6Whitelist, "::1")
 	defProxyDeadlineDuration := time.Duration(5) * time.Second
+	defGlobalExternalAuth := GlobalExternalAuth{"", "", "", "", append(defResponseHeaders, ""), "", ""}
 
 	cfg := Configuration{
 		AllowBackendServerHeader:         false,
@@ -715,6 +722,7 @@ func NewDefault() Configuration {
 		SyslogPort:                   514,
 		NoTLSRedirectLocations:       "/.well-known/acme-challenge",
 		NoAuthLocations:              "/.well-known/acme-challenge",
+		GlobalExternalAuth:           defGlobalExternalAuth,
 	}
 
 	if klog.V(5) {
@@ -772,3 +780,16 @@ type ListenPorts struct {
 	Default  int
 	SSLProxy int
 }
+
+// GlobalExternalAuth describe external authentication configuration for the
+// NGINX Ingress controller
+type GlobalExternalAuth struct {
+	URL string `json:"url"`
+	// Host contains the hostname defined in the URL
+	Host            string   `json:"host"`
+	SigninURL       string   `json:"signinUrl"`
+	Method          string   `json:"method"`
+	ResponseHeaders []string `json:"responseHeaders,omitempty"`
+	RequestRedirect string   `json:"requestRedirect"`
+	AuthSnippet     string   `json:"authSnippet"`
+}
diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go
old mode 100644
new mode 100755
index f0fc95d83..4af63c17f
--- a/internal/ingress/controller/controller.go
+++ b/internal/ingress/controller/controller.go
@@ -98,6 +98,8 @@ type Configuration struct {
 	ValidationWebhook         string
 	ValidationWebhookCertPath string
 	ValidationWebhookKeyPath  string
+
+	GlobalExternalAuth *ngx_config.GlobalExternalAuth
 }
 
 // GetPublishService returns the Service used to set the load-balancer status of Ingresses.
@@ -1151,6 +1153,7 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
 	loc.ConfigurationSnippet = anns.ConfigurationSnippet
 	loc.CorsConfig = anns.CorsConfig
 	loc.ExternalAuth = anns.ExternalAuth
+	loc.EnableGlobalAuth = anns.EnableGlobalAuth
 	loc.HTTP2PushPreload = anns.HTTP2PushPreload
 	loc.Proxy = anns.Proxy
 	loc.RateLimit = anns.RateLimit
diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go
old mode 100644
new mode 100755
diff --git a/internal/ingress/controller/store/store.go b/internal/ingress/controller/store/store.go
old mode 100644
new mode 100755
diff --git a/internal/ingress/controller/store/store_test.go b/internal/ingress/controller/store/store_test.go
old mode 100644
new mode 100755
diff --git a/internal/ingress/controller/template/configmap.go b/internal/ingress/controller/template/configmap.go
old mode 100644
new mode 100755
index 0d0954849..6eaafc42c
--- a/internal/ingress/controller/template/configmap.go
+++ b/internal/ingress/controller/template/configmap.go
@@ -29,27 +29,34 @@ import (
 	"github.com/mitchellh/mapstructure"
 
 	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
 	"k8s.io/ingress-nginx/internal/ingress/controller/config"
 	ing_net "k8s.io/ingress-nginx/internal/net"
 	"k8s.io/ingress-nginx/internal/runtime"
 )
 
 const (
-	customHTTPErrors         = "custom-http-errors"
-	skipAccessLogUrls        = "skip-access-log-urls"
-	whitelistSourceRange     = "whitelist-source-range"
-	proxyRealIPCIDR          = "proxy-real-ip-cidr"
-	bindAddress              = "bind-address"
-	httpRedirectCode         = "http-redirect-code"
-	blockCIDRs               = "block-cidrs"
-	blockUserAgents          = "block-user-agents"
-	blockReferers            = "block-referers"
-	proxyStreamResponses     = "proxy-stream-responses"
-	hideHeaders              = "hide-headers"
-	nginxStatusIpv4Whitelist = "nginx-status-ipv4-whitelist"
-	nginxStatusIpv6Whitelist = "nginx-status-ipv6-whitelist"
-	proxyHeaderTimeout       = "proxy-protocol-header-timeout"
-	workerProcesses          = "worker-processes"
+	customHTTPErrors          = "custom-http-errors"
+	skipAccessLogUrls         = "skip-access-log-urls"
+	whitelistSourceRange      = "whitelist-source-range"
+	proxyRealIPCIDR           = "proxy-real-ip-cidr"
+	bindAddress               = "bind-address"
+	httpRedirectCode          = "http-redirect-code"
+	blockCIDRs                = "block-cidrs"
+	blockUserAgents           = "block-user-agents"
+	blockReferers             = "block-referers"
+	proxyStreamResponses      = "proxy-stream-responses"
+	hideHeaders               = "hide-headers"
+	nginxStatusIpv4Whitelist  = "nginx-status-ipv4-whitelist"
+	nginxStatusIpv6Whitelist  = "nginx-status-ipv6-whitelist"
+	proxyHeaderTimeout        = "proxy-protocol-header-timeout"
+	workerProcesses           = "worker-processes"
+	globalAuthURL             = "global-auth-url"
+	globalAuthMethod          = "global-auth-method"
+	globalAuthSignin          = "global-auth-signin"
+	globalAuthResponseHeaders = "global-auth-response-headers"
+	globalAuthRequestRedirect = "global-auth-request-redirect"
+	globalAuthSnippet         = "global-auth-snippet"
 )
 
 var (
@@ -77,6 +84,7 @@ func ReadConfig(src map[string]string) config.Configuration {
 	blockCIDRList := make([]string, 0)
 	blockUserAgentList := make([]string, 0)
 	blockRefererList := make([]string, 0)
+	responseHeaders := make([]string, 0)
 
 	if val, ok := conf[customHTTPErrors]; ok {
 		delete(conf, customHTTPErrors)
@@ -150,6 +158,74 @@ func ReadConfig(src map[string]string) config.Configuration {
 		}
 	}
 
+	// Verify that the configured global external authorization URL is parsable as URL. if not, set the default value
+	if val, ok := conf[globalAuthURL]; ok {
+		delete(conf, globalAuthURL)
+
+		authURL, message := authreq.ParseStringToURL(val)
+		if authURL == nil {
+			klog.Warningf("Global auth location denied - %v.", message)
+		} else {
+			to.GlobalExternalAuth.URL = val
+			to.GlobalExternalAuth.Host = authURL.Hostname()
+		}
+	}
+
+	// Verify that the configured global external authorization method is a valid HTTP method. if not, set the default value
+	if val, ok := conf[globalAuthMethod]; ok {
+		delete(conf, globalAuthMethod)
+
+		if len(val) != 0 && !authreq.ValidMethod(val) {
+			klog.Warningf("Global auth location denied - %v.", "invalid HTTP method")
+		} else {
+			to.GlobalExternalAuth.Method = val
+		}
+	}
+
+	// Verify that the configured global external authorization error page is set and valid. if not, set the default value
+	if val, ok := conf[globalAuthSignin]; ok {
+		delete(conf, globalAuthSignin)
+
+		signinURL, _ := authreq.ParseStringToURL(val)
+		if signinURL == nil {
+			klog.Warningf("Global auth location denied - %v.", "global-auth-signin setting is undefined and will not be set")
+		} else {
+			to.GlobalExternalAuth.SigninURL = val
+		}
+	}
+
+	// Verify that the configured global external authorization response headers are valid. if not, set the default value
+	if val, ok := conf[globalAuthResponseHeaders]; ok {
+		delete(conf, globalAuthResponseHeaders)
+
+		if len(val) != 0 {
+			harr := strings.Split(val, ",")
+			for _, header := range harr {
+				header = strings.TrimSpace(header)
+				if len(header) > 0 {
+					if !authreq.ValidHeader(header) {
+						klog.Warningf("Global auth location denied - %v.", "invalid headers list")
+					} else {
+						responseHeaders = append(responseHeaders, header)
+					}
+				}
+			}
+		}
+		to.GlobalExternalAuth.ResponseHeaders = responseHeaders
+	}
+
+	if val, ok := conf[globalAuthRequestRedirect]; ok {
+		delete(conf, globalAuthRequestRedirect)
+
+		to.GlobalExternalAuth.RequestRedirect = val
+	}
+
+	if val, ok := conf[globalAuthSnippet]; ok {
+		delete(conf, globalAuthSnippet)
+
+		to.GlobalExternalAuth.AuthSnippet = val
+	}
+
 	// Verify that the configured timeout is parsable as a duration. if not, set the default value
 	if val, ok := conf[proxyHeaderTimeout]; ok {
 		delete(conf, proxyHeaderTimeout)
diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go
old mode 100644
new mode 100755
index 2183715c1..ae750d2e6
--- a/internal/ingress/controller/template/template.go
+++ b/internal/ingress/controller/template/template.go
@@ -131,6 +131,7 @@ var (
 		"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
 		"buildLocation":              buildLocation,
 		"buildAuthLocation":          buildAuthLocation,
+		"shouldApplyGlobalAuth":      shouldApplyGlobalAuth,
 		"buildAuthResponseHeaders":   buildAuthResponseHeaders,
 		"buildProxyPass":             buildProxyPass,
 		"filterRateLimits":           filterRateLimits,
@@ -397,14 +398,14 @@ func buildLocation(input interface{}, enforceRegex bool) string {
 	return path
 }
 
-func buildAuthLocation(input interface{}) string {
+func buildAuthLocation(input interface{}, globalExternalAuthURL string) string {
 	location, ok := input.(*ingress.Location)
 	if !ok {
 		klog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
 		return ""
 	}
 
-	if location.ExternalAuth.URL == "" {
+	if (location.ExternalAuth.URL == "") && (!shouldApplyGlobalAuth(input, globalExternalAuthURL)) {
 		return ""
 	}
 
@@ -414,19 +415,29 @@ func buildAuthLocation(input interface{}) string {
 	return fmt.Sprintf("/_external-auth-%v", str)
 }
 
-func buildAuthResponseHeaders(input interface{}) []string {
+// shouldApplyGlobalAuth returns true only in case when ExternalAuth.URL is not set and
+// GlobalExternalAuth is set and enabled
+func shouldApplyGlobalAuth(input interface{}, globalExternalAuthURL string) bool {
 	location, ok := input.(*ingress.Location)
-	res := []string{}
 	if !ok {
 		klog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
-		return res
 	}
 
-	if len(location.ExternalAuth.ResponseHeaders) == 0 {
+	if (location.ExternalAuth.URL == "") && (globalExternalAuthURL != "") && (location.EnableGlobalAuth) {
+		return true
+	}
+
+	return false
+}
+
+func buildAuthResponseHeaders(headers []string) []string {
+	res := []string{}
+
+	if len(headers) == 0 {
 		return res
 	}
 
-	for i, h := range location.ExternalAuth.ResponseHeaders {
+	for i, h := range headers {
 		hvar := strings.ToLower(h)
 		hvar = strings.NewReplacer("-", "_").Replace(hvar)
 		res = append(res, fmt.Sprintf("auth_request_set $authHeader%v $upstream_http_%v;", i, hvar))
diff --git a/internal/ingress/types.go b/internal/ingress/types.go
old mode 100644
new mode 100755
index 5483914f2..54afa9a18
--- a/internal/ingress/types.go
+++ b/internal/ingress/types.go
@@ -246,6 +246,9 @@ type Location struct {
 	// authentication using an external provider
 	// +optional
 	ExternalAuth authreq.Config `json:"externalAuth,omitempty"`
+	// EnableGlobalAuth indicates if the access to this location requires
+	// authentication using an external provider defined in controller's config
+	EnableGlobalAuth bool `json:"enableGlobalAuth"`
 	// HTTP2PushPreload allows to configure the HTTP2 Push Preload from backend
 	// original location.
 	// +optional
diff --git a/internal/ingress/types_equals.go b/internal/ingress/types_equals.go
old mode 100644
new mode 100755
index f838bb0aa..d3ec2b2ab
--- a/internal/ingress/types_equals.go
+++ b/internal/ingress/types_equals.go
@@ -352,6 +352,9 @@ func (l1 *Location) Equal(l2 *Location) bool {
 	if !(&l1.ExternalAuth).Equal(&l2.ExternalAuth) {
 		return false
 	}
+	if l1.EnableGlobalAuth != l2.EnableGlobalAuth {
+		return false
+	}
 	if l1.HTTP2PushPreload != l2.HTTP2PushPreload {
 		return false
 	}
diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl
old mode 100644
new mode 100755
index 2f7b66e4b..4125d7ae4
--- a/rootfs/etc/nginx/template/nginx.tmpl
+++ b/rootfs/etc/nginx/template/nginx.tmpl
@@ -894,7 +894,13 @@ stream {
         {{ range $location := $server.Locations }}
         {{ $path := buildLocation $location $enforceRegex }}
         {{ $proxySetHeader := proxySetHeader $location }}
-        {{ $authPath := buildAuthLocation $location }}
+        {{ $authPath := buildAuthLocation $location $all.Cfg.GlobalExternalAuth.URL }}
+        {{ $applyGlobalAuth := shouldApplyGlobalAuth $location $all.Cfg.GlobalExternalAuth.URL }}
+
+        {{ $externalAuth := $location.ExternalAuth }}
+        {{ if eq $applyGlobalAuth true }}
+        {{ $externalAuth = $all.Cfg.GlobalExternalAuth }}
+        {{ end }}
 
         {{ if not (empty $location.Rewrite.AppRoot)}}
         if ($uri = /) {
@@ -915,13 +921,13 @@ stream {
             proxy_set_header            Content-Length "";
             proxy_set_header            X-Forwarded-Proto "";
 
-            {{ if $location.ExternalAuth.Method }}
-            proxy_method                {{ $location.ExternalAuth.Method }};
+            {{ if $externalAuth.Method }}
+            proxy_method                {{ $externalAuth.Method }};
             proxy_set_header            X-Original-URI          $request_uri;
             proxy_set_header            X-Scheme                $pass_access_scheme;
             {{ end }}
 
-            proxy_set_header            Host                    {{ $location.ExternalAuth.Host }};
+            proxy_set_header            Host                    {{ $externalAuth.Host }};
             proxy_set_header            X-Original-URL          $scheme://$http_host$request_uri;
             proxy_set_header            X-Original-Method       $request_method;
             proxy_set_header            X-Sent-From             "nginx-ingress-controller";
@@ -932,8 +938,8 @@ stream {
             proxy_set_header            X-Forwarded-For        $the_real_ip;
             {{ end }}
 
-            {{ if $location.ExternalAuth.RequestRedirect }}
-            proxy_set_header            X-Auth-Request-Redirect {{ $location.ExternalAuth.RequestRedirect }};
+            {{ if $externalAuth.RequestRedirect }}
+            proxy_set_header            X-Auth-Request-Redirect {{ $externalAuth.RequestRedirect }};
             {{ else }}
             proxy_set_header            X-Auth-Request-Redirect $request_uri;
             {{ end }}
@@ -963,15 +969,16 @@ stream {
             proxy_set_header ssl-client-issuer-dn   $ssl_client_i_dn;
             {{ end }}
 
-            {{ if not (empty $location.ExternalAuth.AuthSnippet) }}
-            {{ $location.ExternalAuth.AuthSnippet }}
+            {{ if not (empty $externalAuth.AuthSnippet) }}
+            {{ $externalAuth.AuthSnippet }}
             {{ end }}
 
-            set $target {{ $location.ExternalAuth.URL }};
+            set $target {{ $externalAuth.URL }};
             proxy_pass $target;
         }
         {{ end }}
 
+
         location {{ $path }} {
             {{ $ing := (getIngressInformation $location.Ingress $server.Hostname $location.Path) }}
             set $namespace      "{{ $ing.Namespace }}";
@@ -1125,14 +1132,14 @@ stream {
             auth_request        {{ $authPath }};
             auth_request_set    $auth_cookie $upstream_http_set_cookie;
             add_header          Set-Cookie $auth_cookie;
-            {{- range $line := buildAuthResponseHeaders $location }}
+            {{- range $line := buildAuthResponseHeaders $externalAuth.ResponseHeaders }}
             {{ $line }}
             {{- end }}
             {{ end }}
 
-            {{ if $location.ExternalAuth.SigninURL }}
+            {{ if $externalAuth.SigninURL }}
             set_escape_uri $escaped_request_uri $request_uri;
-            error_page 401 = {{ buildAuthSignURL $location.ExternalAuth.SigninURL }};
+            error_page 401 = {{ buildAuthSignURL $externalAuth.SigninURL }};
             {{ end }}
 
             {{ if $location.BasicDigestAuth.Secured }}


Test patch:
diff --git a/internal/ingress/annotations/authreq/main_test.go b/internal/ingress/annotations/authreq/main_test.go
old mode 100644
new mode 100755
index c9aebc678..767ae53fc
--- a/internal/ingress/annotations/authreq/main_test.go
+++ b/internal/ingress/annotations/authreq/main_test.go
@@ -18,6 +18,7 @@ package authreq
 
 import (
 	"fmt"
+	"net/url"
 	"reflect"
 	"testing"
 
@@ -178,3 +179,38 @@ func TestHeaderAnnotations(t *testing.T) {
 		}
 	}
 }
+
+func TestParseStringToURL(t *testing.T) {
+	validURL := "http://bar.foo.com/external-auth"
+	validParsedURL, _ := url.Parse(validURL)
+
+	tests := []struct {
+		title   string
+		url     string
+		message string
+		parsed  *url.URL
+		expErr  bool
+	}{
+		{"empty", "", "url scheme is empty.", nil, true},
+		{"no scheme", "bar", "url scheme is empty.", nil, true},
+		{"invalid host", "http://", "url host is empty.", nil, true},
+		{"invalid host (multiple dots)", "http://foo..bar.com", "invalid url host.", nil, true},
+		{"valid URL", validURL, "", validParsedURL, false},
+	}
+
+	for _, test := range tests {
+
+		i, err := ParseStringToURL(test.url)
+		if test.expErr {
+			if err != test.message {
+				t.Errorf("%v: expected error \"%v\" but \"%v\" was returned", test.title, test.message, err)
+			}
+			continue
+		}
+
+		if i.String() != test.parsed.String() {
+			t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.parsed, i)
+		}
+	}
+
+}
diff --git a/internal/ingress/annotations/authreqglobal/main_test.go b/internal/ingress/annotations/authreqglobal/main_test.go
new file mode 100755
index 000000000..4f686c05a
--- /dev/null
+++ b/internal/ingress/annotations/authreqglobal/main_test.go
@@ -0,0 +1,82 @@
+/*
+Copyright 2015 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package authreqglobal
+
+import (
+	"testing"
+
+	api "k8s.io/api/core/v1"
+	extensions "k8s.io/api/extensions/v1beta1"
+	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
+	"k8s.io/ingress-nginx/internal/ingress/resolver"
+
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+func buildIngress() *extensions.Ingress {
+	defaultBackend := extensions.IngressBackend{
+		ServiceName: "default-backend",
+		ServicePort: intstr.FromInt(80),
+	}
+
+	return &extensions.Ingress{
+		ObjectMeta: meta_v1.ObjectMeta{
+			Name:      "foo",
+			Namespace: api.NamespaceDefault,
+		},
+		Spec: extensions.IngressSpec{
+			Backend: &extensions.IngressBackend{
+				ServiceName: "default-backend",
+				ServicePort: intstr.FromInt(80),
+			},
+			Rules: []extensions.IngressRule{
+				{
+					Host: "foo.bar.com",
+					IngressRuleValue: extensions.IngressRuleValue{
+						HTTP: &extensions.HTTPIngressRuleValue{
+							Paths: []extensions.HTTPIngressPath{
+								{
+									Path:    "/foo",
+									Backend: defaultBackend,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func TestAnnotation(t *testing.T) {
+	ing := buildIngress()
+
+	data := map[string]string{}
+	data[parser.GetAnnotationWithPrefix("auth-url")] = "http://foo.com/external-auth"
+	data[parser.GetAnnotationWithPrefix("enable-global-auth")] = "false"
+	ing.SetAnnotations(data)
+
+	i, _ := NewParser(&resolver.Mock{}).Parse(ing)
+	u, ok := i.(bool)
+	if !ok {
+		t.Errorf("expected a Config type")
+	}
+	if u {
+		t.Errorf("Expected false but returned true")
+	}
+}
diff --git a/internal/ingress/controller/template/configmap_test.go b/internal/ingress/controller/template/configmap_test.go
old mode 100644
new mode 100755
index 328169bdc..0c1bc8e58
--- a/internal/ingress/controller/template/configmap_test.go
+++ b/internal/ingress/controller/template/configmap_test.go
@@ -153,3 +153,121 @@ func TestMergeConfigMapToStruct(t *testing.T) {
 		t.Errorf("unexpected diff: (-got +want)\n%s", diff)
 	}
 }
+
+func TestGlobalExternalAuthURLParsing(t *testing.T) {
+	errorURL := ""
+	validURL := "http://bar.foo.com/external-auth"
+
+	testCases := map[string]struct {
+		url    string
+		expect string
+	}{
+		"no scheme":                    {"bar", errorURL},
+		"invalid host":                 {"http://", errorURL},
+		"invalid host (multiple dots)": {"http://foo..bar.com", errorURL},
+		"valid URL":                    {validURL, validURL},
+	}
+
+	for n, tc := range testCases {
+		cfg := ReadConfig(map[string]string{"global-auth-url": tc.url})
+		if cfg.GlobalExternalAuth.URL != tc.expect {
+			t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", n, tc.expect, cfg.GlobalExternalAuth.URL)
+		}
+	}
+}
+
+func TestGlobalExternalAuthMethodParsing(t *testing.T) {
+	testCases := map[string]struct {
+		method string
+		expect string
+	}{
+		"invalid method": {"FOO", ""},
+		"valid method":   {"POST", "POST"},
+	}
+
+	for n, tc := range testCases {
+		cfg := ReadConfig(map[string]string{"global-auth-method": tc.method})
+		if cfg.GlobalExternalAuth.Method != tc.expect {
+			t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", n, tc.expect, cfg.GlobalExternalAuth.Method)
+		}
+	}
+}
+
+func TestGlobalExternalAuthSigninParsing(t *testing.T) {
+	errorURL := ""
+	validURL := "http://bar.foo.com/auth-error-page"
+
+	testCases := map[string]struct {
+		signin string
+		expect string
+	}{
+		"no scheme":                    {"bar", errorURL},
+		"invalid host":                 {"http://", errorURL},
+		"invalid host (multiple dots)": {"http://foo..bar.com", errorURL},
+		"valid URL":                    {validURL, validURL},
+	}
+
+	for n, tc := range testCases {
+		cfg := ReadConfig(map[string]string{"global-auth-signin": tc.signin})
+		if cfg.GlobalExternalAuth.SigninURL != tc.expect {
+			t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", n, tc.expect, cfg.GlobalExternalAuth.SigninURL)
+		}
+	}
+}
+
+func TestGlobalExternalAuthResponseHeadersParsing(t *testing.T) {
+	testCases := map[string]struct {
+		headers string
+		expect  []string
+	}{
+		"single header":                 {"h1", []string{"h1"}},
+		"nothing":                       {"", []string{}},
+		"spaces":                        {"  ", []string{}},
+		"two headers":                   {"1,2", []string{"1", "2"}},
+		"two headers and empty entries": {",1,,2,", []string{"1", "2"}},
+		"header with spaces":            {"1 2", []string{}},
+		"header with other bad symbols": {"1+2", []string{}},
+	}
+
+	for n, tc := range testCases {
+		cfg := ReadConfig(map[string]string{"global-auth-response-headers": tc.headers})
+
+		if !reflect.DeepEqual(cfg.GlobalExternalAuth.ResponseHeaders, tc.expect) {
+			t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", n, tc.expect, cfg.GlobalExternalAuth.ResponseHeaders)
+		}
+	}
+}
+
+func TestGlobalExternalAuthRequestRedirectParsing(t *testing.T) {
+	testCases := map[string]struct {
+		requestRedirect string
+		expect          string
+	}{
+		"empty":                  {"", ""},
+		"valid request redirect": {"http://foo.com/redirect-me", "http://foo.com/redirect-me"},
+	}
+
+	for n, tc := range testCases {
+		cfg := ReadConfig(map[string]string{"global-auth-request-redirect": tc.requestRedirect})
+		if cfg.GlobalExternalAuth.RequestRedirect != tc.expect {
+			t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", n, tc.expect, cfg.GlobalExternalAuth.RequestRedirect)
+		}
+	}
+}
+
+func TestGlobalExternalAuthSnippetParsing(t *testing.T) {
+	testCases := map[string]struct {
+		authSnippet string
+		expect      string
+	}{
+		"empty":        {"", ""},
+		"auth snippet": {"proxy_set_header My-Custom-Header 42;", "proxy_set_header My-Custom-Header 42;"},
+	}
+
+	for n, tc := range testCases {
+		cfg := ReadConfig(map[string]string{"global-auth-snippet": tc.authSnippet})
+		if cfg.GlobalExternalAuth.AuthSnippet != tc.expect {
+			t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", n, tc.expect, cfg.GlobalExternalAuth.AuthSnippet)
+		}
+	}
+}
diff --git a/internal/ingress/controller/template/template_test.go b/internal/ingress/controller/template/template_test.go
old mode 100644
new mode 100755
index e5fa6c5c1..5ddc0eef1
--- a/internal/ingress/controller/template/template_test.go
+++ b/internal/ingress/controller/template/template_test.go
@@ -283,51 +283,106 @@ func TestBuildProxyPass(t *testing.T) {
 func TestBuildAuthLocation(t *testing.T) {
 	invalidType := &ingress.Ingress{}
 	expected := ""
-	actual := buildAuthLocation(invalidType)
+	actual := buildAuthLocation(invalidType, "")
 
 	if !reflect.DeepEqual(expected, actual) {
 		t.Errorf("Expected '%v' but returned '%v'", expected, actual)
 	}
 
 	authURL := "foo.com/auth"
+	globalAuthURL := "foo.com/global-auth"
 
 	loc := &ingress.Location{
 		ExternalAuth: authreq.Config{
 			URL: authURL,
 		},
-		Path: "/cat",
+		Path:             "/cat",
+		EnableGlobalAuth: true,
 	}
 
-	str := buildAuthLocation(loc)
-
 	encodedAuthURL := strings.Replace(base64.URLEncoding.EncodeToString([]byte(loc.Path)), "=", "", -1)
-	expected = fmt.Sprintf("/_external-auth-%v", encodedAuthURL)
+	externalAuthPath := fmt.Sprintf("/_external-auth-%v", encodedAuthURL)
 
-	if str != expected {
-		t.Errorf("Expected \n'%v'\nbut returned \n'%v'", expected, str)
+	testCases := []struct {
+		title                    string
+		authURL                  string
+		globalAuthURL            string
+		enableglobalExternalAuth bool
+		expected                 string
+	}{
+		{"authURL, globalAuthURL and enabled", authURL, globalAuthURL, true, externalAuthPath},
+		{"authURL, globalAuthURL and disabled", authURL, globalAuthURL, false, externalAuthPath},
+		{"authURL, empty globalAuthURL and enabled", authURL, "", true, externalAuthPath},
+		{"authURL, empty globalAuthURL and disabled", authURL, "", false, externalAuthPath},
+		{"globalAuthURL and enabled", "", globalAuthURL, true, externalAuthPath},
+		{"globalAuthURL and disabled", "", globalAuthURL, false, ""},
+		{"all empty and enabled", "", "", true, ""},
+		{"all empty and disabled", "", "", false, ""},
 	}
-}
 
-func TestBuildAuthResponseHeaders(t *testing.T) {
-	invalidType := &ingress.Ingress{}
-	expected := []string{}
-	actual := buildAuthResponseHeaders(invalidType)
+	for _, testCase := range testCases {
+		loc.ExternalAuth.URL = testCase.authURL
+		loc.EnableGlobalAuth = testCase.enableglobalExternalAuth
 
-	if !reflect.DeepEqual(expected, actual) {
-		t.Errorf("Expected '%v' but returned '%v'", expected, actual)
+		str := buildAuthLocation(loc, testCase.globalAuthURL)
+		if str != testCase.expected {
+			t.Errorf("%v: expected '%v' but returned '%v'", testCase.title, testCase.expected, str)
+		}
 	}
+}
+
+func TestShouldApplyGlobalAuth(t *testing.T) {
+
+	authURL := "foo.com/auth"
+	globalAuthURL := "foo.com/global-auth"
 
 	loc := &ingress.Location{
-		ExternalAuth: authreq.Config{ResponseHeaders: []string{"h1", "H-With-Caps-And-Dashes"}},
+		ExternalAuth: authreq.Config{
+			URL: authURL,
+		},
+		Path:             "/cat",
+		EnableGlobalAuth: true,
+	}
+
+	testCases := []struct {
+		title                    string
+		authURL                  string
+		globalAuthURL            string
+		enableglobalExternalAuth bool
+		expected                 bool
+	}{
+		{"authURL, globalAuthURL and enabled", authURL, globalAuthURL, true, false},
+		{"authURL, globalAuthURL and disabled", authURL, globalAuthURL, false, false},
+		{"authURL, empty globalAuthURL and enabled", authURL, "", true, false},
+		{"authURL, empty globalAuthURL and disabled", authURL, "", false, false},
+		{"globalAuthURL and enabled", "", globalAuthURL, true, true},
+		{"globalAuthURL and disabled", "", globalAuthURL, false, false},
+		{"all empty and enabled", "", "", true, false},
+		{"all empty and disabled", "", "", false, false},
+	}
+
+	for _, testCase := range testCases {
+		loc.ExternalAuth.URL = testCase.authURL
+		loc.EnableGlobalAuth = testCase.enableglobalExternalAuth
+
+		result := shouldApplyGlobalAuth(loc, testCase.globalAuthURL)
+		if result != testCase.expected {
+			t.Errorf("%v: expected '%v' but returned '%v'", testCase.title, testCase.expected, result)
+		}
 	}
-	headers := buildAuthResponseHeaders(loc)
-	expected = []string{
+}
+
+func TestBuildAuthResponseHeaders(t *testing.T) {
+	externalAuthResponseHeaders := []string{"h1", "H-With-Caps-And-Dashes"}
+	expected := []string{
 		"auth_request_set $authHeader0 $upstream_http_h1;",
 		"proxy_set_header 'h1' $authHeader0;",
 		"auth_request_set $authHeader1 $upstream_http_h_with_caps_and_dashes;",
 		"proxy_set_header 'H-With-Caps-And-Dashes' $authHeader1;",
 	}
 
+	headers := buildAuthResponseHeaders(externalAuthResponseHeaders)
+
 	if !reflect.DeepEqual(expected, headers) {
 		t.Errorf("Expected \n'%v'\nbut returned \n'%v'", expected, headers)
 	}
diff --git a/test/e2e/settings/global_external_auth.go b/test/e2e/settings/global_external_auth.go
new file mode 100755
index 000000000..c3916e869
--- /dev/null
+++ b/test/e2e/settings/global_external_auth.go
@@ -0,0 +1,217 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package settings
+
+import (
+	"fmt"
+	"net/http"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"github.com/parnurzeal/gorequest"
+
+	"k8s.io/ingress-nginx/test/e2e/framework"
+)
+
+var _ = framework.IngressNginxDescribe("Global External Auth", func() {
+	f := framework.NewDefaultFramework("global-external-auth")
+
+	host := "global-external-auth"
+
+	echoServiceName := "http-svc"
+
+	globalExternalAuthURLSetting := "global-auth-url"
+
+	fooPath := "/foo"
+	barPath := "/bar"
+
+	noAuthSetting := "no-auth-locations"
+	noAuthLocations := barPath
+
+	enableGlobalExternalAuthAnnotation := "nginx.ingress.kubernetes.io/enable-global-auth"
+
+	BeforeEach(func() {
+		f.NewEchoDeployment()
+		f.NewHttpbinDeployment()
+	})
+
+	AfterEach(func() {
+	})
+
+	Context("when global external authentication is configured", func() {
+
+		BeforeEach(func() {
+			globalExternalAuthURL := fmt.Sprintf("http://httpbin.%s.svc.cluster.local:80/status/401", f.Namespace)
+
+			By("Adding an ingress rule for /foo")
+			fooIng := framework.NewSingleIngress("foo-ingress", fooPath, host, f.Namespace, echoServiceName, 80, nil)
+			f.EnsureIngress(fooIng)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring("location /foo"))
+				})
+
+			By("Adding an ingress rule for /bar")
+			barIng := framework.NewSingleIngress("bar-ingress", barPath, host, f.Namespace, echoServiceName, 80, nil)
+			f.EnsureIngress(barIng)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring("location /bar"))
+				})
+
+			By("Adding a global-auth-url to configMap")
+			f.UpdateNginxConfigMapData(globalExternalAuthURLSetting, globalExternalAuthURL)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring(globalExternalAuthURL))
+				})
+		})
+
+		It("should return status code 401 when request any protected service", func() {
+
+			By("Sending a request to protected service /foo")
+			fooResp, _, _ := gorequest.New().
+				Get(f.GetURL(framework.HTTP)+fooPath).
+				Set("Host", host).
+				End()
+			Expect(fooResp.StatusCode).Should(Equal(http.StatusUnauthorized))
+
+			By("Sending a request to protected service /bar")
+			barResp, _, _ := gorequest.New().
+				Get(f.GetURL(framework.HTTP)+barPath).
+				Set("Host", host).
+				End()
+			Expect(barResp.StatusCode).Should(Equal(http.StatusUnauthorized))
+		})
+
+		It("should return status code 200 when request whitelisted (via no-auth-locations) service and 401 when request protected service", func() {
+
+			By("Adding a no-auth-locations for /bar to configMap")
+			f.UpdateNginxConfigMapData(noAuthSetting, noAuthLocations)
+
+			By("Sending a request to protected service /foo")
+			fooResp, _, _ := gorequest.New().
+				Get(f.GetURL(framework.HTTP)+fooPath).
+				Set("Host", host).
+				End()
+			Expect(fooResp.StatusCode).Should(Equal(http.StatusUnauthorized))
+
+			By("Sending a request to whitelisted service /bar")
+			barResp, _, _ := gorequest.New().
+				Get(f.GetURL(framework.HTTP)+barPath).
+				Set("Host", host).
+				End()
+			Expect(barResp.StatusCode).Should(Equal(http.StatusOK))
+		})
+
+		It("should return status code 200 when request whitelisted (via ingress annotation) service and 401 when request protected service", func() {
+
+			By("Adding an ingress rule for /bar with annotation enable-global-auth = false")
+			annotations := map[string]string{
+				enableGlobalExternalAuthAnnotation: "false",
+			}
+			barIng := framework.NewSingleIngress("bar-ingress", barPath, host, f.Namespace, echoServiceName, 80, &annotations)
+			f.EnsureIngress(barIng)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring("location /bar"))
+				})
+
+			By("Sending a request to protected service /foo")
+			fooResp, _, _ := gorequest.New().
+				Get(f.GetURL(framework.HTTP)+fooPath).
+				Set("Host", host).
+				End()
+			Expect(fooResp.StatusCode).Should(Equal(http.StatusUnauthorized))
+
+			By("Sending a request to whitelisted service /bar")
+			barResp, _, _ := gorequest.New().
+				Get(f.GetURL(framework.HTTP)+barPath).
+				Set("Host", host).
+				End()
+			Expect(barResp.StatusCode).Should(Equal(http.StatusOK))
+		})
+
+		It(`should proxy_method method when global-auth-method is configured`, func() {
+
+			globalExternalAuthMethodSetting := "global-auth-method"
+			globalExternalAuthMethod := "GET"
+
+			By("Adding a global-auth-method to configMap")
+			f.UpdateNginxConfigMapData(globalExternalAuthMethodSetting, globalExternalAuthMethod)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring("proxy_method"))
+				})
+		})
+
+		It(`should add custom error page when global-auth-signin url is configured`, func() {
+
+			globalExternalAuthSigninSetting := "global-auth-signin"
+			globalExternalAuthSignin := "http://foo.com/global-error-page"
+
+			By("Adding a global-auth-signin to configMap")
+			f.UpdateNginxConfigMapData(globalExternalAuthSigninSetting, globalExternalAuthSignin)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring("error_page 401 = "))
+				})
+		})
+
+		It(`should add auth headers when global-auth-response-headers is configured`, func() {
+
+			globalExternalAuthResponseHeadersSetting := "global-auth-response-headers"
+			globalExternalAuthResponseHeaders := "Foo, Bar"
+
+			By("Adding a global-auth-response-headers to configMap")
+			f.UpdateNginxConfigMapData(globalExternalAuthResponseHeadersSetting, globalExternalAuthResponseHeaders)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring("auth_request_set $authHeader0 $upstream_http_foo;")) &&
+						Expect(server).Should(ContainSubstring("auth_request_set $authHeader1 $upstream_http_bar;"))
+				})
+		})
+
+		It(`should set request-redirect when global-auth-request-redirect is configured`, func() {
+
+			globalExternalAuthRequestRedirectSetting := "global-auth-request-redirect"
+			globalExternalAuthRequestRedirect := "Foo-Redirect"
+
+			By("Adding a global-auth-request-redirect to configMap")
+			f.UpdateNginxConfigMapData(globalExternalAuthRequestRedirectSetting, globalExternalAuthRequestRedirect)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring(globalExternalAuthRequestRedirect))
+				})
+		})
+
+		It(`should set snippet when global external auth is configured`, func() {
+
+			globalExternalAuthSnippetSetting := "global-auth-snippet"
+			globalExternalAuthSnippet := "proxy_set_header My-Custom-Header 42;"
+
+			By("Adding a global-auth-snippet to configMap")
+			f.UpdateNginxConfigMapData(globalExternalAuthSnippetSetting, globalExternalAuthSnippet)
+			f.WaitForNginxServer(host,
+				func(server string) bool {
+					return Expect(server).Should(ContainSubstring(globalExternalAuthSnippet))
+				})
+		})
+
+	})
+
+})