Merge pull request #297 from bsinno/feature_multi_known_hashes_for_issuer_hash_based_auth
Feature multi known hashes for issuer hash based auth
This commit is contained in:
@@ -21,6 +21,8 @@ import org.springframework.security.authentication.InsufficientAuthenticationExc
|
|||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import ru.yandex.qatools.allure.annotations.Description;
|
import ru.yandex.qatools.allure.annotations.Description;
|
||||||
import ru.yandex.qatools.allure.annotations.Features;
|
import ru.yandex.qatools.allure.annotations.Features;
|
||||||
import ru.yandex.qatools.allure.annotations.Stories;
|
import ru.yandex.qatools.allure.annotations.Stories;
|
||||||
@@ -45,8 +47,8 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest {
|
|||||||
public void principalAndCredentialsNotTheSameThrowsAuthenticationException() {
|
public void principalAndCredentialsNotTheSameThrowsAuthenticationException() {
|
||||||
final String principal = "controllerIdURL";
|
final String principal = "controllerIdURL";
|
||||||
final String credentials = "controllerIdHeader";
|
final String credentials = "controllerIdHeader";
|
||||||
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal,
|
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(
|
||||||
credentials);
|
principal, Lists.newArrayList(credentials));
|
||||||
token.setDetails(webAuthenticationDetailsMock);
|
token.setDetails(webAuthenticationDetailsMock);
|
||||||
|
|
||||||
// test, should throw authentication exception
|
// test, should throw authentication exception
|
||||||
@@ -64,11 +66,12 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest {
|
|||||||
public void principalAndCredentialsAreTheSameWithNoSourceIpCheckIsSuccessful() {
|
public void principalAndCredentialsAreTheSameWithNoSourceIpCheckIsSuccessful() {
|
||||||
final String principal = "controllerId";
|
final String principal = "controllerId";
|
||||||
final String credentials = "controllerId";
|
final String credentials = "controllerId";
|
||||||
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal,
|
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(
|
||||||
credentials);
|
principal, Lists.newArrayList(credentials));
|
||||||
token.setDetails(webAuthenticationDetailsMock);
|
token.setDetails(webAuthenticationDetailsMock);
|
||||||
|
|
||||||
final Authentication authenticate = underTestWithoutSourceIpCheck.authenticate(token);
|
final Authentication authenticate = underTestWithoutSourceIpCheck
|
||||||
|
.authenticate(token);
|
||||||
assertThat(authenticate.isAuthenticated()).isTrue();
|
assertThat(authenticate.isAuthenticated()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +81,8 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest {
|
|||||||
final String remoteAddress = "192.168.1.1";
|
final String remoteAddress = "192.168.1.1";
|
||||||
final String principal = "controllerId";
|
final String principal = "controllerId";
|
||||||
final String credentials = "controllerId";
|
final String credentials = "controllerId";
|
||||||
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal,
|
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(
|
||||||
credentials);
|
principal, Lists.newArrayList(credentials));
|
||||||
token.setDetails(webAuthenticationDetailsMock);
|
token.setDetails(webAuthenticationDetailsMock);
|
||||||
|
|
||||||
when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(remoteAddress);
|
when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(remoteAddress);
|
||||||
@@ -99,14 +102,15 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest {
|
|||||||
public void priniciapAndCredentialsAreTheSameAndSourceIpIsTrusted() {
|
public void priniciapAndCredentialsAreTheSameAndSourceIpIsTrusted() {
|
||||||
final String principal = "controllerId";
|
final String principal = "controllerId";
|
||||||
final String credentials = "controllerId";
|
final String credentials = "controllerId";
|
||||||
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal,
|
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(
|
||||||
credentials);
|
principal, Lists.newArrayList(credentials));
|
||||||
token.setDetails(webAuthenticationDetailsMock);
|
token.setDetails(webAuthenticationDetailsMock);
|
||||||
|
|
||||||
when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP);
|
when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP);
|
||||||
|
|
||||||
// test, should throw authentication exception
|
// test, should throw authentication exception
|
||||||
final Authentication authenticate = underTestWithSourceIpCheck.authenticate(token);
|
final Authentication authenticate = underTestWithSourceIpCheck
|
||||||
|
.authenticate(token);
|
||||||
assertThat(authenticate.isAuthenticated()).isTrue();
|
assertThat(authenticate.isAuthenticated()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +120,8 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest {
|
|||||||
"192.168.1.3" };
|
"192.168.1.3" };
|
||||||
final String principal = "controllerId";
|
final String principal = "controllerId";
|
||||||
final String credentials = "controllerId";
|
final String credentials = "controllerId";
|
||||||
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal,
|
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(
|
||||||
credentials);
|
principal, Lists.newArrayList(credentials));
|
||||||
token.setDetails(webAuthenticationDetailsMock);
|
token.setDetails(webAuthenticationDetailsMock);
|
||||||
|
|
||||||
when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP);
|
when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP);
|
||||||
@@ -135,8 +139,8 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest {
|
|||||||
final String[] trustedIPAddresses = new String[] { "192.168.1.1", "192.168.1.2", "192.168.1.3" };
|
final String[] trustedIPAddresses = new String[] { "192.168.1.1", "192.168.1.2", "192.168.1.3" };
|
||||||
final String principal = "controllerId";
|
final String principal = "controllerId";
|
||||||
final String credentials = "controllerId";
|
final String credentials = "controllerId";
|
||||||
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal,
|
final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(
|
||||||
credentials);
|
principal, Lists.newArrayList(credentials));
|
||||||
token.setDetails(webAuthenticationDetailsMock);
|
token.setDetails(webAuthenticationDetailsMock);
|
||||||
|
|
||||||
when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP);
|
when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
/**
|
/**
|
||||||
* An abstraction for all controller based security. Check if the tenant
|
* An abstraction for all controller based security. Check if the tenant
|
||||||
* configuration is enabled.
|
* configuration is enabled.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -46,12 +46,6 @@ public abstract class AbstractControllerAuthenticationFilter implements PreAuthe
|
|||||||
return tenantAware.runAsTenant(secruityToken.getTenant(), configurationKeyTenantRunner);
|
return tenantAware.runAsTenant(secruityToken.getTenant(), configurationKeyTenantRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract HeaderAuthentication getPreAuthenticatedPrincipal(TenantSecurityToken secruityToken);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public abstract HeaderAuthentication getPreAuthenticatedCredentials(TenantSecurityToken secruityToken);
|
|
||||||
|
|
||||||
private final class SecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner<Boolean> {
|
private final class SecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner<Boolean> {
|
||||||
@Override
|
@Override
|
||||||
public Boolean run() {
|
public Boolean run() {
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.security;
|
package org.eclipse.hawkbit.security;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
|
import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken;
|
||||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
@@ -20,8 +24,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
* request URI and the credential from a request header in a the
|
* request URI and the credential from a request header in a the
|
||||||
* {@link TenantSecurityToken}.
|
* {@link TenantSecurityToken}.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractControllerAuthenticationFilter {
|
public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractControllerAuthenticationFilter {
|
||||||
|
|
||||||
@@ -77,8 +79,7 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
|
|||||||
@Override
|
@Override
|
||||||
public HeaderAuthentication getPreAuthenticatedPrincipal(final TenantSecurityToken secruityToken) {
|
public HeaderAuthentication getPreAuthenticatedPrincipal(final TenantSecurityToken secruityToken) {
|
||||||
// retrieve the common name header and the authority name header from
|
// retrieve the common name header and the authority name header from
|
||||||
// the http request and
|
// the http request and combine them together
|
||||||
// combine them together
|
|
||||||
final String commonNameValue = secruityToken.getHeader(caCommonNameHeader);
|
final String commonNameValue = secruityToken.getHeader(caCommonNameHeader);
|
||||||
final String knownSslIssuerConfigurationValue = tenantAware.runAsTenant(secruityToken.getTenant(),
|
final String knownSslIssuerConfigurationValue = tenantAware.runAsTenant(secruityToken.getTenant(),
|
||||||
sslIssuerNameConfigTenantRunner);
|
sslIssuerNameConfigTenantRunner);
|
||||||
@@ -97,18 +98,20 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderAuthentication getPreAuthenticatedCredentials(final TenantSecurityToken secruityToken) {
|
public Object getPreAuthenticatedCredentials(final TenantSecurityToken secruityToken) {
|
||||||
final String authorityNameConfigurationValue = tenantAware.runAsTenant(secruityToken.getTenant(),
|
final String authorityNameConfigurationValue = tenantAware.runAsTenant(secruityToken.getTenant(),
|
||||||
sslIssuerNameConfigTenantRunner);
|
sslIssuerNameConfigTenantRunner);
|
||||||
String controllerId = secruityToken.getControllerId();
|
String controllerId = secruityToken.getControllerId();
|
||||||
// in case of legacy download artifact, the controller ID is not in the
|
// in case of legacy download artifact, the controller ID is not in the
|
||||||
// URL path, so then
|
// URL path, so then we just use the common name header
|
||||||
// we just use the common name header
|
|
||||||
if (controllerId == null || "anonymous".equals(controllerId)) {
|
if (controllerId == null || "anonymous".equals(controllerId)) {
|
||||||
controllerId = secruityToken.getHeader(caCommonNameHeader);
|
controllerId = secruityToken.getHeader(caCommonNameHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HeaderAuthentication(controllerId, authorityNameConfigurationValue);
|
List<String> knownHashes = splitMultiHashBySemicolon(authorityNameConfigurationValue);
|
||||||
|
|
||||||
|
final String cntlId = controllerId;
|
||||||
|
return knownHashes.stream().map(hashItem -> new HeaderAuthentication(cntlId, hashItem)).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,12 +120,15 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
|
|||||||
* It's ok if we find the the hash in any the trusted CA chain to accept
|
* It's ok if we find the the hash in any the trusted CA chain to accept
|
||||||
* this request for this tenant.
|
* this request for this tenant.
|
||||||
*/
|
*/
|
||||||
private String getIssuerHashHeader(final TenantSecurityToken secruityToken, final String knownIssuerHash) {
|
private String getIssuerHashHeader(final TenantSecurityToken secruityToken, final String knownIssuerHashes) {
|
||||||
|
// there may be several knownIssuerHashes configured for the tenant
|
||||||
|
List<String> knownHashes = splitMultiHashBySemicolon(knownIssuerHashes);
|
||||||
|
|
||||||
// iterate over the headers until we get a null header.
|
// iterate over the headers until we get a null header.
|
||||||
int iHeader = 1;
|
int iHeader = 1;
|
||||||
String foundHash;
|
String foundHash;
|
||||||
while ((foundHash = secruityToken.getHeader(String.format(sslIssuerHashBasicHeader, iHeader))) != null) {
|
while ((foundHash = secruityToken.getHeader(String.format(sslIssuerHashBasicHeader, iHeader))) != null) {
|
||||||
if (foundHash.equals(knownIssuerHash)) {
|
if (knownHashes.contains(foundHash)) {
|
||||||
if (LOGGER.isTraceEnabled()) {
|
if (LOGGER.isTraceEnabled()) {
|
||||||
LOGGER.trace("Found matching ssl issuer hash at position {}", iHeader);
|
LOGGER.trace("Found matching ssl issuer hash at position {}", iHeader);
|
||||||
}
|
}
|
||||||
@@ -148,4 +154,8 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont
|
|||||||
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue());
|
TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> splitMultiHashBySemicolon(String knownIssuerHashes) {
|
||||||
|
return Arrays.asList(knownIssuerHashes.split(";"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,17 +27,17 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA
|
|||||||
* An spring authentication provider which supports authentication tokens of
|
* An spring authentication provider which supports authentication tokens of
|
||||||
* type {@link PreAuthenticatedAuthenticationToken} created by the
|
* type {@link PreAuthenticatedAuthenticationToken} created by the
|
||||||
* {@link ControllerPreAuthenticatedSecurityHeaderFilter}.
|
* {@link ControllerPreAuthenticatedSecurityHeaderFilter}.
|
||||||
*
|
*
|
||||||
* Additionally to the authentication token providing the principal and the
|
* Additionally to the authentication token providing the principal and the
|
||||||
* credentials which must be match, this authentication provider can also check
|
* credentials which must be match, this authentication provider can also check
|
||||||
* the remote IP address of the request.
|
* the remote IP address of the request.
|
||||||
*
|
*
|
||||||
* E.g. The request path is /controller/v1/{controllerId} then the controllerId
|
* E.g. The request path is /controller/v1/{controllerId} then the controllerId
|
||||||
* in the path is the principal. The credentials are the extracted information
|
* 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
|
* 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
|
* only allowed from a specific source address this authentication manager can
|
||||||
* also check the remote IP address of the request.
|
* also check the remote IP address of the request.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -58,7 +58,7 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica
|
|||||||
* Creates a new PreAuthTokenSourceTrustAuthenticationProvider with given
|
* Creates a new PreAuthTokenSourceTrustAuthenticationProvider with given
|
||||||
* source IP addresses which are trusted and should be checked against the
|
* source IP addresses which are trusted and should be checked against the
|
||||||
* request remote IP address.
|
* request remote IP address.
|
||||||
*
|
*
|
||||||
* @param authorizedSourceIps
|
* @param authorizedSourceIps
|
||||||
* a list of IP addresses.
|
* a list of IP addresses.
|
||||||
*/
|
*/
|
||||||
@@ -70,7 +70,7 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica
|
|||||||
* Creates a new PreAuthTokenSourceTrustAuthenticationProvider with given
|
* Creates a new PreAuthTokenSourceTrustAuthenticationProvider with given
|
||||||
* source IP addresses which are trusted and should be checked against the
|
* source IP addresses which are trusted and should be checked against the
|
||||||
* request remote IP address.
|
* request remote IP address.
|
||||||
*
|
*
|
||||||
* @param authorizedSourceIps
|
* @param authorizedSourceIps
|
||||||
* a list of IP addresses.
|
* a list of IP addresses.
|
||||||
*/
|
*/
|
||||||
@@ -87,7 +87,6 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean successAuthentication = false;
|
|
||||||
final PreAuthenticatedAuthenticationToken token = (PreAuthenticatedAuthenticationToken) authentication;
|
final PreAuthenticatedAuthenticationToken token = (PreAuthenticatedAuthenticationToken) authentication;
|
||||||
final Object credentials = token.getCredentials();
|
final Object credentials = token.getCredentials();
|
||||||
final Object principal = token.getPrincipal();
|
final Object principal = token.getPrincipal();
|
||||||
@@ -97,14 +96,7 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica
|
|||||||
throw new BadCredentialsException("The provided principal and credentials are not match");
|
throw new BadCredentialsException("The provided principal and credentials are not match");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if principal equals credentials because we want to check if
|
boolean successAuthentication = calculateAuthenticationSuccess(principal, credentials, tokenDetails);
|
||||||
// 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
|
|
||||||
if (principal.equals(credentials)) {
|
|
||||||
successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (successAuthentication) {
|
if (successAuthentication) {
|
||||||
final Collection<GrantedAuthority> controllerAuthorities = new ArrayList<>();
|
final Collection<GrantedAuthority> controllerAuthorities = new ArrayList<>();
|
||||||
@@ -119,6 +111,41 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica
|
|||||||
throw new BadCredentialsException("The provided principal and credentials are not match");
|
throw new BadCredentialsException("The provided principal and credentials are not match");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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(Object principal, Object credentials, 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) {
|
private boolean checkSourceIPAddressIfNeccessary(final Object tokenDetails) {
|
||||||
boolean success = authorizedSourceIps == null;
|
boolean success = authorizedSourceIps == null;
|
||||||
String remoteAddress = null;
|
String remoteAddress = null;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public interface PreAuthentificationFilter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the filter is enabled.
|
* Check if the filter is enabled.
|
||||||
*
|
*
|
||||||
* @param secruityToken
|
* @param secruityToken
|
||||||
* the secruity info
|
* the secruity info
|
||||||
* @return <true> is enabled <false> diabled
|
* @return <true> is enabled <false> diabled
|
||||||
@@ -31,7 +31,7 @@ public interface PreAuthentificationFilter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the principal information from the current secruityToken.
|
* Extract the principal information from the current secruityToken.
|
||||||
*
|
*
|
||||||
* @param secruityToken
|
* @param secruityToken
|
||||||
* the secruityToken
|
* the secruityToken
|
||||||
* @return the extracted tenant and controller id
|
* @return the extracted tenant and controller id
|
||||||
@@ -40,17 +40,17 @@ public interface PreAuthentificationFilter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the principal credentials from the current secruityToken.
|
* Extract the principal credentials from the current secruityToken.
|
||||||
*
|
*
|
||||||
* @param secruityToken
|
* @param secruityToken
|
||||||
* the secruityToken
|
* the secruityToken
|
||||||
* @return the extracted tenant and controller id
|
* @return the extracted tenant and controller id
|
||||||
*/
|
*/
|
||||||
HeaderAuthentication getPreAuthenticatedCredentials(TenantSecurityToken secruityToken);
|
Object getPreAuthenticatedCredentials(TenantSecurityToken secruityToken);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to add additional authorities to the successful authenticated
|
* Allows to add additional authorities to the successful authenticated
|
||||||
* token.
|
* token.
|
||||||
*
|
*
|
||||||
* @return the authorities granted to the principal, or an empty collection
|
* @return the authorities granted to the principal, or an empty collection
|
||||||
* if the token has not been authenticated. Never null.
|
* if the token has not been authenticated. Never null.
|
||||||
* @see Authentication#getAuthorities()
|
* @see Authentication#getAuthorities()
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* 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.fest.assertions.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
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 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 UNKNOWN_HASH = "unknown";
|
||||||
|
|
||||||
|
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() {
|
||||||
|
final TenantSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH);
|
||||||
|
// use single known hash
|
||||||
|
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||||
|
eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(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() {
|
||||||
|
final TenantSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH);
|
||||||
|
// use multiple known hashes
|
||||||
|
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||||
|
eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(String.class)))
|
||||||
|
.thenReturn(CONFIG_VALUE_MULTI_HASH);
|
||||||
|
assertThat(underTest.getPreAuthenticatedPrincipal(securityToken)).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Description("Tests the filter for issuer hash based authentication with unknown hash")
|
||||||
|
public void testIssuerHashBasedAuthenticationWithUnknownHash() {
|
||||||
|
final TenantSecurityToken securityToken = prepareSecurityToken(UNKNOWN_HASH);
|
||||||
|
// use single known hash
|
||||||
|
when(tenantConfigurationManagementMock.getConfigurationValue(
|
||||||
|
eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(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 TenantSecurityToken securityToken1 = prepareSecurityToken(SINGLE_HASH);
|
||||||
|
final TenantSecurityToken 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(
|
||||||
|
eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(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);
|
||||||
|
|
||||||
|
Object principal1 = underTest.getPreAuthenticatedPrincipal(securityToken1);
|
||||||
|
Object principal2 = underTest.getPreAuthenticatedPrincipal(securityToken2);
|
||||||
|
|
||||||
|
assertThat(credentials1.contains(expected1)).isTrue();
|
||||||
|
assertThat(credentials2.contains(expected2)).isTrue();
|
||||||
|
|
||||||
|
assertEquals("hash1 expected in principal!", expected1, principal1);
|
||||||
|
assertEquals("hash2 expected in principal!", expected2, principal2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TenantSecurityToken prepareSecurityToken(String issuerHashHeaderValue) {
|
||||||
|
final TenantSecurityToken securityToken = new TenantSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE,
|
||||||
|
FileResource.createFileResourceBySha1("12345"));
|
||||||
|
securityToken.getHeaders().put(CA_COMMON_NAME, CA_COMMON_NAME_VALUE);
|
||||||
|
securityToken.getHeaders().put(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue);
|
||||||
|
return securityToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -63,13 +63,17 @@ public class CertificateAuthenticationConfigurationItem extends AbstractAuthenti
|
|||||||
final Label caRootAuthorityLabel = new LabelBuilder().name("SSL Issuer Hash:").buildLabel();
|
final Label caRootAuthorityLabel = new LabelBuilder().name("SSL Issuer Hash:").buildLabel();
|
||||||
caRootAuthorityLabel.setDescription(
|
caRootAuthorityLabel.setDescription(
|
||||||
"The SSL Issuer iRules.X509 hash, to validate against the controller request certifcate.");
|
"The SSL Issuer iRules.X509 hash, to validate against the controller request certifcate.");
|
||||||
|
caRootAuthorityLabel.setWidthUndefined();
|
||||||
|
|
||||||
caRootAuthorityTextField = new TextFieldBuilder().immediate(true).maxLengthAllowed(128).buildTextComponent();
|
caRootAuthorityTextField = new TextFieldBuilder().immediate(true).maxLengthAllowed(160).buildTextComponent();
|
||||||
caRootAuthorityTextField.setWidth("500px");
|
caRootAuthorityTextField.setWidth("100%");
|
||||||
caRootAuthorityTextField.addTextChangeListener(event -> caRootAuthorityChanged());
|
caRootAuthorityTextField.addTextChangeListener(event -> caRootAuthorityChanged());
|
||||||
|
|
||||||
caRootAuthorityLayout.addComponent(caRootAuthorityLabel);
|
caRootAuthorityLayout.addComponent(caRootAuthorityLabel);
|
||||||
|
caRootAuthorityLayout.setExpandRatio(caRootAuthorityLabel, 0);
|
||||||
caRootAuthorityLayout.addComponent(caRootAuthorityTextField);
|
caRootAuthorityLayout.addComponent(caRootAuthorityTextField);
|
||||||
|
caRootAuthorityLayout.setExpandRatio(caRootAuthorityTextField, 1);
|
||||||
|
caRootAuthorityLayout.setWidth("100%");
|
||||||
|
|
||||||
detailLayout.addComponent(caRootAuthorityLayout);
|
detailLayout.addComponent(caRootAuthorityLayout);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user