Rename hawkbit-security-intenal -> hawkbit-security-controller (#2015)
as it is controller only related * DmfTenantSecurityToken renamed to ControllerSecurityToken - as it is such * hawkbit.security classes from http-security-internal moved to hawkbit.security.controller - as they are such and it is bad practice to have same package in multiple modules _release_notes_ Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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 ControllerPreAuthenticateSecurityTokenFilter 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 ControllerPreAuthenticateSecurityTokenFilter(
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 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.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
|
||||
/**
|
||||
* A pre-authenticated processing filter which add the
|
||||
* {@link SpringEvalExpressions#CONTROLLER_DOWNLOAD_ROLE_ANONYMOUS} to the
|
||||
* security context in case the anonymous download is allowed through
|
||||
* configuration.
|
||||
*/
|
||||
public class ControllerPreAuthenticatedAnonymousDownload extends AbstractControllerAuthenticationFilter {
|
||||
|
||||
/**
|
||||
* 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 ControllerPreAuthenticatedAnonymousDownload(
|
||||
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
|
||||
final SystemSecurityContext systemSecurityContext) {
|
||||
super(tenantConfigurationManagement, tenantAware, systemSecurityContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderAuthentication getPreAuthenticatedPrincipal(final ControllerSecurityToken securityToken) {
|
||||
return new HeaderAuthentication(securityToken.getControllerId(), securityToken.getControllerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderAuthentication getPreAuthenticatedCredentials(final ControllerSecurityToken securityToken) {
|
||||
return new HeaderAuthentication(securityToken.getControllerId(), securityToken.getControllerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTenantConfigurationKey() {
|
||||
return TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
|
||||
/**
|
||||
* An anonymous controller filter which is only enabled in case of anonymous
|
||||
* access is granted. This should only be for development purposes.
|
||||
*
|
||||
* @see org.eclipse.hawkbit.security.DdiSecurityProperties
|
||||
*/
|
||||
public class ControllerPreAuthenticatedAnonymousFilter implements PreAuthenticationFilter {
|
||||
|
||||
private final DdiSecurityProperties ddiSecurityConfiguration;
|
||||
|
||||
/**
|
||||
* @param ddiSecurityConfiguration the security configuration which holds the configuration if
|
||||
* anonymous is enabled or not
|
||||
*/
|
||||
public ControllerPreAuthenticatedAnonymousFilter(final DdiSecurityProperties ddiSecurityConfiguration) {
|
||||
this.ddiSecurityConfiguration = ddiSecurityConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnable(final ControllerSecurityToken securityToken) {
|
||||
return ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderAuthentication getPreAuthenticatedPrincipal(final ControllerSecurityToken securityToken) {
|
||||
return new HeaderAuthentication(securityToken.getControllerId(), securityToken.getControllerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderAuthentication getPreAuthenticatedCredentials(final ControllerSecurityToken securityToken) {
|
||||
return new HeaderAuthentication(securityToken.getControllerId(), securityToken.getControllerId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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).collect(Collectors.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* JSON representation to authenticate a tenant.
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ControllerSecurityToken {
|
||||
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
@JsonProperty
|
||||
private final Long tenantId;
|
||||
@JsonProperty
|
||||
private final Long targetId;
|
||||
@JsonProperty
|
||||
private final String controllerId;
|
||||
@JsonProperty
|
||||
private String tenant;
|
||||
@JsonProperty
|
||||
private Map<String, String> headers;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param tenant the tenant for the security token
|
||||
* @param tenantId alternative tenant identification by technical ID
|
||||
* @param controllerId the ID of the controller for the security token
|
||||
* @param targetId alternative target identification by technical ID
|
||||
*/
|
||||
@JsonCreator
|
||||
public ControllerSecurityToken(
|
||||
@JsonProperty("tenant") final String tenant,
|
||||
@JsonProperty("tenantId") final Long tenantId, @JsonProperty("controllerId") final String controllerId,
|
||||
@JsonProperty("targetId") final Long targetId) {
|
||||
this.tenant = tenant;
|
||||
this.tenantId = tenantId;
|
||||
this.controllerId = controllerId;
|
||||
this.targetId = targetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param tenant the tenant for the security token
|
||||
* @param controllerId the ID of the controller for the security token
|
||||
*/
|
||||
public ControllerSecurityToken(final String tenant, final String controllerId) {
|
||||
this(tenant, null, controllerId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a header value.
|
||||
*
|
||||
* @param name of header
|
||||
* @return the value
|
||||
*/
|
||||
public String getHeader(final String name) {
|
||||
if (headers == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified header value with the specified name.
|
||||
*
|
||||
* @param name of the header
|
||||
* @param value of the header
|
||||
*/
|
||||
public void putHeader(final String name, final String value) {
|
||||
if (headers == null) {
|
||||
headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
headers.put(name, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 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.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<>();
|
||||
for (final String ip : authorizedSourceIps) {
|
||||
this.authorizedSourceIps.add(ip);
|
||||
}
|
||||
}
|
||||
|
||||
@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)) {
|
||||
// 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;
|
||||
} else {
|
||||
remoteAddress = ((TenantAwareWebAuthenticationDetails) tokenDetails).getRemoteAddress();
|
||||
if (authorizedSourceIps.contains(remoteAddress)) {
|
||||
// source ip matches the given pattern -> authenticated
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
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.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;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Feature("Unit Tests - Security")
|
||||
@Story("Exclude path aware shallow ETag filter")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ControllerPreAuthenticatedAnonymousDownloadTest {
|
||||
|
||||
private ControllerPreAuthenticatedAnonymousDownload underTest;
|
||||
|
||||
@Mock
|
||||
private TenantConfigurationManagement tenantConfigurationManagementMock;
|
||||
|
||||
@Mock
|
||||
private TenantAware tenantAwareMock;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
underTest = new ControllerPreAuthenticatedAnonymousDownload(tenantConfigurationManagementMock, tenantAwareMock,
|
||||
new SystemSecurityContext(tenantAwareMock));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useCorrectTenantConfiguationKey() {
|
||||
assertThat(underTest.getTenantConfigurationKey()).as("Should be using the correct tenant configuration key")
|
||||
.isEqualTo(underTest.getTenantConfigurationKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulAuthenticationAdditionalAuthoritiesForDownload() {
|
||||
assertThat(underTest.getSuccessfulAuthenticationAuthorities())
|
||||
.as("Additional authorities should be containing the download anonymous role")
|
||||
.contains(new SimpleGrantedAuthority(SpringEvalExpressions.CONTROLLER_ROLE));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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.Collection;
|
||||
|
||||
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("Issuer hash based authentication")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class ControllerPreAuthenticatedSecurityHeaderFilterTest {
|
||||
|
||||
private static final String CA_COMMON_NAME = "ca-cn";
|
||||
private static final String CA_COMMON_NAME_VALUE = "box1";
|
||||
|
||||
private static final String X_SSL_ISSUER_HASH_1 = "X-Ssl-Issuer-Hash-1";
|
||||
|
||||
private static final String SINGLE_HASH = "hash1";
|
||||
private static final String SECOND_HASH = "hash2";
|
||||
private static final String THIRD_HASH = "hash3";
|
||||
private static final String UNKNOWN_HASH = "unknown";
|
||||
|
||||
private static final String MULTI_HASH = "HASH1;hash2,HASH3,HASH1";
|
||||
|
||||
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 ControllerPreAuthenticatedSecurityHeaderFilter underTest;
|
||||
|
||||
@Mock
|
||||
private TenantConfigurationManagement tenantConfigurationManagementMock;
|
||||
@Mock
|
||||
private UserAuthoritiesResolver authoritiesResolver;
|
||||
@Mock
|
||||
private SecurityContextSerializer securityContextSerializer;
|
||||
|
||||
@BeforeEach
|
||||
public 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));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the filter for issuer hash based authentication with a single known hash")
|
||||
public void testIssuerHashBasedAuthenticationWithSingleKnownHash() {
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the filter for issuer hash based authentication with multiple known hashes")
|
||||
public void testIssuerHashBasedAuthenticationWithMultipleKnownHashes() {
|
||||
// use multiple known hashes
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests the filter for issuer hash based authentication with unknown hash")
|
||||
public void testIssuerHashBasedAuthenticationWithUnknownHash() {
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Tests different values for issuer hash header and inspects the credentials")
|
||||
public void useDifferentValuesForIssuerHashHeader() {
|
||||
final ControllerSecurityToken securityToken1 = prepareSecurityToken(SINGLE_HASH);
|
||||
final ControllerSecurityToken securityToken2 = prepareSecurityToken(SECOND_HASH);
|
||||
|
||||
final HeaderAuthentication expected1 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SINGLE_HASH);
|
||||
final HeaderAuthentication expected2 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SECOND_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);
|
||||
|
||||
}
|
||||
|
||||
private static ControllerSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) {
|
||||
final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE);
|
||||
securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE);
|
||||
securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue);
|
||||
return securityToken;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user