Maintainable Content-Security-Policy configuration for Apache HTTP servers

In this article, we will create better maintainable Apache 2 configuration for Content-Security-Policy configuration. Which means, that instead of having an ultra long statement like this one:

Header set Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' 'self' https://gist.github.com https://cdn.jsdelivr.net; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com https://github.githubassets.com https://cdn.jsdelivr.net; font-src 'self' data: https://fonts.gstatic.com; object-src 'none'; frame-src 'self' https://wp-themes.com; worker-src 'none';img-src 'self' data: blob: https://secure.gravatar.com https://assets-cdn.github.com https://*.w.org; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; connect-src 'self'"

See? Now let’s try to split it. First idea would be to use the Apache’s header directive append directive.

Header set Content-Security-Policy "default-src 'none';
Header append Content-Security-Policy "script-src;" "script-src 'unsafe-inline' 'unsafe-eval' 'self' https://gist.github.com https://cdn.jsdelivr.net;"
# ...

The problem is: the append statement automatically add a comma but the Content-Security-Policy use semicolon instead. Not sure why this header is designed like this :/

The idea itself is quite simple. Since we can not append, we replace it by using the header directive edit. Let’s create a base header first, which we can replace.

Header set Content-Security-Policy "default-src; script-src; style-src; font-src; object-src; frame-src; img-src; frame-ancestors; base-uri; form-action; connect-src;"

Now we can replace each part, that we want in individual directives. Let’s begin with the default-src.

Header edit Content-Security-Policy "default-src;" "default-src 'none';"

Now we can proceed with the rest. And in the end, we should end up with something like this.

Header set Content-Security-Policy "default-src; script-src; style-src; font-src; object-src; frame-src; img-src; frame-ancestors; base-uri; form-action; connect-src;"
Header edit Content-Security-Policy "default-src;" "default-src 'none';"
Header edit Content-Security-Policy "script-src;" "script-src 'unsafe-inline' 'unsafe-eval' 'self' https://gist.github.com https://cdn.jsdelivr.net;"
Header edit Content-Security-Policy "style-src;" "style-src 'unsafe-inline' 'self' https://fonts.googleapis.com https://github.githubassets.com https://cdn.jsdelivr.net;"
Header edit Content-Security-Policy "font-src;" "font-src 'self' data: https://fonts.gstatic.com;"
Header edit Content-Security-Policy "object-src;" "object-src 'none';"
Header edit Content-Security-Policy "frame-src;" "frame-src 'self' https://wp-themes.com;"
Header edit Content-Security-Policy "worker-src;" "worker-src 'none';"
Header edit Content-Security-Policy "img-src;" "img-src 'self' data: blob: https://secure.gravatar.com https://assets-cdn.github.com https://*.w.org https://library.ghostkit.io;"
Header edit Content-Security-Policy "frame-ancestors;" "frame-ancestors 'self';"
Header edit Content-Security-Policy "base-uri;" "base-uri 'self';"
Header edit Content-Security-Policy "form-action;" "form-action 'self';"
Header edit Content-Security-Policy "connect-src;" "connect-src 'self';"

As you can see, you can now edit each part in its own directive.