directus / directus

The flexible backend for all your projects 🐰 Turn your DB into a headless CMS, admin panels, or apps with a custom UI, instant APIs, auth & more.
https://directus.io
Other
28.08k stars 3.91k forks source link

Self Hosted config options (helmet's adaptation) #21548

Open triplecasquette opened 8 months ago

triplecasquette commented 8 months ago

Describe the Request

I think it would be nice to add at least:

OR

because now (not some times ago) I had a CSP error with IFRAME.

So I spend hours to understand (not find it but I deducted it) that

CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC="array:https://www.yourwebsite.com/"

In the .env file was the solution ^^

Maintainence Strategy

As often as the config option of CSP will change

Th1nhNg0 commented 6 months ago

Thanks. we need a document for this

tresorama commented 4 weeks ago

Had the same problem. Here is my not-expert description:

Directus Default CSP

By default the configuration for CONTENT_SECURITY_POLICY is highly restrictive, and this is a feature, because it provides more security without effort. The default CSP Response Header is:

content-security-policy: 
  script-src 'self' 'unsafe-eval';
  worker-src 'self' blob:;
  child-src 'self' blob:;
  img-src 'self' data: blob: https://raw.githubusercontent.com https://avatars.githubusercontent.com;media-src 'self';
  connect-src 'self' https://* wss://*;
  default-src 'self';
  base-uri 'self';
  font-src 'self' https: data:;
  form-action 'self';
  frame-ancestors 'self';
  object-src 'none';
  script-src-attr 'none';
  style-src 'self' https: 'unsafe-inline'

This header is sent as an HTTP Response Header content-security-policy for any Directus Admin page.

These directives define which domain/host a request can be sent to, by HTML elements present in the Directus Admin page. The CSP specs allows granular control, defining whitelist domains for every HTML element that can perform an HTTP request

<script src="..."/>    => script-src
<iframe src="..."/>   => frame-src
<object src="..."/>   => object-src
<img src="..." />       => img-src
...

If a frame-src directive is not defined, the fallback will be child-src, but it's recommended to use frame-src.

Like frame-src, every other directives, if not defined, will fallback to child-src as well, but THIS IS ONLY MY UNVERIFIED ASSUMPTION.

Directus Live Preview / Draft Mode

In Directus, to enable live preview mode, you go in Settings > Data Model > Select a Collection and find an input where you defined the URL of the draft page. Directus will inject this URL as the src attribute of the iframe used for Live Preview.

Here is an example of that URL:

http://localhost:3000/draft/page

The domain entered here must be reachable by the CSP policy, so you must explicitly define it. In this case you must define http://localhost:3000.

Define CSP for Directus Admin

You must use Environment Variables for that (or any equivalent based on your env, like docker-compose )

# .env

# frame-src
CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC="array:http://localhost:3000/"

# child-src
CONTENT_SECURITY_POLICY_DIRECTIVES__CHILD_SRC="array:http://localhost:3000/"

...

Why this strange syntax ?

Directus uses helmet inside the Web Server so these env var will be passed to helmet.

This is how in an express app you define global helmet configration

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        "frame-src": ["'self'", "http://localhost:3000", "instagram.com"],
        "script-src": ["'self'", "http://localhost:3000"],
      },
    },
  })
);

Directus parse the env var, looks for the tail i.e. DIRECTIVES__FRAME_SRC and inject the value in the helmet config.

# .env
CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC="array:'self',http://localhost:3000/,instagram.com" # array syntax 1
CONTENT_SECURITY_POLICY_DIRECTIVES__IMG_SRC="'self',http://localhost:3000/,instagram.com" # array syntax 2

# helmet config
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        "frame-src": ["'self'", "http://localhost:3000", "instagram.com"],
        "img-src": ["'self'", "http://localhost:3000", "instagram.com"],
      },
    },
  })
);

NOTE: 'self' refers the the actual page, and must be 'self' and not self. This is why it has double quotes.

giray123 commented 2 weeks ago

I have tried above solution on Self Hosted Directus with Coolify and it did not work. I am not able to use Live Previews with NextJS. Is there a solution to this CSP iframing problem?

triplecasquette commented 2 weeks ago

I have tried above solution on Self Hosted Directus with Coolify and it did not work. I am not able to use Live Previews with NextJS. Is there a solution to this CSP iframing problem?

You probably made a mistake somewhere. Or in the draft route on the next api or in your directus instance.

If both right then we need more to understand what happens.

Hope it helps :)

giray123 commented 2 weeks ago

Probably. Could you help me out? I have tested the live preview on my browser it is working. On Directus, it does not work and gives me this console error:

Refused to frame 'https://www.mywebsite.com/' because it violates the following Content Security Policy directive: "child-src 'self' blob:". Note that 'frame-src' was not explicitly set, so 'child-src' is used as a fallback.

I have deployed with Coolify, so I have set below environment variables and restart the service there, but I still does not work:

CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC="array:'self',https://www.mywebsite.com/"
CONTENT_SECURITY_POLICY_DIRECTIVES__CHILD_SRC="array:'self',https://www.mywebsite.com/"
tresorama commented 2 weeks ago

It seems that the directives you defined are not used.

To be sure, open the Dev Tools > Network tab and look at response header of the "document" request. You should find the content-security-policy header.

How did you define ENV vars?

If you used .ENV file and Docker compose you must tell Docker compose to use it


services:
  directus:
    image: directus
    ...
    env_file:
      - .env

This tells Docker Compose to load environment variables from a file named ".env" in the same directory as the compose file.

giray123 commented 2 weeks ago

@tresorama Thanks, I just used the Coolify's dashboard to add the environment variables.

image

If you are saying the env variables are correct, than probably my problem is on Coolify's side. Actually I found an issue on their side, probably that is the reason.

tresorama commented 2 weeks ago

If you are saying the env variables are correct, than probably my problem is on Coolify's side. Actually I found an issue on their side, probably that is the reason.

I never used coolify, but likely you guessed right.

You should understand if Directus docker container receive or not these env var, then fix that.

For example, for debugging if env vars defined in Coolify are effectively passed to Directus, you can add

SERVE_APP=false

to env vars and redeploy. If the env var is taken into consideration you shouldn't be able to use the Directus Admin


Definition:

 SERVE_APP => Whether or not to serve the Data Studio. default: true
triplecasquette commented 2 weeks ago

Probably. Could you help me out? I have tested the live preview on my browser it is working. On Directus, it does not work and gives me this console error:

Refused to frame 'https://www.mywebsite.com/' because it violates the following Content Security Policy directive: "child-src 'self' blob:". Note that 'frame-src' was not explicitly set, so 'child-src' is used as a fallback.

I have deployed with Coolify, so I have set below environment variables and restart the service there, but I still does not work:

CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC="array:'self',https://www.mywebsite.com/"
CONTENT_SECURITY_POLICY_DIRECTIVES__CHILD_SRC="array:'self',https://www.mywebsite.com/"

Have you tried to only write :

CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC="'self' https: etc with space separated url

It works for me on docker and on my VPS no need to use array

lttr commented 2 weeks ago

In my case, the environment variable was completely missing due to my lack of understanding of how Coolify apply the variables. Details in this ticket: https://github.com/coollabsio/coolify/issues/4024

It now works for me, with the syntax mentioned above, it looks like this in my case:

CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC="'self' https://example.com https://test.example.com"
giray123 commented 2 weeks ago

@lttr Worked! I also did not know that I had to edit the compose file. Thank you very much.