Refactor header authority controller authentication (#2954)

1. (breaking changes) hawkbit.server.ddi.security.rp.cnHeader and sslIssuerHashHeader are renamed to controllerIdHeader and authorityHeader correspondingly.
2. (breaking changes) their default values are changed: X-Ssl-Client-Cn -> X-Controller-Id and X-Ssl-Issuer-Hash-%d -> X-Authority
3. Now the authority header configuration is not a string forma but just a string. The implemenation checks for this header as comma or ; separated list or seeks for header iteration <authority_header>-%d (iteration starts from 0 or 1
4. Doc fixed
5. As there are breaking changes configuration changes may be needed: a) with changing the hawkbit.server.ddi.security.rp you could turn back the previous default headers (note X-Ssl-Issuer-Hash-%d shall now be X-Ssl-Issuer-Hash), or b) you may change the headers sent by the reverse proxy

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2026-03-12 10:36:37 +02:00
committed by GitHub
parent a1608cce19
commit 011d7f567e
8 changed files with 127 additions and 126 deletions

View File

@@ -118,8 +118,8 @@ server.forward-headers-strategy=NATIVE
``` ```
2. In Hawkbit's UI section, under system configuration, make sure to select *Allow targets to authenticate via a 2. In Hawkbit's UI section, under system configuration, make sure to select *Allow targets to authenticate via a
certificate authenticated by a reverse proxy* and input the fixed issuer hash as "Hawkbit". This can be whetever you certificate authenticated by a reverse proxy* and input the fixed issuer hash as "Hawkbit". This can be whenever you
have configured in the nginx configuration in `proxy_set_header X-Ssl-Issuer-Hash-1` below. have configured in the nginx configuration in `proxy_set_header X-Authority-1` below.
3. After placing your certificates and keys, you need to deploy your proxy server and apply the provided configurations. 3. After placing your certificates and keys, you need to deploy your proxy server and apply the provided configurations.
You can apply mutual TLS specifically to the URL given below to implement the process only for devices using the You can apply mutual TLS specifically to the URL given below to implement the process only for devices using the
@@ -184,8 +184,8 @@ server {
# Client certificate Common Name and Issuer Hash is required # Client certificate Common Name and Issuer Hash is required
# for auth in hawkbit. # for auth in hawkbit.
proxy_set_header X-Ssl-Client-Cn $ssl_client_s_dn_cn; proxy_set_header X-Controller-Id $ssl_client_s_dn_cn;
proxy_set_header X-Ssl-Issuer-Hash-1 Hawkbit; proxy_set_header X-Authority-1 Hawkbit;
# These are required for clients to upload and download software. # These are required for clients to upload and download software.
proxy_request_buffering off; proxy_request_buffering off;

View File

@@ -29,14 +29,6 @@ public class DdiSecurityProperties {
private final Rp rp = new Rp(); private final Rp rp = new Rp();
private final Authentication authentication = new Authentication(); private final Authentication authentication = new Authentication();
public Authentication getAuthentication() {
return authentication;
}
public Rp getRp() {
return rp;
}
/** /**
* Reverse proxy configuration. Defines the security properties for * Reverse proxy configuration. Defines the security properties for
* authenticating controllers behind a reverse proxy which terminates the * authenticating controllers behind a reverse proxy which terminates the
@@ -47,16 +39,19 @@ public class DdiSecurityProperties {
public static class Rp { public static class Rp {
/** /**
* HTTP header field for common name of a DDI target client certificate. * HTTP header field for controller ID (e.g. CN of the controller certificate) of a DDI target client certificate.
*/ */
private String cnHeader = "X-Ssl-Client-Cn"; private String controllerIdHeader = "X-Controller-Id";
/** /**
* HTTP header field for issuer hash of a DDI target client certificate. * HTTP header field for authority(ies) (e.g. SHA-256 fingerprints of issuer certificates) of a DDI target client certificate.
*/ */
private String sslIssuerHashHeader = "X-Ssl-Issuer-Hash-%d"; private String authorityHeader = "X-Authority";
/** /**
* List of trusted (reverse proxy) IP addresses for performing DDI * Regular expression for authorities list separator
* client certificate auth. */
private String authoritiesSeparatorRegex = "[;,]";
/**
* List of trusted (reverse proxy) IP addresses for performing DDI client certificate auth.
*/ */
private List<String> trustedIPs; private List<String> trustedIPs;
} }
@@ -67,14 +62,14 @@ public class DdiSecurityProperties {
@Data @Data
public static class Authentication { public static class Authentication {
private final Targettoken targettoken = new Targettoken(); private final TargetToken targettoken = new TargetToken();
private final Gatewaytoken gatewaytoken = new Gatewaytoken(); private final GatewayToken gatewaytoken = new GatewayToken();
/** /**
* Target token auth. Tokens are defined per target. * Target token auth. Tokens are defined per target.
*/ */
@Data @Data
public static class Targettoken { public static class TargetToken {
/** /**
* Set to true to enable target token auth. * Set to true to enable target token auth.
@@ -86,7 +81,7 @@ public class DdiSecurityProperties {
* Gateway token auth. Tokens are defined per tenant. Use with care! * Gateway token auth. Tokens are defined per tenant. Use with care!
*/ */
@Data @Data
public static class Gatewaytoken { public static class GatewayToken {
/** /**
* Gateway token based authentication enabled. * Gateway token based authentication enabled.

View File

@@ -9,17 +9,17 @@
*/ */
package org.eclipse.hawkbit.security.controller; package org.eclipse.hawkbit.security.controller;
import static org.eclipse.hawkbit.audit.SecurityLogger.LOGGER;
import static org.eclipse.hawkbit.context.AccessContext.asTenant; import static org.eclipse.hawkbit.context.AccessContext.asTenant;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME; import static org.eclipse.hawkbit.repository.helper.TenantConfigHelper.getAsSystem;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
/** /**
@@ -29,62 +29,46 @@ import org.springframework.security.core.Authentication;
@Slf4j @Slf4j
public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthenticator { public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthenticator {
private static final Logger LOG_SECURITY_AUTH = LoggerFactory.getLogger("server-security.auth"); // e.g: X-Controller-Id: controller-1 or X-Subject-CN: controller-1
private final String controllerIdHeader;
// e.g.: X-Authority: X,Y or X-CA-Fingerprint-0: <e.g. SHA-256 fingerprint>
// could be used with one or multiple authorities that has confirmed the controller id:
// 1. <authority>=X,Y,Z -> comma separated list of authorities (could be single authority)
// 2. <authority>-0=X, <authority>-1=Y, .. ... until we get a null header
private final String authorityHeader;
private final String authoritiesSeparatorRegex;
// Example Headers with Cert Information public SecurityHeaderAuthenticator(final DdiSecurityProperties.Rp rp) {
// Clientip: 217.24.201.180 this.controllerIdHeader = rp.getControllerIdHeader();
// X-Forwarded-Proto: https this.authorityHeader = rp.getAuthorityHeader();
// X-Ssl-Client-Cn: my.name this.authoritiesSeparatorRegex = rp.getAuthoritiesSeparatorRegex();
// 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;
public SecurityHeaderAuthenticator(final String caCommonNameHeader, final String caAuthorityNameHeader) {
this.caCommonNameHeader = caCommonNameHeader;
this.sslIssuerHashBasicHeader = caAuthorityNameHeader;
} }
@Override @Override
public Authentication authenticate(final ControllerSecurityToken controllerSecurityToken) { public Authentication authenticate(final ControllerSecurityToken controllerSecurityToken) {
// retrieve the common name header and the authority name header from the http request and combine them together // retrieve the common name header and the authority name header from the http request and combine them together
final String commonNameValue = controllerSecurityToken.getHeader(caCommonNameHeader); final String verifiedControllerId = controllerSecurityToken.getHeader(controllerIdHeader);
if (commonNameValue == null) { if (verifiedControllerId == null) {
log.debug("The request doesn't contain the 'common name' header"); log.debug("The request doesn't contain the '{}' header", controllerIdHeader);
return null; return null;
} }
if (!commonNameValue.equals(controllerSecurityToken.getControllerId())) { if (!verifiedControllerId.equals(controllerSecurityToken.getControllerId())) {
log.debug("The request contains the 'common name' header but it doesn't match the controller id"); log.debug("The request contains the '{}' header but it doesn't match the controller id", controllerIdHeader);
return null; return null;
} }
if (!isEnabled(controllerSecurityToken)) { if (!isEnabled(controllerSecurityToken)) { // in order to do not do calls to db - check after previous header checks
log.debug("The gateway header authentication is disabled"); log.debug("The gateway header authentication is disabled");
return null; return null;
} }
final String sslIssuerHashValue = getIssuerHashHeader( final String tenant = controllerSecurityToken.getTenant();
controllerSecurityToken, if (verify(controllerSecurityToken, asTenant(tenant, () -> getAsSystem(AUTHENTICATION_HEADER_AUTHORITY, String.class)))) {
asTenant( log.trace("Found trusted authority ****, using as credentials (tenant: {})", tenant);
controllerSecurityToken.getTenant(), return authenticatedController(tenant, verifiedControllerId);
() -> TenantConfigHelper.getAsSystem(AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class))); } else {
if (sslIssuerHashValue == null) {
log.debug("The request contains the 'common name' header but trusted hash is not found");
return null; return null;
} }
if (log.isTraceEnabled()) {
log.debug("Found sslIssuerHash ****, using as credentials for tenant {}", controllerSecurityToken.getTenant());
}
return authenticatedController(controllerSecurityToken.getTenant(), commonNameValue);
} }
@Override @Override
@@ -98,28 +82,52 @@ public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthentic
} }
/** /**
* Iterates over the {@link #sslIssuerHashBasicHeader} basic header {@code X-Ssl-Issuer-Hash-%d} and try to find the same hash as known. * Check {@link #authorityHeader} basic header or iterates over {@link #authorityHeader}-%d and try to find the same authority as trusted.
* It's ok if we find the hash in any the trusted CA chain to accept this request for this tenant. * It's ok if we find any authority (in headers, authenticated the controller) to accept this request for this tenant.
*/ */
@SuppressWarnings("java:S2629") // check if debug is enabled is maybe heavier then evaluation @SuppressWarnings({ "java:S2629", "java:S135", "java:S3776" }) // check if debug is enabled is maybe heavier than evaluation, rest - fine
private String getIssuerHashHeader(final ControllerSecurityToken controllerSecurityToken, final String knownIssuerHashes) { private boolean verify(final ControllerSecurityToken controllerSecurityToken, final String headerAuthority) {
// there may be several knownIssuerHashes configured for the tenant // there may be several trusted authorities (headerAuthority config value) configured for the tenant
final List<String> knownHashes = Arrays.stream(knownIssuerHashes.split("[;,]")).map(String::toLowerCase).toList(); final List<String> trustedAuthorities = Arrays.stream(headerAuthority.split(authoritiesSeparatorRegex))
.map(String::toLowerCase).map(String::trim).toList();
// iterate over the headers until we get a null header. boolean hasAuthorityHeader = false;
String foundHash; String matchingAuthority = null;
for (int iHeader = 1; (foundHash = controllerSecurityToken.getHeader(
String.format(sslIssuerHashBasicHeader, iHeader))) != null; iHeader++) { final String authorityHeaderValue = controllerSecurityToken.getHeader(authorityHeader);
if (knownHashes.contains(foundHash.toLowerCase())) { if (authorityHeaderValue == null) {
if (log.isTraceEnabled()) { // go for authority header prefixed iteration. iterate over the headers until we get a null header. Start from index 0 or 1
log.trace("Found matching ssl issuer hash at position {}", iHeader); for (int i = 0; ; i++) {
} final String authority = controllerSecurityToken.getHeader(authorityHeader + "-" + i);
return foundHash.toLowerCase(); if (authority == null) {
if (i != 0) {
break; // end of index iteration
} // if 0, try if start from 1
} else {
hasAuthorityHeader = true;
final String authorityLower = authority.toLowerCase();
if (trustedAuthorities.contains(authorityLower)) {
matchingAuthority = authorityLower;
break;
} }
} }
LOG_SECURITY_AUTH.debug( }
"Certificate request but no matching hash found in headers {} for common name {} in request", } else {
sslIssuerHashBasicHeader, controllerSecurityToken.getHeader(caCommonNameHeader)); hasAuthorityHeader = true;
return null; matchingAuthority = Arrays.stream(authorityHeaderValue.split(authoritiesSeparatorRegex))
.map(String::toLowerCase).map(String::trim)
.filter(trustedAuthorities::contains)
.findFirst().orElse(null);
}
if (matchingAuthority == null) {
if (hasAuthorityHeader) {
LOGGER.debug("[SEC_HEADER_AUTH] Request has an authority header(s) but it doesn't match any trusted authority");
}
} else if (log.isTraceEnabled()) {
log.trace("Found matching authority {}", matchingAuthority);
}
return matchingAuthority != null;
} }
} }

View File

@@ -10,7 +10,7 @@
package org.eclipse.hawkbit.security.controller; package org.eclipse.hawkbit.security.controller;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_ENABLED;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -33,23 +33,23 @@ class SecurityHeaderAuthenticatorTest {
private static final String CA_COMMON_NAME = "ca-cn"; private static final String CA_COMMON_NAME = "ca-cn";
private static final String CA_COMMON_NAME_VALUE = "box1"; 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 X_AUTHORITY_1 = "X-Authority-1";
private static final String SINGLE_HASH = "hash1"; private static final String SINGLE_AUTHORITY = "hash1";
private static final String SECOND_HASH = "hash2"; private static final String SECOND_AUTHORITY = "hash2";
private static final String THIRD_HASH = "hash3"; private static final String THIRD_AUTHORITY = "hash3";
private static final String UNKNOWN_HASH = "unknown"; private static final String UNKNOWN_AUTHORITY = "unknown";
private static final String MULTI_HASH = "HASH1;hash2,HASH3,HASH1"; private static final String MULTI_AUTHORITY = "HASH1;hash2,HASH3,HASH1";
private static final TenantConfigurationValue<String> CONFIG_VALUE_SINGLE_HASH = TenantConfigurationValue private static final TenantConfigurationValue<String> CONFIG_VALUE_SINGLE_AUTHORITY = TenantConfigurationValue.<String> builder()
.<String> builder().value(SINGLE_HASH).build(); .value(SINGLE_AUTHORITY).build();
private static final TenantConfigurationValue<String> CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue private static final TenantConfigurationValue<String> CONFIG_VALUE_MULTI_AUTHORITY = TenantConfigurationValue.<String> builder()
.<String> builder().value(MULTI_HASH).build(); .value(MULTI_AUTHORITY).build();
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_ENABLED = TenantConfigurationValue private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_ENABLED = TenantConfigurationValue.<Boolean> builder()
.<Boolean> builder().value(true).build(); .value(true).build();
private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_DISABLED = TenantConfigurationValue private static final TenantConfigurationValue<Boolean> CONFIG_VALUE_DISABLED = TenantConfigurationValue.<Boolean> builder()
.<Boolean> builder().value(false).build(); .value(false).build();
private Authenticator authenticator; private Authenticator authenticator;
@@ -59,17 +59,19 @@ class SecurityHeaderAuthenticatorTest {
@BeforeEach @BeforeEach
void before() { void before() {
TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagementMock); TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagementMock);
authenticator = new SecurityHeaderAuthenticator(CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d"); final DdiSecurityProperties.Rp rp = new DdiSecurityProperties.Rp();
rp.setControllerIdHeader(CA_COMMON_NAME);
authenticator = new SecurityHeaderAuthenticator(rp);
} }
/** /**
* Tests successful authentication with multiple a single hashes * Tests successful authentication with multiple a single hashes
*/ */
@Test @Test
void testWithSingleKnownHash() { void testWithSingleTrustedAuthority() {
final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH); final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_AUTHORITY);
when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class)) when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_AUTHORITY, String.class))
.thenReturn(CONFIG_VALUE_SINGLE_HASH); .thenReturn(CONFIG_VALUE_SINGLE_AUTHORITY);
when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_ENABLED, Boolean.class)) when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_ENABLED, Boolean.class))
.thenReturn(CONFIG_VALUE_ENABLED); .thenReturn(CONFIG_VALUE_ENABLED);
@@ -82,19 +84,19 @@ class SecurityHeaderAuthenticatorTest {
* Tests successful authentication with multiple hashes * Tests successful authentication with multiple hashes
*/ */
@Test @Test
void testWithMultipleKnownHashes() { void testWithMultipleTrustedAuthority() {
when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class)) when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_AUTHORITY, String.class))
.thenReturn(CONFIG_VALUE_MULTI_HASH); .thenReturn(CONFIG_VALUE_MULTI_AUTHORITY);
when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_ENABLED, Boolean.class)) when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_ENABLED, Boolean.class))
.thenReturn(CONFIG_VALUE_ENABLED); .thenReturn(CONFIG_VALUE_ENABLED);
assertThat(authenticator.authenticate(prepareSecurityToken(SINGLE_HASH))) assertThat(authenticator.authenticate(prepareSecurityToken(SINGLE_AUTHORITY)))
.isNotNull() .isNotNull()
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE); .hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
assertThat(authenticator.authenticate(prepareSecurityToken(SECOND_HASH))) assertThat(authenticator.authenticate(prepareSecurityToken(SECOND_AUTHORITY)))
.isNotNull() .isNotNull()
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE); .hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
assertThat(authenticator.authenticate(prepareSecurityToken(THIRD_HASH))) assertThat(authenticator.authenticate(prepareSecurityToken(THIRD_AUTHORITY)))
.isNotNull() .isNotNull()
.hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE); .hasFieldOrPropertyWithValue("principal", CA_COMMON_NAME_VALUE);
} }
@@ -103,10 +105,10 @@ class SecurityHeaderAuthenticatorTest {
* Tests that if the hash is unknown, the authentication fails * Tests that if the hash is unknown, the authentication fails
*/ */
@Test @Test
void testWithUnknownHash() { void testWithUnTrustedAuthority() {
final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_HASH); final ControllerSecurityToken securityToken = prepareSecurityToken(UNKNOWN_AUTHORITY);
when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class)) when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_AUTHORITY, String.class))
.thenReturn(CONFIG_VALUE_MULTI_HASH); .thenReturn(CONFIG_VALUE_MULTI_AUTHORITY);
when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_ENABLED, Boolean.class)) when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_ENABLED, Boolean.class))
.thenReturn(CONFIG_VALUE_ENABLED); .thenReturn(CONFIG_VALUE_ENABLED);
@@ -120,7 +122,7 @@ class SecurityHeaderAuthenticatorTest {
void testWithNonMatchingCN() { void testWithNonMatchingCN() {
final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", "otherControllerID"); final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", "otherControllerID");
securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE); securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE);
securityToken.putHeader(X_SSL_ISSUER_HASH_1, SINGLE_HASH); securityToken.putHeader(X_AUTHORITY_1, SINGLE_AUTHORITY);
assertThat(authenticator.authenticate(securityToken)).isNull(); assertThat(authenticator.authenticate(securityToken)).isNull();
} }
@@ -137,10 +139,9 @@ class SecurityHeaderAuthenticatorTest {
* Tests that if disabled, the authentication fails * Tests that if disabled, the authentication fails
*/ */
@Test @Test
void testWithSingleKnownHashButDisabled() { void testWithSingleTrustedAuthorityButDisabled() {
final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH); final ControllerSecurityToken securityToken = prepareSecurityToken(SINGLE_AUTHORITY);
when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_ENABLED, Boolean.class)) when(tenantConfigurationManagementMock.getConfigurationValue(AUTHENTICATION_HEADER_ENABLED, Boolean.class)).thenReturn(CONFIG_VALUE_DISABLED);
.thenReturn(CONFIG_VALUE_DISABLED);
assertThat(authenticator.authenticate(securityToken)).isNull(); assertThat(authenticator.authenticate(securityToken)).isNull();
} }
@@ -148,7 +149,7 @@ class SecurityHeaderAuthenticatorTest {
private static ControllerSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) { private static ControllerSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) {
final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE); final ControllerSecurityToken securityToken = new ControllerSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE);
securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE); securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE);
securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue); securityToken.putHeader(X_AUTHORITY_1, issuerHashHeaderValue);
return securityToken; return securityToken;
} }
} }

