Add support for multiple hashes in Issuer hash based authentication

Signed-off-by: Marcel Mager (INST-IOT/ESB) <Marcel.Mager@bosch-si.com>
This commit is contained in:
Marcel Mager (INST-IOT/ESB)
2016-08-25 09:35:18 +02:00
parent 93d509fbcd
commit 0ccd458585
6 changed files with 160 additions and 18 deletions

View File

@@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory;
/**
* An abstraction for all controller based security. Check if the tenant
* configuration is enabled.
*
*
*
*
*/
@@ -50,7 +50,7 @@ public abstract class AbstractControllerAuthenticationFilter implements PreAuthe
public abstract HeaderAuthentication getPreAuthenticatedPrincipal(TenantSecurityToken secruityToken);
@Override
public abstract HeaderAuthentication getPreAuthenticatedCredentials(TenantSecurityToken secruityToken);
public abstract Object getPreAuthenticatedCredentials(TenantSecurityToken secruityToken);
private final class SecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner<Boolean> {
@Override

View File

@@ -8,6 +8,10 @@
*/
package org.eclipse.hawkbit.security;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.tenancy.TenantAware;
@@ -97,7 +101,7 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
}
@Override
public HeaderAuthentication getPreAuthenticatedCredentials(final TenantSecurityToken secruityToken) {
public Object getPreAuthenticatedCredentials(final TenantSecurityToken secruityToken) {
final String authorityNameConfigurationValue = tenantAware.runAsTenant(secruityToken.getTenant(),
sslIssuerNameConfigTenantRunner);
String controllerId = secruityToken.getControllerId();
@@ -108,7 +112,17 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
controllerId = secruityToken.getHeader(caCommonNameHeader);
}
return new HeaderAuthentication(controllerId, authorityNameConfigurationValue);
String[] knownHashes = splitMultiHash(authorityNameConfigurationValue);
if (knownHashes.length > 1) {
Set<HeaderAuthentication> multiHashes = new HashSet<>();
final String cntlId = controllerId;
Arrays.asList(knownHashes)
.forEach(hashItem -> multiHashes.add(new HeaderAuthentication(cntlId, hashItem)));
return multiHashes;
} else {
return new HeaderAuthentication(controllerId, authorityNameConfigurationValue);
}
}
/**
@@ -117,12 +131,13 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
* It's ok if we find the the hash in any the trusted CA chain to accept
* this request for this tenant.
*/
private String getIssuerHashHeader(final TenantSecurityToken secruityToken, final String knownIssuerHash) {
private String getIssuerHashHeader(final TenantSecurityToken secruityToken, final String knownIssuerHashes) {
// iterate over the headers until we get a null header.
String[] knownHashes = splitMultiHash(knownIssuerHashes);
int iHeader = 1;
String foundHash;
while ((foundHash = secruityToken.getHeader(String.format(sslIssuerHashBasicHeader, iHeader))) != null) {
if (foundHash.equals(knownIssuerHash)) {
if (Arrays.asList(knownHashes).contains(foundHash)) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Found matching ssl issuer hash at position {}", iHeader);
}
@@ -148,4 +163,8 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue());
}
}
private static String[] splitMultiHash(String knownIssuerHashes) {
return knownIssuerHashes.split(";");
}
}

View File

@@ -27,17 +27,17 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA
* 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.
*
*
*
*
*/
@@ -58,7 +58,7 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica
* 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.
*/
@@ -70,7 +70,7 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica
* 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.
*/
@@ -104,6 +104,12 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica
// proxy which extracted the CN from the certificate
if (principal.equals(credentials)) {
successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails);
} else if (Collection.class.isAssignableFrom(credentials.getClass())) {
final Collection<?> multiValueCredentials = (Collection<?>) credentials;
if (multiValueCredentials.contains(principal)) {
successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails);
}
}
if (successAuthentication) {

View File

@@ -22,7 +22,7 @@ public interface PreAuthentificationFilter {
/**
* Check if the filter is enabled.
*
*
* @param secruityToken
* the secruity info
* @return <true> is enabled <false> diabled
@@ -31,7 +31,7 @@ public interface PreAuthentificationFilter {
/**
* Extract the principal information from the current secruityToken.
*
*
* @param secruityToken
* the secruityToken
* @return the extracted tenant and controller id
@@ -40,17 +40,17 @@ public interface PreAuthentificationFilter {
/**
* Extract the principal credentials from the current secruityToken.
*
*
* @param secruityToken
* the secruityToken
* @return the extracted tenant and controller id
*/
HeaderAuthentication getPreAuthenticatedCredentials(TenantSecurityToken secruityToken);
Object getPreAuthenticatedCredentials(TenantSecurityToken secruityToken);
/**
* 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()

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.security;
import static org.junit.Assert.*;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@Features("Unit Tests - Security")
@Stories("Issuer hash based authentication")
@RunWith(MockitoJUnitRunner.class)
public class ControllerPreAuthenticatedSecurityHeaderFilterTest {
private ControllerPreAuthenticatedSecurityHeaderFilter underTest;
@Mock
private TenantConfigurationManagement tenantConfigurationManagementMock;
@Mock
private TenantSecurityToken tenantSecurityTokenMock;
private SecurityContextTenantAware tenantAware = new SecurityContextTenantAware();
private static final String CA_COMMON_NAME = "ca-cn";
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 MULTI_HASH = "hash1;hash2;hash3";
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();
@Before
public void before() {
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() {
// prepare security token
final TenantSecurityToken securityToken = prepareSecurityToken();
securityToken.getHeaders().put(X_SSL_ISSUER_HASH_1, SINGLE_HASH);
// use single known hash
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(String.class)))
.thenReturn(CONFIG_VALUE_SINGLE_HASH);
assertNotNull(underTest.getPreAuthenticatedPrincipal(securityToken));
}
@Test
@Description("Tests the filter for issuer hash based authentication with multiple known hashes")
public void testIssuerHashBasedAuthenticationWithMultipleKnownHashes() {
// prepare security token
final TenantSecurityToken securityToken = prepareSecurityToken();
securityToken.getHeaders().put(X_SSL_ISSUER_HASH_1, SINGLE_HASH);
// use multiple known hashes
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(String.class)))
.thenReturn(CONFIG_VALUE_MULTI_HASH);
assertNotNull(underTest.getPreAuthenticatedPrincipal(securityToken));
}
@Test
@Description("Tests the filter for issuer hash based authentication with unknown hash")
public void testIssuerHashBasedAuthenticationWithUnknownHash() {
// prepare security token
final TenantSecurityToken securityToken = prepareSecurityToken();
securityToken.getHeaders().put(X_SSL_ISSUER_HASH_1, "unknown");
// use single known hash
when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(String.class)))
.thenReturn(CONFIG_VALUE_MULTI_HASH);
assertNull(underTest.getPreAuthenticatedPrincipal(securityToken));
}
private static TenantSecurityToken prepareSecurityToken() {
final TenantSecurityToken securityToken = new TenantSecurityToken("default", "1234",
FileResource.createFileResourceBySha1("12345"));
securityToken.getHeaders().put(CA_COMMON_NAME, "any");
return securityToken;
}
}