diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72b5a6380..f7942085e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ dependencies on hawkBit users. We in fact are looking into reducing them in futu So we kindly ask contributors: * 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 * use utility functions in general based in the following priority: * use utility functions from JDK if feasible diff --git a/hawkbit-autoconfigure/pom.xml b/hawkbit-autoconfigure/pom.xml index 06a52e6a4..22fdb0b66 100644 --- a/hawkbit-autoconfigure/pom.xml +++ b/hawkbit-autoconfigure/pom.xml @@ -33,12 +33,6 @@ ${project.version} true - - org.eclipse.hawkbit - hawkbit-http-security - ${project.version} - true - org.eclipse.hawkbit hawkbit-security-core diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/EnableHawkbitManagedSecurityConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/EnableHawkbitManagedSecurityConfiguration.java deleted file mode 100644 index ddbe7b8f5..000000000 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/EnableHawkbitManagedSecurityConfiguration.java +++ /dev/null @@ -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 { - -} diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java deleted file mode 100644 index c74ae3ee4..000000000 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java +++ /dev/null @@ -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 dosSystemFilter(final HawkbitSecurityProperties securityProperties) { - final FilterRegistrationBean 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 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(final Collection includeAntPaths, - final HawkbitSecurityProperties.Dos.Filter filterProperties, - final HawkbitSecurityProperties.Clients clientProperties) { - final FilterRegistrationBean 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 dosFilterDDI(final HawkbitSecurityProperties securityProperties) { - final FilterRegistrationBean 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 dosFilterDDIDL(final HawkbitSecurityProperties securityProperties) { - final FilterRegistrationBean 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 dosFilterREST() { - final FilterRegistrationBean 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> 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 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 pathsToIgnore; - - public IgnorePathsStrictHttpFirewall(final Collection 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); - } - } -} diff --git a/hawkbit-ddi/hawkbit-ddi-resource/pom.xml b/hawkbit-ddi/hawkbit-ddi-resource/pom.xml index 83298d8f3..38e65cb9c 100644 --- a/hawkbit-ddi/hawkbit-ddi-resource/pom.xml +++ b/hawkbit-ddi/hawkbit-ddi-resource/pom.xml @@ -80,12 +80,6 @@ spring-security-config test - - org.eclipse.hawkbit - hawkbit-http-security - ${project.version} - test - org.springframework.boot spring-boot-starter-json diff --git a/hawkbit-ddi/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DosFilterTest.java b/hawkbit-ddi/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DosFilterTest.java index de3d89f26..3da6f9f0a 100644 --- a/hawkbit-ddi/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DosFilterTest.java +++ b/hawkbit-ddi/hawkbit-ddi-resource/src/test/java/org/eclipse/hawkbit/ddi/rest/resource/DosFilterTest.java @@ -23,7 +23,7 @@ import io.qameta.allure.Story; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; 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.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; diff --git a/hawkbit-ddi/hawkbit-ddi-server/src/main/java/org/eclipse/hawkbit/app/ddi/DDIStart.java b/hawkbit-ddi/hawkbit-ddi-server/src/main/java/org/eclipse/hawkbit/app/ddi/DDIStart.java index 6f46b5471..029e45848 100644 --- a/hawkbit-ddi/hawkbit-ddi-server/src/main/java/org/eclipse/hawkbit/app/ddi/DDIStart.java +++ b/hawkbit-ddi/hawkbit-ddi-server/src/main/java/org/eclipse/hawkbit/app/ddi/DDIStart.java @@ -9,7 +9,6 @@ */ package org.eclipse.hawkbit.app.ddi; -import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; 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. */ @SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit") -@EnableHawkbitManagedSecurityConfiguration public class DDIStart { /** diff --git a/hawkbit-ddi/hawkbit-ddi-starter/pom.xml b/hawkbit-ddi/hawkbit-ddi-starter/pom.xml index 4b820fa92..d8f002f65 100644 --- a/hawkbit-ddi/hawkbit-ddi-starter/pom.xml +++ b/hawkbit-ddi/hawkbit-ddi-starter/pom.xml @@ -56,11 +56,6 @@ hawkbit-security-integration ${project.version} - - org.eclipse.hawkbit - hawkbit-http-security - ${project.version} - org.eclipse.hawkbit hawkbit-repository-jpa diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerDownloadSecurityConfiguration.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerDownloadSecurityConfiguration.java new file mode 100644 index 000000000..5c0be056e --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerDownloadSecurityConfiguration.java @@ -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 dosFilterDDIDL(final HawkbitSecurityProperties securityProperties) { + final FilterRegistrationBean 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(); + } +} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerSecurityConfiguration.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerSecurityConfiguration.java new file mode 100644 index 000000000..6e8ba5c45 --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/ControllerSecurityConfiguration.java @@ -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 dosFilterDDI(final HawkbitSecurityProperties securityProperties) { + final FilterRegistrationBean 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; + } +} diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/DDiApiAutoConfiguration.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/DdiApiAutoConfiguration.java similarity index 95% rename from hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/DDiApiAutoConfiguration.java rename to hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/DdiApiAutoConfiguration.java index e8780a802..5cef6428b 100644 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/DDiApiAutoConfiguration.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/DdiApiAutoConfiguration.java @@ -20,6 +20,6 @@ import org.springframework.context.annotation.Import; @Configuration @ConditionalOnClass(DdiApiConfiguration.class) @Import(DdiApiConfiguration.class) -public class DDiApiAutoConfiguration { +public class DdiApiAutoConfiguration { } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/AbstractHttpControllerAuthenticationFilter.java similarity index 97% rename from hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java rename to hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/AbstractHttpControllerAuthenticationFilter.java index 6f344fca3..52d36963e 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/AbstractHttpControllerAuthenticationFilter.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.security; +package org.eclipse.hawkbit.autoconfigure.ddi.security; import java.io.IOException; import java.util.ArrayList; @@ -23,6 +23,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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.util.UrlUtils; import org.slf4j.Logger; diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/ControllerTenantAwareAuthenticationDetailsSource.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/ControllerTenantAwareAuthenticationDetailsSource.java similarity index 95% rename from hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/ControllerTenantAwareAuthenticationDetailsSource.java rename to hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/ControllerTenantAwareAuthenticationDetailsSource.java index 4cbf4fa01..6dda977a3 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/ControllerTenantAwareAuthenticationDetailsSource.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/ControllerTenantAwareAuthenticationDetailsSource.java @@ -7,13 +7,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.security; +package org.eclipse.hawkbit.autoconfigure.ddi.security; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.security.TenantAwareWebAuthenticationDetails; import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.util.UrlUtils; import org.springframework.security.authentication.AuthenticationDetailsSource; diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateAnonymousDownloadFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateAnonymousDownloadFilter.java similarity index 84% rename from hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateAnonymousDownloadFilter.java rename to hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateAnonymousDownloadFilter.java index a61c104b5..9d207d0b8 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateAnonymousDownloadFilter.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateAnonymousDownloadFilter.java @@ -7,11 +7,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.security; +package org.eclipse.hawkbit.autoconfigure.ddi.security; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; 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.slf4j.Logger; @@ -39,8 +42,7 @@ public class HttpControllerPreAuthenticateAnonymousDownloadFilter extends Abstra @Override protected PreAuthenticationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticatedAnonymousDownload(tenantConfigurationManagement, tenantAware, - systemSecurityContext); + return new ControllerPreAuthenticatedAnonymousDownload(tenantConfigurationManagement, tenantAware, systemSecurityContext); } @Override diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateSecurityTokenFilter.java similarity index 91% rename from hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java rename to hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateSecurityTokenFilter.java index 563e148a5..16378de37 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateSecurityTokenFilter.java @@ -7,11 +7,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.security; +package org.eclipse.hawkbit.autoconfigure.ddi.security; import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.repository.ControllerManagement; 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.slf4j.Logger; diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java similarity index 88% rename from hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java rename to hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java index 23d681a58..84fb3c2a5 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java @@ -7,10 +7,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.security; +package org.eclipse.hawkbit.autoconfigure.ddi.security; import lombok.extern.slf4j.Slf4j; 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.slf4j.Logger; diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java similarity index 85% rename from hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java rename to hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java index 541dd63cf..06184a58b 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java @@ -7,10 +7,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.security; +package org.eclipse.hawkbit.autoconfigure.ddi.security; import lombok.extern.slf4j.Slf4j; 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.slf4j.Logger; @@ -25,7 +28,7 @@ public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends Abstract 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 * 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 @@ -51,7 +54,8 @@ public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends Abstract @Override protected PreAuthenticationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticatedSecurityHeaderFilter(caCommonNameHeader, caAuthorityNameHeader, + return new ControllerPreAuthenticatedSecurityHeaderFilter( + caCommonNameHeader, caAuthorityNameHeader, tenantConfigurationManagement, tenantAware, systemSecurityContext); } diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hawkbit-ddi/hawkbit-ddi-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 2213d66ee..772f92438 100644 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -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 diff --git a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java b/hawkbit-ddi/hawkbit-ddi-starter/src/test/java/org/eclipse/hawkbit/autoconfigure/ddi/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java similarity index 94% rename from hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java rename to hawkbit-ddi/hawkbit-ddi-starter/src/test/java/org/eclipse/hawkbit/autoconfigure/ddi/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java index ff2cd1cb8..45e08f714 100644 --- a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java +++ b/hawkbit-ddi/hawkbit-ddi-starter/src/test/java/org/eclipse/hawkbit/autoconfigure/ddi/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java @@ -7,7 +7,7 @@ * * 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.assertThatExceptionOfType; @@ -18,6 +18,8 @@ import java.util.Collections; import io.qameta.allure.Description; import io.qameta.allure.Feature; 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.extension.ExtendWith; import org.mockito.Mock; @@ -34,9 +36,10 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest { private static final String REQUEST_SOURCE_IP = "127.0.0.1"; - private final PreAuthTokenSourceTrustAuthenticationProvider underTestWithoutSourceIpCheck = new PreAuthTokenSourceTrustAuthenticationProvider(); - private final PreAuthTokenSourceTrustAuthenticationProvider underTestWithSourceIpCheck = new PreAuthTokenSourceTrustAuthenticationProvider( - REQUEST_SOURCE_IP); + private final PreAuthTokenSourceTrustAuthenticationProvider underTestWithoutSourceIpCheck = + new PreAuthTokenSourceTrustAuthenticationProvider(); + private final PreAuthTokenSourceTrustAuthenticationProvider underTestWithSourceIpCheck = + new PreAuthTokenSourceTrustAuthenticationProvider(REQUEST_SOURCE_IP); @Mock private TenantAwareWebAuthenticationDetails webAuthenticationDetailsMock; diff --git a/hawkbit-dmf/hawkbit-dmf-server/src/main/java/org/eclipse/hawkbit/app/dmf/DMFStart.java b/hawkbit-dmf/hawkbit-dmf-server/src/main/java/org/eclipse/hawkbit/app/dmf/DMFStart.java index d8621e659..c532beb4e 100644 --- a/hawkbit-dmf/hawkbit-dmf-server/src/main/java/org/eclipse/hawkbit/app/dmf/DMFStart.java +++ b/hawkbit-dmf/hawkbit-dmf-server/src/main/java/org/eclipse/hawkbit/app/dmf/DMFStart.java @@ -9,7 +9,6 @@ */ package org.eclipse.hawkbit.app.dmf; -import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration; import org.springframework.boot.SpringApplication; 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. */ @SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit") -@EnableHawkbitManagedSecurityConfiguration public class DMFStart { /** diff --git a/hawkbit-http-security/pom.xml b/hawkbit-http-security/pom.xml deleted file mode 100644 index 1bafe1181..000000000 --- a/hawkbit-http-security/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - 4.0.0 - - hawkbit-parent - ${revision} - org.eclipse.hawkbit - - hawkbit-http-security - hawkBit :: HTTP Security - - - - - org.eclipse.hawkbit - hawkbit-repository-api - ${project.version} - - - org.eclipse.hawkbit - hawkbit-security-core - ${project.version} - - - org.eclipse.hawkbit - hawkbit-security-integration - ${project.version} - - - jakarta.servlet - jakarta.servlet-api - provided - - - com.github.ben-manes.caffeine - caffeine - - - - \ No newline at end of file diff --git a/hawkbit-mgmt/hawkbit-mgmt-resource/pom.xml b/hawkbit-mgmt/hawkbit-mgmt-resource/pom.xml index bbf9efed5..2325466f7 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-resource/pom.xml +++ b/hawkbit-mgmt/hawkbit-mgmt-resource/pom.xml @@ -83,12 +83,6 @@ spring-security-config test - - org.eclipse.hawkbit - hawkbit-http-security - ${project.version} - test - org.springframework.boot spring-boot-starter-json diff --git a/hawkbit-mgmt/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/MgmtServerStart.java b/hawkbit-mgmt/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/MgmtServerStart.java index 3c8a11e3d..58a328efb 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/MgmtServerStart.java +++ b/hawkbit-mgmt/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/MgmtServerStart.java @@ -9,7 +9,6 @@ */ package org.eclipse.hawkbit.app.mgmt; -import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; 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. */ @SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit") -@EnableHawkbitManagedSecurityConfiguration public class MgmtServerStart { /** diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml b/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml index 642f7e001..f356b5051 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml +++ b/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml @@ -33,11 +33,6 @@ hawkbit-security-integration ${project.version} - - org.eclipse.hawkbit - hawkbit-http-security - ${project.version} - org.eclipse.hawkbit hawkbit-repository-jpa diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java new file mode 100644 index 000000000..6b4787f2c --- /dev/null +++ b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/mgmt/MgmtSecurityConfiguration.java @@ -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 dosFilterREST() { + final FilterRegistrationBean 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> 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 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; + } +} diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 702a23a39..c7d0a02bb 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/hawkbit-mgmt/hawkbit-mgmt-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,3 @@ org.eclipse.hawkbit.autoconfigure.mgmt.MgmtApiAutoConfiguration +org.eclipse.hawkbit.rest.SecurityManagedConfiguration +org.eclipse.hawkbit.autoconfigure.mgmt.MgmtSecurityConfiguration diff --git a/hawkbit-monolith/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/Start.java b/hawkbit-monolith/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/Start.java index a93b67064..12fed2d39 100644 --- a/hawkbit-monolith/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/Start.java +++ b/hawkbit-monolith/hawkbit-update-server/src/main/java/org/eclipse/hawkbit/app/Start.java @@ -9,7 +9,6 @@ */ package org.eclipse.hawkbit.app; -import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; 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. */ @SpringBootApplication(scanBasePackages = "org.eclipse.hawkbit") -@EnableHawkbitManagedSecurityConfiguration // Exception squid:S1118 - Spring boot standard behavior @SuppressWarnings({ "squid:S1118" }) public class Start { diff --git a/hawkbit-rest-core/pom.xml b/hawkbit-rest-core/pom.xml index 4500c82e3..73c8b69ca 100644 --- a/hawkbit-rest-core/pom.xml +++ b/hawkbit-rest-core/pom.xml @@ -29,18 +29,15 @@ org.eclipse.hawkbit - hawkbit-artifact-api + hawkbit-security-integration ${project.version} - org.apache.commons - commons-lang3 - - - commons-io - commons-io - ${commons-io.version} + org.eclipse.hawkbit + hawkbit-artifact-api + ${project.version} + org.springframework.boot spring-boot-starter-web @@ -61,12 +58,27 @@ org.springdoc springdoc-openapi-starter-webmvc-ui + jakarta.servlet jakarta.servlet-api provided + + org.apache.commons + commons-lang3 + + + commons-io + commons-io + ${commons-io.version} + + + com.github.ben-manes.caffeine + caffeine + + org.eclipse.hawkbit diff --git a/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/SecurityManagedConfiguration.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/SecurityManagedConfiguration.java new file mode 100644 index 000000000..312459164 --- /dev/null +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/SecurityManagedConfiguration.java @@ -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 dosSystemFilter(final HawkbitSecurityProperties securityProperties) { + final FilterRegistrationBean 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 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(final Collection includeAntPaths, + final HawkbitSecurityProperties.Dos.Filter filterProperties, + final HawkbitSecurityProperties.Clients clientProperties) { + final FilterRegistrationBean 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 pathsToIgnore; + + public IgnorePathsStrictHttpFirewall(final Collection 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); + } + } +} diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/DosFilter.java b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/security/DosFilter.java similarity index 98% rename from hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/DosFilter.java rename to hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/security/DosFilter.java index 11e8dfc8d..2bfa2dc04 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/DosFilter.java +++ b/hawkbit-rest-core/src/main/java/org/eclipse/hawkbit/rest/security/DosFilter.java @@ -7,7 +7,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.eclipse.hawkbit.security; +package org.eclipse.hawkbit.rest.security; import java.io.IOException; 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.Caffeine; import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.security.SecurityConstants; import org.eclipse.hawkbit.util.IpUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/hawkbit-test-report/pom.xml b/hawkbit-test-report/pom.xml index 4af2916cc..39b24e678 100644 --- a/hawkbit-test-report/pom.xml +++ b/hawkbit-test-report/pom.xml @@ -36,11 +36,6 @@ hawkbit-security-integration ${project.version} - - org.eclipse.hawkbit - hawkbit-http-security - ${project.version} - org.eclipse.hawkbit hawkbit-repository-api diff --git a/pom.xml b/pom.xml index 5566dc145..2a42368e5 100644 --- a/pom.xml +++ b/pom.xml @@ -135,7 +135,6 @@ hawkbit-core hawkbit-security-core hawkbit-security-integration - hawkbit-http-security hawkbit-artifact hawkbit-repository hawkbit-autoconfigure diff --git a/site/content/guides/clustering.md b/site/content/guides/clustering.md index d031d4c2a..548a9916d 100644 --- a/site/content/guides/clustering.md +++ b/site/content/guides/clustering.md @@ -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 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. 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 diff --git a/site/src/main/java/org/eclipse/hawkbit/doc/Start.java b/site/src/main/java/org/eclipse/hawkbit/doc/Start.java index b5d684a7a..7054b615d 100644 --- a/site/src/main/java/org/eclipse/hawkbit/doc/Start.java +++ b/site/src/main/java/org/eclipse/hawkbit/doc/Start.java @@ -9,7 +9,6 @@ */ package org.eclipse.hawkbit.doc; -import org.eclipse.hawkbit.autoconfigure.security.EnableHawkbitManagedSecurityConfiguration; import org.springframework.boot.SpringApplication; 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. */ @SpringBootApplication -@EnableHawkbitManagedSecurityConfiguration // Exception squid:S1118 - Spring boot standard behavior @SuppressWarnings({ "squid:S1118" }) public class Start {