diff --git a/README.md b/README.md index 7d0ed3d78e..0a27bb7d8d 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,23 @@ nginx based ingress controller to server all icp management service The design doc is [here](https://github.ibm.com/IBMPrivateCloud/roadmap/blob/master/feature-specs/icp-router-refactor.md) + +# annotations + +| Name | Description | Values | +| --- | --- | --- | +| icp.management.ibm.com/auth-type | Authentication method for management service | string | +| icp.management.ibm.com/authz-type | Authorization method for management service | string | +| icp.management.ibm.com/rewrite-target | Target URI where the traffic must be redirected | string | +| icp.management.ibm.com/app-root | Base URI fort the server | string | +| icp.management.ibm.com/configuration-snippet | Additional configuration to the NGINX location | string | +| icp.management.ibm.com/secure-backends | uses https to reach the services | bool | +| icp.management.ibm.com/secure-verify-ca-secret | secret name that stores ca cert for upstream service | string | +| icp.management.ibm.com/secure-client-ca-secret | secret name that stores ca cert/key for client authentication of upstream server | string | +| icp.management.ibm.com/upstream-uri | URI of upstream | string | +| icp.management.ibm.com/location-modifier | Location modifier | string | +| icp.management.ibm.com/proxy-connect-timeout | proxy connect timeout | string | +| icp.management.ibm.com/proxy-send-timeout | proxy send timeout | string | +| icp.management.ibm.com/proxy-read-timeout | proxy read timeout | string | +| icp.management.ibm.com/proxy-buffer-size | buffer size of response | string | +| icp.management.ibm.com/proxy-body-size | max response body | string | diff --git a/examples/metering-ingress.yaml b/examples/metering-ingress.yaml index 6d833f4331..b06021deb7 100644 --- a/examples/metering-ingress.yaml +++ b/examples/metering-ingress.yaml @@ -6,6 +6,7 @@ metadata: annotations: kubernetes.io/ingress.class: "ibm-icp-management" icp.management.ibm.com/auth-type: "id-token" + icp.management.ibm.com/rewrite-target: "/" spec: rules: - http: diff --git a/pkg/ingress/annotations/annotations.go b/pkg/ingress/annotations/annotations.go index b56c0fbec7..bb313f8b38 100644 --- a/pkg/ingress/annotations/annotations.go +++ b/pkg/ingress/annotations/annotations.go @@ -27,6 +27,7 @@ import ( "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/authz" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/locationmodifier" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/parser" + "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/proxy" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/rewrite" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/secureupstream" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/snippet" @@ -52,6 +53,7 @@ type Ingress struct { Rewrite rewrite.Config SecureUpstream secureupstream.Config XForwardedPrefix bool + Proxy proxy.Config } // Extractor defines the annotation parsers to be used in the extraction of annotations @@ -72,6 +74,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { "XForwardedPrefix": xforwardedprefix.NewParser(cfg), "LocationModifier": locationmodifier.NewParser(cfg), "UpstreamURI": upstreamuri.NewParser(cfg), + "Proxy": proxy.NewParser(cfg), }, } } diff --git a/pkg/ingress/annotations/proxy/main.go b/pkg/ingress/annotations/proxy/main.go new file mode 100644 index 0000000000..6de00bebcf --- /dev/null +++ b/pkg/ingress/annotations/proxy/main.go @@ -0,0 +1,108 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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 proxy + +import ( + extensions "k8s.io/api/extensions/v1beta1" + + "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/parser" + "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/resolver" +) + +var DefaultProxyConfig = Config{ + BodySize: "1m", + ConnectTimeout: 5, + SendTimeout: 60, + ReadTimeout: 60, + BufferSize: "4k", +} + +// Config returns the proxy timeout to use in the upstream server/s +type Config struct { + BodySize string `json:"bodySize"` + ConnectTimeout int `json:"connectTimeout"` + SendTimeout int `json:"sendTimeout"` + ReadTimeout int `json:"readTimeout"` + BufferSize string `json:"bufferSize"` +} + +// Equal tests for equality between two Configuration types +func (l1 *Config) Equal(l2 *Config) bool { + if l1 == l2 { + return true + } + if l1 == nil || l2 == nil { + return false + } + if l1.BodySize != l2.BodySize { + return false + } + if l1.ConnectTimeout != l2.ConnectTimeout { + return false + } + if l1.SendTimeout != l2.SendTimeout { + return false + } + if l1.ReadTimeout != l2.ReadTimeout { + return false + } + if l1.BufferSize != l2.BufferSize { + return false + } + + return true +} + +type proxy struct { + r resolver.Resolver +} + +// NewParser creates a new reverse proxy configuration annotation parser +func NewParser(r resolver.Resolver) parser.IngressAnnotation { + return proxy{r} +} + +// ParseAnnotations parses the annotations contained in the ingress +// rule used to configure upstream check parameters +func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) { + ct, err := parser.GetIntAnnotation("proxy-connect-timeout", ing) + if err != nil { + ct = DefaultProxyConfig.ConnectTimeout + } + + st, err := parser.GetIntAnnotation("proxy-send-timeout", ing) + if err != nil { + st = DefaultProxyConfig.SendTimeout + } + + rt, err := parser.GetIntAnnotation("proxy-read-timeout", ing) + if err != nil { + rt = DefaultProxyConfig.ReadTimeout + } + + bufs, err := parser.GetStringAnnotation("proxy-buffer-size", ing) + if err != nil || bufs == "" { + bufs = DefaultProxyConfig.BufferSize + } + + bs, err := parser.GetStringAnnotation("proxy-body-size", ing) + if err != nil || bs == "" { + bs = DefaultProxyConfig.BodySize + } + + return &Config{bs, ct, st, rt, bufs}, nil +} diff --git a/pkg/ingress/annotations/proxy/main_test.go b/pkg/ingress/annotations/proxy/main_test.go new file mode 100644 index 0000000000..615f960632 --- /dev/null +++ b/pkg/ingress/annotations/proxy/main_test.go @@ -0,0 +1,139 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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 proxy + +import ( + "testing" + + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/parser" + "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/resolver" +) + +func buildIngress() *extensions.Ingress { + defaultBackend := extensions.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + } + + return &extensions.Ingress{ + ObjectMeta: meta_v1.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, + }, + }, + }, + }, + }, + }, + }, + } +} + +type mockBackend struct { + resolver.Mock +} + +func TestProxy(t *testing.T) { + ing := buildIngress() + + data := map[string]string{} + data[parser.GetAnnotationWithPrefix("proxy-connect-timeout")] = "1" + data[parser.GetAnnotationWithPrefix("proxy-send-timeout")] = "2" + data[parser.GetAnnotationWithPrefix("proxy-read-timeout")] = "3" + data[parser.GetAnnotationWithPrefix("proxy-buffer-size")] = "1k" + data[parser.GetAnnotationWithPrefix("proxy-body-size")] = "2k" + ing.SetAnnotations(data) + + i, err := NewParser(&resolver.Mock{}).Parse(ing) + if err != nil { + t.Fatalf("unexpected error parsing a valid") + } + + p, ok := i.(*Config) + if !ok { + t.Fatalf("expected a Config type") + } + if !ok { + t.Fatalf("expected a Config type") + } + if p.ConnectTimeout != 1 { + t.Errorf("expected 1 as connect-timeout but returned %v", p.ConnectTimeout) + } + if p.SendTimeout != 2 { + t.Errorf("expected 2 as send-timeout but returned %v", p.SendTimeout) + } + if p.ReadTimeout != 3 { + t.Errorf("expected 3 as read-timeout but returned %v", p.ReadTimeout) + } + if p.BufferSize != "1k" { + t.Errorf("expected 1k as buffer-size but returned %v", p.BufferSize) + } + if p.BodySize != "2k" { + t.Errorf("expected 2k as body-size but returned %v", p.BodySize) + } +} + +func TestProxyWithNoAnnotation(t *testing.T) { + ing := buildIngress() + + data := map[string]string{} + ing.SetAnnotations(data) + + i, err := NewParser(&resolver.Mock{}).Parse(ing) + if err != nil { + t.Fatalf("unexpected error parsing a valid") + } + p, ok := i.(*Config) + if !ok { + t.Fatalf("expected a Config type") + } + if p.ConnectTimeout != 5 { + t.Errorf("expected 5 as connect-timeout but returned %v", p.ConnectTimeout) + } + if p.SendTimeout != 60 { + t.Errorf("expected 60 as send-timeout but returned %v", p.SendTimeout) + } + if p.ReadTimeout != 60 { + t.Errorf("expected 60 as read-timeout but returned %v", p.ReadTimeout) + } + if p.BufferSize != "4k" { + t.Errorf("expected 4k as buffer-size but returned %v", p.BufferSize) + } + if p.BodySize != "1m" { + t.Errorf("expected 1m as body-size but returned %v", p.BodySize) + } +} diff --git a/pkg/ingress/controller/controller.go b/pkg/ingress/controller/controller.go index 2664683025..225d3635e1 100644 --- a/pkg/ingress/controller/controller.go +++ b/pkg/ingress/controller/controller.go @@ -26,6 +26,7 @@ import ( "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/class" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/parser" + "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/proxy" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/rewrite" ngx_config "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/controller/config" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/resolver" @@ -296,6 +297,7 @@ func (n *NGINXController) createServers(data []*extensions.Ingress, Rewrite: rewrite.Config{ Target: "/", }, + Proxy: proxy.DefaultProxyConfig, }, }} @@ -464,6 +466,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] loc.Ingress = ing loc.ConfigurationSnippet = anns.ConfigurationSnippet loc.Rewrite = anns.Rewrite + loc.Proxy = anns.Proxy loc.XForwardedPrefix = anns.XForwardedPrefix loc.AuthType = anns.AuthType loc.AuthzType = anns.AuthzType @@ -487,6 +490,7 @@ func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([] Ingress: ing, ConfigurationSnippet: anns.ConfigurationSnippet, Rewrite: anns.Rewrite, + Proxy: anns.Proxy, XForwardedPrefix: anns.XForwardedPrefix, AuthType: anns.AuthType, AuthzType: anns.AuthzType, diff --git a/pkg/ingress/types.go b/pkg/ingress/types.go index 18a768ee4e..0c2976932f 100644 --- a/pkg/ingress/types.go +++ b/pkg/ingress/types.go @@ -15,6 +15,7 @@ import ( extensions "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/util/intstr" + "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/proxy" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/annotations/rewrite" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/resolver" "github.ibm.com/IBMPrivateCloud/icp-management-ingress/pkg/ingress/store" @@ -145,4 +146,8 @@ type Location struct { LocationModifier string `json:"locationModifier,omitempty"` // Upstream uri gives the additional uri to the current path of location UpstreamURI string `json:"upstreamURI,omitempty"` + // Proxy contains information about timeouts and buffer sizes + // to be used in connections against endpoints + // +optional + Proxy proxy.Config `json:"proxy,omitempty"` } diff --git a/pkg/ingress/types_equals.go b/pkg/ingress/types_equals.go index f6f9b13625..5c4beb3546 100644 --- a/pkg/ingress/types_equals.go +++ b/pkg/ingress/types_equals.go @@ -197,6 +197,9 @@ func (l1 *Location) Equal(l2 *Location) bool { if l1.UpstreamURI != l2.UpstreamURI { return false } + if !(&l1.Proxy).Equal(&l2.Proxy) { + return false + } return true } diff --git a/rootfs/opt/ibm/router/nginx/template/nginx.tmpl b/rootfs/opt/ibm/router/nginx/template/nginx.tmpl index eceac1ae38..af5a898ac9 100644 --- a/rootfs/opt/ibm/router/nginx/template/nginx.tmpl +++ b/rootfs/opt/ibm/router/nginx/template/nginx.tmpl @@ -216,6 +216,8 @@ http { set $ingress_name "{{ $ing.Rule }}"; set $service_name "{{ $ing.Service }}"; + client_max_body_size "{{ $location.Proxy.BodySize }}"; + proxy_set_header Host $best_http_host; # Allow websocket connections @@ -237,6 +239,14 @@ http { # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/ proxy_set_header Proxy ""; + proxy_connect_timeout {{ $location.Proxy.ConnectTimeout }}s; + proxy_send_timeout {{ $location.Proxy.SendTimeout }}s; + proxy_read_timeout {{ $location.Proxy.ReadTimeout }}s; + + proxy_buffering off; + proxy_buffer_size "{{ $location.Proxy.BufferSize }}"; + proxy_buffers 4 "{{ $location.Proxy.BufferSize }}"; + {{/* Add any additional configuration defined */}} {{ $location.ConfigurationSnippet }}