Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NAE-1794] Configurable security headers #149

Merged
merged 13 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
public class ChangePasswordRequest {

public String login;

public String password;

public String newPassword;

public ChangePasswordRequest() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import com.google.common.collect.Ordering;
import com.netgrif.application.engine.configuration.authentication.providers.NaeAuthProperties;
import com.netgrif.application.engine.configuration.authentication.providers.NetgrifAuthenticationProvider;
import com.netgrif.application.engine.configuration.properties.SecurityConfigProperties;
import com.netgrif.application.engine.configuration.properties.ServerAuthProperties;
import com.netgrif.application.engine.configuration.properties.enumeration.HSTS;
import com.netgrif.application.engine.configuration.properties.enumeration.XFrameOptionsMode;
import com.netgrif.application.engine.configuration.properties.enumeration.XXSSProtection;
import com.netgrif.application.engine.configuration.security.SessionUtilsProperties;
import com.netgrif.application.engine.ldap.filters.LoginAttemptsFilter;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -14,10 +18,12 @@
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.header.writers.StaticHeadersWriter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public abstract class AbstractSecurityConfiguration extends WebSecurityConfigurerAdapter {
Expand All @@ -34,6 +40,132 @@ public abstract class AbstractSecurityConfiguration extends WebSecurityConfigure
@Autowired
private ApplicationContext context;

protected void setHeaders(HttpSecurity http) throws Exception {
setStrictTransportSecurity(http);
setContentSecurityPolicy(http);
setXFrameOptions(http);
setXXSSProtection(http);

}

protected void setStrictTransportSecurity(HttpSecurity http) throws Exception {
if (existConfigurationHeaders()
&& getSecurityConfigProperties().getHeaders().getHsts() != null
&& getSecurityConfigProperties().getHeaders().getHsts().isEnable()
&& getSecurityConfigProperties().getHeaders().getHsts().getMaxAge() >= 0) {
HSTS headers = getSecurityConfigProperties().getHeaders().getHsts();
if (Objects.nonNull(headers.isIncludeSubDomains())
&& Objects.nonNull(headers.isPreload())) {
http
.headers()
.httpStrictTransportSecurity()
.maxAgeInSeconds(headers.getMaxAge())
.includeSubDomains(headers.isIncludeSubDomains())
.preload(headers.isPreload());

} else if (Objects.nonNull(headers.isIncludeSubDomains())
&& Objects.isNull(headers.isPreload())) {
http
.headers()
.httpStrictTransportSecurity()
.maxAgeInSeconds(headers.getMaxAge())
.includeSubDomains(headers.isIncludeSubDomains());
} else if (Objects.isNull(headers.isIncludeSubDomains())
&& Objects.nonNull(headers.isPreload())) {
http
.headers()
.httpStrictTransportSecurity()
.maxAgeInSeconds(headers.getMaxAge())
.preload(headers.isPreload());
} else {
http
.headers()
.httpStrictTransportSecurity()
.maxAgeInSeconds(headers.getMaxAge());
}
} else {
http
.headers()
.httpStrictTransportSecurity().disable();
}
}

protected void setXXSSProtection(HttpSecurity http) throws Exception {
XXSSProtection mode;
if (!existConfigurationHeaders()
|| getSecurityConfigProperties().getHeaders().getXxSsProtection() == null) {
mode = XXSSProtection.ENABLE;
} else {
mode = getSecurityConfigProperties().getHeaders().getXxSsProtection();
}
switch (mode) {
case DISABLE:
http
.headers()
.xssProtection().disable();
break;
case DISABLE_XSS:
http
.headers()
.xssProtection();
break;
case ENABLE:
http
.headers()
.xssProtection().xssProtectionEnabled(false);
break;
case ENABLE_MODE:
http
.headers()
.xssProtection().xssProtectionEnabled(true);
break;
}
}

protected void setContentSecurityPolicy(HttpSecurity http) throws Exception {
if (!existConfigurationHeaders()
|| getSecurityConfigProperties().getHeaders().getContentSecurityPolicy() == null
|| getSecurityConfigProperties().getHeaders().getContentSecurityPolicy().isEmpty()) {
http
.headers()
.addHeaderWriter(new StaticHeadersWriter("X-Content-Security-Policy", "frame-src: 'none'"));
} else {
http
.headers()
.contentSecurityPolicy(getSecurityConfigProperties().getHeaders().getContentSecurityPolicy());
}
}

protected void setXFrameOptions(HttpSecurity http) throws Exception {
XFrameOptionsMode mode;
if (!existConfigurationHeaders() || getSecurityConfigProperties().getHeaders().getXFrameOptions() == null) {
mode = XFrameOptionsMode.DISABLE;
} else {
mode = getSecurityConfigProperties().getHeaders().getXFrameOptions();
}
switch (mode) {
case SAMEORIGIN:
http
.headers()
.frameOptions()
.sameOrigin();
break;
case DENY:
http
.headers()
.frameOptions()
.deny();
break;
case DISABLE:
default:
http
.headers()
.frameOptions()
.disable();
break;
}
}

protected void setCsrf(HttpSecurity http) throws Exception {
if (isCsrfEnabled()) {
http
Expand Down Expand Up @@ -91,6 +223,10 @@ protected void configureFilters(HttpSecurity http) {
}
}

protected boolean existConfigurationHeaders() {
return getSecurityConfigProperties() != null && getSecurityConfigProperties().getHeaders() != null;
}

protected abstract boolean isOpenRegistration();

protected abstract boolean isCsrfEnabled();
Expand All @@ -102,4 +238,7 @@ protected void configureFilters(HttpSecurity http) {
protected abstract String[] getServerPatterns();

protected abstract Environment getEnvironment();

protected abstract SecurityConfigProperties getSecurityConfigProperties();

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.stereotype.Controller;
Expand Down Expand Up @@ -118,6 +117,7 @@ protected void configure(HttpSecurity http) throws Exception {
.addFilterBefore(createPublicAuthenticationFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(createSecurityContextFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(impersonationRequestFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(hostValidationRequestFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(getPatterns()).permitAll()
.antMatchers(OPTIONS).permitAll()
Expand All @@ -126,14 +126,9 @@ protected void configure(HttpSecurity http) throws Exception {
.logout()
.logoutUrl("/api/auth/logout")
.invalidateHttpSession(true)
.logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)))
.and()
.headers()
.frameOptions().disable()
.httpStrictTransportSecurity().includeSubDomains(true).maxAgeInSeconds(31536000)
.and()
.addHeaderWriter(new StaticHeadersWriter("X-Content-Security-Policy", "frame-src: 'none'"));
.logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)));

setHeaders(http);
setCsrf(http);
corsEnable(http);
}
Expand Down Expand Up @@ -173,6 +168,11 @@ protected Environment getEnvironment() {
return env;
}

@Override
protected SecurityConfigProperties getSecurityConfigProperties() {
return properties;
}

protected PublicAuthenticationFilter createPublicAuthenticationFilter() throws Exception {
Authority authority = authorityService.getOrCreate(Authority.anonymous);
authority.setUsers(new HashSet<>());
Expand All @@ -191,6 +191,10 @@ private SecurityContextFilter createSecurityContextFilter() {
return new SecurityContextFilter(securityContextService);
}

private HostValidationRequestFilter hostValidationRequestFilter() {
return new HostValidationRequestFilter(properties);
}

private ImpersonationRequestFilter impersonationRequestFilter() {
return new ImpersonationRequestFilter(impersonatorRepository);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,60 @@
package com.netgrif.application.engine.configuration.properties;

import com.netgrif.application.engine.configuration.properties.enumeration.HSTS;
import com.netgrif.application.engine.configuration.properties.enumeration.XFrameOptionsMode;
import com.netgrif.application.engine.configuration.properties.enumeration.XXSSProtection;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Data
@Configuration
@ConfigurationProperties(prefix = "nae.server.security.headers")
public class Headers {

/**
* Allowed HOST in HTTP header
*/
private List<String> hostAllowed;

/**
* The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be
* allowed to render a page in a <frame>, <iframe>, <embed> or <object>.
* Sites can use this to avoid click-jacking attacks, by ensuring that their content is not embedded into other sites.
* <p>
* More <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options">Info</a>
*/
private XFrameOptionsMode xFrameOptions = XFrameOptionsMode.DISABLE;

/**
* The HTTP X-XSS-Protection response header is a feature of Internet Explorer, Chrome and Safari
* that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks.
* These protections are largely unnecessary in modern browsers when sites implement a strong Content-Security-Policy
* that disables the use of inline JavaScript ('unsafe-inline').
* <p>
* More <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection">Info</a>
*/
private XXSSProtection xxSsProtection = XXSSProtection.ENABLE_MODE;

/**
* The HTTP Content-Security-Policy response header allows website administrators
* to control resources the user agent is allowed to load for a given page.
* With a few exceptions, policies mostly involve specifying server origins and script endpoints.
* This helps guard against cross-site scripting attacks (Cross-site_scripting).
* <p>
* More <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy">Info</a>
*/
private String contentSecurityPolicy;

/**
* The HTTP Strict-Transport-Security response header (often abbreviated as HSTS)
* informs browsers that the site should only be accessed using HTTPS,
* and that any future attempts to access it using HTTP should automatically be converted to HTTPS.
* <p>
* More <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">Info</a>
*/
private HSTS hsts;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.netgrif.application.engine.configuration.properties.enumeration;

import lombok.Data;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* The HTTP Strict-Transport-Security response header (often abbreviated as HSTS)
* informs browsers that the site should only be accessed using HTTPS,
* and that any future attempts to access it using HTTP should automatically be converted to HTTPS.
* <p>
* More <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">Info</a>
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "nae.server.security.headers.hsts")
public class HSTS {

private boolean enable;

/**
* The time, in seconds, that the browser should remember that a site is only to be accessed using HTTPS.
* <p>
* Default value: 31536000
*/
private long maxAge = 31536000;

/**
* If this optional parameter is specified, this rule applies to all of the site's subdomains as well.
*/
private boolean includeSubDomains;

/**
* See Preloading Strict Transport Security for details.
* When using preload, the max-age directive must be at least 31536000 (1 year),
* and the includeSubDomains directive must be present. Not part of the specification.
* <p>
*/
private boolean preload;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.netgrif.application.engine.configuration.properties.enumeration;

/**
* DENY - The page cannot be displayed in a frame, regardless of the site attempting to do so.
*
* SAMEORIGIN - The page can only be displayed if all ancestor frames are same origin to the page itself.
*
* More <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options">Info</a>
*/
public enum XFrameOptionsMode {

/**
* Disable X-Frame-Options
*/
DISABLE,

/**
* The page cannot be displayed in a frame, regardless of the site attempting to do so.
*/
DENY,

/**
* The page can only be displayed if all ancestor frames are same origin to the page itself.
*/
SAMEORIGIN

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.netgrif.application.engine.configuration.properties.enumeration;

/**
* DISABLE - Disable
*
* DISABLE_XSS (0) - Disables XSS filtering.
*
* ENABLE (1) - Enables XSS filtering (usually default in browsers). If a cross-site scripting attack is detected, the browser will sanitize the page (remove the unsafe parts).
*
* ENABLE_MODE (1; mode=block) - Enables XSS filtering. Rather than sanitizing the page, the browser will prevent rendering of the page if an attack is detected.
*
* More <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection">Info</a>
*/
public enum XXSSProtection {

DISABLE,
DISABLE_XSS,
ENABLE,
ENABLE_MODE


}