Skip to content
This repository was archived by the owner on Apr 17, 2019. It is now read-only.

Commit 6c87fed

Browse files
authored
Merge pull request #1144 from aledbf/ip-whitelisting
[nginx-ingress-controller] Add cidr whitelist support
2 parents 6e0e061 + 64f4cfe commit 6c87fed

File tree

7 files changed

+325
-0
lines changed

7 files changed

+325
-0
lines changed

ingress/controllers/nginx/controller.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
"k8s.io/contrib/ingress/controllers/nginx/nginx/auth"
4545
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
4646
"k8s.io/contrib/ingress/controllers/nginx/nginx/healthcheck"
47+
"k8s.io/contrib/ingress/controllers/nginx/nginx/ipwhitelist"
4748
"k8s.io/contrib/ingress/controllers/nginx/nginx/ratelimit"
4849
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
4950
"k8s.io/contrib/ingress/controllers/nginx/nginx/secureupstream"
@@ -697,6 +698,12 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
697698
glog.V(3).Infof("error parsing rewrite annotations for Ingress rule %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
698699
}
699700

701+
wl, err := ipwhitelist.ParseAnnotations(ngxCfg.WhitelistSourceRange, ing)
702+
glog.V(3).Infof("nginx white list %v", wl)
703+
if err != nil {
704+
glog.V(3).Infof("error reading white list annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
705+
}
706+
700707
host := rule.Host
701708
if host == "" {
702709
host = defServerName
@@ -728,6 +735,7 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
728735
loc.RateLimit = *rl
729736
loc.Redirect = *locRew
730737
loc.SecureUpstream = secUpstream
738+
loc.Whitelist = *wl
731739

732740
addLoc = false
733741
continue
@@ -750,6 +758,7 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
750758
RateLimit: *rl,
751759
Redirect: *locRew,
752760
SecureUpstream: secUpstream,
761+
Whitelist: *wl,
753762
})
754763
}
755764
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
2+
This example shows how is possible to restrict access
3+
4+
echo "
5+
apiVersion: extensions/v1beta1
6+
kind: Ingress
7+
metadata:
8+
name: whitelist
9+
annotations:
10+
ingress.kubernetes.io/whitelist-source-range: "1.1.1.1/24"
11+
spec:
12+
rules:
13+
- host: foo.bar.com
14+
http:
15+
paths:
16+
- path: /
17+
backend:
18+
serviceName: echoheaders
19+
servicePort: 80
20+
" | kubectl create -f -
21+
22+
23+
Check the annotation is present in the Ingress rule:
24+
```
25+
$ kubectl get ingress whitelist -o yaml
26+
apiVersion: extensions/v1beta1
27+
kind: Ingress
28+
metadata:
29+
annotations:
30+
ingress.kubernetes.io/whitelist-source-range: 1.1.1.1/24
31+
creationTimestamp: 2016-06-09T21:39:06Z
32+
generation: 2
33+
name: whitelist
34+
namespace: default
35+
resourceVersion: "419363"
36+
selfLink: /apis/extensions/v1beta1/namespaces/default/ingresses/whitelist
37+
uid: 97b74737-2e8a-11e6-90db-080027d2dc94
38+
spec:
39+
rules:
40+
- host: whitelist.bar.com
41+
http:
42+
paths:
43+
- backend:
44+
serviceName: echoheaders
45+
servicePort: 80
46+
path: /
47+
status:
48+
loadBalancer:
49+
ingress:
50+
- ip: 172.17.4.99
51+
``
52+
53+
Finally test is not possible to access the URL
54+
55+
```
56+
$ curl -v http://172.17.4.99/ -H 'Host: whitelist.bar.com'
57+
* Trying 172.17.4.99...
58+
* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
59+
> GET / HTTP/1.1
60+
> Host: whitelist.bar.com
61+
> User-Agent: curl/7.43.0
62+
> Accept: */*
63+
>
64+
< HTTP/1.1 403 Forbidden
65+
< Server: nginx/1.11.1
66+
< Date: Thu, 09 Jun 2016 21:56:17 GMT
67+
< Content-Type: text/html
68+
< Content-Length: 169
69+
< Connection: keep-alive
70+
<
71+
<html>
72+
<head><title>403 Forbidden</title></head>
73+
<body bgcolor="white">
74+
<center><h1>403 Forbidden</h1></center>
75+
<hr><center>nginx/1.11.1</center>
76+
</body>
77+
</html>
78+
* Connection #0 to host 172.17.4.99 left intact
79+
```
80+
81+
Removing the annotation removes the restriction
82+
83+
```
84+
* Trying 172.17.4.99...
85+
* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
86+
> GET / HTTP/1.1
87+
> Host: whitelist.bar.com
88+
> User-Agent: curl/7.43.0
89+
> Accept: */*
90+
>
91+
< HTTP/1.1 200 OK
92+
< Server: nginx/1.11.1
93+
< Date: Thu, 09 Jun 2016 21:57:44 GMT
94+
< Content-Type: text/plain
95+
< Transfer-Encoding: chunked
96+
< Connection: keep-alive
97+
<
98+
CLIENT VALUES:
99+
client_address=10.2.89.7
100+
command=GET
101+
real path=/
102+
query=nil
103+
request_version=1.1
104+
request_uri=http://whitelist.bar.com:8080/
105+
106+
SERVER VALUES:
107+
server_version=nginx: 1.9.11 - lua: 10001
108+
109+
HEADERS RECEIVED:
110+
accept=*/*
111+
connection=close
112+
host=whitelist.bar.com
113+
user-agent=curl/7.43.0
114+
x-forwarded-for=10.2.89.1
115+
x-forwarded-host=whitelist.bar.com
116+
x-forwarded-port=80
117+
x-forwarded-proto=http
118+
x-real-ip=10.2.89.1
119+
BODY:
120+
* Connection #0 to host 172.17.4.99 left intact
121+
```
122+

ingress/controllers/nginx/nginx.tmpl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ http {
180180
{{- range $location := $server.Locations }}
181181
{{ $path := buildLocation $location }}
182182
location {{ $path }} {
183+
{{ if gt (len $location.Whitelist.CIDR) 0 }}
184+
{{- range $ip := $location.Whitelist.CIDR }}
185+
allow {{ $ip }};{{ end }}
186+
deny all;
187+
{{ end -}}
188+
183189
{{ if (and $server.SSL $location.Redirect.SSLRedirect) -}}
184190
# enforce ssl on server side
185191
if ($scheme = http) {

ingress/controllers/nginx/nginx/config/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ type Configuration struct {
233233
// Responses with the “text/html” type are always compressed if UseGzip is enabled
234234
GzipTypes string `structs:"gzip-types,omitempty"`
235235

236+
// WhitelistSourceRange allows limiting access to certain client addresses
237+
// http://nginx.org/en/docs/http/ngx_http_access_module.html
238+
WhitelistSourceRange []string `structs:"whitelist-source-range,omitempty"`
239+
236240
// Defines the number of worker processes. By default auto means number of available CPU cores
237241
// http://nginx.org/en/docs/ngx_core_module.html#worker_processes
238242
WorkerProcesses string `structs:"worker-processes,omitempty"`
@@ -270,6 +274,7 @@ func NewDefault() Configuration {
270274
VtsStatusZoneSize: "10m",
271275
UseHTTP2: true,
272276
CustomHTTPErrors: make([]int, 0),
277+
WhitelistSourceRange: make([]string, 0),
273278
}
274279

275280
if glog.V(5) {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Copyright 2016 The Kubernetes Authors All rights reserved.
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 ipwhitelist
18+
19+
import (
20+
"errors"
21+
"strings"
22+
23+
"k8s.io/kubernetes/pkg/apis/extensions"
24+
"k8s.io/kubernetes/pkg/util/net/sets"
25+
)
26+
27+
const (
28+
whitelist = "ingress.kubernetes.io/whitelist-source-range"
29+
)
30+
31+
var (
32+
// ErrMissingWhitelist returned error when the ingress does not contains the
33+
// whitelist annotation
34+
ErrMissingWhitelist = errors.New("whitelist annotation is missing")
35+
36+
// ErrInvalidCIDR returned error when the whitelist annotation does not
37+
// contains a valid IP or network address
38+
ErrInvalidCIDR = errors.New("the annotation does not contains a valid IP address or network")
39+
)
40+
41+
// SourceRange returns the CIDR
42+
type SourceRange struct {
43+
CIDR []string
44+
}
45+
46+
type ingAnnotations map[string]string
47+
48+
func (a ingAnnotations) whitelist() ([]string, error) {
49+
val, ok := a[whitelist]
50+
if !ok {
51+
return nil, ErrMissingWhitelist
52+
}
53+
54+
values := strings.Split(val, ",")
55+
ipnets, err := sets.ParseIPNets(values...)
56+
if err != nil {
57+
return nil, ErrInvalidCIDR
58+
}
59+
60+
cidrs := make([]string, 0)
61+
for k := range ipnets {
62+
cidrs = append(cidrs, k)
63+
}
64+
65+
return cidrs, nil
66+
}
67+
68+
// ParseAnnotations parses the annotations contained in the ingress
69+
// rule used to limit access to certain client addresses or networks.
70+
// Multiple ranges can specified using commas as separator
71+
// e.g. `18.0.0.0/8,56.0.0.0/8`
72+
func ParseAnnotations(whiteList []string, ing *extensions.Ingress) (*SourceRange, error) {
73+
if ing.GetAnnotations() == nil {
74+
return &SourceRange{whiteList}, ErrMissingWhitelist
75+
}
76+
77+
wl, err := ingAnnotations(ing.GetAnnotations()).whitelist()
78+
if err != nil {
79+
wl = whiteList
80+
}
81+
82+
return &SourceRange{wl}, err
83+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
Copyright 2016 The Kubernetes Authors All rights reserved.
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 ipwhitelist
18+
19+
import (
20+
"reflect"
21+
"testing"
22+
23+
"k8s.io/kubernetes/pkg/api"
24+
"k8s.io/kubernetes/pkg/apis/extensions"
25+
"k8s.io/kubernetes/pkg/util/intstr"
26+
)
27+
28+
func buildIngress() *extensions.Ingress {
29+
defaultBackend := extensions.IngressBackend{
30+
ServiceName: "default-backend",
31+
ServicePort: intstr.FromInt(80),
32+
}
33+
34+
return &extensions.Ingress{
35+
ObjectMeta: api.ObjectMeta{
36+
Name: "foo",
37+
Namespace: api.NamespaceDefault,
38+
},
39+
Spec: extensions.IngressSpec{
40+
Backend: &extensions.IngressBackend{
41+
ServiceName: "default-backend",
42+
ServicePort: intstr.FromInt(80),
43+
},
44+
Rules: []extensions.IngressRule{
45+
{
46+
Host: "foo.bar.com",
47+
IngressRuleValue: extensions.IngressRuleValue{
48+
HTTP: &extensions.HTTPIngressRuleValue{
49+
Paths: []extensions.HTTPIngressPath{
50+
{
51+
Path: "/foo",
52+
Backend: defaultBackend,
53+
},
54+
},
55+
},
56+
},
57+
},
58+
},
59+
},
60+
}
61+
}
62+
63+
func TestAnnotations(t *testing.T) {
64+
ing := buildIngress()
65+
66+
_, err := ingAnnotations(ing.GetAnnotations()).whitelist()
67+
if err == nil {
68+
t.Error("Expected a validation error")
69+
}
70+
71+
testNet := "10.0.0.0/24"
72+
enet := []string{testNet}
73+
74+
data := map[string]string{}
75+
data[whitelist] = testNet
76+
ing.SetAnnotations(data)
77+
78+
wl, err := ingAnnotations(ing.GetAnnotations()).whitelist()
79+
if err != nil {
80+
t.Errorf("Unexpected error: %v", err)
81+
}
82+
83+
if !reflect.DeepEqual(wl, enet) {
84+
t.Errorf("Expected %v but returned %s", enet, wl)
85+
}
86+
87+
data[whitelist] = "10.0.0.0/24,10.0.1.0/25"
88+
ing.SetAnnotations(data)
89+
90+
wl, err = ingAnnotations(ing.GetAnnotations()).whitelist()
91+
if err != nil {
92+
t.Errorf("Unexpected error: %v", err)
93+
}
94+
95+
if len(wl) != 2 {
96+
t.Errorf("Expected 2 netwotks but %v was returned", len(wl))
97+
}
98+
}

ingress/controllers/nginx/nginx/nginx.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package nginx
1818

1919
import (
2020
"k8s.io/contrib/ingress/controllers/nginx/nginx/auth"
21+
"k8s.io/contrib/ingress/controllers/nginx/nginx/ipwhitelist"
2122
"k8s.io/contrib/ingress/controllers/nginx/nginx/ratelimit"
2223
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
2324
)
@@ -99,6 +100,7 @@ type Location struct {
99100
RateLimit ratelimit.RateLimit
100101
Redirect rewrite.Redirect
101102
SecureUpstream bool
103+
Whitelist ipwhitelist.SourceRange
102104
}
103105

104106
// LocationByPath sorts location by path

0 commit comments

Comments
 (0)