Skip to content

Commit 8cc9afe

Browse files
Added Global External Authentication settings to configmap parameters incl. addons
1 parent b4f2880 commit 8cc9afe

File tree

20 files changed

+814
-67
lines changed

20 files changed

+814
-67
lines changed

Diff for: docs/user-guide/nginx-configuration/annotations.md

100644100755
+9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
2727
|[nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream](#client-certificate-authentication)|"true" or "false"|
2828
|[nginx.ingress.kubernetes.io/auth-url](#external-authentication)|string|
2929
|[nginx.ingress.kubernetes.io/auth-snippet](#external-authentication)|string|
30+
|[nginx.ingress.kubernetes.io/enable-global-auth](#external-authentication)|"true" or "false"|
3031
|[nginx.ingress.kubernetes.io/backend-protocol](#backend-protocol)|string|HTTP,HTTPS,GRPC,GRPCS,AJP|
3132
|[nginx.ingress.kubernetes.io/canary](#canary)|"true" or "false"|
3233
|[nginx.ingress.kubernetes.io/canary-by-header](#canary)|string|
@@ -389,6 +390,14 @@ nginx.ingress.kubernetes.io/auth-snippet: |
389390
!!! example
390391
Please check the [external-auth](../../examples/auth/external-auth/README.md) example.
391392

393+
#### Global External Authentication
394+
395+
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.
396+
`nginx.ingress.kubernetes.io/enable-global-auth`:
397+
indicates if GlobalExternalAuth configuration should be applied or not to this Ingress rule. Default values is set to `"true"`.
398+
399+
!!! note For more information please see [global-auth-url](./configmap.md#global-auth-url).
400+
392401
### Rate limiting
393402

394403
These annotations define a limit on the connections that can be opened by a single client IP address.

Diff for: docs/user-guide/nginx-configuration/configmap.md

100644100755
+45
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ The following table shows a configuration option's name, type, and the default v
152152
|[limit-req-status-code](#limit-req-status-code)|int|503|
153153
|[limit-conn-status-code](#limit-conn-status-code)|int|503|
154154
|[no-tls-redirect-locations](#no-tls-redirect-locations)|string|"/.well-known/acme-challenge"|
155+
|[global-auth-url](#global-auth-url)|string|""|
156+
|[global-auth-method](#global-auth-method)|string|""|
157+
|[global-auth-signin](#global-auth-signin)|string|""|
158+
|[global-auth-response-headers](#global-auth-response-headers)|string|""|
159+
|[global-auth-request-redirect](#global-auth-request-redirect)|string|""|
160+
|[global-auth-snippet](#global-auth-snippet)|string|""|
155161
|[no-auth-locations](#no-auth-locations)|string|"/.well-known/acme-challenge"|
156162
|[block-cidrs](#block-cidrs)|[]string|""|
157163
|[block-user-agents](#block-user-agents)|[]string|""|
@@ -864,6 +870,45 @@ Sets the [status code to return in response to rejected connections](http://ngin
864870
A comma-separated list of locations on which http requests will never get redirected to their https counterpart.
865871
_**default:**_ "/.well-known/acme-challenge"
866872

873+
## global-auth-url
874+
875+
A url to an existing service that provides authentication for all the locations.
876+
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-url`.
877+
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".
878+
_**default:**_ ""
879+
880+
_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)
881+
882+
## global-auth-method
883+
884+
A HTTP method to use for an existing service that provides authentication for all the locations.
885+
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-method`.
886+
_**default:**_ ""
887+
888+
## global-auth-signin
889+
890+
Sets the location of the error page for an existing service that provides authentication for all the locations.
891+
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-signin`.
892+
_**default:**_ ""
893+
894+
## global-auth-response-headers
895+
896+
Sets the headers to pass to backend once authentication request completes. Applied to all the locations.
897+
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-response-headers`.
898+
_**default:**_ ""
899+
900+
## global-auth-request-redirect
901+
902+
Sets the X-Auth-Request-Redirect header value. Applied to all the locations.
903+
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-request-redirect`.
904+
_**default:**_ ""
905+
906+
## global-auth-snippet
907+
908+
Sets a custom snippet to use with external authentication. Applied to all the locations.
909+
Similar to the Ingress rule annotation `nginx.ingress.kubernetes.io/auth-request-redirect`.
910+
_**default:**_ ""
911+
867912
## no-auth-locations
868913

869914
A comma-separated list of locations that should not get authenticated.

Diff for: internal/ingress/annotations/annotations.go

100644100755
+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"k8s.io/ingress-nginx/internal/ingress/annotations/alias"
3131
"k8s.io/ingress-nginx/internal/ingress/annotations/auth"
3232
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
33+
"k8s.io/ingress-nginx/internal/ingress/annotations/authreqglobal"
3334
"k8s.io/ingress-nginx/internal/ingress/annotations/authtls"
3435
"k8s.io/ingress-nginx/internal/ingress/annotations/backendprotocol"
3536
"k8s.io/ingress-nginx/internal/ingress/annotations/clientbodybuffersize"
@@ -83,6 +84,7 @@ type Ingress struct {
8384
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
8485
Denied *string
8586
ExternalAuth authreq.Config
87+
EnableGlobalAuth bool
8688
HTTP2PushPreload bool
8789
Proxy proxy.Config
8890
RateLimit ratelimit.Config
@@ -127,6 +129,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
127129
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
128130
"DefaultBackend": defaultbackend.NewParser(cfg),
129131
"ExternalAuth": authreq.NewParser(cfg),
132+
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
130133
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
131134
"Proxy": proxy.NewParser(cfg),
132135
"RateLimit": ratelimit.NewParser(cfg),

Diff for: internal/ingress/annotations/authreq/main.go

100644100755
+29-16
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package authreq
1818

1919
import (
20+
"fmt"
2021
"net/url"
2122
"regexp"
2223
"strings"
@@ -84,7 +85,8 @@ var (
8485
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
8586
)
8687

87-
func validMethod(method string) bool {
88+
// ValidMethod checks is the provided string a valid HTTP method
89+
func ValidMethod(method string) bool {
8890
if len(method) == 0 {
8991
return false
9092
}
@@ -97,7 +99,8 @@ func validMethod(method string) bool {
9799
return false
98100
}
99101

100-
func validHeader(header string) bool {
102+
// ValidHeader checks is the provided string satisfies the header's name regex
103+
func ValidHeader(header string) bool {
101104
return headerRegexp.Match([]byte(header))
102105
}
103106

@@ -119,22 +122,13 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
119122
return nil, err
120123
}
121124

122-
authURL, err := url.Parse(urlString)
123-
if err != nil {
124-
return nil, err
125-
}
126-
if authURL.Scheme == "" {
127-
return nil, ing_errors.NewLocationDenied("url scheme is empty")
128-
}
129-
if authURL.Host == "" {
130-
return nil, ing_errors.NewLocationDenied("url host is empty")
131-
}
132-
if strings.Contains(authURL.Host, "..") {
133-
return nil, ing_errors.NewLocationDenied("invalid url host")
125+
authURL, message := ParseStringToURL(urlString)
126+
if authURL == nil {
127+
return nil, ing_errors.NewLocationDenied(message)
134128
}
135129

136130
authMethod, _ := parser.GetStringAnnotation("auth-method", ing)
137-
if len(authMethod) != 0 && !validMethod(authMethod) {
131+
if len(authMethod) != 0 && !ValidMethod(authMethod) {
138132
return nil, ing_errors.NewLocationDenied("invalid HTTP method")
139133
}
140134

@@ -156,7 +150,7 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
156150
for _, header := range harr {
157151
header = strings.TrimSpace(header)
158152
if len(header) > 0 {
159-
if !validHeader(header) {
153+
if !ValidHeader(header) {
160154
return nil, ing_errors.NewLocationDenied("invalid headers list")
161155
}
162156
responseHeaders = append(responseHeaders, header)
@@ -176,3 +170,22 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
176170
AuthSnippet: authSnippet,
177171
}, nil
178172
}
173+
174+
// ParseStringToURL parses the provided string into URL and returns error
175+
// message in case of failure
176+
func ParseStringToURL(input string) (*url.URL, string) {
177+
178+
parsedURL, err := url.Parse(input)
179+
if err != nil {
180+
return nil, fmt.Sprintf("%v is not a valid URL: %v", input, err)
181+
}
182+
if parsedURL.Scheme == "" {
183+
return nil, "url scheme is empty."
184+
} else if parsedURL.Host == "" {
185+
return nil, "url host is empty."
186+
} else if strings.Contains(parsedURL.Host, "..") {
187+
return nil, "invalid url host."
188+
}
189+
return parsedURL, ""
190+
191+
}

Diff for: internal/ingress/annotations/authreq/main_test.go

100644100755
+36
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package authreq
1818

1919
import (
2020
"fmt"
21+
"net/url"
2122
"reflect"
2223
"testing"
2324

@@ -178,3 +179,38 @@ func TestHeaderAnnotations(t *testing.T) {
178179
}
179180
}
180181
}
182+
183+
func TestParseStringToURL(t *testing.T) {
184+
validURL := "http://bar.foo.com/external-auth"
185+
validParsedURL, _ := url.Parse(validURL)
186+
187+
tests := []struct {
188+
title string
189+
url string
190+
message string
191+
parsed *url.URL
192+
expErr bool
193+
}{
194+
{"empty", "", "url scheme is empty.", nil, true},
195+
{"no scheme", "bar", "url scheme is empty.", nil, true},
196+
{"invalid host", "http://", "url host is empty.", nil, true},
197+
{"invalid host (multiple dots)", "http://foo..bar.com", "invalid url host.", nil, true},
198+
{"valid URL", validURL, "", validParsedURL, false},
199+
}
200+
201+
for _, test := range tests {
202+
203+
i, err := ParseStringToURL(test.url)
204+
if test.expErr {
205+
if err != test.message {
206+
t.Errorf("%v: expected error \"%v\" but \"%v\" was returned", test.title, test.message, err)
207+
}
208+
continue
209+
}
210+
211+
if i.String() != test.parsed.String() {
212+
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.parsed, i)
213+
}
214+
}
215+
216+
}

Diff for: internal/ingress/annotations/authreqglobal/main.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Copyright 2015 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package authreqglobal
18+
19+
import (
20+
extensions "k8s.io/api/extensions/v1beta1"
21+
22+
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
23+
"k8s.io/ingress-nginx/internal/ingress/resolver"
24+
)
25+
26+
type authReqGlobal struct {
27+
r resolver.Resolver
28+
}
29+
30+
// NewParser creates a new authentication request annotation parser
31+
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
32+
return authReqGlobal{r}
33+
}
34+
35+
// ParseAnnotations parses the annotations contained in the ingress
36+
// rule used to enable or disable global external authentication
37+
func (a authReqGlobal) Parse(ing *extensions.Ingress) (interface{}, error) {
38+
39+
enableGlobalAuth, err := parser.GetBoolAnnotation("enable-global-auth", ing)
40+
if err != nil {
41+
enableGlobalAuth = true
42+
}
43+
44+
return enableGlobalAuth, nil
45+
}
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
Copyright 2015 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package authreqglobal
18+
19+
import (
20+
"testing"
21+
22+
api "k8s.io/api/core/v1"
23+
extensions "k8s.io/api/extensions/v1beta1"
24+
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
26+
"k8s.io/ingress-nginx/internal/ingress/resolver"
27+
28+
"k8s.io/apimachinery/pkg/util/intstr"
29+
)
30+
31+
func buildIngress() *extensions.Ingress {
32+
defaultBackend := extensions.IngressBackend{
33+
ServiceName: "default-backend",
34+
ServicePort: intstr.FromInt(80),
35+
}
36+
37+
return &extensions.Ingress{
38+
ObjectMeta: meta_v1.ObjectMeta{
39+
Name: "foo",
40+
Namespace: api.NamespaceDefault,
41+
},
42+
Spec: extensions.IngressSpec{
43+
Backend: &extensions.IngressBackend{
44+
ServiceName: "default-backend",
45+
ServicePort: intstr.FromInt(80),
46+
},
47+
Rules: []extensions.IngressRule{
48+
{
49+
Host: "foo.bar.com",
50+
IngressRuleValue: extensions.IngressRuleValue{
51+
HTTP: &extensions.HTTPIngressRuleValue{
52+
Paths: []extensions.HTTPIngressPath{
53+
{
54+
Path: "/foo",
55+
Backend: defaultBackend,
56+
},
57+
},
58+
},
59+
},
60+
},
61+
},
62+
},
63+
}
64+
}
65+
66+
func TestAnnotation(t *testing.T) {
67+
ing := buildIngress()
68+
69+
data := map[string]string{}
70+
data[parser.GetAnnotationWithPrefix("auth-url")] = "http://foo.com/external-auth"
71+
data[parser.GetAnnotationWithPrefix("enable-global-auth")] = "false"
72+
ing.SetAnnotations(data)
73+
74+
i, _ := NewParser(&resolver.Mock{}).Parse(ing)
75+
u, ok := i.(bool)
76+
if !ok {
77+
t.Errorf("expected a Config type")
78+
}
79+
if u {
80+
t.Errorf("Expected false but returned true")
81+
}
82+
}

0 commit comments

Comments
 (0)