summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@oath.com>2018-07-09 16:44:38 +0200
committerBjørn Christian Seime <bjorncs@oath.com>2018-07-26 13:07:33 +0200
commitc28a15d4b1bd40aae6097114b215303646f9fc34 (patch)
tree7332272bbf302f717cc4b55b177d06d94fc099f7
parent2525dfe510c7488f7992eac629fd9e69f8aebdae (diff)
Move AthenzPrincipalFilter to jdisc-security-filters
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java8
-rw-r--r--controller-server/src/main/resources/configdefinitions/athenz.def3
-rw-r--r--jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilter.java (renamed from controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java)13
-rw-r--r--jdisc-security-filters/src/main/resources/configdefinitions/athenz-principal-filter.def8
-rw-r--r--jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilterTest.java (renamed from controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java)139
5 files changed, 85 insertions, 86 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
index 0aa5c89c971..26cd9f2e9b8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java
@@ -4,6 +4,8 @@ package com.yahoo.vespa.hosted.controller.athenz.filter;
import com.google.inject.Inject;
import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
+import com.yahoo.jdisc.http.filter.security.athenz.AthenzPrincipalFilter;
+import com.yahoo.jdisc.http.filter.security.athenz.AthenzPrincipalFilterConfig;
import com.yahoo.jdisc.http.filter.security.cors.CorsFilterConfig;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
@@ -36,10 +38,10 @@ public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter {
private final String principalHeaderName;
@Inject
- public UserAuthWithAthenzPrincipalFilter(AthenzConfig athenzConfig, CorsFilterConfig corsConfig) {
- super(athenzConfig, corsConfig);
+ public UserAuthWithAthenzPrincipalFilter(AthenzPrincipalFilterConfig filterConfig, AthenzConfig athenzConfig, CorsFilterConfig corsConfig) {
+ super(filterConfig, corsConfig);
this.userAuthenticationPassThruAttribute = athenzConfig.userAuthenticationPassThruAttribute();
- this.principalHeaderName = athenzConfig.principalHeaderName();
+ this.principalHeaderName = filterConfig.principalHeaderName();
}
@Override
diff --git a/controller-server/src/main/resources/configdefinitions/athenz.def b/controller-server/src/main/resources/configdefinitions/athenz.def
index 8026c0d7f44..f8d65c25e47 100644
--- a/controller-server/src/main/resources/configdefinitions/athenz.def
+++ b/controller-server/src/main/resources/configdefinitions/athenz.def
@@ -42,6 +42,3 @@ service.privateKeySecretName string
# Expiry of service principal token and certificate
service.credentialsExpiryMinutes int default=43200 # 30 days
-
-# Path to athenz.conf file
-athenzConfFile string
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilter.java
index b7ede7635c6..ad6c82138e1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java
+++ b/jdisc-security-filters/src/main/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilter.java
@@ -1,5 +1,5 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.athenz.filter;
+// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter.security.athenz;
import com.google.inject.Inject;
import com.yahoo.jdisc.Response;
@@ -9,9 +9,7 @@ import com.yahoo.jdisc.http.filter.security.cors.CorsRequestFilterBase;
import com.yahoo.vespa.athenz.api.AthenzPrincipal;
import com.yahoo.vespa.athenz.api.NToken;
import com.yahoo.vespa.athenz.utils.AthenzIdentities;
-import com.yahoo.vespa.athenz.utils.ntoken.AthenzConfTruststore;
import com.yahoo.vespa.athenz.utils.ntoken.NTokenValidator;
-import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig;
import java.nio.file.Paths;
import java.security.cert.X509Certificate;
@@ -31,15 +29,16 @@ import java.util.Set;
*
* @author bjorncs
*/
-// TODO bjorncs: Move this class to jdisc-security-filters bundle
public class AthenzPrincipalFilter extends CorsRequestFilterBase {
private final NTokenValidator validator;
private final String principalTokenHeader;
@Inject
- public AthenzPrincipalFilter(AthenzConfig athenzConfig, CorsFilterConfig corsConfig) {
- this(new NTokenValidator(Paths.get(athenzConfig.athenzConfFile())), athenzConfig.principalHeaderName(), new HashSet<>(corsConfig.allowedUrls()));
+ public AthenzPrincipalFilter(AthenzPrincipalFilterConfig athenzPrincipalFilterConfig, CorsFilterConfig corsConfig) {
+ this(new NTokenValidator(Paths.get(athenzPrincipalFilterConfig.athenzConfFile())),
+ athenzPrincipalFilterConfig.principalHeaderName(),
+ new HashSet<>(corsConfig.allowedUrls()));
}
AthenzPrincipalFilter(NTokenValidator validator,
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
new file mode 100644
index 00000000000..59e481b0d80
--- /dev/null
+++ b/jdisc-security-filters/src/main/resources/configdefinitions/athenz-principal-filter.def
@@ -0,0 +1,8 @@
+# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+namespace=jdisc.http.filter.security.athenz
+
+# Principal header name
+principalHeaderName string default="Athenz-Principal-Auth"
+
+# Path to athenz.conf file
+athenzConfFile string
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilterTest.java
index d6481b741dd..be5ab9c1d77 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilterTest.java
+++ b/jdisc-security-filters/src/test/java/com/yahoo/jdisc/http/filter/security/athenz/AthenzPrincipalFilterTest.java
@@ -1,7 +1,6 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.athenz.filter;
+package com.yahoo.jdisc.http.filter.security.athenz;
-import com.yahoo.application.container.handler.Request;
import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.handler.ContentChannel;
import com.yahoo.jdisc.handler.ReadableContentChannel;
@@ -14,9 +13,7 @@ import com.yahoo.vespa.athenz.api.NToken;
import com.yahoo.vespa.athenz.tls.KeyAlgorithm;
import com.yahoo.vespa.athenz.tls.KeyUtils;
import com.yahoo.vespa.athenz.tls.X509CertificateBuilder;
-import com.yahoo.vespa.athenz.utils.ntoken.AthenzTruststore;
import com.yahoo.vespa.athenz.utils.ntoken.NTokenValidator;
-import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper;
import org.junit.Before;
import org.junit.Test;
@@ -29,20 +26,22 @@ import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED;
import static com.yahoo.vespa.athenz.tls.SignatureAlgorithm.SHA256_WITH_RSA;
+import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.joining;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
/**
* @author bjorncs
@@ -56,98 +55,115 @@ public class AthenzPrincipalFilterTest {
private static final String ORIGIN = "http://localhost";
private static final Set<String> CORS_ALLOWED_URLS = singleton(ORIGIN);
- private NTokenValidatorMock validator;
- private ResponseHandlerMock responseHandler;
+ private NTokenValidator validator;
@Before
public void before() {
- this.validator = new NTokenValidatorMock();
- this.responseHandler = new ResponseHandlerMock();
+ validator = mock(NTokenValidator.class);
}
@Test
public void valid_ntoken_is_accepted() {
- Request request = defaultRequest();
-
+ DiscFilterRequest request = createRequestMock();
AthenzPrincipal principal = new AthenzPrincipal(IDENTITY, NTOKEN);
- validator.add(NTOKEN, principal);
+ when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken());
+ when(request.getClientCertificateChain()).thenReturn(emptyList());
+ when(validator.validate(NTOKEN)).thenReturn(principal);
AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS);
- DiscFilterRequest filterRequest = new ApplicationRequestToDiscFilterRequestWrapper(request);
- filter.filter(filterRequest, new ResponseHandlerMock());
+ filter.filter(request, new ResponseHandlerMock());
+
+ verify(request).setUserPrincipal(principal);
+ }
- assertEquals(principal, filterRequest.getUserPrincipal());
+ private DiscFilterRequest createRequestMock() {
+ DiscFilterRequest request = mock(DiscFilterRequest.class);
+ when(request.getHeader("Origin")).thenReturn(ORIGIN);
+ return request;
}
@Test
public void missing_token_and_certificate_is_unauthorized() {
+ DiscFilterRequest request = createRequestMock();
+ when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(null);
+ when(request.getClientCertificateChain()).thenReturn(emptyList());
+
+ ResponseHandlerMock responseHandler = new ResponseHandlerMock();
+
AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS);
- DiscFilterRequest filterRequest = new ApplicationRequestToDiscFilterRequestWrapper(new Request("/"));
- filter.filter(filterRequest, responseHandler);
+ filter.filter(request, responseHandler);
assertUnauthorized(responseHandler, "Unable to authenticate Athenz identity");
}
@Test
public void invalid_token_is_unauthorized() {
- Request request = defaultRequest();
+ DiscFilterRequest request = createRequestMock();
+ String errorMessage = "Invalid token";
+ when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken());
+ when(request.getClientCertificateChain()).thenReturn(emptyList());
+ when(validator.validate(NTOKEN)).thenThrow(new NTokenValidator.InvalidTokenException(errorMessage));
+
+ ResponseHandlerMock responseHandler = new ResponseHandlerMock();
AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS);
- DiscFilterRequest filterRequest = new ApplicationRequestToDiscFilterRequestWrapper(request);
- filter.filter(filterRequest, responseHandler);
+ filter.filter(request, responseHandler);
- String errorMessage = "Invalid token";
assertUnauthorized(responseHandler, errorMessage);
}
@Test
public void certificate_is_accepted() {
+ DiscFilterRequest request = createRequestMock();
+ when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(null);
+ when(request.getClientCertificateChain()).thenReturn(singletonList(CERTIFICATE));
+
+ ResponseHandlerMock responseHandler = new ResponseHandlerMock();
+
AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS);
- DiscFilterRequest filterRequest = new ApplicationRequestToDiscFilterRequestWrapper(new Request("/"), singletonList(CERTIFICATE));
- filter.filter(filterRequest, responseHandler);
+ filter.filter(request, responseHandler);
AthenzPrincipal expectedPrincipal = new AthenzPrincipal(IDENTITY);
- assertEquals(expectedPrincipal, filterRequest.getUserPrincipal());
+ verify(request).setUserPrincipal(expectedPrincipal);
}
@Test
public void both_ntoken_and_certificate_is_accepted() {
- Request request = defaultRequest();
-
+ DiscFilterRequest request = createRequestMock();
AthenzPrincipal principalWithToken = new AthenzPrincipal(IDENTITY, NTOKEN);
- validator.add(NTOKEN, principalWithToken);
+ when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken());
+ when(request.getClientCertificateChain()).thenReturn(singletonList(CERTIFICATE));
+ when(validator.validate(NTOKEN)).thenReturn(principalWithToken);
+
+ ResponseHandlerMock responseHandler = new ResponseHandlerMock();
AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS);
- DiscFilterRequest filterRequest = new ApplicationRequestToDiscFilterRequestWrapper(request, singletonList(CERTIFICATE));
- filter.filter(filterRequest, responseHandler);
+ filter.filter(request, responseHandler);
- assertEquals(principalWithToken, filterRequest.getUserPrincipal());
+ verify(request).setUserPrincipal(principalWithToken);
}
@Test
public void conflicting_ntoken_and_certificate_is_unauthorized() {
- Request request = defaultRequest();
- validator.add(NTOKEN, new AthenzPrincipal(IDENTITY));
-
+ DiscFilterRequest request = createRequestMock();
AthenzUser conflictingIdentity = AthenzUser.fromUserId("mallory");
- DiscFilterRequest filterRequest = new ApplicationRequestToDiscFilterRequestWrapper(request, singletonList(createSelfSignedCertificate(conflictingIdentity)));
+ when(request.getHeader(ATHENZ_PRINCIPAL_HEADER)).thenReturn(NTOKEN.getRawToken());
+ when(request.getClientCertificateChain())
+ .thenReturn(singletonList(createSelfSignedCertificate(conflictingIdentity)));
+ when(validator.validate(NTOKEN)).thenReturn(new AthenzPrincipal(IDENTITY));
+
+ ResponseHandlerMock responseHandler = new ResponseHandlerMock();
+
AthenzPrincipalFilter filter = new AthenzPrincipalFilter(validator, ATHENZ_PRINCIPAL_HEADER, CORS_ALLOWED_URLS);
- filter.filter(filterRequest, responseHandler);
+ filter.filter(request, responseHandler);
assertUnauthorized(responseHandler, "Identity in principal token does not match x509 CN");
}
- private static Request defaultRequest() {
- Request request = new Request("/");
- request.getHeaders().add("Origin", ORIGIN);
- request.getHeaders().add(ATHENZ_PRINCIPAL_HEADER, NTOKEN.getRawToken());
- return request;
- }
-
private static void assertUnauthorized(ResponseHandlerMock responseHandler, String expectedMessageSubstring) {
- assertNotNull(responseHandler.response);
- assertEquals(UNAUTHORIZED, responseHandler.response.getStatus());
- assertTrue(responseHandler.getResponseContent().contains(expectedMessageSubstring));
+ assertThat(responseHandler.response, notNullValue());
+ assertThat(responseHandler.response.getStatus(), equalTo(UNAUTHORIZED));
+ assertThat(responseHandler.getResponseContent(), containsString(expectedMessageSubstring));
}
private static class ResponseHandlerMock implements ResponseHandler {
@@ -172,29 +188,6 @@ public class AthenzPrincipalFilterTest {
}
- private static class NTokenValidatorMock extends NTokenValidator {
-
- private final Map<NToken, AthenzPrincipal> validTokens = new HashMap<>();
-
- NTokenValidatorMock() {
- super((AthenzTruststore)null);
- }
-
- public NTokenValidatorMock add(NToken token, AthenzPrincipal principal) {
- validTokens.put(token, principal);
- return this;
- }
-
- @Override
- public AthenzPrincipal validate(NToken token) throws InvalidTokenException {
- if (!validTokens.containsKey(token)) {
- throw new InvalidTokenException("Invalid token");
- }
- return validTokens.get(token);
- }
-
- }
-
private static X509Certificate createSelfSignedCertificate(AthenzIdentity identity) {
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA, 512);
X500Principal x500Name = new X500Principal("CN="+ identity.getFullName());