Split SecurityManagedConfiguration to mgmt and ddi starters (#2014)
* SecurityManagedConfiguration is moved to hawkbit-rest-core with commons for mgmt and ddi only * Configurations for DDI and Management API are moved to respective starters * hawkbit-http-security is removed - DosFilter (as common) is moved in hawkbit-rest-security, rest to the ddi starter as used only there * some classes are moved into different packages - it is a bad practice to have same packet into multiple artifacts _release_notes_ Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -33,7 +33,7 @@ dependencies on hawkBit users. We in fact are looking into reducing them in futu
|
|||||||
So we kindly ask contributors:
|
So we kindly ask contributors:
|
||||||
|
|
||||||
* not introduce extra utility library dependencies
|
* not introduce extra utility library dependencies
|
||||||
* keep them out of the core modules (e.g. hawkbit-core, hawkbit-rest-core, hawkbit-http-security) to avoid that all
|
* keep them out of the core modules (e.g. hawkbit-core, hawkbit-rest-core) to avoid that all
|
||||||
modules have them as transitive dependency
|
modules have them as transitive dependency
|
||||||
* use utility functions in general based in the following priority:
|
* use utility functions in general based in the following priority:
|
||||||
* use utility functions from JDK if feasible
|
* use utility functions from JDK if feasible
|
||||||
|
|||||||
@@ -33,12 +33,6 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-http-security</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-security-core</artifactId>
|
<artifactId>hawkbit-security-core</artifactId>
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.autoconfigure.security;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to enable the managed security configuration.
|
|
||||||
*/
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Import(SecurityManagedConfiguration.class)
|
|
||||||
public @interface EnableHawkbitManagedSecurityConfiguration {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,526 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.autoconfigure.security;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
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.mgmt.rest.api.MgmtRestConstants;
|
|
||||||
import org.eclipse.hawkbit.mgmt.rest.resource.MgmtApiConfiguration;
|
|
||||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
|
||||||
import org.eclipse.hawkbit.repository.SystemManagement;
|
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
|
||||||
import org.eclipse.hawkbit.security.ControllerTenantAwareAuthenticationDetailsSource;
|
|
||||||
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
|
||||||
import org.eclipse.hawkbit.security.DosFilter;
|
|
||||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
|
||||||
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateAnonymousDownloadFilter;
|
|
||||||
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateSecurityTokenFilter;
|
|
||||||
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
|
||||||
import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
|
|
||||||
import org.eclipse.hawkbit.security.MdcHandler;
|
|
||||||
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
|
||||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
|
||||||
import org.springframework.context.annotation.AdviceMode;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
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.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.config.Customizer;
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
|
||||||
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.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
|
||||||
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.SessionManagementFilter;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All configurations related to HawkBit's authentication and authorization layer.
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.ASPECTJ, proxyTargetClass = true, securedEnabled = true)
|
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
|
||||||
@PropertySource("classpath:hawkbit-security-defaults.properties")
|
|
||||||
public class SecurityManagedConfiguration {
|
|
||||||
|
|
||||||
public static final String ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES = """
|
|
||||||
******************
|
|
||||||
** Anonymous controller security enabled, should only be used for development purposes **
|
|
||||||
******************""";
|
|
||||||
private static final int DOS_FILTER_ORDER = -200;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter to protect the hawkBit server system management interface against too 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> dosSystemFilter(final HawkbitSecurityProperties securityProperties) {
|
|
||||||
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(Collections.emptyList(),
|
|
||||||
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
|
||||||
filterRegBean.setUrlPatterns(List.of("/system/*"));
|
|
||||||
filterRegBean.setOrder(DOS_FILTER_ORDER);
|
|
||||||
filterRegBean.setName("dosSystemFilter");
|
|
||||||
|
|
||||||
return filterRegBean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HttpFirewall which enables to define a list of allowed host names.
|
|
||||||
*
|
|
||||||
* @return the http firewall.
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public HttpFirewall httpFirewall(final HawkbitSecurityProperties hawkbitSecurityProperties) {
|
|
||||||
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 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(),
|
|
||||||
filterProperties.getMaxWrite(), filterProperties.getWhitelist(), clientProperties.getBlacklist(),
|
|
||||||
clientProperties.getRemoteIpHeader()));
|
|
||||||
|
|
||||||
return filterRegBean;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Security configuration for the hawkBit server DDI interface.
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@ConditionalOnClass(DdiApiConfiguration.class)
|
|
||||||
static class ControllerSecurityConfigurationAdapter {
|
|
||||||
|
|
||||||
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" };
|
|
||||||
|
|
||||||
private final ControllerManagement controllerManagement;
|
|
||||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
|
||||||
private final TenantAware tenantAware;
|
|
||||||
private final DdiSecurityProperties ddiSecurityConfiguration;
|
|
||||||
private final HawkbitSecurityProperties securityProperties;
|
|
||||||
private final SystemSecurityContext systemSecurityContext;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
ControllerSecurityConfigurationAdapter(final ControllerManagement controllerManagement,
|
|
||||||
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
|
|
||||||
final DdiSecurityProperties ddiSecurityConfiguration,
|
|
||||||
final HawkbitSecurityProperties securityProperties, final SystemSecurityContext systemSecurityContext) {
|
|
||||||
this.controllerManagement = controllerManagement;
|
|
||||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
|
||||||
this.tenantAware = tenantAware;
|
|
||||||
this.ddiSecurityConfiguration = ddiSecurityConfiguration;
|
|
||||||
this.securityProperties = securityProperties;
|
|
||||||
this.systemSecurityContext = systemSecurityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter to protect the hawkBit server DDI interface against too 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)
|
|
||||||
protected 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Order(300)
|
|
||||||
protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception {
|
|
||||||
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
|
|
||||||
|
|
||||||
http
|
|
||||||
.securityMatcher(DDI_ANT_MATCHERS)
|
|
||||||
.csrf(AbstractHttpConfigurer::disable);
|
|
||||||
|
|
||||||
if (securityProperties.isRequireSsl()) {
|
|
||||||
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
|
||||||
}
|
|
||||||
|
|
||||||
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
|
||||||
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
|
|
||||||
log.warn(ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES);
|
|
||||||
|
|
||||||
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
|
|
||||||
"controllerAnonymousFilter", "anonymous",
|
|
||||||
List.of(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
|
||||||
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
||||||
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);
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
MdcHandler.Filter.addMdcFilter(http);
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Security configuration for the hawkBit server DDI download interface.
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@ConditionalOnClass(DdiApiConfiguration.class)
|
|
||||||
static class ControllerDownloadSecurityConfigurationAdapter {
|
|
||||||
|
|
||||||
private static final String DDI_DL_ANT_MATCHER = DdiRestConstants.BASE_V1_REQUEST_MAPPING
|
|
||||||
+ "/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/*";
|
|
||||||
|
|
||||||
private final ControllerManagement controllerManagement;
|
|
||||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
|
||||||
private final TenantAware tenantAware;
|
|
||||||
private final DdiSecurityProperties ddiSecurityConfiguration;
|
|
||||||
private final HawkbitSecurityProperties securityProperties;
|
|
||||||
private final SystemSecurityContext systemSecurityContext;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
ControllerDownloadSecurityConfigurationAdapter(final ControllerManagement controllerManagement,
|
|
||||||
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
|
|
||||||
final DdiSecurityProperties ddiSecurityConfiguration,
|
|
||||||
final HawkbitSecurityProperties securityProperties, final SystemSecurityContext systemSecurityContext) {
|
|
||||||
this.controllerManagement = controllerManagement;
|
|
||||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
|
||||||
this.tenantAware = tenantAware;
|
|
||||||
this.ddiSecurityConfiguration = ddiSecurityConfiguration;
|
|
||||||
this.securityProperties = securityProperties;
|
|
||||||
this.systemSecurityContext = systemSecurityContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter to protect the hawkBit server DDI download interface against too 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> 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");
|
|
||||||
|
|
||||||
return filterRegBean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Order(301)
|
|
||||||
protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception {
|
|
||||||
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
|
|
||||||
|
|
||||||
http
|
|
||||||
.securityMatcher(DDI_DL_ANT_MATCHER)
|
|
||||||
.csrf(AbstractHttpConfigurer::disable);
|
|
||||||
|
|
||||||
if (securityProperties.isRequireSsl()) {
|
|
||||||
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
|
||||||
}
|
|
||||||
|
|
||||||
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
|
||||||
|
|
||||||
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
|
|
||||||
log.warn(ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES);
|
|
||||||
|
|
||||||
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
|
|
||||||
"controllerAnonymousFilter", "anonymous",
|
|
||||||
List.of(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
|
||||||
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
|
||||||
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);
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
MdcHandler.Filter.addMdcFilter(http);
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Security configuration for the REST management API.
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@ConditionalOnClass(MgmtApiConfiguration.class)
|
|
||||||
public static class RestSecurityConfigurationAdapter {
|
|
||||||
|
|
||||||
private final HawkbitSecurityProperties securityProperties;
|
|
||||||
|
|
||||||
public RestSecurityConfigurationAdapter(final HawkbitSecurityProperties securityProperties) {
|
|
||||||
this.securityProperties = securityProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter to protect the hawkBit server Management interface against to
|
|
||||||
* many requests.
|
|
||||||
*
|
|
||||||
* @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> dosFilterREST() {
|
|
||||||
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(null,
|
|
||||||
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
|
||||||
filterRegBean.setUrlPatterns(List.of(
|
|
||||||
MgmtRestConstants.BASE_REST_MAPPING + "/*",
|
|
||||||
MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/*"));
|
|
||||||
filterRegBean.setOrder(DOS_FILTER_ORDER);
|
|
||||||
filterRegBean.setName("dosMgmtFilter");
|
|
||||||
|
|
||||||
return filterRegBean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Order(350)
|
|
||||||
SecurityFilterChain filterChainREST(
|
|
||||||
final HttpSecurity http,
|
|
||||||
@Autowired(required = false)
|
|
||||||
@Qualifier("hawkbitOAuth2ResourceServerCustomizer") final Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>> oauth2ResourceServerCustomizer,
|
|
||||||
// called just before build of the SecurityFilterChain.
|
|
||||||
// could be used for instance to set authentication provider
|
|
||||||
// Note: implementation of the customizer shall always take in account what is the already set by the
|
|
||||||
// hawkBit
|
|
||||||
@Autowired(required = false)
|
|
||||||
@Qualifier("hawkbitHttpSecurityCustomizer") final Customizer<HttpSecurity> httpSecurityCustomizer,
|
|
||||||
final SystemManagement systemManagement,
|
|
||||||
final SystemSecurityContext systemSecurityContext) throws Exception {
|
|
||||||
http
|
|
||||||
.securityMatcher(MgmtRestConstants.BASE_REST_MAPPING + "/**", MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")
|
|
||||||
.authorizeHttpRequests(amrmRegistry ->
|
|
||||||
amrmRegistry
|
|
||||||
.requestMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")
|
|
||||||
.hasAnyAuthority(SpPermission.SYSTEM_ADMIN)
|
|
||||||
.anyRequest()
|
|
||||||
.authenticated())
|
|
||||||
.anonymous(AbstractHttpConfigurer::disable)
|
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
|
||||||
.requestCache(AbstractHttpConfigurer::disable)
|
|
||||||
.exceptionHandling(Customizer.withDefaults())
|
|
||||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
||||||
.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);
|
|
||||||
|
|
||||||
if (securityProperties.getCors().isEnabled()) {
|
|
||||||
http.cors(configurer -> configurer.configurationSource(corsConfigurationSource()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (securityProperties.isRequireSsl()) {
|
|
||||||
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oauth2ResourceServerCustomizer != null) {
|
|
||||||
http.oauth2ResourceServer(oauth2ResourceServerCustomizer);
|
|
||||||
}
|
|
||||||
if (oauth2ResourceServerCustomizer == null || securityProperties.isAllowHttpBasicOnOAuthEnabled()) {
|
|
||||||
http.httpBasic(configurer -> {
|
|
||||||
final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
|
|
||||||
basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm());
|
|
||||||
configurer.authenticationEntryPoint(basicAuthEntryPoint);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (httpSecurityCustomizer != null) {
|
|
||||||
httpSecurityCustomizer.customize(http);
|
|
||||||
}
|
|
||||||
|
|
||||||
MdcHandler.Filter.addMdcFilter(http);
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CorsConfigurationSource corsConfigurationSource() {
|
|
||||||
final CorsConfiguration corsConfiguration = new CorsConfiguration();
|
|
||||||
|
|
||||||
corsConfiguration.setAllowedOrigins(securityProperties.getCors().getAllowedOrigins());
|
|
||||||
corsConfiguration.setAllowCredentials(true);
|
|
||||||
corsConfiguration.setAllowedHeaders(securityProperties.getCors().getAllowedHeaders());
|
|
||||||
corsConfiguration.setAllowedMethods(securityProperties.getCors().getAllowedMethods());
|
|
||||||
corsConfiguration.setExposedHeaders(securityProperties.getCors().getExposedHeaders());
|
|
||||||
return request -> corsConfiguration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -80,12 +80,6 @@
|
|||||||
<artifactId>spring-security-config</artifactId>
|
<artifactId>spring-security-config</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-http-security</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-json</artifactId>
|
<artifactId>spring-boot-starter-json</artifactId>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import io.qameta.allure.Story;
|
|||||||
import org.eclipse.hawkbit.repository.model.Action;
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.security.DosFilter;
|
import org.eclipse.hawkbit.rest.security.DosFilter;
|
||||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.app.ddi;
|
package org.eclipse.hawkbit.app.ddi;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -24,7 +23,6 @@ import org.springframework.web.servlet.view.RedirectView;
|
|||||||
* The minimal configuration for the stand alone hawkBit DDI server.
|
* The minimal configuration for the stand alone hawkBit DDI server.
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
||||||
@EnableHawkbitManagedSecurityConfiguration
|
|
||||||
public class DDIStart {
|
public class DDIStart {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -56,11 +56,6 @@
|
|||||||
<artifactId>hawkbit-security-integration</artifactId>
|
<artifactId>hawkbit-security-integration</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-http-security</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-repository-jpa</artifactId>
|
<artifactId>hawkbit-repository-jpa</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.autoconfigure.ddi;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.ControllerTenantAwareAuthenticationDetailsSource;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticateAnonymousDownloadFilter;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticateSecurityTokenFilter;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
|
||||||
|
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
|
||||||
|
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||||
|
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.rest.SecurityManagedConfiguration;
|
||||||
|
import org.eclipse.hawkbit.rest.security.DosFilter;
|
||||||
|
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||||
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
|
import org.eclipse.hawkbit.security.MdcHandler;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security configuration for the hawkBit server DDI download interface.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
class ControllerDownloadSecurityConfiguration {
|
||||||
|
|
||||||
|
private static final String DDI_DL_ANT_MATCHER = DdiRestConstants.BASE_V1_REQUEST_MAPPING +
|
||||||
|
"/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/*";
|
||||||
|
|
||||||
|
private final ControllerManagement controllerManagement;
|
||||||
|
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||||
|
private final TenantAware tenantAware;
|
||||||
|
private final DdiSecurityProperties ddiSecurityConfiguration;
|
||||||
|
private final HawkbitSecurityProperties securityProperties;
|
||||||
|
private final SystemSecurityContext systemSecurityContext;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ControllerDownloadSecurityConfiguration(final ControllerManagement controllerManagement,
|
||||||
|
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
|
||||||
|
final DdiSecurityProperties ddiSecurityConfiguration,
|
||||||
|
final HawkbitSecurityProperties securityProperties, final SystemSecurityContext systemSecurityContext) {
|
||||||
|
this.controllerManagement = controllerManagement;
|
||||||
|
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||||
|
this.tenantAware = tenantAware;
|
||||||
|
this.ddiSecurityConfiguration = ddiSecurityConfiguration;
|
||||||
|
this.securityProperties = securityProperties;
|
||||||
|
this.systemSecurityContext = systemSecurityContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter to protect the hawkBit server DDI download interface against too 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> dosFilterDDIDL(final HawkbitSecurityProperties securityProperties) {
|
||||||
|
final FilterRegistrationBean<DosFilter> filterRegBean = SecurityManagedConfiguration.dosFilter(List.of(DDI_DL_ANT_MATCHER),
|
||||||
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
||||||
|
filterRegBean.setOrder(SecurityManagedConfiguration.DOS_FILTER_ORDER);
|
||||||
|
filterRegBean.setName("dosDDiDlFilter");
|
||||||
|
|
||||||
|
return filterRegBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(301)
|
||||||
|
protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception {
|
||||||
|
final AuthenticationManager authenticationManager = ControllerSecurityConfiguration.setAuthenticationManager(
|
||||||
|
http, ddiSecurityConfiguration);
|
||||||
|
|
||||||
|
http
|
||||||
|
.securityMatcher(DDI_DL_ANT_MATCHER)
|
||||||
|
.csrf(AbstractHttpConfigurer::disable);
|
||||||
|
|
||||||
|
if (securityProperties.isRequireSsl()) {
|
||||||
|
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
||||||
|
|
||||||
|
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
|
||||||
|
log.warn(
|
||||||
|
SecurityManagedConfiguration.ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES);
|
||||||
|
|
||||||
|
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
|
||||||
|
"controllerAnonymousFilter", "anonymous",
|
||||||
|
List.of(new SimpleGrantedAuthority(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
||||||
|
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
MdcHandler.Filter.addMdcFilter(http);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.autoconfigure.ddi;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.ControllerTenantAwareAuthenticationDetailsSource;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticateSecurityTokenFilter;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
||||||
|
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
|
||||||
|
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
|
||||||
|
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||||
|
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.rest.SecurityManagedConfiguration;
|
||||||
|
import org.eclipse.hawkbit.rest.security.DosFilter;
|
||||||
|
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||||
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
|
import org.eclipse.hawkbit.security.MdcHandler;
|
||||||
|
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security configuration for the hawkBit server DDI interface.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
class ControllerSecurityConfiguration {
|
||||||
|
|
||||||
|
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" };
|
||||||
|
|
||||||
|
private final ControllerManagement controllerManagement;
|
||||||
|
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||||
|
private final TenantAware tenantAware;
|
||||||
|
private final DdiSecurityProperties ddiSecurityConfiguration;
|
||||||
|
private final HawkbitSecurityProperties securityProperties;
|
||||||
|
private final SystemSecurityContext systemSecurityContext;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ControllerSecurityConfiguration(final ControllerManagement controllerManagement,
|
||||||
|
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
|
||||||
|
final DdiSecurityProperties ddiSecurityConfiguration,
|
||||||
|
final HawkbitSecurityProperties securityProperties, final SystemSecurityContext systemSecurityContext) {
|
||||||
|
this.controllerManagement = controllerManagement;
|
||||||
|
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||||
|
this.tenantAware = tenantAware;
|
||||||
|
this.ddiSecurityConfiguration = ddiSecurityConfiguration;
|
||||||
|
this.securityProperties = securityProperties;
|
||||||
|
this.systemSecurityContext = systemSecurityContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter to protect the hawkBit server DDI interface against too 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)
|
||||||
|
protected FilterRegistrationBean<DosFilter> dosFilterDDI(final HawkbitSecurityProperties securityProperties) {
|
||||||
|
final FilterRegistrationBean<DosFilter> filterRegBean =
|
||||||
|
SecurityManagedConfiguration.dosFilter(List.of(DDI_ANT_MATCHERS),
|
||||||
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
||||||
|
filterRegBean.setOrder(SecurityManagedConfiguration.DOS_FILTER_ORDER);
|
||||||
|
filterRegBean.setName("dosDDiFilter");
|
||||||
|
|
||||||
|
return filterRegBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(300)
|
||||||
|
protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception {
|
||||||
|
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
|
||||||
|
|
||||||
|
http
|
||||||
|
.securityMatcher(DDI_ANT_MATCHERS)
|
||||||
|
.csrf(AbstractHttpConfigurer::disable);
|
||||||
|
|
||||||
|
if (securityProperties.isRequireSsl()) {
|
||||||
|
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
||||||
|
if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) {
|
||||||
|
log.warn(SecurityManagedConfiguration.ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES);
|
||||||
|
|
||||||
|
final AnonymousAuthenticationFilter anonymousFilter = new AnonymousAuthenticationFilter(
|
||||||
|
"controllerAnonymousFilter", "anonymous",
|
||||||
|
List.of(new SimpleGrantedAuthority(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS)));
|
||||||
|
anonymousFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
MdcHandler.Filter.addMdcFilter(http);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,6 @@ import org.springframework.context.annotation.Import;
|
|||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnClass(DdiApiConfiguration.class)
|
@ConditionalOnClass(DdiApiConfiguration.class)
|
||||||
@Import(DdiApiConfiguration.class)
|
@Import(DdiApiConfiguration.class)
|
||||||
public class DDiApiAutoConfiguration {
|
public class DdiApiAutoConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.autoconfigure.ddi.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -23,6 +23,9 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
|
||||||
|
import org.eclipse.hawkbit.security.PreAuthenticationFilter;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
import org.eclipse.hawkbit.util.UrlUtils;
|
import org.eclipse.hawkbit.util.UrlUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -7,13 +7,14 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.autoconfigure.ddi.security;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.hawkbit.security.TenantAwareWebAuthenticationDetails;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
||||||
import org.eclipse.hawkbit.util.UrlUtils;
|
import org.eclipse.hawkbit.util.UrlUtils;
|
||||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||||
@@ -7,11 +7,14 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.autoconfigure.ddi.security;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedAnonymousDownload;
|
||||||
|
import org.eclipse.hawkbit.security.PreAuthenticationFilter;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
@@ -39,8 +42,7 @@ public class HttpControllerPreAuthenticateAnonymousDownloadFilter extends Abstra
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PreAuthenticationFilter createControllerAuthenticationFilter() {
|
protected PreAuthenticationFilter createControllerAuthenticationFilter() {
|
||||||
return new ControllerPreAuthenticatedAnonymousDownload(tenantConfigurationManagement, tenantAware,
|
return new ControllerPreAuthenticatedAnonymousDownload(tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||||
systemSecurityContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -7,11 +7,14 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.autoconfigure.ddi.security;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.security.ControllerPreAuthenticateSecurityTokenFilter;
|
||||||
|
import org.eclipse.hawkbit.security.PreAuthenticationFilter;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
@@ -7,10 +7,13 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.autoconfigure.ddi.security;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
||||||
|
import org.eclipse.hawkbit.security.PreAuthenticationFilter;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
@@ -7,10 +7,13 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.autoconfigure.ddi.security;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
|
import org.eclipse.hawkbit.security.ControllerPreAuthenticatedSecurityHeaderFilter;
|
||||||
|
import org.eclipse.hawkbit.security.PreAuthenticationFilter;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
@@ -25,7 +28,7 @@ public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends Abstract
|
|||||||
private final String caAuthorityNameHeader;
|
private final String caAuthorityNameHeader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link ControllerPreAuthenticatedSecurityHeaderFilter}, in
|
* Creates a new {@link org.eclipse.hawkbit.security.ControllerPreAuthenticatedSecurityHeaderFilter}, in
|
||||||
* case the HTTP request matches the given pattern the principal is parsed
|
* case the HTTP request matches the given pattern the principal is parsed
|
||||||
* from the HTTP request with the given URI pattern, in case the URI pattern
|
* from the HTTP request with the given URI pattern, in case the URI pattern
|
||||||
* does not match the current request then only the existence of the
|
* does not match the current request then only the existence of the
|
||||||
@@ -51,7 +54,8 @@ public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends Abstract
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PreAuthenticationFilter createControllerAuthenticationFilter() {
|
protected PreAuthenticationFilter createControllerAuthenticationFilter() {
|
||||||
return new ControllerPreAuthenticatedSecurityHeaderFilter(caCommonNameHeader, caAuthorityNameHeader,
|
return new ControllerPreAuthenticatedSecurityHeaderFilter(
|
||||||
|
caCommonNameHeader, caAuthorityNameHeader,
|
||||||
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1 +1,4 @@
|
|||||||
org.eclipse.hawkbit.autoconfigure.ddi.DDiApiAutoConfiguration
|
org.eclipse.hawkbit.autoconfigure.ddi.DdiApiAutoConfiguration
|
||||||
|
org.eclipse.hawkbit.rest.SecurityManagedConfiguration
|
||||||
|
org.eclipse.hawkbit.autoconfigure.ddi.ControllerSecurityConfiguration
|
||||||
|
org.eclipse.hawkbit.autoconfigure.ddi.ControllerDownloadSecurityConfiguration
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.autoconfigure.ddi.security;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
@@ -18,6 +18,8 @@ import java.util.Collections;
|
|||||||
import io.qameta.allure.Description;
|
import io.qameta.allure.Description;
|
||||||
import io.qameta.allure.Feature;
|
import io.qameta.allure.Feature;
|
||||||
import io.qameta.allure.Story;
|
import io.qameta.allure.Story;
|
||||||
|
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
||||||
|
import org.eclipse.hawkbit.security.TenantAwareWebAuthenticationDetails;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
@@ -34,9 +36,10 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest {
|
|||||||
|
|
||||||
private static final String REQUEST_SOURCE_IP = "127.0.0.1";
|
private static final String REQUEST_SOURCE_IP = "127.0.0.1";
|
||||||
|
|
||||||
private final PreAuthTokenSourceTrustAuthenticationProvider underTestWithoutSourceIpCheck = new PreAuthTokenSourceTrustAuthenticationProvider();
|
private final PreAuthTokenSourceTrustAuthenticationProvider underTestWithoutSourceIpCheck =
|
||||||
private final PreAuthTokenSourceTrustAuthenticationProvider underTestWithSourceIpCheck = new PreAuthTokenSourceTrustAuthenticationProvider(
|
new PreAuthTokenSourceTrustAuthenticationProvider();
|
||||||
REQUEST_SOURCE_IP);
|
private final PreAuthTokenSourceTrustAuthenticationProvider underTestWithSourceIpCheck =
|
||||||
|
new PreAuthTokenSourceTrustAuthenticationProvider(REQUEST_SOURCE_IP);
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private TenantAwareWebAuthenticationDetails webAuthenticationDetailsMock;
|
private TenantAwareWebAuthenticationDetails webAuthenticationDetailsMock;
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.app.dmf;
|
package org.eclipse.hawkbit.app.dmf;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@@ -18,7 +17,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
* The minimal configuration for the stand alone hawkBit DMF server.
|
* The minimal configuration for the stand alone hawkBit DMF server.
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
||||||
@EnableHawkbitManagedSecurityConfiguration
|
|
||||||
public class DMFStart {
|
public class DMFStart {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
<!--
|
|
||||||
|
|
||||||
Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
|
||||||
|
|
||||||
This program and the accompanying materials are made
|
|
||||||
available under the terms of the Eclipse Public License 2.0
|
|
||||||
which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
|
|
||||||
SPDX-License-Identifier: EPL-2.0
|
|
||||||
|
|
||||||
-->
|
|
||||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<artifactId>hawkbit-parent</artifactId>
|
|
||||||
<version>${revision}</version>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
</parent>
|
|
||||||
<artifactId>hawkbit-http-security</artifactId>
|
|
||||||
<name>hawkBit :: HTTP Security</name>
|
|
||||||
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-repository-api</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-security-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-security-integration</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jakarta.servlet</groupId>
|
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
|
||||||
<artifactId>caffeine</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -83,12 +83,6 @@
|
|||||||
<artifactId>spring-security-config</artifactId>
|
<artifactId>spring-security-config</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-http-security</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-json</artifactId>
|
<artifactId>spring-boot-starter-json</artifactId>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.app.mgmt;
|
package org.eclipse.hawkbit.app.mgmt;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -24,7 +23,6 @@ import org.springframework.web.servlet.view.RedirectView;
|
|||||||
* The minimal configuration for the stand alone hawkBit server.
|
* The minimal configuration for the stand alone hawkBit server.
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
||||||
@EnableHawkbitManagedSecurityConfiguration
|
|
||||||
public class MgmtServerStart {
|
public class MgmtServerStart {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -33,11 +33,6 @@
|
|||||||
<artifactId>hawkbit-security-integration</artifactId>
|
<artifactId>hawkbit-security-integration</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-http-security</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-repository-jpa</artifactId>
|
<artifactId>hawkbit-repository-jpa</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.autoconfigure.mgmt;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.rest.SecurityManagedConfiguration;
|
||||||
|
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||||
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||||
|
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||||
|
import org.eclipse.hawkbit.rest.security.DosFilter;
|
||||||
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
|
import org.eclipse.hawkbit.security.MdcHandler;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.session.SessionManagementFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security configuration for the REST management API.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class MgmtSecurityConfiguration {
|
||||||
|
|
||||||
|
private final HawkbitSecurityProperties securityProperties;
|
||||||
|
|
||||||
|
public MgmtSecurityConfiguration(final HawkbitSecurityProperties securityProperties) {
|
||||||
|
this.securityProperties = securityProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter to protect the hawkBit server Management interface against to many requests.
|
||||||
|
*
|
||||||
|
* @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> dosFilterREST() {
|
||||||
|
final FilterRegistrationBean<DosFilter> filterRegBean = SecurityManagedConfiguration.dosFilter(null,
|
||||||
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
||||||
|
filterRegBean.setUrlPatterns(List.of(
|
||||||
|
MgmtRestConstants.BASE_REST_MAPPING + "/*",
|
||||||
|
MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/*"));
|
||||||
|
filterRegBean.setOrder(SecurityManagedConfiguration.DOS_FILTER_ORDER);
|
||||||
|
filterRegBean.setName("dosMgmtFilter");
|
||||||
|
|
||||||
|
return filterRegBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(350)
|
||||||
|
SecurityFilterChain filterChainREST(
|
||||||
|
final HttpSecurity http,
|
||||||
|
@Autowired(required = false)
|
||||||
|
@Qualifier("hawkbitOAuth2ResourceServerCustomizer") final Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>> oauth2ResourceServerCustomizer,
|
||||||
|
// called just before build of the SecurityFilterChain.
|
||||||
|
// could be used for instance to set authentication provider
|
||||||
|
// Note: implementation of the customizer shall always take in account what is the already set by the
|
||||||
|
// hawkBit
|
||||||
|
@Autowired(required = false)
|
||||||
|
@Qualifier("hawkbitHttpSecurityCustomizer") final Customizer<HttpSecurity> httpSecurityCustomizer,
|
||||||
|
final SystemManagement systemManagement,
|
||||||
|
final SystemSecurityContext systemSecurityContext) throws Exception {
|
||||||
|
http
|
||||||
|
.securityMatcher(MgmtRestConstants.BASE_REST_MAPPING + "/**", MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")
|
||||||
|
.authorizeHttpRequests(amrmRegistry ->
|
||||||
|
amrmRegistry
|
||||||
|
.requestMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")
|
||||||
|
.hasAnyAuthority(SpPermission.SYSTEM_ADMIN)
|
||||||
|
.anyRequest()
|
||||||
|
.authenticated())
|
||||||
|
.anonymous(AbstractHttpConfigurer::disable)
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.requestCache(AbstractHttpConfigurer::disable)
|
||||||
|
.exceptionHandling(Customizer.withDefaults())
|
||||||
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.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);
|
||||||
|
|
||||||
|
if (securityProperties.getCors().isEnabled()) {
|
||||||
|
http.cors(configurer -> configurer.configurationSource(corsConfigurationSource()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (securityProperties.isRequireSsl()) {
|
||||||
|
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oauth2ResourceServerCustomizer != null) {
|
||||||
|
http.oauth2ResourceServer(oauth2ResourceServerCustomizer);
|
||||||
|
}
|
||||||
|
if (oauth2ResourceServerCustomizer == null || securityProperties.isAllowHttpBasicOnOAuthEnabled()) {
|
||||||
|
http.httpBasic(configurer -> {
|
||||||
|
final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
|
||||||
|
basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm());
|
||||||
|
configurer.authenticationEntryPoint(basicAuthEntryPoint);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpSecurityCustomizer != null) {
|
||||||
|
httpSecurityCustomizer.customize(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
MdcHandler.Filter.addMdcFilter(http);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
final CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||||
|
|
||||||
|
corsConfiguration.setAllowedOrigins(securityProperties.getCors().getAllowedOrigins());
|
||||||
|
corsConfiguration.setAllowCredentials(true);
|
||||||
|
corsConfiguration.setAllowedHeaders(securityProperties.getCors().getAllowedHeaders());
|
||||||
|
corsConfiguration.setAllowedMethods(securityProperties.getCors().getAllowedMethods());
|
||||||
|
corsConfiguration.setExposedHeaders(securityProperties.getCors().getExposedHeaders());
|
||||||
|
return request -> corsConfiguration;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,3 @@
|
|||||||
org.eclipse.hawkbit.autoconfigure.mgmt.MgmtApiAutoConfiguration
|
org.eclipse.hawkbit.autoconfigure.mgmt.MgmtApiAutoConfiguration
|
||||||
|
org.eclipse.hawkbit.rest.SecurityManagedConfiguration
|
||||||
|
org.eclipse.hawkbit.autoconfigure.mgmt.MgmtSecurityConfiguration
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.app;
|
package org.eclipse.hawkbit.app;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -24,7 +23,6 @@ import org.springframework.web.servlet.view.RedirectView;
|
|||||||
* The minimal configuration for the stand alone hawkBit server.
|
* The minimal configuration for the stand alone hawkBit server.
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
@SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit")
|
||||||
@EnableHawkbitManagedSecurityConfiguration
|
|
||||||
// Exception squid:S1118 - Spring boot standard behavior
|
// Exception squid:S1118 - Spring boot standard behavior
|
||||||
@SuppressWarnings({ "squid:S1118" })
|
@SuppressWarnings({ "squid:S1118" })
|
||||||
public class Start {
|
public class Start {
|
||||||
|
|||||||
@@ -29,18 +29,15 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-artifact-api</artifactId>
|
<artifactId>hawkbit-security-integration</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>hawkbit-artifact-api</artifactId>
|
||||||
</dependency>
|
<version>${project.version}</version>
|
||||||
<dependency>
|
|
||||||
<groupId>commons-io</groupId>
|
|
||||||
<artifactId>commons-io</artifactId>
|
|
||||||
<version>${commons-io.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
@@ -61,12 +58,27 @@
|
|||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.servlet</groupId>
|
<groupId>jakarta.servlet</groupId>
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>${commons-io.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Test -->
|
<!-- Test -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.rest;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||||
|
import org.eclipse.hawkbit.rest.security.DosFilter;
|
||||||
|
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||||
|
import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.AdviceMode;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.web.firewall.FirewalledRequest;
|
||||||
|
import org.springframework.security.web.firewall.HttpFirewall;
|
||||||
|
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All configurations related to HawkBit's authentication and authorization layer.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.ASPECTJ, proxyTargetClass = true, securedEnabled = true)
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
@PropertySource("classpath:hawkbit-security-defaults.properties")
|
||||||
|
public class SecurityManagedConfiguration {
|
||||||
|
|
||||||
|
public static final String ANONYMOUS_CONTROLLER_SECURITY_ENABLED_SHOULD_ONLY_BE_USED_FOR_DEVELOPMENT_PURPOSES = """
|
||||||
|
******************
|
||||||
|
** Anonymous controller security enabled, should only be used for development purposes **
|
||||||
|
******************""";
|
||||||
|
public static final int DOS_FILTER_ORDER = -200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter to protect the hawkBit server system management interface against too 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> dosSystemFilter(final HawkbitSecurityProperties securityProperties) {
|
||||||
|
final FilterRegistrationBean<DosFilter> filterRegBean = dosFilter(Collections.emptyList(),
|
||||||
|
securityProperties.getDos().getFilter(), securityProperties.getClients());
|
||||||
|
filterRegBean.setUrlPatterns(List.of("/system/*"));
|
||||||
|
filterRegBean.setOrder(DOS_FILTER_ORDER);
|
||||||
|
filterRegBean.setName("dosSystemFilter");
|
||||||
|
|
||||||
|
return filterRegBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpFirewall which enables to define a list of allowed host names.
|
||||||
|
*
|
||||||
|
* @return the http firewall.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public HttpFirewall httpFirewall(final HawkbitSecurityProperties hawkbitSecurityProperties) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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(),
|
||||||
|
filterProperties.getMaxWrite(), filterProperties.getWhitelist(), clientProperties.getBlacklist(),
|
||||||
|
clientProperties.getRemoteIpHeader()));
|
||||||
|
|
||||||
|
return filterRegBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.rest.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -23,6 +23,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.hawkbit.security.SecurityConstants;
|
||||||
import org.eclipse.hawkbit.util.IpUtil;
|
import org.eclipse.hawkbit.util.IpUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -36,11 +36,6 @@
|
|||||||
<artifactId>hawkbit-security-integration</artifactId>
|
<artifactId>hawkbit-security-integration</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-http-security</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-repository-api</artifactId>
|
<artifactId>hawkbit-repository-api</artifactId>
|
||||||
|
|||||||
1
pom.xml
1
pom.xml
@@ -135,7 +135,6 @@
|
|||||||
<module>hawkbit-core</module>
|
<module>hawkbit-core</module>
|
||||||
<module>hawkbit-security-core</module>
|
<module>hawkbit-security-core</module>
|
||||||
<module>hawkbit-security-integration</module>
|
<module>hawkbit-security-integration</module>
|
||||||
<module>hawkbit-http-security</module>
|
|
||||||
<module>hawkbit-artifact</module>
|
<module>hawkbit-artifact</module>
|
||||||
<module>hawkbit-repository</module>
|
<module>hawkbit-repository</module>
|
||||||
<module>hawkbit-autoconfigure</module>
|
<module>hawkbit-autoconfigure</module>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ This has to be kept in mind e.g. if the scheduler executes critical code which h
|
|||||||
### Denial-of-Service (DoS) filter
|
### Denial-of-Service (DoS) filter
|
||||||
|
|
||||||
hawkBit owns the feature of guarding itself from DoS attacks,
|
hawkBit owns the feature of guarding itself from DoS attacks,
|
||||||
a [DoS filter](https://github.com/eclipse-hawkbit/hawkbit/blob/master/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/DosFilter.java).
|
a [DoS filter](https://github.com/eclipse-hawkbit/hawkbit/blob/master/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/security/DosFilter.java).
|
||||||
It reduces the maximum number of requests per seconds which can be configured for read and write requests.
|
It reduces the maximum number of requests per seconds which can be configured for read and write requests.
|
||||||
This mechanism is only working for every node separately, i.e. in a cluster environment the worst-case behaviour would
|
This mechanism is only working for every node separately, i.e. in a cluster environment the worst-case behaviour would
|
||||||
be that the maximum number of requests per seconds will be increased to its product if every request is handled by a
|
be that the maximum number of requests per seconds will be increased to its product if every request is handled by a
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.doc;
|
package org.eclipse.hawkbit.doc;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@@ -18,7 +17,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|||||||
* The minimal configuration for the stand alone hawkBit server.
|
* The minimal configuration for the stand alone hawkBit server.
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableHawkbitManagedSecurityConfiguration
|
|
||||||
// Exception squid:S1118 - Spring boot standard behavior
|
// Exception squid:S1118 - Spring boot standard behavior
|
||||||
@SuppressWarnings({ "squid:S1118" })
|
@SuppressWarnings({ "squid:S1118" })
|
||||||
public class Start {
|
public class Start {
|
||||||
|
|||||||
Reference in New Issue
Block a user