diff --git a/controllers/nginx/Dockerfile b/controllers/nginx/Dockerfile index 198c4b3cb..853dbe364 100644 --- a/controllers/nginx/Dockerfile +++ b/controllers/nginx/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM gcr.io/google_containers/nginx-slim:0.4 +FROM gcr.io/google_containers/nginx-slim:0.5 RUN apt-get update && apt-get install -y \ diffutils \ diff --git a/controllers/nginx/Makefile b/controllers/nginx/Makefile index 4b2c6a95c..bebfb4cd3 100644 --- a/controllers/nginx/Makefile +++ b/controllers/nginx/Makefile @@ -1,7 +1,7 @@ all: push # 0.0 shouldn't clobber any release builds -TAG = 0.4 +TAG = 0.5 PREFIX = gcr.io/google_containers/nginx-ingress-controller controller: controller.go clean diff --git a/controllers/nginx/README.md b/controllers/nginx/README.md index c4d2eb6dd..9e27f61d6 100644 --- a/controllers/nginx/README.md +++ b/controllers/nginx/README.md @@ -130,6 +130,27 @@ data: Please check the [tcp services](examples/tcp/README.md) example +## Exposing UDP services + +Since 1.9.13 NGINX provides [UDP Load Balancing](https://www.nginx.com/blog/announcing-udp-load-balancing/). + +Ingress does not support UDP services (yet). For this reason this Ingress controller uses a ConfigMap where the key is the external port to use and the value is +`:` +It is possible to use a number or the name of the port. + +The next example shows how to expose the service `kube-dns` running in the namespace `kube-system` in the port `53` using the port `53` +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: udp-configmap-example +data: + 53: "kube-system/kube-dns:53" +``` + + +Please check the [udp services](examples/udp/README.md) example + ## Custom NGINX configuration diff --git a/controllers/nginx/controller.go b/controllers/nginx/controller.go index e5c064284..31dda25bc 100644 --- a/controllers/nginx/controller.go +++ b/controllers/nginx/controller.go @@ -62,6 +62,7 @@ type loadBalancerController struct { defaultSvc string nxgConfigMap string tcpConfigMap string + udpConfigMap string syncQueue *taskQueue @@ -75,7 +76,7 @@ type loadBalancerController struct { // newLoadBalancerController creates a controller for nginx loadbalancer func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Duration, defaultSvc, - namespace, nxgConfigMapName, tcpConfigMapName string, lbRuntimeInfo *lbInfo) (*loadBalancerController, error) { + namespace, nxgConfigMapName, tcpConfigMapName, udpConfigMapName string, lbRuntimeInfo *lbInfo) (*loadBalancerController, error) { lbc := loadBalancerController{ client: kubeClient, stopCh: make(chan struct{}), @@ -83,6 +84,7 @@ func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Dura nginx: nginx.NewManager(kubeClient), nxgConfigMap: nxgConfigMapName, tcpConfigMap: tcpConfigMapName, + udpConfigMap: udpConfigMapName, defaultSvc: defaultSvc, } @@ -174,6 +176,10 @@ func (lbc *loadBalancerController) getTCPConfigMap(ns, name string) (*api.Config return lbc.client.ConfigMaps(ns).Get(name) } +func (lbc *loadBalancerController) getUDPConfigMap(ns, name string) (*api.ConfigMap, error) { + return lbc.client.ConfigMaps(ns).Get(name) +} + func (lbc *loadBalancerController) sync(key string) { if !lbc.controllersInSync() { lbc.syncQueue.requeue(key, fmt.Errorf("deferring sync till endpoints controller has synced")) @@ -217,10 +223,34 @@ func (lbc *loadBalancerController) getTCPServices() []*nginx.Location { return []*nginx.Location{} } - var tcpSvcs []*nginx.Location + return lbc.getServices(tcpMap.Data, api.ProtocolTCP) +} + +func (lbc *loadBalancerController) getUDPServices() []*nginx.Location { + if lbc.udpConfigMap == "" { + // no configmap for TCP services + return []*nginx.Location{} + } + + ns, name, err := parseNsName(lbc.udpConfigMap) + if err != nil { + glog.Warningf("%v", err) + return []*nginx.Location{} + } + tcpMap, err := lbc.getUDPConfigMap(ns, name) + if err != nil { + glog.V(3).Infof("no configured tcp services found: %v", err) + return []*nginx.Location{} + } + + return lbc.getServices(tcpMap.Data, api.ProtocolUDP) +} + +func (lbc *loadBalancerController) getServices(data map[string]string, proto api.Protocol) []*nginx.Location { + var svcs []*nginx.Location // k -> port to expose in nginx // v -> /: - for k, v := range tcpMap.Data { + for k, v := range data { port, err := strconv.Atoi(k) if err != nil { glog.Warningf("%v is not valid as a TCP port", k) @@ -273,7 +303,7 @@ func (lbc *loadBalancerController) getTCPServices() []*nginx.Location { continue } - tcpSvcs = append(tcpSvcs, &nginx.Location{ + svcs = append(svcs, &nginx.Location{ Path: k, Upstream: nginx.Upstream{ Name: fmt.Sprintf("%v-%v-%v", svcNs, svcName, port), @@ -282,7 +312,7 @@ func (lbc *loadBalancerController) getTCPServices() []*nginx.Location { }) } - return tcpSvcs + return svcs } func (lbc *loadBalancerController) getDefaultUpstream() *nginx.Upstream { diff --git a/controllers/nginx/main.go b/controllers/nginx/main.go index 7149dac53..0511f49e5 100644 --- a/controllers/nginx/main.go +++ b/controllers/nginx/main.go @@ -57,6 +57,12 @@ var ( name of the port. The ports 80 and 443 are not allowed as external ports. This ports are reserved for nginx`) + udpConfigMapName = flags.String("udp-services-configmap", "", + `Name of the ConfigMap that containes the definition of the UDP services to expose. + The key in the map indicates the external port to be used. The value is the name of the + service with the format namespace/serviceName and the port of the service could be a number of the + name of the port.`) + resyncPeriod = flags.Duration("sync-period", 30*time.Second, `Relist and confirm cloud resources this often.`) @@ -99,7 +105,7 @@ func main() { glog.Fatalf("no service with name %v found: %v", *defaultSvc, err) } - lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod, *defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName, lbInfo) + lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod, *defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName, *udpConfigMapName, lbInfo) if err != nil { glog.Fatalf("%v", err) } diff --git a/controllers/nginx/naxsi/basic.rules b/controllers/nginx/naxsi/basic.rules new file mode 100644 index 000000000..a35777d81 --- /dev/null +++ b/controllers/nginx/naxsi/basic.rules @@ -0,0 +1,8 @@ +SecRulesEnabled; +DeniedUrl "/RequestDenied"; +## check rules +CheckRule "$SQL >= 8" BLOCK; +CheckRule "$RFI >= 8" BLOCK; +CheckRule "$TRAVERSAL >= 4" BLOCK; +CheckRule "$EVADE >= 4" BLOCK; +CheckRule "$XSS >= 8" BLOCK; \ No newline at end of file diff --git a/controllers/nginx/nginx.tmpl b/controllers/nginx/nginx.tmpl index 21d498ae5..8b9328f1c 100644 --- a/controllers/nginx/nginx.tmpl +++ b/controllers/nginx/nginx.tmpl @@ -24,6 +24,11 @@ http { require("error_page") } + {{ if $cfg.enableWaf}} + # https://github.com/nbs-system/naxsi/wiki/basicsetup + include /etc/nginx/naxsi/*.rules; + {{ end }} + sendfile on; aio threads; tcp_nopush on; @@ -80,11 +85,6 @@ http { '' $scheme; } - map $http_x_forwarded_proto $pass_forwarded_for { - default $http_x_forwarded_for; - '' $proxy_add_x_forwarded_for; - } - map $pass_access_scheme $sts { 'https' 'max-age={{ $cfg.htsMaxAge }}{{ if $cfg.htsIncludeSubdomains }}; includeSubDomains{{ end }}; preload'; } @@ -202,7 +202,7 @@ http { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; - proxy_set_header X-Forwarded-For $pass_forwarded_for; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-Proto $pass_access_scheme; @@ -219,6 +219,11 @@ http { proxy_pass http://{{ $location.Upstream.Name }}; } {{ end }} + {{ if $cfg.enableWaf}} + location /RequestDenied { + return 418; + } + {{ end }} {{ template "CUSTOM_ERRORS" $cfg }} } {{ end }} @@ -265,8 +270,10 @@ http { } } -# TCP services + stream { + +# TCP services {{ range $i, $tcpServer := .tcpUpstreams }} upstream tcp-{{ $tcpServer.Upstream.Name }} { {{ range $server := $tcpServer.Upstream.Backends }}server {{ $server.Address }}:{{ $server.Port }}; @@ -280,6 +287,22 @@ stream { proxy_pass tcp-{{ $tcpServer.Upstream.Name }}; } {{ end }} + +# UDP services +{{ range $i, $udpServer := .udpUpstreams }} + upstream udp-{{ $udpServer.Upstream.Name }} { + {{ range $server := $tcpServer.Upstream.Backends }}server {{ $server.Address }}:{{ $server.Port }}; + {{ end }} + } + + server { + listen {{ $tcpServer.Path }} udp; + proxy_timeout 1s; + proxy_responses 1; + proxy_pass udp-{{ $tcpServer.Upstream.Name }}; + } +{{ end }} + } {{/* definition of templates to avoid repetitions */}} diff --git a/controllers/nginx/nginx/nginx.go b/controllers/nginx/nginx/nginx.go index 428831133..7f05650a6 100644 --- a/controllers/nginx/nginx/nginx.go +++ b/controllers/nginx/nginx/nginx.go @@ -21,6 +21,7 @@ type IngressConfig struct { Upstreams []*Upstream Servers []*Server TCPUpstreams []*Location + UDPUpstreams []*Location } // Upstream describes an NGINX upstream diff --git a/controllers/nginx/nginx/template.go b/controllers/nginx/nginx/template.go index 0765cff50..fc67cfd1c 100644 --- a/controllers/nginx/nginx/template.go +++ b/controllers/nginx/nginx/template.go @@ -56,6 +56,7 @@ func (ngx *Manager) writeCfg(cfg nginxConfiguration, ingressCfg IngressConfig) ( conf["upstreams"] = ingressCfg.Upstreams conf["servers"] = ingressCfg.Servers conf["tcpUpstreams"] = ingressCfg.TCPUpstreams + conf["udpUpstreams"] = ingressCfg.UDPUpstreams conf["defResolver"] = ngx.defResolver conf["sslDHParam"] = ngx.sslDHParam conf["cfg"] = fixKeyNames(curNginxCfg)