Added Global External Authentication settings to configmap parameters incl. addons
This commit is contained in:
parent
b4f2880ee6
commit
8cc9afe8ee
20 changed files with 819 additions and 72 deletions
9
docs/user-guide/nginx-configuration/annotations.md
Normal file → Executable file
9
docs/user-guide/nginx-configuration/annotations.md
Normal file → Executable file
|
@ -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-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-url](#external-authentication)|string|
|
||||||
|[nginx.ingress.kubernetes.io/auth-snippet](#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/backend-protocol](#backend-protocol)|string|HTTP,HTTPS,GRPC,GRPCS,AJP|
|
||||||
|[nginx.ingress.kubernetes.io/canary](#canary)|"true" or "false"|
|
|[nginx.ingress.kubernetes.io/canary](#canary)|"true" or "false"|
|
||||||
|[nginx.ingress.kubernetes.io/canary-by-header](#canary)|string|
|
|[nginx.ingress.kubernetes.io/canary-by-header](#canary)|string|
|
||||||
|
@ -389,6 +390,14 @@ nginx.ingress.kubernetes.io/auth-snippet: |
|
||||||
!!! example
|
!!! example
|
||||||
Please check the [external-auth](../../examples/auth/external-auth/README.md) 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 ssl-redirect: "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
|
### Rate limiting
|
||||||
|
|
||||||
These annotations define a limit on the connections that can be opened by a single client IP address.
|
These annotations define a limit on the connections that can be opened by a single client IP address.
|
||||||
|
|
45
docs/user-guide/nginx-configuration/configmap.md
Normal file → Executable file
45
docs/user-guide/nginx-configuration/configmap.md
Normal file → Executable file
|
@ -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-req-status-code](#limit-req-status-code)|int|503|
|
||||||
|[limit-conn-status-code](#limit-conn-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"|
|
|[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"|
|
|[no-auth-locations](#no-auth-locations)|string|"/.well-known/acme-challenge"|
|
||||||
|[block-cidrs](#block-cidrs)|[]string|""|
|
|[block-cidrs](#block-cidrs)|[]string|""|
|
||||||
|[block-user-agents](#block-user-agents)|[]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.
|
A comma-separated list of locations on which http requests will never get redirected to their https counterpart.
|
||||||
_**default:**_ "/.well-known/acme-challenge"
|
_**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
|
## no-auth-locations
|
||||||
|
|
||||||
A comma-separated list of locations that should not get authenticated.
|
A comma-separated list of locations that should not get authenticated.
|
||||||
|
|
3
internal/ingress/annotations/annotations.go
Normal file → Executable file
3
internal/ingress/annotations/annotations.go
Normal file → Executable file
|
@ -30,6 +30,7 @@ import (
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/alias"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/alias"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/auth"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/auth"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
|
"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/authtls"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/backendprotocol"
|
"k8s.io/ingress-nginx/internal/ingress/annotations/backendprotocol"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/annotations/clientbodybuffersize"
|
"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
|
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
|
||||||
Denied *string
|
Denied *string
|
||||||
ExternalAuth authreq.Config
|
ExternalAuth authreq.Config
|
||||||
|
EnableGlobalAuth bool
|
||||||
HTTP2PushPreload bool
|
HTTP2PushPreload bool
|
||||||
Proxy proxy.Config
|
Proxy proxy.Config
|
||||||
RateLimit ratelimit.Config
|
RateLimit ratelimit.Config
|
||||||
|
@ -127,6 +129,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
|
||||||
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
|
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
|
||||||
"DefaultBackend": defaultbackend.NewParser(cfg),
|
"DefaultBackend": defaultbackend.NewParser(cfg),
|
||||||
"ExternalAuth": authreq.NewParser(cfg),
|
"ExternalAuth": authreq.NewParser(cfg),
|
||||||
|
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
|
||||||
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
|
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
|
||||||
"Proxy": proxy.NewParser(cfg),
|
"Proxy": proxy.NewParser(cfg),
|
||||||
"RateLimit": ratelimit.NewParser(cfg),
|
"RateLimit": ratelimit.NewParser(cfg),
|
||||||
|
|
45
internal/ingress/annotations/authreq/main.go
Normal file → Executable file
45
internal/ingress/annotations/authreq/main.go
Normal file → Executable file
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package authreq
|
package authreq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -84,7 +85,8 @@ var (
|
||||||
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
|
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 {
|
if len(method) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -97,7 +99,8 @@ func validMethod(method string) bool {
|
||||||
return false
|
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))
|
return headerRegexp.Match([]byte(header))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,22 +122,13 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
authURL, err := url.Parse(urlString)
|
authURL, message := ParseStringToURL(urlString)
|
||||||
if err != nil {
|
if authURL == nil {
|
||||||
return nil, err
|
return nil, ing_errors.NewLocationDenied(message)
|
||||||
}
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
authMethod, _ := parser.GetStringAnnotation("auth-method", ing)
|
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")
|
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 {
|
for _, header := range harr {
|
||||||
header = strings.TrimSpace(header)
|
header = strings.TrimSpace(header)
|
||||||
if len(header) > 0 {
|
if len(header) > 0 {
|
||||||
if !validHeader(header) {
|
if !ValidHeader(header) {
|
||||||
return nil, ing_errors.NewLocationDenied("invalid headers list")
|
return nil, ing_errors.NewLocationDenied("invalid headers list")
|
||||||
}
|
}
|
||||||
responseHeaders = append(responseHeaders, header)
|
responseHeaders = append(responseHeaders, header)
|
||||||
|
@ -176,3 +170,22 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
AuthSnippet: authSnippet,
|
AuthSnippet: authSnippet,
|
||||||
}, nil
|
}, 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, ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
36
internal/ingress/annotations/authreq/main_test.go
Normal file → Executable file
36
internal/ingress/annotations/authreq/main_test.go
Normal file → Executable file
|
@ -18,6 +18,7 @@ package authreq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
45
internal/ingress/annotations/authreqglobal/main.go
Executable file
45
internal/ingress/annotations/authreqglobal/main.go
Executable file
|
@ -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
|
||||||
|
}
|
82
internal/ingress/annotations/authreqglobal/main_test.go
Executable file
82
internal/ingress/annotations/authreqglobal/main_test.go
Executable file
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
21
internal/ingress/controller/config/config.go
Normal file → Executable file
21
internal/ingress/controller/config/config.go
Normal file → Executable file
|
@ -563,6 +563,11 @@ type Configuration struct {
|
||||||
// should not get authenticated
|
// should not get authenticated
|
||||||
NoAuthLocations string `json:"no-auth-locations"`
|
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
|
// DisableLuaRestyWAF disables lua-resty-waf globally regardless
|
||||||
// of whether there's an ingress that has enabled the WAF using annotation
|
// of whether there's an ingress that has enabled the WAF using annotation
|
||||||
DisableLuaRestyWAF bool `json:"disable-lua-resty-waf"`
|
DisableLuaRestyWAF bool `json:"disable-lua-resty-waf"`
|
||||||
|
@ -592,11 +597,13 @@ func NewDefault() Configuration {
|
||||||
defBlockEntity := make([]string, 0)
|
defBlockEntity := make([]string, 0)
|
||||||
defNginxStatusIpv4Whitelist := make([]string, 0)
|
defNginxStatusIpv4Whitelist := make([]string, 0)
|
||||||
defNginxStatusIpv6Whitelist := make([]string, 0)
|
defNginxStatusIpv6Whitelist := make([]string, 0)
|
||||||
|
defResponseHeaders := make([]string, 0)
|
||||||
|
|
||||||
defIPCIDR = append(defIPCIDR, "0.0.0.0/0")
|
defIPCIDR = append(defIPCIDR, "0.0.0.0/0")
|
||||||
defNginxStatusIpv4Whitelist = append(defNginxStatusIpv4Whitelist, "127.0.0.1")
|
defNginxStatusIpv4Whitelist = append(defNginxStatusIpv4Whitelist, "127.0.0.1")
|
||||||
defNginxStatusIpv6Whitelist = append(defNginxStatusIpv6Whitelist, "::1")
|
defNginxStatusIpv6Whitelist = append(defNginxStatusIpv6Whitelist, "::1")
|
||||||
defProxyDeadlineDuration := time.Duration(5) * time.Second
|
defProxyDeadlineDuration := time.Duration(5) * time.Second
|
||||||
|
degGlobalExternalAuth := GlobalExternalAuth{"", "", "", "", append(defResponseHeaders, ""), "", ""}
|
||||||
|
|
||||||
cfg := Configuration{
|
cfg := Configuration{
|
||||||
AllowBackendServerHeader: false,
|
AllowBackendServerHeader: false,
|
||||||
|
@ -715,6 +722,7 @@ func NewDefault() Configuration {
|
||||||
SyslogPort: 514,
|
SyslogPort: 514,
|
||||||
NoTLSRedirectLocations: "/.well-known/acme-challenge",
|
NoTLSRedirectLocations: "/.well-known/acme-challenge",
|
||||||
NoAuthLocations: "/.well-known/acme-challenge",
|
NoAuthLocations: "/.well-known/acme-challenge",
|
||||||
|
GlobalExternalAuth: degGlobalExternalAuth,
|
||||||
}
|
}
|
||||||
|
|
||||||
if klog.V(5) {
|
if klog.V(5) {
|
||||||
|
@ -772,3 +780,16 @@ type ListenPorts struct {
|
||||||
Default int
|
Default int
|
||||||
SSLProxy 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"`
|
||||||
|
}
|
||||||
|
|
3
internal/ingress/controller/controller.go
Normal file → Executable file
3
internal/ingress/controller/controller.go
Normal file → Executable file
|
@ -98,6 +98,8 @@ type Configuration struct {
|
||||||
ValidationWebhook string
|
ValidationWebhook string
|
||||||
ValidationWebhookCertPath string
|
ValidationWebhookCertPath string
|
||||||
ValidationWebhookKeyPath string
|
ValidationWebhookKeyPath string
|
||||||
|
|
||||||
|
GlobalExternalAuth *ngx_config.GlobalExternalAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublishService returns the Service used to set the load-balancer status of Ingresses.
|
// 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.ConfigurationSnippet = anns.ConfigurationSnippet
|
||||||
loc.CorsConfig = anns.CorsConfig
|
loc.CorsConfig = anns.CorsConfig
|
||||||
loc.ExternalAuth = anns.ExternalAuth
|
loc.ExternalAuth = anns.ExternalAuth
|
||||||
|
loc.EnableGlobalAuth = anns.EnableGlobalAuth
|
||||||
loc.HTTP2PushPreload = anns.HTTP2PushPreload
|
loc.HTTP2PushPreload = anns.HTTP2PushPreload
|
||||||
loc.Proxy = anns.Proxy
|
loc.Proxy = anns.Proxy
|
||||||
loc.RateLimit = anns.RateLimit
|
loc.RateLimit = anns.RateLimit
|
||||||
|
|
0
internal/ingress/controller/nginx.go
Normal file → Executable file
0
internal/ingress/controller/nginx.go
Normal file → Executable file
0
internal/ingress/controller/store/store.go
Normal file → Executable file
0
internal/ingress/controller/store/store.go
Normal file → Executable file
0
internal/ingress/controller/store/store_test.go
Normal file → Executable file
0
internal/ingress/controller/store/store_test.go
Normal file → Executable file
76
internal/ingress/controller/template/configmap.go
Normal file → Executable file
76
internal/ingress/controller/template/configmap.go
Normal file → Executable file
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||||
ing_net "k8s.io/ingress-nginx/internal/net"
|
ing_net "k8s.io/ingress-nginx/internal/net"
|
||||||
"k8s.io/ingress-nginx/internal/runtime"
|
"k8s.io/ingress-nginx/internal/runtime"
|
||||||
|
@ -50,6 +51,12 @@ const (
|
||||||
nginxStatusIpv6Whitelist = "nginx-status-ipv6-whitelist"
|
nginxStatusIpv6Whitelist = "nginx-status-ipv6-whitelist"
|
||||||
proxyHeaderTimeout = "proxy-protocol-header-timeout"
|
proxyHeaderTimeout = "proxy-protocol-header-timeout"
|
||||||
workerProcesses = "worker-processes"
|
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 (
|
var (
|
||||||
|
@ -77,6 +84,7 @@ func ReadConfig(src map[string]string) config.Configuration {
|
||||||
blockCIDRList := make([]string, 0)
|
blockCIDRList := make([]string, 0)
|
||||||
blockUserAgentList := make([]string, 0)
|
blockUserAgentList := make([]string, 0)
|
||||||
blockRefererList := make([]string, 0)
|
blockRefererList := make([]string, 0)
|
||||||
|
responseHeaders := make([]string, 0)
|
||||||
|
|
||||||
if val, ok := conf[customHTTPErrors]; ok {
|
if val, ok := conf[customHTTPErrors]; ok {
|
||||||
delete(conf, customHTTPErrors)
|
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
|
// Verify that the configured timeout is parsable as a duration. if not, set the default value
|
||||||
if val, ok := conf[proxyHeaderTimeout]; ok {
|
if val, ok := conf[proxyHeaderTimeout]; ok {
|
||||||
delete(conf, proxyHeaderTimeout)
|
delete(conf, proxyHeaderTimeout)
|
||||||
|
|
118
internal/ingress/controller/template/configmap_test.go
Normal file → Executable file
118
internal/ingress/controller/template/configmap_test.go
Normal file → Executable file
|
@ -153,3 +153,121 @@ func TestMergeConfigMapToStruct(t *testing.T) {
|
||||||
t.Errorf("unexpected diff: (-got +want)\n%s", diff)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
29
internal/ingress/controller/template/template.go
Normal file → Executable file
29
internal/ingress/controller/template/template.go
Normal file → Executable file
|
@ -131,6 +131,7 @@ var (
|
||||||
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
|
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
|
||||||
"buildLocation": buildLocation,
|
"buildLocation": buildLocation,
|
||||||
"buildAuthLocation": buildAuthLocation,
|
"buildAuthLocation": buildAuthLocation,
|
||||||
|
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
|
||||||
"buildAuthResponseHeaders": buildAuthResponseHeaders,
|
"buildAuthResponseHeaders": buildAuthResponseHeaders,
|
||||||
"buildProxyPass": buildProxyPass,
|
"buildProxyPass": buildProxyPass,
|
||||||
"filterRateLimits": filterRateLimits,
|
"filterRateLimits": filterRateLimits,
|
||||||
|
@ -397,14 +398,14 @@ func buildLocation(input interface{}, enforceRegex bool) string {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAuthLocation(input interface{}) string {
|
func buildAuthLocation(input interface{}, globalExternalAuthURL string) string {
|
||||||
location, ok := input.(*ingress.Location)
|
location, ok := input.(*ingress.Location)
|
||||||
if !ok {
|
if !ok {
|
||||||
klog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
klog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if location.ExternalAuth.URL == "" {
|
if (location.ExternalAuth.URL == "") && (!shouldApplyGlobalAuth(input, globalExternalAuthURL)) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,19 +415,29 @@ func buildAuthLocation(input interface{}) string {
|
||||||
return fmt.Sprintf("/_external-auth-%v", str)
|
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)
|
location, ok := input.(*ingress.Location)
|
||||||
res := []string{}
|
|
||||||
if !ok {
|
if !ok {
|
||||||
klog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
klog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location.ExternalAuth.URL == "") && (globalExternalAuthURL != "") && (location.EnableGlobalAuth) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAuthResponseHeaders(headers []string) []string {
|
||||||
|
res := []string{}
|
||||||
|
|
||||||
|
if len(headers) == 0 {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(location.ExternalAuth.ResponseHeaders) == 0 {
|
for i, h := range headers {
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, h := range location.ExternalAuth.ResponseHeaders {
|
|
||||||
hvar := strings.ToLower(h)
|
hvar := strings.ToLower(h)
|
||||||
hvar = strings.NewReplacer("-", "_").Replace(hvar)
|
hvar = strings.NewReplacer("-", "_").Replace(hvar)
|
||||||
res = append(res, fmt.Sprintf("auth_request_set $authHeader%v $upstream_http_%v;", i, hvar))
|
res = append(res, fmt.Sprintf("auth_request_set $authHeader%v $upstream_http_%v;", i, hvar))
|
||||||
|
|
93
internal/ingress/controller/template/template_test.go
Normal file → Executable file
93
internal/ingress/controller/template/template_test.go
Normal file → Executable file
|
@ -283,51 +283,106 @@ func TestBuildProxyPass(t *testing.T) {
|
||||||
func TestBuildAuthLocation(t *testing.T) {
|
func TestBuildAuthLocation(t *testing.T) {
|
||||||
invalidType := &ingress.Ingress{}
|
invalidType := &ingress.Ingress{}
|
||||||
expected := ""
|
expected := ""
|
||||||
actual := buildAuthLocation(invalidType)
|
actual := buildAuthLocation(invalidType, "")
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, actual) {
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
authURL := "foo.com/auth"
|
authURL := "foo.com/auth"
|
||||||
|
globalAuthURL := "foo.com/global-auth"
|
||||||
|
|
||||||
loc := &ingress.Location{
|
loc := &ingress.Location{
|
||||||
ExternalAuth: authreq.Config{
|
ExternalAuth: authreq.Config{
|
||||||
URL: authURL,
|
URL: authURL,
|
||||||
},
|
},
|
||||||
Path: "/cat",
|
Path: "/cat",
|
||||||
|
EnableGlobalAuth: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
str := buildAuthLocation(loc)
|
|
||||||
|
|
||||||
encodedAuthURL := strings.Replace(base64.URLEncoding.EncodeToString([]byte(loc.Path)), "=", "", -1)
|
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 {
|
testCases := []struct {
|
||||||
t.Errorf("Expected \n'%v'\nbut returned \n'%v'", expected, str)
|
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, ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
loc.ExternalAuth.URL = testCase.authURL
|
||||||
|
loc.EnableGlobalAuth = testCase.enableglobalExternalAuth
|
||||||
|
|
||||||
|
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{
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildAuthResponseHeaders(t *testing.T) {
|
func TestBuildAuthResponseHeaders(t *testing.T) {
|
||||||
invalidType := &ingress.Ingress{}
|
externalAuthResponseHeaders := []string{"h1", "H-With-Caps-And-Dashes"}
|
||||||
expected := []string{}
|
expected := []string{
|
||||||
actual := buildAuthResponseHeaders(invalidType)
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, actual) {
|
|
||||||
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
loc := &ingress.Location{
|
|
||||||
ExternalAuth: authreq.Config{ResponseHeaders: []string{"h1", "H-With-Caps-And-Dashes"}},
|
|
||||||
}
|
|
||||||
headers := buildAuthResponseHeaders(loc)
|
|
||||||
expected = []string{
|
|
||||||
"auth_request_set $authHeader0 $upstream_http_h1;",
|
"auth_request_set $authHeader0 $upstream_http_h1;",
|
||||||
"proxy_set_header 'h1' $authHeader0;",
|
"proxy_set_header 'h1' $authHeader0;",
|
||||||
"auth_request_set $authHeader1 $upstream_http_h_with_caps_and_dashes;",
|
"auth_request_set $authHeader1 $upstream_http_h_with_caps_and_dashes;",
|
||||||
"proxy_set_header 'H-With-Caps-And-Dashes' $authHeader1;",
|
"proxy_set_header 'H-With-Caps-And-Dashes' $authHeader1;",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headers := buildAuthResponseHeaders(externalAuthResponseHeaders)
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, headers) {
|
if !reflect.DeepEqual(expected, headers) {
|
||||||
t.Errorf("Expected \n'%v'\nbut returned \n'%v'", expected, headers)
|
t.Errorf("Expected \n'%v'\nbut returned \n'%v'", expected, headers)
|
||||||
}
|
}
|
||||||
|
|
3
internal/ingress/types.go
Normal file → Executable file
3
internal/ingress/types.go
Normal file → Executable file
|
@ -246,6 +246,9 @@ type Location struct {
|
||||||
// authentication using an external provider
|
// authentication using an external provider
|
||||||
// +optional
|
// +optional
|
||||||
ExternalAuth authreq.Config `json:"externalAuth,omitempty"`
|
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
|
// HTTP2PushPreload allows to configure the HTTP2 Push Preload from backend
|
||||||
// original location.
|
// original location.
|
||||||
// +optional
|
// +optional
|
||||||
|
|
3
internal/ingress/types_equals.go
Normal file → Executable file
3
internal/ingress/types_equals.go
Normal file → Executable file
|
@ -352,6 +352,9 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
||||||
if !(&l1.ExternalAuth).Equal(&l2.ExternalAuth) {
|
if !(&l1.ExternalAuth).Equal(&l2.ExternalAuth) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if l1.EnableGlobalAuth != l2.EnableGlobalAuth {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if l1.HTTP2PushPreload != l2.HTTP2PushPreload {
|
if l1.HTTP2PushPreload != l2.HTTP2PushPreload {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
31
rootfs/etc/nginx/template/nginx.tmpl
Normal file → Executable file
31
rootfs/etc/nginx/template/nginx.tmpl
Normal file → Executable file
|
@ -894,7 +894,13 @@ stream {
|
||||||
{{ range $location := $server.Locations }}
|
{{ range $location := $server.Locations }}
|
||||||
{{ $path := buildLocation $location $enforceRegex }}
|
{{ $path := buildLocation $location $enforceRegex }}
|
||||||
{{ $proxySetHeader := proxySetHeader $location }}
|
{{ $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 not (empty $location.Rewrite.AppRoot)}}
|
||||||
if ($uri = /) {
|
if ($uri = /) {
|
||||||
|
@ -915,13 +921,13 @@ stream {
|
||||||
proxy_set_header Content-Length "";
|
proxy_set_header Content-Length "";
|
||||||
proxy_set_header X-Forwarded-Proto "";
|
proxy_set_header X-Forwarded-Proto "";
|
||||||
|
|
||||||
{{ if $location.ExternalAuth.Method }}
|
{{ if $externalAuth.Method }}
|
||||||
proxy_method {{ $location.ExternalAuth.Method }};
|
proxy_method {{ $externalAuth.Method }};
|
||||||
proxy_set_header X-Original-URI $request_uri;
|
proxy_set_header X-Original-URI $request_uri;
|
||||||
proxy_set_header X-Scheme $pass_access_scheme;
|
proxy_set_header X-Scheme $pass_access_scheme;
|
||||||
{{ end }}
|
{{ 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-URL $scheme://$http_host$request_uri;
|
||||||
proxy_set_header X-Original-Method $request_method;
|
proxy_set_header X-Original-Method $request_method;
|
||||||
proxy_set_header X-Sent-From "nginx-ingress-controller";
|
proxy_set_header X-Sent-From "nginx-ingress-controller";
|
||||||
|
@ -932,8 +938,8 @@ stream {
|
||||||
proxy_set_header X-Forwarded-For $the_real_ip;
|
proxy_set_header X-Forwarded-For $the_real_ip;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if $location.ExternalAuth.RequestRedirect }}
|
{{ if $externalAuth.RequestRedirect }}
|
||||||
proxy_set_header X-Auth-Request-Redirect {{ $location.ExternalAuth.RequestRedirect }};
|
proxy_set_header X-Auth-Request-Redirect {{ $externalAuth.RequestRedirect }};
|
||||||
{{ else }}
|
{{ else }}
|
||||||
proxy_set_header X-Auth-Request-Redirect $request_uri;
|
proxy_set_header X-Auth-Request-Redirect $request_uri;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -963,15 +969,16 @@ stream {
|
||||||
proxy_set_header ssl-client-issuer-dn $ssl_client_i_dn;
|
proxy_set_header ssl-client-issuer-dn $ssl_client_i_dn;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if not (empty $location.ExternalAuth.AuthSnippet) }}
|
{{ if not (empty $externalAuth.AuthSnippet) }}
|
||||||
{{ $location.ExternalAuth.AuthSnippet }}
|
{{ $externalAuth.AuthSnippet }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
set $target {{ $location.ExternalAuth.URL }};
|
set $target {{ $externalAuth.URL }};
|
||||||
proxy_pass $target;
|
proxy_pass $target;
|
||||||
}
|
}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
location {{ $path }} {
|
location {{ $path }} {
|
||||||
{{ $ing := (getIngressInformation $location.Ingress $server.Hostname $location.Path) }}
|
{{ $ing := (getIngressInformation $location.Ingress $server.Hostname $location.Path) }}
|
||||||
set $namespace "{{ $ing.Namespace }}";
|
set $namespace "{{ $ing.Namespace }}";
|
||||||
|
@ -1125,14 +1132,14 @@ stream {
|
||||||
auth_request {{ $authPath }};
|
auth_request {{ $authPath }};
|
||||||
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||||
add_header Set-Cookie $auth_cookie;
|
add_header Set-Cookie $auth_cookie;
|
||||||
{{- range $line := buildAuthResponseHeaders $location }}
|
{{- range $line := buildAuthResponseHeaders $externalAuth.ResponseHeaders }}
|
||||||
{{ $line }}
|
{{ $line }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if $location.ExternalAuth.SigninURL }}
|
{{ if $externalAuth.SigninURL }}
|
||||||
set_escape_uri $escaped_request_uri $request_uri;
|
set_escape_uri $escaped_request_uri $request_uri;
|
||||||
error_page 401 = {{ buildAuthSignURL $location.ExternalAuth.SigninURL }};
|
error_page 401 = {{ buildAuthSignURL $externalAuth.SigninURL }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if $location.BasicDigestAuth.Secured }}
|
{{ if $location.BasicDigestAuth.Secured }}
|
||||||
|
|
217
test/e2e/settings/global_external_auth.go
Executable file
217
test/e2e/settings/global_external_auth.go
Executable file
|
@ -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))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
Loading…
Reference in a new issue