diff --git a/CHANGELOG.md b/CHANGELOG.md index d6302c7..8c8c89e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added +- Implemented [#74](https://github.com/cyberark/conjur-api-java/issues/74) +- Updated code to enable adding custom javax.net.ssl.SSLContext to Conjur which + enables us to set up a trust between application and Conjur server from Java + code +- README has been updated with example SSLContext setup and it's use in Conjur + class constructors + ## [2.2.1] - 2020-05-08 ### Fixed - README has been updated to reflect the correct/expected usage of this SDK ([#70](https://github.com/cyberark/conjur-api-java/issues/70), diff --git a/README.md b/README.md index 74f3473..5449bb7 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,42 @@ values (like the API key) in source-controlled property files! ## Set Up Trust Between App and Conjur +By default, the Conjur appliance generates and uses self-signed SSL certificates (Java-specific +certificates known as cacerts). Without trusting them, your Java app will not be able to connect +to the Conjur server over APIs and so you will need to configure your app to trust them. You can +accomplish this by using the [Client-level `SSLContext`](#client--level-trust) when creating +the client or with a [JVM-level trust](#jvm--level-trust) by loading the Conjur certificate into +Java's CA keystore that holds the list of all the allowed certificates for https connections. + +### Client-level trust + +We can set up a trust between the client application and a Conjur server using +Java javax.net.ssl.SSLContext. This can be done from Java code during +Conjur class initialization. + +Usable in Kubernetes/OpenShift environment to setup TLS trust with Conjur +server dynamically from the Kubernetes secret and/or configmap data. + +```java +final String conjurTlsCaPath = "/var/conjur-config/tls-ca.pem"; + +final CertificateFactory cf = CertificateFactory.getInstance("X.509"); +final FileInputStream certIs = new FileInputStream(conjurTlsCaPath); +final Certificate cert = cf.generateCertificate(certIs); + +final KeyStore ks = KeyStore.getInstance("JKS"); +ks.load(null); +ks.setCertificateEntry("conjurTlsCaPath", cert); + +final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); +tmf.init(ks); + +SSLContext conjurSslContext = SSLContext.getInstance("TLS"); +conjurSslContext.init(null, tmf.getTrustManagers(), null); +``` + +### JVM-level trust + By default, the Conjur appliance generates and uses self-signed SSL certificates (Java-specific certificates known as cacerts). Without trusting them, your Java app will not be able to connect to the Conjur server over APIs and so you will need to configure your app to trust them. You can @@ -287,6 +323,8 @@ import net.conjur.api.Conjur; // Configured using environment variables Conjur conjur = new Conjur(); +// or using custom SSLContext setup as conjurSslContext variable +Conjur conjur = new Conjur(conjurSslContext); ``` ### System Properties @@ -303,6 +341,8 @@ import net.conjur.api.Conjur; // Configured using system properties Conjur conjur = new Conjur(); +// or using custom SSLContext setup as conjurSslContext variable +Conjur conjur = new Conjur(conjurSslContext); ``` ### System Properties with Maven @@ -320,6 +360,8 @@ import net.conjur.api.Conjur; // Configured using system properties Conjur conjur = new Conjur(); +// or using custom SSLContext setup as conjurSslContext variable +Conjur conjur = new Conjur(conjurSslContext); ``` ### Username and Password @@ -337,6 +379,8 @@ import net.conjur.api.Conjur; Conjur conjur = new Conjur('host/host-id', 'password-or-api-key'); // or Conjur conjur = new Conjur('username', 'password-or-api-key'); +// or using custom SSLContext setup as conjurSslContext variable +Conjur conjur = new Conjur('username', 'password-or-api-key', conjurSslContext); ``` ### Credentials @@ -354,6 +398,8 @@ import net.conjur.api.Credentials; // regarding how 'password-or-api-key' is processed. Credentials credentials = new Credentials('username', 'password-or-api-key'); Conjur conjur = new Conjur(credentials); +// or using custom SSLContext setup as conjurSslContext variable +Conjur conjur = new Conjur(credentials, conjurSslContext); ``` ### Authorization Token @@ -371,6 +417,8 @@ import net.conjur.api.Token; Token token = Token.fromFile(Paths.get('path/to/conjur/authentication/token.json')); Conjur conjur = new Conjur(token); +// or using custom SSLContext setup as conjurSslContext variable +Conjur conjur = new Conjur(token, conjurSslContext); ``` Alternatively, use the `CONJUR_AUTHN_TOKEN_FILE` environment variable: @@ -387,6 +435,8 @@ import net.conjur.api.Token; Token token = Token.fromEnv(); Conjur conjur = new Conjur(token); +// or using custom SSLContext setup as conjurSslContext variable +Conjur conjur = new Conjur(token, conjurSslContext); ``` ## Client APIs @@ -400,10 +450,15 @@ a secret from Conjur, so we provide some sample code for this use case below. The client can be instantiated with any of these methods: ```java Conjur client = Conjur(); +Conjur client = Conjur(SSLContext sslContext); Conjur client = Conjur(String username, String password); +Conjur client = Conjur(String username, String password, SSLContext sslContext); Conjur client = Conjur(String username, String password, String authnUrl); +Conjur client = Conjur(String username, String password, String authnUrl, SSLContext sslContext); Conjur client = Conjur(Credentials credentials); +Conjur client = Conjur(Credentials credentials, SSLContext sslContext); Conjur client = Conjur(Token token); +Conjur client = Conjur(Token token, SSLContext sslContext); ``` _Note:_ **As mentioned before, if you use the default `CONJUR_AUTHN_URL` value or your diff --git a/src/main/java/net/conjur/api/Conjur.java b/src/main/java/net/conjur/api/Conjur.java index bc18894..a06238c 100644 --- a/src/main/java/net/conjur/api/Conjur.java +++ b/src/main/java/net/conjur/api/Conjur.java @@ -1,5 +1,7 @@ package net.conjur.api; +import javax.net.ssl.SSLContext; + /** * Entry point for the Conjur API client. */ @@ -14,6 +16,14 @@ public Conjur(){ this(Credentials.fromSystemProperties()); } + /** + * Create a Conjur instance that uses credentials from the system properties + * @param sslContext the {@link SSLContext} to use for connections to Conjur server + */ + public Conjur(SSLContext sslContext){ + this(Credentials.fromSystemProperties(), sslContext); + } + /** * Create a Conjur instance that uses a ResourceClient & an AuthnClient constructed with the given credentials * @param username username for the Conjur identity to authenticate as @@ -23,6 +33,16 @@ public Conjur(String username, String password) { this(new Credentials(username, password)); } + /** + * Create a Conjur instance that uses a ResourceClient & an AuthnClient constructed with the given credentials + * @param username username for the Conjur identity to authenticate as + * @param password password or api key for the Conjur identity to authenticate as + * @param sslContext the {@link SSLContext} to use for connections to Conjur server + */ + public Conjur(String username, String password, SSLContext sslContext) { + this(new Credentials(username, password), sslContext); + } + /** * Create a Conjur instance that uses a ResourceClient & an AuthnClient constructed with the given credentials * @param username username for the Conjur identity to authenticate as @@ -33,21 +53,49 @@ public Conjur(String username, String password, String authnUrl) { this(new Credentials(username, password, authnUrl)); } + /** + * Create a Conjur instance that uses a ResourceClient & an AuthnClient constructed with the given credentials + * @param username username for the Conjur identity to authenticate as + * @param password password or api key for the Conjur identity to authenticate as + * @param authnUrl the conjur authentication url + * @param sslContext the {@link SSLContext} to use for connections to Conjur server + */ + public Conjur(String username, String password, String authnUrl, SSLContext sslContext) { + this(new Credentials(username, password, authnUrl), sslContext); + } + /** * Create a Conjur instance that uses a ResourceClient & an AuthnClient constructed with the given credentials * @param credentials the conjur identity to authenticate as */ public Conjur(Credentials credentials) { - variables = new Variables(credentials); + this(credentials, null); } + /** + * Create a Conjur instance that uses a ResourceClient & an AuthnClient constructed with the given credentials + * @param credentials the conjur identity to authenticate as + * @param sslContext the {@link SSLContext} to use for connections to Conjur server + */ + public Conjur(Credentials credentials, SSLContext sslContext) { + variables = new Variables(credentials, sslContext); + } /** * Create a Conjur instance that uses a ResourceClient & an AuthnClient constructed with the given credentials * @param token the conjur authorization token to use */ public Conjur(Token token) { - variables = new Variables(token); + this(token, null); + } + + /** + * Create a Conjur instance that uses a ResourceClient & an AuthnClient constructed with the given credentials + * @param token the conjur authorization token to use + * @param sslContext the {@link SSLContext} to use for connections to Conjur server + */ + public Conjur(Token token, SSLContext sslContext) { + variables = new Variables(token, sslContext); } /** diff --git a/src/main/java/net/conjur/api/Variables.java b/src/main/java/net/conjur/api/Variables.java index ba391ac..1eab1b4 100644 --- a/src/main/java/net/conjur/api/Variables.java +++ b/src/main/java/net/conjur/api/Variables.java @@ -1,5 +1,7 @@ package net.conjur.api; +import javax.net.ssl.SSLContext; + import net.conjur.api.clients.ResourceClient; public class Variables { @@ -7,11 +9,20 @@ public class Variables { private ResourceClient resourceClient; public Variables(Credentials credentials) { - resourceClient = new ResourceClient(credentials, Endpoints.fromCredentials(credentials)); + this(credentials, null); + } + + public Variables(Credentials credentials, SSLContext sslContext) { + resourceClient = + new ResourceClient(credentials, Endpoints.fromCredentials(credentials), sslContext); } public Variables(Token token) { - resourceClient = new ResourceClient(token, Endpoints.fromSystemProperties()); + this(token, null); + } + + public Variables(Token token, SSLContext sslContext) { + resourceClient = new ResourceClient(token, Endpoints.fromSystemProperties(), sslContext); } public String retrieveSecret(String variableId) { diff --git a/src/main/java/net/conjur/api/clients/AuthnClient.java b/src/main/java/net/conjur/api/clients/AuthnClient.java index b5d78be..2c4bd6d 100644 --- a/src/main/java/net/conjur/api/clients/AuthnClient.java +++ b/src/main/java/net/conjur/api/clients/AuthnClient.java @@ -1,11 +1,8 @@ package net.conjur.api.clients; -import net.conjur.api.AuthnProvider; -import net.conjur.api.Credentials; -import net.conjur.api.Endpoints; -import net.conjur.api.Token; -import net.conjur.util.rs.HttpBasicAuthFilter; +import static net.conjur.util.EncodeUriComponent.encodeUriComponent; +import javax.net.ssl.SSLContext; import javax.ws.rs.WebApplicationException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; @@ -13,11 +10,15 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Response; -import static net.conjur.util.EncodeUriComponent.encodeUriComponent; +import net.conjur.api.AuthnProvider; +import net.conjur.api.Credentials; +import net.conjur.api.Endpoints; +import net.conjur.api.Token; +import net.conjur.util.rs.HttpBasicAuthFilter; /** * Conjur authentication service client. - * + * * This client provides methods to get API tokens from the conjur authentication service, * which can then be used to make authenticated calls to other conjur services. * @@ -32,9 +33,15 @@ public class AuthnClient implements AuthnProvider { private String apiKey; public AuthnClient(final Credentials credentials, final Endpoints endpoints) { + this(credentials, endpoints, null); + } + + public AuthnClient(final Credentials credentials, + final Endpoints endpoints, + final SSLContext sslContext) { this.endpoints = endpoints; - init(credentials.getUsername(), credentials.getPassword()); + init(credentials.getUsername(), credentials.getPassword(), sslContext); // replacing the password with an API key this.apiKey = credentials.getPassword(); @@ -43,6 +50,7 @@ public AuthnClient(final Credentials credentials, final Endpoints endpoints) { } } + @Override public Token authenticate() { Response res = authenticate.request("application/json").post(Entity.text(apiKey), Response.class); validateResponse(res); @@ -51,6 +59,7 @@ public Token authenticate() { } // implementation of AuthnProvider method + @Override public Token authenticate(boolean useCachedToken) { return authenticate(); } @@ -66,9 +75,10 @@ public String login(){ return res.readEntity(String.class); } - private void init(final String username, final String password){ + private void init(final String username, final String password, final SSLContext sslContext) { final ClientBuilder builder = ClientBuilder.newBuilder() - .register(new HttpBasicAuthFilter(username, password)); + .register(new HttpBasicAuthFilter(username, password)) + .sslContext(sslContext); Client client = builder.build(); WebTarget root = client.target(endpoints.getAuthnUri()); diff --git a/src/main/java/net/conjur/api/clients/ResourceClient.java b/src/main/java/net/conjur/api/clients/ResourceClient.java index 7e2bddd..843f82a 100644 --- a/src/main/java/net/conjur/api/clients/ResourceClient.java +++ b/src/main/java/net/conjur/api/clients/ResourceClient.java @@ -1,5 +1,6 @@ package net.conjur.api.clients; +import javax.net.ssl.SSLContext; import javax.ws.rs.WebApplicationException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; @@ -23,18 +24,32 @@ public class ResourceClient implements ResourceProvider { private final Endpoints endpoints; public ResourceClient(final Credentials credentials, final Endpoints endpoints) { + this(credentials, endpoints, null); + } + + public ResourceClient(final Credentials credentials, + final Endpoints endpoints, + final SSLContext sslContext) { this.endpoints = endpoints; - init(credentials); + init(credentials, sslContext); } // Build ResourceClient using a Conjur auth token public ResourceClient(final Token token, final Endpoints endpoints) { + this(token, endpoints, null); + } + + // Build ResourceClient using a Conjur auth token + public ResourceClient(final Token token, + final Endpoints endpoints, + final SSLContext sslContext) { this.endpoints = endpoints; - init(token); + init(token, sslContext); } + @Override public String retrieveSecret(String variableId) { Response response = secrets.path(variableId).request().get(Response.class); validateResponse(response); @@ -42,6 +57,7 @@ public String retrieveSecret(String variableId) { return response.readEntity(String.class); } + @Override public void addSecret(String variableId, String secret) { Response response = secrets.path(EncodeUriComponent.encodeUriComponent(variableId)).request().post(Entity.text(secret), Response.class); validateResponse(response); @@ -51,18 +67,20 @@ private Endpoints getEndpoints() { return endpoints; } - private void init(Credentials credentials){ - final ClientBuilder builder = ClientBuilder.newBuilder() - .register(new TokenAuthFilter(new AuthnClient(credentials, endpoints))); + private void init(Credentials credentials, SSLContext sslContext){ + ClientBuilder builder = ClientBuilder.newBuilder() + .register(new TokenAuthFilter(new AuthnClient(credentials, endpoints, sslContext))) + .sslContext(sslContext); Client client = builder.build(); secrets = client.target(getEndpoints().getSecretsUri()); } - private void init(Token token){ - final ClientBuilder builder = ClientBuilder.newBuilder() - .register(new TokenAuthFilter(new AuthnTokenClient(token))); + private void init(Token token, SSLContext sslContext){ + ClientBuilder builder = ClientBuilder.newBuilder() + .register(new TokenAuthFilter(new AuthnTokenClient(token))) + .sslContext(sslContext); Client client = builder.build();