KNOX-1072 - Add Client Cert Required Capability to KnoxToken Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/7b4755d5 Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/7b4755d5 Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/7b4755d5
Branch: refs/heads/KNOX-998-Package_Restructuring Commit: 7b4755d57c8998d1aed62c100124b8a94a3427db Parents: 145ed5d Author: Larry McCay <lmc...@hortonworks.com> Authored: Thu Sep 28 19:27:38 2017 -0400 Committer: Larry McCay <lmc...@hortonworks.com> Committed: Thu Sep 28 19:27:54 2017 -0400 ---------------------------------------------------------------------- .../service/knoxtoken/TokenResource.java | 35 +++++ .../knoxtoken/TokenServiceResourceTest.java | 144 +++++++++++++++++++ 2 files changed, 179 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/7b4755d5/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java index 43dd526..9d8bae3 100644 --- a/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java +++ b/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java @@ -19,6 +19,7 @@ package org.apache.hadoop.gateway.service.knoxtoken; import java.io.IOException; import java.security.Principal; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; @@ -54,12 +55,16 @@ public class TokenResource { private static final String TOKEN_AUDIENCES_PARAM = "knox.token.audiences"; private static final String TOKEN_TARGET_URL = "knox.token.target.url"; private static final String TOKEN_CLIENT_DATA = "knox.token.client.data"; + private static final String TOKEN_CLIENT_CERT_REQUIRED = "knox.token.client.cert.required"; + private static final String TOKEN_ALLOWED_PRINCIPALS = "knox.token.allowed.principals"; static final String RESOURCE_PATH = "knoxtoken/api/v1/token"; private static TokenServiceMessages log = MessagesFactory.get( TokenServiceMessages.class ); private long tokenTTL = 30000l; private List<String> targetAudiences = new ArrayList<>(); private String tokenTargetUrl = null; private Map<String,Object> tokenClientDataMap = null; + private ArrayList<String> allowedDNs = new ArrayList<>(); + private boolean clientCertRequired = false; @Context HttpServletRequest request; @@ -81,6 +86,17 @@ public class TokenResource { } } + String clientCert = context.getInitParameter(TOKEN_CLIENT_CERT_REQUIRED); + clientCertRequired = "true".equals(clientCert); + + String principals = context.getInitParameter(TOKEN_ALLOWED_PRINCIPALS); + if (principals != null) { + String[] dns = principals.split(";"); + for (int i = 0; i < dns.length; i++) { + allowedDNs.add(dns[i]); + } + } + String ttl = context.getInitParameter(TOKEN_TTL_PARAM); if (ttl != null) { try { @@ -113,7 +129,26 @@ public class TokenResource { return getAuthenticationToken(); } + private X509Certificate extractCertificate(HttpServletRequest req) { + X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate"); + if (null != certs && certs.length > 0) { + return certs[0]; + } + return null; + } + private Response getAuthenticationToken() { + if (clientCertRequired) { + X509Certificate cert = extractCertificate(request); + if (cert != null) { + if (!allowedDNs.contains(cert.getSubjectDN().getName())) { + return Response.status(403).entity("{ \"Unable to get token - untrusted client cert.\" }").build(); + } + } + else { + return Response.status(403).entity("{ \"Unable to get token - client cert required.\" }").build(); + } + } GatewayServices services = (GatewayServices) request.getServletContext() .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); http://git-wip-us.apache.org/repos/asf/knox/blob/7b4755d5/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java index bddd13d..b4e51e6 100644 --- a/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java +++ b/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.gateway.service.knoxtoken; +import org.apache.hadoop.gateway.security.PrimaryPrincipal; import org.apache.hadoop.gateway.service.knoxtoken.TokenResource; import org.apache.hadoop.gateway.services.GatewayServices; import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority; @@ -49,6 +50,7 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.Principal; +import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; @@ -203,6 +205,148 @@ public class TokenServiceResourceTest { assertTrue(audiences.contains("recipient2")); } + @Test + public void testValidClientCert() throws Exception { + + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.expect(context.getInitParameter("knox.token.client.cert.required")).andReturn("true"); + EasyMock.expect(context.getInitParameter("knox.token.allowed.principals")).andReturn("CN=localhost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US"); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes(); + X509Certificate trustedCertMock = EasyMock.createMock(X509Certificate.class); + EasyMock.expect(trustedCertMock.getSubjectDN()).andReturn(new PrimaryPrincipal("CN=localhost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US")).anyTimes(); + ArrayList<X509Certificate> certArrayList = new ArrayList<X509Certificate>(); + certArrayList.add(trustedCertMock); + X509Certificate[] certs = {}; + EasyMock.expect(request.getAttribute("javax.servlet.request.X509Certificate")).andReturn(certArrayList.toArray(certs)).anyTimes(); + + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("alice").anyTimes(); + EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes(); + + GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services); + + JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey); + EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority); + + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.getWriter()).andReturn(printWriter); + + EasyMock.replay(principal, services, context, request, response, trustedCertMock); + + TokenResource tr = new TokenResource(); + tr.request = request; + tr.response = response; + tr.context = context; + tr.init(); + + // Issue a token + Response retResponse = tr.doGet(); + + assertEquals(200, retResponse.getStatus()); + + // Parse the response + String retString = writer.toString(); + String accessToken = getTagValue(retString, "access_token"); + assertNotNull(accessToken); + String expiry = getTagValue(retString, "expires_in"); + assertNotNull(expiry); + + // Verify the token + JWTToken parsedToken = new JWTToken(accessToken); + assertEquals("alice", parsedToken.getSubject()); + assertTrue(authority.verifyToken(parsedToken)); + } + + @Test + public void testValidClientCertWrongUser() throws Exception { + + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.expect(context.getInitParameter("knox.token.client.cert.required")).andReturn("true"); + EasyMock.expect(context.getInitParameter("knox.token.allowed.principals")).andReturn("CN=remotehost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US"); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes(); + X509Certificate trustedCertMock = EasyMock.createMock(X509Certificate.class); + EasyMock.expect(trustedCertMock.getSubjectDN()).andReturn(new PrimaryPrincipal("CN=localhost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US")).anyTimes(); + ArrayList<X509Certificate> certArrayList = new ArrayList<X509Certificate>(); + certArrayList.add(trustedCertMock); + X509Certificate[] certs = {}; + EasyMock.expect(request.getAttribute("javax.servlet.request.X509Certificate")).andReturn(certArrayList.toArray(certs)).anyTimes(); + + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("alice").anyTimes(); + EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes(); + + GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services); + + JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey); + EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority); + + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.getWriter()).andReturn(printWriter); + + EasyMock.replay(principal, services, context, request, response, trustedCertMock); + + TokenResource tr = new TokenResource(); + tr.request = request; + tr.response = response; + tr.context = context; + tr.init(); + + // Issue a token + Response retResponse = tr.doGet(); + + assertEquals(403, retResponse.getStatus()); + } + + @Test + public void testMissingClientCert() throws Exception { + + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.expect(context.getInitParameter("knox.token.client.cert.required")).andReturn("true"); + EasyMock.expect(context.getInitParameter("knox.token.allowed.principals")).andReturn("CN=remotehost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US"); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes(); + EasyMock.expect(request.getAttribute("javax.servlet.request.X509Certificate")).andReturn(null).anyTimes(); + + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("alice").anyTimes(); + EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes(); + + GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services); + + JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey); + EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority); + + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.getWriter()).andReturn(printWriter); + + EasyMock.replay(principal, services, context, request, response); + + TokenResource tr = new TokenResource(); + tr.request = request; + tr.response = response; + tr.context = context; + tr.init(); + + // Issue a token + Response retResponse = tr.doGet(); + + assertEquals(403, retResponse.getStatus()); + } + private String getTagValue(String token, String tagName) { String searchString = tagName + "\":"; String value = token.substring(token.indexOf(searchString) + searchString.length());