Skip to content

Commit

Permalink
docs: copyedit CSRF page (#4826)
Browse files Browse the repository at this point in the history
Copyedit and divide CSRF page with headings.

(Also fix broken links)

---------

Co-authored-by: Bryn Cooke <BrynCooke@gmail.com>
  • Loading branch information
shorgi and BrynCooke committed Mar 26, 2024
1 parent cc31624 commit 3308b97
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 18 deletions.
49 changes: 37 additions & 12 deletions docs/source/configuration/csrf.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,41 @@ description: Prevent cross-site request forgery (CSRF) attacks in the Apollo Rou
minVersion: 0.9.0
---

Your router's [CORS policy](./cors/) enables you to control which websites can talk to your server. In most cases, the browser checks your server's CORS policy by sending a [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) before sending the actual operation. This is a separate HTTP request. Unlike most HTTP requests (which use the `GET` or `POST` method), this request uses a method called `OPTIONS`. The browser sends an `Origin` header, along with some other headers that start with `Access-Control-`. These headers describe the kind of request that the potentially untrusted JavaScript wants to make. Your server returns a response with `Access-Control-*` headers describing its policies (as described above), and the browser uses that response to decide whether it's OK to send the real request. Processing the `OPTIONS` preflight request never actually executes GraphQL operations.
## About CSRF

[Cross-site request forgery (CSRF)](https://owasp.org/www-community/attacks/csrf) attacks use side effects of ["simple"](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) requests to execute GraphQL operations from requests sent by sites that shouldn't be allowed to communicate with your server, based on your Apollo Router's [CORS policy](./cors/).

Your router's CORS policy enables you to control which websites can talk to your server. In most cases, the browser checks your server's CORS policy by sending a [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) before sending the actual operation. This is a separate HTTP request. Unlike most HTTP requests (which use the `GET` or `POST` method), this request uses a method called `OPTIONS`. The browser sends an `Origin` header, along with some other headers that start with `Access-Control-`. These headers describe the kind of request that the potentially untrusted JavaScript wants to make. Your server returns a response with `Access-Control-*` headers describing its policies (as described above), and the browser uses that response to decide whether it's OK to send the real request. Processing the `OPTIONS` preflight request never actually executes GraphQL operations.

However, in some circumstances, the browser will *not* send this preflight request. If the request is considered ["simple"](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests), then the browser just sends the request without sending a preflight first. Your server's response can still contain `Access-Control-*` headers, and if they indicate that the origin that sent the request shouldn't be able to access the site, the browser will hide your server's response from the problematic JavaScript code.

Unfortunately, this means that your server might execute GraphQL operations from "simple" requests sent by sites that shouldn't be allowed to communicate with your server. And these requests can even contain cookies! Although the browser will hide your server's response data from the malicious code, that might not be sufficient. If running the operation has side effects, then the attacker might not care if it can read the response or not, as long as it can use an unsuspecting user's browser (and cookies!) to trigger those side effects. Even with a read-only query, the malicious code might be able to figure out something about the response based entirely on how long the query takes to execute.
Unfortunately, this means that your server might execute GraphQL operations from "simple" requests sent by sites that shouldn't be allowed to communicate with your server. These requests can even contain cookies. Although the browser will hide your server's response data from the malicious code, that might not be sufficient. If running the operation has side effects, then the attacker might not care if it can read the response or not, as long as it can use an unsuspecting user's browser (and cookies) to trigger those side effects. Even with a read-only query, the malicious code might be able to figure out something about the response based entirely on how long the query takes to execute.

Attacks that use simple requests for their side effects are called ["cross-site request forgery" attacks](https://owasp.org/www-community/attacks/csrf), or CSRF. Attacks that measure the timing of simple requests are called "cross-site search" attacks, or XS-Search.
In addition to CSRF, attacks that measure the timing of simple requests are called "cross-site search" attacks, or XS-Search.

## Preventing CSRF

To avoid CSRF and XS-Search attacks, GraphQL servers should refuse to execute any operation coming from a browser that has not "preflighted" that operation. There's no reliable way to detect whether a request came from a browser, so GraphQL servers should not execute any operation in a "simple request".

The most important rule for whether or not a request is ["simple"](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) is whether it tries to set arbitrary HTTP request headers. Any request that sets the `Content-Type` header to `application/json` (or anything other than a list of three particular values) cannot be a simple request, and thus it must be preflighted. Because all `POST` requests recognized by Apollo Server must contain a `Content-Type` header specifying `application/json`, we can be confident that they are not simple requests and that if they come from a browser, they have been preflighted.
The most important rule for whether or not a request is "simple" is whether it tries to set arbitrary HTTP request headers. Any request that sets the `Content-Type` header to `application/json` (or anything other than a list of three particular values) cannot be a simple request, and thus it must be preflighted. Because all `POST` requests recognized by Apollo Server must contain a `Content-Type` header specifying `application/json`, we can be confident that they are not simple requests and that if they come from a browser, they have been preflighted.

However, the Apollo Router also handles `GET` requests which do not require a `Content-Type` header, so they can potentially be simple requests. So how can we ensure that we only execute `GET` requests that are _not_ simple requests? If we require the request to include an HTTP header that is never set automatically by the browser, then that is sufficient: requests that set HTTP headers other than the handful defined in the spec must be preflighted.

## Configuring CSRF prevention in the router

### Enable CSRF prevention

The Apollo Router **enables CSRF prevention by default**. When this feature is enabled, the router only executes GraphQL operations if at least one of the following conditions is true:

However, Apollo Router also handles `GET` requests which do not require a `Content-Type` header, so they can potentially be simple requests. So how can we ensure that we only execute `GET` requests that are _not_ simple requests? If we require the request to include an HTTP header that is never set automatically by the browser, then that is sufficient: requests that set HTTP headers other than the handful defined in the spec must be preflighted.
- The incoming request includes a `Content-Type` header that specifies a type other than `text/plain`, `application/x-www-form-urlencoded`, or `multipart/form-data`.

Apollo Router 0.9 introduced a CSRF prevention feature, which is enabled by default. When this feature is enabled, Apollo Router only executes GraphQL operations if at least one of the following conditions is true:
<Note>

A `Content-Type` of `application/json` is sufficient, including any suffix like `application/json; charset=utf-8`. Since all `POST` requests must use `Content-Type: application/json`, this means all `POST` requests will be executed. And because all versions of [Apollo Client Web](/react/api/link/apollo-link-http) that support `GET` requests include `Content-Type: application/json` headers, any request from Apollo Client Web (`POST` or `GET`) will also be executed.

- The incoming request includes a `Content-Type` header that specifies a type other than `text/plain`, `application/x-www-form-urlencoded`, or `multipart/form-data`. Notably, a `Content-Type` of `application/json` (including any suffix like `application/json; charset=utf-8`) is sufficient. This means that all `POST` requests (which must use `Content-Type: application/json`) will be executed. Additionally, all versions of [Apollo Client Web](/react/api/link/apollo-link-http) that support `GET` requests do include `Content-Type: application/json` headers, so any request from Apollo Client Web (`POST` or `GET`) will be executed.
- There is a `X-Apollo-Operation-Name` header. This header is sent with all operations (`POST` or `GET`) by [Apollo iOS](/ios) (v0.13.0+) and [Apollo Kotlin](/kotlin) (all versions, including its former name "Apollo Android"), so any request from Apollo iOS or Apollo Kotlin will be executed.
</Note>

- There is a `X-Apollo-Operation-Name` header. This header is sent with all `POST` or `GET` operations by [Apollo iOS](/ios) (v0.13.0+) and [Apollo Kotlin](/kotlin) (all versions, including its former name "Apollo Android"), so any request from Apollo iOS or Apollo Kotlin will be executed.
- There is a `Apollo-Require-Preflight` header.

<Note>
Expand All @@ -33,7 +50,7 @@ Apollo Router 0.9 introduced a CSRF prevention feature, which is enabled by defa

</Note>

HTTP requests that satisfy none of the conditions above will be rejected with a 400 status code and a message clearly explaining which headers need to be added in order to make the request succeed.
HTTP requests that satisfy none of the conditions above will be rejected with a `400` status code and a message clearly explaining which headers need to be added in order to make the request succeed.

This should have no impact on legitimate use of your graph, *unless* you have clients that send `GET` requests and are not Apollo Client Web, Apollo iOS, or Apollo Kotlin. If you do send `GET` requests with other clients, you should configure them to send a non-empty `Apollo-Require-Preflight` header along with all requests.

Expand All @@ -49,11 +66,19 @@ csrf:

The check for `Content-Type` remains the same.

**We highly recommend that you leave CSRF prevention enabled**. However it is still possible to disable the plugin entirely:
### Disable CSRF prevention

<Caution>

We highly recommend that you keep CSRF prevention enabled in the Apollo Router.

</Caution>

Disabling CSRF in the Apollo Router might be appropriate for a GraphQL server that only contains public data and allows no mutation, or on a closed private network where browsers cannot reach untrusted websites.

To disable CSRF prevention in the router, set `csrf.unsafe_disabled` to `true`:

```yaml title="router.yaml"
csrf:
unsafe_disabled: true
```

This could be appropriate for a GraphQL server that only contains public data and allows no mutation, or on a closed private network where browsers cannot reach untrusted websites.
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ telemetry:

<Note>

OpenTelemetry includes many [standard attributes](https://opentelemetry.io/docs/specs/semconv/attributes-registry/) that you can use via custom [instruments](../instrumentation/instruments).
OpenTelemetry includes many [standard attributes](https://opentelemetry.io/docs/specs/semconv/attributes-registry/) that you can use via custom [instruments](../../instrumentation/instruments).

</Note>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ telemetry:
endpoint: "http://${env.JAEGER_HOST}:4317"
```

See [OTLP configuration](./opentelemetry#configuration) for more details on settings.
See [OTLP configuration](./otlp#configuration) for more details on settings.

## Jaeger Native configuration

Expand Down
6 changes: 3 additions & 3 deletions docs/source/containerization/kubernetes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -185,20 +185,20 @@ To enable metrics endpoints in your deployed router through a YAML configuration

## Deploy with Rhai scripts

The router supports [Rhai scripting](../../customizations/rhai) to add custom functionality.
The router supports [Rhai scripting](../customizations/rhai) to add custom functionality.

Enabling Rhai scripts in your deployed router requires mounting an extra volume for your Rhai scripts and getting your scripts onto the volume. That can be done by following steps in [a separate example for creating a custom in-house router chart](https://github.com/apollographql/in-house-router-example). The example creates a new (in-house) chart that wraps (and depends on) the released router chart, and the new chart has templates that add the necessary configuration to allow Rhai scripts for a deployed router.

## Deploy with a coprocessor

The router supports [external coprocessing](../../customizations/coprocessor) to run custom logic on requests throughout the [router's request-handling lifecycle](../../customizations/rhai/#router-request-lifecycle).
The router supports [external coprocessing](../customizations/coprocessor) to run custom logic on requests throughout the [router's request-handling lifecycle](../customizations/rhai/#router-request-lifecycle).

A deployed coprocessor has its own application image and container in the router pod.

To configure a coprocessor and its container for your deployed router through a YAML configuration file:

1. Create a YAML file, `my_values.yaml`, to contain additional values that override default values.
1. Edit `my_values.yaml` to configure a coprocessor for the router. For reference, follow the [typical](../../customizations/coprocessor#typical-configuration) and [minimal](../../customizations/coprocessor#minimal-configuration) configuration examples, and apply them to `router.configuration.coprocessor`.
1. Edit `my_values.yaml` to configure a coprocessor for the router. For reference, follow the [typical](../customizations/coprocessor#typical-configuration) and [minimal](../customizations/coprocessor#minimal-configuration) configuration examples, and apply them to `router.configuration.coprocessor`.

<ExpansionPanel title="Example of typical configuration for a coprocessor">

Expand Down
2 changes: 1 addition & 1 deletion docs/source/errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ The router encountered an unexpected issue. [Report](https://github.com/apollogr

<Note>

You can create Rhai scripts that throw custom status codes. See [Terminating client requests](/customizations/rhai-api#terminating-client-requests) to learn more.
You can create Rhai scripts that throw custom status codes. See [Terminating client requests](./customizations/rhai-api#terminating-client-requests) to learn more.

</Note>

0 comments on commit 3308b97

Please sign in to comment.