Merge pull request #1144 from aledbf/ip-whitelisting

[nginx-ingress-controller] Add cidr whitelist support
This commit is contained in:
Prashanth B 2016-06-13 18:34:20 -07:00 committed by GitHub
commit 5d49051168
7 changed files with 325 additions and 0 deletions

View file

@ -44,6 +44,7 @@ import (
"k8s.io/contrib/ingress/controllers/nginx/nginx/auth"
"k8s.io/contrib/ingress/controllers/nginx/nginx/config"
"k8s.io/contrib/ingress/controllers/nginx/nginx/healthcheck"
"k8s.io/contrib/ingress/controllers/nginx/nginx/ipwhitelist"
"k8s.io/contrib/ingress/controllers/nginx/nginx/ratelimit"
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
"k8s.io/contrib/ingress/controllers/nginx/nginx/secureupstream"
@ -697,6 +698,12 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
glog.V(3).Infof("error parsing rewrite annotations for Ingress rule %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
}
wl, err := ipwhitelist.ParseAnnotations(ngxCfg.WhitelistSourceRange, ing)
glog.V(3).Infof("nginx white list %v", wl)
if err != nil {
glog.V(3).Infof("error reading white list annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
}
host := rule.Host
if host == "" {
host = defServerName
@ -728,6 +735,7 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
loc.RateLimit = *rl
loc.Redirect = *locRew
loc.SecureUpstream = secUpstream
loc.Whitelist = *wl
addLoc = false
continue
@ -750,6 +758,7 @@ func (lbc *loadBalancerController) getUpstreamServers(ngxCfg config.Configuratio
RateLimit: *rl,
Redirect: *locRew,
SecureUpstream: secUpstream,
Whitelist: *wl,
})
}
}

View file

