Skip to content

Commit

Permalink
Merge pull request #2236 from pi-hole/new/webserver_headers
Browse files Browse the repository at this point in the history
Make additional_headers configurable
  • Loading branch information
DL6ER authored Feb 22, 2025
2 parents e2297ea + 814a268 commit 39a852e
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 34 deletions.
10 changes: 10 additions & 0 deletions src/api/docs/content/specs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,10 @@ components:
type: string
threads:
type: integer
headers:
type: array
items:
type: string
session:
type: object
properties:
Expand Down Expand Up @@ -744,6 +748,12 @@ components:
acl: "+0.0.0.0/0,::/0"
port: 80,[::]:80
threads: 0
headers:
- "Content-Security-Policy: default-src 'self' 'unsafe-inline';"
- "X-Frame-Options: DENY"
- "X-XSS-Protection: 0"
- "X-Content-Type-Options: nosniff"
- "Referrer-Policy: strict-origin-when-cross-origin"
session:
timeout: 300
restore: true
Expand Down
32 changes: 31 additions & 1 deletion src/config/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,19 @@ static void initConfig(struct config *conf)
conf->webserver.threads.d.ui = 0;
conf->webserver.threads.c = validate_stub; // Only type-based checking

conf->webserver.headers.k = "webserver.headers";
conf->webserver.headers.h = "Additional HTTP headers added to the web server responses.\n The headers are added to all responses, including those for the API.\n Note about the default additional headers:\n - Content-Security-Policy: [...] 'unsafe-inline' is both required by Chart.js styling some elements directly, and index.html containing some inlined Javascript code.\n - X-Frame-Options: DENY: The page can not be displayed in a frame, regardless of the site attempting to do so.\n - X-Xss-Protection: 0: Disables XSS filtering in browsers that support it. This header is usually enabled by default in browsers, and is not recommended as it can hurt the security of the site. (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection).\n - X-Content-Type-Options: nosniff: Marker used by the server to indicate that the MIME types advertised in the Content-Type headers should not be changed and be followed. This allows to opt-out of MIME type sniffing, or, in other words, it is a way to say that the webmasters knew what they were doing. Site security testers usually expect this header to be set.\n - Referrer-Policy: strict-origin-when-cross-origin: A referrer will be sent for same-site origins, but cross-origin requests will send no referrer information.\n The latter four headers are set as expected by https://securityheaders.io";
conf->webserver.headers.a = cJSON_CreateStringReference("array of HTTP headers");
conf->webserver.headers.t = CONF_JSON_STRING_ARRAY;
conf->webserver.headers.f = FLAG_RESTART_FTL;
conf->webserver.headers.d.json = cJSON_CreateArray();
cJSON_AddItemReferenceToArray(conf->webserver.headers.d.json, cJSON_CreateStringReference("Content-Security-Policy: default-src 'self' 'unsafe-inline';"));
cJSON_AddItemReferenceToArray(conf->webserver.headers.d.json, cJSON_CreateStringReference("X-Frame-Options: DENY"));
cJSON_AddItemReferenceToArray(conf->webserver.headers.d.json, cJSON_CreateStringReference("X-XSS-Protection: 0"));
cJSON_AddItemReferenceToArray(conf->webserver.headers.d.json, cJSON_CreateStringReference("X-Content-Type-Options: nosniff"));
cJSON_AddItemReferenceToArray(conf->webserver.headers.d.json, cJSON_CreateStringReference("Referrer-Policy: strict-origin-when-cross-origin"));
conf->webserver.headers.c = validate_stub; // Only type-based checking

