This is an automated email from the ASF dual-hosted git repository.
lmccay pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new eb831f2 KNOX-1711 - Provide Endpoint Public Cert for KnoxToken
eb831f2 is described below
commit eb831f2b1e4174239c804da1c8cdb19caa47c092
Author: Larry McCay <[email protected]>
AuthorDate: Wed Dec 26 17:44:50 2018 -0500
KNOX-1711 - Provide Endpoint Public Cert for KnoxToken
---
gateway-service-knoxtoken/pom.xml | 5 +
.../gateway/service/knoxtoken/TokenResource.java | 69 ++++++---
.../service/knoxtoken/TokenServiceMessages.java | 5 +-
gateway-shell/pom.xml | 10 ++
.../apache/knox/gateway/shell/ClientContext.java | 17 ++-
.../org/apache/knox/gateway/shell/Credentials.java | 14 +-
.../apache/knox/gateway/shell/ErrorResponse.java | 4 +-
.../org/apache/knox/gateway/shell/KnoxSession.java | 154 +++++++++++++++------
.../{ErrorResponse.java => KnoxShellMessages.java} | 22 ++-
.../shell/KnoxTokenCredentialCollector.java | 64 ++++++---
.../apache/knox/gateway/shell/KnoxSessionTest.java | 75 ++++++++++
.../shell/KnoxTokenCredentialCollectorTest.java | 74 +++++++++-
.../security/impl/X509CertificateUtil.java | 17 ++-
13 files changed, 416 insertions(+), 114 deletions(-)
diff --git a/gateway-service-knoxtoken/pom.xml
b/gateway-service-knoxtoken/pom.xml
index c080327..dbc3cf9 100644
--- a/gateway-service-knoxtoken/pom.xml
+++ b/gateway-service-knoxtoken/pom.xml
@@ -69,6 +69,11 @@
</dependency>
<dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index 526f7d9..0da2def 100644
---
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
+++
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
@@ -17,7 +17,10 @@
*/
package org.apache.knox.gateway.service.knoxtoken;
+import java.security.KeyStoreException;
import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Map;
@@ -33,8 +36,12 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
+
+import org.apache.commons.codec.binary.Base64;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.security.KeystoreService;
+import org.apache.knox.gateway.services.security.KeystoreServiceException;
import org.apache.knox.gateway.services.security.token.JWTokenAuthority;
import org.apache.knox.gateway.services.security.token.TokenServiceException;
import org.apache.knox.gateway.services.security.token.impl.JWT;
@@ -43,12 +50,13 @@ import org.apache.knox.gateway.util.JsonUtils;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.APPLICATION_XML;
-@Path( TokenResource.RESOURCE_PATH )
+@Path(TokenResource.RESOURCE_PATH)
public class TokenResource {
private static final String EXPIRES_IN = "expires_in";
private static final String TOKEN_TYPE = "token_type";
private static final String ACCESS_TOKEN = "access_token";
private static final String TARGET_URL = "target_url";
+ private static final String ENDPOINT_PUBLIC_CERT = "endpoint_public_cert";
private static final String BEARER = "Bearer";
private static final String TOKEN_TTL_PARAM = "knox.token.ttl";
private static final String TOKEN_AUDIENCES_PARAM = "knox.token.audiences";
@@ -59,14 +67,16 @@ public class TokenResource {
private static final String TOKEN_SIG_ALG = "knox.token.sigalg";
private static final long TOKEN_TTL_DEFAULT = 30000L;
static final String RESOURCE_PATH = "knoxtoken/api/v1/token";
- private static TokenServiceMessages log = MessagesFactory.get(
TokenServiceMessages.class );
+ private static final String TARGET_ENDPOINT_PULIC_CERT_PEM =
"knox.token.target.endpoint.cert.pem";
+ private static TokenServiceMessages log =
MessagesFactory.get(TokenServiceMessages.class);
private long tokenTTL = TOKEN_TTL_DEFAULT;
private List<String> targetAudiences = new ArrayList<>();
private String tokenTargetUrl;
- private Map<String,Object> tokenClientDataMap;
+ private Map<String, Object> tokenClientDataMap;
private List<String> allowedDNs = new ArrayList<>();
private boolean clientCertRequired;
private String signatureAlgorithm = "RS256";
+ private String endpointPublicCert;
@Context
HttpServletRequest request;
@@ -86,7 +96,7 @@ public class TokenResource {
}
String clientCert = context.getInitParameter(TOKEN_CLIENT_CERT_REQUIRED);
- clientCertRequired = Boolean.parseBoolean(clientCert);
+ clientCertRequired = "true".equals(clientCert);
String principals = context.getInitParameter(TOKEN_ALLOWED_PRINCIPALS);
if (principals != null) {
@@ -104,8 +114,7 @@ public class TokenResource {
log.invalidTokenTTLEncountered(ttl);
tokenTTL = TOKEN_TTL_DEFAULT;
}
- }
- catch (NumberFormatException nfe) {
+ } catch (NumberFormatException nfe) {
log.invalidTokenTTLEncountered(ttl);
}
}
@@ -123,6 +132,11 @@ public class TokenResource {
if (sigAlg != null) {
signatureAlgorithm = sigAlg;
}
+
+ String targetEndpointPublicCert =
context.getInitParameter(TARGET_ENDPOINT_PULIC_CERT_PEM);
+ if (targetEndpointPublicCert != null) {
+ endpointPublicCert = targetEndpointPublicCert;
+ }
}
@GET
@@ -140,7 +154,7 @@ public class TokenResource {
private X509Certificate extractCertificate(HttpServletRequest req) {
X509Certificate[] certs = (X509Certificate[])
req.getAttribute("javax.servlet.request.X509Certificate");
if (null != certs && certs.length > 0) {
- return certs[0];
+ return certs[0];
}
return null;
}
@@ -149,23 +163,38 @@ public class TokenResource {
if (clientCertRequired) {
X509Certificate cert = extractCertificate(request);
if (cert != null) {
- if
(!allowedDNs.contains(cert.getSubjectDN().getName().replaceAll("\\s+",""))) {
+ if
(!allowedDNs.contains(cert.getSubjectDN().getName().replaceAll("\\s+", ""))) {
return Response.status(403).entity("{ \"Unable to get token -
untrusted client cert.\" }").build();
}
- }
- else {
+ } else {
return Response.status(403).entity("{ \"Unable to get token - client
cert required.\" }").build();
}
}
GatewayServices services = (GatewayServices) request.getServletContext()
- .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+ .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
JWTokenAuthority ts = services.getService(GatewayServices.TOKEN_SERVICE);
- Principal p = request.getUserPrincipal();
+ Principal p = ((HttpServletRequest) request).getUserPrincipal();
long expires = getExpiry();
+ if (endpointPublicCert == null) {
+ // acquire PEM for gateway-identity of this gateway instance
+ KeystoreService ks =
services.getService(GatewayServices.KEYSTORE_SERVICE);
+ if (ks != null) {
+ try {
+ Certificate cert =
ks.getKeystoreForGateway().getCertificate("gateway-identity");
+ byte[] bytes = cert.getEncoded();
+ //Base64 encoder = new Base64(76, "\n".getBytes("ASCII"));
+ endpointPublicCert = Base64.encodeBase64String(bytes);
+ } catch (KeyStoreException | KeystoreServiceException |
CertificateEncodingException e) {
+ // assuming that certs will be properly provisioned across all
clients
+ log.unableToAcquireCertForEndpointClients(e);
+ }
+ }
+ }
+
try {
- JWT token;
+ JWT token = null;
if (targetAudiences.isEmpty()) {
token = ts.issueToken(p, signatureAlgorithm, expires);
} else {
@@ -185,16 +214,17 @@ public class TokenResource {
if (tokenClientDataMap != null) {
map.putAll(tokenClientDataMap);
}
+ if (endpointPublicCert != null) {
+ map.put(ENDPOINT_PUBLIC_CERT, endpointPublicCert);
+ }
String jsonResponse = JsonUtils.renderAsJsonString(map);
return Response.ok().entity(jsonResponse).build();
- }
- else {
+ } else {
return Response.serverError().build();
}
- }
- catch (TokenServiceException e) {
+ } catch (TokenServiceException e) {
log.unableToIssueToken(e);
}
return Response.ok().entity("{ \"Unable to acquire token.\" }").build();
@@ -212,11 +242,10 @@ public class TokenResource {
}
private long getExpiry() {
- long expiry;
+ long expiry = 0L;
if (tokenTTL == -1) {
expiry = -1;
- }
- else {
+ } else {
expiry = System.currentTimeMillis() + tokenTTL;
}
return expiry;
diff --git
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
index 4219d66..f9d97ad 100644
---
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
+++
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceMessages.java
@@ -63,4 +63,7 @@ public interface TokenServiceMessages {
@Message( level = MessageLevel.ERROR, text = "The original URL: {0} for
redirecting back after authentication is " +
"not valid according to the configured whitelist: {1}. See documentation
for KnoxSSO Whitelisting.")
void whiteListMatchFail(String original, String whitelist);
-}
\ No newline at end of file
+
+ @Message( level = MessageLevel.WARN, text = "Unable to acquire cert for
endpoint clients - assume trust will be provisioned separately: {0}.")
+ void unableToAcquireCertForEndpointClients(@StackTrace( level =
MessageLevel.DEBUG ) Exception e);
+}
diff --git a/gateway-shell/pom.xml b/gateway-shell/pom.xml
index e7c0c2a..35382cf 100644
--- a/gateway-shell/pom.xml
+++ b/gateway-shell/pom.xml
@@ -35,6 +35,16 @@
<groupId>org.apache.knox</groupId>
<artifactId>gateway-util-common</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-i18n</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.knox</groupId>
+ <artifactId>gateway-test-utils</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
index 0434e5a..3bb1339 100644
---
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
+++
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ClientContext.java
@@ -31,7 +31,7 @@ public class ClientContext {
private final ConnectionContext connectionContext;
private final KerberosContext kerberos;
- private ClientContext() {
+ public ClientContext() {
configuration = new MapConfiguration(new HashMap<>());
poolContext = new PoolContext(this);
socketContext = new SocketContext(this);
@@ -43,7 +43,7 @@ public class ClientContext {
private final ClientContext clientContext;
protected final Configuration configuration;
- Context(ClientContext clientContext, String prefix) {
+ private Context(ClientContext clientContext, String prefix) {
this.clientContext = clientContext;
this.configuration = new
SubsetConfiguration(clientContext.configuration, prefix);
}
@@ -54,6 +54,7 @@ public class ClientContext {
}
public static class PoolContext extends Context {
+
PoolContext(ClientContext clientContext) {
super(clientContext, "pool");
}
@@ -78,6 +79,7 @@ public class ClientContext {
}
public static class SocketContext extends Context {
+
SocketContext(ClientContext clientContext) {
super(clientContext, "socket");
}
@@ -129,6 +131,7 @@ public class ClientContext {
}
public static class ConnectionContext extends Context {
+
ConnectionContext(ClientContext clientContext) {
super(clientContext, "connection");
}
@@ -176,6 +179,11 @@ public class ClientContext {
return this;
}
+ public ConnectionContext withPublicCertPem(final String
endpointPublicCertPem) {
+ configuration.addProperty("endpointPublicCertPem",
endpointPublicCertPem);
+ return this;
+ }
+
public String truststoreLocation() {
return configuration.getString("truststoreLocation");
}
@@ -183,6 +191,10 @@ public class ClientContext {
public String truststorePass() {
return configuration.getString("truststorePass");
}
+
+ public String endpointPublicCertPem() {
+ return configuration.getString("endpointPublicCertPem");
+ }
}
/**
@@ -190,6 +202,7 @@ public class ClientContext {
* @since 1.3.0
*/
public static class KerberosContext extends Context {
+
KerberosContext(ClientContext clientContext) {
super(clientContext, "kerberos");
}
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/Credentials.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/Credentials.java
index 610bd16..13ac9bf 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/Credentials.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/Credentials.java
@@ -22,7 +22,7 @@ import java.util.List;
import java.util.ServiceLoader;
public class Credentials {
- List<CredentialCollector> collectors = new ArrayList<>();
+ List<CredentialCollector> collectors = new ArrayList<CredentialCollector>();
public Credentials add(String collectorType, String prompt, String name)
throws CredentialCollectionException {
@@ -37,6 +37,18 @@ public class Credentials {
return this;
}
+ public Credentials add(CredentialCollector collector, String prompt, String
name)
+ throws CredentialCollectionException {
+ if (collector == null) {
+ throw new CredentialCollectionException("Null CredentialCollector cannot
be added.");
+ }
+ collector.setPrompt(prompt);
+ collector.setName(name);
+ collectors.add(collector);
+
+ return this;
+ }
+
public void collect() throws CredentialCollectionException {
for (CredentialCollector collector : collectors) {
collector.collect();
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ErrorResponse.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ErrorResponse.java
index 92661e9..58a5ed6 100644
---
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ErrorResponse.java
+++
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ErrorResponse.java
@@ -23,8 +23,8 @@ class ErrorResponse extends RuntimeException {
HttpResponse response;
- ErrorResponse( HttpResponse response ) {
- super(String.valueOf(response.getStatusLine()));
+ ErrorResponse(String text, HttpResponse response) {
+ super(text + response.getStatusLine());
this.response = response;
}
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
index d7d962e..da7036a 100644
--- a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
+++ b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxSession.java
@@ -18,6 +18,9 @@
package org.apache.knox.gateway.shell;
import com.sun.security.auth.callback.TextCallbackHandler;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
@@ -48,12 +51,15 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.ssl.SSLContexts;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
+
+import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
@@ -70,6 +76,8 @@ import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -90,7 +98,10 @@ public class KnoxSession implements Closeable {
private static final String KNOX_CLIENT_TRUSTSTORE_DIR =
"KNOX_CLIENT_TRUSTSTORE_DIR";
private static final String DEFAULT_JAAS_FILE = "/jaas.conf";
public static final String JGSS_LOGIN_MOUDLE =
"com.sun.security.jgss.initiate";
+ public static final String END_CERTIFICATE = "-----END CERTIFICATE-----\n";
+ public static final String BEGIN_CERTIFICATE = "-----BEGIN
CERTIFICATE-----\n";
+ private static final KnoxShellMessages LOG =
MessagesFactory.get(KnoxShellMessages.class);
private boolean isKerberos;
String base;
@@ -114,6 +125,18 @@ public class KnoxSession implements Closeable {
return instance;
}
+ public static KnoxSession login( String url,
+ Map<String,String> headers,
+ String truststoreLocation,
+ String truststorePass ) throws
URISyntaxException {
+ KnoxSession instance = new KnoxSession(ClientContext.with(url)
+ .connection()
+
.withTruststore(truststoreLocation, truststorePass)
+ .end());
+ instance.setHeaders(headers);
+ return instance;
+ }
+
public static KnoxSession login( String url, String username, String
password ) throws URISyntaxException {
return new KnoxSession(ClientContext.with(username, password, url));
}
@@ -122,12 +145,11 @@ public class KnoxSession implements Closeable {
String truststoreLocation, String truststorePass ) throws
URISyntaxException {
return new KnoxSession(ClientContext.with(username, password, url)
- .connection().withTruststore(truststoreLocation,
truststorePass).end());
+ .connection().withTruststore(truststoreLocation,
truststorePass).end());
}
- public static KnoxSession loginInsecure(String url, String username, String
password) throws URISyntaxException {
- return new KnoxSession(ClientContext.with(username, password, url)
- .connection().secure(false).end());
+ public static KnoxSession login(ClientContext context) throws
URISyntaxException {
+ return new KnoxSession(context);
}
/**
@@ -170,10 +192,15 @@ public class KnoxSession implements Closeable {
return kerberosLogin(url, "", "", false);
}
+ public static KnoxSession loginInsecure(String url, String username, String
password) throws URISyntaxException {
+ return new KnoxSession(ClientContext.with(username, password, url)
+ .connection().secure(false).end());
+ }
+
protected KnoxSession() throws KnoxShellException, URISyntaxException {
}
- public KnoxSession( ClientContext clientContext) throws KnoxShellException,
URISyntaxException {
+ public KnoxSession( final ClientContext clientContext) throws
KnoxShellException, URISyntaxException {
this.executor = Executors.newCachedThreadPool();
this.base = clientContext.url();
@@ -194,19 +221,19 @@ public class KnoxSession implements Closeable {
} else {
trustStrategy = TrustSelfSignedStrategy.INSTANCE;
System.out.println("**************** WARNING ******************\n"
- + "This is an insecure client instance and may\n"
- + "leave the interactions subject to a man in\n"
- + "the middle attack. Please use the login()\n"
- + "method instead of loginInsecure() for any\n"
- + "sensitive or production usecases.\n"
- + "*******************************************");
+ + "This is an insecure client instance and may\n"
+ + "leave the interactions subject to a man in\n"
+ + "the middle attack. Please use the login()\n"
+ + "method instead of loginInsecure() for any\n"
+ + "sensitive or production usecases.\n"
+ + "*******************************************");
}
KeyStore trustStore = getTrustStore(clientContext);
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(trustStore,
trustStrategy).build();
Registry<ConnectionSocketFactory> registry =
RegistryBuilder.<ConnectionSocketFactory>create()
- .register("http", PlainConnectionSocketFactory.getSocketFactory())
- .register("https", new SSLConnectionSocketFactory(sslContext,
hostnameVerifier)).build();
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .register("https", new SSLConnectionSocketFactory(sslContext,
hostnameVerifier)).build();
// Pool
PoolingHttpClientConnectionManager connectionManager = new
PoolingHttpClientConnectionManager(registry);
@@ -214,17 +241,17 @@ public class KnoxSession implements Closeable {
connectionManager.setDefaultMaxPerRoute(clientContext.pool().defaultMaxPerRoute());
ConnectionConfig connectionConfig = ConnectionConfig.custom()
- .setBufferSize(clientContext.connection().bufferSize())
- .build();
+ .setBufferSize(clientContext.connection().bufferSize())
+ .build();
connectionManager.setDefaultConnectionConfig(connectionConfig);
SocketConfig socketConfig = SocketConfig.custom()
- .setSoKeepAlive(clientContext.socket().keepalive())
- .setSoLinger(clientContext.socket().linger())
- .setSoReuseAddress(clientContext.socket().reuseAddress())
- .setSoTimeout(clientContext.socket().timeout())
- .setTcpNoDelay(clientContext.socket().tcpNoDelay())
- .build();
+ .setSoKeepAlive(clientContext.socket().keepalive())
+ .setSoLinger(clientContext.socket().linger())
+ .setSoReuseAddress(clientContext.socket().reuseAddress())
+ .setSoTimeout(clientContext.socket().timeout())
+ .setTcpNoDelay(clientContext.socket().tcpNoDelay())
+ .build();
connectionManager.setDefaultSocketConfig(socketConfig);
// Auth
@@ -273,25 +300,24 @@ public class KnoxSession implements Closeable {
final Registry<AuthSchemeProvider> authSchemeRegistry =
RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true)).build();
- return HttpClients.custom()
- .setConnectionManager(connectionManager)
+ return HttpClients.custom().setConnectionManager(connectionManager)
.setDefaultAuthSchemeRegistry(authSchemeRegistry)
- .setDefaultCredentialsProvider(credentialsProvider)
- .build();
-
+ .setDefaultCredentialsProvider(credentialsProvider).build();
} else {
+ AuthCache authCache = new BasicAuthCache();
+ BasicScheme authScheme = new BasicScheme();
+ authCache.put(host, authScheme);
+ context = new BasicHttpContext();
+
context.setAttribute(org.apache.http.client.protocol.HttpClientContext.AUTH_CACHE,
+ authCache);
+
CredentialsProvider credentialsProvider = null;
if (clientContext.username() != null && clientContext.password() !=
null) {
credentialsProvider = new BasicCredentialsProvider();
- credentialsProvider.setCredentials(
- new AuthScope(host.getHostName(), host.getPort()),
- new UsernamePasswordCredentials(clientContext.username(),
clientContext.password()));
-
- AuthCache authCache = new BasicAuthCache();
- BasicScheme authScheme = new BasicScheme();
- authCache.put(host, authScheme);
- context = new BasicHttpContext();
-
context.setAttribute(org.apache.http.client.protocol.HttpClientContext.AUTH_CACHE,
authCache);
+ credentialsProvider
+ .setCredentials(new AuthScope(host.getHostName(), host.getPort()),
+ new UsernamePasswordCredentials(clientContext.username(),
+ clientContext.password()));
}
return HttpClients.custom()
.setConnectionManager(connectionManager)
@@ -301,12 +327,39 @@ public class KnoxSession implements Closeable {
}
+ protected X509Certificate generateCertificateFromBytes(byte[] certBytes)
throws CertificateException {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate)factory.generateCertificate(new
ByteArrayInputStream(certBytes));
+ }
+
private KeyStore getTrustStore(ClientContext clientContext) throws
GeneralSecurityException {
KeyStore ks;
String truststorePass = null;
+ // if a PEM file was provided create a keystore from that and use
+ // it as the truststore
+ String pem = clientContext.connection().endpointPublicCertPem();
+ if (pem != null) {
+ // strip delimiters
+ if (pem.contains("BEGIN")) {
+ pem = pem.substring(BEGIN_CERTIFICATE.length()-1,
+ pem.indexOf(END_CERTIFICATE.substring(0,
END_CERTIFICATE.length()-1)));
+ }
+ try {
+ byte[] bytes = Base64.decodeBase64(pem);
+ KeyStore keystore = KeyStore.getInstance("JKS");
+ keystore.load(null);
+ keystore.setCertificateEntry("knox-gateway",
generateCertificateFromBytes(bytes));
+
+ return keystore;
+ } catch (IOException e) {
+ LOG.unableToLoadProvidedPEMEncodedTrustedCert(e);
+ }
+ }
+
discoverTruststoreDetails(clientContext);
+ InputStream is = null;
try {
ks = KeyStore.getInstance("JKS");
File file = new File(clientContext.connection().truststoreLocation());
@@ -324,14 +377,13 @@ public class KnoxSession implements Closeable {
}
if (file.exists()) {
- try (InputStream is = Files.newInputStream(file.toPath())) {
- ks.load(is, truststorePass.toCharArray());
- }
+ is = Files.newInputStream(file.toPath());
+ ks.load(is, truststorePass.toCharArray());
}
else {
throw new KnoxShellException("Unable to find a truststore for secure
login."
+ "Please import the gateway-identity certificate into the JVM"
- + " truststore or set the truststore location ENV variables.");
+ + " truststore or set the truststore location ENV variables.");
}
} catch (KeyStoreException e) {
throw new KnoxShellException("Unable to create keystore of expected
type.", e);
@@ -350,14 +402,16 @@ public class KnoxSession implements Closeable {
} catch (IOException e) {
throw new KnoxShellException("Unable to load truststore."
+ " May be related to password setting or truststore format.", e);
+ } finally {
+ IOUtils.closeQuietly(is);
}
return ks;
}
protected void discoverTruststoreDetails(ClientContext clientContext) {
- String truststoreDir;
- String truststoreFileName;
+ String truststoreDir = null;
+ String truststoreFileName = null;
if (clientContext.connection().truststoreLocation() != null &&
clientContext.connection().truststorePass() != null) {
return;
@@ -399,7 +453,8 @@ public class KnoxSession implements Closeable {
if (response.getStatusLine().getStatusCode() < 400) {
return response;
} else {
- throw new ErrorResponse(response);
+ throw new ErrorResponse(
+ request.getRequestLine().getUri() + ": ", response);
}
} catch (final IOException e) {
throw new KnoxShellException(e.toString(), e);
@@ -411,11 +466,13 @@ public class KnoxSession implements Closeable {
}
} else {
- CloseableHttpResponse response = client.execute( host, request, context
);
- if( response.getStatusLine().getStatusCode() < 400 ) {
+
+ CloseableHttpResponse response = client.execute(host, request, context);
+ if (response.getStatusLine().getStatusCode() < 400) {
return response;
} else {
- throw new ErrorResponse( response );
+ throw new ErrorResponse(request.getRequestLine().getUri() + ": ",
+ response);
}
}
@@ -476,4 +533,11 @@ public class KnoxSession implements Closeable {
throw new KnoxShellException("Can not shutdown underlying resources", e);
}
}
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(
+ "KnoxSession{base='").append(base).append("\'}");
+ return sb.toString();
+ }
}
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ErrorResponse.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellMessages.java
similarity index 58%
copy from
gateway-shell/src/main/java/org/apache/knox/gateway/shell/ErrorResponse.java
copy to
gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellMessages.java
index 92661e9..f3671b2 100644
---
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/ErrorResponse.java
+++
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxShellMessages.java
@@ -17,19 +17,15 @@
*/
package org.apache.knox.gateway.shell;
-import org.apache.http.HttpResponse;
+import java.io.IOException;
-class ErrorResponse extends RuntimeException {
-
- HttpResponse response;
-
- ErrorResponse( HttpResponse response ) {
- super(String.valueOf(response.getStatusLine()));
- this.response = response;
- }
-
- public HttpResponse getReponse() {
- return response;
- }
+import org.apache.knox.gateway.i18n.messages.Message;
+import org.apache.knox.gateway.i18n.messages.MessageLevel;
+import org.apache.knox.gateway.i18n.messages.Messages;
+import org.apache.knox.gateway.i18n.messages.StackTrace;
+@Messages(logger="org.apache.knox.gateway.shell")
+public interface KnoxShellMessages {
+ @Message( level = MessageLevel.WARN, text = "Unable to load provided PEM
encoded trusted cert - falling through for other truststores: {0}" )
+ void unableToLoadProvidedPEMEncodedTrustedCert(@StackTrace( level =
MessageLevel.DEBUG ) IOException e);
}
diff --git
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxTokenCredentialCollector.java
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxTokenCredentialCollector.java
index 412764b..31ae149 100644
---
a/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxTokenCredentialCollector.java
+++
b/gateway-shell/src/main/java/org/apache/knox/gateway/shell/KnoxTokenCredentialCollector.java
@@ -39,31 +39,52 @@ public class KnoxTokenCredentialCollector extends
AbstractCredentialCollector {
private String tokenType;
+ private String endpointPublicCertPem;
+
+ private long expiresIn;
+
+ /* (non-Javadoc)
+ * @see CredentialCollector#collect()
+ */
@Override
public void collect() throws CredentialCollectionException {
+ try {
+ String knoxtoken = getCachedKnoxToken();
+ if (knoxtoken != null) {
+ Map<String, String> attrs = JsonUtils.getMapFromJsonString(knoxtoken);
+ value = attrs.get("access_token");
+ targetUrl = attrs.get("target_url");
+ tokenType = attrs.get("token_type");
+ endpointPublicCertPem = attrs.get("endpoint_public_cert");
+ expiresIn = Long.parseLong(attrs.get("expires_in"));
+ if (expiresIn > 0) {
+ Date expires = new Date(expiresIn);
+ if (expires.before(new Date())) {
+ throw new CredentialCollectionException("Cached knox token has
expired. Please relogin through knoxinit.");
+ }
+ }
+ } else {
+ throw new CredentialCollectionException("Cached knox token cannot be
found. Please login through knoxinit.");
+ }
+ } catch (IOException e) {
+ throw new CredentialCollectionException("Cached knox token cannot be
read. Please login through knoxinit.", e);
+ }
+ }
+
+ protected String getCachedKnoxToken() throws IOException {
+ String line = null;
String userDir = System.getProperty("user.home");
File knoxtoken = new File(userDir, KNOXTOKENCACHE);
if (knoxtoken.exists()) {
Path path = Paths.get(knoxtoken.toURI());
List<String> lines;
- try {
- lines = Files.readAllLines(path, StandardCharsets.UTF_8);
- if (!lines.isEmpty()) {
- Map<String, String> attrs =
JsonUtils.getMapFromJsonString(lines.get(0));
- value = attrs.get("access_token");
- targetUrl = attrs.get("target_url");
- tokenType = attrs.get("token_type");
- Date expires = new Date(Long.parseLong(attrs.get("expires_in")));
- if (expires.before(new Date())) {
- throw new CredentialCollectionException("Cached knox token has
expired. Please relogin through knoxinit.");
- }
- }
- } catch (IOException e) {
- throw new CredentialCollectionException("Cached knox token cannot be
read. Please login through knoxinit.", e);
+ lines = Files.readAllLines(path, StandardCharsets.UTF_8);
+ if (!lines.isEmpty()) {
+ line = lines.get(0);
}
- } else {
- throw new CredentialCollectionException("Cached knox token cannot be
found. Please login through knoxinit.");
}
+
+ return line;
}
public String getTargetUrl() {
@@ -74,6 +95,17 @@ public class KnoxTokenCredentialCollector extends
AbstractCredentialCollector {
return tokenType;
}
+ public String getEndpointClientCertPEM() {
+ return endpointPublicCertPem;
+ }
+
+ public long getExpiresIn() {
+ return expiresIn;
+ }
+
+ /* (non-Javadoc)
+ * @see CredentialCollector#name()
+ */
@Override
public String type() {
return COLLECTOR_TYPE;
diff --git
a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxSessionTest.java
b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxSessionTest.java
new file mode 100644
index 0000000..56e5712
--- /dev/null
+++
b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxSessionTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.shell;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class KnoxSessionTest {
+ public static final String PEM =
"MIICOjCCAaOgAwIBAgIJAN5kp1oW3Up8MA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNVBAYTAlVTMQ0w\n"
+ +
"CwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRvb3AxDTALBgNVBAsTBFRl\n"
+ +
"c3QxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xODEyMTMwMzE2MTFaFw0xOTEyMTMwMzE2MTFaMF8x\n"
+ +
"CzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRv\n"
+ +
"b3AxDTALBgNVBAsTBFRlc3QxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOB\n"
+ +
"jQAwgYkCgYEAqxnzKNhNgPEeOWsTabaxR9N3QjKohvDOrAwwVvzVhHIb1GKRo+TfSkDozS3BmzuO\n"
+ +
"+xQN6LvIsE6pzl+TFvTJvM9Ir5vMyybww8ZVkeD7vaHvBT9+w+1R79wYEhC7kqj68bGJJpl+1fGa\n"
+ +
"c6yTKBYcAs3hO54Zg56rgreQKwXeBysCAwEAATANBgkqhkiG9w0BAQUFAAOBgQACFpBmy7KgSiBG\n"
+ +
"0flF1+l8KXCU7t3LL8F3RlJSF4fyexfojilkHW7u6TdJbrAsz5nhe85AchFl6/jtmvCMGMFPobMI\n"
+ +
"f/44w9sYdC3u604wJy8CF5xKqDb/en4xmiLnEc0LzOeEvtFv0ociu82SuRara7ua1J6UR9JsNu5p\n"
+ + "dWEFEA==\n";
+
+ @Test
+ public void testParsingPublicCertPem() throws Exception {
+
+ final ClientContext context =
ClientContext.with("https://localhost:8443/gateway/dt");
+ context.connection().withPublicCertPem(PEM);
+ KnoxSession session = KnoxSession.login(context);
+ session.close();
+ }
+
+ @Test
+ public void testParsingInvalidPublicCertPem() throws Exception {
+
+ final ClientContext context =
ClientContext.with("https://localhost:8443/gateway/dt");
+ try {
+ context.connection().withPublicCertPem("INVLID-" + PEM);
+ KnoxSession session = KnoxSession.login(context);
+ fail("Invalid Public Cert should have resulted in CertificateException
wrapped by KnoxShellException");
+ session.close();
+ }
+ catch (KnoxShellException e) {
+ assertTrue(e.getCause().toString().contains("CertificateException"));
+ }
+ }
+
+ @Test
+ public void testParsingPublicCertPemWithCertDelimiters() throws Exception {
+
+ final ClientContext context =
ClientContext.with("https://localhost:8443/gateway/dt");
+ try {
+ context.connection().withPublicCertPem(KnoxSession.BEGIN_CERTIFICATE +
PEM + KnoxSession.END_CERTIFICATE);
+ KnoxSession session = KnoxSession.login(context);
+ session.close();
+ }
+ catch (KnoxShellException e) {
+ fail("Should have been able to parse cert with BEGIN and END Certificate
delimiters.");
+ }
+ }
+}
diff --git
a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxTokenCredentialCollectorTest.java
b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxTokenCredentialCollectorTest.java
index 5ca06bc..65e5cd8 100644
---
a/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxTokenCredentialCollectorTest.java
+++
b/gateway-shell/src/test/java/org/apache/knox/gateway/shell/KnoxTokenCredentialCollectorTest.java
@@ -16,18 +16,40 @@
*/
package org.apache.knox.gateway.shell;
-import org.apache.commons.io.FileUtils;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.File;
+import java.io.IOException;
import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.apache.commons.io.FileUtils;
+import org.apache.knox.gateway.util.JsonUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
public class KnoxTokenCredentialCollectorTest {
+ public static final String PEM =
"MIICOjCCAaOgAwIBAgIJAN5kp1oW3Up8MA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNVBAYTAlVTMQ0w\n"
+ +
"CwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRvb3AxDTALBgNVBAsTBFRl\n"
+ +
"c3QxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xODEyMTMwMzE2MTFaFw0xOTEyMTMwMzE2MTFaMF8x\n"
+ +
"CzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ8wDQYDVQQKEwZIYWRv\n"
+ +
"b3AxDTALBgNVBAsTBFRlc3QxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOB\n"
+ +
"jQAwgYkCgYEAqxnzKNhNgPEeOWsTabaxR9N3QjKohvDOrAwwVvzVhHIb1GKRo+TfSkDozS3BmzuO\n"
+ +
"+xQN6LvIsE6pzl+TFvTJvM9Ir5vMyybww8ZVkeD7vaHvBT9+w+1R79wYEhC7kqj68bGJJpl+1fGa\n"
+ +
"c6yTKBYcAs3hO54Zg56rgreQKwXeBysCAwEAATANBgkqhkiG9w0BAQUFAAOBgQACFpBmy7KgSiBG\n"
+ +
"0flF1+l8KXCU7t3LL8F3RlJSF4fyexfojilkHW7u6TdJbrAsz5nhe85AchFl6/jtmvCMGMFPobMI\n"
+ +
"f/44w9sYdC3u604wJy8CF5xKqDb/en4xmiLnEc0LzOeEvtFv0ociu82SuRara7ua1J6UR9JsNu5p\n"
+ + "dWEFEA==\n";
+
+ public static final String JWT = "eyJhbGciOiJSUzI1NiJ9."
+ +
"eyJzdWIiOiJndWVzdCIsImF1ZCI6InRva2VuYmFzZWQiLCJpc3MiOiJLTk9YU1NPIiwiZXhwIjoxNT"
+ +
"Q0ODMxNTI3fQ.gcIuNQN1_6dF6guk_7-QZo13xQMtlhtrc53H0lBzhj4Ft8OjUw-QNNMz6-bohz5Al"
+ +
"XBF6r_whfqFBm8MZUHIh8-hmqt91458acqR3jtJNDrjs5cv2ExaycK40KgyX58cnh6wfph5RLgiAo4"
+ + "j3zRSOaykZBq8W1DhYliXkRBFm1w";
private static final File tokenCacheBackup = new
File("tokenCacheBackup.bin");
@@ -78,12 +100,17 @@ public class KnoxTokenCredentialCollectorTest {
fail("Could not create empty Knox token cache file: " + e.getMessage());
}
+ boolean caughtCredentialCollectionException = false;
+
// Attempt to collect the Knox token
KnoxTokenCredentialCollector collector = new
KnoxTokenCredentialCollector();
try {
collector.collect();
+ } catch (CredentialCollectionException e) {
+ // Expected
+ caughtCredentialCollectionException = true;
} catch (Exception e) {
- fail(e.getMessage());
+ fail("Unexpected exception: " + e.getMessage());
} finally {
try {
getTokenCacheFile().delete();
@@ -91,6 +118,39 @@ public class KnoxTokenCredentialCollectorTest {
// Ignore
}
}
+
+ assertTrue("Expected exception not thrown.",
caughtCredentialCollectionException);
+ }
+
+ @Test
+ public void testParsingPublicCertPem() throws Exception {
+ Map<String, Object> map = new HashMap<>();
+ map.put("access_token", JWT);
+ map.put("target_url", "https://localhost:8443/gateway/sandbox" );
+ map.put("token_type", "Bearer");
+ map.put("endpoint_public_cert", PEM);
+
+ // NOTE: we are setting the expiry to -1 however this is not the actual
expiration inside
+ // the JWT. Any tests that are added to test that the token is not meant
to expire will fail.
+ map.put("expires_in", -1L);
+ Credentials credentials = new org.apache.knox.gateway.shell.Credentials();
+ KnoxTokenCredentialCollector knoxTokenCollector = new
KnoxTokenCredentialCollector() {
+ @Override
+ protected String getCachedKnoxToken() throws IOException {
+ return JsonUtils.renderAsJsonString(map);
+ }
+ };
+
+ credentials.add(knoxTokenCollector, "none: ", "token");
+ credentials.collect();
+
+ KnoxTokenCredentialCollector token = (KnoxTokenCredentialCollector)
credentials.get("token");
+
+ assertEquals(token.string(), map.get("access_token"));
+ assertEquals(token.getTargetUrl(), map.get("target_url"));
+ assertEquals(token.getTokenType(), map.get("token_type"));
+ assertEquals(token.getEndpointClientCertPEM(),
map.get("endpoint_public_cert"));
+ assertEquals(token.getExpiresIn(), map.get("expires_in"));
}
}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/X509CertificateUtil.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/X509CertificateUtil.java
index 8e0a370..7e051ee 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/X509CertificateUtil.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/X509CertificateUtil.java
@@ -25,8 +25,8 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -40,12 +40,14 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.commons.codec.binary.Base64;
import org.apache.knox.gateway.i18n.GatewaySpiMessages;
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
public class X509CertificateUtil {
+ public static final String END_CERTIFICATE = "-----END CERTIFICATE-----\n";
+ public static final String BEGIN_CERTIFICATE = "-----BEGIN
CERTIFICATE-----\n";
private static GatewaySpiMessages LOG =
MessagesFactory.get(GatewaySpiMessages.class);
/**
@@ -56,7 +58,8 @@ public class X509CertificateUtil {
* @param algorithm the signing algorithm, eg "SHA1withRSA"
* @return self-signed X.509 certificate
*/
- public static X509Certificate generateCertificate(String dn, KeyPair pair,
int days, String algorithm) {
+ public static X509Certificate generateCertificate(String dn, KeyPair pair,
+ int days, String algorithm) throws GeneralSecurityException, IOException
{
PrivateKey privkey = pair.getPrivate();
Object x509CertImplObject = null;
@@ -276,11 +279,11 @@ public class X509CertificateUtil {
public static void writeCertificateToFile(Certificate cert, final File file)
throws CertificateEncodingException, IOException {
byte[] bytes = cert.getEncoded();
- Base64 encoder = new Base64( 76, "\n".getBytes( StandardCharsets.US_ASCII
) );
+ Base64 encoder = new Base64( 76, "\n".getBytes( "ASCII" ) );
try(OutputStream out = Files.newOutputStream(file.toPath()) ) {
- out.write( "-----BEGIN CERTIFICATE-----\n".getBytes(
StandardCharsets.US_ASCII ) );
- out.write( encoder.encodeToString( bytes ).getBytes(
StandardCharsets.US_ASCII ) );
- out.write( "-----END CERTIFICATE-----\n".getBytes(
StandardCharsets.US_ASCII ) );
+ out.write( BEGIN_CERTIFICATE.getBytes( "ASCII" ) );
+ out.write( encoder.encodeToString( bytes ).getBytes( "ASCII" ) );
+ out.write( END_CERTIFICATE.getBytes( "ASCII" ) );
}
}