View File

@@ -83,8 +83,7 @@ class ControllerDownloadSecurityConfiguration {
.anonymous(AbstractHttpConfigurer::disable) .anonymous(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable)
.addFilterBefore(new AuthenticationFilters.SecurityHeaderAuthenticationFilter( .addFilterBefore(new AuthenticationFilters.SecurityHeaderAuthenticationFilter(
new SecurityHeaderAuthenticator( new SecurityHeaderAuthenticator(ddiSecurityConfiguration.getRp()),
ddiSecurityConfiguration.getRp().getCnHeader(), ddiSecurityConfiguration.getRp().getSslIssuerHashHeader()),
ddiSecurityConfiguration), AuthorizationFilter.class) ddiSecurityConfiguration), AuthorizationFilter.class)
.addFilterBefore(new AuthenticationFilters.SecurityTokenAuthenticationFilter( .addFilterBefore(new AuthenticationFilters.SecurityTokenAuthenticationFilter(
new SecurityTokenAuthenticator(controllerManagement), new SecurityTokenAuthenticator(controllerManagement),

View File

@@ -91,9 +91,7 @@ class ControllerSecurityConfiguration {
.csrf(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable)
.addFilterBefore( .addFilterBefore(
new AuthenticationFilters.SecurityHeaderAuthenticationFilter( new AuthenticationFilters.SecurityHeaderAuthenticationFilter(
new SecurityHeaderAuthenticator( new SecurityHeaderAuthenticator(ddiSecurityConfiguration.getRp()), ddiSecurityConfiguration),
ddiSecurityConfiguration.getRp().getCnHeader(),
ddiSecurityConfiguration.getRp().getSslIssuerHashHeader()), ddiSecurityConfiguration),
AuthorizationFilter.class) AuthorizationFilter.class)
.addFilterBefore( .addFilterBefore(
new AuthenticationFilters.SecurityTokenAuthenticationFilter( new AuthenticationFilters.SecurityTokenAuthenticationFilter(

View File

@@ -69,9 +69,9 @@ public class TenantConfigurationProperties {
*/ */
public static final String AUTHENTICATION_HEADER_ENABLED = "authentication.header.enabled"; public static final String AUTHENTICATION_HEADER_ENABLED = "authentication.header.enabled";
/** /**
* Header based authentication authority name. * Header based authentication authority(-ies, could be list).
*/ */
public static final String AUTHENTICATION_HEADER_AUTHORITY_NAME = "authentication.header.authority"; public static final String AUTHENTICATION_HEADER_AUTHORITY = "authentication.header.authority";
/** /**
* Target token based authentication enabled. * Target token based authentication enabled.
*/ */

View File

@@ -90,7 +90,7 @@ public class HawkbitFlywayDbInit {
log.info("Start ({}): {}@{}, table: {}, locations: {}, sql-migration-suffixes: {}", log.info("Start ({}): {}@{}, table: {}, locations: {}, sql-migration-suffixes: {}",
MODE, USER, URL, TABLE, LOCATIONS, SQL_MIGRATION_SUFFIXES); MODE, USER, URL, TABLE, LOCATIONS, SQL_MIGRATION_SUFFIXES);
// configured via system properties callbacks are with prority. If not confiured - try to load via service loader // configured via system properties callbacks are with priority. If not configured - try to load via service loader
final Callback[] callbackViaServiceLoader = CALLBACKS.length == 0 ? ServiceLoader.load(Callback.class).stream() final Callback[] callbackViaServiceLoader = CALLBACKS.length == 0 ? ServiceLoader.load(Callback.class).stream()
.map(ServiceLoader.Provider::get) .map(ServiceLoader.Provider::get)
.toArray(Callback[]::new) : new Callback[0]; .toArray(Callback[]::new) : new Callback[0];