conf->webserver.tls.cert.k = "webserver.tls.cert";
conf->webserver.tls.cert.h = "Path to the TLS (SSL) certificate file. All directories along the path must be readable and accessible by the user running FTL (typically 'pihole'). This option is only required when at least one of webserver.port is TLS. The file must be in PEM format, and it must have both, private key and certificate (the *.pem file created must contain a 'CERTIFICATE' section as well as a 'RSA PRIVATE KEY' section).\n The *.pem file can be created using\n cp server.crt server.pem\n cat server.key >> server.pem\n if you have these files instead";
conf->webserver.tls.cert.a = cJSON_CreateStringReference("<valid TLS certificate file (*.pem)>");
Expand Down Expand Up @@ -1562,10 +1575,27 @@ static void initConfig(struct config *conf)
// Verify all config options are defined above
if(!conf_item->p || !conf_item->k || !conf_item->h || !conf_item->e || conf_item->t == 0)
{
log_err("Config option %u/%u is not fully configured!", i, (unsigned int)CONFIG_ELEMENTS);
log_err("Config option %u/%u (%s) is not fully configured!",
i, (unsigned int)CONFIG_ELEMENTS, conf_item->k ? conf_item->k : "N/A");
continue;
}

// Verify config options with non-trivial type have allowed values
if(conf_item->t != CONF_BOOL &&
conf_item->t != CONF_UINT &&
conf_item->t != CONF_UINT16 &&
conf_item->t != CONF_DOUBLE &&
conf_item->t != CONF_INT &&
conf_item->t != CONF_ALL_DEBUG_BOOL)
{
if(conf_item->a == NULL)
{
// At this point we know that conf_item->k is not NULL
log_err("Config option %s (type %u) has no allowed values!", conf_item->k, conf_item->t);
continue;
}
}

