Skip to content

Commit

Permalink
feat: add more fine-grained CSP support
Browse files Browse the repository at this point in the history
This introduces CSP settings for attachments and show/list funs and
streamlines the configuration with the existing Fauxton CSP options.

Deprecates the old `[csp] enable` and `[csp] header_value` config
options, but they are honoured going forward.

They are replaced with `[csp] utils_enable` and `[csp] utils_header_value`
respectively. The funcitonality and default values remain the same.

In addition, these new config options are added, along with their
default values:

```
[csp]
attachments_enable = true
attachments_header_value = sandbox
showlist_enable = true
showlist_header_value = sandbox
```

These add `Content-Security-Policy` headers to all attachment requests
and to all non-JSON show and all list function responses.

Co-authored-by: Nick Vatamaniuc <[email protected]>
Co-authored-by: Robert Newson <[email protected]>
  • Loading branch information
3 people committed Sep 8, 2021
1 parent aabc7ae commit 64281c0
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 46 deletions.
10 changes: 7 additions & 3 deletions rel/overlay/etc/default.ini
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,14 @@ authentication_db = _users
; max_iterations, password_scheme, password_regexp, proxy_use_secret,
; public_fields, secret, users_db_public, cookie_domain, same_site

; CSP (Content Security Policy) Support for _utils
; CSP (Content Security Policy) Support
[csp]
;enable = true
; header_value = default-src 'self'; img-src 'self'; font-src *; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';
;utils_enable = true
;utils_header_value = default-src 'self'; img-src 'self'; font-src *; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';
;attachments_enable = true
;attachments_header_value = sandbox
;showlist_enable = true
;showlist_header_value = sandbox

[cors]
;credentials = false
Expand Down
4 changes: 3 additions & 1 deletion src/chttpd/src/chttpd_db.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1477,7 +1477,7 @@ db_attachment_req(#httpd{method='GET',mochi_req=MochiReq}=Req, Db, DocId, FileNa
atom_to_list(Enc),
couch_httpd:accepted_encodings(Req)
),
Headers = [
Headers0 = [
{"ETag", Etag},
{"Cache-Control", "must-revalidate"},
{"Content-Type", binary_to_list(Type)}
Expand All @@ -1494,6 +1494,8 @@ db_attachment_req(#httpd{method='GET',mochi_req=MochiReq}=Req, Db, DocId, FileNa
_ ->
[{"Accept-Ranges", "none"}]
end,
Headers = chttpd_util:maybe_add_csp_header("attachments", Headers0, "sandbox"),
couch_log:notice("~n Headers0: ~n~p ~nHeaders: ~n~p~n", [Headers0, Headers]),
Len = case {Enc, ReqAcceptsAttEnc} of
{identity, _} ->
% stored and served in identity form
Expand Down
3 changes: 2 additions & 1 deletion src/chttpd/src/chttpd_external.erl
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ send_external_response(Req, Response) ->
Headers1 = default_or_content_type(CType, Headers0),
case Json of
nil ->
chttpd:send_response(Req, Code, Headers1, Data);
Headers2 = chttpd_util:maybe_add_csp_header("showlist", Headers1, "sandbox"),
chttpd:send_response(Req, Code, Headers2, Data);
Json ->
chttpd:send_json(Req, Code, Headers1, Json)
end.
Expand Down
13 changes: 3 additions & 10 deletions src/chttpd/src/chttpd_misc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ handle_utils_dir_req(#httpd{method='GET'}=Req, DocumentRoot) ->
{_ActionKey, "/", RelativePath} ->
% GET /_utils/path or GET /_utils/
CachingHeaders = [{"Cache-Control", "private, must-revalidate"}],
EnableCsp = config:get("csp", "enable", "true"),
Headers = maybe_add_csp_headers(CachingHeaders, EnableCsp),
DefaultValues = "child-src 'self' data: blob:; default-src 'self'; img-src 'self' data:; font-src 'self'; "
"script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';",
Headers = chttpd_util:maybe_add_csp_header("utils", CachingHeaders, DefaultValues),
chttpd:serve_file(Req, RelativePath, DocumentRoot, Headers);
{_ActionKey, "", _RelativePath} ->
% GET /_utils
Expand All @@ -104,14 +105,6 @@ handle_utils_dir_req(#httpd{method='GET'}=Req, DocumentRoot) ->
handle_utils_dir_req(Req, _) ->
send_method_not_allowed(Req, "GET,HEAD").

maybe_add_csp_headers(Headers, "true") ->
DefaultValues = "child-src 'self' data: blob:; default-src 'self'; img-src 'self' data:; font-src 'self'; "
"script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';",
Value = config:get("csp", "header_value", DefaultValues),
[{"Content-Security-Policy", Value} | Headers];
maybe_add_csp_headers(Headers, _) ->
Headers.

handle_all_dbs_req(#httpd{method='GET'}=Req) ->
Args = couch_mrview_http:parse_params(Req, undefined),
ShardDbName = config:get("mem3", "shards_db", "_dbs"),
Expand Down
38 changes: 37 additions & 1 deletion src/chttpd/src/chttpd_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
get_chttpd_auth_config/1,
get_chttpd_auth_config/2,
get_chttpd_auth_config_integer/2,
get_chttpd_auth_config_boolean/2
get_chttpd_auth_config_boolean/2,
maybe_add_csp_header/3
]).


Expand Down Expand Up @@ -60,3 +61,38 @@ get_chttpd_auth_config_integer(Key, Default) ->
get_chttpd_auth_config_boolean(Key, Default) ->
config:get_boolean("chttpd_auth", Key,
config:get_boolean("couch_httpd_auth", Key, Default)).


maybe_add_csp_header(Component, OriginalHeaders, DefaultHeaderValue) ->
Enabled = config:get_boolean("csp", Component ++ "_enable", true),
couch_log:notice("~n> maybe_add_csp_header~n Component: ~n~p~n OriginalHeaders: ~n~p~n DefaultHeaderValue: ~n~p~n, Enabled: ~n~p~n", [Component, OriginalHeaders, DefaultHeaderValue, Enabled]),
case Enabled of
true ->
HeaderValue = config:get("csp", Component ++ "_header_value", DefaultHeaderValue),
% As per https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#multiple_content_security_policies
% The top most CSP header defines the most open policy,
% subsequent CSP headers set by show/list functions can
% only further restrict the policy.
%
% Ours goes on top and we don’t have to worry about additional
% headers set by users.
[{"Content-Security-Policy", HeaderValue} | OriginalHeaders];
false ->
% Fallback for old config vars
case Component of
"utils" ->
handle_legacy_config(OriginalHeaders, DefaultHeaderValue);
_ ->
OriginalHeaders
end
end.

handle_legacy_config(OriginalHeaders, DefaultHeaderValue) ->
LegacyUtilsEnabled = config:get_boolean("csp", "enable", true),
case LegacyUtilsEnabled of
true ->
LegacyUtilsHeaderValue = config:get("csp", "header_value", DefaultHeaderValue),
[{"Content-Security-Policy", LegacyUtilsHeaderValue} | OriginalHeaders];
false ->
OriginalHeaders
end.
Loading

0 comments on commit 64281c0

Please sign in to comment.