Repository: zest-java Updated Branches: refs/heads/develop 57412977d -> 39b0b6344
ZEST-176 Add authentication support to Riak EntityStore Project: http://git-wip-us.apache.org/repos/asf/zest-java/repo Commit: http://git-wip-us.apache.org/repos/asf/zest-java/commit/39b0b634 Tree: http://git-wip-us.apache.org/repos/asf/zest-java/tree/39b0b634 Diff: http://git-wip-us.apache.org/repos/asf/zest-java/diff/39b0b634 Branch: refs/heads/develop Commit: 39b0b6344f18be1463cf824138b5b0a703780962 Parents: 5741297 Author: Paul Merlin <paulmer...@apache.org> Authored: Sun Sep 18 15:36:29 2016 -0700 Committer: Paul Merlin <paulmer...@apache.org> Committed: Sun Sep 18 15:36:29 2016 -0700 ---------------------------------------------------------------------- extensions/entitystore-riak/build.gradle | 1 + .../entitystore-riak/src/docs/es-riak.txt | 6 + .../riak/RiakEntityStoreConfiguration.java | 75 ++++++++++++ .../riak/RiakMapEntityStoreMixin.java | 119 ++++++++++++++++--- 4 files changed, 182 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zest-java/blob/39b0b634/extensions/entitystore-riak/build.gradle ---------------------------------------------------------------------- diff --git a/extensions/entitystore-riak/build.gradle b/extensions/entitystore-riak/build.gradle index 7a4c14c..54304f0 100644 --- a/extensions/entitystore-riak/build.gradle +++ b/extensions/entitystore-riak/build.gradle @@ -26,6 +26,7 @@ dependencies { compile( project( ":org.apache.zest.core:org.apache.zest.core.bootstrap" ) ) compile( project( ":org.apache.zest.libraries:org.apache.zest.library.locking" ) ) + compile( project( ":org.apache.zest.libraries:org.apache.zest.library.constraints" ) ) compile libraries.slf4j_api compile( libraries.riak ) http://git-wip-us.apache.org/repos/asf/zest-java/blob/39b0b634/extensions/entitystore-riak/src/docs/es-riak.txt ---------------------------------------------------------------------- diff --git a/extensions/entitystore-riak/src/docs/es-riak.txt b/extensions/entitystore-riak/src/docs/es-riak.txt index cc067b5..b2a2c69 100644 --- a/extensions/entitystore-riak/src/docs/es-riak.txt +++ b/extensions/entitystore-riak/src/docs/es-riak.txt @@ -52,3 +52,9 @@ Here are the available configuration properties: source=extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakEntityStoreConfiguration.java tag=config ---- + +All authentication related properties are optional. +By default no authentication is used. +As soon as you provide a `username`, authentication is set up. +Please note that you should then at least provide `truststoreType`, `truststorePath` and `truststorePassword`. +To use client certificate authentication, set `keystoreType`, `keystorePath`, `keystorePassword` and `keyPassword`. http://git-wip-us.apache.org/repos/asf/zest-java/blob/39b0b634/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakEntityStoreConfiguration.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakEntityStoreConfiguration.java b/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakEntityStoreConfiguration.java index febd329..ee797f7 100644 --- a/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakEntityStoreConfiguration.java +++ b/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakEntityStoreConfiguration.java @@ -21,6 +21,7 @@ import org.apache.zest.api.common.Optional; import org.apache.zest.api.common.UseDefaults; import org.apache.zest.api.configuration.ConfigurationComposite; import org.apache.zest.api.property.Property; +import org.apache.zest.library.constraints.annotation.OneOf; import java.util.List; @@ -44,6 +45,80 @@ public interface RiakEntityStoreConfiguration extends ConfigurationComposite Property<List<String>> hosts(); /** + * User name to use for authentication. + * + * @return Authentication user name + */ + @Optional + Property<String> username(); + + /** + * Password to use for authentication. + * + * @return Authentication password + */ + @Optional + Property<String> password(); + + /** + * Type of the keystore used for server certificate authentication. + * + * @return Type of the keystore used for server certificate authentication + */ + @Optional + @OneOf( { "PKCS12", "JCEKS", "JKS" } ) + Property<String> truststoreType(); + + /** + * Path of the keystore used for server certificate authentication. + * + * @return Path of the keystore used for server certificate authentication + */ + @Optional + Property<String> truststorePath(); + + /** + * Password of the keystore used for server certificate authentication. + * + * @return Password of the keystore used for server certificate authentication + */ + @Optional + Property<String> truststorePassword(); + + /** + * Type of the keystore used for client certificate authentication. + * + * @return Type of the keystore used for client certificate authentication + */ + @Optional + @OneOf( { "PKCS12", "JCEKS", "JKS" } ) + Property<String> keystoreType(); + + /** + * Path of the keystore used for client certificate authentication. + * + * @return Path of the keystore used for client certificate authentication + */ + @Optional + Property<String> keystorePath(); + + /** + * Password of the keystore used for client certificate authentication. + * + * @return Password of the keystore used for client certificate authentication + */ + @Optional + Property<String> keystorePassword(); + + /** + * Password of the key used for client certificate authentication. + * + * @return Password of the key used for client certificate authentication + */ + @Optional + Property<String> keyPassword(); + + /** * Riak Bucket where Entities state will be stored. * * Defaulted to "zest:entities". http://git-wip-us.apache.org/repos/asf/zest-java/blob/39b0b634/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakMapEntityStoreMixin.java ---------------------------------------------------------------------- diff --git a/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakMapEntityStoreMixin.java b/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakMapEntityStoreMixin.java index 5b14890..4402c0e 100644 --- a/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakMapEntityStoreMixin.java +++ b/extensions/entitystore-riak/src/main/java/org/apache/zest/entitystore/riak/RiakMapEntityStoreMixin.java @@ -28,6 +28,7 @@ import com.basho.riak.client.core.RiakNode; import com.basho.riak.client.core.query.Location; import com.basho.riak.client.core.query.Namespace; import com.basho.riak.client.core.util.HostAndPort; +import org.apache.zest.api.common.InvalidApplicationException; import org.apache.zest.api.configuration.Configuration; import org.apache.zest.api.entity.EntityDescriptor; import org.apache.zest.api.entity.EntityReference; @@ -41,7 +42,18 @@ import org.apache.zest.spi.entitystore.EntityNotFoundException; import org.apache.zest.spi.entitystore.EntityStoreException; import org.apache.zest.spi.entitystore.helpers.MapEntityStore; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -67,16 +79,37 @@ public class RiakMapEntityStoreMixin implements ServiceActivation, MapEntityStor RiakEntityStoreConfiguration config = configuration.get(); String bucketName = config.bucket().get(); List<String> hosts = config.hosts().get(); - Integer clusterExecutionAttempts = config.clusterExecutionAttempts().get(); + + // Setup Riak Cluster Client + List<HostAndPort> hostsAndPorts = parseHosts( hosts ); + RiakNode.Builder nodeBuilder = new RiakNode.Builder(); + nodeBuilder = configureNodes( config, nodeBuilder ); + nodeBuilder = configureAuthentication( config, nodeBuilder ); + List<RiakNode> nodes = new ArrayList<>(); + for( HostAndPort host : hostsAndPorts ) + { + nodes.add( nodeBuilder.withRemoteAddress( host ).build() ); + } + RiakCluster.Builder clusterBuilder = RiakCluster.builder( nodes ); + clusterBuilder = configureCluster(config, clusterBuilder); + + // Start Riak Cluster + RiakCluster cluster = clusterBuilder.build(); + cluster.start(); + namespace = new Namespace( bucketName ); + riakClient = new RiakClient( cluster ); + + // Initialize Bucket + riakClient.execute( new StoreBucketProperties.Builder( namespace ).build() ); + } + + private RiakNode.Builder configureNodes( RiakEntityStoreConfiguration config, RiakNode.Builder nodeBuilder ) + { Integer minConnections = config.minConnections().get(); Integer maxConnections = config.maxConnections().get(); Boolean blockOnMaxConnections = config.blockOnMaxConnections().get(); Integer connectionTimeout = config.connectionTimeout().get(); Integer idleTimeout = config.idleTimeout().get(); - - // Setup Riak Cluster Client - List<HostAndPort> hostsAndPorts = parseHosts( hosts ); - RiakNode.Builder nodeBuilder = new RiakNode.Builder(); if( minConnections != null ) { nodeBuilder = nodeBuilder.withMinConnections( minConnections ); @@ -94,25 +127,73 @@ public class RiakMapEntityStoreMixin implements ServiceActivation, MapEntityStor { nodeBuilder = nodeBuilder.withIdleTimeout( idleTimeout ); } - List<RiakNode> nodes = new ArrayList<>(); - for( HostAndPort host : hostsAndPorts ) + return nodeBuilder; + } + + private RiakNode.Builder configureAuthentication( RiakEntityStoreConfiguration config, RiakNode.Builder nodeBuilder ) + throws IOException, GeneralSecurityException + { + String username = config.username().get(); + String password = config.password().get(); + String truststoreType = config.truststoreType().get(); + String truststorePath = config.truststorePath().get(); + String truststorePassword = config.truststorePassword().get(); + String keystoreType = config.keystoreType().get(); + String keystorePath = config.keystorePath().get(); + String keystorePassword = config.keystorePassword().get(); + String keyPassword = config.keyPassword().get(); + if( username != null ) { - nodes.add( nodeBuilder.withRemoteAddress( host ).build() ); + // Eventually load BouncyCastle to support PKCS12 + if( "PKCS12".equals( keystoreType ) || "PKCS12".equals( truststoreType ) ) + { + Provider bc = Security.getProvider( "BC" ); + if( bc == null ) + { + try + { + Class<?> bcType = Class.forName( "org.bouncycastle.jce.provider.BouncyCastleProvider" ); + Security.addProvider( (Provider) bcType.newInstance() ); + } + catch( Exception ex ) + { + throw new InvalidApplicationException( "Need to open a PKCS#12 but was unable to register BouncyCastle, check your classpath", ex ); + } + } + } + KeyStore truststore = loadStore( truststoreType, truststorePath, truststorePassword ); + if( keystorePath != null ) + { + KeyStore keyStore = loadStore( keystoreType, keystorePath, keystorePassword ); + nodeBuilder = nodeBuilder.withAuth( username, password, truststore, keyStore, keyPassword ); + } + else + { + nodeBuilder = nodeBuilder.withAuth( username, password, truststore ); + } } - RiakCluster.Builder clusterBuilder = RiakCluster.builder( nodes ); + return nodeBuilder; + } + + private KeyStore loadStore( String type, String path, String password ) + throws IOException, GeneralSecurityException + { + try( InputStream keystoreInput = new FileInputStream( new File( path ) ) ) + { + KeyStore keyStore = KeyStore.getInstance( type ); + keyStore.load( keystoreInput, password.toCharArray() ); + return keyStore; + } + } + + private RiakCluster.Builder configureCluster( RiakEntityStoreConfiguration config, RiakCluster.Builder clusterBuilder ) + { + Integer clusterExecutionAttempts = config.clusterExecutionAttempts().get(); if( clusterExecutionAttempts != null ) { clusterBuilder = clusterBuilder.withExecutionAttempts( clusterExecutionAttempts ); } - - // Start Riak Cluster - RiakCluster cluster = clusterBuilder.build(); - cluster.start(); - namespace = new Namespace( bucketName ); - riakClient = new RiakClient( cluster ); - - // Initialize Bucket - riakClient.execute( new StoreBucketProperties.Builder( namespace ).build() ); + return clusterBuilder; } @Override