# CORS

## Summary

Grey Matter supports the configuration of cross-origin resource sharing on a sidecar. CORS can be configured to allow an application to access resources at a different origin (domain, protocol, or port) than its own.

For more information on CORS, see this [CORS reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).

### Simple Requests

Simple requests are classified as requests that don't require a [CORS preflight check](#preflight-requests). This distinction is made between requests that might be dangerous (i.e. modifies server resources) and those that are most likely benign. A request is considered simple when *all* of the following criteria is true:

* The method is `GET`, `HEAD`, or `POST`
* Only [CORS safe-listed headers](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) are present
* The `Content Type` header is set to one of `application/x-www-form-urlencoded` or `multipart/form-data` or `text/plain`

A more comprehensive list and explanation can be found [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple-requests).

As an example, say an app running at `http://localhost:8080` is trying to call a backend service with a Grey Matter sidecar at `http://localhost:10808`. Without a CORS configuration, this request would fail, because the app `localhost:8080` is trying to access resources from a server at a different origin, `localhost:10808`. To solve this, the following CORS config is set on its domain:

```javascript
{
  "zone_key": "zone-default-zone",
  "domain_key": "domain-backend-service",
  "name": "*",
  "port": 10808,
  "cors_config": {
    "allowed_origins": [
      { "match_type": "exact", "value": "http://localhost:8080" }
    ],
    "allowed_headers": [],
    "allowed_methods": [],
    "exposed_headers": [],
    "max_age": 60
  }
}
```

With this configuration, if a simple request comes in to the sidecar from the app, it will have an `Origin` header value of `http://localhost:8080`, and this request will succeed. The server will attach a header `Access-Control-Allow-Origin: http://localhost:8080` to the response, which signals to the browser that this request is allowed.

### Preflight Requests

[Preflight requests](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) are initiated by the browser using the `OPTIONS` HTTP method before sending a request in order to determine if the real request is safe to send. The response to this kind of request contains information about what is allowed from a request, and the server determines whether or not to send the actual request based on this information.

This response information is in the form of three HTTP headers, `access-control-request-method`, `access-control-request-headers`, and the `origin` header. These correspond to [fields](#fields) of the `cors_config` - thus these configurations can be specified to determine how the Grey Matter sidecar will respond to preflight requests. If a preflight request comes in to the Grey Matter Sidecar that does not meet the specification for one of these configured fields, the sidecar will send back a response to not initiate the request.

Based on the same [example from simple requests](#simple-requests), say the app running at `http://localhost:8080` wants to send requests to the backend service with a content-type of `application/json;charset=UTF-8`. This particular content-type is outside of those allowed by CORS for simple requests (see [simple requests](#simple-requests)), and thus would result in sending a preflight request to determine if the request can be sent. In order for the CORS configuration to indicate that the request can be sent, it would need to allow the `content-type` header by configuring the [`allowed_headers`](#allowed_headers) field:

```javascript
{
  "zone_key": "zone-default-zone",
  "domain_key": "domain-backend-service",
  "name": "*",
  "port": 10808,
  "cors_config": {
    "allowed_origins": [
      { "match_type": "exact", "value": "http://localhost:8080" }
    ],
    "allowed_headers": ["content-type"],
    "allowed_methods": [],
    "exposed_headers": [],
    "max_age": 60
  }
}
```

In the above configuration, CORS will allow requests with `Origin` header value `http://localhost:8080` only *and* indicate that the content-type header can be set according to the request.

### Configuration

To set up CORS, set the `cors_config` field on a domain object with the desired configuration, see the [fields](#fields) below.

For an existing domain, run

```bash
greymatter edit domain <domain-name>
```

and add the desired `cors_config` object.

### Example object

```javascript
  "cors_config": {
    "allowed_origins": [],
    "allowed_headers": [],
    "allowed_methods": [],
    "exposed_headers": [],
    "max_age": 0,
    "allow_credentials": true
  }
```

### Fields

#### `allowed_origins`

This field specifies an array of string patterns that match allowed origins. The proxy will use these matchers to set the [`access-control-allow-origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) header. This header will be set on any cross-origin response that matches one of the `allowed_origins`.

Available matchers include:

* `exact`
* `prefix`
* `suffix`
* `regex`

Example:

```javascript
  "allowed_origins": [
      { "match_type": "exact", "value": "http://localhost:8080" }
    ]
```

A wildcard value `*` is allowed except when using the `regex` matcher.

#### `allow_credentials`

Specifies the content for the [`access-control-allow-credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) header that the proxy will set on any cross-origin request that matches one of the `allowed_origins`. This header specifies whether or not the upstream service allows credentials.

#### `exposed_headers`

Specifies the content for the [`access-control-expose-headers`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) header that the proxy will set on any cross-origin request that matches one of the `allowed_origins`. This header specifies an array of headers that are allowed on the response.

#### `max_age`

Specifies the content for the [`access-control-max-age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age) header that the proxy will set on the preflight response. This header is an integer value specifying how long a preflight request can be cached by the browser.

#### `allowed_methods`

Specifies the content for the [`access-control-allow-methods`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) header that the proxy will set on the preflight response. This header specifies an array of methods allowed by the upstream service.

#### `allowed_headers`

Specifies the content for the [`access-control-allow-headers`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) header that the proxy will set on the preflight response. This header specifies an array of headers allowed by the upstream service.

### Notes

* By default, the proxy will use the upstream service's CORS policy on the gateway **and** on the upstream service. The gateway policy is ignored.
* Because CORS is a browser construct, curl can always make a request to the server, with or without CORS. However, it can be used to mimic a browser and verify how the proxy will react to CORS requests:

```bash
$ curl -v 'http://localhost:9080/services/catalog/latest/' \
    -X OPTIONS \
    -H 'Access-Control-Request-Method: POST' \
    -H 'Access-Control-Request-Headers: content-type' \
    -H 'Origin: http://localhost:8080'
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 9080 (#0)
> OPTIONS /services/catalog/latest/ HTTP/1.1
> Host: localhost:9080
> User-Agent: curl/7.64.1
> Accept: */*
> Access-Control-Request-Method: POST
> Access-Control-Request-Headers: content-type
> Origin: http://localhost:8080
>
< HTTP/1.1 200 OK
< access-control-allow-origin: http://localhost:8080
< access-control-max-age: 60
< date: Tue, 12 May 2020 20:11:13 GMT
< server: envoy
< content-length: 0
<
* Connection #0 to host localhost left intact
* Closing connection 0
```
