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

Reply via email to