@ -0,0 +1,122 @@
This example shows how is possible to restrict access
echo "
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: whitelist
annotations:
ingress.kubernetes.io/whitelist-source-range: "1.1.1.1/24"
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /
backend:
serviceName: echoheaders
servicePort: 80
" | kubectl create -f -
Check the annotation is present in the Ingress rule:
```
$ kubectl get ingress whitelist -o yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/whitelist-source-range: 1.1.1.1/24
creationTimestamp: 2016-06-09T21:39:06Z
generation: 2
name: whitelist
namespace: default
resourceVersion: "419363"
selfLink: /apis/extensions/v1beta1/namespaces/default/ingresses/whitelist
uid: 97b74737-2e8a-11e6-90db-080027d2dc94
spec:
rules:
- host: whitelist.bar.com
http:
paths:
- backend:
serviceName: echoheaders
servicePort: 80
path: /
status:
loadBalancer:
ingress:
- ip: 172.17.4.99
``
Finally test is not possible to access the URL
```
$ curl -v http://172.17.4.99/ -H 'Host: whitelist.bar.com'
* Trying 172.17.4.99...
* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
> GET / HTTP/1.1
> Host: whitelist.bar.com
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Server: nginx/1.11.1
< Date: Thu, 09 Jun 2016 21:56:17 GMT
< Content-Type: text/html
< Content-Length: 169
< Connection: keep-alive
<
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.11.1</center>
</body>
</html>
* Connection #0 to host 172.17.4.99 left intact
```
Removing the annotation removes the restriction
```
* Trying 172.17.4.99...
* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
> GET / HTTP/1.1
> Host: whitelist.bar.com
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.11.1
< Date: Thu, 09 Jun 2016 21:57:44 GMT
< Content-Type: text/plain
< Transfer-Encoding: chunked
< Connection: keep-alive
<
CLIENT VALUES:
client_address=10.2.89.7
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://whitelist.bar.com:8080/
SERVER VALUES:
server_version=nginx: 1.9.11 - lua: 10001
HEADERS RECEIVED:
accept=*/*
connection=close
host=whitelist.bar.com
user-agent=curl/7.43.0
x-forwarded-for=10.2.89.1
x-forwarded-host=whitelist.bar.com
x-forwarded-port=80
x-forwarded-proto=http
x-real-ip=10.2.89.1
BODY:
* Connection #0 to host 172.17.4.99 left intact
```

View file

@ -180,6 +180,12 @@ http {
{{- range $location := $server.Locations }}
{{ $path := buildLocation $location }}
location {{ $path }} {
{{ if gt (len $location.Whitelist.CIDR) 0 }}
{{- range $ip := $location.Whitelist.CIDR }}
allow {{ $ip }};{{ end }}
deny all;
{{ end -}}
{{ if (and $server.SSL $location.Redirect.SSLRedirect) -}}
# enforce ssl on server side
if ($scheme = http) {

View file

@ -233,6 +233,10 @@ type Configuration struct {
// Responses with the “text/html” type are always compressed if UseGzip is enabled
GzipTypes string `structs:"gzip-types,omitempty"`
// WhitelistSourceRange allows limiting access to certain client addresses
// http://nginx.org/en/docs/http/ngx_http_access_module.html
WhitelistSourceRange []string `structs:"whitelist-source-range,omitempty"`
// Defines the number of worker processes. By default auto means number of available CPU cores
// http://nginx.org/en/docs/ngx_core_module.html#worker_processes
WorkerProcesses string `structs:"worker-processes,omitempty"`
@ -270,6 +274,7 @@ func NewDefault() Configuration {
VtsStatusZoneSize: "10m",
UseHTTP2: true,
CustomHTTPErrors: make([]int, 0),
WhitelistSourceRange: make([]string, 0),
}
if glog.V(5) {

View file

@ -0,0 +1,83 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 ipwhitelist
import (
"errors"
"strings"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/net/sets"
)
const (
whitelist = "ingress.kubernetes.io/whitelist-source-range"
)
var (
// ErrMissingWhitelist returned error when the ingress does not contains the
// whitelist annotation
ErrMissingWhitelist = errors.New("whitelist annotation is missing")
// ErrInvalidCIDR returned error when the whitelist annotation does not
// contains a valid IP or network address
ErrInvalidCIDR = errors.New("the annotation does not contains a valid IP address or network")
)
// SourceRange returns the CIDR
type SourceRange struct {
CIDR []string
}
type ingAnnotations map[string]string
func (a ingAnnotations) whitelist() ([]string, error) {
val, ok := a[whitelist]
if !ok {
return nil, ErrMissingWhitelist
}
values := strings.Split(val, ",")
ipnets, err := sets.ParseIPNets(values...)
if err != nil {
return nil, ErrInvalidCIDR
}
cidrs := make([]string, 0)
for k := range ipnets {
cidrs = append(cidrs, k)
}
return cidrs, nil
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to limit access to certain client addresses or networks.
// Multiple ranges can specified using commas as separator
// e.g. `18.0.0.0/8,56.0.0.0/8`
func ParseAnnotations(whiteList []string, ing *extensions.Ingress) (*SourceRange, error) {
if ing.GetAnnotations() == nil {
return &SourceRange{whiteList}, ErrMissingWhitelist
}
wl, err := ingAnnotations(ing.GetAnnotations()).whitelist()
if err != nil {
wl = whiteList
}
return &SourceRange{wl}, err
}

View file

@ -0,0 +1,98 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 ipwhitelist
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.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 TestAnnotations(t *testing.T) {
ing := buildIngress()
_, err := ingAnnotations(ing.GetAnnotations()).whitelist()
if err == nil {
t.Error("Expected a validation error")
}
testNet := "10.0.0.0/24"
enet := []string{testNet}
data := map[string]string{}
data[whitelist] = testNet
ing.SetAnnotations(data)
wl, err := ingAnnotations(ing.GetAnnotations()).whitelist()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(wl, enet) {
t.Errorf("Expected %v but returned %s", enet, wl)
}
data[whitelist] = "10.0.0.0/24,10.0.1.0/25"
ing.SetAnnotations(data)
wl, err = ingAnnotations(ing.GetAnnotations()).whitelist()
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(wl) != 2 {
t.Errorf("Expected 2 netwotks but %v was returned", len(wl))
}
}

View file

@ -18,6 +18,7 @@ package nginx
import (
"k8s.io/contrib/ingress/controllers/nginx/nginx/auth"
"k8s.io/contrib/ingress/controllers/nginx/nginx/ipwhitelist"
"k8s.io/contrib/ingress/controllers/nginx/nginx/ratelimit"
"k8s.io/contrib/ingress/controllers/nginx/nginx/rewrite"
)
@ -99,6 +100,7 @@ type Location struct {
RateLimit ratelimit.RateLimit
Redirect rewrite.Redirect
SecureUpstream bool
Whitelist ipwhitelist.SourceRange
}
// LocationByPath sorts location by path