diff options
author | Bjørn Christian Seime <bjorncs@oath.com> | 2018-10-04 15:41:15 +0200 |
---|---|---|
committer | Bjørn Christian Seime <bjorncs@oath.com> | 2018-10-04 15:41:15 +0200 |
commit | db93461fbcb56af55d08a81d9b93db07846646a0 (patch) | |
tree | 3d00b6c7cf7c94ba445a76fdc03d315910c11ead | |
parent | 963d16440f7534e39cbe36be695c272a95b7e3fb (diff) |
Add passthrough mode to AthenzPrincipalFilter
- No http response when passthrough mode is enable
- Introduce attributes for error code and message
- Introduce attribute for AthenzPrincipal instance
3 files changed, 66 insertions, 17 deletions
diff --git a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilter.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilter.java index ad6c82138e1..5b79b806190 100644 --- a/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilter.java +++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilter.java @@ -31,22 +31,31 @@ import java.util.Set; */ public class AthenzPrincipalFilter extends CorsRequestFilterBase { + private static final String RESULT_ATTRIBUTE_PREFIX = "jdisc-security-filters.athenz-principal-filter.result"; + public static final String RESULT_ERROR_CODE_ATTRIBUTE = RESULT_ATTRIBUTE_PREFIX + ".error.code"; + public static final String RESULT_ERROR_MESSAGE_ATTRIBUTE = RESULT_ATTRIBUTE_PREFIX + ".error.message"; + public static final String RESULT_PRINCIPAL = RESULT_ATTRIBUTE_PREFIX + ".principal"; + private final NTokenValidator validator; private final String principalTokenHeader; + private final boolean passthroughMode; @Inject public AthenzPrincipalFilter(AthenzPrincipalFilterConfig athenzPrincipalFilterConfig, CorsFilterConfig corsConfig) { this(new NTokenValidator(Paths.get(athenzPrincipalFilterConfig.athenzConfFile())), athenzPrincipalFilterConfig.principalHeaderName(), - new HashSet<>(corsConfig.allowedUrls())); + new HashSet<>(corsConfig.allowedUrls()), + athenzPrincipalFilterConfig.passthroughMode()); } AthenzPrincipalFilter(NTokenValidator validator, String principalTokenHeader, - Set<String> corsAllowedUrls) { + Set<String> corsAllowedUrls, + boolean passthroughMode) { super(corsAllowedUrls); this.validator = validator; this.principalTokenHeader = principalTokenHeader; + this.passthroughMode = passthroughMode; } @Override @@ -61,7 +70,7 @@ public class AthenzPrincipalFilter extends CorsRequestFilterBase { if (!certificatePrincipal.isPresent() && !nTokenPrincipal.isPresent()) { String errorMessage = "Unable to authenticate Athenz identity. " + "Either client certificate or principal token is required."; - return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, errorMessage)); + return createResponse(request, Response.Status.UNAUTHORIZED, errorMessage); } if (certificatePrincipal.isPresent() && nTokenPrincipal.isPresent() && !certificatePrincipal.get().getIdentity().equals(nTokenPrincipal.get().getIdentity())) { @@ -69,14 +78,15 @@ public class AthenzPrincipalFilter extends CorsRequestFilterBase { "Identity in principal token does not match x509 CN: token-identity=%s, cert-identity=%s", nTokenPrincipal.get().getIdentity().getFullName(), certificatePrincipal.get().getIdentity().getFullName()); - return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, errorMessage)); + return createResponse(request, Response.Status.UNAUTHORIZED, errorMessage); } AthenzPrincipal principal = nTokenPrincipal.orElseGet(certificatePrincipal::get); request.setUserPrincipal(principal); request.setRemoteUser(principal.getName()); + request.setAttribute(RESULT_PRINCIPAL, principal); return Optional.empty(); } catch (Exception e) { - return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, e.getMessage())); + return createResponse(request, Response.Status.UNAUTHORIZED, e.getMessage()); } } @@ -92,4 +102,14 @@ public class AthenzPrincipalFilter extends CorsRequestFilterBase { .map(NToken::new); } + private Optional<ErrorResponse> createResponse(DiscFilterRequest request, int statusCode, String message) { + request.setAttribute(RESULT_ERROR_CODE_ATTRIBUTE, statusCode); + request.setAttribute(RESULT_ERROR_MESSAGE_ATTRIBUTE, message); + if (passthroughMode) { + return Optional.empty(); + } else { + return Optional.of(new ErrorResponse(statusCode, message)); + } + } + } diff --git a/jdisc-security-filters/src/main/resources/configdefinitions/athenz-principal-filter.def b/jdisc-security-filters/src/main/resources/configdefinitions/athenz-principal-filter.def index 59e481b0d80..96f79f6d664 100644 --- a/jdisc-security-filters/src/main/resources/configdefinitions/athenz-principal-filter.def +++ b/jdisc-security-filters/src/main/resources/configdefinitions/athenz-principal-filter.def @@ -6,3 +6,6 @@ principalHeaderName string default="Athenz-Principal-Auth" # Path to athenz.conf file athenzConfFile string + +# Pass-through mode +passthroughMode bool default=false diff --git a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilterTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilterTest.java index fdab450b435..3bbc606cf2b 100644 --- a/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilterTest.java +++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilterTest.java @@ -14,6 +14,7 @@ import com.yahoo.security.KeyAlgorithm; import com.yahoo.security.KeyUtils; import com.yahoo.security.X509CertificateBuilder; import com.yahoo.vespa.athenz.utils.ntoken.NTokenValidator; +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; @@ -40,6 +41,7 @@ import static java.util.stream.Collectors.joining; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -72,10 +74,10 @@ public class AthenzPrincipalFilterTest { when(request.getClientCertificateChain()).thenReturn(emptyList()); when(validator.validate(NTOKEN)).thenReturn(principal); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); + AthenzPrincipalFilter filter = createFilter(false); filter.filter(request, new ResponseHandlerMock()); - verify(request).setUserPrincipal(principal); + assertAuthenticated(request, principal); } private DiscFilterRequest createRequestMock() { @@ -92,10 +94,10 @@ public class AthenzPrincipalFilterTest { ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); + AthenzPrincipalFilter filter = createFilter(false); filter.filter(request, responseHandler); - assertUnauthorized(responseHandler, "Unable to authenticate Athenz identity"); + assertUnauthorized(request, responseHandler, "Unable to authenticate Athenz identity"); } @Test @@ -108,10 +110,10 @@ public class AthenzPrincipalFilterTest { ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); + AthenzPrincipalFilter filter = createFilter(false); filter.filter(request, responseHandler); - assertUnauthorized(responseHandler, errorMessage); + assertUnauthorized(request, responseHandler, errorMessage); } @Test @@ -122,11 +124,16 @@ public class AthenzPrincipalFilterTest { ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); + AthenzPrincipalFilter filter = createFilter(false); filter.filter(request, responseHandler); AthenzPrincipal expectedPrincipal = new AthenzPrincipal(IDENTITY); + assertAuthenticated(request, expectedPrincipal); + } + + private void assertAuthenticated(DiscFilterRequest request, AthenzPrincipal expectedPrincipal) { verify(request).setUserPrincipal(expectedPrincipal); + verify(request).setAttribute(AthenzPrincipalFilter.RESULT_PRINCIPAL, expectedPrincipal); } @Test @@ -139,10 +146,10 @@ public class AthenzPrincipalFilterTest { ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); + AthenzPrincipalFilter filter = createFilter(false); filter.filter(request, responseHandler); - verify(request).setUserPrincipal(principalWithToken); + assertAuthenticated(request, principalWithToken); } @Test @@ -156,16 +163,35 @@ public class AthenzPrincipalFilterTest { ResponseHandlerMock responseHandler = new ResponseHandlerMock(); - AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS); + AthenzPrincipalFilter filter = createFilter(false); filter.filter(request, responseHandler); - assertUnauthorized(responseHandler, "Identity in principal token does not match x509 CN"); + assertUnauthorized(request, responseHandler, "Identity in principal token does not match x509 CN"); + } + + @Test + public void no_response_produced_when_passthrough_mode_is_enabled() { + DiscFilterRequest request = createRequestMock(); + when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(null); + when(request.getClientCertificateChain()).thenReturn(emptyList()); + + ResponseHandlerMock responseHandler = new ResponseHandlerMock(); + + AthenzPrincipalFilter filter = createFilter(true); + filter.filter(request, responseHandler); + + assertThat(responseHandler.response, nullValue()); + } + + private AthenzPrincipalFilter createFilter(boolean passthroughModeEnabled) { + return new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS, passthroughModeEnabled); } - private static void assertUnauthorized(ResponseHandlerMock responseHandler, String expectedMessageSubstring) { + private static void assertUnauthorized(DiscFilterRequest request, ResponseHandlerMock responseHandler, String expectedMessageSubstring) { assertThat(responseHandler.response, notNullValue()); assertThat(responseHandler.response.getStatus(), equalTo(UNAUTHORIZED)); assertThat(responseHandler.getResponseContent(), containsString(expectedMessageSubstring)); + verify(request).setAttribute(AthenzPrincipalFilter.RESULT_ERROR_CODE_ATTRIBUTE, UNAUTHORIZED); } private static class ResponseHandlerMock implements ResponseHandler { |