Merge pull request #1553 from estaleiro/cors-improvement
Cors features improvements
This commit is contained in:
commit
e2790c8f6f
10 changed files with 277 additions and 83 deletions
|
@ -52,12 +52,20 @@ Key:
|
||||||
| `base-url-scheme` | Specify the scheme of the `<base>` tags. | | nginx
|
| `base-url-scheme` | Specify the scheme of the `<base>` tags. | | nginx
|
||||||
| `preserve-host` | Whether to pass the client request host (`true`) or the origin hostname (`false`) in the HTTP Host field. | | trafficserver
|
| `preserve-host` | Whether to pass the client request host (`true`) or the origin hostname (`false`) in the HTTP Host field. | | trafficserver
|
||||||
|
|
||||||
|
## CORS Related
|
||||||
|
| Name | Meaning | Default | Controller
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `enable-cors` | Enable CORS headers in response. | false | nginx, voyager
|
||||||
|
| `cors-allow-origin` | Specifies the Origin allowed in CORS (Access-Control-Allow-Origin) | * | nginx
|
||||||
|
| `cors-allow-headers` | Specifies the Headers allowed in CORS (Access-Control-Allow-Headers) | DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization | nginx
|
||||||
|
| `cors-allow-methods` | Specifies the Methods allowed in CORS (Access-Control-Allow-Methods) | GET, PUT, POST, DELETE, PATCH, OPTIONS | nginx
|
||||||
|
| `cors-allow-credentials` | Specifies the Access-Control-Allow-Credentials | true | nginx
|
||||||
|
|
||||||
## Miscellaneous
|
## Miscellaneous
|
||||||
|
|
||||||
| Name | Meaning | Default | Controller
|
| Name | Meaning | Default | Controller
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `configuration-snippet` | Arbitrary text to put in the generated configuration file. | | nginx
|
| `configuration-snippet` | Arbitrary text to put in the generated configuration file. | | nginx
|
||||||
| `enable-cors` | Enable CORS headers in response. | | nginx, voyager
|
|
||||||
| `limit-connections` | Limit concurrent connections per IP address[1]. | | nginx, voyager
|
| `limit-connections` | Limit concurrent connections per IP address[1]. | | nginx, voyager
|
||||||
| `limit-rps` | Limit requests per second per IP address[1]. | | nginx, voyager
|
| `limit-rps` | Limit requests per second per IP address[1]. | | nginx, voyager
|
||||||
| `limit-rpm` | Limit requests per minute per IP address. | | nginx, voyager
|
| `limit-rpm` | Limit requests per minute per IP address. | | nginx, voyager
|
||||||
|
|
|
@ -20,6 +20,10 @@ The following annotations are supported:
|
||||||
|[ingress.kubernetes.io/configuration-snippet](#configuration-snippet)|string|
|
|[ingress.kubernetes.io/configuration-snippet](#configuration-snippet)|string|
|
||||||
|[ingress.kubernetes.io/default-backend](#default-backend)|string|
|
|[ingress.kubernetes.io/default-backend](#default-backend)|string|
|
||||||
|[ingress.kubernetes.io/enable-cors](#enable-cors)|true or false|
|
|[ingress.kubernetes.io/enable-cors](#enable-cors)|true or false|
|
||||||
|
|[ingress.kubernetes.io/cors-allow-origin](#enable-cors)|string|
|
||||||
|
|[ingress.kubernetes.io/cors-allow-methods](#enable-cors)|string|
|
||||||
|
|[ingress.kubernetes.io/cors-allow-headers](#enable-cors)|string|
|
||||||
|
|[ingress.kubernetes.io/cors-allow-credentials](#enable-cors)|true or false|
|
||||||
|[ingress.kubernetes.io/force-ssl-redirect](#server-side-https-enforcement-through-redirect)|true or false|
|
|[ingress.kubernetes.io/force-ssl-redirect](#server-side-https-enforcement-through-redirect)|true or false|
|
||||||
|[ingress.kubernetes.io/from-to-www-redirect](#redirect-from-to-www)|true or false|
|
|[ingress.kubernetes.io/from-to-www-redirect](#redirect-from-to-www)|true or false|
|
||||||
|[ingress.kubernetes.io/limit-connections](#rate-limiting)|number|
|
|[ingress.kubernetes.io/limit-connections](#rate-limiting)|number|
|
||||||
|
@ -162,6 +166,26 @@ This is a global configuration for the ingress controller. In some cases could b
|
||||||
### Enable CORS
|
### Enable CORS
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
CORS can be controlled with the following annotations:
|
||||||
|
|
||||||
|
* `ingress.kubernetes.io/cors-allow-methods` controls which methods are accepted. This is a multi-valued field, separated by ',' and accepts only letters (upper and lower case).
|
||||||
|
|
||||||
|
Example: `ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"`
|
||||||
|
|
||||||
|
* `ingress.kubernetes.io/cors-allow-headers` controls which headers are accepted. This is a multi-valued field, separated by ',' and accepts letters, numbers, _ and -.
|
||||||
|
|
||||||
|
Example: `ingress.kubernetes.io/cors-allow-methods: "X-Forwarded-For, X-app123-XPTO"`
|
||||||
|
|
||||||
|
* `ingress.kubernetes.io/cors-allow-origin` controls what's the accepted Origin for CORS and defaults to '*'. This is a single field value, with the following format: http(s)://origin-site.com or http(s)://origin-site.com:port
|
||||||
|
|
||||||
|
Example: `ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"`
|
||||||
|
|
||||||
|
* `ingress.kubernetes.io/cors-allow-credentials` controls if credentials can be passed during CORS operations.
|
||||||
|
|
||||||
|
Example: `ingress.kubernetes.io/cors-allow-credentials: "true"`
|
||||||
|
|
||||||
|
|
||||||
For more information please check https://enable-cors.org/server_nginx.html
|
For more information please check https://enable-cors.org/server_nginx.html
|
||||||
|
|
||||||
### Server Alias
|
### Server Alias
|
||||||
|
|
|
@ -17,25 +17,112 @@ limitations under the License.
|
||||||
package cors
|
package cors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
annotation = "ingress.kubernetes.io/enable-cors"
|
annotationCorsEnabled = "ingress.kubernetes.io/enable-cors"
|
||||||
|
annotationCorsAllowOrigin = "ingress.kubernetes.io/cors-allow-origin"
|
||||||
|
annotationCorsAllowMethods = "ingress.kubernetes.io/cors-allow-methods"
|
||||||
|
annotationCorsAllowHeaders = "ingress.kubernetes.io/cors-allow-headers"
|
||||||
|
annotationCorsAllowCredentials = "ingress.kubernetes.io/cors-allow-credentials"
|
||||||
|
// Default values
|
||||||
|
defaultCorsMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS"
|
||||||
|
defaultCorsHeaders = "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Regex are defined here to prevent information leak, if user tries to set anything not valid
|
||||||
|
// that could cause the Response to contain some internal value/variable (like returning $pid, $upstream_addr, etc)
|
||||||
|
// Origin must contain a http/s Origin (including or not the port) or the value '*'
|
||||||
|
corsOriginRegex = regexp.MustCompile(`^(https?://[A-Za-z0-9\-\.]*(:[0-9]+)?|\*)?$`)
|
||||||
|
// Method must contain valid methods list (PUT, GET, POST, BLA)
|
||||||
|
// May contain or not spaces between each verb
|
||||||
|
corsMethodsRegex = regexp.MustCompile(`^([A-Za-z]+,?\s?)+$`)
|
||||||
|
// Headers must contain valid values only (X-HEADER12, X-ABC)
|
||||||
|
// May contain or not spaces between each Header
|
||||||
|
corsHeadersRegex = regexp.MustCompile(`^([A-Za-z0-9\-\_]+,?\s?)+$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
type cors struct {
|
type cors struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CorsConfig contains the Cors configuration to be used in the Ingress
|
||||||
|
type CorsConfig struct {
|
||||||
|
CorsEnabled bool `json:"corsEnabled"`
|
||||||
|
CorsAllowOrigin string `json:"corsAllowOrigin"`
|
||||||
|
CorsAllowMethods string `json:"corsAllowMethods"`
|
||||||
|
CorsAllowHeaders string `json:"corsAllowHeaders"`
|
||||||
|
CorsAllowCredentials bool `json:"corsAllowCredentials"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewParser creates a new CORS annotation parser
|
// NewParser creates a new CORS annotation parser
|
||||||
func NewParser() parser.IngressAnnotation {
|
func NewParser() parser.IngressAnnotation {
|
||||||
return cors{}
|
return cors{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two External types
|
||||||
|
func (c1 *CorsConfig) Equal(c2 *CorsConfig) bool {
|
||||||
|
if c1 == c2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if c1 == nil || c2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c1.CorsAllowCredentials != c2.CorsAllowCredentials {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c1.CorsAllowHeaders != c2.CorsAllowHeaders {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c1.CorsAllowOrigin != c2.CorsAllowOrigin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c1.CorsEnabled != c2.CorsEnabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parses the annotations contained in the ingress
|
// Parse parses the annotations contained in the ingress
|
||||||
// rule used to indicate if the location/s should allows CORS
|
// rule used to indicate if the location/s should allows CORS
|
||||||
func (a cors) Parse(ing *extensions.Ingress) (interface{}, error) {
|
func (a cors) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
return parser.GetBoolAnnotation(annotation, ing)
|
corsenabled, err := parser.GetBoolAnnotation(annotationCorsEnabled, ing)
|
||||||
|
if err != nil {
|
||||||
|
corsenabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
corsalloworigin, err := parser.GetStringAnnotation(annotationCorsAllowOrigin, ing)
|
||||||
|
if err != nil || corsalloworigin == "" || !corsOriginRegex.MatchString(corsalloworigin) {
|
||||||
|
corsalloworigin = "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
corsallowheaders, err := parser.GetStringAnnotation(annotationCorsAllowHeaders, ing)
|
||||||
|
if err != nil || corsallowheaders == "" || !corsHeadersRegex.MatchString(corsallowheaders) {
|
||||||
|
corsallowheaders = defaultCorsHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
corsallowmethods, err := parser.GetStringAnnotation(annotationCorsAllowMethods, ing)
|
||||||
|
if err != nil || corsallowmethods == "" || !corsMethodsRegex.MatchString(corsallowmethods) {
|
||||||
|
corsallowmethods = defaultCorsMethods
|
||||||
|
}
|
||||||
|
|
||||||
|
corsallowcredentials, err := parser.GetBoolAnnotation(annotationCorsAllowCredentials, ing)
|
||||||
|
if err != nil {
|
||||||
|
corsallowcredentials = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CorsConfig{
|
||||||
|
CorsEnabled: corsenabled,
|
||||||
|
CorsAllowOrigin: corsalloworigin,
|
||||||
|
CorsAllowHeaders: corsallowheaders,
|
||||||
|
CorsAllowMethods: corsallowmethods,
|
||||||
|
CorsAllowCredentials: corsallowcredentials,
|
||||||
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,42 +22,75 @@ import (
|
||||||
api "k8s.io/api/core/v1"
|
api "k8s.io/api/core/v1"
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func buildIngress() *extensions.Ingress {
|
||||||
notCorsAnnotation = "ingress.kubernetes.io/enable-not-cors"
|
defaultBackend := extensions.IngressBackend{
|
||||||
)
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
ap := NewParser()
|
|
||||||
if ap == nil {
|
|
||||||
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
return &extensions.Ingress{
|
||||||
annotations map[string]string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{map[string]string{annotation: "true"}, true},
|
|
||||||
{map[string]string{annotation: "false"}, false},
|
|
||||||
{map[string]string{notCorsAnnotation: "true"}, false},
|
|
||||||
{map[string]string{}, false},
|
|
||||||
{nil, false},
|
|
||||||
}
|
|
||||||
|
|
||||||
ing := &extensions.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Namespace: api.NamespaceDefault,
|
Namespace: api.NamespaceDefault,
|
||||||
},
|
},
|
||||||
Spec: extensions.IngressSpec{},
|
Spec: extensions.IngressSpec{
|
||||||
}
|
Backend: &extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
for _, testCase := range testCases {
|
ServicePort: intstr.FromInt(80),
|
||||||
ing.SetAnnotations(testCase.annotations)
|
},
|
||||||
result, _ := ap.Parse(ing)
|
Rules: []extensions.IngressRule{
|
||||||
if result != testCase.expected {
|
{
|
||||||
t.Errorf("expected %t but returned %t, annotations: %s", testCase.expected, result, testCase.annotations)
|
Host: "foo.bar.com",
|
||||||
}
|
IngressRuleValue: extensions.IngressRuleValue{
|
||||||
|
HTTP: &extensions.HTTPIngressRuleValue{
|
||||||
|
Paths: []extensions.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/foo",
|
||||||
|
Backend: defaultBackend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIngressCorsConfig(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[annotationCorsEnabled] = "true"
|
||||||
|
data[annotationCorsAllowHeaders] = "DNT,X-CustomHeader, Keep-Alive,User-Agent"
|
||||||
|
data[annotationCorsAllowCredentials] = "false"
|
||||||
|
data[annotationCorsAllowMethods] = "PUT, GET,OPTIONS, PATCH, $nginx_version"
|
||||||
|
data[annotationCorsAllowOrigin] = "https://origin123.test.com:4443"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
corst, _ := NewParser().Parse(ing)
|
||||||
|
nginxCors, ok := corst.(*CorsConfig)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Config type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if nginxCors.CorsEnabled != true {
|
||||||
|
t.Errorf("expected cors enabled but returned %v", nginxCors.CorsEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nginxCors.CorsAllowHeaders != "DNT,X-CustomHeader, Keep-Alive,User-Agent" {
|
||||||
|
t.Errorf("expected headers not found. Found %v", nginxCors.CorsAllowHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nginxCors.CorsAllowMethods != "GET, PUT, POST, DELETE, PATCH, OPTIONS" {
|
||||||
|
t.Errorf("expected default methods, but got %v", nginxCors.CorsAllowMethods)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nginxCors.CorsAllowOrigin != "https://origin123.test.com:4443" {
|
||||||
|
t.Errorf("expected origin https://origin123.test.com:4443, but got %v", nginxCors.CorsAllowOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
|
||||||
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
||||||
"ExternalAuth": authreq.NewParser(),
|
"ExternalAuth": authreq.NewParser(),
|
||||||
"CertificateAuth": authtls.NewParser(cfg),
|
"CertificateAuth": authtls.NewParser(cfg),
|
||||||
"EnableCORS": cors.NewParser(),
|
"CorsConfig": cors.NewParser(),
|
||||||
"HealthCheck": healthcheck.NewParser(cfg),
|
"HealthCheck": healthcheck.NewParser(cfg),
|
||||||
"Whitelist": ipwhitelist.NewParser(cfg),
|
"Whitelist": ipwhitelist.NewParser(cfg),
|
||||||
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
||||||
|
@ -130,6 +130,7 @@ const (
|
||||||
sessionAffinity = "SessionAffinity"
|
sessionAffinity = "SessionAffinity"
|
||||||
serviceUpstream = "ServiceUpstream"
|
serviceUpstream = "ServiceUpstream"
|
||||||
serverAlias = "Alias"
|
serverAlias = "Alias"
|
||||||
|
enableCors = "EnableCORS"
|
||||||
clientBodyBufferSize = "ClientBodyBufferSize"
|
clientBodyBufferSize = "ClientBodyBufferSize"
|
||||||
certificateAuth = "CertificateAuth"
|
certificateAuth = "CertificateAuth"
|
||||||
serverSnippet = "ServerSnippet"
|
serverSnippet = "ServerSnippet"
|
||||||
|
@ -175,6 +176,11 @@ func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessiona
|
||||||
return val.(*sessionaffinity.AffinityConfig)
|
return val.(*sessionaffinity.AffinityConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) Cors(ing *extensions.Ingress) *cors.CorsConfig {
|
||||||
|
val, _ := e.annotations[enableCors].Parse(ing)
|
||||||
|
return val.(*cors.CorsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
func (e *annotationExtractor) CertificateAuth(ing *extensions.Ingress) *authtls.AuthSSLConfig {
|
func (e *annotationExtractor) CertificateAuth(ing *extensions.Ingress) *authtls.AuthSSLConfig {
|
||||||
val, err := e.annotations[certificateAuth].Parse(ing)
|
val, err := e.annotations[certificateAuth].Parse(ing)
|
||||||
if errors.IsMissingAnnotations(err) {
|
if errors.IsMissingAnnotations(err) {
|
||||||
|
|
|
@ -35,6 +35,13 @@ const (
|
||||||
annotationUpsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
|
annotationUpsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
|
||||||
annotationPassthrough = "ingress.kubernetes.io/ssl-passthrough"
|
annotationPassthrough = "ingress.kubernetes.io/ssl-passthrough"
|
||||||
annotationAffinityType = "ingress.kubernetes.io/affinity"
|
annotationAffinityType = "ingress.kubernetes.io/affinity"
|
||||||
|
annotationCorsEnabled = "ingress.kubernetes.io/enable-cors"
|
||||||
|
annotationCorsAllowOrigin = "ingress.kubernetes.io/cors-allow-origin"
|
||||||
|
annotationCorsAllowMethods = "ingress.kubernetes.io/cors-allow-methods"
|
||||||
|
annotationCorsAllowHeaders = "ingress.kubernetes.io/cors-allow-headers"
|
||||||
|
annotationCorsAllowCredentials = "ingress.kubernetes.io/cors-allow-credentials"
|
||||||
|
defaultCorsMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS"
|
||||||
|
defaultCorsHeaders = "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
|
||||||
annotationAffinityCookieName = "ingress.kubernetes.io/session-cookie-name"
|
annotationAffinityCookieName = "ingress.kubernetes.io/session-cookie-name"
|
||||||
annotationAffinityCookieHash = "ingress.kubernetes.io/session-cookie-hash"
|
annotationAffinityCookieHash = "ingress.kubernetes.io/session-cookie-hash"
|
||||||
annotationUpstreamHashBy = "ingress.kubernetes.io/upstream-hash-by"
|
annotationUpstreamHashBy = "ingress.kubernetes.io/upstream-hash-by"
|
||||||
|
@ -293,3 +300,54 @@ func TestAffinitySession(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCors(t *testing.T) {
|
||||||
|
ec := newAnnotationExtractor(mockCfg{})
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
fooAnns := []struct {
|
||||||
|
annotations map[string]string
|
||||||
|
corsenabled bool
|
||||||
|
methods string
|
||||||
|
headers string
|
||||||
|
origin string
|
||||||
|
credentials bool
|
||||||
|
}{
|
||||||
|
{map[string]string{annotationCorsEnabled: "true"}, true, defaultCorsMethods, defaultCorsHeaders, "*", true},
|
||||||
|
{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowMethods: "POST, GET, OPTIONS", annotationCorsAllowHeaders: "$nginx_version", annotationCorsAllowCredentials: "false"}, true, "POST, GET, OPTIONS", defaultCorsHeaders, "*", false},
|
||||||
|
{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowCredentials: "false"}, true, defaultCorsMethods, defaultCorsHeaders, "*", false},
|
||||||
|
{map[string]string{}, false, defaultCorsMethods, defaultCorsHeaders, "*", true},
|
||||||
|
{nil, false, defaultCorsMethods, defaultCorsHeaders, "*", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, foo := range fooAnns {
|
||||||
|
ing.SetAnnotations(foo.annotations)
|
||||||
|
r := ec.Cors(ing)
|
||||||
|
t.Logf("Testing pass %v %v %v %v %v", foo.corsenabled, foo.methods, foo.headers, foo.origin, foo.credentials)
|
||||||
|
if r == nil {
|
||||||
|
t.Errorf("Returned nil but expected a Cors.CorsConfig")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.CorsEnabled != foo.corsenabled {
|
||||||
|
t.Errorf("Returned %v but expected %v for Cors Enabled", r.CorsEnabled, foo.corsenabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.CorsAllowHeaders != foo.headers {
|
||||||
|
t.Errorf("Returned %v but expected %v for Cors Headers", r.CorsAllowHeaders, foo.headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.CorsAllowMethods != foo.methods {
|
||||||
|
t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowMethods, foo.methods)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.CorsAllowOrigin != foo.origin {
|
||||||
|
t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowOrigin, foo.origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.CorsAllowCredentials != foo.credentials {
|
||||||
|
t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowCredentials, foo.credentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"k8s.io/ingress-nginx/pkg/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/cors"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/ipwhitelist"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/ipwhitelist"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
||||||
|
@ -45,7 +46,7 @@ func TestMergeLocationAnnotations(t *testing.T) {
|
||||||
"Backend": "foo_backend",
|
"Backend": "foo_backend",
|
||||||
"BasicDigestAuth": auth.BasicDigest{},
|
"BasicDigestAuth": auth.BasicDigest{},
|
||||||
DeniedKeyName: &fakeError{},
|
DeniedKeyName: &fakeError{},
|
||||||
"EnableCORS": true,
|
"EnableCORS": cors.CorsConfig{},
|
||||||
"ExternalAuth": authreq.External{},
|
"ExternalAuth": authreq.External{},
|
||||||
"RateLimit": ratelimit.RateLimit{},
|
"RateLimit": ratelimit.RateLimit{},
|
||||||
"Redirect": redirect.Redirect{},
|
"Redirect": redirect.Redirect{},
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/authtls"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/authtls"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/cors"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/ipwhitelist"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/ipwhitelist"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
||||||
|
@ -293,9 +294,9 @@ type Location struct {
|
||||||
// Denied returns an error when this location cannot not be allowed
|
// Denied returns an error when this location cannot not be allowed
|
||||||
// Requesting a denied location should return HTTP code 403.
|
// Requesting a denied location should return HTTP code 403.
|
||||||
Denied error `json:"denied,omitempty"`
|
Denied error `json:"denied,omitempty"`
|
||||||
// EnableCORS indicates if path must support CORS
|
// CorsConfig returns the Cors Configration for the ingress rule
|
||||||
// +optional
|
// +optional
|
||||||
EnableCORS bool `json:"enableCors,omitempty"`
|
CorsConfig cors.CorsConfig `json:"corsConfig,omitempty"`
|
||||||
// ExternalAuth indicates the access to this location requires
|
// ExternalAuth indicates the access to this location requires
|
||||||
// authentication using an external provider
|
// authentication using an external provider
|
||||||
// +optional
|
// +optional
|
||||||
|
|
|
@ -355,7 +355,7 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
||||||
if l1.Denied != l2.Denied {
|
if l1.Denied != l2.Denied {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if l1.EnableCORS != l2.EnableCORS {
|
if !(&l1.CorsConfig).Equal(&l2.CorsConfig) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !(&l1.ExternalAuth).Equal(&l2.ExternalAuth) {
|
if !(&l1.ExternalAuth).Equal(&l2.ExternalAuth) {
|
||||||
|
|
|
@ -505,48 +505,24 @@ stream {
|
||||||
|
|
||||||
{{/* CORS support from https://michielkalkman.com/snippets/nginx-cors-open-configuration.html */}}
|
{{/* CORS support from https://michielkalkman.com/snippets/nginx-cors-open-configuration.html */}}
|
||||||
{{ define "CORS" }}
|
{{ define "CORS" }}
|
||||||
|
{{ $cors := .CorsConfig }}
|
||||||
|
# Cors Preflight methods needs additional options and different Return Code
|
||||||
if ($request_method = 'OPTIONS') {
|
if ($request_method = 'OPTIONS') {
|
||||||
add_header 'Access-Control-Allow-Origin' '*';
|
add_header 'Access-Control-Allow-Origin' '{{ $cors.CorsAllowOrigin }}';
|
||||||
#
|
{{ if $cors.CorsAllowCredentials }} add_header 'Access-Control-Allow-Credentials' '{{ $cors.CorsAllowCredentials }}'; {{ end }}
|
||||||
# Om nom nom cookies
|
add_header 'Access-Control-Allow-Methods' '{{ $cors.CorsAllowMethods }}';
|
||||||
#
|
add_header 'Access-Control-Allow-Headers' '{{ $cors.CorsAllowHeaders }}';
|
||||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
|
||||||
add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, PATCH, OPTIONS';
|
|
||||||
#
|
|
||||||
# Custom headers and headers various browsers *should* be OK with but aren't
|
|
||||||
#
|
|
||||||
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
|
|
||||||
#
|
|
||||||
# Tell client that this pre-flight info is valid for 20 days
|
|
||||||
#
|
|
||||||
add_header 'Access-Control-Max-Age' 1728000;
|
add_header 'Access-Control-Max-Age' 1728000;
|
||||||
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
add_header 'Content-Type' 'text/plain charset=UTF-8';
|
||||||
add_header 'Content-Length' 0;
|
add_header 'Content-Length' 0;
|
||||||
return 204;
|
return 204;
|
||||||
}
|
}
|
||||||
set $cors_method 0;
|
|
||||||
if ($request_method = 'GET') {
|
|
||||||
set $cors_method 1;
|
|
||||||
}
|
|
||||||
if ($request_method = 'PUT') {
|
|
||||||
set $cors_method 1;
|
|
||||||
}
|
|
||||||
if ($request_method = 'POST') {
|
|
||||||
set $cors_method 1;
|
|
||||||
}
|
|
||||||
if ($request_method = 'DELETE') {
|
|
||||||
set $cors_method 1;
|
|
||||||
}
|
|
||||||
if ($request_method = 'PATCH') {
|
|
||||||
set $cors_method 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($cors_method = 1) {
|
add_header 'Access-Control-Allow-Origin' '{{ $cors.CorsAllowOrigin }}';
|
||||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
{{ if $cors.CorsAllowCredentials }} add_header 'Access-Control-Allow-Credentials' '{{ $cors.CorsAllowCredentials }}'; {{ end }}
|
||||||
add_header 'Access-Control-Allow-Credentials' 'true';
|
add_header 'Access-Control-Allow-Methods' '{{ $cors.CorsAllowMethods }}';
|
||||||
add_header 'Access-Control-Allow-Methods' 'GET, PUT, POST, DELETE, PATCH, OPTIONS';
|
add_header 'Access-Control-Allow-Headers' '{{ $cors.CorsAllowHeaders }}';
|
||||||
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
|
|
||||||
}
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{/* definition of server-template to avoid repetitions with server-alias */}}
|
{{/* definition of server-template to avoid repetitions with server-alias */}}
|
||||||
|
@ -719,8 +695,8 @@ stream {
|
||||||
proxy_set_header Authorization "";
|
proxy_set_header Authorization "";
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if $location.EnableCORS }}
|
{{ if $location.CorsConfig.CorsEnabled }}
|
||||||
{{ template "CORS" }}
|
{{ template "CORS" $location }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if not (empty $location.Redirect.URL) }}
|
{{ if not (empty $location.Redirect.URL) }}
|
||||||
|
|
Loading…
Reference in a new issue