Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable CORS configuration #3809

Merged
merged 2 commits into from
Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
179 changes: 138 additions & 41 deletions docs/content/middlewares/headers.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,22 @@ The Headers middleware can manage the requests/responses headers.
Add the `X-Script-Name` header to the proxied request and the `X-Custom-Response-Header` to the response

```yaml tab="Docker"
a-container:
image: a-container-image
labels:
- "traefik.http.middlewares.testHeader.Headers.CustomRequestHeaders.X-Script-Name=test",
- "traefik.http.middlewares.testHeader.Headers.CustomResponseHeaders.X-Custom-Response-Header=True",
- "traefik.http.middlewares.testHeader.Headers.CustomRequestHeaders.X-Script-Name=test"
- "traefik.http.middlewares.testHeader.Headers.CustomResponseHeaders.X-Custom-Response-Header=True"
```

```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: testHeader
spec:
headers:
CustomRequestHeaders:
X-Script-Name: "test"
CustomResponseHeaders:
X-Custom-Response-Header: "True"
```

```toml tab="File"
Expand All @@ -34,51 +45,101 @@ labels:

`X-Script-Name` header added to the proxied request, the `X-Custom-Request-Header` header removed from the request, and the `X-Custom-Response-Header` header removed from the response.

??? example "File"

```toml
[http.middlewares]
[http.middlewares.testHeader.headers]
[http.middlewares.testHeader.headers.CustomRequestHeaders]
X-Script-Name = "test"
[http.middlewares.testHeader.headers.CustomResponseHeaders]
X-Custom-Response-Header = "True"
```
```yaml tab="Docker"
labels:
- "traefik.http.middlewares.testHeader.Headers.CustomRequestHeaders.X-Script-Name=test"
- "traefik.http.middlewares.testHeader.Headers.CustomResponseHeaders.X-Custom-Response-Header=True"
```

??? example "Docker"
```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: testHeader
spec:
headers:
CustomRequestHeaders:
X-Script-Name: "test"
CustomResponseHeaders:
X-Custom-Response-Header: "True"
```

```yml
a-container:
image: a-container-image
labels:
- "traefik.http.middlewares.testHeader.Headers.CustomRequestHeaders.X-Script-Name=test",
- "traefik.http.middlewares.testHeader.Headers.CustomResponseHeaders.X-Custom-Response-Header=True",
```
```toml tab="File"
[http.middlewares]
[http.middlewares.testHeader.headers]
[http.middlewares.testHeader.headers.CustomRequestHeaders]
X-Script-Name = "test"
[http.middlewares.testHeader.headers.CustomResponseHeaders]
X-Custom-Response-Header = "True"
```

### Using Security Headers

Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above.
This functionality allows for some easy security features to quickly be set.

??? example "File"

```toml
[http.middlewares]
[http.middlewares.testHeader.headers]
FrameDeny = true
SSLRedirect = true
```

??? example "Docker"

```yml
a-container:
image: a-container-image
labels:
- "traefik.http.middlewares.testHeader.Headers.FrameDeny=true",
- "traefik.http.middlewares.testHeader.Headers.SSLRedirect=true",
```

```yaml tab="Docker"
labels:
- "traefik.http.middlewares.testHeader.Headers.FrameDeny=true"
- "traefik.http.middlewares.testHeader.Headers.SSLRedirect=true"
```

```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: testHeader
spec:
headers:
FrameDeny: "true"
SSLRedirect: "true"
```

```toml tab="File"
[http.middlewares]
[http.middlewares.testHeader.headers]
FrameDeny = true
SSLRedirect = true
```

### CORS Headers

CORS (Cross-Origin Resource Sharing) headers can be added and configured per frontend in a similar manner to the custom headers above.
This functionality allows for more advanced security features to quickly be set.

```yaml tab="Docker"
labels:
- "traefik.http.middlewares.testHeader.Headers.AccessControlAllowMethods=GET,OPTIONS,PUT"
- "traefik.http.middlewares.testHeader.Headers.AccessControlAllowOrigin=origin-list-or-null"
- "traefik.http.middlewares.testHeader.Headers.AccessControlMaxAge=100"
- "traefik.http.middlewares.testHeader.Headers.AddVaryHeader=true"
```

```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: testHeader
spec:
headers:
AccessControlAllowMethods:
- "GET"
- "OPTIONS"
- "PUT"
AccessControlAllowOrigin: "origin-list-or-null"
AccessControlMaxAge: 100
AddVaryHeader: "true"
```

```toml tab="File"
[http.middlewares]
dtomcej marked this conversation as resolved.
Show resolved Hide resolved
[http.middlewares.testHeader.headers]
AccessControlAllowMethods= ["GET", "OPTIONS", "PUT"]
AccessControlAllowOrigin = "origin-list-or-null"
AccessControlMaxAge = 100
AddVaryHeader = true
```

## Configuration Options

### General
Expand All @@ -93,6 +154,42 @@ This functionality allows for some easy security features to quickly be set.

The `customRequestHeaders` option lists the Header names and values to apply to the request.

### customResponseHeaders

The `customResponseHeaders` option lists the Header names and values to apply to the response.

### accessControlAllowCredentials

The `accessControlAllowCredentials` indicates whether the request can include user credentials.

### accessControlAllowHeaders

The `accessControlAllowHeaders` indicates which header field names can be used as part of the request.

### accessControlAllowMethods

The `accessControlAllowMethods` indicates which methods can be used during requests.

### accessControlAllowOrigin

The `accessControlAllowOrigin` indicates whether a resource can be shared by returning different values. The three options for this value are:

- `origin-list-or-null`
- `*`
- `null`

### accessControlExposeHeaders

The `accessControlExposeHeaders` indicates which headers are safe to expose to the api of a CORS API specification.

### accessControlMaxAge

The `accessControlMaxAge` indicates how long a preflight request can be cached.

### addVaryHeader

The `addVaryHeader` is used in conjunction with `accessControlAllowOrigin` to determine whether the vary header should be added or modified to demonstrate that server responses can differ beased on the value of the origin header.

### allowedHosts

The `allowedHosts` option lists fully qualified domain names that are allowed.
Expand Down
7 changes: 7 additions & 0 deletions docs/content/reference/providers/file.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
excludedIPs = ["127.0.0.1/16", "192.168.1.7"]

[http.middlewares.my-headers.Headers]
accessControlAllowCredentials = true
accessControlAllowHeaders = ["X-foobar", "X-fiibar"]
accessControlAllowMethods = ["GET", "PUT"]
accessControlAllowOrigin = "*"
accessControlExposeHeaders = ["X-foobar", "X-fiibar"]
accessControlMaxAge = 200
addVaryHeader = true
allowedHosts = ["foobar", "foobar"]
hostsProxyHeaders = ["foobar", "foobar"]
sslRedirect = true
Expand Down
21 changes: 21 additions & 0 deletions integration/fixtures/headers/basic.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

[entrypoints]
[entrypoints.web]
address = ":8000"

[log]
logLevel = "DEBUG"

[providers]
[providers.file]

[http.routers]
[http.routers.router1]
rule = "Host(`test.localhost`)"
service = "service1"

[http.services]
[http.services.service1.loadbalancer]
[[http.services.service1.loadbalancer.servers]]
url = "http://172.17.0.2:80"
weight = 1
28 changes: 28 additions & 0 deletions integration/fixtures/headers/cors.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

[entrypoints]
[entrypoints.web]
address = ":8000"

[log]
logLevel = "DEBUG"

[providers]
[providers.file]

[http.routers]
[http.routers.router1]
rule = "Host(`test.localhost`)"
service = "service1"

[http.middlewares]
[http.middlewares.cors.Headers]
AccessControlAllowMethods= ["GET", "OPTIONS", "PUT"]
AccessControlAllowOrigin = "origin-list-or-null"
AccessControlMaxAge = 100
AddVaryHeader = true

[http.services]
[http.services.service1.loadbalancer]
[[http.services.service1.loadbalancer.servers]]
url = "http://172.17.0.2:80"
weight = 1
106 changes: 106 additions & 0 deletions integration/headers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package integration

import (
"net/http"
"time"

"github.com/containous/traefik/integration/try"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)

// Headers test suites
type HeadersSuite struct{ BaseSuite }

func (s *HeadersSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "headers")

s.composeProject.Start(c)
}

func (s *HeadersSuite) TestSimpleConfiguration(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/headers/basic.toml"))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()

// Expected a 404 as we did not configure anything
err = try.GetRequest("http://127.0.0.1:8000/", 1000*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)
}

func (s *HeadersSuite) TestCorsResponses(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/headers/cors.toml"))
defer display(c)

err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()

testCase := []struct {
desc string
requestHeaders http.Header
expected http.Header
}{
{
desc: "simple access control allow origin",
requestHeaders: http.Header{
"Origin": {"https://foo.bar.org"},
},
expected: http.Header{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Vary": {"Origin"},
},
},
}

for _, test := range testCase {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.localhost"
req.Header = test.requestHeaders

err = try.Request(req, 500*time.Millisecond, try.HasBody(), try.HasHeaderStruct(test.expected))
c.Assert(err, checker.IsNil)
}
}

func (s *HeadersSuite) TestCorsPreflightResponses(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/headers/cors.toml"))
defer display(c)

err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()

testCase := []struct {
desc string
requestHeaders http.Header
expected http.Header
}{
{
desc: "simple preflight request",
requestHeaders: http.Header{
"Access-Control-Request-Headers": {"origin"},
"Access-Control-Request-Method": {"GET", "OPTIONS"},
"Origin": {"https://foo.bar.org"},
},
expected: http.Header{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
"Access-Control-Max-Age": {"100"},
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
},
},
}

for _, test := range testCase {
req, err := http.NewRequest(http.MethodOptions, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.localhost"
req.Header = test.requestHeaders

err = try.Request(req, 500*time.Millisecond, try.HasBody(), try.HasHeaderStruct(test.expected))
c.Assert(err, checker.IsNil)
}
}
1 change: 1 addition & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func init() {
check.Suite(&FileSuite{})
check.Suite(&GRPCSuite{})
check.Suite(&HealthCheckSuite{})
check.Suite(&HeadersSuite{})
check.Suite(&HostResolverSuite{})
check.Suite(&HTTPSSuite{})
check.Suite(&LogRotationSuite{})
Expand Down
4 changes: 4 additions & 0 deletions integration/resources/compose/headers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
whoami1:
image: containous/whoami
ports:
- "8881:80"