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>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-security-parent</artifactId>
|
||||
<artifactId>hawkbit-ddi-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hawkbit-security-controller</artifactId>
|
||||
<artifactId>hawkbit-ddi-security</artifactId>
|
||||
<name>hawkBit :: Security :: Controller</name>
|
||||
|
||||
<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.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
@@ -31,9 +29,9 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@Feature("Unit Tests - Security")
|
||||
@Story("Issuer hash based authentication")
|
||||
@Story("Security header authenticator")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
||||
class SecurityHeaderAuthenticatorTest {
|
||||
|
||||
private static final String CA_COMMON_NAME = "ca-cn";
|
||||
private static final String CA_COMMON_NAME_VALUE = "box1";
|
||||
@@ -49,11 +47,14 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
||||
|
||||
private static final TenantConfigurationValue<String> CONFIG_VALUE_SINGLE_HASH = TenantConfigurationValue
|
||||
.<String> builder().value(SINGLE_HASH).build();
|
||||
|
||||
private static final TenantConfigurationValue<String> CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue
|
||||
.<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
|
||||
private TenantConfigurationManagement tenantConfigurationManagementMock;
|
||||
@@ -65,72 +66,88 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
||||
@BeforeEach
|
||||
void before() {
|
||||
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
|
||||
underTest = new ControllerPreAuthenticatedSecurityHeaderFilter(
|
||||
CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d",
|
||||
tenantConfigurationManagementMock, tenantAware, new SystemSecurityContext(tenantAware));
|
||||
authenticator = new SecurityHeaderAuthenticator(
|
||||
tenantConfigurationManagementMock, tenantAware,
|
||||
new SystemSecurityContext(tenantAware), CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the filter for issuer hash based authentication with a single known hash")
|
||||
void testIssuerHashBasedAuthenticationWithSingleKnownHash() {
|
||||
@Description("Tests successful authentication with multiple a single hashes")
|
||||
void testWithSingleKnownHash() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH);
|
||||
// use single known hash
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||
.thenReturn(CONFIG_VALUE_SINGLE_HASH);
|
||||
assertThat(underTest.getPreAuthenticatedPrincipal(securityToken)).isNotNull();
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken))
|
||||
.isNotNull()
|
||||
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the filter for issuer hash based authentication with multiple known hashes")
|
||||
void testIssuerHashBasedAuthenticationWithMultipleKnownHashes() {
|
||||
// use multiple known hashes
|
||||
@Description("Tests successful authentication with multiple hashes")
|
||||
void testWithMultipleKnownHashes() {
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
||||
assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(SINGLE_HASH))).isNotNull();
|
||||
assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(SECOND_HASH))).isNotNull();
|
||||
assertThat(underTest.getPreAuthenticatedPrincipal(prepareSecurityToken(THIRD_HASH))).isNotNull();
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(prepareSecurityToken(SINGLE_HASH)))
|
||||
.isNotNull()
|
||||
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
|
||||
assertThat(authenticator.authenticate(prepareSecurityToken(SECOND_HASH)))
|
||||
.isNotNull()
|
||||
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
|
||||
assertThat(authenticator.authenticate(prepareSecurityToken(THIRD_HASH)))
|
||||
.isNotNull()
|
||||
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the filter for issuer hash based authentication with unknown hash")
|
||||
void testIssuerHashBasedAuthenticationWithUnknownHash() {
|
||||
@Description("Tests that if the hash is unknown, the authentication fails")
|
||||
void testWithUnknownHash() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_HASH);
|
||||
// use single known hash
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
||||
assertThat(underTest.getPreAuthenticatedPrincipal(securityToken)).isNull();
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_ENABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests different values for issuer hash header and inspects the credentials")
|
||||
void useDifferentValuesForIssuerHashHeader() {
|
||||
final ControllerSecurityToken securityToken1 = prepareSecurityToken(SINGLE_HASH);
|
||||
final ControllerSecurityToken securityToken2 = prepareSecurityToken(SECOND_HASH);
|
||||
@Description("Tests that if CN doesn't match the CN in the security token, the authentication fails")
|
||||
void testWithNonMatchingCN() {
|
||||
final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", "otherControllerID");
|
||||
securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE);
|
||||
securityToken.putHeader(X_SSL_ISSUER_HASH_1, SINGLE_HASH);
|
||||
|
||||
final HeaderAuthentication expected1 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SINGLE_HASH);
|
||||
final HeaderAuthentication expected2 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SECOND_HASH);
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that if the hash miss, the authentication fails")
|
||||
void testWithoutHash() {
|
||||
assertThat(authenticator.authenticate(new ControllerSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests that if disabled, the authentication fails")
|
||||
void testWithSingleKnownHashButDisabled() {
|
||||
final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH);
|
||||
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class))
|
||||
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
||||
|
||||
final Collection<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);
|
||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED, Boolean.class))
|
||||
.thenReturn(CONFIG_VALUE_DISABLED);
|
||||
|
||||
assertThat(authenticator.authenticate(securityToken)).isNull();
|
||||
}
|
||||
|
||||
private static ControllerSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) {
|
||||
@@ -139,5 +156,4 @@ class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
||||
securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue);
|
||||
return securityToken;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-security-controller</artifactId>
|
||||
<artifactId>hawkbit-ddi-security</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -12,10 +12,6 @@ package org.eclipse.hawkbit.autoconfigure.ddi;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.autoconfigure.ddi.security.ControllerTenantAwareAuthenticationDetailsSource;
|
||||
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticateSecurityTokenFilter;
|
||||
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
||||
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
|
||||
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
@@ -25,7 +21,15 @@ import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.MdcHandler;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.security.controller.AuthenticationFilters;
|
||||
import org.eclipse.hawkbit.security.controller.Authenticator;
|
||||
import org.eclipse.hawkbit.security.controller.ControllerSecurityToken;
|
||||
import org.eclipse.hawkbit.security.controller.GatewayTokenAuthenticator;
|
||||
import org.eclipse.hawkbit.security.controller.SecurityHeaderAuthenticator;
|
||||
import org.eclipse.hawkbit.security.controller.SecurityTokenAuthenticator;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
@@ -33,11 +37,12 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
|
||||
/**
|
||||
* Security configuration for the hawkBit server DDI download interface.
|
||||
@@ -89,8 +94,6 @@ class ControllerDownloadSecurityConfiguration {
|
||||
@Bean
|
||||
@Order(300) // higher priority than HawkBit DDI security, so that the DDI DL security is applied first
|
||||
protected SecurityFilterChain filterChainDDIDL(final HttpSecurity http) throws Exception {
|
||||
final AuthenticationManager authenticationManager = ControllerSecurityConfiguration.setAuthenticationManager(http, ddiSecurityConfiguration);
|
||||
|
||||
http
|
||||
.securityMatcher(DDI_DL_ANT_MATCHER)
|
||||
.csrf(AbstractHttpConfigurer::disable);
|
||||
@@ -99,34 +102,27 @@ class ControllerDownloadSecurityConfiguration {
|
||||
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
||||
}
|
||||
|
||||
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
||||
|
||||
final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter(
|
||||
ddiSecurityConfiguration.getRp().getCnHeader(),
|
||||
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement,
|
||||
tenantAware, systemSecurityContext);
|
||||
securityHeaderFilter.setAuthenticationManager(authenticationManager);
|
||||
securityHeaderFilter.setCheckForPrincipalChanges(true);
|
||||
securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||
|
||||
final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter(
|
||||
tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext);
|
||||
securityTokenFilter.setAuthenticationManager(authenticationManager);
|
||||
securityTokenFilter.setCheckForPrincipalChanges(true);
|
||||
securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||
|
||||
final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager);
|
||||
gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true);
|
||||
gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||
|
||||
http
|
||||
.authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated())
|
||||
.anonymous(AbstractHttpConfigurer::disable)
|
||||
.addFilter(securityHeaderFilter)
|
||||
.addFilter(securityTokenFilter)
|
||||
.addFilter(gatewaySecurityTokenFilter)
|
||||
.addFilterBefore(new AuthenticationFilters.SecurityHeaderAuthenticationFilter(
|
||||
new SecurityHeaderAuthenticator(
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext,
|
||||
ddiSecurityConfiguration.getRp().getCnHeader(), ddiSecurityConfiguration.getRp().getSslIssuerHashHeader()),
|
||||
ddiSecurityConfiguration), AuthorizationFilter.class)
|
||||
.addFilterBefore(new AuthenticationFilters.SecurityTokenAuthenticationFilter(
|
||||
new SecurityTokenAuthenticator(
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext,
|
||||
controllerManagement),
|
||||
ddiSecurityConfiguration), AuthorizationFilter.class)
|
||||
.addFilterBefore(new AuthenticationFilters.GatewayTokenAuthenticationFilter(
|
||||
new GatewayTokenAuthenticator(
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext),
|
||||
ddiSecurityConfiguration), AuthorizationFilter.class)
|
||||
.addFilterBefore(new AuthenticationFilters.AbstractAuthenticationFilter(
|
||||
new AnonymousAuthenticator(
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext),
|
||||
ddiSecurityConfiguration) {}, AuthorizationFilter.class)
|
||||
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(
|
||||
(request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value())))
|
||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||
@@ -135,4 +131,29 @@ class ControllerDownloadSecurityConfiguration {
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
private static class AnonymousAuthenticator extends Authenticator.AbstractAuthenticator {
|
||||
|
||||
protected AnonymousAuthenticator(
|
||||
final TenantConfigurationManagement tenantConfigurationManagement,
|
||||
final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) {
|
||||
super(tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(final ControllerSecurityToken controllerSecurityToken) {
|
||||
return isEnabled(controllerSecurityToken) ? authenticatedController(controllerSecurityToken.getTenant(), null) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTenantConfigurationKey() {
|
||||
return TenantConfigurationProperties.TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger log() {
|
||||
return log;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,6 @@ package org.eclipse.hawkbit.autoconfigure.ddi;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.autoconfigure.ddi.security.ControllerTenantAwareAuthenticationDetailsSource;
|
||||
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticateSecurityTokenFilter;
|
||||
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter;
|
||||
import org.eclipse.hawkbit.autoconfigure.ddi.security.HttpControllerPreAuthenticatedSecurityHeaderFilter;
|
||||
import org.eclipse.hawkbit.ddi.rest.api.DdiRestConstants;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
@@ -25,7 +21,10 @@ import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.MdcHandler;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.security.controller.PreAuthTokenSourceTrustAuthenticationProvider;
|
||||
import org.eclipse.hawkbit.security.controller.AuthenticationFilters;
|
||||
import org.eclipse.hawkbit.security.controller.GatewayTokenAuthenticator;
|
||||
import org.eclipse.hawkbit.security.controller.SecurityHeaderAuthenticator;
|
||||
import org.eclipse.hawkbit.security.controller.SecurityTokenAuthenticator;
|
||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -34,13 +33,12 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
|
||||
/**
|
||||
* Security configuration for the hawkBit server DDI interface.
|
||||
@@ -93,8 +91,6 @@ class ControllerSecurityConfiguration {
|
||||
@Bean
|
||||
@Order(301)
|
||||
protected SecurityFilterChain filterChainDDI(final HttpSecurity http) throws Exception {
|
||||
final AuthenticationManager authenticationManager = setAuthenticationManager(http, ddiSecurityConfiguration);
|
||||
|
||||
http
|
||||
.securityMatcher(DDI_ANT_MATCHERS)
|
||||
.csrf(AbstractHttpConfigurer::disable);
|
||||
@@ -103,34 +99,22 @@ class ControllerSecurityConfiguration {
|
||||
http.requiresChannel(crmRegistry -> crmRegistry.anyRequest().requiresSecure());
|
||||
}
|
||||
|
||||
final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource();
|
||||
|
||||
final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter(
|
||||
ddiSecurityConfiguration.getRp().getCnHeader(),
|
||||
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement,
|
||||
tenantAware, systemSecurityContext);
|
||||
securityHeaderFilter.setAuthenticationManager(authenticationManager);
|
||||
securityHeaderFilter.setCheckForPrincipalChanges(true);
|
||||
securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||
|
||||
final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter(
|
||||
tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext);
|
||||
securityTokenFilter.setAuthenticationManager(authenticationManager);
|
||||
securityTokenFilter.setCheckForPrincipalChanges(true);
|
||||
securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||
|
||||
final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(
|
||||
tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||
gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager);
|
||||
gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true);
|
||||
gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
|
||||
|
||||
http
|
||||
.authorizeHttpRequests(amrmRegistry -> amrmRegistry.anyRequest().authenticated())
|
||||
.anonymous(AbstractHttpConfigurer::disable)
|
||||
.addFilter(securityHeaderFilter)
|
||||
.addFilter(securityTokenFilter)
|
||||
.addFilter(gatewaySecurityTokenFilter)
|
||||
.addFilterBefore(new AuthenticationFilters.SecurityHeaderAuthenticationFilter(
|
||||
new SecurityHeaderAuthenticator(
|
||||
tenantConfigurationManagement, tenantAware,
|
||||
systemSecurityContext, ddiSecurityConfiguration.getRp().getCnHeader(), ddiSecurityConfiguration.getRp().getSslIssuerHashHeader()
|
||||
), ddiSecurityConfiguration), AuthorizationFilter.class)
|
||||
.addFilterBefore(new AuthenticationFilters.SecurityTokenAuthenticationFilter(
|
||||
new SecurityTokenAuthenticator(
|
||||
tenantConfigurationManagement, tenantAware,
|
||||
systemSecurityContext, controllerManagement), ddiSecurityConfiguration), AuthorizationFilter.class)
|
||||
.addFilterBefore(new AuthenticationFilters.GatewayTokenAuthenticationFilter(
|
||||
new GatewayTokenAuthenticator(
|
||||
tenantConfigurationManagement, tenantAware,
|
||||
systemSecurityContext), ddiSecurityConfiguration), AuthorizationFilter.class)
|
||||
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(
|
||||
(request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value())))
|
||||
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||
@@ -139,17 +123,4 @@ class ControllerSecurityConfiguration {
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
static AuthenticationManager setAuthenticationManager(final HttpSecurity http, final DdiSecurityProperties ddiSecurityConfiguration)
|
||||
throws Exception {
|
||||
// configure authentication manager
|
||||
final AuthenticationManager authenticationManager =
|
||||
http
|
||||
.getSharedObject(AuthenticationManagerBuilder.class)
|
||||
.authenticationProvider(
|
||||
new PreAuthTokenSourceTrustAuthenticationProvider(ddiSecurityConfiguration.getRp().getTrustedIPs()))
|
||||
.build();
|
||||
http.authenticationManager(authenticationManager);
|
||||
return authenticationManager;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
<module>hawkbit-ddi-api</module>
|
||||
<module>hawkbit-ddi-resource</module>
|
||||
<module>hawkbit-ddi-security</module>
|
||||
<module>hawkbit-ddi-starter</module>
|
||||
<module>hawkbit-ddi-server</module>
|
||||
</modules>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-security-controller</artifactId>
|
||||
<artifactId>hawkbit-security-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<!-- hawkBit - START -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-security-controller</artifactId>
|
||||
<artifactId>hawkbit-security-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-security-parent</artifactId>
|
||||
<artifactId>hawkbit-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</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>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-security-controller</artifactId>
|
||||
<artifactId>hawkbit-ddi-security</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
5
pom.xml
5
pom.xml
@@ -132,10 +132,10 @@
|
||||
|
||||
<modules>
|
||||
<module>hawkbit-core</module>
|
||||
<module>hawkbit-rest-core</module>
|
||||
<module>hawkbit-security</module>
|
||||
<module>hawkbit-security-core</module>
|
||||
<module>hawkbit-artifact</module>
|
||||
<module>hawkbit-repository</module>
|
||||
<module>hawkbit-rest-core</module>
|
||||
<module>hawkbit-autoconfigure</module>
|
||||
|
||||
<module>hawkbit-mgmt</module>
|
||||
@@ -144,7 +144,6 @@
|
||||
<module>hawkbit-monolith</module>
|
||||
|
||||
<module>hawkbit-simple-ui</module>
|
||||
|
||||
<module>hawkbit-sdk</module>
|
||||
|
||||
<module>hawkbit-test-report</module>
|
||||
|
||||
Reference in New Issue
Block a user