Skip to content

Commit

Permalink
KEYCLOAK-14232 Add Referrer-Policy: no-referrer to each response from…
Browse files Browse the repository at this point in the history
… Keycloak

(cherry picked from commit 0b49640231abc6e465542bd2608e1c908c079ced)
  • Loading branch information
mhajas authored and stianst committed Sep 18, 2020
1 parent f037dab commit f7e0af4
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 163 deletions.
153 changes: 41 additions & 112 deletions server-spi-private/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -21,125 +21,54 @@
import java.util.HashMap;
import java.util.Map;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class BrowserSecurityHeaders {

public static final String X_FRAME_OPTIONS = "X-Frame-Options";

public static final String X_FRAME_OPTIONS_DEFAULT = "SAMEORIGIN";

public static final String X_FRAME_OPTIONS_KEY = "xFrameOptions";

public static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy";

public static final String CONTENT_SECURITY_POLICY_DEFAULT = ContentSecurityPolicyBuilder.create().build();

public static final String CONTENT_SECURITY_POLICY_KEY = "contentSecurityPolicy";

public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only";

public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_DEFAULT = "";

public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY = "contentSecurityPolicyReportOnly";

public static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options";

public static final String X_CONTENT_TYPE_OPTIONS_DEFAULT = "nosniff";

public static final String X_CONTENT_TYPE_OPTIONS_KEY = "xContentTypeOptions";

public static final String X_ROBOTS_TAG = "X-Robots-Tag";

public static final String X_ROBOTS_TAG_KEY = "xRobotsTag";

public static final String X_ROBOTS_TAG_DEFAULT = "none";

public static final String X_XSS_PROTECTION = "X-XSS-Protection";

public static final String X_XSS_PROTECTION_DEFAULT = "1; mode=block";

public static final String X_XSS_PROTECTION_KEY = "xXSSProtection";
public enum BrowserSecurityHeaders {

X_FRAME_OPTIONS("xFrameOptions", "X-Frame-Options", "SAMEORIGIN"),
CONTENT_SECURITY_POLICY("contentSecurityPolicy", "Content-Security-Policy", ContentSecurityPolicyBuilder.create().build()),
CONTENT_SECURITY_POLICY_REPORT_ONLY("contentSecurityPolicyReportOnly", "Content-Security-Policy-Report-Only", ""),
X_CONTENT_TYPE_OPTIONS("xContentTypeOptions", "X-Content-Type-Options", "nosniff"),
X_ROBOTS_TAG("xRobotsTag", "X-Robots-Tag", "none"),
X_XSS_PROTECTION("xXSSProtection", "X-XSS-Protection", "1; mode=block"),
STRICT_TRANSPORT_SECURITY("strictTransportSecurity", "Strict-Transport-Security", "max-age=31536000; includeSubDomains"),
REFERRER_POLICY("referrerPolicy", "Referrer-Policy", "no-referrer"),
;

private final String key;
private final String headerName;
private final String defaultValue;

BrowserSecurityHeaders(String key, String headerName, String defaultValue) {
this.key = key;
this.headerName = headerName;
this.defaultValue = defaultValue;
}

public static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security";
public String getKey() {
return key;
}

public static final String STRICT_TRANSPORT_SECURITY_DEFAULT = "max-age=31536000; includeSubDomains";
public String getHeaderName() {
return headerName;
}

public static final String STRICT_TRANSPORT_SECURITY_KEY = "strictTransportSecurity";
public String getDefaultValue() {
return defaultValue;
}

public static final Map<String, String> headerAttributeMap;
public static final Map<String, String> defaultHeaders;
@Deprecated // should be removed eventually
public static final Map<String, String> realmDefaultHeaders;

static {
Map<String, String> headerMap = new HashMap<>();
headerMap.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS);
headerMap.put(CONTENT_SECURITY_POLICY_KEY, CONTENT_SECURITY_POLICY);
headerMap.put(CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY, CONTENT_SECURITY_POLICY_REPORT_ONLY);
headerMap.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS);
headerMap.put(X_ROBOTS_TAG_KEY, X_ROBOTS_TAG);
headerMap.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION);
headerMap.put(STRICT_TRANSPORT_SECURITY_KEY, STRICT_TRANSPORT_SECURITY);

Map<String, String> dh = new HashMap<>();
dh.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS_DEFAULT);
dh.put(CONTENT_SECURITY_POLICY_KEY, CONTENT_SECURITY_POLICY_DEFAULT);
dh.put(CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY, CONTENT_SECURITY_POLICY_REPORT_ONLY_DEFAULT);
dh.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS_DEFAULT);
dh.put(X_ROBOTS_TAG_KEY, X_ROBOTS_TAG_DEFAULT);
dh.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION_DEFAULT);
dh.put(STRICT_TRANSPORT_SECURITY_KEY, STRICT_TRANSPORT_SECURITY_DEFAULT);

