diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md index 101f0ee2d..888856448 100644 --- a/controllers/nginx/configuration.md +++ b/controllers/nginx/configuration.md @@ -64,7 +64,7 @@ The following annotations are supported: |[ingress.kubernetes.io/upstream-max-fails](#custom-nginx-upstream-checks)|number| |[ingress.kubernetes.io/upstream-fail-timeout](#custom-nginx-upstream-checks)|number| |[ingress.kubernetes.io/whitelist-source-range](#whitelist-source-range)|CIDR| - +|[ingress.kubernetes.io/server-alias](#server-alias)|string| #### Custom NGINX template @@ -155,7 +155,7 @@ Please check the [tls-auth](/examples/auth/client-certs/nginx/README.md) example ### Configuration snippet -Using this annotion you can add additional configuration to the NGINX location. For example: +Using this annotation you can add additional configuration to the NGINX location. For example: ``` ingress.kubernetes.io/configuration-snippet: | @@ -167,6 +167,17 @@ ingress.kubernetes.io/configuration-snippet: | To enable Cross-Origin Resource Sharing (CORS) in an Ingress rule add the annotation `ingress.kubernetes.io/enable-cors: "true"`. This will add a section in the server location enabling this functionality. For more information please check https://enable-cors.org/server_nginx.html +### Server Alias + +To add Server Aliases to an Ingress rule add the annotation `ingress.kubernetes.io/server-alias: ":;...;:"`. +This will append a server-alias to the end of the server_name in the NGINX server. A server-alias can accept wildcards, but +it cannot accept port numbers. + +The server-name must match a valid server within the ingress resource for it to append the server-alias. Multiple server-aliases +can be added for multiple server-names using `;` as a delimiter. + +For more information please see http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name + ### External Authentication To use an existing service that provides authentication the Ingress rule can be annotated with `ingress.kubernetes.io/auth-url` to indicate the URL where the HTTP request should be sent. diff --git a/controllers/nginx/pkg/cmd/controller/nginx.go b/controllers/nginx/pkg/cmd/controller/nginx.go index 9f36d770c..990a86901 100644 --- a/controllers/nginx/pkg/cmd/controller/nginx.go +++ b/controllers/nginx/pkg/cmd/controller/nginx.go @@ -452,6 +452,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error { IP: svc.Spec.ClusterIP, Port: port, ProxyProtocol: false, + }) } diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index a3953e38a..4ee4e7155 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -295,7 +295,7 @@ http { {{ $backlogSize := .BacklogSize }} {{ range $index, $server := .Servers }} server { - server_name {{ $server.Hostname }}; + server_name {{ $server.Hostname }} {{ $server.Alias }}; listen 80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{end}}; {{ if $IsIPV6Enabled }}listen [::]:80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $backlogSize }}{{ end }};{{ end }} set $proxy_upstream_name "-"; diff --git a/core/pkg/ingress/annotations/alias/main.go b/core/pkg/ingress/annotations/alias/main.go new file mode 100644 index 000000000..1e56d15f3 --- /dev/null +++ b/core/pkg/ingress/annotations/alias/main.go @@ -0,0 +1,42 @@ +/* +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 alias + +import ( + extensions "k8s.io/api/extensions/v1beta1" + + "k8s.io/ingress/core/pkg/ingress/annotations/parser" +) + +const ( + annotation = "ingress.kubernetes.io/server-alias" +) + +type alias struct { +} + +// NewParser creates a new CORS annotation parser +func NewParser() parser.IngressAnnotation { + return alias{} +} + +// Parse parses the annotations contained in the ingress rule +// used to indicate if the location/s contains a fragment of +// configuration to be included inside the paths of the rules +func (a alias) Parse(ing *extensions.Ingress) (interface{}, error) { + return parser.GetStringAnnotation(annotation, ing) +} \ No newline at end of file diff --git a/core/pkg/ingress/annotations/alias/main_test.go b/core/pkg/ingress/annotations/alias/main_test.go new file mode 100644 index 000000000..de4fe17f5 --- /dev/null +++ b/core/pkg/ingress/annotations/alias/main_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2017 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 alias + +import ( + "testing" + + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestParse(t *testing.T) { + ap := NewParser() + if ap == nil { + t.Fatalf("expected a parser.IngressAnnotation but returned nil") + } + + testCases := []struct { + annotations map[string]string + expected string + }{ + {map[string]string{annotation: "www.example.com"}, "www.example.com"}, + {map[string]string{annotation: "*.example.com www.example.*"}, "*.example.com www.example.*"}, + {map[string]string{annotation: `~^www\d+\.example\.com$`}, `~^www\d+\.example\.com$`}, + {map[string]string{annotation: ""}, ""}, + {map[string]string{}, ""}, + {nil, ""}, + } + + ing := &extensions.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{}, + } + + for _, testCase := range testCases { + ing.SetAnnotations(testCase.annotations) + result, _ := ap.Parse(ing) + if result != testCase.expected { + t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) + } + } +} diff --git a/core/pkg/ingress/controller/annotations.go b/core/pkg/ingress/controller/annotations.go index 7aadb9ddd..a122b2bf7 100644 --- a/core/pkg/ingress/controller/annotations.go +++ b/core/pkg/ingress/controller/annotations.go @@ -37,6 +37,7 @@ import ( "k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough" "k8s.io/ingress/core/pkg/ingress/errors" "k8s.io/ingress/core/pkg/ingress/resolver" + "k8s.io/ingress/core/pkg/ingress/annotations/alias" ) type extractorConfig interface { @@ -69,6 +70,7 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor { "SessionAffinity": sessionaffinity.NewParser(), "SSLPassthrough": sslpassthrough.NewParser(), "ConfigurationSnippet": snippet.NewParser(), + "Alias": alias.NewParser(), }, } } @@ -107,6 +109,7 @@ const ( sslPassthrough = "SSLPassthrough" sessionAffinity = "SessionAffinity" serviceUpstream = "ServiceUpstream" + serverAlias = "Alias" ) func (e *annotationExtractor) ServiceUpstream(ing *extensions.Ingress) bool { @@ -133,6 +136,11 @@ func (e *annotationExtractor) SSLPassthrough(ing *extensions.Ingress) bool { return val.(bool) } +func (e *annotationExtractor) Alias(ing *extensions.Ingress) string { + val, _ := e.annotations[serverAlias].Parse(ing) + return val.(string) +} + func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig { val, _ := e.annotations[sessionAffinity].Parse(ing) return val.(*sessionaffinity.AffinityConfig) diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index 1181c3b2d..c7f6444f4 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -1055,19 +1055,37 @@ func (ic *GenericController) createServers(data []interface{}, } } - // configure default location and SSL + // configure default location, alias, and SSL for _, ingIf := range data { ing := ingIf.(*extensions.Ingress) if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) { continue } + // setup server-aliases based on annotations + aliasMap := map[string]string{} + aliasAnnotation := ic.annotations.Alias(ing) + + // Here we parse the annotation string in the following format: + // ingress.kubernetes.io/server-alias: "host_0:alias_0;...;host_n:alias_n" + aliases := strings.Split(aliasAnnotation, ";") + for _, alias := range aliases { + aliasParts := strings.Split(alias, ":") + if len(aliasParts) == 2 { + // aliasMap[host] = alias + aliasMap[aliasParts[0]] = aliasParts[1] + } + } + for _, rule := range ing.Spec.Rules { host := rule.Host if host == "" { host = defServerName } + // setup server aliases + servers[host].Alias = aliasMap[host] + // only add a certificate if the server does not have one previously configured if len(ing.Spec.TLS) == 0 || servers[host].SSLCertificate != "" { continue diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index 28a30aa07..51e12592e 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -220,6 +220,8 @@ type Server struct { SSLPemChecksum string `json:"sslPemChecksum"` // Locations list of URIs configured in the server. Locations []*Location `json:"locations,omitempty"` + // return the alias of the server name + Alias string `json:"alias,omitempty"` } // Location describes an URI inside a server.