diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java index edf2ef034..c45cfb5e4 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java @@ -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 { @Override diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java index 6836b8a31..6fd13a1c6 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java @@ -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 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(";"); + } } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java index b4960737d..c653108a0 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java @@ -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) { diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthentificationFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthentificationFilter.java index 90f2acd50..f0801ff4d 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthentificationFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthentificationFilter.java @@ -22,7 +22,7 @@ public interface PreAuthentificationFilter { /** * Check if the filter is enabled. - * + * * @param secruityToken * the secruity info * @return is enabled 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() diff --git a/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java b/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java new file mode 100644 index 000000000..eb34d5253 --- /dev/null +++ b/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java @@ -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 CONFIG_VALUE_SINGLE_HASH = TenantConfigurationValue + .builder().value(SINGLE_HASH).build(); + + private static final TenantConfigurationValue CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue + .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; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java index c50b9f8fb..e041f5564 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java @@ -63,13 +63,17 @@ public class CertificateAuthenticationConfigurationItem extends AbstractAuthenti final Label caRootAuthorityLabel = new LabelBuilder().name("SSL Issuer Hash:").buildLabel(); caRootAuthorityLabel.setDescription( "The SSL Issuer iRules.X509 hash, to validate against the controller request certifcate."); + caRootAuthorityLabel.setWidthUndefined(); - caRootAuthorityTextField = new TextFieldBuilder().immediate(true).maxLengthAllowed(128).buildTextComponent(); - caRootAuthorityTextField.setWidth("500px"); + caRootAuthorityTextField = new TextFieldBuilder().immediate(true).maxLengthAllowed(160).buildTextComponent(); + caRootAuthorityTextField.setWidth("100%"); caRootAuthorityTextField.addTextChangeListener(event -> caRootAuthorityChanged()); caRootAuthorityLayout.addComponent(caRootAuthorityLabel); + caRootAuthorityLayout.setExpandRatio(caRootAuthorityLabel, 0); caRootAuthorityLayout.addComponent(caRootAuthorityTextField); + caRootAuthorityLayout.setExpandRatio(caRootAuthorityTextField, 1); + caRootAuthorityLayout.setWidth("100%"); detailLayout.addComponent(caRootAuthorityLayout);