Cleanup and improve the controller authentication (#2287)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -14,11 +14,11 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-security-parent</artifactId>
|
<artifactId>hawkbit-ddi-parent</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hawkbit-security-controller</artifactId>
|
<artifactId>hawkbit-ddi-security</artifactId>
|
||||||
<name>hawkBit :: Security :: Controller</name>
|
<name>hawkBit :: Security :: Controller</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -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<String> 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<String, String> 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<String, String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Boolean> 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<GrantedAuthority> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
* <p>
|
||||||
|
* {@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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String> 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
* <p>
|
||||||
|
* {@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String> CONFIG_VALUE_GW_TOKEN = TenantConfigurationValue
|
||||||
|
.<String> builder().value(GATEWAY_TOKEN).build();
|
||||||
|
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_ENABLED = TenantConfigurationValue
|
||||||
|
.<Boolean> builder().value(true).build();
|
||||||
|
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_DISABLED = TenantConfigurationValue
|
||||||
|
.<Boolean> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,6 @@ package org.eclipse.hawkbit.security.controller;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import io.qameta.allure.Description;
|
import io.qameta.allure.Description;
|
||||||
import io.qameta.allure.Feature;
|
import io.qameta.allure.Feature;
|
||||||
import io.qameta.allure.Story;
|
import io.qameta.allure.Story;
|
||||||
@@ -31,9 +29,9 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
@Feature("Unit Tests - Security")
|
@Feature("Unit Tests - Security")
|
||||||
@Story("Issuer hash based authentication")
|
@Story("Security header authenticator")
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
class SecurityHeaderAuthenticatorTest {
|
||||||
|
|
||||||
private static final String CA_COMMON_NAME = "ca-cn";
|
private static final String CA_COMMON_NAME = "ca-cn";
|
||||||
private static final String CA_COMMON_NAME_VALUE = "box1";
|
private static final String CA_COMMON_NAME_VALUE = "box1";
|
||||||
@@ -49,11 +47,14 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
|||||||
|
|
||||||
private static final TenantConfigurationValue<String> CONFIG_VALUE_SINGLE_HASH = TenantConfigurationValue
|
private static final TenantConfigurationValue<String> CONFIG_VALUE_SINGLE_HASH = TenantConfigurationValue
|
||||||
.<String> builder().value(SINGLE_HASH).build();
|
.<String> builder().value(SINGLE_HASH).build();
|
||||||
|
|
||||||
private static final TenantConfigurationValue<String> CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue
|
private static final TenantConfigurationValue<String> CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue
|
||||||
.<String> builder().value(MULTI_HASH).build();
|
.<String> builder().value(MULTI_HASH).build();
|
||||||
|
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_ENABLED = TenantConfigurationValue
|
||||||
|
.<Boolean> builder().value(true).build();
|
||||||
|
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_DISABLED = TenantConfigurationValue
|
||||||
|
.<Boolean> builder().value(false).build();
|
||||||
|
|
||||||
private ControllerPreAuthenticatedSecurityHeaderFilter underTest;
|
private Authenticator authenticator;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private TenantConfigurationManagement tenantConfigurationManagementMock;
|
private TenantConfigurationManagement tenantConfigurationManagementMock;
|
||||||
@@ -65,72 +66,88 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
void before() {
|
void before() {
|
||||||
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
|
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
|
||||||
underTest = new ControllerPreAuthenticatedSecurityHeaderFilter(
|
authenticator = new SecurityHeaderAuthenticator(
|
||||||
CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d",
|
tenantConfigurationManagementMock, tenantAware,
|
||||||
tenantConfigurationManagementMock, tenantAware, new SystemSecurityContext(tenantAware));
|
new SystemSecurityContext(tenantAware), CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Description("Tests the filter for issuer hash based authentication with a single known hash")
|
@Description("Tests successful authentication with multiple a single hashes")
|
||||||
void testIssuerHashBasedAuthenticationWithSingleKnownHash() {
|
void testWithSingleKnownHash() {
|
||||||
final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH);
|
final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH);
|
||||||
// use single known hash
|
|
||||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||||
.thenReturn(CONFIG_VALUE_SINGLE_HASH);
|
.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
|
@Test
|
||||||
@Description("Tests the filter for issuer hash based authentication with multiple known hashes")
|
@Description("Tests successful authentication with multiple hashes")
|
||||||
void testIssuerHashBasedAuthenticationWithMultipleKnownHashes() {
|
void testWithMultipleKnownHashes() {
|
||||||
// use multiple known hashes
|
|
||||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||||
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
||||||
assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(SINGLE_HASH))).isNotNull();
|
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||||
assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(SECOND_HASH))).isNotNull();
|
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||||
assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(THIRD_HASH))).isNotNull();
|
.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
|
@Test
|
||||||
@Description("Tests the filter for issuer hash based authentication with unknown hash")
|
@Description("Tests that if the hash is unknown, the authentication fails")
|
||||||
void testIssuerHashBasedAuthenticationWithUnknownHash() {
|
void testWithUnknownHash() {
|
||||||
final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_HASH);
|
final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_HASH);
|
||||||
// use single known hash
|
|
||||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||||
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
.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
|
@Test
|
||||||
@Description("Tests different values for issuer hash header and inspects the credentials")
|
@Description("Tests that if CN doesn't match the CN in the security token, the authentication fails")
|
||||||
void useDifferentValuesForIssuerHashHeader() {
|
void testWithNonMatchingCN() {
|
||||||
final ControllerSecurityToken securityToken1 = prepareSecurityToken(SINGLE_HASH);
|
final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", "otherControllerID");
|
||||||
final ControllerSecurityToken securityToken2 = prepareSecurityToken(SECOND_HASH);
|
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);
|
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||||
final HeaderAuthentication expected2 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SECOND_HASH);
|
}
|
||||||
|
|
||||||
|
@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(
|
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||||
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
.thenReturn(CONFIG_VALUE_DISABLED);
|
||||||
|
|
||||||
final Collection<HeaderAuthentication> credentials1 = (Collection<HeaderAuthentication>) underTest
|
|
||||||
.getPreAuthenticatedCredentials(securityToken1);
|
|
||||||
final Collection<HeaderAuthentication> credentials2 = (Collection<HeaderAuthentication>) 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);
|
|
||||||
|
|
||||||
|
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ControllerSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) {
|
private static ControllerSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) {
|
||||||
@@ -139,5 +156,4 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
|||||||
securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue);
|
securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue);
|
||||||
return securityToken;
|
return securityToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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<String> CONFIG_VALUE_GW_TOKEN = TenantConfigurationValue
|
||||||
|
.<String> builder().value(SECURITY_TOKEN).build();
|
||||||
|
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_ENABLED = TenantConfigurationValue
|
||||||
|
.<Boolean> builder().value(true).build();
|
||||||
|
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_DISABLED = TenantConfigurationValue
|
||||||
|
.<Boolean> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<!-- hawkBit - START -->
|
<!-- hawkBit - START -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-security-controller</artifactId>
|
<artifactId>hawkbit-ddi-security</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ package org.eclipse.hawkbit.autoconfigure.ddi;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.ddi.rest.api.DdiRestConstants;
|
||||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
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.HawkbitSecurityProperties;
|
||||||
import org.eclipse.hawkbit.security.MdcHandler;
|
import org.eclipse.hawkbit.security.MdcHandler;
|
||||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
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.TenantAware;
|
||||||
|
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||||
|
import org.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
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.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.http.HttpStatus;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security configuration for the hawkBit server DDI download interface.
|
* Security configuration for the hawkBit server DDI download interface.
|
||||||
@@ -89,8 +94,6 @@ class ControllerDownloadSecurityConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@Order(300) // higher priority than HawkBit DDI security, so that the DDI DL security is applied first
|
@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 {
|
protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception {
|
||||||
final AuthenticationManager authenticationManager = ControllerSecurityConfiguration.setAuthenticationManager(http, ddiSecurityConfiguration);
|
|
||||||
|
|
||||||
http
|
http
|
||||||
.securityMatcher(DDI_DL_ANT_MATCHER)
|
.securityMatcher(DDI_DL_ANT_MATCHER)
|
||||||
.csrf(AbstractHttpConfigurer::disable);
|
.csrf(AbstractHttpConfigurer::disable);
|
||||||
@@ -99,34 +102,27 @@ class ControllerDownloadSecurityConfiguration {
|
|||||||
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
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
|
http
|
||||||
.authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated())
|
.authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated())
|
||||||
.anonymous(AbstractHttpConfigurer::disable)
|
.anonymous(AbstractHttpConfigurer::disable)
|
||||||
.addFilter(securityHeaderFilter)
|
.addFilterBefore(new AuthenticationFilters.SecurityHeaderAuthenticationFilter(
|
||||||
.addFilter(securityTokenFilter)
|
new SecurityHeaderAuthenticator(
|
||||||
.addFilter(gatewaySecurityTokenFilter)
|
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(
|
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(
|
||||||
(request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value())))
|
(request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value())))
|
||||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
@@ -135,4 +131,29 @@ class ControllerDownloadSecurityConfiguration {
|
|||||||
|
|
||||||
return http.build();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,10 +12,6 @@ package org.eclipse.hawkbit.autoconfigure.ddi;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.ddi.rest.api.DdiRestConstants;
|
||||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
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.HawkbitSecurityProperties;
|
||||||
import org.eclipse.hawkbit.security.MdcHandler;
|
import org.eclipse.hawkbit.security.MdcHandler;
|
||||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
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.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
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.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.http.HttpStatus;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security configuration for the hawkBit server DDI interface.
|
* Security configuration for the hawkBit server DDI interface.
|
||||||
@@ -93,8 +91,6 @@ class ControllerSecurityConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
@Order(301)
|
@Order(301)
|
||||||
protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception {
|
protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception {
|
||||||
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
|
|
||||||
|
|
||||||
http
|
http
|
||||||
.securityMatcher(DDI_ANT_MATCHERS)
|
.securityMatcher(DDI_ANT_MATCHERS)
|
||||||
.csrf(AbstractHttpConfigurer::disable);
|
.csrf(AbstractHttpConfigurer::disable);
|
||||||
@@ -103,34 +99,22 @@ class ControllerSecurityConfiguration {
|
|||||||
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
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
|
http
|
||||||
.authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated())
|
.authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated())
|
||||||
.anonymous(AbstractHttpConfigurer::disable)
|
.anonymous(AbstractHttpConfigurer::disable)
|
||||||
.addFilter(securityHeaderFilter)
|
.addFilterBefore(new AuthenticationFilters.SecurityHeaderAuthenticationFilter(
|
||||||
.addFilter(securityTokenFilter)
|
new SecurityHeaderAuthenticator(
|
||||||
.addFilter(gatewaySecurityTokenFilter)
|
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(
|
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(
|
||||||
(request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value())))
|
(request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value())))
|
||||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
@@ -139,17 +123,4 @@ class ControllerSecurityConfiguration {
|
|||||||
|
|
||||||
return http.build();
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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<GrantedAuthority> 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<String, String> 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<String, String> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<HttpServletRequest, TenantAwareAuthenticationDetails> {
|
|
||||||
|
|
||||||
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<String, String> 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>hawkbit-ddi-api</module>
|
<module>hawkbit-ddi-api</module>
|
||||||
<module>hawkbit-ddi-resource</module>
|
<module>hawkbit-ddi-resource</module>
|
||||||
|
<module>hawkbit-ddi-security</module>
|
||||||
<module>hawkbit-ddi-starter</module>
|
<module>hawkbit-ddi-starter</module>
|
||||||
<module>hawkbit-ddi-server</module>
|
<module>hawkbit-ddi-server</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-security-controller</artifactId>
|
<artifactId>hawkbit-security-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<!-- hawkBit - START -->
|
<!-- hawkBit - START -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-security-controller</artifactId>
|
<artifactId>hawkbit-security-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-security-parent</artifactId>
|
<artifactId>hawkbit-parent</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@@ -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<GrantedAuthority> getSuccessfulAuthenticationAuthorities() {
|
|
||||||
return Arrays.asList(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract String getTenantConfigurationKey();
|
|
||||||
|
|
||||||
private final class SecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner<Boolean> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Boolean run() {
|
|
||||||
|
|
||||||
log.trace("retrieving configuration value for configuration key {}", getTenantConfigurationKey());
|
|
||||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
|
|
||||||
.getConfigurationValue(getTenantConfigurationKey(), Boolean.class).getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<String> {
|
|
||||||
|
|
||||||
@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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<String> 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<String> 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<String> 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<String> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String run() {
|
|
||||||
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(
|
|
||||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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> 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<Target> foundTarget = systemSecurityContext.runAsSystemAsTenant(
|
|
||||||
() -> controllerManagement.get(securityToken.getTargetId()), securityToken.getTenant());
|
|
||||||
return foundTarget.map(Target::getControllerId).orElse(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<String> 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<String> 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<GrantedAuthority> 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<HeaderAuthentication> 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 <code>true</code> if authentication succeeded, otherwise
|
|
||||||
* <code>false</code>
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 <code>true</code> is enabled <code>false</code> 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<GrantedAuthority> getSuccessfulAuthenticationAuthorities() {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<!--
|
|
||||||
|
|
||||||
Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
|
||||||
|
|
||||||
This program and the accompanying materials are made
|
|
||||||
available under the terms of the Eclipse Public License 2.0
|
|
||||||
which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
|
|
||||||
SPDX-License-Identifier: EPL-2.0
|
|
||||||
|
|
||||||
-->
|
|
||||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
|
||||||
<artifactId>hawkbit-parent</artifactId>
|
|
||||||
<version>${revision}</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>hawkbit-security-parent</artifactId>
|
|
||||||
<name>hawkBit :: Security :: Parent</name>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<modules>
|
|
||||||
<module>hawkbit-security-core</module>
|
|
||||||
<module>hawkbit-security-controller</module>
|
|
||||||
</modules>
|
|
||||||
</project>
|
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.hawkbit</groupId>
|
<groupId>org.eclipse.hawkbit</groupId>
|
||||||
<artifactId>hawkbit-security-controller</artifactId>
|
<artifactId>hawkbit-ddi-security</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
5
pom.xml
5
pom.xml
@@ -132,10 +132,10 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>hawkbit-core</module>
|
<module>hawkbit-core</module>
|
||||||
<module>hawkbit-rest-core</module>
|
<module>hawkbit-security-core</module>
|
||||||
<module>hawkbit-security</module>
|
|
||||||
<module>hawkbit-artifact</module>
|
<module>hawkbit-artifact</module>
|
||||||
<module>hawkbit-repository</module>
|
<module>hawkbit-repository</module>
|
||||||
|
<module>hawkbit-rest-core</module>
|
||||||
<module>hawkbit-autoconfigure</module>
|
<module>hawkbit-autoconfigure</module>
|
||||||
|
|
||||||
<module>hawkbit-mgmt</module>
|
<module>hawkbit-mgmt</module>
|
||||||
@@ -144,7 +144,6 @@
|
|||||||
<module>hawkbit-monolith</module>
|
<module>hawkbit-monolith</module>
|
||||||
|
|
||||||
<module>hawkbit-simple-ui</module>
|
<module>hawkbit-simple-ui</module>
|
||||||
|
|
||||||
<module>hawkbit-sdk</module>
|
<module>hawkbit-sdk</module>
|
||||||
|
|
||||||
<module>hawkbit-test-report</module>
|
<module>hawkbit-test-report</module>
|
||||||
|
|||||||
Reference in New Issue
Block a user