Merge pull request #4278 from moolen/feat/auth-req-cache
feat: auth-req caching
This commit is contained in:
commit
589c9a20f9
13 changed files with 583 additions and 52 deletions
|
@ -26,6 +26,8 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|
||||||
|[nginx.ingress.kubernetes.io/auth-tls-error-page](#client-certificate-authentication)|string|
|
|[nginx.ingress.kubernetes.io/auth-tls-error-page](#client-certificate-authentication)|string|
|
||||||
|[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-cache-key](#external-authentication)|string|
|
||||||
|
|[nginx.ingress.kubernetes.io/auth-cache-duration](#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/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|
|
||||||
|
@ -379,6 +381,10 @@ Additionally it is possible to set:
|
||||||
`<Response_Header_1, ..., Response_Header_n>` to specify headers to pass to backend once authentication request completes.
|
`<Response_Header_1, ..., Response_Header_n>` to specify headers to pass to backend once authentication request completes.
|
||||||
* `nginx.ingress.kubernetes.io/auth-request-redirect`:
|
* `nginx.ingress.kubernetes.io/auth-request-redirect`:
|
||||||
`<Request_Redirect_URL>` to specify the X-Auth-Request-Redirect header value.
|
`<Request_Redirect_URL>` to specify the X-Auth-Request-Redirect header value.
|
||||||
|
* `nginx.ingress.kubernetes.io/auth-cache-key`:
|
||||||
|
`<Cache_Key>` this enables caching for auth requests. specify a lookup key for auth responses. e.g. `$remote_user$http_authorization`. Each server and location has it's own keyspace. Hence a cached response is only valid on a per-server and per-location basis.
|
||||||
|
* `nginx.ingress.kubernetes.io/auth-cache-duration`:
|
||||||
|
`<Cache_duration>` to specify a caching time for auth responses based on their response codes, e.g. `200 202 30m`. See [proxy_cache_valid](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_valid) for details. You may specify multiple, comma-separated values: `200 202 10m, 401 5m`. defaults to `200 202 401 5m`.
|
||||||
* `nginx.ingress.kubernetes.io/auth-snippet`:
|
* `nginx.ingress.kubernetes.io/auth-snippet`:
|
||||||
`<Auth_Snippet>` to specify a custom snippet to use with external authentication, e.g.
|
`<Auth_Snippet>` to specify a custom snippet to use with external authentication, e.g.
|
||||||
|
|
||||||
|
|
|
@ -160,6 +160,8 @@ The following table shows a configuration option's name, type, and the default v
|
||||||
|[global-auth-response-headers](#global-auth-response-headers)|string|""|
|
|[global-auth-response-headers](#global-auth-response-headers)|string|""|
|
||||||
|[global-auth-request-redirect](#global-auth-request-redirect)|string|""|
|
|[global-auth-request-redirect](#global-auth-request-redirect)|string|""|
|
||||||
|[global-auth-snippet](#global-auth-snippet)|string|""|
|
|[global-auth-snippet](#global-auth-snippet)|string|""|
|
||||||
|
|[global-auth-cache-key](#global-auth-cache-key)|string|""|
|
||||||
|
|[global-auth-cache-duration](#global-auth-cache-duration)|string|"200 202 401 5m"|
|
||||||
|[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|""|
|
||||||
|
@ -922,6 +924,14 @@ Sets a custom snippet to use with external authentication. Applied to all the lo
|
||||||
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-request-redirect`.
|
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-request-redirect`.
|
||||||
_**default:**_ ""
|
_**default:**_ ""
|
||||||
|
|
||||||
|
## global-auth-cache-key
|
||||||
|
|
||||||
|
Enables caching for global auth requests. Specify a lookup key for auth responses, e.g. `$remote_user$http_authorization`.
|
||||||
|
|
||||||
|
## global-auth-cache-duration
|
||||||
|
|
||||||
|
Set a caching time for auth responses based on their response codes, e.g. `200 202 30m`. See [proxy_cache_valid](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_valid) for details. You may specify multiple, comma-separated values: `200 202 10m, 401 5m`. defaults to `200 202 401 5m`.
|
||||||
|
|
||||||
## 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.
|
||||||
|
|
|
@ -36,14 +36,19 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
// Host contains the hostname defined in the URL
|
// Host contains the hostname defined in the URL
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
SigninURL string `json:"signinUrl"`
|
SigninURL string `json:"signinUrl"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
ResponseHeaders []string `json:"responseHeaders,omitempty"`
|
ResponseHeaders []string `json:"responseHeaders,omitempty"`
|
||||||
RequestRedirect string `json:"requestRedirect"`
|
RequestRedirect string `json:"requestRedirect"`
|
||||||
AuthSnippet string `json:"authSnippet"`
|
AuthSnippet string `json:"authSnippet"`
|
||||||
|
AuthCacheKey string `json:"authCacheKey"`
|
||||||
|
AuthCacheDuration []string `json:"authCacheDuration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultCacheDuration is the fallback value if no cache duration is provided
|
||||||
|
const DefaultCacheDuration = "200 202 401 5m"
|
||||||
|
|
||||||
// Equal tests for equality between two Config types
|
// Equal tests for equality between two Config types
|
||||||
func (e1 *Config) Equal(e2 *Config) bool {
|
func (e1 *Config) Equal(e2 *Config) bool {
|
||||||
if e1 == e2 {
|
if e1 == e2 {
|
||||||
|
@ -77,12 +82,23 @@ func (e1 *Config) Equal(e2 *Config) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if e1.AuthCacheKey != e2.AuthCacheKey {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
match = sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
|
||||||
|
if !match {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
|
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
|
||||||
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
|
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
|
||||||
|
statusCodeRegex = regexp.MustCompile(`^[\d]{3}$`)
|
||||||
|
durationRegex = regexp.MustCompile(`^[\d]+(ms|s|m|h|d|w|M|y)$`) // see http://nginx.org/en/docs/syntax.html
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidMethod checks is the provided string a valid HTTP method
|
// ValidMethod checks is the provided string a valid HTTP method
|
||||||
|
@ -104,6 +120,31 @@ func ValidHeader(header string) bool {
|
||||||
return headerRegexp.Match([]byte(header))
|
return headerRegexp.Match([]byte(header))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidCacheDuration checks if the provided string is a valid cache duration
|
||||||
|
// spec: [code ...] [time ...];
|
||||||
|
// with: code is an http status code
|
||||||
|
// time must match the time regex and may appear multiple times, e.g. `1h 30m`
|
||||||
|
func ValidCacheDuration(duration string) bool {
|
||||||
|
elements := strings.Split(duration, " ")
|
||||||
|
seenDuration := false
|
||||||
|
|
||||||
|
for _, element := range elements {
|
||||||
|
if len(element) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if statusCodeRegex.Match([]byte(element)) {
|
||||||
|
if seenDuration {
|
||||||
|
return false // code after duration
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if durationRegex.Match([]byte(element)) {
|
||||||
|
seenDuration = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return seenDuration
|
||||||
|
}
|
||||||
|
|
||||||
type authReq struct {
|
type authReq struct {
|
||||||
r resolver.Resolver
|
r resolver.Resolver
|
||||||
}
|
}
|
||||||
|
@ -143,6 +184,17 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
klog.V(3).Infof("auth-snippet annotation is undefined and will not be set")
|
klog.V(3).Infof("auth-snippet annotation is undefined and will not be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authCacheKey, err := parser.GetStringAnnotation("auth-cache-key", ing)
|
||||||
|
if err != nil {
|
||||||
|
klog.V(3).Infof("auth-cache-key annotation is undefined and will not be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
durstr, _ := parser.GetStringAnnotation("auth-cache-duration", ing)
|
||||||
|
authCacheDuration, err := ParseStringToCacheDurations(durstr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
responseHeaders := []string{}
|
responseHeaders := []string{}
|
||||||
hstr, _ := parser.GetStringAnnotation("auth-response-headers", ing)
|
hstr, _ := parser.GetStringAnnotation("auth-response-headers", ing)
|
||||||
if len(hstr) != 0 {
|
if len(hstr) != 0 {
|
||||||
|
@ -161,13 +213,15 @@ func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
requestRedirect, _ := parser.GetStringAnnotation("auth-request-redirect", ing)
|
requestRedirect, _ := parser.GetStringAnnotation("auth-request-redirect", ing)
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
URL: urlString,
|
URL: urlString,
|
||||||
Host: authURL.Hostname(),
|
Host: authURL.Hostname(),
|
||||||
SigninURL: signIn,
|
SigninURL: signIn,
|
||||||
Method: authMethod,
|
Method: authMethod,
|
||||||
ResponseHeaders: responseHeaders,
|
ResponseHeaders: responseHeaders,
|
||||||
RequestRedirect: requestRedirect,
|
RequestRedirect: requestRedirect,
|
||||||
AuthSnippet: authSnippet,
|
AuthSnippet: authSnippet,
|
||||||
|
AuthCacheKey: authCacheKey,
|
||||||
|
AuthCacheDuration: authCacheDuration,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,3 +243,28 @@ func ParseStringToURL(input string) (*url.URL, string) {
|
||||||
return parsedURL, ""
|
return parsedURL, ""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseStringToCacheDurations parses and validates the provided string
|
||||||
|
// into a list of cache durations.
|
||||||
|
// It will always return at least one duration (the default duration)
|
||||||
|
func ParseStringToCacheDurations(input string) ([]string, error) {
|
||||||
|
authCacheDuration := []string{}
|
||||||
|
if len(input) != 0 {
|
||||||
|
arr := strings.Split(input, ",")
|
||||||
|
for _, duration := range arr {
|
||||||
|
duration = strings.TrimSpace(duration)
|
||||||
|
if len(duration) > 0 {
|
||||||
|
if !ValidCacheDuration(duration) {
|
||||||
|
authCacheDuration = []string{DefaultCacheDuration}
|
||||||
|
return authCacheDuration, ing_errors.NewLocationDenied(fmt.Sprintf("invalid cache duration: %s", duration))
|
||||||
|
}
|
||||||
|
authCacheDuration = append(authCacheDuration, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(authCacheDuration) == 0 {
|
||||||
|
authCacheDuration = append(authCacheDuration, DefaultCacheDuration)
|
||||||
|
}
|
||||||
|
return authCacheDuration, nil
|
||||||
|
}
|
||||||
|
|
|
@ -79,17 +79,19 @@ func TestAnnotations(t *testing.T) {
|
||||||
method string
|
method string
|
||||||
requestRedirect string
|
requestRedirect string
|
||||||
authSnippet string
|
authSnippet string
|
||||||
|
authCacheKey string
|
||||||
expErr bool
|
expErr bool
|
||||||
}{
|
}{
|
||||||
{"empty", "", "", "", "", "", true},
|
{"empty", "", "", "", "", "", "", true},
|
||||||
{"no scheme", "bar", "bar", "", "", "", true},
|
{"no scheme", "bar", "bar", "", "", "", "", true},
|
||||||
{"invalid host", "http://", "http://", "", "", "", true},
|
{"invalid host", "http://", "http://", "", "", "", "", true},
|
||||||
{"invalid host (multiple dots)", "http://foo..bar.com", "http://foo..bar.com", "", "", "", true},
|
{"invalid host (multiple dots)", "http://foo..bar.com", "http://foo..bar.com", "", "", "", "", true},
|
||||||
{"valid URL", "http://bar.foo.com/external-auth", "http://bar.foo.com/external-auth", "", "", "", false},
|
{"valid URL", "http://bar.foo.com/external-auth", "http://bar.foo.com/external-auth", "", "", "", "", false},
|
||||||
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "POST", "", "", false},
|
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "POST", "", "", "", false},
|
||||||
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", "", "", false},
|
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", "", "", "", false},
|
||||||
{"valid URL - request redirect", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", "http://foo.com/redirect-me", "", false},
|
{"valid URL - request redirect", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", "http://foo.com/redirect-me", "", "", false},
|
||||||
{"auth snippet", "http://foo.com/external-auth", "http://foo.com/external-auth", "", "", "proxy_set_header My-Custom-Header 42;", false},
|
{"auth snippet", "http://foo.com/external-auth", "http://foo.com/external-auth", "", "", "proxy_set_header My-Custom-Header 42;", "", false},
|
||||||
|
{"auth cache ", "http://foo.com/external-auth", "http://foo.com/external-auth", "", "", "", "$foo$bar", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -98,6 +100,7 @@ func TestAnnotations(t *testing.T) {
|
||||||
data[parser.GetAnnotationWithPrefix("auth-method")] = fmt.Sprintf("%v", test.method)
|
data[parser.GetAnnotationWithPrefix("auth-method")] = fmt.Sprintf("%v", test.method)
|
||||||
data[parser.GetAnnotationWithPrefix("auth-request-redirect")] = test.requestRedirect
|
data[parser.GetAnnotationWithPrefix("auth-request-redirect")] = test.requestRedirect
|
||||||
data[parser.GetAnnotationWithPrefix("auth-snippet")] = test.authSnippet
|
data[parser.GetAnnotationWithPrefix("auth-snippet")] = test.authSnippet
|
||||||
|
data[parser.GetAnnotationWithPrefix("auth-cache-key")] = test.authCacheKey
|
||||||
|
|
||||||
i, err := NewParser(&resolver.Mock{}).Parse(ing)
|
i, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||||
if test.expErr {
|
if test.expErr {
|
||||||
|
@ -129,6 +132,9 @@ func TestAnnotations(t *testing.T) {
|
||||||
if u.AuthSnippet != test.authSnippet {
|
if u.AuthSnippet != test.authSnippet {
|
||||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.authSnippet, u.AuthSnippet)
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.authSnippet, u.AuthSnippet)
|
||||||
}
|
}
|
||||||
|
if u.AuthCacheKey != test.authCacheKey {
|
||||||
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.authCacheKey, u.AuthCacheKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +186,54 @@ func TestHeaderAnnotations(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCacheDurationAnnotations(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
title string
|
||||||
|
url string
|
||||||
|
duration string
|
||||||
|
parsedDuration []string
|
||||||
|
expErr bool
|
||||||
|
}{
|
||||||
|
{"nothing", "http://goog.url", "", []string{DefaultCacheDuration}, false},
|
||||||
|
{"spaces", "http://goog.url", " ", []string{DefaultCacheDuration}, false},
|
||||||
|
{"one duration", "http://goog.url", "5m", []string{"5m"}, false},
|
||||||
|
{"two durations", "http://goog.url", "200 202 10m, 401 5m", []string{"200 202 10m", "401 5m"}, false},
|
||||||
|
{"two durations and empty entries", "http://goog.url", ",5m,,401 10m,", []string{"5m", "401 10m"}, false},
|
||||||
|
{"only status code provided", "http://goog.url", "200", []string{DefaultCacheDuration}, true},
|
||||||
|
{"mixed valid/invalid", "http://goog.url", "5m, xaxax", []string{DefaultCacheDuration}, true},
|
||||||
|
{"code after duration", "http://goog.url", "5m 200", []string{DefaultCacheDuration}, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
data[parser.GetAnnotationWithPrefix("auth-url")] = test.url
|
||||||
|
data[parser.GetAnnotationWithPrefix("auth-cache-duration")] = test.duration
|
||||||
|
|
||||||
|
i, err := NewParser(&resolver.Mock{}).Parse(ing)
|
||||||
|
if test.expErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%v: expected error but retuned nil", err.Error())
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(i)
|
||||||
|
u, ok := i.(*Config)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%v: expected an External type", test.title)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(u.AuthCacheDuration, test.parsedDuration) {
|
||||||
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.duration, u.AuthCacheDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseStringToURL(t *testing.T) {
|
func TestParseStringToURL(t *testing.T) {
|
||||||
validURL := "http://bar.foo.com/external-auth"
|
validURL := "http://bar.foo.com/external-auth"
|
||||||
validParsedURL, _ := url.Parse(validURL)
|
validParsedURL, _ := url.Parse(validURL)
|
||||||
|
@ -214,3 +268,35 @@ func TestParseStringToURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseStringToCacheDurations(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
title string
|
||||||
|
duration string
|
||||||
|
expectedDurations []string
|
||||||
|
expErr bool
|
||||||
|
}{
|
||||||
|
{"empty", "", []string{DefaultCacheDuration}, false},
|
||||||
|
{"invalid", ",200,", []string{DefaultCacheDuration}, true},
|
||||||
|
{"single", ",200 5m,", []string{"200 5m"}, false},
|
||||||
|
{"multiple with duration", ",5m,,401 10m,", []string{"5m", "401 10m"}, false},
|
||||||
|
{"multiple durations", "200 202 401 5m, 418 30m", []string{"200 202 401 5m", "418 30m"}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
|
||||||
|
dur, err := ParseStringToCacheDurations(test.duration)
|
||||||
|
if test.expErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%v: expected error but nil was returned", test.title)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(dur, test.expectedDurations) {
|
||||||
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.expectedDurations, dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -622,7 +622,7 @@ func NewDefault() Configuration {
|
||||||
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
|
||||||
defGlobalExternalAuth := GlobalExternalAuth{"", "", "", "", append(defResponseHeaders, ""), "", ""}
|
defGlobalExternalAuth := GlobalExternalAuth{"", "", "", "", append(defResponseHeaders, ""), "", "", "", []string{}}
|
||||||
|
|
||||||
cfg := Configuration{
|
cfg := Configuration{
|
||||||
AllowBackendServerHeader: false,
|
AllowBackendServerHeader: false,
|
||||||
|
@ -808,10 +808,12 @@ type ListenPorts struct {
|
||||||
type GlobalExternalAuth struct {
|
type GlobalExternalAuth struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
// Host contains the hostname defined in the URL
|
// Host contains the hostname defined in the URL
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
SigninURL string `json:"signinUrl"`
|
SigninURL string `json:"signinUrl"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
ResponseHeaders []string `json:"responseHeaders,omitempty"`
|
ResponseHeaders []string `json:"responseHeaders,omitempty"`
|
||||||
RequestRedirect string `json:"requestRedirect"`
|
RequestRedirect string `json:"requestRedirect"`
|
||||||
AuthSnippet string `json:"authSnippet"`
|
AuthSnippet string `json:"authSnippet"`
|
||||||
|
AuthCacheKey string `json:"authCacheKey"`
|
||||||
|
AuthCacheDuration []string `json:"authCacheDuration"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,8 @@ const (
|
||||||
globalAuthResponseHeaders = "global-auth-response-headers"
|
globalAuthResponseHeaders = "global-auth-response-headers"
|
||||||
globalAuthRequestRedirect = "global-auth-request-redirect"
|
globalAuthRequestRedirect = "global-auth-request-redirect"
|
||||||
globalAuthSnippet = "global-auth-snippet"
|
globalAuthSnippet = "global-auth-snippet"
|
||||||
|
globalAuthCacheKey = "global-auth-cache-key"
|
||||||
|
globalAuthCacheDuration = "global-auth-cache-duration"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -226,6 +228,23 @@ func ReadConfig(src map[string]string) config.Configuration {
|
||||||
to.GlobalExternalAuth.AuthSnippet = val
|
to.GlobalExternalAuth.AuthSnippet = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, ok := conf[globalAuthCacheKey]; ok {
|
||||||
|
delete(conf, globalAuthCacheKey)
|
||||||
|
|
||||||
|
to.GlobalExternalAuth.AuthCacheKey = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the configured global external authorization cache duration is valid
|
||||||
|
if val, ok := conf[globalAuthCacheDuration]; ok {
|
||||||
|
delete(conf, globalAuthCacheDuration)
|
||||||
|
|
||||||
|
cacheDurations, err := authreq.ParseStringToCacheDurations(val)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("Global auth location denied - %s", err)
|
||||||
|
}
|
||||||
|
to.GlobalExternalAuth.AuthCacheDuration = cacheDurations
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/kylelemons/godebug/pretty"
|
"github.com/kylelemons/godebug/pretty"
|
||||||
"github.com/mitchellh/hashstructure"
|
"github.com/mitchellh/hashstructure"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -280,3 +281,25 @@ func TestGlobalExternalAuthSnippetParsing(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGlobalExternalAuthCacheDurationParsing(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
durations string
|
||||||
|
expect []string
|
||||||
|
}{
|
||||||
|
"nothing": {"", []string{authreq.DefaultCacheDuration}},
|
||||||
|
"spaces": {" ", []string{authreq.DefaultCacheDuration}},
|
||||||
|
"one duration": {"5m", []string{"5m"}},
|
||||||
|
"two durations and empty entries": {",200 5m,,401 30m,", []string{"200 5m", "401 30m"}},
|
||||||
|
"only status code provided": {"200", []string{authreq.DefaultCacheDuration}},
|
||||||
|
"mixed valid/invalid": {"5m, xaxax", []string{authreq.DefaultCacheDuration}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, tc := range testCases {
|
||||||
|
cfg := ReadConfig(map[string]string{"global-auth-cache-duration": tc.durations})
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(cfg.GlobalExternalAuth.AuthCacheDuration, tc.expect) {
|
||||||
|
t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", n, tc.expect, cfg.GlobalExternalAuth.AuthCacheDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -443,6 +443,9 @@ http {
|
||||||
{{ $zone }}
|
{{ $zone }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
# Cache for internal auth checks
|
||||||
|
proxy_cache_path /tmp/nginx-cache-auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=30m use_temp_path=off;
|
||||||
|
|
||||||
# Global filters
|
# Global filters
|
||||||
{{ range $ip := $cfg.BlockCIDRs }}deny {{ trimSpace $ip }};
|
{{ range $ip := $cfg.BlockCIDRs }}deny {{ trimSpace $ip }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -896,6 +899,23 @@ stream {
|
||||||
location = {{ $authPath }} {
|
location = {{ $authPath }} {
|
||||||
internal;
|
internal;
|
||||||
|
|
||||||
|
{{ if $externalAuth.AuthCacheKey }}
|
||||||
|
set $tmp_cache_key '{{ $server.Hostname }}{{ $authPath }}{{ $externalAuth.AuthCacheKey }}';
|
||||||
|
set $cache_key '';
|
||||||
|
|
||||||
|
rewrite_by_lua_block {
|
||||||
|
ngx.var.cache_key = ngx.encode_base64(ngx.sha1_bin(ngx.var.tmp_cache_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_cache auth_cache;
|
||||||
|
|
||||||
|
{{- range $dur := $externalAuth.AuthCacheDuration }}
|
||||||
|
proxy_cache_valid {{ $dur }};
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
proxy_cache_key "$cache_key";
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
# ngx_auth_request module overrides variables in the parent request,
|
# ngx_auth_request module overrides variables in the parent request,
|
||||||
# therefore we have to explicitly set this variable again so that when the parent request
|
# therefore we have to explicitly set this variable again so that when the parent request
|
||||||
# resumes it has the correct value set for this variable so that Lua can pick backend correctly
|
# resumes it has the correct value set for this variable so that Lua can pick backend correctly
|
||||||
|
@ -928,7 +948,11 @@ stream {
|
||||||
proxy_set_header X-Auth-Request-Redirect $request_uri;
|
proxy_set_header X-Auth-Request-Redirect $request_uri;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if $externalAuth.AuthCacheKey }}
|
||||||
|
proxy_buffering "on";
|
||||||
|
{{ else }}
|
||||||
proxy_buffering {{ $location.Proxy.ProxyBuffering }};
|
proxy_buffering {{ $location.Proxy.ProxyBuffering }};
|
||||||
|
{{ end }}
|
||||||
proxy_buffer_size {{ $location.Proxy.BufferSize }};
|
proxy_buffer_size {{ $location.Proxy.BufferSize }};
|
||||||
proxy_buffers {{ $location.Proxy.BuffersNumber }} {{ $location.Proxy.BufferSize }};
|
proxy_buffers {{ $location.Proxy.BuffersNumber }} {{ $location.Proxy.BufferSize }};
|
||||||
proxy_request_buffering {{ $location.Proxy.RequestBuffering }};
|
proxy_request_buffering {{ $location.Proxy.RequestBuffering }};
|
||||||
|
|
|
@ -260,6 +260,26 @@ var _ = framework.IngressNginxDescribe("Annotations - Auth", func() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It(`should set cache_key when external auth cache is configured`, func() {
|
||||||
|
host := "auth"
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/auth-url": "http://foo.bar/basic-auth/user/password",
|
||||||
|
"nginx.ingress.kubernetes.io/auth-cache-key": "foo",
|
||||||
|
"nginx.ingress.kubernetes.io/auth-cache-duration": "200 202 401 30m",
|
||||||
|
}
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, "http-svc", 80, &annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host,
|
||||||
|
func(server string) bool {
|
||||||
|
return Expect(server).Should(MatchRegexp(`\$cache_key.*foo`)) &&
|
||||||
|
Expect(server).Should(ContainSubstring(`proxy_cache_valid 200 202 401 30m;`))
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Context("when external authentication is configured", func() {
|
Context("when external authentication is configured", func() {
|
||||||
host := "auth"
|
host := "auth"
|
||||||
|
|
||||||
|
@ -322,6 +342,185 @@ var _ = framework.IngressNginxDescribe("Annotations - Auth", func() {
|
||||||
Expect(resp.Header.Get("Location")).Should(Equal(fmt.Sprintf("http://%s/auth/start?rd=http://%s%s", host, host, url.QueryEscape("/?a=b&c=d"))))
|
Expect(resp.Header.Get("Location")).Should(Equal(fmt.Sprintf("http://%s/auth/start?rd=http://%s%s", host, host, url.QueryEscape("/?a=b&c=d"))))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("when external authentication with caching is configured", func() {
|
||||||
|
thisHost := "auth"
|
||||||
|
thatHost := "different"
|
||||||
|
|
||||||
|
fooPath := "/foo"
|
||||||
|
barPath := "/bar"
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
f.NewHttpbinDeployment()
|
||||||
|
|
||||||
|
var httpbinIP string
|
||||||
|
|
||||||
|
err := framework.WaitForEndpoints(f.KubeClientSet, framework.DefaultTimeout, "httpbin", f.Namespace, 1)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
e, err := f.KubeClientSet.CoreV1().Endpoints(f.Namespace).Get("httpbin", metav1.GetOptions{})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
httpbinIP = e.Subsets[0].Addresses[0].IP
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/auth-url": fmt.Sprintf("http://%s/basic-auth/user/password", httpbinIP),
|
||||||
|
"nginx.ingress.kubernetes.io/auth-signin": "http://$host/auth/start",
|
||||||
|
"nginx.ingress.kubernetes.io/auth-cache-key": "fixed",
|
||||||
|
"nginx.ingress.kubernetes.io/auth-cache-duration": "200 201 401 30m",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range []string{thisHost, thatHost} {
|
||||||
|
By("Adding an ingress rule for /foo")
|
||||||
|
fooIng := framework.NewSingleIngress(fmt.Sprintf("foo-%s-ing", host), fooPath, host, f.Namespace, "http-svc", 80, &annotations)
|
||||||
|
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(fmt.Sprintf("bar-%s-ing", host), barPath, host, f.Namespace, "http-svc", 80, &annotations)
|
||||||
|
f.EnsureIngress(barIng)
|
||||||
|
f.WaitForNginxServer(host, func(server string) bool {
|
||||||
|
return Expect(server).Should(ContainSubstring("location /bar"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should return status code 200 when signed in after auth backend is deleted ", func() {
|
||||||
|
resp, _, errs := gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+fooPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thisHost).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
|
||||||
|
err := f.DeleteDeployment("httpbin")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
resp, _, errs = gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+fooPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thisHost).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should deny login for different location on same server", func() {
|
||||||
|
resp, _, errs := gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+fooPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thisHost).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
|
||||||
|
err := f.DeleteDeployment("httpbin")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
resp, _, errs = gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+fooPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thisHost).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _, errs = gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+barPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thisHost).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
By("receiving an internal server error without cache on location /bar")
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should deny login for different servers", func() {
|
||||||
|
By("logging into server thisHost /foo")
|
||||||
|
resp, _, errs := gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+fooPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thisHost).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
|
||||||
|
err := f.DeleteDeployment("httpbin")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
resp, _, errs = gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+fooPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thisHost).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
|
||||||
|
resp, _, errs = gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+fooPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thatHost).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
By("receiving an internal server error without cache on thisHost location /bar")
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should redirect to signin url when not signed in", func() {
|
||||||
|
resp, _, errs := gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", thisHost).
|
||||||
|
RedirectPolicy(func(req gorequest.Request, via []gorequest.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}).
|
||||||
|
Param("a", "b").
|
||||||
|
Param("c", "d").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusFound))
|
||||||
|
Expect(resp.Header.Get("Location")).Should(Equal(fmt.Sprintf("http://%s/auth/start?rd=http://%s%s", thisHost, thisHost, url.QueryEscape("/?a=b&c=d"))))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: test Digest Auth
|
// TODO: test Digest Auth
|
||||||
|
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||||
package framework
|
package framework
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
@ -138,3 +140,14 @@ func (f *Framework) NewDeployment(name, image string, port int32, replicas int32
|
||||||
err = WaitForEndpoints(f.KubeClientSet, DefaultTimeout, name, f.Namespace, int(replicas))
|
err = WaitForEndpoints(f.KubeClientSet, DefaultTimeout, name, f.Namespace, int(replicas))
|
||||||
Expect(err).NotTo(HaveOccurred(), "failed to wait for endpoints to become ready")
|
Expect(err).NotTo(HaveOccurred(), "failed to wait for endpoints to become ready")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteDeployment deletes a deployment with a particular name and waits for the pods to be deleted
|
||||||
|
func (f *Framework) DeleteDeployment(name string) error {
|
||||||
|
d, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Get(name, metav1.GetOptions{})
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to get a deployment")
|
||||||
|
err = f.KubeClientSet.AppsV1().Deployments(f.Namespace).Delete(name, &metav1.DeleteOptions{})
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to delete a deployment")
|
||||||
|
return WaitForPodsDeleted(f.KubeClientSet, time.Second*60, f.Namespace, metav1.ListOptions{
|
||||||
|
LabelSelector: labelSelectorToString(d.Spec.Selector.MatchLabels),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -143,6 +143,21 @@ func WaitForPodsReady(kubeClientSet kubernetes.Interface, timeout time.Duration,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WaitForPodsDeleted waits for a given amount of time until a group of Pods are deleted in the given namespace.
|
||||||
|
func WaitForPodsDeleted(kubeClientSet kubernetes.Interface, timeout time.Duration, namespace string, opts metav1.ListOptions) error {
|
||||||
|
return wait.Poll(2*time.Second, timeout, func() (bool, error) {
|
||||||
|
pl, err := kubeClientSet.CoreV1().Pods(namespace).List(opts)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pl.Items) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// WaitForEndpoints waits for a given amount of time until an endpoint contains.
|
// WaitForEndpoints waits for a given amount of time until an endpoint contains.
|
||||||
func WaitForEndpoints(kubeClientSet kubernetes.Interface, timeout time.Duration, name, ns string, expectedEndpoints int) error {
|
func WaitForEndpoints(kubeClientSet kubernetes.Interface, timeout time.Duration, name, ns string, expectedEndpoints int) error {
|
||||||
if expectedEndpoints == 0 {
|
if expectedEndpoints == 0 {
|
||||||
|
|
|
@ -288,6 +288,14 @@ func podRunning(c kubernetes.Interface, podName, namespace string) wait.Conditio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func labelSelectorToString(labels map[string]string) string {
|
||||||
|
var str string
|
||||||
|
for k, v := range labels {
|
||||||
|
str += fmt.Sprintf("%s=%s,", k, v)
|
||||||
|
}
|
||||||
|
return str[:len(str)-1]
|
||||||
|
}
|
||||||
|
|
||||||
// NewInt32 converts int32 to a pointer
|
// NewInt32 converts int32 to a pointer
|
||||||
func NewInt32(val int32) *int32 {
|
func NewInt32(val int32) *int32 {
|
||||||
p := new(int32)
|
p := new(int32)
|
||||||
|
|
|
@ -19,6 +19,7 @@ package settings
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
@ -146,6 +147,52 @@ var _ = framework.IngressNginxDescribe("Global External Auth", func() {
|
||||||
Expect(barResp.StatusCode).Should(Equal(http.StatusOK))
|
Expect(barResp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should still return status code 200 after auth backend is deleted using cache ", func() {
|
||||||
|
|
||||||
|
globalExternalAuthCacheKeySetting := "global-auth-cache-key"
|
||||||
|
globalExternalAuthCacheKey := "foo"
|
||||||
|
globalExternalAuthCacheDurationSetting := "global-auth-cache-duration"
|
||||||
|
globalExternalAuthCacheDuration := "200 201 401 30m"
|
||||||
|
globalExternalAuthURL := fmt.Sprintf("http://httpbin.%s.svc.cluster.local:80/status/200", f.Namespace)
|
||||||
|
|
||||||
|
By("Adding a global-auth-cache-key to configMap")
|
||||||
|
f.UpdateNginxConfigMapData(globalExternalAuthCacheKeySetting, globalExternalAuthCacheKey)
|
||||||
|
f.UpdateNginxConfigMapData(globalExternalAuthCacheDurationSetting, globalExternalAuthCacheDuration)
|
||||||
|
f.UpdateNginxConfigMapData(globalExternalAuthURLSetting, globalExternalAuthURL)
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host,
|
||||||
|
func(server string) bool {
|
||||||
|
return Expect(server).Should(MatchRegexp(`\$cache_key.*foo`)) &&
|
||||||
|
Expect(server).Should(ContainSubstring(`proxy_cache_valid 200 201 401 30m;`))
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, _, errs := gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)+barPath).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", host).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||||
|
|
||||||
|
err := f.DeleteDeployment("httpbin")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
resp, _, errs = gorequest.New().
|
||||||
|
Get(f.GetURL(framework.HTTP)).
|
||||||
|
Retry(10, 1*time.Second, http.StatusNotFound).
|
||||||
|
Set("Host", host).
|
||||||
|
SetBasicAuth("user", "password").
|
||||||
|
End()
|
||||||
|
|
||||||
|
for _, err := range errs {
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
It(`should proxy_method method when global-auth-method is configured`, func() {
|
It(`should proxy_method method when global-auth-method is configured`, func() {
|
||||||
|
|
||||||
globalExternalAuthMethodSetting := "global-auth-method"
|
globalExternalAuthMethodSetting := "global-auth-method"
|
||||||
|
|
Loading…
Reference in a new issue