summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@verizonmedia.com>2019-08-19 12:49:58 +0200
committerGitHub <noreply@github.com>2019-08-19 12:49:58 +0200
commit41ef9280799fc63cf1d7db934c7b3ea771099d7b (patch)
tree01197f8e52f90755d2ee371b693e7dfcddedb832
parentcf9c4139ea1167ff1a4656772f5e689356a70568 (diff)
parentd0e2b5082708cdf5044509dc242cfadc6ee68461 (diff)
Merge pull request #10261 from vespa-engine/vekterli/set-status-page-basic-http-security-headers
Set basic HTTP security headers on status pages served from backend
-rw-r--r--staging_vespalib/src/tests/state_server/state_server_test.cpp6
-rw-r--r--storage/src/tests/frameworkimpl/status/statustest.cpp18
-rw-r--r--vespalib/src/tests/portal/portal_test.cpp6
-rw-r--r--vespalib/src/vespa/vespalib/portal/http_connection.cpp21
4 files changed, 51 insertions, 0 deletions
diff --git a/staging_vespalib/src/tests/state_server/state_server_test.cpp b/staging_vespalib/src/tests/state_server/state_server_test.cpp
index 6c7397a1719..e61d3d216cd 100644
--- a/staging_vespalib/src/tests/state_server/state_server_test.cpp
+++ b/staging_vespalib/src/tests/state_server/state_server_test.cpp
@@ -87,6 +87,12 @@ TEST_FF("require that non-empty known url returns expected headers", DummyHandle
"Connection: close\r\n"
"Content-Type: application/json\r\n"
"Content-Length: 5\r\n"
+ "X-XSS-Protection: 1; mode=block\r\n"
+ "X-Frame-Options: DENY\r\n"
+ "Content-Security-Policy: default-src 'none'\r\n"
+ "X-Content-Type-Options: nosniff\r\n"
+ "Cache-Control: no-store\r\n"
+ "Pragma: no-cache\r\n"
"\r\n"
"[123]");
std::string actual = getFull(f2.port(), my_path);
diff --git a/storage/src/tests/frameworkimpl/status/statustest.cpp b/storage/src/tests/frameworkimpl/status/statustest.cpp
index e7d0d496cc8..81d91e2f08a 100644
--- a/storage/src/tests/frameworkimpl/status/statustest.cpp
+++ b/storage/src/tests/frameworkimpl/status/statustest.cpp
@@ -115,6 +115,12 @@ TEST_F(StatusTest, index_status_page) {
"Connection: close\r\n"
"Content-Type: text\\/html\r\n"
"Content-Length: [0-9]+\r\n"
+ "X-XSS-Protection: 1; mode=block\r\n"
+ "X-Frame-Options: DENY\r\n"
+ "Content-Security-Policy: default-src 'none'\r\n"
+ "X-Content-Type-Options: nosniff\r\n"
+ "Cache-Control: no-store\r\n"
+ "Pragma: no-cache\r\n"
"\r\n"
"<html>\n"
"<head>\n"
@@ -144,6 +150,12 @@ TEST_F(StatusTest, html_status) {
"Connection: close\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 117\r\n"
+ "X-XSS-Protection: 1; mode=block\r\n"
+ "X-Frame-Options: DENY\r\n"
+ "Content-Security-Policy: default-src 'none'\r\n"
+ "X-Content-Type-Options: nosniff\r\n"
+ "Cache-Control: no-store\r\n"
+ "Pragma: no-cache\r\n"
"\r\n"
"<html>\n"
"<head>\n"
@@ -170,6 +182,12 @@ TEST_F(StatusTest, xml_sStatus) {
"Connection: close\r\n"
"Content-Type: application/xml\r\n"
"Content-Length: 100\r\n"
+ "X-XSS-Protection: 1; mode=block\r\n"
+ "X-Frame-Options: DENY\r\n"
+ "Content-Security-Policy: default-src 'none'\r\n"
+ "X-Content-Type-Options: nosniff\r\n"
+ "Cache-Control: no-store\r\n"
+ "Pragma: no-cache\r\n"
"\r\n"
"<?xml version=\"1.0\"?>\n"
"<status id=\"fooid\" name=\"Foo impl\">\n"
diff --git a/vespalib/src/tests/portal/portal_test.cpp b/vespalib/src/tests/portal/portal_test.cpp
index 1baebc69e97..e54700306fe 100644
--- a/vespalib/src/tests/portal/portal_test.cpp
+++ b/vespalib/src/tests/portal/portal_test.cpp
@@ -48,6 +48,12 @@ vespalib::string make_expected_response(const vespalib::string &content_type, co
"Connection: close\r\n"
"Content-Type: %s\r\n"
"Content-Length: %zu\r\n"
+ "X-XSS-Protection: 1; mode=block\r\n"
+ "X-Frame-Options: DENY\r\n"
+ "Content-Security-Policy: default-src 'none'\r\n"
+ "X-Content-Type-Options: nosniff\r\n"
+ "Cache-Control: no-store\r\n"
+ "Pragma: no-cache\r\n"
"\r\n"
"%s", content_type.c_str(), content.size(), content.c_str());
}
diff --git a/vespalib/src/vespa/vespalib/portal/http_connection.cpp b/vespalib/src/vespa/vespalib/portal/http_connection.cpp
index 97a5f6082c9..aa2c0ec4cdd 100644
--- a/vespalib/src/vespa/vespalib/portal/http_connection.cpp
+++ b/vespalib/src/vespa/vespalib/portal/http_connection.cpp
@@ -90,6 +90,26 @@ WriteRes half_close(CryptoSocket &socket) {
}
}
+/**
+ * Emit a basic set of HTTP security headers meant to minimize any impact
+ * in the case of unsanitized/unescaped data making its way to an internal
+ * status page.
+ */
+void emit_http_security_headers(OutputWriter &dst) {
+ // Reject detected cross-site scripting attacks
+ dst.printf("X-XSS-Protection: 1; mode=block\r\n");
+ // Do not allow embedding via iframe (clickjacking prevention)
+ dst.printf("X-Frame-Options: DENY\r\n");
+ // Do not allow _anything_ to be externally loaded, nor inline scripts
+ // etc to be executed.
+ dst.printf("Content-Security-Policy: default-src 'none'\r\n");
+ // No heuristic auto-inference of content-type based on payload.
+ dst.printf("X-Content-Type-Options: nosniff\r\n");
+ // Don't store any potentially sensitive data in any caches.
+ dst.printf("Cache-Control: no-store\r\n");
+ dst.printf("Pragma: no-cache\r\n");
+}
+
} // namespace vespalib::portal::<unnamed>
void
@@ -223,6 +243,7 @@ HttpConnection::respond_with_content(const vespalib::string &content_type,
dst.printf("Connection: close\r\n");
dst.printf("Content-Type: %s\r\n", content_type.c_str());
dst.printf("Content-Length: %zu\r\n", content.size());
+ emit_http_security_headers(dst);
dst.printf("\r\n");
dst.write(content.data(), content.size());
}