defaultHeaders = Collections.unmodifiableMap(dh);
headerAttributeMap = Collections.unmodifiableMap(headerMap);
dh.put(X_FRAME_OPTIONS.getKey(), X_FRAME_OPTIONS.getDefaultValue());
dh.put(CONTENT_SECURITY_POLICY.getKey(), CONTENT_SECURITY_POLICY.getDefaultValue());
dh.put(CONTENT_SECURITY_POLICY_REPORT_ONLY.getKey(), CONTENT_SECURITY_POLICY_REPORT_ONLY.getDefaultValue());
dh.put(X_CONTENT_TYPE_OPTIONS.getKey(), X_CONTENT_TYPE_OPTIONS.getDefaultValue());
dh.put(X_ROBOTS_TAG.getKey(), X_ROBOTS_TAG.getDefaultValue());
dh.put(X_XSS_PROTECTION.getKey(), X_XSS_PROTECTION.getDefaultValue());
dh.put(STRICT_TRANSPORT_SECURITY.getKey(), STRICT_TRANSPORT_SECURITY.getDefaultValue());

realmDefaultHeaders = Collections.unmodifiableMap(dh);
}

public static class ContentSecurityPolicyBuilder {

private String frameSrc = "'self'";
private String frameAncestors = "'self'";
private String objectSrc = "'none'";

private boolean first;
private StringBuilder sb;

public static ContentSecurityPolicyBuilder create() {
return new ContentSecurityPolicyBuilder();
}

public ContentSecurityPolicyBuilder frameSrc(String frameSrc) {
this.frameSrc = frameSrc;
return this;
}

public ContentSecurityPolicyBuilder frameAncestors(String frameancestors) {
this.frameAncestors = frameancestors;
return this;
}

public String build() {
sb = new StringBuilder();
first = true;

build("frame-src", frameSrc);
build("frame-ancestors", frameAncestors);
build("object-src", objectSrc);

return sb.toString();
}

private void build(String k, String v) {
if (v != null) {
if (!first) {
sb.append(" ");
}
first = false;

sb.append(k).append(" ").append(v).append(";");
}
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.keycloak.models;

public class ContentSecurityPolicyBuilder {

private String frameSrc = "'self'";
private String frameAncestors = "'self'";
private String objectSrc = "'none'";

private boolean first;
private StringBuilder sb;

public static ContentSecurityPolicyBuilder create() {
return new ContentSecurityPolicyBuilder();
}

public ContentSecurityPolicyBuilder frameSrc(String frameSrc) {
this.frameSrc = frameSrc;
return this;
}

public ContentSecurityPolicyBuilder frameAncestors(String frameancestors) {
this.frameAncestors = frameancestors;
return this;
}

public String build() {
sb = new StringBuilder();
first = true;

build("frame-src", frameSrc);
build("frame-ancestors", frameAncestors);
build("object-src", objectSrc);

return sb.toString();
}

private void build(String k, String v) {
if (v != null) {
if (!first) {
sb.append(" ");
}
first = false;

sb.append(k).append(" ").append(v).append(";");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ public static void importRealm(KeycloakSession session, RealmRepresentation rep,
if (rep.getBrowserSecurityHeaders() != null) {
newRealm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders());
} else {
newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders);
newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.realmDefaultHeaders);
}

if (rep.getComponents() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ public class BrowserSecurityHeadersTest {

@Test
public void contentSecurityPolicyBuilderTest() {
assertEquals("frame-src 'self'; frame-ancestors 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().build());
assertEquals("frame-ancestors 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameSrc(null).build());
assertEquals("frame-src 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameAncestors(null).build());
assertEquals("frame-src 'custom-frame-src'; frame-ancestors 'custom-frame-ancestors'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameSrc("'custom-frame-src'").frameAncestors("'custom-frame-ancestors'").build());
assertEquals("frame-src 'self'; frame-ancestors 'self'; object-src 'none';", ContentSecurityPolicyBuilder.create().build());
assertEquals("frame-ancestors 'self'; object-src 'none';", ContentSecurityPolicyBuilder.create().frameSrc(null).build());
assertEquals("frame-src 'self'; object-src 'none';", ContentSecurityPolicyBuilder.create().frameAncestors(null).build());
assertEquals("frame-src 'custom-frame-src'; frame-ancestors 'custom-frame-ancestors'; object-src 'none';", ContentSecurityPolicyBuilder.create().frameSrc("'custom-frame-src'").frameAncestors("'custom-frame-ancestors'").build());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.jboss.logging.Logger;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.ContentSecurityPolicyBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;

Expand All @@ -26,8 +27,11 @@
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.util.Collections;
import java.util.Map;

import static org.keycloak.models.BrowserSecurityHeaders.CONTENT_SECURITY_POLICY;

public class DefaultSecurityHeadersProvider implements SecurityHeadersProvider {

private static final Logger LOGGER = Logger.getLogger(DefaultSecurityHeadersProvider.class);
Expand All @@ -44,7 +48,7 @@ public DefaultSecurityHeadersProvider(KeycloakSession session) {
if (realm != null) {
headerValues = realm.getBrowserSecurityHeaders();
} else {
headerValues = BrowserSecurityHeaders.defaultHeaders;
headerValues = Collections.emptyMap();
}
}

Expand Down Expand Up @@ -81,27 +85,31 @@ public void addHeaders(ContainerRequestContext requestContext, ContainerResponse
}

private void addGenericHeaders(MultivaluedMap<String, Object> headers) {
addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_KEY, headers);
addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_KEY, headers);
addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION_KEY, headers);
addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY, headers);
addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS, headers);
addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION, headers);
addHeader(BrowserSecurityHeaders.REFERRER_POLICY, headers);
}

private void addRestHeaders(MultivaluedMap<String, Object> headers) {
addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_KEY, headers);
addHeader(BrowserSecurityHeaders.X_FRAME_OPTIONS_KEY, headers);
addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_KEY, headers);
addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION_KEY, headers);
addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY, headers);
addHeader(BrowserSecurityHeaders.X_FRAME_OPTIONS, headers);
addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS, headers);
addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION, headers);
addHeader(BrowserSecurityHeaders.REFERRER_POLICY, headers);
}

