From 9272aa0dc922d97995ae48d59815207ab3cf1e87 Mon Sep 17 00:00:00 2001 From: Andrea Tabone <39694626+taban03@users.noreply.github.com> Date: Fri, 14 Oct 2022 13:04:00 +0200 Subject: [PATCH] feat: Add custom auth header to support Grafana in SSO (#2618) * Enable token auth via http header Signed-off-by: at670475 * Fix Signed-off-by: at670475 * Add it Signed-off-by: at670475 * add env var to gh workflow Signed-off-by: at670475 * add missing env to other wf Signed-off-by: at670475 * fix test Signed-off-by: at670475 * Fix cert Signed-off-by: at670475 * fix test Signed-off-by: at670475 * use default value for script arg Signed-off-by: at670475 Signed-off-by: at670475 --- .github/workflows/containers.yml | 12 +++++++++++ .../config/AuthConfigurationProperties.java | 1 + config/local/gateway-service.yml | 1 + .../src/main/resources/bin/start.sh | 1 + .../security/service/schema/JwtCommand.java | 12 +++++++++++ .../service/schema/ZoweJwtScheme.java | 21 +++++++++++++++---- .../src/main/resources/application.yml | 1 + .../service/schema/ZoweJwtSchemeTest.java | 12 +++++++++++ .../schemes/ZoweJwtSchemeTest.java | 18 +++++++++++++++- .../resources/environment-configuration.yml | 1 + 10 files changed, 75 insertions(+), 5 deletions(-) diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml index 7b2dff1fab..96839a9843 100644 --- a/.github/workflows/containers.yml +++ b/.github/workflows/containers.yml @@ -49,6 +49,8 @@ jobs: - /api-defs:/api-defs gateway-service: image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }} + env: + APIML_SECURITY_AUTH_CUSTOMAUTHHEADER: customHeader mock-services: image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }} metrics-service: @@ -223,6 +225,8 @@ jobs: - /api-defs:/api-defs gateway-service: image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }} + env: + APIML_SECURITY_AUTH_CUSTOMAUTHHEADER: customHeader mock-services: image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }} env: @@ -275,6 +279,8 @@ jobs: - /api-defs:/api-defs gateway-service: image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }} + env: + APIML_SECURITY_AUTH_CUSTOMAUTHHEADER: customHeader mock-services: image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }} env: @@ -323,6 +329,8 @@ jobs: - /api-defs:/api-defs gateway-service: image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }} + env: + APIML_SECURITY_AUTH_CUSTOMAUTHHEADER: customHeader mock-services: image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }} env: @@ -372,6 +380,8 @@ jobs: - /api-defs:/api-defs gateway-service: image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }} + env: + APIML_SECURITY_AUTH_CUSTOMAUTHHEADER: customHeader mock-services: image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }} env: @@ -420,6 +430,8 @@ jobs: - /api-defs:/api-defs gateway-service: image: ghcr.io/balhar-jakub/gateway-service:${{ github.run_id }}-${{ github.run_number }} + env: + APIML_SECURITY_AUTH_CUSTOMAUTHHEADER: customHeader mock-services: image: ghcr.io/balhar-jakub/mock-services:${{ github.run_id }}-${{ github.run_number }} metrics-service: diff --git a/apiml-security-common/src/main/java/org/zowe/apiml/security/common/config/AuthConfigurationProperties.java b/apiml-security-common/src/main/java/org/zowe/apiml/security/common/config/AuthConfigurationProperties.java index 7cd078826e..34781ec5cc 100644 --- a/apiml-security-common/src/main/java/org/zowe/apiml/security/common/config/AuthConfigurationProperties.java +++ b/apiml-security-common/src/main/java/org/zowe/apiml/security/common/config/AuthConfigurationProperties.java @@ -59,6 +59,7 @@ public class AuthConfigurationProperties { private AuthConfigurationProperties.CookieProperties cookieProperties; private String provider = "zosmf"; + private String customAuthHeader; private AuthConfigurationProperties.X509Cert x509Cert; diff --git a/config/local/gateway-service.yml b/config/local/gateway-service.yml index aeaf1d4721..ca84d42367 100644 --- a/config/local/gateway-service.yml +++ b/config/local/gateway-service.yml @@ -16,6 +16,7 @@ apiml: validationUrl: auth: + customAuthHeader: provider: dummy zosmf: serviceId: mockzosmf # Replace me with the correct z/OSMF service id diff --git a/gateway-package/src/main/resources/bin/start.sh b/gateway-package/src/main/resources/bin/start.sh index 16f2975cec..43ae06d374 100755 --- a/gateway-package/src/main/resources/bin/start.sh +++ b/gateway-package/src/main/resources/bin/start.sh @@ -186,6 +186,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${GATEWAY_CODE} java \ -Dapiml.security.ssl.nonStrictVerifySslCertificatesOfServices=${nonStrictVerifySslCertificatesOfServices:-false} \ -Dapiml.security.auth.zosmf.serviceId=${ZWE_configs_apiml_security_auth_zosmf_serviceId:-zosmf} \ -Dapiml.security.auth.provider=${ZWE_configs_apiml_security_auth_provider:-zosmf} \ + -Dapiml.security.auth.customAuthHeader=${ZWE_configs_apiml_security_auth_customAuthHeader:-} \ -Dapiml.security.personalAccessToken.enabled=${ZWE_configs_apiml_security_personalAccessToken_enabled:-false} \ -Dapiml.zoweManifest=${ZWE_zowe_runtimeDirectory}/manifest.json \ -Dserver.address=0.0.0.0 \ diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/schema/JwtCommand.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/schema/JwtCommand.java index efa20395c5..52eb08b10a 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/schema/JwtCommand.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/schema/JwtCommand.java @@ -11,8 +11,10 @@ package org.zowe.apiml.gateway.security.service.schema; import com.netflix.zuul.context.RequestContext; +import lombok.extern.slf4j.Slf4j; import org.zowe.apiml.util.CookieUtil; +@Slf4j public abstract class JwtCommand extends AuthenticationCommand { public static final String COOKIE_HEADER = "cookie"; @@ -41,6 +43,16 @@ public static void removeCookie(RequestContext context, String[] names) { } + /** + * Add HTTP header containing the JWT token to the request. The header name is defined in the configuration. + * @param context + * @param value + */ + public static void setCustomHeader(RequestContext context, String header, String value) { + log.debug("Adding HTTP header {} to the request", header); + context.addZuulRequestHeader(header, value); + } + @Override public boolean isExpired() { if (getExpireAt() == null) return false; diff --git a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/schema/ZoweJwtScheme.java b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/schema/ZoweJwtScheme.java index 6f7885eff3..130fd82302 100644 --- a/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/schema/ZoweJwtScheme.java +++ b/gateway-service/src/main/java/org/zowe/apiml/gateway/security/service/schema/ZoweJwtScheme.java @@ -12,8 +12,10 @@ import com.netflix.appinfo.InstanceInfo; import com.netflix.zuul.context.RequestContext; -import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.zowe.apiml.auth.Authentication; import org.zowe.apiml.auth.AuthenticationScheme; @@ -30,13 +32,21 @@ import java.util.Optional; @Component -@AllArgsConstructor public class ZoweJwtScheme implements IAuthenticationScheme { @InjectApimlLogger private final ApimlLogger logger = ApimlLogger.empty(); - private AuthSourceService authSourceService; - private AuthConfigurationProperties configurationProperties; + private final AuthSourceService authSourceService; + private final AuthConfigurationProperties configurationProperties; + + @Value("${apiml.security.auth.customAuthHeader:}") + private String customHeader; + + @Autowired + public ZoweJwtScheme(AuthSourceService authSourceService, AuthConfigurationProperties configurationProperties) { + this.authSourceService = authSourceService; + this.configurationProperties = configurationProperties; + } @Override public AuthenticationScheme getScheme() { @@ -90,6 +100,9 @@ public void apply(InstanceInfo instanceInfo) { if (jwt != null) { final RequestContext context = RequestContext.getCurrentContext(); JwtCommand.setCookie(context, configurationProperties.getCookieProperties().getCookieName(), jwt); + if (StringUtils.isNotEmpty(customHeader)) { + JwtCommand.setCustomHeader(context, customHeader, jwt); + } } } diff --git a/gateway-service/src/main/resources/application.yml b/gateway-service/src/main/resources/application.yml index 474eb51950..194c1d6a78 100644 --- a/gateway-service/src/main/resources/application.yml +++ b/gateway-service/src/main/resources/application.yml @@ -71,6 +71,7 @@ apiml: clientSecret: validationUrl: auth: + customAuthHeader: provider: zosmf x509: enabled: false diff --git a/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/schema/ZoweJwtSchemeTest.java b/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/schema/ZoweJwtSchemeTest.java index ed4d268161..3ba435fa52 100644 --- a/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/schema/ZoweJwtSchemeTest.java +++ b/gateway-service/src/test/java/org/zowe/apiml/gateway/security/service/schema/ZoweJwtSchemeTest.java @@ -224,4 +224,16 @@ void whenExpiredX509AuthSource_thenCommandIsExpired() { } } } + + @Nested + class GivenCustomAuthHeader { + @Test + void thenAddRequestHeaderContainingToken() { + ReflectionTestUtils.setField(scheme, "customHeader", "header"); + when(authSourceService.parse(authSource)).thenReturn(new ParsedTokenAuthSource("user", new Date(), new Date(), Origin.ZOSMF)); + command = scheme.createCommand(null, authSource); + command.apply(null); + verify(requestContext, times(1)).addZuulRequestHeader(eq("header"), any()); + } + } } diff --git a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/schemes/ZoweJwtSchemeTest.java b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/schemes/ZoweJwtSchemeTest.java index 052c40f52d..a80be36b25 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/schemes/ZoweJwtSchemeTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/integration/authentication/schemes/ZoweJwtSchemeTest.java @@ -30,7 +30,7 @@ import java.util.Set; import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.core.Is.is; import static org.zowe.apiml.util.SecurityUtils.gatewayToken; import static org.zowe.apiml.util.SecurityUtils.personalAccessToken; @@ -70,6 +70,22 @@ void givenInvalidClientCertificateInRequest() { .statusCode(200); } + @Nested + class GivenCustomAuthHeader { + @Test + void thenAddAuthHeader() { + String jwt = gatewayToken(); + given() + .config(SslContext.tlsWithoutCert) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwt) + .when() + .get(URL) + .then() + .body("headers.customheader", is(jwt)) + .statusCode(200); + } + } + @Nested class GivenJWTTest { diff --git a/integration-tests/src/test/resources/environment-configuration.yml b/integration-tests/src/test/resources/environment-configuration.yml index 33b57a202b..c229f80cef 100644 --- a/integration-tests/src/test/resources/environment-configuration.yml +++ b/integration-tests/src/test/resources/environment-configuration.yml @@ -81,3 +81,4 @@ instanceEnv: ZWE_configs_storage_size: 100 ZWE_zowe_verifyCertificates: true ZWE_components_gateway_server_ssl_enabled: true + ZWE_configs_apiml_security_auth_customAuthHeader: customHeader