|
|
|
|
@@ -9,7 +9,6 @@
|
|
|
|
|
package org.eclipse.hawkbit.autoconfigure.security;
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.Collection;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
@@ -20,14 +19,12 @@ import javax.servlet.FilterConfig;
|
|
|
|
|
import javax.servlet.ServletException;
|
|
|
|
|
import javax.servlet.ServletRequest;
|
|
|
|
|
import javax.servlet.ServletResponse;
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
|
|
|
|
|
import org.eclipse.hawkbit.cache.DownloadIdCache;
|
|
|
|
|
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
|
|
|
|
|
import org.eclipse.hawkbit.ddi.rest.resource.DdiApiConfiguration;
|
|
|
|
|
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
|
|
|
|
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
|
|
|
|
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
|
|
|
|
import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter;
|
|
|
|
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
|
|
|
|
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtApiConfiguration;
|
|
|
|
|
@@ -46,7 +43,6 @@ import org.eclipse.hawkbit.security.HttpDownloadAuthenticationFilter;
|
|
|
|
|
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
|
|
|
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
|
|
|
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
|
|
|
|
import org.eclipse.hawkbit.ui.MgmtUiConfiguration;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
@@ -54,62 +50,41 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
|
|
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
|
|
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
|
|
|
|
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
|
|
|
|
|
import org.springframework.context.annotation.AdviceMode;
|
|
|
|
|
import org.springframework.context.annotation.Bean;
|
|
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
|
import org.springframework.context.annotation.Lazy;
|
|
|
|
|
import org.springframework.context.annotation.Primary;
|
|
|
|
|
import org.springframework.context.annotation.PropertySource;
|
|
|
|
|
import org.springframework.core.Ordered;
|
|
|
|
|
import org.springframework.core.annotation.Order;
|
|
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
|
|
import org.springframework.security.access.AccessDecisionManager;
|
|
|
|
|
import org.springframework.security.authentication.AuthenticationManager;
|
|
|
|
|
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
|
|
|
|
import org.springframework.security.config.Customizer;
|
|
|
|
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
|
|
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
|
|
|
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
|
|
|
|
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
|
|
|
|
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
|
|
|
|
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
|
|
|
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
|
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
|
|
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
|
|
|
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
|
|
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
|
|
|
import org.springframework.security.core.Authentication;
|
|
|
|
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
|
|
|
import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
|
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
|
|
|
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
|
|
|
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
|
|
|
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
|
|
|
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
|
|
|
|
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
|
|
|
|
|
import org.springframework.security.web.SecurityFilterChain;
|
|
|
|
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
|
|
|
|
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
|
|
|
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
|
|
|
|
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
|
|
|
|
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
|
|
|
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
|
|
|
|
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
|
|
|
|
|
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
|
|
|
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
|
|
|
|
import org.springframework.security.web.firewall.FirewalledRequest;
|
|
|
|
|
import org.springframework.security.web.firewall.HttpFirewall;
|
|
|
|
|
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
|
|
|
|
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
|
|
|
|
import org.springframework.security.web.session.SessionManagementFilter;
|
|
|
|
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
|
|
|
import org.springframework.util.Assert;
|
|
|
|
|
import org.springframework.util.CollectionUtils;
|
|
|
|
|
import org.springframework.util.StringUtils;
|
|
|
|
|
import org.springframework.web.cors.CorsConfiguration;
|
|
|
|
|
import org.vaadin.spring.http.HttpService;
|
|
|
|
|
import org.vaadin.spring.security.annotation.EnableVaadinSharedSecurity;
|
|
|
|
|
import org.vaadin.spring.security.config.VaadinSharedSecurityConfiguration;
|
|
|
|
|
import org.vaadin.spring.security.shared.VaadinAuthenticationSuccessHandler;
|
|
|
|
|
import org.vaadin.spring.security.shared.VaadinUrlAuthenticationSuccessHandler;
|
|
|
|
|
import org.vaadin.spring.security.web.VaadinRedirectStrategy;
|
|
|
|
|
import org.springframework.web.cors.CorsConfigurationSource;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* All configurations related to HawkBit's authentication and authorization
|
|
|
|
|
@@ -155,18 +130,18 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
* {@link WebSecurityConfigurer} for the hawkBit server DDI interface.
|
|
|
|
|
*/
|
|
|
|
|
@Configuration
|
|
|
|
|
@Order(300)
|
|
|
|
|
@EnableWebSecurity
|
|
|
|
|
@ConditionalOnClass(DdiApiConfiguration.class)
|
|
|
|
|
static class ControllerSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
|
|
|
|
|
static class ControllerSecurityConfigurationAdapter {
|
|
|
|
|
|
|
|
|
|
private static final String[] DDI_ANT_MATCHERS = { DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}",
|
|
|
|
|
private static final String[] DDI_ANT_MATCHERS = {
|
|
|
|
|
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}",
|
|
|
|
|
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/confirmationBase/**",
|
|
|
|
|
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/deploymentBase/**",
|
|
|
|
|
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/installedBase/**",
|
|
|
|
|
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/cancelAction/**",
|
|
|
|
|
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/configData",
|
|
|
|
|
DdiRestConstants.BASE_V1_REQUEST_MAPPING
|
|
|
|
|
+ "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts" };
|
|
|
|
|
DdiRestConstants.BASE_V1_REQUEST_MAPPING + "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts" };
|
|
|
|
|
|
|
|
|
|
private final ControllerManagement controllerManagement;
|
|
|
|
|
private final TenantConfigurationManagement tenantConfigurationManagement;
|
|
|
|
|
@@ -200,75 +175,79 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
*/
|
|
|
|
|
@Bean
|
|
|
|
|
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true)
|
|
|
|
|
public FilterRegistrationBean<DosFilter> dosDDiFilter(final HawkbitSecurityProperties securityProperties) {
|
|
|
|
|
|
|
|
|
|
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(Arrays.asList(DDI_ANT_MATCHERS),
|
|
|
|
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
|
|
|
|
public FilterRegistrationBean<DosFilter> dosFilterDDI(final HawkbitSecurityProperties securityProperties) {
|
|
|
|
|
final FilterRegistrationBean<DosFilter> filterRegBean =
|
|
|
|
|
dosFilter(List.of(DDI_ANT_MATCHERS),
|
|
|
|
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
|
|
|
|
filterRegBean.setOrder(DOS_FILTER_ORDER);
|
|
|
|
|
filterRegBean.setName("dosDDiFilter");
|
|
|
|
|
|
|
|
|
|
return filterRegBean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void configure(final HttpSecurity http) throws Exception {
|
|
|
|
|
@Bean
|
|
|
|
|
@Order(300)
|
|
|
|
|
protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception {
|
|
|
|
|
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
|
|
|
|
|
|
|
|
|
|
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter(
|
|
|
|
|
ddiSecurityConfiguration.getRp().getCnHeader(),
|
|
|
|
|
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement,
|
|
|
|
|
tenantAware, systemSecurityContext);
|
|
|
|
|
securityHeaderFilter.setAuthenticationManager(authenticationManager());
|
|
|
|
|
securityHeaderFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext);
|
|
|
|
|
securityTokenFilter.setAuthenticationManager(authenticationManager());
|
|
|
|
|
securityTokenFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
|
|
|
|
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager());
|
|
|
|
|
gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
HttpSecurity httpSec = http.csrf().disable();
|
|
|
|
|
http
|
|
|
|
|
.requestMatchers(requestMatchers -> requestMatchers.antMatchers(DDI_ANT_MATCHERS))
|
|
|
|
|
.csrf(AbstractHttpConfigurer::disable);
|
|
|
|
|
|
|
|
|
|
if (securityProperties.isRequireSsl()) {
|
|
|
|
|
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
|
|
|
|
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
|
|
|
|
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
|
|
|
|
|
|
|
|
|
|
LOG.info(
|
|
|
|
|
"******************\n** Anonymous controller security enabled, should only be used for developing purposes **\n******************");
|
|
|
|
|
"""
|
|
|
|
|
******************
|
|
|
|
|
** Anonymous controller security enabled, should only be used for developing purposes **
|
|
|
|
|
******************""");
|
|
|
|
|
|
|
|
|
|
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
|
|
|
|
|
"controllerAnonymousFilter", "anonymous",
|
|
|
|
|
Arrays.asList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
|
|
|
|
List.of(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
|
|
|
|
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
httpSec.requestMatchers().antMatchers(DDI_ANT_MATCHERS).and().securityContext().disable().anonymous()
|
|
|
|
|
.authenticationFilter(anonymousFilter);
|
|
|
|
|
http
|
|
|
|
|
.securityContext(AbstractHttpConfigurer::disable)
|
|
|
|
|
.anonymous(configurer -> configurer.authenticationFilter(anonymousFilter));
|
|
|
|
|
} else {
|
|
|
|
|
final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter(
|
|
|
|
|
ddiSecurityConfiguration.getRp().getCnHeader(),
|
|
|
|
|
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement,
|
|
|
|
|
tenantAware, systemSecurityContext);
|
|
|
|
|
securityHeaderFilter.setAuthenticationManager(authenticationManager);
|
|
|
|
|
securityHeaderFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
httpSec.addFilter(securityHeaderFilter).addFilter(securityTokenFilter)
|
|
|
|
|
.addFilter(gatewaySecurityTokenFilter).requestMatchers().antMatchers(DDI_ANT_MATCHERS).and()
|
|
|
|
|
.anonymous().disable().authorizeRequests().anyRequest().authenticated().and()
|
|
|
|
|
.exceptionHandling()
|
|
|
|
|
.authenticationEntryPoint((request, response, authException) -> response
|
|
|
|
|
.setStatus(HttpStatus.UNAUTHORIZED.value()))
|
|
|
|
|
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
|
|
|
|
final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext);
|
|
|
|
|
securityTokenFilter.setAuthenticationManager(authenticationManager);
|
|
|
|
|
securityTokenFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
|
|
|
|
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager);
|
|
|
|
|
gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
http
|
|
|
|
|
.authorizeHttpRequests(amrmRegistry ->
|
|
|
|
|
amrmRegistry.anyRequest().authenticated())
|
|
|
|
|
.anonymous(AbstractHttpConfigurer::disable)
|
|
|
|
|
.addFilter(securityHeaderFilter)
|
|
|
|
|
.addFilter(securityTokenFilter)
|
|
|
|
|
.addFilter(gatewaySecurityTokenFilter)
|
|
|
|
|
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(
|
|
|
|
|
(request, response, authException) ->
|
|
|
|
|
response.setStatus(HttpStatus.UNAUTHORIZED.value())))
|
|
|
|
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
|
|
|
|
|
|
|
|
|
|
auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider(
|
|
|
|
|
ddiSecurityConfiguration.getRp().getTrustedIPs()));
|
|
|
|
|
return http.build();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -277,9 +256,8 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
* interface.
|
|
|
|
|
*/
|
|
|
|
|
@Configuration
|
|
|
|
|
@Order(301)
|
|
|
|
|
@ConditionalOnClass(DdiApiConfiguration.class)
|
|
|
|
|
static class ControllerDownloadSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
|
|
|
|
|
static class ControllerDownloadSecurityConfigurationAdapter {
|
|
|
|
|
|
|
|
|
|
private static final String DDI_DL_ANT_MATCHER = DdiRestConstants.BASE_V1_REQUEST_MAPPING
|
|
|
|
|
+ "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/*";
|
|
|
|
|
@@ -316,9 +294,8 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
*/
|
|
|
|
|
@Bean
|
|
|
|
|
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true)
|
|
|
|
|
public FilterRegistrationBean<DosFilter> dosDDiDlFilter(final HawkbitSecurityProperties securityProperties) {
|
|
|
|
|
|
|
|
|
|
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(Arrays.asList(DDI_DL_ANT_MATCHER),
|
|
|
|
|
public FilterRegistrationBean<DosFilter> dosFilterDDIDL(final HawkbitSecurityProperties securityProperties) {
|
|
|
|
|
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(List.of(DDI_DL_ANT_MATCHER),
|
|
|
|
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
|
|
|
|
filterRegBean.setOrder(DOS_FILTER_ORDER);
|
|
|
|
|
filterRegBean.setName("dosDDiDlFilter");
|
|
|
|
|
@@ -326,71 +303,75 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
return filterRegBean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void configure(final HttpSecurity http) throws Exception {
|
|
|
|
|
@Bean
|
|
|
|
|
@Order(301)
|
|
|
|
|
protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception {
|
|
|
|
|
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
|
|
|
|
|
|
|
|
|
|
http
|
|
|
|
|
.requestMatcher(new AntPathRequestMatcher(DDI_DL_ANT_MATCHER))
|
|
|
|
|
.csrf(AbstractHttpConfigurer::disable);
|
|
|
|
|
|
|
|
|
|
if (securityProperties.isRequireSsl()) {
|
|
|
|
|
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter(
|
|
|
|
|
ddiSecurityConfiguration.getRp().getCnHeader(),
|
|
|
|
|
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement,
|
|
|
|
|
tenantAware, systemSecurityContext);
|
|
|
|
|
securityHeaderFilter.setAuthenticationManager(authenticationManager());
|
|
|
|
|
securityHeaderFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext);
|
|
|
|
|
securityTokenFilter.setAuthenticationManager(authenticationManager());
|
|
|
|
|
securityTokenFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
|
|
|
|
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager());
|
|
|
|
|
gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticateAnonymousDownloadFilter controllerAnonymousDownloadFilter = new HttpControllerPreAuthenticateAnonymousDownloadFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
|
|
|
|
controllerAnonymousDownloadFilter.setAuthenticationManager(authenticationManager());
|
|
|
|
|
controllerAnonymousDownloadFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
controllerAnonymousDownloadFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
HttpSecurity httpSec = http.csrf().disable();
|
|
|
|
|
|
|
|
|
|
if (securityProperties.isRequireSsl()) {
|
|
|
|
|
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
|
|
|
|
|
|
|
|
|
|
LOG.info(
|
|
|
|
|
"******************\n** Anonymous controller security enabled, should only be used for developing purposes **\n******************");
|
|
|
|
|
"""
|
|
|
|
|
******************
|
|
|
|
|
** Anonymous controller security enabled, should only be used for developing purposes **
|
|
|
|
|
******************""");
|
|
|
|
|
|
|
|
|
|
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
|
|
|
|
|
"controllerAnonymousFilter", "anonymous",
|
|
|
|
|
Arrays.asList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
|
|
|
|
List.of(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
|
|
|
|
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
httpSec.requestMatchers().antMatchers(DDI_DL_ANT_MATCHER).and().securityContext().disable().anonymous()
|
|
|
|
|
.authenticationFilter(anonymousFilter);
|
|
|
|
|
http
|
|
|
|
|
.securityContext(AbstractHttpConfigurer::disable)
|
|
|
|
|
.anonymous(configurer -> configurer.authenticationFilter(anonymousFilter));
|
|
|
|
|
} else {
|
|
|
|
|
final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter(
|
|
|
|
|
ddiSecurityConfiguration.getRp().getCnHeader(),
|
|
|
|
|
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement,
|
|
|
|
|
tenantAware, systemSecurityContext);
|
|
|
|
|
securityHeaderFilter.setAuthenticationManager(authenticationManager);
|
|
|
|
|
securityHeaderFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
httpSec.addFilter(securityHeaderFilter).addFilter(securityTokenFilter)
|
|
|
|
|
.addFilter(gatewaySecurityTokenFilter).addFilter(controllerAnonymousDownloadFilter)
|
|
|
|
|
.requestMatchers().antMatchers(DDI_DL_ANT_MATCHER).and().anonymous().disable()
|
|
|
|
|
.authorizeRequests().anyRequest().authenticated().and().exceptionHandling()
|
|
|
|
|
.authenticationEntryPoint((request, response, authException) -> response
|
|
|
|
|
.setStatus(HttpStatus.UNAUTHORIZED.value()))
|
|
|
|
|
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
|
|
|
|
final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext);
|
|
|
|
|
securityTokenFilter.setAuthenticationManager(authenticationManager);
|
|
|
|
|
securityTokenFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
|
|
|
|
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager);
|
|
|
|
|
gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
final HttpControllerPreAuthenticateAnonymousDownloadFilter controllerAnonymousDownloadFilter = new HttpControllerPreAuthenticateAnonymousDownloadFilter(
|
|
|
|
|
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
|
|
|
|
controllerAnonymousDownloadFilter.setAuthenticationManager(authenticationManager);
|
|
|
|
|
controllerAnonymousDownloadFilter.setCheckForPrincipalChanges(true);
|
|
|
|
|
controllerAnonymousDownloadFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
|
|
|
|
|
|
|
|
http
|
|
|
|
|
.authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated())
|
|
|
|
|
.anonymous(AbstractHttpConfigurer::disable)
|
|
|
|
|
.addFilter(securityHeaderFilter)
|
|
|
|
|
.addFilter(securityTokenFilter)
|
|
|
|
|
.addFilter(gatewaySecurityTokenFilter)
|
|
|
|
|
.addFilter(controllerAnonymousDownloadFilter)
|
|
|
|
|
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(
|
|
|
|
|
(request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value())))
|
|
|
|
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
|
|
|
|
|
|
|
|
|
|
auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider(
|
|
|
|
|
ddiSecurityConfiguration.getRp().getTrustedIPs()));
|
|
|
|
|
return http.build();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -407,10 +388,9 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
@Bean
|
|
|
|
|
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true)
|
|
|
|
|
public FilterRegistrationBean<DosFilter> dosSystemFilter(final HawkbitSecurityProperties securityProperties) {
|
|
|
|
|
|
|
|
|
|
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(Collections.emptyList(),
|
|
|
|
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
|
|
|
|
filterRegBean.setUrlPatterns(Arrays.asList("/system/*"));
|
|
|
|
|
filterRegBean.setUrlPatterns(List.of("/system/*"));
|
|
|
|
|
filterRegBean.setOrder(DOS_FILTER_ORDER);
|
|
|
|
|
filterRegBean.setName("dosSystemFilter");
|
|
|
|
|
|
|
|
|
|
@@ -420,7 +400,6 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
private static FilterRegistrationBean<DosFilter> dosFilter(final Collection<String> includeAntPaths,
|
|
|
|
|
final HawkbitSecurityProperties.Dos.Filter filterProperties,
|
|
|
|
|
final HawkbitSecurityProperties.Clients clientProperties) {
|
|
|
|
|
|
|
|
|
|
final FilterRegistrationBean<DosFilter> filterRegBean = new FilterRegistrationBean<>();
|
|
|
|
|
|
|
|
|
|
filterRegBean.setFilter(new DosFilter(includeAntPaths, filterProperties.getMaxRead(),
|
|
|
|
|
@@ -435,36 +414,30 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
*/
|
|
|
|
|
@Configuration
|
|
|
|
|
@EnableWebSecurity
|
|
|
|
|
@Order(320)
|
|
|
|
|
@ConditionalOnClass(MgmtApiConfiguration.class)
|
|
|
|
|
public static class IdRestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
|
|
|
|
|
public static class IdRestSecurityConfigurationAdapter {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private DdiSecurityProperties ddiSecurityConfiguration;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private DownloadIdCache downloadIdCache;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void configure(final HttpSecurity http) throws Exception {
|
|
|
|
|
@Bean
|
|
|
|
|
@Order(320)
|
|
|
|
|
protected SecurityFilterChain filterChainDLID(
|
|
|
|
|
final HttpSecurity http,
|
|
|
|
|
final DdiSecurityProperties ddiSecurityConfiguration, final DownloadIdCache downloadIdCache)
|
|
|
|
|
throws Exception {
|
|
|
|
|
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
|
|
|
|
|
|
|
|
|
|
final HttpDownloadAuthenticationFilter downloadIdAuthenticationFilter = new HttpDownloadAuthenticationFilter(
|
|
|
|
|
downloadIdCache);
|
|
|
|
|
downloadIdAuthenticationFilter.setAuthenticationManager(authenticationManager());
|
|
|
|
|
downloadIdAuthenticationFilter.setAuthenticationManager(authenticationManager);
|
|
|
|
|
|
|
|
|
|
http.csrf().disable();
|
|
|
|
|
http.anonymous().disable();
|
|
|
|
|
http
|
|
|
|
|
.requestMatcher(new AntPathRequestMatcher("/**/downloadId/**"))
|
|
|
|
|
.authorizeHttpRequests(armrRepository -> armrRepository.anyRequest().authenticated())
|
|
|
|
|
.csrf(AbstractHttpConfigurer::disable)
|
|
|
|
|
.anonymous(AbstractHttpConfigurer::disable)
|
|
|
|
|
.addFilterBefore(downloadIdAuthenticationFilter, FilterSecurityInterceptor.class)
|
|
|
|
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
|
|
|
|
|
|
|
|
|
http.antMatcher("/**/downloadId/**").addFilterBefore(downloadIdAuthenticationFilter,
|
|
|
|
|
FilterSecurityInterceptor.class);
|
|
|
|
|
http.authorizeRequests().anyRequest().authenticated().and().sessionManagement()
|
|
|
|
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
|
|
|
|
|
auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider(
|
|
|
|
|
ddiSecurityConfiguration.getRp().getTrustedIPs()));
|
|
|
|
|
return http.build();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -472,72 +445,79 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
* Security configuration for the REST management API.
|
|
|
|
|
*/
|
|
|
|
|
@Configuration
|
|
|
|
|
@Order(350)
|
|
|
|
|
@EnableWebSecurity
|
|
|
|
|
@ConditionalOnClass(MgmtApiConfiguration.class)
|
|
|
|
|
public static class RestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
|
|
|
|
|
public static class RestSecurityConfigurationAdapter {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
@Lazy
|
|
|
|
|
private UserAuthenticationFilter userAuthenticationFilter;
|
|
|
|
|
private final HawkbitSecurityProperties securityProperties;
|
|
|
|
|
|
|
|
|
|
@Autowired(required = false)
|
|
|
|
|
private OidcBearerTokenAuthenticationFilter oidcBearerTokenAuthenticationFilter;
|
|
|
|
|
|
|
|
|
|
@Autowired(required = false)
|
|
|
|
|
private InMemoryClientRegistrationRepository clientRegistrationRepository;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private SystemManagement systemManagement;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private HawkbitSecurityProperties securityProperties;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private SystemSecurityContext systemSecurityContext;
|
|
|
|
|
public RestSecurityConfigurationAdapter(final HawkbitSecurityProperties securityProperties) {
|
|
|
|
|
this.securityProperties = securityProperties;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Filter to protect the hawkBit server Management interface against to
|
|
|
|
|
* many requests.
|
|
|
|
|
*
|
|
|
|
|
* @param securityProperties
|
|
|
|
|
* for filter configuration
|
|
|
|
|
*
|
|
|
|
|
* @return the spring filter registration bean for registering a denial
|
|
|
|
|
* of service protection filter in the filter chain
|
|
|
|
|
*/
|
|
|
|
|
@Bean
|
|
|
|
|
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.filter", name = "enabled", matchIfMissing = true)
|
|
|
|
|
public FilterRegistrationBean<DosFilter> dosMgmtFilter(final HawkbitSecurityProperties securityProperties) {
|
|
|
|
|
|
|
|
|
|
public FilterRegistrationBean<DosFilter> dosFilterREST() {
|
|
|
|
|
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(null,
|
|
|
|
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
|
|
|
|
filterRegBean.setUrlPatterns(Arrays.asList("/rest/*", "/api/*"));
|
|
|
|
|
filterRegBean.setUrlPatterns(List.of("/rest/*", "/api/*"));
|
|
|
|
|
filterRegBean.setOrder(DOS_FILTER_ORDER);
|
|
|
|
|
filterRegBean.setName("dosMgmtFilter");
|
|
|
|
|
|
|
|
|
|
return filterRegBean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void configure(final HttpSecurity http) throws Exception {
|
|
|
|
|
|
|
|
|
|
HttpSecurity httpSec = http.requestMatchers().antMatchers("/rest/**", "/system/admin/**").and().csrf()
|
|
|
|
|
.disable();
|
|
|
|
|
@Bean
|
|
|
|
|
@Order(350)
|
|
|
|
|
protected SecurityFilterChain filterChainREST(
|
|
|
|
|
final HttpSecurity http,
|
|
|
|
|
@Lazy
|
|
|
|
|
final UserAuthenticationFilter userAuthenticationFilter,
|
|
|
|
|
@Autowired(required = false)
|
|
|
|
|
final OidcBearerTokenAuthenticationFilter oidcBearerTokenAuthenticationFilter,
|
|
|
|
|
@Autowired(required = false)
|
|
|
|
|
final InMemoryClientRegistrationRepository clientRegistrationRepository,
|
|
|
|
|
final SystemManagement systemManagement,
|
|
|
|
|
final SystemSecurityContext systemSecurityContext)
|
|
|
|
|
throws Exception {
|
|
|
|
|
http
|
|
|
|
|
.requestMatchers(requestMatchers -> requestMatchers.antMatchers("/rest/**", MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**"))
|
|
|
|
|
.csrf(AbstractHttpConfigurer::disable)
|
|
|
|
|
.authorizeHttpRequests(amrmRegistry ->
|
|
|
|
|
amrmRegistry
|
|
|
|
|
.antMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")
|
|
|
|
|
.hasAnyAuthority(SpPermission.SYSTEM_ADMIN)
|
|
|
|
|
.anyRequest()
|
|
|
|
|
.authenticated())
|
|
|
|
|
.addFilterAfter(
|
|
|
|
|
// Servlet filter to create metadata after successful authentication over RESTful.
|
|
|
|
|
(request, response, chain) -> {
|
|
|
|
|
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
|
|
|
if (authentication != null && authentication.isAuthenticated()) {
|
|
|
|
|
systemSecurityContext.runAsSystem(systemManagement::getTenantMetadata);
|
|
|
|
|
}
|
|
|
|
|
chain.doFilter(request, response);
|
|
|
|
|
},
|
|
|
|
|
SessionManagementFilter.class)
|
|
|
|
|
.anonymous(AbstractHttpConfigurer::disable)
|
|
|
|
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
|
|
|
|
|
|
|
|
|
if (securityProperties.getCors().isEnabled()) {
|
|
|
|
|
httpSec = httpSec.cors().configurationSource(reuest -> corsConfiguration()).and();
|
|
|
|
|
http.cors(configurer -> configurer.configurationSource(corsConfigurationSource()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (securityProperties.isRequireSsl()) {
|
|
|
|
|
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
|
|
|
|
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
httpSec.authorizeRequests().antMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")
|
|
|
|
|
.hasAnyAuthority(SpPermission.SYSTEM_ADMIN).anyRequest().authenticated();
|
|
|
|
|
|
|
|
|
|
if (oidcBearerTokenAuthenticationFilter != null) {
|
|
|
|
|
|
|
|
|
|
// Only get the first client registration. Testing against every
|
|
|
|
|
// client could increase the
|
|
|
|
|
// attack vector
|
|
|
|
|
@@ -547,16 +527,16 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
Assert.notNull(clientRegistration, "There must be a valid client registration");
|
|
|
|
|
httpSec.oauth2ResourceServer().jwt().jwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri());
|
|
|
|
|
http.oauth2ResourceServer(configurer -> configurer.jwt().jwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri()));
|
|
|
|
|
|
|
|
|
|
oidcBearerTokenAuthenticationFilter.setClientRegistration(clientRegistration);
|
|
|
|
|
|
|
|
|
|
httpSec.addFilterAfter(oidcBearerTokenAuthenticationFilter, BearerTokenAuthenticationFilter.class);
|
|
|
|
|
http.addFilterAfter(oidcBearerTokenAuthenticationFilter, BearerTokenAuthenticationFilter.class);
|
|
|
|
|
} else {
|
|
|
|
|
final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
|
|
|
|
|
basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm());
|
|
|
|
|
|
|
|
|
|
httpSec.addFilterBefore(new Filter() {
|
|
|
|
|
http.addFilterBefore(new Filter() {
|
|
|
|
|
@Override
|
|
|
|
|
public void init(final FilterConfig filterConfig) throws ServletException {
|
|
|
|
|
userAuthenticationFilter.init(filterConfig);
|
|
|
|
|
@@ -573,20 +553,22 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
userAuthenticationFilter.destroy();
|
|
|
|
|
}
|
|
|
|
|
}, RequestHeaderAuthenticationFilter.class);
|
|
|
|
|
httpSec.httpBasic().and().exceptionHandling().authenticationEntryPoint(basicAuthEntryPoint);
|
|
|
|
|
http
|
|
|
|
|
.httpBasic(Customizer.withDefaults())
|
|
|
|
|
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(basicAuthEntryPoint));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
httpSec.addFilterAfter(
|
|
|
|
|
new AuthenticationSuccessTenantMetadataCreationFilter(systemManagement, systemSecurityContext),
|
|
|
|
|
SessionManagementFilter.class);
|
|
|
|
|
|
|
|
|
|
httpSec.anonymous().disable();
|
|
|
|
|
httpSec.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
|
|
|
|
return http.build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Bean
|
|
|
|
|
@ConditionalOnProperty(prefix = "hawkbit.server.security.cors", name = "enabled", matchIfMissing = false)
|
|
|
|
|
CorsConfiguration corsConfiguration() {
|
|
|
|
|
@ConditionalOnProperty(prefix = "hawkbit.server.security.cors", name = "enabled")
|
|
|
|
|
CorsConfigurationSource corsConfigurationSource() {
|
|
|
|
|
final CorsConfiguration configuration = corsConfiguration();
|
|
|
|
|
return request -> configuration;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private CorsConfiguration corsConfiguration() {
|
|
|
|
|
final CorsConfiguration corsConfiguration = new CorsConfiguration();
|
|
|
|
|
|
|
|
|
|
corsConfiguration.setAllowedOrigins(securityProperties.getCors().getAllowedOrigins());
|
|
|
|
|
@@ -599,281 +581,15 @@ public class SecurityManagedConfiguration {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* {@link WebSecurityConfigurer} for external (management) access.
|
|
|
|
|
*/
|
|
|
|
|
@Configuration
|
|
|
|
|
@Order(400)
|
|
|
|
|
@EnableWebSecurity
|
|
|
|
|
@EnableVaadinSharedSecurity
|
|
|
|
|
@ConditionalOnClass(MgmtUiConfiguration.class)
|
|
|
|
|
public static class UISecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private HawkbitSecurityProperties hawkbitSecurityProperties;
|
|
|
|
|
|
|
|
|
|
@Autowired(required = false)
|
|
|
|
|
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
|
|
|
|
|
|
|
|
|
|
@Autowired(required = false)
|
|
|
|
|
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private LogoutHandler logoutHandler;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private LogoutSuccessHandler logoutSuccessHandler;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Filter to protect the hawkBit management UI against to many requests.
|
|
|
|
|
*
|
|
|
|
|
* @param securityProperties
|
|
|
|
|
* for filter configuration
|
|
|
|
|
*
|
|
|
|
|
* @return the spring filter registration bean for registering a denial
|
|
|
|
|
* of service protection filter in the filter chain
|
|
|
|
|
*/
|
|
|
|
|
@Bean
|
|
|
|
|
@ConditionalOnProperty(prefix = "hawkbit.server.security.dos.ui-filter", name = "enabled", matchIfMissing = true)
|
|
|
|
|
public FilterRegistrationBean<DosFilter> dosMgmtUiFilter(final HawkbitSecurityProperties securityProperties) {
|
|
|
|
|
|
|
|
|
|
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(null,
|
|
|
|
|
securityProperties.getDos().getUiFilter(), securityProperties.getClients());
|
|
|
|
|
// All URLs that can be called anonymous
|
|
|
|
|
filterRegBean.setUrlPatterns(Arrays.asList("/UI/login", "/UI/login/*", "/UI/logout", "/UI/logout/*"));
|
|
|
|
|
filterRegBean.setOrder(DOS_FILTER_ORDER);
|
|
|
|
|
filterRegBean.setName("dosMgmtUiFilter");
|
|
|
|
|
|
|
|
|
|
return filterRegBean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
@Bean(name = VaadinSharedSecurityConfiguration.AUTHENTICATION_MANAGER_BEAN)
|
|
|
|
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
|
|
|
|
return super.authenticationManagerBean();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Overwriting VaadinAuthenticationSuccessHandler of default
|
|
|
|
|
* VaadinSharedSecurityConfiguration
|
|
|
|
|
*
|
|
|
|
|
* @return the vaadin success authentication handler
|
|
|
|
|
*/
|
|
|
|
|
@Primary
|
|
|
|
|
@Bean(name = VaadinSharedSecurityConfiguration.VAADIN_AUTHENTICATION_SUCCESS_HANDLER_BEAN)
|
|
|
|
|
public VaadinAuthenticationSuccessHandler redirectSaveHandler(final HttpService httpService,
|
|
|
|
|
final VaadinRedirectStrategy redirectStrategy) {
|
|
|
|
|
final VaadinUrlAuthenticationSuccessHandler handler = new TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler(
|
|
|
|
|
httpService, redirectStrategy, "/UI/");
|
|
|
|
|
handler.setTargetUrlParameter("r");
|
|
|
|
|
|
|
|
|
|
return handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Listener to redirect to login page after session timeout. Close the
|
|
|
|
|
* vaadin session, because it's is not possible to redirect in
|
|
|
|
|
* atmosphere.
|
|
|
|
|
*
|
|
|
|
|
* @return the servlet listener.
|
|
|
|
|
*/
|
|
|
|
|
@Bean
|
|
|
|
|
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
|
|
|
|
|
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void configure(final HttpSecurity http) throws Exception {
|
|
|
|
|
|
|
|
|
|
final boolean enableOidc = oidcUserService != null && authenticationSuccessHandler != null;
|
|
|
|
|
|
|
|
|
|
// workaround regex: we need to exclude the URL /UI/HEARTBEAT here
|
|
|
|
|
// because we bound the vaadin application to /UI and not to root,
|
|
|
|
|
// described in vaadin-forum:
|
|
|
|
|
// https://vaadin.com/forum#!/thread/3200565.
|
|
|
|
|
HttpSecurity httpSec;
|
|
|
|
|
if (enableOidc) {
|
|
|
|
|
httpSec = http.requestMatchers().antMatchers("/**/UI/**", "/**/oauth2/**").and();
|
|
|
|
|
} else {
|
|
|
|
|
httpSec = http.antMatcher("/**/UI/**");
|
|
|
|
|
}
|
|
|
|
|
// disable as CSRF is handled by Vaadin
|
|
|
|
|
httpSec.csrf().disable();
|
|
|
|
|
// allow same origin X-Frame-Options for correct file download under
|
|
|
|
|
// Safari
|
|
|
|
|
httpSec.headers().frameOptions().sameOrigin();
|
|
|
|
|
|
|
|
|
|
if (hawkbitSecurityProperties.isRequireSsl()) {
|
|
|
|
|
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
LOG.info(
|
|
|
|
|
"\"******************\\n** Requires HTTPS Security has been disabled for UI, should only be used for developing purposes **\\n******************\"");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!StringUtils.isEmpty(hawkbitSecurityProperties.getContentSecurityPolicy())) {
|
|
|
|
|
httpSec.headers().contentSecurityPolicy(hawkbitSecurityProperties.getContentSecurityPolicy());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UI
|
|
|
|
|
httpSec.authorizeRequests().antMatchers("/UI/login/**", "/UI/UIDL/**").permitAll().anyRequest()
|
|
|
|
|
.authenticated();
|
|
|
|
|
|
|
|
|
|
if (enableOidc) {
|
|
|
|
|
// OIDC
|
|
|
|
|
httpSec.oauth2Login().userInfoEndpoint().oidcUserService(oidcUserService).and()
|
|
|
|
|
.successHandler(authenticationSuccessHandler).and().oauth2Client();
|
|
|
|
|
} else {
|
|
|
|
|
// UI login / Basic auth
|
|
|
|
|
httpSec.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/UI/login"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UI logout
|
|
|
|
|
httpSec.logout().logoutUrl("/UI/logout*").addLogoutHandler(logoutHandler)
|
|
|
|
|
.logoutSuccessHandler(logoutSuccessHandler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* HttpFirewall which enables to define a list of allowed host names.
|
|
|
|
|
*
|
|
|
|
|
* @return the http firewall.
|
|
|
|
|
*/
|
|
|
|
|
@Bean
|
|
|
|
|
public HttpFirewall httpFirewall() {
|
|
|
|
|
final List<String> allowedHostNames = hawkbitSecurityProperties.getAllowedHostNames();
|
|
|
|
|
final IgnorePathsStrictHttpFirewall firewall = new IgnorePathsStrictHttpFirewall(
|
|
|
|
|
hawkbitSecurityProperties.getHttpFirewallIgnoredPaths());
|
|
|
|
|
|
|
|
|
|
if (!CollectionUtils.isEmpty(allowedHostNames)) {
|
|
|
|
|
firewall.setAllowedHostnames(hostName -> {
|
|
|
|
|
LOG.debug("Firewall check host: {}, allowed: {}", hostName, allowedHostNames.contains(hostName));
|
|
|
|
|
return allowedHostNames.contains(hostName);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return firewall;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class IgnorePathsStrictHttpFirewall extends StrictHttpFirewall {
|
|
|
|
|
|
|
|
|
|
private final Collection<String> pathsToIgnore;
|
|
|
|
|
|
|
|
|
|
public IgnorePathsStrictHttpFirewall(final Collection<String> pathsToIgnore) {
|
|
|
|
|
super();
|
|
|
|
|
this.pathsToIgnore = pathsToIgnore;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public FirewalledRequest getFirewalledRequest(final HttpServletRequest request) {
|
|
|
|
|
if (pathsToIgnore != null && pathsToIgnore.contains(request.getRequestURI())) {
|
|
|
|
|
return new FirewalledRequest(request) {
|
|
|
|
|
@Override
|
|
|
|
|
public void reset() {
|
|
|
|
|
// nothing to do
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return super.getFirewalledRequest(request);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void configure(final WebSecurity webSecurity) throws Exception {
|
|
|
|
|
// No security for static content
|
|
|
|
|
webSecurity.ignoring().antMatchers("/documentation/**", "/VAADIN/**", "/*.*", "/docs/**");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Configuration that defines the {@link AccessDecisionManager} bean for
|
|
|
|
|
* UI method security used by the Vaadin Servlet. Notice: we can not use
|
|
|
|
|
* the top-level method security configuration because
|
|
|
|
|
* {@link AdviceMode.ASPECTJ} is not supported.
|
|
|
|
|
*/
|
|
|
|
|
@Configuration
|
|
|
|
|
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
|
|
|
|
|
@ConditionalOnClass(MgmtUiConfiguration.class)
|
|
|
|
|
static class UIMethodSecurity extends GlobalMethodSecurityConfiguration {
|
|
|
|
|
|
|
|
|
|
@Bean(name = VaadinSharedSecurityConfiguration.ACCESS_DECISION_MANAGER_BEAN)
|
|
|
|
|
@Override
|
|
|
|
|
protected AccessDecisionManager accessDecisionManager() {
|
|
|
|
|
return super.accessDecisionManager();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private static AuthenticationManager setAuthenticationManager(final HttpSecurity http, final DdiSecurityProperties ddiSecurityConfiguration) throws Exception {
|
|
|
|
|
// configure authentication manager
|
|
|
|
|
final AuthenticationManager authenticationManager =
|
|
|
|
|
http
|
|
|
|
|
.getSharedObject(AuthenticationManagerBuilder.class)
|
|
|
|
|
.authenticationProvider(
|
|
|
|
|
new PreAuthTokenSourceTrustAuthenticationProvider(ddiSecurityConfiguration.getRp().getTrustedIPs()))
|
|
|
|
|
.build();
|
|
|
|
|
http.authenticationManager(authenticationManager);
|
|
|
|
|
return authenticationManager;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* After a successful login on the UI we need to ensure to create the tenant
|
|
|
|
|
* meta data within SP.
|
|
|
|
|
*/
|
|
|
|
|
class TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler extends VaadinUrlAuthenticationSuccessHandler {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private SystemManagement systemManagement;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private SystemSecurityContext systemSecurityContext;
|
|
|
|
|
|
|
|
|
|
public TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler(final HttpService http,
|
|
|
|
|
final VaadinRedirectStrategy redirectStrategy, final String defaultTargetUrl) {
|
|
|
|
|
super(http, redirectStrategy, defaultTargetUrl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onAuthenticationSuccess(final Authentication authentication) throws Exception {
|
|
|
|
|
systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, getTenantFrom(authentication));
|
|
|
|
|
|
|
|
|
|
super.onAuthenticationSuccess(authentication);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String getTenantFrom(final Authentication authentication) {
|
|
|
|
|
final Object details = authentication.getDetails();
|
|
|
|
|
if (details instanceof TenantAwareAuthenticationDetails) {
|
|
|
|
|
return ((TenantAwareAuthenticationDetails) details).getTenant();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new InsufficientAuthenticationException("Authentication details/tenant info are not specified!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Servletfilter to create metadata after successful authentication over
|
|
|
|
|
* RESTful.
|
|
|
|
|
*/
|
|
|
|
|
class AuthenticationSuccessTenantMetadataCreationFilter implements Filter {
|
|
|
|
|
|
|
|
|
|
private final SystemManagement systemManagement;
|
|
|
|
|
private final SystemSecurityContext systemSecurityContext;
|
|
|
|
|
|
|
|
|
|
AuthenticationSuccessTenantMetadataCreationFilter(final SystemManagement systemManagement,
|
|
|
|
|
final SystemSecurityContext systemSecurityContext) {
|
|
|
|
|
this.systemManagement = systemManagement;
|
|
|
|
|
this.systemSecurityContext = systemSecurityContext;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void init(final FilterConfig filterConfig) throws ServletException {
|
|
|
|
|
// not needed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
|
|
|
|
|
throws IOException, ServletException {
|
|
|
|
|
lazyCreateTenantMetadata();
|
|
|
|
|
chain.doFilter(request, response);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void lazyCreateTenantMetadata() {
|
|
|
|
|
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
|
|
|
if (authentication != null && authentication.isAuthenticated()) {
|
|
|
|
|
systemSecurityContext.runAsSystem(systemManagement::getTenantMetadata);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void destroy() {
|
|
|
|
|
// not needed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|