From 76ce1cf052f4be7dfd30dfa4e6e506c53e09b1ea Mon Sep 17 00:00:00 2001 From: Avgustin Marinov Date: Tue, 18 Feb 2025 15:10:16 +0200 Subject: [PATCH] Cleanup and improve the controller authentication (#2287) Signed-off-by: Avgustin Marinov --- .../hawkbit-ddi-security}/pom.xml | 4 +- .../controller/AuthenticationFilters.java | 165 ++++++++++++++++ .../security/controller/Authenticator.java | 101 ++++++++++ .../controller/ControllerSecurityToken.java | 0 .../controller/GatewayTokenAuthenticator.java | 83 ++++++++ .../SecurityHeaderAuthenticator.java | 130 +++++++++++++ .../SecurityTokenAuthenticator.java | 81 ++++++++ .../GatewayTokenAuthenticatorTest.java | 116 +++++++++++ .../SecurityHeaderAuthenticatorTest.java | 110 ++++++----- .../SecurityTokenAuthenticatorTest.java | 122 ++++++++++++ hawkbit-ddi/hawkbit-ddi-starter/pom.xml | 2 +- ...ntrollerDownloadSecurityConfiguration.java | 85 +++++--- .../ddi/ControllerSecurityConfiguration.java | 65 ++----- ...actHttpControllerAuthenticationFilter.java | 182 ------------------ ...enantAwareAuthenticationDetailsSource.java | 65 ------- ...lerPreAuthenticateSecurityTokenFilter.java | 67 ------- ...thenticatedGatewaySecurityTokenFilter.java | 56 ------ ...rPreAuthenticatedSecurityHeaderFilter.java | 66 ------- ...SourceTrustAuthenticationProviderTest.java | 144 -------------- hawkbit-ddi/pom.xml | 1 + hawkbit-dmf/hawkbit-dmf-amqp/pom.xml | 2 +- hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml | 2 +- .../pom.xml | 2 +- .../im/authentication/SpPermission.java | 0 .../hawkbit/im/authentication/SpRole.java | 0 .../StaticAuthenticationProvider.java | 0 .../security/DdiSecurityProperties.java | 0 .../security/HawkbitSecurityProperties.java | 0 .../InMemoryUserAuthoritiesResolver.java | 0 .../eclipse/hawkbit/security/MdcHandler.java | 0 .../hawkbit/security/SecurityConstants.java | 0 .../security/SecurityContextSerializer.java | 0 .../security/SecurityContextTenantAware.java | 0 .../security/SecurityTokenGenerator.java | 0 .../security/SpringSecurityAuditorAware.java | 0 .../security/SystemSecurityContext.java | 0 .../java/org/eclipse/hawkbit/util/IpUtil.java | 0 .../org/eclipse/hawkbit/util/UrlUtils.java | 0 .../im/authentication/SpPermissionTest.java | 0 .../org/eclipse/hawkbit/util/IpUtilTest.java | 0 ...bstractControllerAuthenticationFilter.java | 67 ------- ...thenticatedGatewaySecurityTokenFilter.java | 95 --------- ...rPreAuthenticatedSecurityHeaderFilter.java | 157 --------------- ...erPreAuthenticatedSecurityTokenFilter.java | 99 ---------- .../controller/HeaderAuthentication.java | 73 ------- ...okenSourceTrustAuthenticationProvider.java | 173 ----------------- .../controller/PreAuthenticationFilter.java | 58 ------ .../TenantAwareWebAuthenticationDetails.java | 50 ----- hawkbit-security/pom.xml | 29 --- hawkbit-test-report/pom.xml | 2 +- pom.xml | 5 +- 51 files changed, 942 insertions(+), 1517 deletions(-) rename {hawkbit-security/hawkbit-security-controller => hawkbit-ddi/hawkbit-ddi-security}/pom.xml (93%) create mode 100644 hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/AuthenticationFilters.java create mode 100644 hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/Authenticator.java rename {hawkbit-security/hawkbit-security-controller => hawkbit-ddi/hawkbit-ddi-security}/src/main/java/org/eclipse/hawkbit/security/controller/ControllerSecurityToken.java (100%) create mode 100644 hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticator.java create mode 100644 hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticator.java create mode 100644 hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityTokenAuthenticator.java create mode 100644 hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticatorTest.java rename hawkbit-security/hawkbit-security-controller/src/test/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityHeaderFilterTest.java => hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticatorTest.java (53%) create mode 100644 hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/SecurityTokenAuthenticatorTest.java delete mode 100644 hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/AbstractHttpControllerAuthenticationFilter.java delete mode 100644 hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/ControllerTenantAwareAuthenticationDetailsSource.java delete mode 100644 hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateSecurityTokenFilter.java delete mode 100644 hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java delete mode 100644 hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java delete mode 100644 hawkbit-ddi/hawkbit-ddi-starter/src/test/java/org/eclipse/hawkbit/autoconfigure/ddi/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/pom.xml (96%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/im/authentication/SpRole.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/DdiSecurityProperties.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/SecurityConstants.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/SecurityContextSerializer.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/SecurityTokenGenerator.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/util/IpUtil.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/main/java/org/eclipse/hawkbit/util/UrlUtils.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/test/java/org/eclipse/hawkbit/im/authentication/SpPermissionTest.java (100%) rename {hawkbit-security/hawkbit-security-core => hawkbit-security-core}/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java (100%) delete mode 100644 hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/AbstractControllerAuthenticationFilter.java delete mode 100644 hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java delete mode 100644 hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityHeaderFilter.java delete mode 100644 hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityTokenFilter.java delete mode 100644 hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/HeaderAuthentication.java delete mode 100644 hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/PreAuthTokenSourceTrustAuthenticationProvider.java delete mode 100644 hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/PreAuthenticationFilter.java delete mode 100644 hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/TenantAwareWebAuthenticationDetails.java delete mode 100644 hawkbit-security/pom.xml diff --git a/hawkbit-security/hawkbit-security-controller/pom.xml b/hawkbit-ddi/hawkbit-ddi-security/pom.xml similarity index 93% rename from hawkbit-security/hawkbit-security-controller/pom.xml rename to hawkbit-ddi/hawkbit-ddi-security/pom.xml index 80ccaa135..479bef45f 100644 --- a/hawkbit-security/hawkbit-security-controller/pom.xml +++ b/hawkbit-ddi/hawkbit-ddi-security/pom.xml @@ -14,11 +14,11 @@ 4.0.0 org.eclipse.hawkbit - hawkbit-security-parent + hawkbit-ddi-parent ${revision} - hawkbit-security-controller + hawkbit-ddi-security hawkBit :: Security :: Controller diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/AuthenticationFilters.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/AuthenticationFilters.java new file mode 100644 index 000000000..aee38b129 --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/AuthenticationFilters.java @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2025 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.security.controller; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.eclipse.hawkbit.security.DdiSecurityProperties; +import org.eclipse.hawkbit.util.UrlUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * An abstraction for all controller based security to parse the e.g. the tenant name from the URL and the controller ID from the URL to do + * security checks based on this information. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class AuthenticationFilters { + + public static class GatewayTokenAuthenticationFilter extends AbstractAuthenticationFilter { + + public GatewayTokenAuthenticationFilter(final GatewayTokenAuthenticator authenticator, final DdiSecurityProperties ddiSecurityProperties) { + super(authenticator, ddiSecurityProperties); + } + } + + public static class SecurityHeaderAuthenticationFilter extends AbstractAuthenticationFilter { + + public SecurityHeaderAuthenticationFilter(final SecurityHeaderAuthenticator authenticator, final DdiSecurityProperties ddiSecurityProperties) { + super(authenticator, ddiSecurityProperties); + } + } + + public static class SecurityTokenAuthenticationFilter extends AbstractAuthenticationFilter { + + public SecurityTokenAuthenticationFilter(final SecurityTokenAuthenticator authenticator, final DdiSecurityProperties ddiSecurityProperties) { + super(authenticator, ddiSecurityProperties); + } + } + + /** + * An abstraction for all controller based security to parse the e.g. the tenant name from the URL and the controller ID from the URL to do + * security checks based on this information. + */ + public static abstract class AbstractAuthenticationFilter extends OncePerRequestFilter { + + private static final String TENANT_PLACE_HOLDER = "tenant"; + private static final String CONTROLLER_ID_PLACE_HOLDER = "controllerId"; + /** + * requestURIPathPattern the request URI path pattern in ANT style containing the placeholder key for retrieving the principal from the URI + * request. e.g."/{tenant}/controller/v1/{controllerId} + */ + private static final String CONTROLLER_REQUEST_ANT_PATTERN = + "/{" + TENANT_PLACE_HOLDER + "}/controller/v1/{" + CONTROLLER_ID_PLACE_HOLDER + "}/**"; + private static final String CONTROLLER_DL_REQUEST_ANT_PATTERN = + "/{" + TENANT_PLACE_HOLDER + "}/controller/artifacts/v1/**"; + + private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy(); + private final AntPathMatcher pathExtractor = new AntPathMatcher(); + private final Authenticator authenticator; + private final List authorizedSourceIps; + + protected AbstractAuthenticationFilter(final Authenticator authenticator, final DdiSecurityProperties ddiSecurityProperties) { + this.authenticator = authenticator; + authorizedSourceIps = ddiSecurityProperties.getRp().getTrustedIPs(); + } + + @Override + protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) + throws IOException, ServletException { + if (acceptIPAddress(request)) { + final Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication(); + if (currentAuthentication == null || !currentAuthentication.isAuthenticated()) { + final ControllerSecurityToken securityToken = createTenantSecurityTokenVariables(request); + if (securityToken != null) { + final Authentication authentication = authenticator.authenticate(securityToken); + if (authentication != null) { + SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); + context.setAuthentication(authentication); + this.securityContextHolderStrategy.setContext(context); + } + } + } else { + authenticator.log().trace("Request is already authenticated. Skip filter"); + } + } + + chain.doFilter(request, response); + } + + /** + * Extracts tenant and controllerId from the request URI as path variables. + * + * @param request the Http request to extract the path variables. + * @return the extracted {@link ControllerSecurityToken} or {@code null} if the request does not match the pattern and no variables could be + * extracted + */ + private ControllerSecurityToken createTenantSecurityTokenVariables(final HttpServletRequest request) { + final String requestURI = request.getRequestURI(); + if (pathExtractor.match(request.getContextPath() + CONTROLLER_REQUEST_ANT_PATTERN, requestURI)) { + authenticator.log().debug("retrieving principal from URI request {}", requestURI); + final Map extractUriTemplateVariables = pathExtractor + .extractUriTemplateVariables(request.getContextPath() + CONTROLLER_REQUEST_ANT_PATTERN, requestURI); + final String controllerId = UrlUtils.decodeUriValue(extractUriTemplateVariables.get(CONTROLLER_ID_PLACE_HOLDER)); + final String tenant = UrlUtils.decodeUriValue(extractUriTemplateVariables.get(TENANT_PLACE_HOLDER)); + authenticator.log().trace("Parsed tenant {} and controllerId {} from path request {}", tenant, controllerId, requestURI); + return createTenantSecurityTokenVariables(request, tenant, controllerId); + } else if (pathExtractor.match(request.getContextPath() + CONTROLLER_DL_REQUEST_ANT_PATTERN, requestURI)) { + authenticator.log().debug("retrieving path variables from URI request {}", requestURI); + final Map extractUriTemplateVariables = pathExtractor.extractUriTemplateVariables( + request.getContextPath() + CONTROLLER_DL_REQUEST_ANT_PATTERN, requestURI); + final String tenant = UrlUtils.decodeUriValue(extractUriTemplateVariables.get(TENANT_PLACE_HOLDER)); + authenticator.log().trace("Parsed tenant {} from path request {}", tenant, requestURI); + return createTenantSecurityTokenVariables(request, tenant, "anonymous"); + } else { + authenticator.log().trace("request {} does not match the path pattern {}, request gets ignored", requestURI, CONTROLLER_REQUEST_ANT_PATTERN); + return null; + } + } + + private ControllerSecurityToken createTenantSecurityTokenVariables( + final HttpServletRequest request, final String tenant, final String controllerId) { + final ControllerSecurityToken securityToken = new ControllerSecurityToken(tenant, null, controllerId, null); + Collections.list(request.getHeaderNames()).forEach(header -> securityToken.putHeader(header, request.getHeader(header))); + return securityToken; + } + + private boolean acceptIPAddress(final HttpServletRequest request) { + if (authorizedSourceIps == null) { + // no trusted IP check, because no authorizedSourceIPs configuration + return true; + } + + final String remoteAddress = request.getRemoteAddr(); + if (authorizedSourceIps.contains(remoteAddress)) { + // source ip matches the given pattern -> authenticated + return true; + } else { + authenticator.log().debug( + "The remote source IP address {} is not in the list of trusted IP addresses {}", remoteAddress, authorizedSourceIps); + return false; + } + } + } +} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/Authenticator.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/Authenticator.java new file mode 100644 index 000000000..87665e7e0 --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/Authenticator.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2025 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.security.controller; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import lombok.EqualsAndHashCode; +import org.eclipse.hawkbit.im.authentication.SpPermission; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails; +import org.slf4j.Logger; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +/** + * Interface for Authentication mechanism. + */ +public interface Authenticator { + + /** + * If the authentication mechanism is not enabled for the tenant - it just returns null. + * If the authentication mechanism is supported, the filter extracts from the security token the related credentials, + * validate them (do authenticate the caller). + * If validation / authentication is successful returns an authenticated authentication object. Otherwise, + * throws BadCredentialsException. + * + * @param controllerSecurityToken the securityToken + * @return the extracted tenant and controller id + */ + Authentication authenticate(ControllerSecurityToken controllerSecurityToken); + + Logger log(); + + abstract class AbstractAuthenticator implements Authenticator { + + protected final TenantConfigurationManagement tenantConfigurationManagement; + protected final TenantAware tenantAware; + protected final SystemSecurityContext systemSecurityContext; + private final TenantAware.TenantRunner isEnabledTenantRunner; + + protected AbstractAuthenticator( + final TenantConfigurationManagement tenantConfigurationManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + this.tenantConfigurationManagement = tenantConfigurationManagement; + this.tenantAware = tenantAware; + this.systemSecurityContext = systemSecurityContext; + isEnabledTenantRunner = () -> systemSecurityContext.runAsSystem( + () -> tenantConfigurationManagement.getConfigurationValue(getTenantConfigurationKey(), Boolean.class).getValue()); + } + + protected boolean isEnabled(final ControllerSecurityToken securityToken) { + return tenantAware.runAsTenant(securityToken.getTenant(), isEnabledTenantRunner); + } + + protected abstract String getTenantConfigurationKey(); + + protected Authentication authenticatedController(final String tenant, final String controllerId) { + Objects.requireNonNull(tenant, "tenant must not be null"); + Objects.requireNonNull(controllerId, "controllerId must not be null"); + return new AuthenticatedController(tenant, controllerId); + } + + @EqualsAndHashCode(callSuper = true) + private static class AuthenticatedController extends AbstractAuthenticationToken { + + private static final Collection CONTROLLER_AUTHORITY = + List.of(new SimpleGrantedAuthority(SpPermission.SpringEvalExpressions.CONTROLLER_ROLE)); + private final String controllerId; + + AuthenticatedController(final String tenant, final String controllerId) { + super(CONTROLLER_AUTHORITY); + super.setDetails(new TenantAwareAuthenticationDetails(tenant, true)); + this.controllerId = controllerId; + setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getPrincipal() { + return controllerId; + } + } + } +} \ No newline at end of file diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerSecurityToken.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/ControllerSecurityToken.java similarity index 100% rename from hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerSecurityToken.java rename to hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/ControllerSecurityToken.java diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticator.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticator.java new file mode 100644 index 000000000..f82608f67 --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticator.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2025 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.security.controller; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.slf4j.Logger; +import org.springframework.security.core.Authentication; + +/** + * An authenticator which extracts (if enabled through configuration) the possibility to authenticate a target based through + * a gateway security token. This is commonly used for targets connected indirectly via a gateway. This gateway controls multiple targets + * under the gateway security token which can be set via the {@code Authorization} header. + *

+ * {@code Example Header: Authorization: GatewayToken 5d8fSD54fdsFG98DDsa.} + */ +@Slf4j +public class GatewayTokenAuthenticator extends Authenticator.AbstractAuthenticator { + + public static final String GATEWAY_SECURITY_TOKEN_AUTH_SCHEME = "GatewayToken "; + private static final int OFFSET_GATEWAY_TOKEN = GATEWAY_SECURITY_TOKEN_AUTH_SCHEME.length(); + + private final TenantAware.TenantRunner gatewaySecurityTokenKeyConfigRunner; + + public GatewayTokenAuthenticator( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); + gatewaySecurityTokenKeyConfigRunner = () -> { + log.trace("retrieving configuration value for configuration key {}", + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY); + + return systemSecurityContext + .runAsSystem(() -> tenantConfigurationManagement + .getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class) + .getValue()); + }; + } + + @Override + public Authentication authenticate(final ControllerSecurityToken controllerSecurityToken) { + final String authHeader = controllerSecurityToken.getHeader(ControllerSecurityToken.AUTHORIZATION_HEADER); + if (authHeader == null) { + log.debug("The request doesn't contain the 'authorization' header"); + return null; + } else if (!authHeader.startsWith(GATEWAY_SECURITY_TOKEN_AUTH_SCHEME)) { + log.debug("The request contains the 'authorization' header but it doesn't start with '{}'", GATEWAY_SECURITY_TOKEN_AUTH_SCHEME); + return null; + } + + if (!isEnabled(controllerSecurityToken)) { + log.debug("The gateway token authentication is disabled"); + return null; + } + + log.debug("Found 'authorization' header starting with '{}'", GATEWAY_SECURITY_TOKEN_AUTH_SCHEME); + final String presentedToken = authHeader.substring(OFFSET_GATEWAY_TOKEN); + + // validate if the presented token is the same as the gateway token + return presentedToken.equals(tenantAware.runAsTenant(controllerSecurityToken.getTenant(), gatewaySecurityTokenKeyConfigRunner)) + ? authenticatedController(controllerSecurityToken.getTenant(), controllerSecurityToken.getControllerId()) : null; + } + + @Override + public Logger log() { + return log; + } + + @Override + protected String getTenantConfigurationKey() { + return TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED; + } +} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticator.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticator.java new file mode 100644 index 000000000..cffece2e3 --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticator.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2025 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.security.controller; + +import java.util.Arrays; +import java.util.List; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; + +/** + * An authenticator which extracts the principal from a request URI and the credential from a request header in a the + * {@link ControllerSecurityToken}. + */ +@Slf4j +public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthenticator { + + private static final Logger LOG_SECURITY_AUTH = LoggerFactory.getLogger("server-security.authentication"); + + // Example Headers with Cert Information + // Clientip: 217.24.201.180 + // X-Forwarded-Proto: https + // X-Ssl-Client-Cn: my.name + // X-Ssl-Client-Dn: CN=my.name,CN=O,CN=R,CN=DE,CN=BOSCH,CN=pki,DC=bosch,DC=com + // X-Ssl-Client-Hash: 7f:87:cb:b5:9c:e0:c5:0a:1a:a6:57:69:0f:ca:0a:95 + // X-Ssl-Client-Notafter: Dec 18 08:02:45 2017 GMT + // X-Ssl-Client-Notbefore: Dec 18 07:32:45 2014 GMT + // X-Ssl-Client-Verify: ok + // X-Ssl-Issuer: CN=Bosch-CA1-DE,CN=PKI,DC=Bosch,DC=com + // X-Ssl-Issuer-Dn-1: CN=Bosch-CA-DE,CN=PKI,DC=Bosch,DC=com + // X-Ssl-Issuer-Hash-1: ae:11:f5:6a:0a:e8:74:50:81:0e:0c:37:ec:c5:22:fc + private final String caCommonNameHeader; + // the X-Ssl-Issuer-Hash basic header: Contains the x509 fingerprint hash, this + // header exists multiple times in the request for all trusted chains. + private final String sslIssuerHashBasicHeader; + + private final TenantAware.TenantRunner sslIssuerNameConfigTenantRunner; + + public SecurityHeaderAuthenticator( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext, + final String caCommonNameHeader, final String caAuthorityNameHeader) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); + this.caCommonNameHeader = caCommonNameHeader; + this.sslIssuerHashBasicHeader = caAuthorityNameHeader; + sslIssuerNameConfigTenantRunner = () -> systemSecurityContext.runAsSystem( + () -> tenantConfigurationManagement.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue()); + } + + @Override + public Authentication authenticate(final ControllerSecurityToken controllerSecurityToken) { + // retrieve the common name header and the authority name header from the http request and combine them together + final String commonNameValue = controllerSecurityToken.getHeader(caCommonNameHeader); + if (commonNameValue == null) { + log.debug("The request doesn't contain the 'common name' header"); + return null; + } + if (!commonNameValue.equals(controllerSecurityToken.getControllerId())) { + log.debug("The request contains the 'common name' header but it doesn't match the controller id"); + return null; + } + + if (!isEnabled(controllerSecurityToken)) { + log.debug("The gateway header authentication is disabled"); + return null; + } + + final String sslIssuerHashValue = getIssuerHashHeader( + controllerSecurityToken, + tenantAware.runAsTenant(controllerSecurityToken.getTenant(), sslIssuerNameConfigTenantRunner)); + if (sslIssuerHashValue == null) { + log.debug("The request contains the 'common name' header but trusted hash is not found"); + return null; + } + if (log.isTraceEnabled()) { + log.debug("Found sslIssuerHash ****, using as credentials for tenant {}", controllerSecurityToken.getTenant()); + } + + return authenticatedController(controllerSecurityToken.getTenant(), commonNameValue); + } + + @Override + protected String getTenantConfigurationKey() { + return TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; + } + + @Override + public Logger log() { + return log; + } + + /** + * Iterates over the {@link #sslIssuerHashBasicHeader} basic header {@code X-Ssl-Issuer-Hash-%d} and try to find the same hash as known. + * It's ok if we find the hash in any the trusted CA chain to accept this request for this tenant. + */ + @SuppressWarnings("java:S2629") // check if debug is enabled is maybe heavier then evaluation + private String getIssuerHashHeader(final ControllerSecurityToken controllerSecurityToken, final String knownIssuerHashes) { + // there may be several knownIssuerHashes configured for the tenant + final List knownHashes = Arrays.stream(knownIssuerHashes.split("[;,]")).map(String::toLowerCase).toList(); + + // iterate over the headers until we get a null header. + String foundHash; + for (int iHeader = 1; (foundHash = controllerSecurityToken.getHeader(String.format(sslIssuerHashBasicHeader, iHeader))) != null; iHeader++) { + if (knownHashes.contains(foundHash.toLowerCase())) { + if (log.isTraceEnabled()) { + log.trace("Found matching ssl issuer hash at position {}", iHeader); + } + return foundHash.toLowerCase(); + } + } + LOG_SECURITY_AUTH.debug( + "Certificate request but no matching hash found in headers {} for common name {} in request", + sslIssuerHashBasicHeader, controllerSecurityToken.getHeader(caCommonNameHeader)); + return null; + } +} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityTokenAuthenticator.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityTokenAuthenticator.java new file mode 100644 index 000000000..9cfea827b --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityTokenAuthenticator.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2025 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.security.controller; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.repository.ControllerManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.slf4j.Logger; +import org.springframework.security.core.Authentication; + +/** + * An authenticator which extracts (if enabled through configuration) the possibility to authenticate a target based on + * its target security-token with the {@code Authorization} HTTP header. + *

+ * {@code Example Header: Authorization: TargetToken 5d8fSD54fdsFG98DDsa.} + */ +@Slf4j +public class SecurityTokenAuthenticator extends Authenticator.AbstractAuthenticator { + + public static final String TARGET_SECURITY_TOKEN_AUTH_SCHEME = "TargetToken "; + private static final int OFFSET_TARGET_TOKEN = TARGET_SECURITY_TOKEN_AUTH_SCHEME.length(); + + private final ControllerManagement controllerManagement; + + public SecurityTokenAuthenticator( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext, + final ControllerManagement controllerManagement) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); + this.controllerManagement = controllerManagement; + } + + @Override + public Authentication authenticate(final ControllerSecurityToken controllerSecurityToken) { + final String authHeader = controllerSecurityToken.getHeader(ControllerSecurityToken.AUTHORIZATION_HEADER); + if (authHeader == null) { + log.debug("The request doesn't contain the 'authorization' header"); + return null; + } else if (!authHeader.startsWith(TARGET_SECURITY_TOKEN_AUTH_SCHEME)) { + log.debug("The request contains the 'authorization' header but it doesn't start with '{}'", TARGET_SECURITY_TOKEN_AUTH_SCHEME); + return null; + } + + if (!isEnabled(controllerSecurityToken)) { + log.debug("The target security token authentication is disabled"); + return null; + } + + log.debug("Found 'authorization' header starting with '{}'", TARGET_SECURITY_TOKEN_AUTH_SCHEME); + final String presentedToken = authHeader.substring(OFFSET_TARGET_TOKEN); + + return systemSecurityContext.runAsSystemAsTenant(() -> controllerSecurityToken.getTargetId() != null + ? controllerManagement.get(controllerSecurityToken.getTargetId()) + : controllerManagement.getByControllerId(controllerSecurityToken.getControllerId()), + controllerSecurityToken.getTenant()) + // validate if the presented token is the same as the one set for the target + .filter(target -> presentedToken.equals(target.getSecurityToken())) + .map(target -> authenticatedController(controllerSecurityToken.getTenant(), target.getControllerId())) + .orElse(null); + } + + @Override + public Logger log() { + return log; + } + + @Override + protected String getTenantConfigurationKey() { + return TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED; + } +} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticatorTest.java b/hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticatorTest.java new file mode 100644 index 000000000..ecea91e65 --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticatorTest.java @@ -0,0 +1,116 @@ +/** + * 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.security.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.security.SecurityContextSerializer; +import org.eclipse.hawkbit.security.SecurityContextTenantAware; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@Feature("Unit Tests - Security") +@Story("Gateway token authentication") +@ExtendWith(MockitoExtension.class) +class GatewayTokenAuthenticatorTest { + + private static final String CONTROLLER_ID = "controllerId_gwtoken"; + private static final String GATEWAY_TOKEN = "test-gw-token"; + private static final String UNKNOWN_TOKEN = "unknown"; + + private static final TenantConfigurationValue CONFIG_VALUE_GW_TOKEN = TenantConfigurationValue + . builder().value(GATEWAY_TOKEN).build(); + private static final TenantConfigurationValue CONFIG_VALUE_ENABLED = TenantConfigurationValue + . builder().value(true).build(); + private static final TenantConfigurationValue CONFIG_VALUE_DISABLED = TenantConfigurationValue + . builder().value(false).build(); + + private Authenticator authenticator; + + @Mock + private TenantConfigurationManagement tenantConfigurationManagementMock; + @Mock + private UserAuthoritiesResolver authoritiesResolver; + @Mock + private SecurityContextSerializer securityContextSerializer; + + @BeforeEach + void before() { + final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer); + authenticator = new GatewayTokenAuthenticator( + tenantConfigurationManagementMock, tenantAware, + new SystemSecurityContext(tenantAware)); + } + + @Test + @Description("Tests successful authentication with gateway token") + void testWithGwToken() { + final ControllerSecurityToken securityToken = prepareSecurityToken(GATEWAY_TOKEN); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class)) + .thenReturn(CONFIG_VALUE_GW_TOKEN); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_ENABLED); + + assertThat(authenticator.authenticate(securityToken)) + .isNotNull() + .hasFieldOrPropertyWithValue("principal", CONTROLLER_ID); + } + + @Test + @Description("Tests that if gateway token doesn't match, the authentication fails") + void testWithBadGwToken() { + final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_TOKEN); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class)) + .thenReturn(CONFIG_VALUE_GW_TOKEN); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_ENABLED); + + assertThat(authenticator.authenticate(securityToken)).isNull(); + } + + @Test + @Description("Tests that if gateway token miss, the authentication fails") + void testWithoutGwToken() { + assertThat(authenticator.authenticate(new ControllerSecurityToken("DEFAULT", CONTROLLER_ID))).isNull(); + } + + @Test + @Description("Tests that if disabled, the authentication fails") + void testWithGwTokenButDisabled() { + final ControllerSecurityToken securityToken = prepareSecurityToken(GATEWAY_TOKEN); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_DISABLED); + + assertThat(authenticator.authenticate(securityToken)).isNull(); + } + + private static ControllerSecurityToken prepareSecurityToken(final String gwToken) { + final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", CONTROLLER_ID); + securityToken.putHeader(ControllerSecurityToken.AUTHORIZATION_HEADER, GatewayTokenAuthenticator.GATEWAY_SECURITY_TOKEN_AUTH_SCHEME + gwToken); + return securityToken; + } +} \ No newline at end of file diff --git a/hawkbit-security/hawkbit-security-controller/src/test/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityHeaderFilterTest.java b/hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticatorTest.java similarity index 53% rename from hawkbit-security/hawkbit-security-controller/src/test/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityHeaderFilterTest.java rename to hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticatorTest.java index 792f46161..57a585bbf 100644 --- a/hawkbit-security/hawkbit-security-controller/src/test/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityHeaderFilterTest.java +++ b/hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticatorTest.java @@ -12,8 +12,6 @@ package org.eclipse.hawkbit.security.controller; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; -import java.util.Collection; - import io.qameta.allure.Description; import io.qameta.allure.Feature; import io.qameta.allure.Story; @@ -31,9 +29,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @Feature("Unit Tests - Security") -@Story("Issuer hash based authentication") +@Story("Security header authenticator") @ExtendWith(MockitoExtension.class) -class ControllerPreAuthenticatedSecurityHeaderFilterTest { +class SecurityHeaderAuthenticatorTest { private static final String CA_COMMON_NAME = "ca-cn"; private static final String CA_COMMON_NAME_VALUE = "box1"; @@ -49,11 +47,14 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest { private static final TenantConfigurationValue CONFIG_VALUE_SINGLE_HASH = TenantConfigurationValue . builder().value(SINGLE_HASH).build(); - private static final TenantConfigurationValue CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue . builder().value(MULTI_HASH).build(); + private static final TenantConfigurationValue CONFIG_VALUE_ENABLED = TenantConfigurationValue + . builder().value(true).build(); + private static final TenantConfigurationValue CONFIG_VALUE_DISABLED = TenantConfigurationValue + . builder().value(false).build(); - private ControllerPreAuthenticatedSecurityHeaderFilter underTest; + private Authenticator authenticator; @Mock private TenantConfigurationManagement tenantConfigurationManagementMock; @@ -65,72 +66,88 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest { @BeforeEach void before() { final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer); - underTest = new ControllerPreAuthenticatedSecurityHeaderFilter( - CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d", - tenantConfigurationManagementMock, tenantAware, new SystemSecurityContext(tenantAware)); + authenticator = new SecurityHeaderAuthenticator( + tenantConfigurationManagementMock, tenantAware, + new SystemSecurityContext(tenantAware), CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d" + ); } @Test - @Description("Tests the filter for issuer hash based authentication with a single known hash") - void testIssuerHashBasedAuthenticationWithSingleKnownHash() { + @Description("Tests successful authentication with multiple a single hashes") + void testWithSingleKnownHash() { final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH); - // use single known hash when(tenantConfigurationManagementMock.getConfigurationValue( TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class)) .thenReturn(CONFIG_VALUE_SINGLE_HASH); - assertThat(underTest.getPreAuthenticatedPrincipal(securityToken)).isNotNull(); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_ENABLED); + + assertThat(authenticator.authenticate(securityToken)) + .isNotNull() + .hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE); } @Test - @Description("Tests the filter for issuer hash based authentication with multiple known hashes") - void testIssuerHashBasedAuthenticationWithMultipleKnownHashes() { - // use multiple known hashes + @Description("Tests successful authentication with multiple hashes") + void testWithMultipleKnownHashes() { when(tenantConfigurationManagementMock.getConfigurationValue( TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class)) .thenReturn(CONFIG_VALUE_MULTI_HASH); - assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(SINGLE_HASH))).isNotNull(); - assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(SECOND_HASH))).isNotNull(); - assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(THIRD_HASH))).isNotNull(); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_ENABLED); + + assertThat(authenticator.authenticate(prepareSecurityToken(SINGLE_HASH))) + .isNotNull() + .hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE); + assertThat(authenticator.authenticate(prepareSecurityToken(SECOND_HASH))) + .isNotNull() + .hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE); + assertThat(authenticator.authenticate(prepareSecurityToken(THIRD_HASH))) + .isNotNull() + .hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE); } @Test - @Description("Tests the filter for issuer hash based authentication with unknown hash") - void testIssuerHashBasedAuthenticationWithUnknownHash() { + @Description("Tests that if the hash is unknown, the authentication fails") + void testWithUnknownHash() { final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_HASH); - // use single known hash when(tenantConfigurationManagementMock.getConfigurationValue( TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class)) .thenReturn(CONFIG_VALUE_MULTI_HASH); - assertThat(underTest.getPreAuthenticatedPrincipal(securityToken)).isNull(); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_ENABLED); + + assertThat(authenticator.authenticate(securityToken)).isNull(); } @Test - @Description("Tests different values for issuer hash header and inspects the credentials") - void useDifferentValuesForIssuerHashHeader() { - final ControllerSecurityToken securityToken1 = prepareSecurityToken(SINGLE_HASH); - final ControllerSecurityToken securityToken2 = prepareSecurityToken(SECOND_HASH); + @Description("Tests that if CN doesn't match the CN in the security token, the authentication fails") + void testWithNonMatchingCN() { + final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", "otherControllerID"); + securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE); + securityToken.putHeader(X_SSL_ISSUER_HASH_1, SINGLE_HASH); - final HeaderAuthentication expected1 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SINGLE_HASH); - final HeaderAuthentication expected2 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SECOND_HASH); + assertThat(authenticator.authenticate(securityToken)).isNull(); + } + @Test + @Description("Tests that if the hash miss, the authentication fails") + void testWithoutHash() { + assertThat(authenticator.authenticate(new ControllerSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE))).isNull(); + } + + @Test + @Description("Tests that if disabled, the authentication fails") + void testWithSingleKnownHashButDisabled() { + final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH); when(tenantConfigurationManagementMock.getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class)) - .thenReturn(CONFIG_VALUE_MULTI_HASH); - - final Collection credentials1 = (Collection) underTest - .getPreAuthenticatedCredentials(securityToken1); - final Collection credentials2 = (Collection) underTest - .getPreAuthenticatedCredentials(securityToken2); - - final Object principal1 = underTest.getPreAuthenticatedPrincipal(securityToken1); - final Object principal2 = underTest.getPreAuthenticatedPrincipal(securityToken2); - - assertThat(credentials1).contains(expected1); - assertThat(credentials2).contains(expected2); - - assertThat(expected1).as("hash1 expected in principal!").isEqualTo(principal1); - assertThat(expected2).as("hash2 expected in principal!").isEqualTo(principal2); + TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_DISABLED); + assertThat(authenticator.authenticate(securityToken)).isNull(); } private static ControllerSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) { @@ -139,5 +156,4 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest { securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue); return securityToken; } - -} +} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/SecurityTokenAuthenticatorTest.java b/hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/SecurityTokenAuthenticatorTest.java new file mode 100644 index 000000000..12c3de01c --- /dev/null +++ b/hawkbit-ddi/hawkbit-ddi-security/src/test/java/org/eclipse/hawkbit/security/controller/SecurityTokenAuthenticatorTest.java @@ -0,0 +1,122 @@ +/** + * 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.security.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.eclipse.hawkbit.repository.ControllerManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.security.SecurityContextSerializer; +import org.eclipse.hawkbit.security.SecurityContextTenantAware; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@Feature("Unit Tests - Security") +@Story("Gateway token authentication") +@ExtendWith(MockitoExtension.class) +class SecurityTokenAuthenticatorTest { + + private static final String CONTROLLER_ID = "controllerId_gwtoken"; + private static final String SECURITY_TOKEN = "test-sec-token"; + private static final String UNKNOWN_TOKEN = "unknown"; + + private static final TenantConfigurationValue CONFIG_VALUE_GW_TOKEN = TenantConfigurationValue + . builder().value(SECURITY_TOKEN).build(); + private static final TenantConfigurationValue CONFIG_VALUE_ENABLED = TenantConfigurationValue + . builder().value(true).build(); + private static final TenantConfigurationValue CONFIG_VALUE_DISABLED = TenantConfigurationValue + . builder().value(false).build(); + + private Authenticator authenticator; + + @Mock + private TenantConfigurationManagement tenantConfigurationManagementMock; + @Mock + private ControllerManagement controllerManagementMock; + @Mock + private UserAuthoritiesResolver authoritiesResolver; + @Mock + private SecurityContextSerializer securityContextSerializer; + + @BeforeEach + void before() { + final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer); + authenticator = new SecurityTokenAuthenticator( + tenantConfigurationManagementMock, tenantAware, + new SystemSecurityContext(tenantAware), controllerManagementMock); + } + + @Test + @Description("Tests successful authentication with gateway token") + void testWithSecToken() { + final ControllerSecurityToken securityToken = prepareSecurityToken(SECURITY_TOKEN); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_ENABLED); + + final Target target = Mockito.mock(Target.class); + when(target.getControllerId()).thenReturn(CONTROLLER_ID); + when(target.getSecurityToken()).thenReturn(SECURITY_TOKEN); + when(controllerManagementMock.getByControllerId(CONTROLLER_ID)).thenReturn(Optional.of(target)); + + assertThat(authenticator.authenticate(securityToken)) + .isNotNull() + .hasFieldOrPropertyWithValue("principal", CONTROLLER_ID); + } + + @Test + @Description("Tests that if gateway token doesn't match, the authentication fails") + void testWithBadSecToken() { + final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_TOKEN); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_ENABLED); + + assertThat(authenticator.authenticate(securityToken)).isNull(); + } + + @Test + @Description("Tests that if gateway token miss, the authentication fails") + void testWithoutSecToken() { + assertThat(authenticator.authenticate(new ControllerSecurityToken("DEFAULT", CONTROLLER_ID))).isNull(); + } + + @Test + @Description("Tests that if disabled, the authentication fails") + void testWithSecTokenButDisabled() { + final ControllerSecurityToken securityToken = prepareSecurityToken(SECURITY_TOKEN); + when(tenantConfigurationManagementMock.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, Boolean.class)) + .thenReturn(CONFIG_VALUE_DISABLED); + + assertThat(authenticator.authenticate(securityToken)).isNull(); + } + + private static ControllerSecurityToken prepareSecurityToken(final String secToken) { + final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", CONTROLLER_ID); + securityToken.putHeader(ControllerSecurityToken.AUTHORIZATION_HEADER, SecurityTokenAuthenticator.TARGET_SECURITY_TOKEN_AUTH_SCHEME + secToken); + return securityToken; + } +} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-starter/pom.xml b/hawkbit-ddi/hawkbit-ddi-starter/pom.xml index c6fb73415..17988e48a 100644 --- a/hawkbit-ddi/hawkbit-ddi-starter/pom.xml +++ b/hawkbit-ddi/hawkbit-ddi-starter/pom.xml @@ -25,7 +25,7 @@ org.eclipse.hawkbit - hawkbit-security-controller + hawkbit-ddi-security ${project.version} 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 index 457eef9a1..7474e1ef0 100644 --- 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 @@ -12,10 +12,6 @@ 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.repository.ControllerManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; @@ -25,7 +21,15 @@ 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.security.controller.AuthenticationFilters; +import org.eclipse.hawkbit.security.controller.Authenticator; +import org.eclipse.hawkbit.security.controller.ControllerSecurityToken; +import org.eclipse.hawkbit.security.controller.GatewayTokenAuthenticator; +import org.eclipse.hawkbit.security.controller.SecurityHeaderAuthenticator; +import org.eclipse.hawkbit.security.controller.SecurityTokenAuthenticator; import org.eclipse.hawkbit.tenancy.TenantAware; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; +import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -33,11 +37,12 @@ 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.Authentication; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.intercept.AuthorizationFilter; /** * Security configuration for the hawkBit server DDI download interface. @@ -89,8 +94,6 @@ class ControllerDownloadSecurityConfiguration { @Bean @Order(300) // higher priority than HawkBit DDI security, so that the DDI DL security is applied first protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception { - final AuthenticationManager authenticationManager = ControllerSecurityConfiguration.setAuthenticationManager(http, ddiSecurityConfiguration); - http .securityMatcher(DDI_DL_ANT_MATCHER) .csrf(AbstractHttpConfigurer::disable); @@ -99,34 +102,27 @@ class ControllerDownloadSecurityConfiguration { http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure()); } - final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); - - final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter( - ddiSecurityConfiguration.getRp().getCnHeader(), - ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement, - tenantAware, systemSecurityContext); - securityHeaderFilter.setAuthenticationManager(authenticationManager); - securityHeaderFilter.setCheckForPrincipalChanges(true); - securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( - tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); - securityTokenFilter.setAuthenticationManager(authenticationManager); - securityTokenFilter.setCheckForPrincipalChanges(true); - securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( - tenantConfigurationManagement, tenantAware, systemSecurityContext); - gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager); - gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true); - gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - http .authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated()) .anonymous(AbstractHttpConfigurer::disable) - .addFilter(securityHeaderFilter) - .addFilter(securityTokenFilter) - .addFilter(gatewaySecurityTokenFilter) + .addFilterBefore(new AuthenticationFilters.SecurityHeaderAuthenticationFilter( + new SecurityHeaderAuthenticator( + tenantConfigurationManagement, tenantAware, systemSecurityContext, + ddiSecurityConfiguration.getRp().getCnHeader(), ddiSecurityConfiguration.getRp().getSslIssuerHashHeader()), + ddiSecurityConfiguration), AuthorizationFilter.class) + .addFilterBefore(new AuthenticationFilters.SecurityTokenAuthenticationFilter( + new SecurityTokenAuthenticator( + tenantConfigurationManagement, tenantAware, systemSecurityContext, + controllerManagement), + ddiSecurityConfiguration), AuthorizationFilter.class) + .addFilterBefore(new AuthenticationFilters.GatewayTokenAuthenticationFilter( + new GatewayTokenAuthenticator( + tenantConfigurationManagement, tenantAware, systemSecurityContext), + ddiSecurityConfiguration), AuthorizationFilter.class) + .addFilterBefore(new AuthenticationFilters.AbstractAuthenticationFilter( + new AnonymousAuthenticator( + tenantConfigurationManagement, tenantAware, systemSecurityContext), + ddiSecurityConfiguration) {}, AuthorizationFilter.class) .exceptionHandling(configurer -> configurer.authenticationEntryPoint( (request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()))) .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); @@ -135,4 +131,29 @@ class ControllerDownloadSecurityConfiguration { return http.build(); } + + @Slf4j + private static class AnonymousAuthenticator extends Authenticator.AbstractAuthenticator { + + protected AnonymousAuthenticator( + final TenantConfigurationManagement tenantConfigurationManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); + } + + @Override + public Authentication authenticate(final ControllerSecurityToken controllerSecurityToken) { + return isEnabled(controllerSecurityToken) ? authenticatedController(controllerSecurityToken.getTenant(), null) : null; + } + + @Override + protected String getTenantConfigurationKey() { + return TenantConfigurationProperties.TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED; + } + + @Override + public Logger log() { + return log; + } + } } \ 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 index 76481941c..cf7179d0c 100644 --- 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 @@ -12,10 +12,6 @@ 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.repository.ControllerManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; @@ -25,7 +21,10 @@ 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.security.controller.PreAuthTokenSourceTrustAuthenticationProvider; +import org.eclipse.hawkbit.security.controller.AuthenticationFilters; +import org.eclipse.hawkbit.security.controller.GatewayTokenAuthenticator; +import org.eclipse.hawkbit.security.controller.SecurityHeaderAuthenticator; +import org.eclipse.hawkbit.security.controller.SecurityTokenAuthenticator; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -34,13 +33,12 @@ 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.web.SecurityFilterChain; +import org.springframework.security.web.access.intercept.AuthorizationFilter; /** * Security configuration for the hawkBit server DDI interface. @@ -93,8 +91,6 @@ class ControllerSecurityConfiguration { @Bean @Order(301) protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception { - final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration); - http .securityMatcher(DDI_ANT_MATCHERS) .csrf(AbstractHttpConfigurer::disable); @@ -103,34 +99,22 @@ class ControllerSecurityConfiguration { http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure()); } - final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); - - final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter( - ddiSecurityConfiguration.getRp().getCnHeader(), - ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement, - tenantAware, systemSecurityContext); - securityHeaderFilter.setAuthenticationManager(authenticationManager); - securityHeaderFilter.setCheckForPrincipalChanges(true); - securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( - tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); - securityTokenFilter.setAuthenticationManager(authenticationManager); - securityTokenFilter.setCheckForPrincipalChanges(true); - securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - - final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( - tenantConfigurationManagement, tenantAware, systemSecurityContext); - gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager); - gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true); - gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); - http .authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated()) .anonymous(AbstractHttpConfigurer::disable) - .addFilter(securityHeaderFilter) - .addFilter(securityTokenFilter) - .addFilter(gatewaySecurityTokenFilter) + .addFilterBefore(new AuthenticationFilters.SecurityHeaderAuthenticationFilter( + new SecurityHeaderAuthenticator( + tenantConfigurationManagement, tenantAware, + systemSecurityContext, ddiSecurityConfiguration.getRp().getCnHeader(), ddiSecurityConfiguration.getRp().getSslIssuerHashHeader() + ), ddiSecurityConfiguration), AuthorizationFilter.class) + .addFilterBefore(new AuthenticationFilters.SecurityTokenAuthenticationFilter( + new SecurityTokenAuthenticator( + tenantConfigurationManagement, tenantAware, + systemSecurityContext, controllerManagement), ddiSecurityConfiguration), AuthorizationFilter.class) + .addFilterBefore(new AuthenticationFilters.GatewayTokenAuthenticationFilter( + new GatewayTokenAuthenticator( + tenantConfigurationManagement, tenantAware, + systemSecurityContext), ddiSecurityConfiguration), AuthorizationFilter.class) .exceptionHandling(configurer -> configurer.authenticationEntryPoint( (request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()))) .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); @@ -139,17 +123,4 @@ class ControllerSecurityConfiguration { 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; - } } \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/AbstractHttpControllerAuthenticationFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/AbstractHttpControllerAuthenticationFilter.java deleted file mode 100644 index 1568848b5..000000000 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/AbstractHttpControllerAuthenticationFilter.java +++ /dev/null @@ -1,182 +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.ddi.security; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.security.controller.ControllerSecurityToken; -import org.eclipse.hawkbit.security.controller.PreAuthenticationFilter; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.eclipse.hawkbit.util.UrlUtils; -import org.slf4j.Logger; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.util.AntPathMatcher; - -/** - * An abstraction for all controller based security to parse the e.g. the tenant - * name from the URL and the controller ID from the URL to do security checks - * based on this information. - */ -public abstract class AbstractHttpControllerAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter { - - protected TenantConfigurationManagement tenantConfigurationManagement; - protected TenantAware tenantAware; - protected SystemSecurityContext systemSecurityContext; - - private static final String TENANT_PLACE_HOLDER = "tenant"; - private static final String CONTROLLER_ID_PLACE_HOLDER = "controllerId"; - /** - * requestURIPathPattern the request URI path pattern in ANT style - * containing the placeholder key for retrieving the principal from the URI - * request. e.g."/{tenant}/controller/v1/{controllerId} - */ - private static final String CONTROLLER_REQUEST_ANT_PATTERN = "/{" + TENANT_PLACE_HOLDER + "}/controller/v1" + - "/{" + CONTROLLER_ID_PLACE_HOLDER + "}/**"; - private static final String CONTROLLER_DL_REQUEST_ANT_PATTERN = "/{" + TENANT_PLACE_HOLDER + "}/controller/artifacts/v1/**"; - - private final AntPathMatcher pathExtractor; - private PreAuthenticationFilter abstractControllerAuthenticationFilter; - - /** - * Constructor for subclasses. - * - * @param tenantConfigurationManagement the tenant configuration service - * @param tenantAware the tenant aware service - * @param systemSecurityContext the system security context - */ - protected AbstractHttpControllerAuthenticationFilter( - final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, - final SystemSecurityContext systemSecurityContext) { - this.tenantConfigurationManagement = tenantConfigurationManagement; - this.tenantAware = tenantAware; - this.systemSecurityContext = systemSecurityContext; - pathExtractor = new AntPathMatcher(); - } - - @Override - public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) - throws IOException, ServletException { - if (SecurityContextHolder.getContext().getAuthentication() != null) { - log().trace("Request is already authenticated. Skip filter"); - chain.doFilter(request, response); - return; - } - - if (!(request instanceof HttpServletRequest)) { - chain.doFilter(request, response); - return; - } - - final ControllerSecurityToken securityToken = createTenantSecurityTokenVariables((HttpServletRequest) request); - if (securityToken == null) { - chain.doFilter(request, response); - return; - } - - abstractControllerAuthenticationFilter = createControllerAuthenticationFilter(); - if (abstractControllerAuthenticationFilter.isEnable(securityToken)) { - super.doFilter(request, response, chain); - } else { - log().debug("Filter is disabled for the tenant {}", securityToken.getTenant()); - chain.doFilter(request, response); - } - } - - @Override - protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, - final Authentication authResult) throws IOException, ServletException { - final Collection authorities = new ArrayList<>(); - authorities.addAll(authResult.getAuthorities()); - authorities.addAll(abstractControllerAuthenticationFilter.getSuccessfulAuthenticationAuthorities()); - final PreAuthenticatedAuthenticationToken authTokenWithGrantedAuthorities = new PreAuthenticatedAuthenticationToken( - authResult.getPrincipal(), authResult.getCredentials(), authorities); - authTokenWithGrantedAuthorities.setDetails(authResult.getDetails()); - super.successfulAuthentication(request, response, authTokenWithGrantedAuthorities); - } - - @Override - protected Object getPreAuthenticatedPrincipal(final HttpServletRequest request) { - final ControllerSecurityToken securityToken = createTenantSecurityTokenVariables(request); - if (securityToken == null) { - return null; - } - return abstractControllerAuthenticationFilter.getPreAuthenticatedPrincipal(securityToken); - } - - @Override - protected Object getPreAuthenticatedCredentials(final HttpServletRequest request) { - final ControllerSecurityToken securityToken = createTenantSecurityTokenVariables(request); - if (securityToken == null) { - return null; - } - return abstractControllerAuthenticationFilter.getPreAuthenticatedCredentials(securityToken); - } - - protected abstract PreAuthenticationFilter createControllerAuthenticationFilter(); - - protected abstract Logger log(); - - /** - * Extracts tenant and controllerId from the request URI as path variables. - * - * @param request the Http request to extract the path variables. - * @return the extracted {@link ControllerSecurityToken} or {@code null} if the - * request does not match the pattern and no variables could be - * extracted - */ - protected ControllerSecurityToken createTenantSecurityTokenVariables(final HttpServletRequest request) { - final String requestURI = request.getRequestURI(); - - if (pathExtractor.match(request.getContextPath() + CONTROLLER_REQUEST_ANT_PATTERN, requestURI)) { - log().debug("retrieving principal from URI request {}", requestURI); - final Map extractUriTemplateVariables = pathExtractor - .extractUriTemplateVariables(request.getContextPath() + CONTROLLER_REQUEST_ANT_PATTERN, requestURI); - final String controllerId = UrlUtils.decodeUriValue(extractUriTemplateVariables.get(CONTROLLER_ID_PLACE_HOLDER)); - final String tenant = UrlUtils.decodeUriValue(extractUriTemplateVariables.get(TENANT_PLACE_HOLDER)); - log().trace("Parsed tenant {} and controllerId {} from path request {}", tenant, controllerId, requestURI); - return createTenantSecurityTokenVariables(request, tenant, controllerId); - } else if (pathExtractor.match(request.getContextPath() + CONTROLLER_DL_REQUEST_ANT_PATTERN, requestURI)) { - log().debug("retrieving path variables from URI request {}", requestURI); - final Map extractUriTemplateVariables = pathExtractor.extractUriTemplateVariables( - request.getContextPath() + CONTROLLER_DL_REQUEST_ANT_PATTERN, requestURI); - final String tenant = UrlUtils.decodeUriValue(extractUriTemplateVariables.get(TENANT_PLACE_HOLDER)); - log().trace("Parsed tenant {} from path request {}", tenant, requestURI); - return createTenantSecurityTokenVariables(request, tenant, "anonymous"); - } else { - log().trace("request {} does not match the path pattern {}, request gets ignored", requestURI, - CONTROLLER_REQUEST_ANT_PATTERN); - return null; - } - } - - private ControllerSecurityToken createTenantSecurityTokenVariables(final HttpServletRequest request, - final String tenant, final String controllerId) { - final ControllerSecurityToken securityToken = new ControllerSecurityToken(tenant, null, controllerId, null); - Collections.list(request.getHeaderNames()).forEach(header -> securityToken.putHeader(header, request.getHeader(header))); - return securityToken; - } -} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/ControllerTenantAwareAuthenticationDetailsSource.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/ControllerTenantAwareAuthenticationDetailsSource.java deleted file mode 100644 index a34dc730b..000000000 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/ControllerTenantAwareAuthenticationDetailsSource.java +++ /dev/null @@ -1,65 +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.ddi.security; - -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.security.controller.TenantAwareWebAuthenticationDetails; -import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails; -import org.eclipse.hawkbit.util.UrlUtils; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.util.AntPathMatcher; - -/** - * An {@link AuthenticationDetailsSource} implementation which retrieves the - * tenant from a request pattern {@link #TENANT_AWARE_CONTROLLER_PATTERN} and - * stores the retrieved tenant in the {@link TenantAwareAuthenticationDetails}. - */ -@Slf4j -public class ControllerTenantAwareAuthenticationDetailsSource - implements AuthenticationDetailsSource { - - private static final String TENANT_AWARE_CONTROLLER_PATTERN = "/{tenant}/controller/**"; - private static final String TENANT_PLACE_HOLDER = "tenant"; - private final AntPathMatcher pathExtractor; - - /** - * Constructor. - */ - public ControllerTenantAwareAuthenticationDetailsSource() { - pathExtractor = new AntPathMatcher(); - } - - @Override - public TenantAwareAuthenticationDetails buildDetails(final HttpServletRequest request) { - return new TenantAwareWebAuthenticationDetails(getTenantFromRequestUri(request), request.getRemoteAddr(), true); - } - - private String getTenantFromRequestUri(final HttpServletRequest request) { - final String requestURI = request.getRequestURI(); - log.debug("retrieving tenant from URI request {}", requestURI); - final String requestPathPattern = request.getContextPath() + TENANT_AWARE_CONTROLLER_PATTERN; - if (!pathExtractor.match(requestPathPattern, requestURI)) { - log.info("Controller request not matching tenant aware request pattern requestpath: {}, pattern {}", - requestURI, TENANT_AWARE_CONTROLLER_PATTERN); - return null; - } - final Map extractUriTemplateVariables = pathExtractor - .extractUriTemplateVariables(requestPathPattern, requestURI); - if (log.isTraceEnabled()) { - log.trace("Parsed path variables {} using tenant {}", extractUriTemplateVariables, - extractUriTemplateVariables.get(TENANT_PLACE_HOLDER)); - } - return UrlUtils.decodeUriValue(extractUriTemplateVariables.get(TENANT_PLACE_HOLDER)); - } -} diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateSecurityTokenFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateSecurityTokenFilter.java deleted file mode 100644 index 4622c085c..000000000 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticateSecurityTokenFilter.java +++ /dev/null @@ -1,67 +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.ddi.security; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.security.controller.ControllerPreAuthenticatedSecurityTokenFilter; -import org.eclipse.hawkbit.security.controller.PreAuthenticationFilter; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.slf4j.Logger; - -/** - * An pre-authenticated processing filter which extracts (if enabled through - * configuration) the possibility to authenticate a target based on its target - * security-token with the {@code Authorization} HTTP header. - * {@code Example Header: Authorization: TargetToken - * 5d8fSD54fdsFG98DDsa.} - * - * The {@code Authorization} header is a HTTP standard and reverse proxy or - * other proxies will keep the Authorization headers untouched instead of maybe - * custom headers which have then weird side-effects. Furthermore frameworks are - * aware of the sensitivity of the Authorization header and do not log it and - * store it somewhere. - */ -@Slf4j -public class HttpControllerPreAuthenticateSecurityTokenFilter extends AbstractHttpControllerAuthenticationFilter { - - private final ControllerManagement controllerManagement; - - /** - * Constructor. - * - * @param tenantConfigurationManagement the system management service to retrieve configuration - * properties - * @param tenantAware the tenant aware service to get configuration for the specific - * tenant - * @param controllerManagement the controller management to retrieve the specific target - * security token to verify - * @param systemSecurityContext the system security context - */ - public HttpControllerPreAuthenticateSecurityTokenFilter( - final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, - final ControllerManagement controllerManagement, final SystemSecurityContext systemSecurityContext) { - super(tenantConfigurationManagement, tenantAware, systemSecurityContext); - this.controllerManagement = controllerManagement; - } - - @Override - protected PreAuthenticationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticatedSecurityTokenFilter(tenantConfigurationManagement, controllerManagement, - tenantAware, systemSecurityContext); - } - - @Override - protected Logger log() { - return log; - } -} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java deleted file mode 100644 index 22f51d4b0..000000000 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java +++ /dev/null @@ -1,56 +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.ddi.security; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.security.controller.ControllerPreAuthenticatedGatewaySecurityTokenFilter; -import org.eclipse.hawkbit.security.controller.PreAuthenticationFilter; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.slf4j.Logger; - -/** - * Extract the {@code Authorization} header is a HTTP standard and reverse proxy - * or other proxies will keep the Authorization headers untouched instead of - * maybe custom headers which have then weird side-effects. Furthermore - * frameworks are aware of the sensitivity of the Authorization header and do - * not log it and store it somewhere. - */ -@Slf4j -public class HttpControllerPreAuthenticatedGatewaySecurityTokenFilter - extends AbstractHttpControllerAuthenticationFilter { - - /** - * Constructor. - * - * @param tenantConfigurationManagement the system management service to retrieve configuration - * properties - * @param tenantAware the tenant aware service to get configuration for the specific - * tenant - * @param systemSecurityContext the system security context - */ - public HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( - final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, - final SystemSecurityContext systemSecurityContext) { - super(tenantConfigurationManagement, tenantAware, systemSecurityContext); - } - - @Override - protected PreAuthenticationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticatedGatewaySecurityTokenFilter(tenantConfigurationManagement, tenantAware, - systemSecurityContext); - } - - @Override - protected Logger log() { - return log; - } -} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java b/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java deleted file mode 100644 index 52a13cb1c..000000000 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/main/java/org/eclipse/hawkbit/autoconfigure/ddi/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java +++ /dev/null @@ -1,66 +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.ddi.security; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.security.controller.ControllerPreAuthenticatedSecurityHeaderFilter; -import org.eclipse.hawkbit.security.controller.PreAuthenticationFilter; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.slf4j.Logger; - -/** - * An pre-authenticated processing filter which extracts the principal from a - * request URI and the credential from a request header. - */ -@Slf4j -public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends AbstractHttpControllerAuthenticationFilter { - - private final String caCommonNameHeader; - private final String caAuthorityNameHeader; - - /** - * 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 - * configured header field is checked. - * - * @param caCommonNameHeader the http-header which holds the common-name of the certificate - * @param caAuthorityNameHeader the http-header which holds the ca-authority name of the - * certificate - * @param tenantConfigurationManagement the tenant configuration management service to retrieve - * configuration properties to check if the header authentication - * is enabled for this tenant - * @param tenantAware the tenant aware service to get configuration for the specific - * tenant - * @param systemSecurityContext the system security context - */ - public HttpControllerPreAuthenticatedSecurityHeaderFilter(final String caCommonNameHeader, - final String caAuthorityNameHeader, final TenantConfigurationManagement tenantConfigurationManagement, - final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { - super(tenantConfigurationManagement, tenantAware, systemSecurityContext); - this.caCommonNameHeader = caCommonNameHeader; - this.caAuthorityNameHeader = caAuthorityNameHeader; - } - - @Override - protected PreAuthenticationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticatedSecurityHeaderFilter( - caCommonNameHeader, caAuthorityNameHeader, - tenantConfigurationManagement, tenantAware, systemSecurityContext); - } - - @Override - protected Logger log() { - return log; - } -} \ No newline at end of file diff --git a/hawkbit-ddi/hawkbit-ddi-starter/src/test/java/org/eclipse/hawkbit/autoconfigure/ddi/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java b/hawkbit-ddi/hawkbit-ddi-starter/src/test/java/org/eclipse/hawkbit/autoconfigure/ddi/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java deleted file mode 100644 index 8a3f17199..000000000 --- a/hawkbit-ddi/hawkbit-ddi-starter/src/test/java/org/eclipse/hawkbit/autoconfigure/ddi/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java +++ /dev/null @@ -1,144 +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.ddi.security; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.when; - -import java.util.Collections; - -import io.qameta.allure.Description; -import io.qameta.allure.Feature; -import io.qameta.allure.Story; -import org.eclipse.hawkbit.security.controller.PreAuthTokenSourceTrustAuthenticationProvider; -import org.eclipse.hawkbit.security.controller.TenantAwareWebAuthenticationDetails; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; - -@Feature("Unit Tests - Security") -@Story("PreAuthToken Source TrustAuthentication Provider Test") -@ExtendWith(MockitoExtension.class) -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); - - @Mock - private TenantAwareWebAuthenticationDetails webAuthenticationDetailsMock; - - @Test - @Description("Testing in case the containing controllerId in the URI request path does not accord with the controllerId in the request header.") - void principalAndCredentialsNotTheSameThrowsAuthenticationException() { - final String principal = "controllerIdURL"; - final String credentials = "controllerIdHeader"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - Collections.singletonList(credentials)); - token.setDetails(webAuthenticationDetailsMock); - - assertThatExceptionOfType(BadCredentialsException.class).as("Should not work with wrong credentials") - .isThrownBy(() -> underTestWithoutSourceIpCheck.authenticate(token)); - } - - @Test - @Description("Testing that the controllerId within the URI request path is the same with the controllerId within the request header and no source IP check is in place.") - void principalAndCredentialsAreTheSameWithNoSourceIpCheckIsSuccessful() { - final String principal = "controllerId"; - final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - Collections.singletonList(credentials)); - token.setDetails(webAuthenticationDetailsMock); - - final Authentication authenticate = underTestWithoutSourceIpCheck.authenticate(token); - assertThat(authenticate.isAuthenticated()).isTrue(); - } - - @Test - @Description("Testing that the controllerId in the URI request match with the controllerId in the request header but the request are not coming from a trustful source.") - void principalAndCredentialsAreTheSameButSourceIpRequestNotMatching2() { - final String remoteAddress = "192.168.1.1"; - final String principal = "controllerId"; - final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - Collections.singletonList(credentials)); - token.setDetails(webAuthenticationDetailsMock); - - when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(remoteAddress); - - assertThatExceptionOfType(InsufficientAuthenticationException.class).as("as source is not trusted.") - .isThrownBy(() -> underTestWithSourceIpCheck.authenticate(token)); - } - - @Test - @Description("Testing that the controllerId in the URI request match with the controllerId in the request header and the source IP is matching the allowed remote IP address.") - void principalAndCredentialsAreTheSameAndSourceIpIsTrusted() { - final String principal = "controllerId"; - final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - Collections.singletonList(credentials)); - token.setDetails(webAuthenticationDetailsMock); - - when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP); - - // test, should throw authentication exception - final Authentication authenticate = underTestWithSourceIpCheck.authenticate(token); - assertThat(authenticate.isAuthenticated()).isTrue(); - } - - @Test - @Description("Testing that the controllerId in the URI request match with the controllerId in the request header and the source IP matches one of the allowed remote IP addresses.") - void principalAndCredentialsAreTheSameAndSourceIpIsWithinList() { - final String[] trustedIPAddresses = new String[] { "192.168.1.1", "192.168.1.2", REQUEST_SOURCE_IP, - "192.168.1.3" }; - final String principal = "controllerId"; - final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - Collections.singletonList(credentials)); - token.setDetails(webAuthenticationDetailsMock); - - when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP); - - final PreAuthTokenSourceTrustAuthenticationProvider underTestWithList = new PreAuthTokenSourceTrustAuthenticationProvider( - trustedIPAddresses); - - // test, should throw authentication exception - final Authentication authenticate = underTestWithList.authenticate(token); - assertThat(authenticate.isAuthenticated()).isTrue(); - } - - @Test - @Description("Testing that the controllerId in the URI request match with the controllerId in the request header and the source IP does not match any of the allowed remote IP addresses.") - void principalAndCredentialsAreTheSameSourceIpListNotMatches() { - final String[] trustedIPAddresses = new String[] { "192.168.1.1", "192.168.1.2", "192.168.1.3" }; - final String principal = "controllerId"; - final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - Collections.singletonList(credentials)); - token.setDetails(webAuthenticationDetailsMock); - - when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP); - - final PreAuthTokenSourceTrustAuthenticationProvider underTestWithList = new PreAuthTokenSourceTrustAuthenticationProvider( - trustedIPAddresses); - - assertThatExceptionOfType(InsufficientAuthenticationException.class) - .isThrownBy(() -> underTestWithList.authenticate(token)); - } -} diff --git a/hawkbit-ddi/pom.xml b/hawkbit-ddi/pom.xml index 592cb465d..d37f67685 100644 --- a/hawkbit-ddi/pom.xml +++ b/hawkbit-ddi/pom.xml @@ -25,6 +25,7 @@ hawkbit-ddi-api hawkbit-ddi-resource + hawkbit-ddi-security hawkbit-ddi-starter hawkbit-ddi-server diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/pom.xml b/hawkbit-dmf/hawkbit-dmf-amqp/pom.xml index d8b50ae59..dd6874237 100644 --- a/hawkbit-dmf/hawkbit-dmf-amqp/pom.xml +++ b/hawkbit-dmf/hawkbit-dmf-amqp/pom.xml @@ -34,7 +34,7 @@ org.eclipse.hawkbit - hawkbit-security-controller + hawkbit-security-core ${project.version} diff --git a/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml b/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml index f89391022..e4702d81f 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml +++ b/hawkbit-mgmt/hawkbit-mgmt-starter/pom.xml @@ -25,7 +25,7 @@ org.eclipse.hawkbit - hawkbit-security-controller + hawkbit-security-core ${project.version} diff --git a/hawkbit-security/hawkbit-security-core/pom.xml b/hawkbit-security-core/pom.xml similarity index 96% rename from hawkbit-security/hawkbit-security-core/pom.xml rename to hawkbit-security-core/pom.xml index 28e965b35..d61d62c5b 100644 --- a/hawkbit-security/hawkbit-security-core/pom.xml +++ b/hawkbit-security-core/pom.xml @@ -14,7 +14,7 @@ 4.0.0 org.eclipse.hawkbit - hawkbit-security-parent + hawkbit-parent ${revision} diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpRole.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpRole.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpRole.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpRole.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/StaticAuthenticationProvider.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DdiSecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DdiSecurityProperties.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DdiSecurityProperties.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DdiSecurityProperties.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/InMemoryUserAuthoritiesResolver.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityConstants.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityConstants.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityConstants.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityConstants.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextSerializer.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextSerializer.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextSerializer.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextSerializer.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityTokenGenerator.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityTokenGenerator.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityTokenGenerator.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityTokenGenerator.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SpringSecurityAuditorAware.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java diff --git a/hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/UrlUtils.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/UrlUtils.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/UrlUtils.java rename to hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/UrlUtils.java diff --git a/hawkbit-security/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/SpPermissionTest.java b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/SpPermissionTest.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/SpPermissionTest.java rename to hawkbit-security-core/src/test/java/org/eclipse/hawkbit/im/authentication/SpPermissionTest.java diff --git a/hawkbit-security/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java similarity index 100% rename from hawkbit-security/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java rename to hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/AbstractControllerAuthenticationFilter.java b/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/AbstractControllerAuthenticationFilter.java deleted file mode 100644 index 1d092ce70..000000000 --- a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/AbstractControllerAuthenticationFilter.java +++ /dev/null @@ -1,67 +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.security.controller; - -import java.util.Arrays; -import java.util.Collection; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -/** - * An abstraction for all controller based security. Check if the tenant - * configuration is enabled. - */ -@Slf4j -public abstract class AbstractControllerAuthenticationFilter implements PreAuthenticationFilter { - - protected final TenantConfigurationManagement tenantConfigurationManagement; - protected final TenantAware tenantAware; - protected final SystemSecurityContext systemSecurityContext; - private final SecurityConfigurationKeyTenantRunner configurationKeyTenantRunner; - - protected AbstractControllerAuthenticationFilter( - final TenantConfigurationManagement systemManagement, final TenantAware tenantAware, - final SystemSecurityContext systemSecurityContext) { - this.tenantConfigurationManagement = systemManagement; - this.tenantAware = tenantAware; - this.systemSecurityContext = systemSecurityContext; - this.configurationKeyTenantRunner = new SecurityConfigurationKeyTenantRunner(); - } - - @Override - public boolean isEnable(final ControllerSecurityToken securityToken) { - return tenantAware.runAsTenant(securityToken.getTenant(), configurationKeyTenantRunner); - } - - @Override - public Collection getSuccessfulAuthenticationAuthorities() { - return Arrays.asList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE)); - } - - protected abstract String getTenantConfigurationKey(); - - private final class SecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner { - - @Override - public Boolean run() { - - log.trace("retrieving configuration value for configuration key {}", getTenantConfigurationKey()); - return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement - .getConfigurationValue(getTenantConfigurationKey(), Boolean.class).getValue()); - } - - } -} diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java b/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java deleted file mode 100644 index 9c3c69bea..000000000 --- a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java +++ /dev/null @@ -1,95 +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.security.controller; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; - -/** - * An pre-authenticated processing filter which extracts (if enabled through - * configuration) the possibility to authenticate a target based through a - * gateway security token. This is commonly used for targets connected - * indirectly via a gateway. This gateway controls multiple targets under the - * gateway security token which can be set via the {@code TenantsecurityToken} - * header. {@code Example Header: Authorization: GatewayToken - * 5d8fSD54fdsFG98DDsa.} - */ -@Slf4j -public class ControllerPreAuthenticatedGatewaySecurityTokenFilter extends AbstractControllerAuthenticationFilter { - - private static final String GATEWAY_SECURITY_TOKEN_AUTH_SCHEME = "GatewayToken "; - private static final int OFFSET_GATEWAY_TOKEN = GATEWAY_SECURITY_TOKEN_AUTH_SCHEME.length(); - - private final GetGatewaySecurityConfigurationKeyTenantRunner gatewaySecurityTokenKeyConfigRunner = new GetGatewaySecurityConfigurationKeyTenantRunner(); - - /** - * Constructor. - * - * @param tenantConfigurationManagement the tenant management service to retrieve configuration - * properties - * @param tenantAware the tenant aware service to get configuration for the specific - * tenant - * @param systemSecurityContext the system security context to get access to tenant - * configuration - */ - public ControllerPreAuthenticatedGatewaySecurityTokenFilter( - final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, - final SystemSecurityContext systemSecurityContext) { - super(tenantConfigurationManagement, tenantAware, systemSecurityContext); - } - - @Override - public HeaderAuthentication getPreAuthenticatedPrincipal(final ControllerSecurityToken securityToken) { - final String authHeader = securityToken.getHeader(ControllerSecurityToken.AUTHORIZATION_HEADER); - if (authHeader != null && - authHeader.startsWith(GATEWAY_SECURITY_TOKEN_AUTH_SCHEME) && - authHeader.length() > OFFSET_GATEWAY_TOKEN) { // disables empty string token - log.debug("found authorization header with scheme {} using target security token for authentication", - GATEWAY_SECURITY_TOKEN_AUTH_SCHEME); - return new HeaderAuthentication(securityToken.getControllerId(), - authHeader.substring(OFFSET_GATEWAY_TOKEN)); - } - log.debug( - "security token filter is enabled but request does not contain either the necessary security token {} or the authorization header with scheme {}", - securityToken, GATEWAY_SECURITY_TOKEN_AUTH_SCHEME); - return null; - } - - @Override - public HeaderAuthentication getPreAuthenticatedCredentials(final ControllerSecurityToken securityToken) { - final String gatewayToken = tenantAware.runAsTenant(securityToken.getTenant(), - gatewaySecurityTokenKeyConfigRunner); - return new HeaderAuthentication(securityToken.getControllerId(), gatewayToken); - } - - @Override - protected String getTenantConfigurationKey() { - return TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED; - } - - private final class GetGatewaySecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner { - - @Override - public String run() { - log.trace("retrieving configuration value for configuration key {}", - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY); - - return systemSecurityContext - .runAsSystem(() -> tenantConfigurationManagement - .getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class) - .getValue()); - } - } - -} diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityHeaderFilter.java b/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityHeaderFilter.java deleted file mode 100644 index 7df8c9813..000000000 --- a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityHeaderFilter.java +++ /dev/null @@ -1,157 +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.security.controller; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A pre-authenticated processing filter which extracts the principal from a - * request URI and the credential from a request header in a the - * {@link ControllerSecurityToken}. - */ -@Slf4j -public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractControllerAuthenticationFilter { - - private static final Logger LOG_SECURITY_AUTH = LoggerFactory.getLogger("server-security.authentication"); - - private final GetSecurityAuthorityNameTenantRunner sslIssuerNameConfigTenantRunner = new GetSecurityAuthorityNameTenantRunner(); - // Example Headers with Cert Information - // Clientip: 217.24.201.180 - // X-Forwarded-Proto: https - // X-Ssl-Client-Cn: my.name - // X-Ssl-Client-Dn: - // CN=my.name,CN=O,CN=R,CN=DE,CN=BOSCH,CN=pki,DC=bosch,DC=com - // X-Ssl-Client-Hash: 7f:87:cb:b5:9c:e0:c5:0a:1a:a6:57:69:0f:ca:0a:95 - // X-Ssl-Client-Notafter: Dec 18 08:02:45 2017 GMT - // X-Ssl-Client-Notbefore: Dec 18 07:32:45 2014 GMT - // X-Ssl-Client-Verify: ok - // X-Ssl-Issuer: CN=Bosch-CA1-DE,CN=PKI,DC=Bosch,DC=com - // X-Ssl-Issuer-Dn-1: CN=Bosch-CA-DE,CN=PKI,DC=Bosch,DC=com - // X-Ssl-Issuer-Hash-1: ae:11:f5:6a:0a:e8:74:50:81:0e:0c:37:ec:c5:22:fc - private final String caCommonNameHeader; - // the X-Ssl-Issuer-Hash basic header: Contains the x509 fingerprint hash, this - // header exists multiple times in the request for all trusted chains. - private final String sslIssuerHashBasicHeader; - - /** - * Constructor. - * - * @param caCommonNameHeader the http-header which holds the common-name of the certificate - * @param caAuthorityNameHeader the http-header which holds the ca-authority name of the - * certificate - * @param tenantConfigurationManagement the tenant management service to retrieve configuration - * properties - * @param tenantAware the tenant aware service to get configuration for the specific - * tenant - * @param systemSecurityContext the system security context to get access to tenant - * configuration - */ - public ControllerPreAuthenticatedSecurityHeaderFilter(final String caCommonNameHeader, - final String caAuthorityNameHeader, final TenantConfigurationManagement tenantConfigurationManagement, - final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { - super(tenantConfigurationManagement, tenantAware, systemSecurityContext); - this.caCommonNameHeader = caCommonNameHeader; - this.sslIssuerHashBasicHeader = caAuthorityNameHeader; - } - - @Override - public HeaderAuthentication getPreAuthenticatedPrincipal(final ControllerSecurityToken securityToken) { - // retrieve the common name header and the authority name header from - // the http request and combine them together - final String commonNameValue = securityToken.getHeader(caCommonNameHeader); - final String knownSslIssuerConfigurationValue = tenantAware.runAsTenant(securityToken.getTenant(), - sslIssuerNameConfigTenantRunner); - final String sslIssuerHashValue = getIssuerHashHeader(securityToken, knownSslIssuerConfigurationValue); - if (commonNameValue != null && log.isTraceEnabled()) { - log.trace("Found commonNameHeader {}={}, using as credentials", caCommonNameHeader, commonNameValue); - } - if (sslIssuerHashValue != null && log.isTraceEnabled()) { - log.trace("Found sslIssuerHash ****, using as credentials for tenant {}", securityToken.getTenant()); - } - - if (commonNameValue != null && sslIssuerHashValue != null) { - return new HeaderAuthentication(commonNameValue, sslIssuerHashValue); - } - return null; - } - - @Override - public Object getPreAuthenticatedCredentials(final ControllerSecurityToken securityToken) { - final String authorityNameConfigurationValue = tenantAware.runAsTenant(securityToken.getTenant(), - sslIssuerNameConfigTenantRunner); - - // in case of legacy download artifact, the controller ID is not in the - // URL path, so then we just use the common name header - final String controllerId = // - (securityToken.getControllerId() == null || "anonymous".equals(securityToken.getControllerId()) // - ? securityToken.getHeader(caCommonNameHeader) - : securityToken.getControllerId()); - - final List knownHashes = splitMultiHashBySemicolon(authorityNameConfigurationValue); - return knownHashes.stream().map(hashItem -> new HeaderAuthentication(controllerId, hashItem)).collect(Collectors.toSet()); - } - - @Override - protected String getTenantConfigurationKey() { - return TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - } - - private static List splitMultiHashBySemicolon(final String knownIssuerHashes) { - return Arrays.stream(knownIssuerHashes.split("[;,]")).map(String::toLowerCase).toList(); - } - - /** - * Iterates over the {@link #sslIssuerHashBasicHeader} basic header - * {@code X-Ssl-Issuer-Hash-%d} and try to find the same hash as known. It's ok - * if we find the hash in any the trusted CA chain to accept this request for - * this tenant. - */ - @SuppressWarnings("java:S2629") // check if debug is enabled is maybe heavier then evaluation - private String getIssuerHashHeader(final ControllerSecurityToken securityToken, final String knownIssuerHashes) { - // there may be several knownIssuerHashes configured for the tenant - final List knownHashes = splitMultiHashBySemicolon(knownIssuerHashes); - - // iterate over the headers until we get a null header. - int iHeader = 1; - String foundHash; - while ((foundHash = securityToken.getHeader(String.format(sslIssuerHashBasicHeader, iHeader))) != null) { - if (knownHashes.contains(foundHash.toLowerCase())) { - if (log.isTraceEnabled()) { - log.trace("Found matching ssl issuer hash at position {}", iHeader); - } - return foundHash.toLowerCase(); - } - iHeader++; - } - LOG_SECURITY_AUTH.debug( - "Certificate request but no matching hash found in headers {} for common name {} in request", - sslIssuerHashBasicHeader, securityToken.getHeader(caCommonNameHeader)); - return null; - } - - private final class GetSecurityAuthorityNameTenantRunner implements TenantAware.TenantRunner { - - @Override - public String run() { - return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue()); - } - } -} diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityTokenFilter.java b/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityTokenFilter.java deleted file mode 100644 index aa30529d9..000000000 --- a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/ControllerPreAuthenticatedSecurityTokenFilter.java +++ /dev/null @@ -1,99 +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.security.controller; - -import java.util.Optional; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.TenantConfigurationManagement; -import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.security.SystemSecurityContext; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; - -/** - * An pre-authenticated processing filter which extracts (if enabled through - * configuration) the possibility to authenticate a target based on its target - * security-token with the {@code Authorization} HTTP header. - * {@code Example Header: Authorization: TargetToken - * 5d8fSD54fdsFG98DDsa.} - */ -@Slf4j -public class ControllerPreAuthenticatedSecurityTokenFilter extends AbstractControllerAuthenticationFilter { - - private static final String TARGET_SECURITY_TOKEN_AUTH_SCHEME = "TargetToken "; - private static final int OFFSET_TARGET_TOKEN = TARGET_SECURITY_TOKEN_AUTH_SCHEME.length(); - - private final ControllerManagement controllerManagement; - - /** - * Constructor. - * - * @param tenantConfigurationManagement the tenant management service to retrieve configuration - * properties - * @param controllerManagement the controller management to retrieve the specific target - * security token to verify - * @param tenantAware the tenant aware service to get configuration for the specific - * tenant - * @param systemSecurityContext the system security context to get access to tenant - * configuration - */ - public ControllerPreAuthenticatedSecurityTokenFilter( - final TenantConfigurationManagement tenantConfigurationManagement, - final ControllerManagement controllerManagement, final TenantAware tenantAware, - final SystemSecurityContext systemSecurityContext) { - super(tenantConfigurationManagement, tenantAware, systemSecurityContext); - this.controllerManagement = controllerManagement; - } - - @Override - public HeaderAuthentication getPreAuthenticatedPrincipal(final ControllerSecurityToken securityToken) { - final String controllerId = resolveControllerId(securityToken); - final String authHeader = securityToken.getHeader(ControllerSecurityToken.AUTHORIZATION_HEADER); - if ((authHeader != null) && authHeader.startsWith(TARGET_SECURITY_TOKEN_AUTH_SCHEME)) { - log.debug("found authorization header with scheme {} using target security token for authentication", - TARGET_SECURITY_TOKEN_AUTH_SCHEME); - return new HeaderAuthentication(controllerId, authHeader.substring(OFFSET_TARGET_TOKEN)); - } - log.debug( - "security token filter is enabled but requst does not contain either the necessary path variables {} or the authorization header with scheme {}", - securityToken, TARGET_SECURITY_TOKEN_AUTH_SCHEME); - return null; - } - - @Override - public HeaderAuthentication getPreAuthenticatedCredentials(final ControllerSecurityToken securityToken) { - final Optional target = systemSecurityContext.runAsSystemAsTenant(() -> { - if (securityToken.getTargetId() != null) { - return controllerManagement.get(securityToken.getTargetId()); - } - return controllerManagement.getByControllerId(securityToken.getControllerId()); - }, securityToken.getTenant()); - - return target.map(t -> new HeaderAuthentication( - t.getControllerId(), systemSecurityContext.runAsSystemAsTenant(t::getSecurityToken, securityToken.getTenant()))) - .orElse(null); - } - - @Override - protected String getTenantConfigurationKey() { - return TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED; - } - - private String resolveControllerId(final ControllerSecurityToken securityToken) { - if (securityToken.getControllerId() != null) { - return securityToken.getControllerId(); - } - final Optional foundTarget = systemSecurityContext.runAsSystemAsTenant( - () -> controllerManagement.get(securityToken.getTargetId()), securityToken.getTenant()); - return foundTarget.map(Target::getControllerId).orElse(null); - } -} diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/HeaderAuthentication.java b/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/HeaderAuthentication.java deleted file mode 100644 index 91ccf5fb0..000000000 --- a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/HeaderAuthentication.java +++ /dev/null @@ -1,73 +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.security.controller; - -/** - * The authentication principal and credentials object which holds the - * controller-id and the authority name from the http-headers as principal or - * from the http-url and tenant configuration for the credentials. - */ -final class HeaderAuthentication { - - private final String controllerId; - private final String headerAuth; - - HeaderAuthentication(final String controllerId, final String headerAuth) { - this.controllerId = controllerId; - this.headerAuth = headerAuth; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((controllerId == null) ? 0 : controllerId.hashCode()); - result = prime * result + ((headerAuth == null) ? 0 : headerAuth.hashCode()); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final HeaderAuthentication other = (HeaderAuthentication) obj; - if (controllerId == null) { - if (other.controllerId != null) { - return false; - } - } else if (!controllerId.equals(other.controllerId)) { - return false; - } - if (headerAuth == null) { - if (other.headerAuth != null) { - return false; - } - } else if (!headerAuth.equals(other.headerAuth)) { - return false; - } - return true; - } - - @Override - public String toString() { - // only the controller ID because the principal is stored as string for - // audit information - // etc. - return controllerId; - } - -} diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/PreAuthTokenSourceTrustAuthenticationProvider.java b/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/PreAuthTokenSourceTrustAuthenticationProvider.java deleted file mode 100644 index d71ad6c82..000000000 --- a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/PreAuthTokenSourceTrustAuthenticationProvider.java +++ /dev/null @@ -1,173 +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.security.controller; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; - -/** - * An spring authentication provider which supports authentication tokens of - * type {@link PreAuthenticatedAuthenticationToken} created by the - * {@link ControllerPreAuthenticatedSecurityHeaderFilter}. - * - * Additionally to the authentication token providing the principal and the - * credentials which must be match, this authentication provider can also check - * the remote IP address of the request. - * - * E.g. The request path is /controller/v1/{controllerId} then the controllerId - * in the path is the principal. The credentials are the extracted information - * from e.g. a certificate provided by an reverse proxy. Due this request is - * only allowed from a specific source address this authentication manager can - * also check the remote IP address of the request. - */ -@Slf4j -public class PreAuthTokenSourceTrustAuthenticationProvider implements AuthenticationProvider { - - private final List authorizedSourceIps; - - /** - * Creates a new PreAuthTokenSourceTrustAuthenticationProvider without - * source IPs, which disables the source IP check. - */ - public PreAuthTokenSourceTrustAuthenticationProvider() { - authorizedSourceIps = null; - } - - /** - * Creates a new PreAuthTokenSourceTrustAuthenticationProvider with given - * source IP addresses which are trusted and should be checked against the - * request remote IP address. - * - * @param authorizedSourceIps a list of IP addresses. - */ - public PreAuthTokenSourceTrustAuthenticationProvider(final List authorizedSourceIps) { - this.authorizedSourceIps = authorizedSourceIps; - } - - /** - * Creates a new PreAuthTokenSourceTrustAuthenticationProvider with given - * source IP addresses which are trusted and should be checked against the - * request remote IP address. - * - * @param authorizedSourceIps a list of IP addresses. - */ - public PreAuthTokenSourceTrustAuthenticationProvider(final String... authorizedSourceIps) { - this.authorizedSourceIps = new ArrayList<>(); - this.authorizedSourceIps.addAll(Arrays.asList(authorizedSourceIps)); - } - - @Override - public Authentication authenticate(final Authentication authentication) { - if (!supports(authentication.getClass())) { - return null; - } - - final PreAuthenticatedAuthenticationToken token = (PreAuthenticatedAuthenticationToken) authentication; - final Object credentials = token.getCredentials(); - final Object principal = token.getPrincipal(); - final Object tokenDetails = token.getDetails(); - final Collection authorities = token.getAuthorities(); - - if (principal == null) { - throw new BadCredentialsException("The provided principal and credentials are not match"); - } - - final boolean successAuthentication = calculateAuthenticationSuccess(principal, credentials, tokenDetails); - - if (successAuthentication) { - final PreAuthenticatedAuthenticationToken successToken = new PreAuthenticatedAuthenticationToken(principal, - credentials, authorities); - successToken.setDetails(tokenDetails); - return successToken; - } - - throw new BadCredentialsException("The provided principal and credentials are not match"); - } - - @Override - public boolean supports(final Class authentication) { - return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * The credentials may either be of type HeaderAuthentication or of type - * Collection depending on the authentication mode in - * use (the latter is used in case of trusted reverse-proxy). It is checked - * whether principal equals credentials (respectively if credentials - * contains principal in case of collection) because we want to check if - * e.g. controllerId containing in the URL equals the controllerId in the - * special header set by the reverse-proxy which extracted the CN from the - * certificate. - * - * @param principal the {@link HeaderAuthentication} from the header - * @param credentials a single {@link HeaderAuthentication} or a Collection of - * HeaderAuthentication - * @param tokenDetails authentication details - * @return true if authentication succeeded, otherwise - * false - */ - private boolean calculateAuthenticationSuccess(final Object principal, final Object credentials, - final Object tokenDetails) { - boolean successAuthentication = false; - if (credentials instanceof Collection) { - final Collection multiValueCredentials = (Collection) credentials; - if (multiValueCredentials.contains(principal)) { - successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails); - } - } else if (principal.equals(credentials)) { - successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails); - } - - return successAuthentication; - } - - private boolean checkSourceIPAddressIfNeccessary(final Object tokenDetails) { - boolean success = authorizedSourceIps == null; - String remoteAddress = null; - // controllerIds in URL path and request header are the same but is the - // request coming - // from a trustful source, like the reverse proxy. - if (authorizedSourceIps != null) { - if (tokenDetails instanceof TenantAwareWebAuthenticationDetails tenantAwareWebAuthenticationDetails) { - remoteAddress = tenantAwareWebAuthenticationDetails.getRemoteAddress(); - if (authorizedSourceIps.contains(remoteAddress)) { - // source ip matches the given pattern -> authenticated - success = true; - } - } else { - // is not of type WebAuthenticationDetails, then we cannot - // determine the remote address! - log.error( - "Cannot determine the controller remote-ip-address based on the given authentication token - {} , token details are not TenantAwareWebAuthenticationDetails! ", - tokenDetails); - success = false; - } - } - - if (!success) { - throw new InsufficientAuthenticationException("The remote source IP address " + remoteAddress - + " is not in the list of trusted IP addresses " + authorizedSourceIps); - } - - // no trusted IP check, because no authorizedSourceIPs configuration - return true; - } - -} diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/PreAuthenticationFilter.java b/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/PreAuthenticationFilter.java deleted file mode 100644 index 152af7026..000000000 --- a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/PreAuthenticationFilter.java +++ /dev/null @@ -1,58 +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.security.controller; - -import java.util.Collection; -import java.util.Collections; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; - -/** - * Interface for Pre Authentication. - */ -public interface PreAuthenticationFilter { - - /** - * Check if the filter is enabled. - * - * @param securityToken the secruity info - * @return true is enabled false diabled - */ - boolean isEnable(ControllerSecurityToken securityToken); - - /** - * Extract the principal information from the current securityToken. - * - * @param securityToken the securityToken - * @return the extracted tenant and controller id - */ - HeaderAuthentication getPreAuthenticatedPrincipal(ControllerSecurityToken securityToken); - - /** - * Extract the principal credentials from the current securityToken. - * - * @param securityToken the securityToken - * @return the extracted tenant and controller id - */ - Object getPreAuthenticatedCredentials(ControllerSecurityToken securityToken); - - /** - * Allows to add additional authorities to the successful authenticated token. - * - * @return the authorities granted to the principal, or an empty collection if - * the token has not been authenticated. Never null. - * @see Authentication#getAuthorities() - */ - default Collection getSuccessfulAuthenticationAuthorities() { - return Collections.emptyList(); - } - -} diff --git a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/TenantAwareWebAuthenticationDetails.java b/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/TenantAwareWebAuthenticationDetails.java deleted file mode 100644 index 0687d2b27..000000000 --- a/hawkbit-security/hawkbit-security-controller/src/main/java/org/eclipse/hawkbit/security/controller/TenantAwareWebAuthenticationDetails.java +++ /dev/null @@ -1,50 +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.security.controller; - -import java.io.Serial; - -import jakarta.servlet.http.HttpServletRequest; - -import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails; - -/** - * Extends the {@link TenantAwareAuthenticationDetails} to web information to - * retrieve also e.g. the remoteAddress of the {@link HttpServletRequest} when - * authenticating the requested controller e.g. based on the security header and - * trusted IP address we need the remote address of the http request to verify - * the e.g. the reverse proxy is trusted and allowed to set the header. - */ -public class TenantAwareWebAuthenticationDetails extends TenantAwareAuthenticationDetails { - - @Serial - private static final long serialVersionUID = 1L; - - private final String remoteAddress; - - /** - * @param tenant the current tenant - * @param remoteAddress the remote address of this web request - * @param controller {@code true} indicates this is an controller HTTP request - * otherwise {@code false}. - */ - public TenantAwareWebAuthenticationDetails(final String tenant, final String remoteAddress, - final boolean controller) { - super(tenant, controller); - this.remoteAddress = remoteAddress; - } - - /** - * @return the remoteAddress - */ - public String getRemoteAddress() { - return remoteAddress; - } -} diff --git a/hawkbit-security/pom.xml b/hawkbit-security/pom.xml deleted file mode 100644 index aff058c70..000000000 --- a/hawkbit-security/pom.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - 4.0.0 - - org.eclipse.hawkbit - hawkbit-parent - ${revision} - - - hawkbit-security-parent - hawkBit :: Security :: Parent - pom - - - hawkbit-security-core - hawkbit-security-controller - - \ No newline at end of file diff --git a/hawkbit-test-report/pom.xml b/hawkbit-test-report/pom.xml index 3eeb7a19e..65acd4810 100644 --- a/hawkbit-test-report/pom.xml +++ b/hawkbit-test-report/pom.xml @@ -34,7 +34,7 @@ org.eclipse.hawkbit - hawkbit-security-controller + hawkbit-ddi-security ${project.version} diff --git a/pom.xml b/pom.xml index 3b6fcbff5..7fb15ffbd 100644 --- a/pom.xml +++ b/pom.xml @@ -132,10 +132,10 @@ hawkbit-core - hawkbit-rest-core - hawkbit-security + hawkbit-security-core hawkbit-artifact hawkbit-repository + hawkbit-rest-core hawkbit-autoconfigure hawkbit-mgmt @@ -144,7 +144,6 @@ hawkbit-monolith hawkbit-simple-ui - hawkbit-sdk hawkbit-test-report