private void addHtmlHeaders(MultivaluedMap<String, Object> headers) {
BrowserSecurityHeaders.headerAttributeMap.keySet().forEach(k -> addHeader(k, headers));
for (BrowserSecurityHeaders header : BrowserSecurityHeaders.values()) {
addHeader(header, headers);
}

// TODO This will be refactored as part of introducing a more strict CSP header
if (options != null) {
BrowserSecurityHeaders.ContentSecurityPolicyBuilder csp = BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create();
ContentSecurityPolicyBuilder csp = ContentSecurityPolicyBuilder.create();

if (options.isAllowAnyFrameAncestor()) {
headers.remove(BrowserSecurityHeaders.X_FRAME_OPTIONS);
headers.remove(BrowserSecurityHeaders.X_FRAME_OPTIONS.getHeaderName());

csp.frameAncestors(null);
}
Expand All @@ -111,17 +119,16 @@ private void addHtmlHeaders(MultivaluedMap<String, Object> headers) {
csp.frameSrc(allowedFrameSrc);
}

if (BrowserSecurityHeaders.CONTENT_SECURITY_POLICY_DEFAULT.equals(headers.getFirst(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY))) {
headers.putSingle(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY, csp.build());
if (CONTENT_SECURITY_POLICY.getDefaultValue().equals(headers.getFirst(CONTENT_SECURITY_POLICY.getHeaderName()))) {
headers.putSingle(CONTENT_SECURITY_POLICY.getHeaderName(), csp.build());
}
}
}

private void addHeader(String key, MultivaluedMap<String, Object> headers) {
String header = BrowserSecurityHeaders.headerAttributeMap.get(key);
String value = headerValues.get(key);
private void addHeader(BrowserSecurityHeaders header, MultivaluedMap<String, Object> headers) {
String value = headerValues.getOrDefault(header.getKey(), header.getDefaultValue());
if (value != null && !value.isEmpty()) {
headers.putSingle(header, value);
headers.putSingle(header.getHeaderName(), value);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ public String getRealmAdminClientId(RealmRepresentation realm) {


protected void setupRealmDefaults(RealmModel realm) {
realm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders);
realm.setBrowserSecurityHeaders(BrowserSecurityHeaders.realmDefaultHeaders);

// brute force
realm.setBruteForceProtected(false); // default settings off for now todo set it on
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
package org.keycloak.testsuite.admin;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.services.resources.Cors;
import org.keycloak.testsuite.util.UserBuilder;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.IOException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

public class AdminHeadersTest extends AbstractAdminTest {

Expand All @@ -42,14 +40,19 @@ public void testHeaders() {
Response response = realm.users().create(UserBuilder.create().username("headers-user").build());
MultivaluedMap<String, Object> h = response.getHeaders();

assertEquals(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_DEFAULT, h.getFirst(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY));
assertEquals(BrowserSecurityHeaders.X_FRAME_OPTIONS_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_FRAME_OPTIONS));
assertEquals(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS));
assertEquals(BrowserSecurityHeaders.X_XSS_PROTECTION_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_XSS_PROTECTION));
assertDefaultValue(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY, h);
assertDefaultValue(BrowserSecurityHeaders.X_FRAME_OPTIONS, h);
assertDefaultValue(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS, h);
assertDefaultValue(BrowserSecurityHeaders.X_XSS_PROTECTION, h);
assertDefaultValue(BrowserSecurityHeaders.REFERRER_POLICY, h);

response.close();
}

private void assertDefaultValue(BrowserSecurityHeaders header, MultivaluedMap<String, Object> h) {
assertThat(h.getFirst(header.getHeaderName()), is(equalTo(header.getDefaultValue())));
}

private String getAdminUrl(String resource) {
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/admin/" + resource;
}
Expand Down
Loading

0 comments on commit f7e0af4

Please sign in to comment.