// Verify we have no default string pointers to NULL
if((conf_item->t == CONF_STRING || conf_item->t == CONF_STRING_ALLOCATED) && conf_item->d.s == NULL)
{
Expand Down
1 change: 1 addition & 0 deletions src/config/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ struct config {
struct conf_item acl;
struct conf_item port;
struct conf_item threads;
struct conf_item headers;
struct {
struct conf_item timeout;
struct conf_item restore;
Expand Down
8 changes: 4 additions & 4 deletions src/config/toml_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -730,13 +730,13 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
}
case CONF_JSON_STRING_ARRAY:
{
// Free previously allocated JSON array
cJSON_Delete(conf_item->v.json);
conf_item->v.json = cJSON_CreateArray();
// Parse TOML array and generate a JSON array
const toml_array_t *array = toml_array_in(toml, key);
if(array != NULL)
{
// Free previously allocated JSON array if element exists
cJSON_Delete(conf_item->v.json);
conf_item->v.json = cJSON_CreateArray();

const unsigned int nelem = toml_array_nelem(array);
for(unsigned int i = 0; i < nelem; i++)
{
Expand Down
51 changes: 25 additions & 26 deletions src/webserver/webserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -443,28 +443,31 @@ void http_init(void)
return;
}

// Construct additional headers
char *webheaders = strdup("");
cJSON *header;
cJSON_ArrayForEach(header, config.webserver.headers.v.json)
{
if(!cJSON_IsString(header))
{
log_err("Invalid header in webserver.headers!");
continue;
}

// Get header value
const char *h = cJSON_GetStringValue(header);

// Allocate memory for the new header
webheaders = realloc(webheaders, strlen(webheaders) + strlen(h) + 3);
if (webheaders == NULL) {
log_err("Failed to allocate memory for webheaders!");
return;
}
strcat(webheaders, h);
strcat(webheaders, "\r\n");
}

// Prepare options for HTTP server (NULL-terminated list)
// Note about the additional headers:
// - "Content-Security-Policy: [...]"
// 'unsafe-inline' is both required by Chart.js styling some elements directly, and
// index.html containing some inlined Javascript code.
// - "X-Frame-Options: DENY"
// The page can not be displayed in a frame, regardless of the site attempting to do
// so.
// - "X-Xss-Protection: 0"
// Disables XSS filtering in browsers that support it. This header is usually
// enabled by default in browsers, and is not recommended as it can hurt the
// security of the site. (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection)
// - "X-Content-Type-Options: nosniff"
// Marker used by the server to indicate that the MIME types advertised in the
// Content-Type headers should not be changed and be followed. This allows to
// opt-out of MIME type sniffing, or, in other words, it is a way to say that the
// webmasters knew what they were doing. Site security testers usually expect this
// header to be set.
// - "Referrer-Policy: strict-origin-when-cross-origin"
// A referrer will be sent for same-site origins, but cross-origin requests will
// send no referrer information.
// The latter four headers are set as expected by https://securityheaders.io
const char *options[] = {
"document_root", config.webserver.paths.webroot.v.s,
"error_pages", error_pages,
Expand All @@ -473,11 +476,7 @@ void http_init(void)
"enable_directory_listing", "no",
"num_threads", num_threads,
"authentication_domain", config.webserver.domain.v.s,
"additional_header", "Content-Security-Policy: default-src 'self' 'unsafe-inline';\r\n"
"X-Frame-Options: DENY\r\n"
"X-XSS-Protection: 0\r\n"
"X-Content-Type-Options: nosniff\r\n"
"Referrer-Policy: strict-origin-when-cross-origin",
"additional_header", webheaders,
"index_files", "index.html,index.htm,index.lp",
NULL, NULL,
NULL, NULL, // Leave slots for access control list (ACL) and TLS configuration at the end
Expand Down
37 changes: 34 additions & 3 deletions test/pihole.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Pi-hole configuration file (v6.0.1-3-g03cc1d34-dirty)
# Pi-hole configuration file (v6.0.2-18-g392eaa08)
# Encoding: UTF-8
# This file is managed by pihole-FTL
# Last updated on 2025-02-21 10:58:04 UTC
# Last updated on 2025-02-22 18:05:05 UTC

[dns]
# Array of upstream DNS servers used by Pi-hole
Expand Down Expand Up @@ -677,6 +677,37 @@
# threads are only created when needed due to incoming connections.
threads = 0

# Additional HTTP headers added to the web server responses.
# The headers are added to all responses, including those for the API.
# Note about the default additional headers:
# - Content-Security-Policy: [...] 'unsafe-inline' is both required by Chart.js
# styling some elements directly, and index.html containing some inlined Javascript
# code.
# - X-Frame-Options: DENY: The page can not be displayed in a frame, regardless of the
# site attempting to do so.
# - X-Xss-Protection: 0: Disables XSS filtering in browsers that support it. This
# header is usually enabled by default in browsers, and is not recommended as it can
# hurt the security of the site.
# (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection).
# - X-Content-Type-Options: nosniff: Marker used by the server to indicate that the
# MIME types advertised in the Content-Type headers should not be changed and be
# followed. This allows to opt-out of MIME type sniffing, or, in other words, it is a
# way to say that the webmasters knew what they were doing. Site security testers
# usually expect this header to be set.
# - Referrer-Policy: strict-origin-when-cross-origin: A referrer will be sent for
# same-site origins, but cross-origin requests will send no referrer information.
# The latter four headers are set as expected by https://securityheaders.io
#
# Possible values are:
# array of HTTP headers
headers = [
"Content-Security-Policy: default-src 'self' 'unsafe-inline';",
"X-Frame-Options: DENY",
"X-XSS-Protection: 0",
"X-Content-Type-Options: nosniff",
"Referrer-Policy: strict-origin-when-cross-origin"
]

[webserver.session]
# Session timeout in seconds. If a session is inactive for more than this time, it will
# be terminated. Sessions are continuously refreshed by the web interface, preventing
Expand Down Expand Up @@ -1145,7 +1176,7 @@
all = true ### CHANGED, default = false

# Configuration statistics:
# 152 total entries out of which 95 entries are default
# 153 total entries out of which 96 entries are default
# --> 57 entries are modified
# 2 entries are forced through environment:
# - misc.nice
Expand Down

0 comments on commit 39a852e

Please sign in to comment.