You are viewing a plain text version of this content. The canonical link for it is here.
Posted to pr@cassandra.apache.org by GitBox <gi...@apache.org> on 2021/05/27 20:24:25 UTC

[GitHub] [cassandra] maulin-vasavada opened a new pull request #1027: [CEP-9] Make SSL Context creation pluggable

maulin-vasavada opened a new pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027


   For details of the change please see the CEP-9: [CEP-9](https://cwiki.apache.org/confluence/display/CASSANDRA/CEP-9%3A+Make+SSLContext+creation+pluggable).
   
   Specifically while review I am seeking the Cassandra community input on [this section of the design](https://cwiki.apache.org/confluence/display/CASSANDRA/CEP-9%3A+Make+SSLContext+creation+pluggable#CEP9:MakeSSLContextcreationpluggable-ImportantnoteaboutcommonSSLconfigurations).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] bereng commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
bereng commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r667712849



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+        return sslContext;
+    }
+
+    @Override
+    public synchronized void initHotReloading(EncryptionOptions options) throws SSLException {
+
+        List<HotReloadableFile> fileList = new ArrayList<>();
+
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+        {
+            fileList.add(new HotReloadableFile(options.keystore));
+            fileList.add(new HotReloadableFile(options.truststore));
+        }
+
+        hotReloadableFiles = ImmutableList.copyOf(fileList);
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore(EncryptionOptions options) {
+        return new File(options.keystore).exists();
+    }
+
+    /**
+     * Helper class for hot reloading SSL Contexts
+     */
+    private static class HotReloadableFile
+    {
+        private final File file;
+        private volatile long lastModTime;
+
+        HotReloadableFile(String path)
+        {
+            file = new File(path);
+            lastModTime = file.lastModified();
+        }
+
+        boolean shouldReload()
+        {
+            long curModTime = file.lastModified();
+            boolean result = curModTime != lastModTime;
+            lastModTime = curModTime;
+            return result;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "HotReloadableFile{" +
+                   "file=" + file +
+                   ", lastModTime=" + lastModTime +
+                   '}';
+        }
+    }
+
+    KeyManagerFactory buildKeyManagerFactory(EncryptionOptions options) throws SSLException
+    {
+        try (InputStream ksf = Files.newInputStream(Paths.get(options.keystore)))
+        {
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(
+            options.algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : options.algorithm);
+            KeyStore ks = KeyStore.getInstance(options.store_type);
+            ks.load(ksf, options.keystore_password.toCharArray());
+            if (!checkedExpiry)
+            {
+                for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements(); )
+                {
+                    String alias = aliases.nextElement();
+                    if (ks.getCertificate(alias).getType().equals("X.509"))
+                    {
+                        Date expires = ((X509Certificate) ks.getCertificate(alias)).getNotAfter();
+                        if (expires.before(new Date()))
+                            logger.warn("Certificate for {} expired on {}", alias, expires);

Review comment:
       +1 it deserves some thought. Being expired it will at least be an encrypted connection. Idk how desirable it would be for a cluster to suddenly stop working. In many places I worked before people onlu notice a cert expired when a system stopped working. I don't know if we want to be that strict :thinking: Strictly speaking yes, but more opinions welcomed...




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686188603



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default

Review comment:
       typo carried over in the move from `SSLFactory`, `opertor` -> `operator`

##########
File path: src/java/org/apache/cassandra/config/EncryptionOptions.java
##########
@@ -456,6 +502,8 @@ public int hashCode()
         result += 31 * (cipher_suites == null ? 0 : cipher_suites.hashCode());
         result += 31 * Boolean.hashCode(require_client_auth);
         result += 31 * Boolean.hashCode(require_endpoint_verification);
+        result += 31 * (ssl_context_factory == null ? 0 : ssl_context_factory.class_name.hashCode());
+        result += 31 * (ssl_context_factory == null ? 0 : ssl_context_factory.parameters.hashCode());

Review comment:
       We should fix `hashCode()` in `ParameterizedClass` instead, following the "Item 11: Always override hashCode when you override equals" advice from Effective Java given that it overrides `equals`.

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }

Review comment:
       Why wrap the exception? If we would like to improve the UX for misconfiguration/problems perhaps it would be better to catch exceptions in `InboundConnectionInitiator.getSslHandler` and `org.apache.cassandra.net.OutboundConnectionInitiator.Initializer#initChannel` and log an error that we are `"Unable to create SslContext for inbound connection"` to make it clear what activity is causing the problem and close down the netty connection.

##########
File path: src/java/org/apache/cassandra/security/SSLFactory.java
##########
@@ -351,9 +194,16 @@ public static void checkCertFilesForHotReloading(EncryptionOptions.ServerEncrypt
         if (!isHotReloadingInitialized)
             throw new IllegalStateException("Hot reloading functionality has not been initialized.");
 
-        logger.debug("Checking whether certificates have been updated {}", hotReloadableFiles);
+        logger.debug("Checking whether certificates have been updated {} and {}",

Review comment:
       nit: `updated for server {} and client {}` - could look at the source to decipher but this would be more explicit.

##########
File path: test/conf/cassandra-sslcontextfactory.yaml
##########
@@ -0,0 +1,84 @@
+#
+# 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.
+#
+
+#
+# Warning!
+# Consider the effects on 'o.a.c.i.s.LegacySSTableTest' before changing schemas in this file.
+#

Review comment:
       This warning doesn't really hold true here - perhaps replace with a reference to the source yaml file and what is changed for testing the SSL context factory?

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise

Review comment:
       same comment as for `createJSSESslContext`

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;

Review comment:
       `buildTruststore` is really a request to validate certificates on receipt - and always set true for servers and only true if client auth is requested. What do you think about making that more explicit by renaming it to something like `verifyTrust` the trust store is just the way of achieving that goal?

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory

Review comment:
       I can see having this as `final` being important to know you're definitely getting an unmodified version of the class, but it means that somebody who wanted to just tweak a few settings has to deal with the maintenance issues of keeping their copy up to date.
   
   What do you think about dropping the final from this class and adding a new `public final class DefaultSslContextFactory extends DefaulSslContextFactoryImpl`. If somebody just wanted to tweak some of the Netty builder settings, though they could just override the `buildKeyManagerFactory`/`buildTrustManagerFactory` and if `useOpenSsl` gets moved into this module as a something like `getSslProvider()`.

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       I think this is an implementation detail and we should move it to the default implementation instead. The setting is usually statically initialized and the override is just to disable native library loading in the in-JVM dtests.
   
   If we go ahead with removal, it should also be dropped from the `CacheKey`.

##########
File path: test/unit/org/apache/cassandra/security/DefaultSslContextFactoryImplTest.java
##########
@@ -0,0 +1,133 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DefaultSslContextFactoryImplTest
+{
+    private Map<String,Object> commonConfig = new HashMap<>();
+
+    @Before
+    public void setup()
+    {
+        commonConfig.put("truststore", "test/conf/cassandra_ssl_test.truststore");
+        commonConfig.put("truststore_password", "cassandra");
+        commonConfig.put("require_client_auth", Boolean.FALSE);
+        commonConfig.put("cipher_suites", Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA"));
+    }
+
+    private void addKeystoreOptions(Map<String,Object> config)
+    {
+        config.put("keystore", "test/conf/cassandra_ssl_test.keystore");
+        config.put("keystore_password", "cassandra");
+    }
+
+    @Test(expected = IOException.class)
+    public void buildTrustManagerFactory_NoFile() throws IOException

Review comment:
       nit: copied over mix of snake case and camel case in method names. Probably more sympathetic with the rest of the tests just to use camel. No need to clean it up unless it annoys you.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r698757405



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
##########
@@ -0,0 +1,270 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Optional;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables. The official Kubernetes Secret Spec can be found <a href="https://kubernetes.io/docs/concepts/configuration/secret/ ">here</a>.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * at the mounted paths ({@code KEYSTORE_UPDATED_TIMESTAMP_PATH} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_PATH} for the truststore. The values in those paths are expected to be numeric values.
+ * Most obvious choice might be to just use time in nano/milli-seconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     keystore updated timestamp path = /etc/my-ssl-store/keystore-last-updatedtime
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ *     truststore updated timestamp path = /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/keystore-last-updatedtime
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: keystore-last-updatedtime
+ *           path: keystore-last-updatedtime
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ *         - key: truststore-last-updatedtime
+ *           path: truststore-last-updatedtime
+ * </pre>
+ */
+public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";
+        String KEYSTORE_PASSWORD_ENV_VAR = "KEYSTORE_PASSWORD_ENV_VAR";
+        String TRUSTSTORE_PATH = "TRUSTSTORE_PATH";
+        String TRUSTSTORE_PASSWORD_ENV_VAR = "TRUSTSTORE_PASSWORD_ENV_VAR";
+        String KEYSTORE_UPDATED_TIMESTAMP_PATH = "KEYSTORE_UPDATED_TIMESTAMP_PATH";
+        String TRUSTSTORE_UPDATED_TIMESTAMP_PATH = "TRUSTSTORE_UPDATED_TIMESTAMP_PATH";
+    }
+
+    public static final String DEFAULT_KEYSTORE_PASSWORD = "";
+    public static final String DEFAULT_TRUSTSTORE_PASSWORD = "";
+
+    @VisibleForTesting
+    static final String DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME = "KEYSTORE_PASSWORD";
+    @VisibleForTesting
+    static final String DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME = "TRUSTSTORE_PASSWORD";
+
+    private static final String KEYSTORE_PATH_VALUE = "/etc/my-ssl-store/keystore";
+    private static final String TRUSTSTORE_PATH_VALUE = "/etc/my-ssl-store/truststore";
+    private static final String KEYSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE = "/etc/my-ssl-store/keystore-last-updatedtime";
+    private static final String TRUSTSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String TRUSTSTORE_UPDATED_TIMESTAMP_PATH_VALUE = "/etc/my-ssl-store/truststore-last-updatedtime";
+
+    private long keystoreLastUpdatedTime;
+    private long truststoreLastUpdatedTime;
+
+    public KubernetesSecretsSslContextFactory()
+    {
+        keystore = KEYSTORE_PATH_VALUE;
+        keystore_password = getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME,
+                                            DEFAULT_KEYSTORE_PASSWORD);
+        truststore = TRUSTSTORE_PATH_VALUE;
+        truststore_password = getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME,
+                                              DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.nanoTime();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    public KubernetesSecretsSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString(ConfigKeys.KEYSTORE_PATH, KEYSTORE_PATH_VALUE);
+        keystore_password = getValueFromEnv(getString(ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR,
+                                                      KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD);
+        truststore = getString(ConfigKeys.TRUSTSTORE_PATH, TRUSTSTORE_PATH_VALUE);
+        truststore_password = getValueFromEnv(getString(ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR,
+                                                        TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.nanoTime();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        // No-op
+    }
+
+    /**
+     * Checks environment variables for {@code K8_SECRET_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} and {@code K8_SECRET_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR}
+     * and compares the values for those variables with the current timestamps. In case the environment variables are
+     * not valid (either they are not initialized yet, got removed or got corrupted in-flight), this method considers
+     * that nothing is changed.
+     * @return {@code true} if either of the timestamps (keystore or truststore) got updated;{@code false} otherwise
+     */
+    @Override
+    public boolean shouldReload()
+    {
+        return hasKeystoreUpdated() || hasTruststoreUpdated();
+    }
+
+    @VisibleForTesting
+    String getValueFromEnv(String envVarName, String defaultValue) {
+        String valueFromEnv = StringUtils.isEmpty(envVarName) ? null : System.getenv(envVarName);
+        return StringUtils.isEmpty(valueFromEnv) ? defaultValue : valueFromEnv;
+    }
+
+    private boolean hasKeystoreUpdated() {
+        long keystoreUpdatedTime = getKeystoreLastUpdatedTime();
+        if (keystoreUpdatedTime > keystoreLastUpdatedTime) {
+            logger.info("Updating the keystoreLastUpdatedTime from oldValue {} to newValue {}",
+                        keystoreLastUpdatedTime, keystoreUpdatedTime);
+            keystoreLastUpdatedTime = keystoreUpdatedTime;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean hasTruststoreUpdated() {
+        long truststoreUpdatedTime = getTruststoreLastUpdatedTime();
+        if (truststoreUpdatedTime > truststoreLastUpdatedTime) {
+            logger.info("Updating the truststoreLastUpdatedTime from oldValue {} to newValue {}",
+                        truststoreLastUpdatedTime, truststoreUpdatedTime);
+            truststoreLastUpdatedTime = truststoreUpdatedTime;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private long getKeystoreLastUpdatedTime() {
+        String keystoreUpdatedTimeSecretKeyPath = getString(ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH,
+                                                        KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE);

Review comment:
       Yes it could be initialized with the object instead of doing it everytime. I didn't intend to demo the reading from the parameters in the base case. Will change it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r662502170



##########
File path: src/java/org/apache/cassandra/config/EncryptionOptions.java
##########
@@ -29,8 +30,17 @@
 
 import org.apache.cassandra.locator.IEndpointSnitch;
 import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.security.ISslContextFactory;
 import org.apache.cassandra.security.SSLFactory;
+import org.apache.cassandra.utils.FBUtilities;
 
+/**

Review comment:
       would you mind to make this Javadoc "nicer"? It is not formatted correctly, I mean, empty line at the end, then the last sentence is short and might be put on the same same line as "require" and so on ...

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType

Review comment:
       could be method parameters aligned? ideally below each other, same column as the opening bracket (plus one char)

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.cassandra.security;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,String>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return

Review comment:
       would you mind to finish what it returns?

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,

Review comment:
       Could you spent more time on wording and making this grammatically correct? A sentence should start with an upper case letter and similar.

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);

Review comment:
       Can not be this just chained after .forServer(kmf) like "builder = SslContextBuilder.forServer(kmf).clientAuth(...);"

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+        return sslContext;
+    }
+
+    @Override
+    public synchronized void initHotReloading(EncryptionOptions options) throws SSLException {
+
+        List<HotReloadableFile> fileList = new ArrayList<>();
+
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+        {
+            fileList.add(new HotReloadableFile(options.keystore));
+            fileList.add(new HotReloadableFile(options.truststore));
+        }
+
+        hotReloadableFiles = ImmutableList.copyOf(fileList);
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore(EncryptionOptions options) {
+        return new File(options.keystore).exists();
+    }
+
+    /**
+     * Helper class for hot reloading SSL Contexts
+     */
+    private static class HotReloadableFile
+    {
+        private final File file;
+        private volatile long lastModTime;
+
+        HotReloadableFile(String path)
+        {
+            file = new File(path);
+            lastModTime = file.lastModified();
+        }
+
+        boolean shouldReload()
+        {
+            long curModTime = file.lastModified();
+            boolean result = curModTime != lastModTime;
+            lastModTime = curModTime;
+            return result;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "HotReloadableFile{" +
+                   "file=" + file +
+                   ", lastModTime=" + lastModTime +
+                   '}';
+        }
+    }
+
+    KeyManagerFactory buildKeyManagerFactory(EncryptionOptions options) throws SSLException
+    {
+        try (InputStream ksf = Files.newInputStream(Paths.get(options.keystore)))
+        {
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(
+            options.algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : options.algorithm);
+            KeyStore ks = KeyStore.getInstance(options.store_type);
+            ks.load(ksf, options.keystore_password.toCharArray());
+            if (!checkedExpiry)
+            {
+                for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements(); )
+                {
+                    String alias = aliases.nextElement();
+                    if (ks.getCertificate(alias).getType().equals("X.509"))
+                    {
+                        Date expires = ((X509Certificate) ks.getCertificate(alias)).getNotAfter();
+                        if (expires.before(new Date()))
+                            logger.warn("Certificate for {} expired on {}", alias, expires);

Review comment:
       we just warn and that is it? are we ok with using expired certificate?

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+        return sslContext;
+    }
+
+    @Override
+    public synchronized void initHotReloading(EncryptionOptions options) throws SSLException {
+
+        List<HotReloadableFile> fileList = new ArrayList<>();
+
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+        {

Review comment:
       Why is `hotReloadableFiles` static and immutable? Can not it be just cleared and new files added there again? Is not DefaultSslContextFactoryImpl ever going to be initialised just once?

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.cassandra.security;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,String>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise
+     * @param cipherFilter to allow Netty's cipher suite filtering, e.g.
+     * {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType,
+                                     boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException;
+
+    /**
+     * Initializes hot reloading of the security keys/certs. The implementation must guarantee this to be thread safe.
+     * @param options Encryption options
+     * @throws SSLException
+     */
+    void initHotReloading(EncryptionOptions options) throws SSLException;
+
+    /**
+     * Returns if any changes require the reloading of the SSL context returned by this factory.
+     * This will be called by Cassandra's periodic polling for any potential changes that will reload the SSL context
+     * . However only newer connections established after the reload will use the reloaded SSL context.
+     * @return

Review comment:
       would you mind to finish what it returns?

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();

Review comment:
       Can't this just "return builder.build()"? There does not need to be "SslContext sslContext" and returning at the end of the method.

##########
File path: src/java/org/apache/cassandra/config/EncryptionOptions.java
##########
@@ -76,8 +92,16 @@ public String description()
     protected Boolean isEnabled = null;
     protected Boolean isOptional = null;
 
+    /*
+     * We will wait to initialize this until applyConfig() call to make sure we do it only when the caller is ready
+     * to use this option instance.
+     */
+    public ISslContextFactory sslContextFactoryInstance;
+
     public EncryptionOptions()
     {
+        ssl_context_factory = new ParameterizedClass("org.apache.cassandra.security.DefaultSslContextFactoryImpl",

Review comment:
       There is an empty constructor in DefaultSsl.... so after its creation, `parameters` will be null but here you are setting it like an empty map, even it is a detail, it should be same and that empty constructor should call the one with map arg and create new map there.

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);

Review comment:
       same, can't be this chained (with protocols)?

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.cassandra.security;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,String>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise
+     * @param cipherFilter to allow Netty's cipher suite filtering, e.g.
+     * {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
+     * @return

Review comment:
       same

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.cassandra.security;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,String>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise
+     * @param cipherFilter to allow Netty's cipher suite filtering, e.g.
+     * {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType,
+                                     boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException;
+
+    /**
+     * Initializes hot reloading of the security keys/certs. The implementation must guarantee this to be thread safe.
+     * @param options Encryption options
+     * @throws SSLException
+     */
+    void initHotReloading(EncryptionOptions options) throws SSLException;
+
+    /**
+     * Returns if any changes require the reloading of the SSL context returned by this factory.
+     * This will be called by Cassandra's periodic polling for any potential changes that will reload the SSL context
+     * . However only newer connections established after the reload will use the reloaded SSL context.

Review comment:
       Could you just move that dot at the end of the previous line please? :)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r704857386



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       Added some comments on your self-PR :)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-917043886


   @bereng we have you as one of the reviewers on this PR. Can you please take a look at the changes and see if you have any comments before approving it? 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r695303276



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * environment variable ({@code KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the truststore. The values in those timestamp variables is expected to
+ * be numeric values. Most obvious choice might be to just use time in milliseconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR: KEYSTORE_LAST_UPDATEDTIME
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR: TRUSTSTORE_LAST_UPDATEDTIME
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: KEYSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-last-updatedtime
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *       - name: TRUSTSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-last-updatedtime
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ * </pre>
+ */
+public class K8SecretsSslContextFactory extends FileBasedSslContextFactory

Review comment:
       I think `K8s` is the right name. However instead of `K8SSecretsSslContextFactory` I prefer `KubernetesSecretsSslContextFactory`. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada edited a comment on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada edited a comment on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-906809781


   Thanks @jonmeredith and @tolbertam for the latest feedback on the usability aspect. Can you please point me to some test class you wrote and provide little more detail on what configuration issue you faced on the first attempt? I assume that when you say some values defaulted from the ones in `EncryptionOptions` you meant the values like "conf/.keystore" and "conf/.truststore" right? (Because the FileBasedSslContextFactory doesn't have any dependency on the EncryptionOption class as such.)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r690075874



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       @jonmeredith Out of the above three - I moved the useOpenSsl flag determination to the AbstractSslContextFactory implementation. Please review. Now will work on the other two.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r687275165



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {

Review comment:
       I put it for allowing the other implementations to throw an exception that is well processed by the cassandra code. The default impl doesn't have a need as you point out.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688283660



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);

Review comment:
       The `initHotReloading` happens during the initialization from the DatabaseDescriptor. Let us say keystore and truststore both files exists at that time. Now question is - what happens when one of those files is removed and `shouldReload` is called? Since Java APIs doesn't throw any errors for [file.lastModified() ](https://docs.oracle.com/javase/8/docs/api/java/io/File.html#lastModified--) in case file doesn't exist, it would think it should reload the file and [log error](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/security/SSLFactory.java#L366) silently and continue. However, based on the current code (with or without this PR) when it actually tries to build the key/trust manager factory it would fail. This is the existing behavior in SSLFactory also.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686617582



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore() {
+        return keystore == null ? false : new File(keystore).exists();

Review comment:
       can be simplified to "return keystore != null && new File(keystore).exists();"




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r696199626



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       @jonmeredith
   1) For the contract about `shouldReload()` you are right. The implementation having an internal state to check for possible updates needs to be toggled upon calling the `shouldReload()`. So it will return if there was supposed to be an update and then flip the internal flag so that next internal thread update can set it correctly. However the implementation needs to have thread-safe code if it uses another internal thread/async-process to detect changes on the key/trust stores.
   
   Also, in my case we have a http webservice and it can return HTTP 304 - Not modified upon calling so we don't have to have complexity of the running an internal thread.
   
   I can update the javadocs to clarify this contract for `shouldReload()`.
   
   2) About using different Ssl factories for the client/internnode connectivity but refreshing both upon changes to any of them, what do you think about making a change in SSLFactory to just invalidate the cache for the relevant options? The CacheKey already has EncryptionOptions and SocketType so all we should have to just do is separate the code in `checkCertFilesForHotReloading()` to separately check for `shouldReload()` and only clear the corresponding cache entries.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688285780



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);

Review comment:
       This makes me think if it is worth to change [HotReloadableFile#shouldReload](https://github.com/maulin-vasavada/cassandra/blob/CEP-9/src/java/org/apache/cassandra/security/DefaultSslContextFactory.java#L189) to make sure the new modified time is greater than the previous modified time instead of just `!=` comparison.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r696199626



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       @jonmeredith
   1) For the contract about `shouldReload()` you are right. The implementation having an internal state to check for possible updates needs to be toggled upon calling the `shouldReload()`. So it will return if there was supposed to be an update and then flip the internal flag so that next internal thread update can set it correctly. However the implementation needs to have thread-safe code if it uses another internal thread/async-process to detect changes on the key/trust stores.
   
   Also, in my case we have a http webservice and it can return `HTTP 304 - Not modified` upon calling so we don't have to have complexity of the running an internal thread.
   
   I can update the javadocs to clarify this contract for `shouldReload()`.
   
   2) About using different Ssl factories for the client/internnode connectivity but refreshing both upon changes to any of them, what do you think about making a change in SSLFactory to just invalidate the cache for the relevant options? The CacheKey already has EncryptionOptions and SocketType so all we should have to just do is separate the code in `checkCertFilesForHotReloading()` to separately check for `shouldReload()` and only clear the corresponding cache entries. Did you have the same thing on mind when you said introduce `SSLFactory.validateAndRefreshCache()` method?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r681314295



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+        return sslContext;
+    }
+
+    @Override
+    public synchronized void initHotReloading(EncryptionOptions options) throws SSLException {
+
+        List<HotReloadableFile> fileList = new ArrayList<>();
+
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+        {

Review comment:
       @smiklosovic It makes sense but let me think little bit more. DefaultSslContextFactoryImpl will be initialized twice because it is under each encryption options - for client and server. However, making the HotReloadableFile list an instance variable might still make sense instead of static and immutable.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686620657



##########
File path: src/java/org/apache/cassandra/security/SSLFactory.java
##########
@@ -351,9 +194,16 @@ public static void checkCertFilesForHotReloading(EncryptionOptions.ServerEncrypt
         if (!isHotReloadingInitialized)
             throw new IllegalStateException("Hot reloading functionality has not been initialized.");
 
-        logger.debug("Checking whether certificates have been updated {}", hotReloadableFiles);
+        logger.debug("Checking whether certificates have been updated {} and {}",
+                     serverOpts.sslContextFactoryInstance.getClass().getName(), clientOpts.sslContextFactoryInstance.getClass().getName());
+
+        boolean serverSslContextShouldReload = serverOpts == null ? false :

Review comment:
       Both of these might be simplified similarly as for hot loading files case, like serverOpts != null && serverOpts...shouldReload();




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r694625192



##########
File path: examples/security/test/unit/org/apache/cassandra/security/K8SecretsSslContextFactoryTest.java
##########
@@ -0,0 +1,224 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class K8SecretsSslContextFactoryTest
+{
+    private static final Logger logger = LoggerFactory.getLogger(K8SecretsSslContextFactoryTest.class);
+
+    private Map<String,Object> commonConfig = new HashMap<>();
+
+    private static class K8SecretsSslContextFactoryForTestOnly extends K8SecretsSslContextFactory {
+
+        public K8SecretsSslContextFactoryForTestOnly() {
+        }
+
+        public K8SecretsSslContextFactoryForTestOnly(Map<String,Object> config) {
+            super(config);
+        }
+
+        /*
+         * This is overriden to first give priority to the input map configuration since we should not be setting env
+         * variables from the unit tests. However, if the input map configuration doesn't have the value for the
+         * given key then fallback to loading from the real environment variables.
+         */
+        @Override
+        String getValueFromEnv(String envVarName, String defaultValue) {
+            String envVarValue = parameters.get(envVarName) != null ? parameters.get(envVarName).toString() : null;
+            if (StringUtils.isEmpty(envVarValue)) {
+                logger.info("Configuration doesn't have env variable {}. Will use parent's implementation", envVarName);
+                return super.getValueFromEnv(envVarName, defaultValue);
+            } else {
+                logger.info("Configuration has env variable {} with value {}. Will use that.",
+                            envVarName, envVarValue);
+                return envVarValue;
+            }
+        }
+    }
+
+    /*

Review comment:
       is this meant to stay or this commented lines can be deleted?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r687393669



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;

Review comment:
       I agree. It indeed took me a while to understand this variable while changing the code originally. Let me give it a shot for renaming.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r697754626



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
##########
@@ -0,0 +1,270 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Optional;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables. The official Kubernetes Secret Spec can be found <a href="https://kubernetes.io/docs/concepts/configuration/secret/ ">here</a>.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * at the mounted paths ({@code KEYSTORE_UPDATED_TIMESTAMP_PATH} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_PATH} for the truststore. The values in those paths are expected to be numeric values.
+ * Most obvious choice might be to just use time in nano/milli-seconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     keystore updated timestamp path = /etc/my-ssl-store/keystore-last-updatedtime
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ *     truststore updated timestamp path = /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/keystore-last-updatedtime
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: keystore-last-updatedtime
+ *           path: keystore-last-updatedtime
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ *         - key: truststore-last-updatedtime
+ *           path: truststore-last-updatedtime
+ * </pre>
+ */
+public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";
+        String KEYSTORE_PASSWORD_ENV_VAR = "KEYSTORE_PASSWORD_ENV_VAR";
+        String TRUSTSTORE_PATH = "TRUSTSTORE_PATH";
+        String TRUSTSTORE_PASSWORD_ENV_VAR = "TRUSTSTORE_PASSWORD_ENV_VAR";
+        String KEYSTORE_UPDATED_TIMESTAMP_PATH = "KEYSTORE_UPDATED_TIMESTAMP_PATH";
+        String TRUSTSTORE_UPDATED_TIMESTAMP_PATH = "TRUSTSTORE_UPDATED_TIMESTAMP_PATH";
+    }
+
+    public static final String DEFAULT_KEYSTORE_PASSWORD = "";
+    public static final String DEFAULT_TRUSTSTORE_PASSWORD = "";
+
+    @VisibleForTesting
+    static final String DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME = "KEYSTORE_PASSWORD";
+    @VisibleForTesting
+    static final String DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME = "TRUSTSTORE_PASSWORD";
+
+    private static final String KEYSTORE_PATH_VALUE = "/etc/my-ssl-store/keystore";
+    private static final String TRUSTSTORE_PATH_VALUE = "/etc/my-ssl-store/truststore";
+    private static final String KEYSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE = "/etc/my-ssl-store/keystore-last-updatedtime";
+    private static final String TRUSTSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String TRUSTSTORE_UPDATED_TIMESTAMP_PATH_VALUE = "/etc/my-ssl-store/truststore-last-updatedtime";
+
+    private long keystoreLastUpdatedTime;
+    private long truststoreLastUpdatedTime;
+
+    public KubernetesSecretsSslContextFactory()
+    {
+        keystore = KEYSTORE_PATH_VALUE;
+        keystore_password = getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME,
+                                            DEFAULT_KEYSTORE_PASSWORD);
+        truststore = TRUSTSTORE_PATH_VALUE;
+        truststore_password = getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME,
+                                              DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.nanoTime();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    public KubernetesSecretsSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString(ConfigKeys.KEYSTORE_PATH, KEYSTORE_PATH_VALUE);
+        keystore_password = getValueFromEnv(getString(ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR,
+                                                      KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD);
+        truststore = getString(ConfigKeys.TRUSTSTORE_PATH, TRUSTSTORE_PATH_VALUE);
+        truststore_password = getValueFromEnv(getString(ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR,
+                                                        TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.nanoTime();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        // No-op
+    }
+
+    /**
+     * Checks environment variables for {@code K8_SECRET_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} and {@code K8_SECRET_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR}
+     * and compares the values for those variables with the current timestamps. In case the environment variables are
+     * not valid (either they are not initialized yet, got removed or got corrupted in-flight), this method considers
+     * that nothing is changed.
+     * @return {@code true} if either of the timestamps (keystore or truststore) got updated;{@code false} otherwise
+     */
+    @Override
+    public boolean shouldReload()
+    {
+        return hasKeystoreUpdated() || hasTruststoreUpdated();
+    }
+
+    @VisibleForTesting
+    String getValueFromEnv(String envVarName, String defaultValue) {
+        String valueFromEnv = StringUtils.isEmpty(envVarName) ? null : System.getenv(envVarName);
+        return StringUtils.isEmpty(valueFromEnv) ? defaultValue : valueFromEnv;
+    }
+
+    private boolean hasKeystoreUpdated() {
+        long keystoreUpdatedTime = getKeystoreLastUpdatedTime();
+        if (keystoreUpdatedTime > keystoreLastUpdatedTime) {
+            logger.info("Updating the keystoreLastUpdatedTime from oldValue {} to newValue {}",
+                        keystoreLastUpdatedTime, keystoreUpdatedTime);
+            keystoreLastUpdatedTime = keystoreUpdatedTime;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean hasTruststoreUpdated() {
+        long truststoreUpdatedTime = getTruststoreLastUpdatedTime();
+        if (truststoreUpdatedTime > truststoreLastUpdatedTime) {
+            logger.info("Updating the truststoreLastUpdatedTime from oldValue {} to newValue {}",
+                        truststoreLastUpdatedTime, truststoreUpdatedTime);
+            truststoreLastUpdatedTime = truststoreUpdatedTime;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private long getKeystoreLastUpdatedTime() {
+        String keystoreUpdatedTimeSecretKeyPath = getString(ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH,
+                                                        KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE);

Review comment:
       As this doesn't change it could be initialized with the object. Are you demonstrating reading from the parameters in the base class?

##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
##########
@@ -0,0 +1,270 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Optional;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables. The official Kubernetes Secret Spec can be found <a href="https://kubernetes.io/docs/concepts/configuration/secret/ ">here</a>.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * at the mounted paths ({@code KEYSTORE_UPDATED_TIMESTAMP_PATH} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_PATH} for the truststore. The values in those paths are expected to be numeric values.
+ * Most obvious choice might be to just use time in nano/milli-seconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     keystore updated timestamp path = /etc/my-ssl-store/keystore-last-updatedtime
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ *     truststore updated timestamp path = /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/keystore-last-updatedtime
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: keystore-last-updatedtime
+ *           path: keystore-last-updatedtime
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ *         - key: truststore-last-updatedtime
+ *           path: truststore-last-updatedtime
+ * </pre>
+ */
+public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";

Review comment:
       Are you introducing keystone_path as an example of picking things up differently from the default behavior. If so it is probably worth documenting that for somebody copying this.  I think it would be just as useful to use the encryption options for keystore/truststore and passwords instead and use the files containing timestamps as the extension point you are demonstrating in this example.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686613900



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;

Review comment:
       why are these members not final? my IDE marks them as eligible for being `final`. Maybe I am just missing more context?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688166626



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       On the comment around triggering the hot reloading, I feel having a reasonable polling check from the Cassandra could be okay as far as the polling period is not very long. So the implementation can have aggressive check to know if something changed about keys/certs, but it can still wait couple of more seconds may be before applying the change. We should also worry about the potential implementation bug causing more issues if we have a reverse mechanism to allow the implementation to trigger invalidation for the existing SSL contexts. What do you think?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688164452



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;

Review comment:
       I like `verifyPeerCertificate` name. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r704847530



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       Thanks @jonmeredith You can see the changes done for separating hot reloading trigger for client and server [here](https://github.com/maulin-vasavada/cassandra/pull/1/files). I can add that to this PR for the further review once you can take a look at it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-900580781


   The `deriveIfOpenSslAvailable` change looks good. It will be interesting to see what you need to change to support your use case.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686617786



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore() {
+        return keystore == null ? false : new File(keystore).exists();
+    }
+
+    private boolean hasTruststore() {
+        return truststore == null ? false : new File(truststore).exists();

Review comment:
       same simplification as above




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686600513



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory

Review comment:
       This is good point, related to what I was thinking recently ... What if somebody takes the default implementation like this and it will put it into "custom class" config param in cassandra.yaml? (That also means that it would get all params + custom params into its constructor as side effect).
   
   So I think it definitely makes sense to make this not final if there is ever somebody who wants to build on top of this for whatever reason. We should enable to eat our own dog food and extend what we did. This implementation, even the default one, should be treated just like all other ones. It just happens to be _default one_.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-900580781


   The `deriveIfOpenSslAvailable` change looks good. It will be interesting to see what you need to change to support your use case.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada edited a comment on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada edited a comment on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-906809781


   Thanks @jonmeredith and @tolbertam for the latest feedback on the usability aspect. Can you please point me to some test class you wrote and provide little more detail on what configuration issue you faced on the first attempt? I assume that when you say some values defaulted from the ones in `EncryptionOptions` you meant the values like "conf/.keystore" and "conf/.truststore" right? (Because the FileBasedSslContextFactory doesn't have any dependency on the EncryptionOption class as such but it does have the same defaults as in EncryptionOptions today.)
   
   I am trying it out with a unit test and verifying. I may be able to reproduce your concern.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r694626534



##########
File path: src/java/org/apache/cassandra/config/ParameterizedClass.java
##########
@@ -59,6 +59,11 @@ public boolean equals(ParameterizedClass that)
         return Objects.equal(class_name, that.class_name) && Objects.equal(parameters, that.parameters);
     }
 
+    public int hashCode()

Review comment:
       please add Override annotation on top of this method.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r695304581



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * environment variable ({@code KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the truststore. The values in those timestamp variables is expected to
+ * be numeric values. Most obvious choice might be to just use time in milliseconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR: KEYSTORE_LAST_UPDATEDTIME
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR: TRUSTSTORE_LAST_UPDATEDTIME
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: KEYSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-last-updatedtime
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *       - name: TRUSTSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-last-updatedtime
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ * </pre>
+ */
+public class K8SecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(K8SecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";
+        String KEYSTORE_PASSWORD_ENV_VAR = "KEYSTORE_PASSWORD_ENV_VAR";
+        String TRUSTSTORE_PATH = "TRUSTSTORE_PATH";
+        String TRUSTSTORE_PASSWORD_ENV_VAR = "TRUSTSTORE_PASSWORD_ENV_VAR";
+        String KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR = "KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR";
+        String TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR = "TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR";
+    }
+
+    public static final String DEFAULT_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR = "KEYSTORE_LAST_UPDATEDTIME";
+    public static final String DEFAULT_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR = "TRUSTSTORE_LAST_UPDATEDTIME";
+
+    public static final String DEFAULT_KEYSTORE_PASSWORD = "cassandra";
+    public static final String DEFAULT_TRUSTSTORE_PASSWORD = "cassandra";
+
+    @VisibleForTesting
+    static final String DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME = "KEYSTORE_PASSWORD";
+    @VisibleForTesting
+    static final String DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME = "TRUSTSTORE_PASSWORD";
+
+    private static final String KEYSTORE_PATH_VALUE = "/etc/my-ssl-store/keystore";
+    private static final String TRUSTSTORE_PATH_VALUE = "/etc/my-ssl-store/truststore";
+    private static final String KEYSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR_NAME = DEFAULT_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR;
+    private static final String TRUSTSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR;
+
+    private long keystoreLastUpdatedTime;
+    private long truststoreLastUpdatedTime;
+
+    public K8SecretsSslContextFactory()
+    {
+        keystore = KEYSTORE_PATH_VALUE;
+        keystore_password = getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME,
+                                            DEFAULT_KEYSTORE_PASSWORD);
+        truststore = TRUSTSTORE_PATH_VALUE;
+        truststore_password = getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME,
+                                              DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.currentTimeMillis();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    public K8SecretsSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString(ConfigKeys.KEYSTORE_PATH, KEYSTORE_PATH_VALUE);
+        keystore_password = getValueFromEnv(getString(ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR,
+                                                      KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD);
+        truststore = getString(ConfigKeys.TRUSTSTORE_PATH, TRUSTSTORE_PATH_VALUE);
+        truststore_password = getValueFromEnv(getString(ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR,
+                                                        TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.currentTimeMillis();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        // No-op
+    }
+
+    /**
+     * Checks environment variables for {@code K8_SECRET_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} and {@code K8_SECRET_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR}
+     * and compares the values for those variables with the current timestamps. In case the environment variables are
+     * not valid (either they are not initialized yet, got removed or got corrupted in-flight), this method considers
+     * that nothing is changed.
+     * @return {@code true} if either of the timestamps (keystore or truststore) got updated;{@code false} otherwise
+     */
+    @Override
+    public boolean shouldReload()
+    {
+        return hasKeystoreUpdated() || hasTruststoreUpdated();
+    }
+
+    @VisibleForTesting
+    String getValueFromEnv(String envVarName, String defaultValue) {
+        String valueFromEnv = StringUtils.isEmpty(envVarName) ? null : System.getenv(envVarName);
+        return StringUtils.isEmpty(valueFromEnv) ? defaultValue : valueFromEnv;

Review comment:
       Good point. I think it should allow empty values for password for the truststore atleast. Let me see how to enable that.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r687275165



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {

Review comment:
       I put it for allowing the other implementations to throw an exception that is well processed by the cassandra code. The default impl doesn't have a need as you point out. I'll remove it from the Default Implementation.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688283660



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);

Review comment:
       The `initHotReloading` happens during the initialization from the DatabaseDescriptor. Let us say keystore and truststore both files exists at that time. Now the question is - what happens when one of those files is removed and `shouldReload` is called? Since Java APIs doesn't throw any errors for [file.lastModified() ](https://docs.oracle.com/javase/8/docs/api/java/io/File.html#lastModified--) in case file doesn't exist, it would think it should reload the file and [log error](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/security/SSLFactory.java#L366) silently and continue. However, based on the current code (with or without this PR) when it actually tries to build the key/trust manager factory it would fail. This is the existing behavior in SSLFactory. Since we are not changing that behavior I am not sure if we need to change anything here.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r695303783



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * environment variable ({@code KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the truststore. The values in those timestamp variables is expected to
+ * be numeric values. Most obvious choice might be to just use time in milliseconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR: KEYSTORE_LAST_UPDATEDTIME
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR: TRUSTSTORE_LAST_UPDATEDTIME

Review comment:
       The updated timestamps are read as System.getEnv() not the program environment. Let me test it on my local minikube and verify.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-906692004


   @tolbertam gave me a hand to test this out in our environment using a custom class derived from FileBasedSslContextFactory and it worked well for us for internode (we haven't had a chance to test clients or filesystem-timestamp based reloading yet).
   
   From the test I have some usability feedback - the first attempt at configuration failed because I set the `keystore` / `truststore` in the parameterized class parameters and they were silently overwritten from the ones in `EncryptionOptions`. I think this could be an easy source of security misconfiguration bugs.
   
   The two options I can think of are:
   1) Prevent the copied values from being set in the parameters and through an IllegalArgumentException preventing startup.
   2) Only copy the `EncryptionOption` version if there is no parameter set and log a warning if they are both set.
   
   I think I prefer option one as it is unambiguous from reading the configuration which setting is being used, wdyt?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r696199626



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       @jonmeredith
   1) For the contract about `shouldReload()` you are right. The implementation having an internal state to check for possible updates needs to be toggled upon calling the `shouldReload()`. So it will return if there was supposed to be an update and then flip the internal flag so that next internal thread update can set it correctly. However the implementation needs to have thread-safe code if it uses another internal thread to detect changes on the key/trust stores.
   
   Also, in my case we have a http webservice and it can return HTTP 304 - Not modified upon calling so we don't have to have complexity of the running an internal thread.
   
   I can update the javadocs to clarify this contract for `shouldReload()`.
   
   2) About using different Ssl factories for the client/internnode connectivity but refreshing both upon changes to any of them, what do you think about making a change in SSLFactory to just invalidate the cache for the relevant options? The CacheKey already has EncryptionOptions and SocketType so all we should have to just do is separate the code in `checkCertFilesForHotReloading()` to separately check for `shouldReload()` and only clear the corresponding cache entries.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686595391



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }

Review comment:
       I am of same opinion, I think I have already commented on this, probably on some other branch related to this CEP which got abandoned in favor of this one or similar ... we can just `return builder.build()`.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r701454062



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       Hi @jonmeredith can you please take a look at this last set of comments here? Beyond this I guess we are all done with code review comments. I'll update contract in the ISslContextFactory interface and ask for review and wait for the documentation guidelines in docs/ folder.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada removed a comment on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada removed a comment on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-906809781


   Thanks @jonmeredith and @tolbertam for the latest feedback on the usability aspect. Can you please point me to some test class you wrote and provide little more detail on what configuration issue you faced on the first attempt? I assume that when you say some values defaulted from the ones in `EncryptionOptions` you meant the values like "conf/.keystore" and "conf/.truststore" right? (Because the FileBasedSslContextFactory doesn't have any dependency on the EncryptionOption class as such but it does have the same defaults as in EncryptionOptions today.)
   
   I am trying it out with a unit test and verifying. I may be able to reproduce your concern.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r695301351



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and

Review comment:
       Yes, that is the correct one. Will include the link.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686606635



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {

Review comment:
       nit: is it really important to have a space after opening and before closing bracket? I do not think we follow this code style offically.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686619824



##########
File path: src/java/org/apache/cassandra/security/SSLFactory.java
##########
@@ -351,9 +194,16 @@ public static void checkCertFilesForHotReloading(EncryptionOptions.ServerEncrypt
         if (!isHotReloadingInitialized)
             throw new IllegalStateException("Hot reloading functionality has not been initialized.");
 
-        logger.debug("Checking whether certificates have been updated {}", hotReloadableFiles);
+        logger.debug("Checking whether certificates have been updated {} and {}",
+                     serverOpts.sslContextFactoryInstance.getClass().getName(), clientOpts.sslContextFactoryInstance.getClass().getName());
+
+        boolean serverSslContextShouldReload = serverOpts == null ? false :
+                                               serverOpts.sslContextFactoryInstance.shouldReload();
+
+        boolean clientSslContextShouldReload = clientOpts == null ? false :
+                                               clientOpts.sslContextFactoryInstance.shouldReload();
 
-        if (hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload))
+        if (serverSslContextShouldReload || clientSslContextShouldReload)
         {
             logger.info("SSL certificates have been updated. Reseting the ssl contexts for new connections.");

Review comment:
       "Resetting" - double "t".




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688165459



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       I think I get your suggestion after looking at how it is used but I need little bit more time to think over it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r696182028



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * environment variable ({@code KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the truststore. The values in those timestamp variables is expected to
+ * be numeric values. Most obvious choice might be to just use time in milliseconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR: KEYSTORE_LAST_UPDATEDTIME
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR: TRUSTSTORE_LAST_UPDATEDTIME
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: KEYSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-last-updatedtime
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *       - name: TRUSTSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-last-updatedtime
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ * </pre>
+ */
+public class K8SecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(K8SecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";
+        String KEYSTORE_PASSWORD_ENV_VAR = "KEYSTORE_PASSWORD_ENV_VAR";
+        String TRUSTSTORE_PATH = "TRUSTSTORE_PATH";
+        String TRUSTSTORE_PASSWORD_ENV_VAR = "TRUSTSTORE_PASSWORD_ENV_VAR";
+        String KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR = "KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR";
+        String TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR = "TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR";
+    }
+
+    public static final String DEFAULT_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR = "KEYSTORE_LAST_UPDATEDTIME";
+    public static final String DEFAULT_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR = "TRUSTSTORE_LAST_UPDATEDTIME";
+
+    public static final String DEFAULT_KEYSTORE_PASSWORD = "cassandra";
+    public static final String DEFAULT_TRUSTSTORE_PASSWORD = "cassandra";
+
+    @VisibleForTesting
+    static final String DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME = "KEYSTORE_PASSWORD";
+    @VisibleForTesting
+    static final String DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME = "TRUSTSTORE_PASSWORD";
+
+    private static final String KEYSTORE_PATH_VALUE = "/etc/my-ssl-store/keystore";
+    private static final String TRUSTSTORE_PATH_VALUE = "/etc/my-ssl-store/truststore";
+    private static final String KEYSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR_NAME = DEFAULT_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR;
+    private static final String TRUSTSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR;
+
+    private long keystoreLastUpdatedTime;
+    private long truststoreLastUpdatedTime;
+
+    public K8SecretsSslContextFactory()
+    {
+        keystore = KEYSTORE_PATH_VALUE;
+        keystore_password = getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME,
+                                            DEFAULT_KEYSTORE_PASSWORD);
+        truststore = TRUSTSTORE_PATH_VALUE;
+        truststore_password = getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME,
+                                              DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.currentTimeMillis();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    public K8SecretsSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString(ConfigKeys.KEYSTORE_PATH, KEYSTORE_PATH_VALUE);
+        keystore_password = getValueFromEnv(getString(ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR,
+                                                      KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD);
+        truststore = getString(ConfigKeys.TRUSTSTORE_PATH, TRUSTSTORE_PATH_VALUE);
+        truststore_password = getValueFromEnv(getString(ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR,
+                                                        TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.currentTimeMillis();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        // No-op
+    }
+
+    /**
+     * Checks environment variables for {@code K8_SECRET_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} and {@code K8_SECRET_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR}
+     * and compares the values for those variables with the current timestamps. In case the environment variables are
+     * not valid (either they are not initialized yet, got removed or got corrupted in-flight), this method considers
+     * that nothing is changed.
+     * @return {@code true} if either of the timestamps (keystore or truststore) got updated;{@code false} otherwise
+     */
+    @Override
+    public boolean shouldReload()
+    {
+        return hasKeystoreUpdated() || hasTruststoreUpdated();
+    }
+
+    @VisibleForTesting
+    String getValueFromEnv(String envVarName, String defaultValue) {
+        String valueFromEnv = StringUtils.isEmpty(envVarName) ? null : System.getenv(envVarName);
+        return StringUtils.isEmpty(valueFromEnv) ? defaultValue : valueFromEnv;

Review comment:
       Done.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688817941



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactory.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactory extends AbstractSslContextFactory

Review comment:
       I created FileBasedSslContextFactory which is abstract and has File Based key/trust stores support + Hot-reloading assuming the file based system. I created the the DefaultSslContextFactory as a shell class. I was thinking about PEM format files instead of standard keystore formats and this model would fit in better with that. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r696199626



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       @jonmeredith
   1) For the contract about `shouldReload()` you are right. The implementation having an internal state to check for possible updates needs to be toggled upon calling the `shouldReload()`. So it will return if there was supposed to be an update and then flip the internal flag so that next internal thread update can set it correctly. However the implementation needs to have thread-safe code if it uses another internal thread/async-process to detect changes on the key/trust stores.
   
   Also, in my case we have a http webservice and it can return `HTTP 304 - Not modified` upon calling so we don't have to have complexity of the running an internal thread.
   
   I can update the javadocs to clarify this contract for `shouldReload()`.
   
   2) About using different Ssl factories for the client/internnode connectivity but refreshing both upon changes to any of them, what do you think about making a change in SSLFactory to just invalidate the cache for the relevant options? The CacheKey already has EncryptionOptions and SocketType so all we should have to just do is separate the code in `checkCertFilesForHotReloading()` to separately check for `shouldReload()` and only clear the corresponding cache entries.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r682179176



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.cassandra.security;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,String>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise
+     * @param cipherFilter to allow Netty's cipher suite filtering, e.g.
+     * {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType,
+                                     boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException;
+
+    /**
+     * Initializes hot reloading of the security keys/certs. The implementation must guarantee this to be thread safe.
+     * @param options Encryption options
+     * @throws SSLException
+     */
+    void initHotReloading(EncryptionOptions options) throws SSLException;
+
+    /**
+     * Returns if any changes require the reloading of the SSL context returned by this factory.
+     * This will be called by Cassandra's periodic polling for any potential changes that will reload the SSL context
+     * . However only newer connections established after the reload will use the reloaded SSL context.
+     * @return
+     */
+    boolean shouldReload();
+
+    /**
+     * Returns if this factory uses private keystore.
+     * @param options Encryption options
+     * @return {@code true} by default unless the implementation overrides this
+     */
+    default boolean hasKeystore(EncryptionOptions options) {
+        return true;
+    }
+
+    /**
+     * Indicates if the process holds the inbound/listening end of the socket ({@link SSLFactory.SocketType#SERVER})), or the
+     * outbound side ({@link SSLFactory.SocketType#CLIENT}).
+     */
+    enum SocketType {
+        SERVER, CLIENT;
+    }
+}

Review comment:
       @sumanth-pasupuleti Done - consolidating SocketType and only having it in ISslContextFactory now.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-850554319


   @zznate When you get a chance can you please review this?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada closed pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada closed pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688151909



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory

Review comment:
       I created 'AbstractSslContextFactory' and `final` DefaultSslContextFactory classes with Abstract class not dealing with any file based credentials.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r698755259



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
##########
@@ -0,0 +1,270 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Optional;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables. The official Kubernetes Secret Spec can be found <a href="https://kubernetes.io/docs/concepts/configuration/secret/ ">here</a>.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * at the mounted paths ({@code KEYSTORE_UPDATED_TIMESTAMP_PATH} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_PATH} for the truststore. The values in those paths are expected to be numeric values.
+ * Most obvious choice might be to just use time in nano/milli-seconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     keystore updated timestamp path = /etc/my-ssl-store/keystore-last-updatedtime
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ *     truststore updated timestamp path = /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/keystore-last-updatedtime
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: keystore-last-updatedtime
+ *           path: keystore-last-updatedtime
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ *         - key: truststore-last-updatedtime
+ *           path: truststore-last-updatedtime
+ * </pre>
+ */
+public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";

Review comment:
       For the keystore/truststore paths I agree we can just use the existing encryption option keys to configure. With the password its different because in the current encryption options the value in that field is the actual password vs with K8s we are asking for yet-another-indirection, a secrets name that will give us the password. However, I will change the paths to come from existing encryption option keys instead of duplicating.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r698755259



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
##########
@@ -0,0 +1,270 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Optional;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables. The official Kubernetes Secret Spec can be found <a href="https://kubernetes.io/docs/concepts/configuration/secret/ ">here</a>.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * at the mounted paths ({@code KEYSTORE_UPDATED_TIMESTAMP_PATH} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_PATH} for the truststore. The values in those paths are expected to be numeric values.
+ * Most obvious choice might be to just use time in nano/milli-seconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     keystore updated timestamp path = /etc/my-ssl-store/keystore-last-updatedtime
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ *     truststore updated timestamp path = /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/keystore-last-updatedtime
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: keystore-last-updatedtime
+ *           path: keystore-last-updatedtime
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ *         - key: truststore-last-updatedtime
+ *           path: truststore-last-updatedtime
+ * </pre>
+ */
+public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";

Review comment:
       For the keystore/truststore paths I agree we can just use the existing encryption option keys to configure. With the password its different because in the current encryption options the value in that field is the actual password vs with K8s we are asking for yet-another-indirection, a secrets name that will give us the password. However, I will change the paths to come from existing encryption option keys instead of duplicating.

##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/KubernetesSecretsSslContextFactory.java
##########
@@ -0,0 +1,270 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.Optional;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables. The official Kubernetes Secret Spec can be found <a href="https://kubernetes.io/docs/concepts/configuration/secret/ ">here</a>.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * at the mounted paths ({@code KEYSTORE_UPDATED_TIMESTAMP_PATH} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_PATH} for the truststore. The values in those paths are expected to be numeric values.
+ * Most obvious choice might be to just use time in nano/milli-seconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     keystore updated timestamp path = /etc/my-ssl-store/keystore-last-updatedtime
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ *     truststore updated timestamp path = /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/keystore-last-updatedtime
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/truststore-last-updatedtime
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: keystore-last-updatedtime
+ *           path: keystore-last-updatedtime
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ *         - key: truststore-last-updatedtime
+ *           path: truststore-last-updatedtime
+ * </pre>
+ */
+public class KubernetesSecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";
+        String KEYSTORE_PASSWORD_ENV_VAR = "KEYSTORE_PASSWORD_ENV_VAR";
+        String TRUSTSTORE_PATH = "TRUSTSTORE_PATH";
+        String TRUSTSTORE_PASSWORD_ENV_VAR = "TRUSTSTORE_PASSWORD_ENV_VAR";
+        String KEYSTORE_UPDATED_TIMESTAMP_PATH = "KEYSTORE_UPDATED_TIMESTAMP_PATH";
+        String TRUSTSTORE_UPDATED_TIMESTAMP_PATH = "TRUSTSTORE_UPDATED_TIMESTAMP_PATH";
+    }
+
+    public static final String DEFAULT_KEYSTORE_PASSWORD = "";
+    public static final String DEFAULT_TRUSTSTORE_PASSWORD = "";
+
+    @VisibleForTesting
+    static final String DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME = "KEYSTORE_PASSWORD";
+    @VisibleForTesting
+    static final String DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME = "TRUSTSTORE_PASSWORD";
+
+    private static final String KEYSTORE_PATH_VALUE = "/etc/my-ssl-store/keystore";
+    private static final String TRUSTSTORE_PATH_VALUE = "/etc/my-ssl-store/truststore";
+    private static final String KEYSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE = "/etc/my-ssl-store/keystore-last-updatedtime";
+    private static final String TRUSTSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String TRUSTSTORE_UPDATED_TIMESTAMP_PATH_VALUE = "/etc/my-ssl-store/truststore-last-updatedtime";
+
+    private long keystoreLastUpdatedTime;
+    private long truststoreLastUpdatedTime;
+
+    public KubernetesSecretsSslContextFactory()
+    {
+        keystore = KEYSTORE_PATH_VALUE;
+        keystore_password = getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME,
+                                            DEFAULT_KEYSTORE_PASSWORD);
+        truststore = TRUSTSTORE_PATH_VALUE;
+        truststore_password = getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME,
+                                              DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.nanoTime();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    public KubernetesSecretsSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString(ConfigKeys.KEYSTORE_PATH, KEYSTORE_PATH_VALUE);
+        keystore_password = getValueFromEnv(getString(ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR,
+                                                      KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD);
+        truststore = getString(ConfigKeys.TRUSTSTORE_PATH, TRUSTSTORE_PATH_VALUE);
+        truststore_password = getValueFromEnv(getString(ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR,
+                                                        TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.nanoTime();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        // No-op
+    }
+
+    /**
+     * Checks environment variables for {@code K8_SECRET_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} and {@code K8_SECRET_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR}
+     * and compares the values for those variables with the current timestamps. In case the environment variables are
+     * not valid (either they are not initialized yet, got removed or got corrupted in-flight), this method considers
+     * that nothing is changed.
+     * @return {@code true} if either of the timestamps (keystore or truststore) got updated;{@code false} otherwise
+     */
+    @Override
+    public boolean shouldReload()
+    {
+        return hasKeystoreUpdated() || hasTruststoreUpdated();
+    }
+
+    @VisibleForTesting
+    String getValueFromEnv(String envVarName, String defaultValue) {
+        String valueFromEnv = StringUtils.isEmpty(envVarName) ? null : System.getenv(envVarName);
+        return StringUtils.isEmpty(valueFromEnv) ? defaultValue : valueFromEnv;
+    }
+
+    private boolean hasKeystoreUpdated() {
+        long keystoreUpdatedTime = getKeystoreLastUpdatedTime();
+        if (keystoreUpdatedTime > keystoreLastUpdatedTime) {
+            logger.info("Updating the keystoreLastUpdatedTime from oldValue {} to newValue {}",
+                        keystoreLastUpdatedTime, keystoreUpdatedTime);
+            keystoreLastUpdatedTime = keystoreUpdatedTime;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean hasTruststoreUpdated() {
+        long truststoreUpdatedTime = getTruststoreLastUpdatedTime();
+        if (truststoreUpdatedTime > truststoreLastUpdatedTime) {
+            logger.info("Updating the truststoreLastUpdatedTime from oldValue {} to newValue {}",
+                        truststoreLastUpdatedTime, truststoreUpdatedTime);
+            truststoreLastUpdatedTime = truststoreUpdatedTime;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private long getKeystoreLastUpdatedTime() {
+        String keystoreUpdatedTimeSecretKeyPath = getString(ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH,
+                                                        KEYSTORE_UPDATED_TIMESTAMP_PATH_VALUE);

Review comment:
       Yes it could be initialized with the object instead of doing it everytime. I didn't intend to demo the reading from the parameters in the base case. Will change it.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r706315954



##########
File path: src/java/org/apache/cassandra/security/SSLFactory.java
##########
@@ -351,21 +182,58 @@ public static void checkCertFilesForHotReloading(EncryptionOptions.ServerEncrypt
         if (!isHotReloadingInitialized)
             throw new IllegalStateException("Hot reloading functionality has not been initialized.");
 
-        logger.debug("Checking whether certificates have been updated {}", hotReloadableFiles);
+        logger.debug("Checking whether certificates have been updated for server {} and client {}",
+                     serverOpts.sslContextFactoryInstance.getClass().getName(), clientOpts.sslContextFactoryInstance.getClass().getName());
 
-        if (hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload))
+        if (serverOpts != null)
         {
-            logger.info("SSL certificates have been updated. Reseting the ssl contexts for new connections.");
-            try
+            checkCertFilesForHotReloading(serverOpts, "server_encryption_options", true);
+        }
+        if (clientOpts != null)
+        {
+            checkCertFilesForHotReloading(clientOpts, "client_encryption_options", clientOpts.require_client_auth);
+        }
+    }
+
+    private static void checkCertFilesForHotReloading(EncryptionOptions options, String contextDescription,
+                                                      boolean verifyPeerCertificate)
+    {
+        try
+        {
+            if (options.sslContextFactoryInstance.shouldReload())
             {
-                validateSslCerts(serverOpts, clientOpts);
-                cachedSslContexts.clear();
+                logger.info("SSL certificates have been updated for {}. Resetting the ssl contexts for new " +
+                            "connections.", options.getClass().getName());
+                validateSslContext(contextDescription, options, verifyPeerCertificate, false);
+                clearSslContextCache(options);
             }
-            catch(Exception e)
+        }
+        catch(Exception e)
+        {
+            logger.error("Failed to hot reload the SSL Certificates! Please check the certificate files.", e);
+        }
+    }
+
+    /**
+     * This clears the cache of Netty's SslContext objects for Client and Server sockets. This is made publically
+     * available so that any {@link ISslContextFactory}'s implementation can call this to handle any special scenario
+     * to invalidate the SslContext cache.
+     * This should be used with caution since the purpose of this cache is save costly creation of Netty's SslContext
+     * objects and this essentially results in re-creating it.
+     */
+    public static void clearSslContextCache()

Review comment:
       @jonmeredith I made the last round of changes for the SSLFactory based on our prior conversations. Please review when you get a chance.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686606635



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {

Review comment:
       nit: is it really important to have a space after opening and before closing bracket? I do not think we follow this code style offically (applies for this whole method at least)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] bereng commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
bereng commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r667719570



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+        return sslContext;
+    }
+
+    @Override
+    public synchronized void initHotReloading(EncryptionOptions options) throws SSLException {
+
+        List<HotReloadableFile> fileList = new ArrayList<>();
+
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+        {
+            fileList.add(new HotReloadableFile(options.keystore));
+            fileList.add(new HotReloadableFile(options.truststore));
+        }
+
+        hotReloadableFiles = ImmutableList.copyOf(fileList);
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore(EncryptionOptions options) {
+        return new File(options.keystore).exists();
+    }
+
+    /**
+     * Helper class for hot reloading SSL Contexts
+     */
+    private static class HotReloadableFile
+    {
+        private final File file;
+        private volatile long lastModTime;
+
+        HotReloadableFile(String path)
+        {
+            file = new File(path);
+            lastModTime = file.lastModified();
+        }
+
+        boolean shouldReload()
+        {
+            long curModTime = file.lastModified();
+            boolean result = curModTime != lastModTime;
+            lastModTime = curModTime;
+            return result;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "HotReloadableFile{" +
+                   "file=" + file +
+                   ", lastModTime=" + lastModTime +
+                   '}';
+        }
+    }
+
+    KeyManagerFactory buildKeyManagerFactory(EncryptionOptions options) throws SSLException
+    {
+        try (InputStream ksf = Files.newInputStream(Paths.get(options.keystore)))
+        {
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(
+            options.algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : options.algorithm);
+            KeyStore ks = KeyStore.getInstance(options.store_type);
+            ks.load(ksf, options.keystore_password.toCharArray());
+            if (!checkedExpiry)
+            {
+                for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements(); )
+                {
+                    String alias = aliases.nextElement();
+                    if (ks.getCertificate(alias).getType().equals("X.509"))
+                    {
+                        Date expires = ((X509Certificate) ks.getCertificate(alias)).getNotAfter();
+                        if (expires.before(new Date()))
+                            logger.warn("Certificate for {} expired on {}", alias, expires);

Review comment:
       Ok so this comes from [here](https://github.com/apache/cassandra/pull/1027/files#diff-0d569398cfd58566fc56bfb80c971a72afe3f392addc2df731a0b44baf29019eL242) iiuc. Hence this is the current and accepted behavior which settles it for me.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688166626



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       On the comment around triggering the hot reloading, I feel having a reasonable polling check from the Cassandra could be okay as far as the polling period is not very long. So the implementation can have aggressive check to know if something changed about keys/certs, but it can still wait couple of more seconds may be before applying the change. What do you think?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r695302606



##########
File path: src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java
##########
@@ -0,0 +1,206 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract implementation for {@link ISslContextFactory} using file based, standard keystore format with the ability
+ * to hot-reload the files upon file changes (detected by the {@code last modified timestamp}).
+ *
+ * {@code CAUTION:} While this is a useful abstraction, please be careful if you need to modify this class
+ * given possible custom implementations out there!
+ */
+abstract public class FileBasedSslContextFactory extends AbstractSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(FileBasedSslContextFactory.class);
+
+    @VisibleForTesting
+    protected volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    protected volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    protected String keystore;
+    protected String keystore_password;
+    protected String truststore;
+    protected String truststore_password;

Review comment:
       This was done in order to enable other extensions like K8 example. It won't allow setting it in the K8 example constructor otherwise. Hence all possible implementation will have the same challenge if it was left as final.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r681317385



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.cassandra.security;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,String>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise
+     * @param cipherFilter to allow Netty's cipher suite filtering, e.g.
+     * {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType,
+                                     boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException;
+
+    /**
+     * Initializes hot reloading of the security keys/certs. The implementation must guarantee this to be thread safe.
+     * @param options Encryption options
+     * @throws SSLException
+     */
+    void initHotReloading(EncryptionOptions options) throws SSLException;
+
+    /**
+     * Returns if any changes require the reloading of the SSL context returned by this factory.
+     * This will be called by Cassandra's periodic polling for any potential changes that will reload the SSL context
+     * . However only newer connections established after the reload will use the reloaded SSL context.

Review comment:
       Indeed, it looks really odd




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r681333333



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.cassandra.security;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,String>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise
+     * @param cipherFilter to allow Netty's cipher suite filtering, e.g.
+     * {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType,
+                                     boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException;
+
+    /**
+     * Initializes hot reloading of the security keys/certs. The implementation must guarantee this to be thread safe.
+     * @param options Encryption options
+     * @throws SSLException
+     */
+    void initHotReloading(EncryptionOptions options) throws SSLException;
+
+    /**
+     * Returns if any changes require the reloading of the SSL context returned by this factory.
+     * This will be called by Cassandra's periodic polling for any potential changes that will reload the SSL context
+     * . However only newer connections established after the reload will use the reloaded SSL context.
+     * @return
+     */
+    boolean shouldReload();
+
+    /**
+     * Returns if this factory uses private keystore.
+     * @param options Encryption options
+     * @return {@code true} by default unless the implementation overrides this
+     */
+    default boolean hasKeystore(EncryptionOptions options) {
+        return true;
+    }
+
+    /**
+     * Indicates if the process holds the inbound/listening end of the socket ({@link SSLFactory.SocketType#SERVER})), or the
+     * outbound side ({@link SSLFactory.SocketType#CLIENT}).
+     */
+    enum SocketType {
+        SERVER, CLIENT;
+    }
+}

Review comment:
       Still need to do this. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r682215827



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+        return sslContext;
+    }
+
+    @Override
+    public synchronized void initHotReloading(EncryptionOptions options) throws SSLException {
+
+        List<HotReloadableFile> fileList = new ArrayList<>();
+
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+        {

Review comment:
       @smiklosovic While checking for this I also realized that we need to fix existing CheckedExpiry check. Once the expiry check runs for a keystore it doesn't run it again. This means If Client and Server options have different keystores it won't check for expired key in the second keystore (for whichever options the call is made after the first one to buildKeyManagerFactroy). Let me add a test around this first to validate the issue and fix it. Most likely I'll remove the 'static' for checkedExpiry variable also.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r687275632



##########
File path: src/java/org/apache/cassandra/security/SSLFactory.java
##########
@@ -351,9 +194,16 @@ public static void checkCertFilesForHotReloading(EncryptionOptions.ServerEncrypt
         if (!isHotReloadingInitialized)
             throw new IllegalStateException("Hot reloading functionality has not been initialized.");
 
-        logger.debug("Checking whether certificates have been updated {}", hotReloadableFiles);
+        logger.debug("Checking whether certificates have been updated {} and {}",
+                     serverOpts.sslContextFactoryInstance.getClass().getName(), clientOpts.sslContextFactoryInstance.getClass().getName());
+
+        boolean serverSslContextShouldReload = serverOpts == null ? false :
+                                               serverOpts.sslContextFactoryInstance.shouldReload();
+
+        boolean clientSslContextShouldReload = clientOpts == null ? false :
+                                               clientOpts.sslContextFactoryInstance.shouldReload();
 
-        if (hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload))
+        if (serverSslContextShouldReload || clientSslContextShouldReload)
         {
             logger.info("SSL certificates have been updated. Reseting the ssl contexts for new connections.");

Review comment:
       Sorry about it. Most of these were from the SSLFactory's code but this is a good time to make it better!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada edited a comment on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada edited a comment on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-906809781


   Thanks @jonmeredith and @tolbertam for the latest feedback on the usability aspect. Can you please point me to some test class you wrote and provide little more detail on what configuration issue you faced on the first attempt? I assume that when you say some values defaulted from the ones in `EncryptionOptions` you meant the values like "conf/.keystore" and "conf/.truststore" right? (Because the FileBasedSslContextFactory doesn't have any dependency on the EncryptionOption class as such but it does have the same defaults as in EncryptionOptions today.)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688285780



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);

Review comment:
       However, this makes me think if it is worth to change [HotReloadableFile#shouldReload](https://github.com/maulin-vasavada/cassandra/blob/CEP-9/src/java/org/apache/cassandra/security/DefaultSslContextFactory.java#L189) to make sure the new modified time is greater than the previous modified time instead of just `!=` comparison. However, that could still not change the behavior because it evaluates both the files and if any of them is changed, it would reload the SSL context.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] bereng commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
bereng commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r667699920



##########
File path: src/java/org/apache/cassandra/config/EncryptionOptions.java
##########
@@ -307,104 +342,121 @@ else if (isEnabled())
         }
     }
 
+    public EncryptionOptions withSslContextFactory(ParameterizedClass sslContextFactoryClass) {
+        return new EncryptionOptions(sslContextFactoryClass, keystore, keystore_password, truststore,

Review comment:
       Formatting.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-906816765


   @jonmeredith and @tolbertam Thanks for playing around it and verifying. I tried to reproduce what you experienced with the unit test and now I can clearly see what you mean. Based on that I agree with Jon that we should prevent the copied values from being set in the params and throw IllegalArgumentException - basically what we are saying is - if you want to just re-use the existing config keys that exist in the encryption options today, then `do not` set the same keys as parameterized class parameters.
   
   I'll add unit test around it to verify the exception.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r695188506



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and

Review comment:
       Perhaps worth adding a link to the Kubernetes Secrets spec - I found https://kubernetes.io/docs/concepts/configuration/secret/ which is hopefully the right one.

##########
File path: src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java
##########
@@ -0,0 +1,206 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract implementation for {@link ISslContextFactory} using file based, standard keystore format with the ability
+ * to hot-reload the files upon file changes (detected by the {@code last modified timestamp}).
+ *
+ * {@code CAUTION:} While this is a useful abstraction, please be careful if you need to modify this class
+ * given possible custom implementations out there!
+ */
+abstract public class FileBasedSslContextFactory extends AbstractSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(FileBasedSslContextFactory.class);
+
+    @VisibleForTesting
+    protected volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    protected volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    protected String keystore;
+    protected String keystore_password;
+    protected String truststore;
+    protected String truststore_password;

Review comment:
       Why remove `final` on these? I might have missed some other review feedback. 

##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * environment variable ({@code KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the truststore. The values in those timestamp variables is expected to
+ * be numeric values. Most obvious choice might be to just use time in milliseconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR: KEYSTORE_LAST_UPDATEDTIME
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR: TRUSTSTORE_LAST_UPDATEDTIME
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: KEYSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-last-updatedtime
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *       - name: TRUSTSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-last-updatedtime
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ * </pre>
+ */
+public class K8SecretsSslContextFactory extends FileBasedSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(K8SecretsSslContextFactory.class);
+
+    /**
+     * Use below config-keys to configure this factory.
+     */
+    public interface ConfigKeys {
+        String KEYSTORE_PATH = "KEYSTORE_PATH";
+        String KEYSTORE_PASSWORD_ENV_VAR = "KEYSTORE_PASSWORD_ENV_VAR";
+        String TRUSTSTORE_PATH = "TRUSTSTORE_PATH";
+        String TRUSTSTORE_PASSWORD_ENV_VAR = "TRUSTSTORE_PASSWORD_ENV_VAR";
+        String KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR = "KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR";
+        String TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR = "TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR";
+    }
+
+    public static final String DEFAULT_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR = "KEYSTORE_LAST_UPDATEDTIME";
+    public static final String DEFAULT_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR = "TRUSTSTORE_LAST_UPDATEDTIME";
+
+    public static final String DEFAULT_KEYSTORE_PASSWORD = "cassandra";
+    public static final String DEFAULT_TRUSTSTORE_PASSWORD = "cassandra";
+
+    @VisibleForTesting
+    static final String DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME = "KEYSTORE_PASSWORD";
+    @VisibleForTesting
+    static final String DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME = "TRUSTSTORE_PASSWORD";
+
+    private static final String KEYSTORE_PATH_VALUE = "/etc/my-ssl-store/keystore";
+    private static final String TRUSTSTORE_PATH_VALUE = "/etc/my-ssl-store/truststore";
+    private static final String KEYSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_KEYSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR_NAME = DEFAULT_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR;
+    private static final String TRUSTSTORE_PASSWORD_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_PASSWORD_ENV_VAR_NAME;
+    private static final String TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR_NAME = DEFAULT_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR;
+
+    private long keystoreLastUpdatedTime;
+    private long truststoreLastUpdatedTime;
+
+    public K8SecretsSslContextFactory()
+    {
+        keystore = KEYSTORE_PATH_VALUE;
+        keystore_password = getValueFromEnv(KEYSTORE_PASSWORD_ENV_VAR_NAME,
+                                            DEFAULT_KEYSTORE_PASSWORD);
+        truststore = TRUSTSTORE_PATH_VALUE;
+        truststore_password = getValueFromEnv(TRUSTSTORE_PASSWORD_ENV_VAR_NAME,
+                                              DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.currentTimeMillis();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    public K8SecretsSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString(ConfigKeys.KEYSTORE_PATH, KEYSTORE_PATH_VALUE);
+        keystore_password = getValueFromEnv(getString(ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR,
+                                                      KEYSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_KEYSTORE_PASSWORD);
+        truststore = getString(ConfigKeys.TRUSTSTORE_PATH, TRUSTSTORE_PATH_VALUE);
+        truststore_password = getValueFromEnv(getString(ConfigKeys.TRUSTSTORE_PASSWORD_ENV_VAR,
+                                                        TRUSTSTORE_PASSWORD_ENV_VAR_NAME), DEFAULT_TRUSTSTORE_PASSWORD);
+        keystoreLastUpdatedTime = System.currentTimeMillis();
+        truststoreLastUpdatedTime = keystoreLastUpdatedTime;
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        // No-op
+    }
+
+    /**
+     * Checks environment variables for {@code K8_SECRET_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} and {@code K8_SECRET_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR}
+     * and compares the values for those variables with the current timestamps. In case the environment variables are
+     * not valid (either they are not initialized yet, got removed or got corrupted in-flight), this method considers
+     * that nothing is changed.
+     * @return {@code true} if either of the timestamps (keystore or truststore) got updated;{@code false} otherwise
+     */
+    @Override
+    public boolean shouldReload()
+    {
+        return hasKeystoreUpdated() || hasTruststoreUpdated();
+    }
+
+    @VisibleForTesting
+    String getValueFromEnv(String envVarName, String defaultValue) {
+        String valueFromEnv = StringUtils.isEmpty(envVarName) ? null : System.getenv(envVarName);
+        return StringUtils.isEmpty(valueFromEnv) ? defaultValue : valueFromEnv;

Review comment:
       Does this mean it isn't possible to set an empty environment variable value - for example an empty password if not desired? Sounds like `keytool` refuses, but [apparently it's possible](https://stackoverflow.com/questions/23629246/is-it-possible-to-create-jks-keystore-file-without-a-password) to do it programmatically.

##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * environment variable ({@code KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the truststore. The values in those timestamp variables is expected to
+ * be numeric values. Most obvious choice might be to just use time in milliseconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR: KEYSTORE_LAST_UPDATEDTIME
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR: TRUSTSTORE_LAST_UPDATEDTIME

Review comment:
       Are `KEYSTORE_LAST_UPDATEDTIME` and `TRUSTSTORE_LAST_UPDATEDTIME` propagated to the running Cassandra process when the secret is updated? I can't think of any other operating environment that externally modifies a programs environment variables after startup. I can see it being updated when a pod restarts, but that wouldn't be very useful for the implementation below.

##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * environment variable ({@code KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the truststore. The values in those timestamp variables is expected to
+ * be numeric values. Most obvious choice might be to just use time in milliseconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR: KEYSTORE_LAST_UPDATEDTIME
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR: TRUSTSTORE_LAST_UPDATEDTIME
+ * </pre>
+ *
+ * Below is the corresponding sample YAML configuration for K8 env.
+ * <pre>
+ * apiVersion: v1
+ * kind: Pod
+ * metadata:
+ *   name: my-pod
+ *   labels:
+ *     app: my-app
+ * spec:
+ *   containers:
+ *   - name: my-app
+ *     image: my-app:latest
+ *     imagePullPolicy: Always
+ *     env:
+ *       - name: KEYSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-password
+ *       - name: KEYSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: keystore-last-updatedtime
+ *       - name: TRUSTSTORE_PASSWORD
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-password
+ *       - name: TRUSTSTORE_LAST_UPDATEDTIME
+ *         valueFrom:
+ *           secretKeyRef:
+ *             name: my-ssl-store
+ *             key: truststore-last-updatedtime
+ *     volumeMounts:
+ *     - name: my-ssl-store
+ *       mountPath: "/etc/my-ssl-store"
+ *       readOnly: true
+ *   volumes:
+ *   - name: my-ssl-store
+ *     secret:
+ *       secretName: my-ssl-store
+ *       items:
+ *         - key: cassandra_ssl_keystore
+ *           path: keystore
+ *         - key: cassandra_ssl_truststore
+ *           path: truststore
+ * </pre>
+ */
+public class K8SecretsSslContextFactory extends FileBasedSslContextFactory

Review comment:
       Naming question - is Kubernetes more accurately `K8S` rather than `K8`, so this would be `K8SSecretSslContextFactory`?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686614940



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {

Review comment:
       SSLException is never thrown from this method.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r697081153



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       @jonmeredith Please let me know your thoughts when you get a chance.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r696071705



##########
File path: examples/ssl-factory/src/org/apache/cassandra/security/K8SecretsSslContextFactory.java
##########
@@ -0,0 +1,260 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Custom {@link ISslContextFactory} implementation based on Kubernetes Secrets. It allows the keystore and
+ * truststore paths to be configured from the K8 secrets via volumeMount and passwords via K8 secrets environment
+ * variables.
+ *
+ * When keystore or truststore is updated, this implementation can detect that based on updated K8 secrets
+ * environment variable ({@code KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the keystore and {@code
+ * TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR} for the truststore. The values in those timestamp variables is expected to
+ * be numeric values. Most obvious choice might be to just use time in milliseconds but any other strategy would work
+ * as well, as far as the comparison of those values can be done in a consistent/predictable manner. Again, those
+ * values don't have to necessarily reflect actual file's update timestamps, using the actual file's timestamps is
+ * just one of the valid options to signal updates.
+ *
+ * Defaults:
+ * <pre>
+ *     keystore path = /etc/my-ssl-store/keystore
+ *     keystore password = cassandra
+ *     truststore path = /etc/my-ssl-store/truststore
+ *     truststore password = cassandra
+ * </pre>
+ *
+ * Customization: In order to customize the K8 secret configuration, override appropriate values in the below cassandra
+ * configuration. The similar configuration can be applied to {@code client_encryption_options}.
+ * <pre>
+ *     server_encryption_options:
+ *       internode_encryption: none
+ *       ssl_context_factory:
+ *         class_name: org.apache.cassandra.security.K8SecretsSslContextFactory
+ *         parameters:
+ *           KEYSTORE_PATH: /etc/my-ssl-store/keystore
+ *           KEYSTORE_PASSWORD_ENV_VAR: KEYSTORE_PASSWORD
+ *           KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR: KEYSTORE_LAST_UPDATEDTIME
+ *           TRUSTSTORE_PATH: /etc/my-ssl-store/truststore
+ *           TRUSTSTORE_PASSWORD_ENV_VAR: TRUSTSTORE_PASSWORD
+ *           TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR: TRUSTSTORE_LAST_UPDATEDTIME

Review comment:
       @jonmeredith You are right. Only the mounted volumes gets updates (depending upon how you pull + kubelet sync configuration) and env variables [don't get updated in the running pods](https://kubernetes.io/docs/concepts/configuration/secret/#environment-variables-are-not-updated-after-a-secret-update). Given that I should change the K8s example to use a variable from a mounted volume but its little ugly since I've to read it like a file just for a single value!




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] bereng commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
bereng commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r667710196



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+        return sslContext;
+    }
+
+    @Override
+    public synchronized void initHotReloading(EncryptionOptions options) throws SSLException {
+
+        List<HotReloadableFile> fileList = new ArrayList<>();
+
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+        {
+            fileList.add(new HotReloadableFile(options.keystore));
+            fileList.add(new HotReloadableFile(options.truststore));
+        }
+
+        hotReloadableFiles = ImmutableList.copyOf(fileList);
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore(EncryptionOptions options) {
+        return new File(options.keystore).exists();
+    }
+
+    /**
+     * Helper class for hot reloading SSL Contexts
+     */
+    private static class HotReloadableFile
+    {
+        private final File file;
+        private volatile long lastModTime;
+
+        HotReloadableFile(String path)
+        {
+            file = new File(path);
+            lastModTime = file.lastModified();
+        }
+
+        boolean shouldReload()
+        {
+            long curModTime = file.lastModified();
+            boolean result = curModTime != lastModTime;
+            lastModTime = curModTime;
+            return result;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "HotReloadableFile{" +
+                   "file=" + file +
+                   ", lastModTime=" + lastModTime +
+                   '}';
+        }
+    }
+
+    KeyManagerFactory buildKeyManagerFactory(EncryptionOptions options) throws SSLException
+    {
+        try (InputStream ksf = Files.newInputStream(Paths.get(options.keystore)))
+        {
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(
+            options.algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : options.algorithm);
+            KeyStore ks = KeyStore.getInstance(options.store_type);
+            ks.load(ksf, options.keystore_password.toCharArray());
+            if (!checkedExpiry)
+            {
+                for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements(); )
+                {
+                    String alias = aliases.nextElement();

Review comment:
       Could this be moved into the `for` loop?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688164452



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;

Review comment:
       I like `verifyPeerCertificate` name. I feel verifyTrust is again going back to having a notion of 'truststore' in the variable name vs verify Peer Certificate is clearly communicating the intent and truststore is just a way to verify it so the implementations need to have the truststore build.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r686624699



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);

Review comment:
       what happens when `initHotReloading` populates hotReloadableFiles with one file only? For example, I can remove the truststore file from disk so it would not exist but I would make keystore existing and newer.
   
   So that means that shouldReload would return true because there is a file which should be reloaded, but when reloading is being to be conducted, does not it fail because my other file, truststore, does not exist anymore?
   
   In other words, what happens with reloading when I (e.g. accidentaly) remove the other file? 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688530766



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactory.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactory extends AbstractSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactory.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+
+    public DefaultSslContextFactory()
+    {
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+    }
+
+    public DefaultSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore() {
+        return keystore != null && new File(keystore).exists();
+    }
+
+    private boolean hasTruststore() {
+        return truststore != null && new File(truststore).exists();
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if (hasKeystore || hasTruststore) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    /**
+     * Builds required KeyManagerFactory from the file based keystore. It also checks for the PrivateKey's certificate's
+     * expiry and logs {@code warning} for each expired PrivateKey's certitificate.
+     *
+     * @return KeyManagerFactory built from the file based keystore.
+     * @throws SSLException if any issues encountered during the build process
+     */
+    protected KeyManagerFactory buildKeyManagerFactory() throws SSLException

Review comment:
       I don't think there's any point having protected members in a `final` class.

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       I think that would probably work. There's a very small time-of-check, time-of-use issue that I'm not sure if it would be a problem in that you say 'ready to reload' and 'reload'. CEP-9 was considering the use case where the key/trust store materials are retrieved from some remote service.  It's probably ok to poll for changes and load, and probably just as bad as the local filesystem as you can't atomically replace both the keystone and truststore at the same time if they were separate files.
   
   If the implementation does have to track some kind of state about whether to reload, when should that be cleared? Is there a contract with the interface that if `shouldReload()` is called the SSLContext will be revalidated and the cache cleared?
   
   So the other thing that could be an issue is configurations where different SSL factories are used for the client and internode connections.  At the moment if either of the configurations flags a reload, then both are reloaded which may be unexpected by an implementation.
   
   I think it would be cleaner to move all of the hot-loading stuff moved out to something like `HotReloadingSslFactory` and just expose an `SSLFactory.validateAndRefreshCache` method that can be called to indicate the cache is now stale and can be cleared.
   
   The 'cache' itself is a bit weird, only likely containing at most eight items, and more likely just two.
   
   

##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactory.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactory extends AbstractSslContextFactory

Review comment:
       Thanks - this is along the lines of what I was thinking.  I think having the `DefaultSslContextFactory` as final is a good thing but it would prevent the use case of somebody who just wants to just do minor tweaks the the netty SSL context and keep all of the netty machinery in place.
   
   What do you think about renaming this to a non-final `HotReloadingSslContextFactory` class and making a shell `final class DefaultSslContextFactory extends HotReloadingSslContextFactory`?
   
   Either that or we drop the `final` from this class so it can be extended, but not sure if that opens up any security issues - probably not as an attacker would have had sufficient credentials to alter the config to pick up a hostile parameterized class anyway.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688820397



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       I've to still think about this bit more. 
   
   Three points to still address (apart from providing an example),
   1. I agree that currently - as soon as either of the configurations (server or client) changes, it triggers reloading for both which is unnecessary. 
   2. I've to address the SSLFactory.validateAndRefreshCache related comment and 
   3. `useOpenSsl` flag's removal. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688285780



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);

Review comment:
       This makes me think if it is worth to change [HotReloadableFile#shouldReload](https://github.com/maulin-vasavada/cassandra/blob/CEP-9/src/java/org/apache/cassandra/security/DefaultSslContextFactory.java#L189) to make sure the new modified time is greater than the previous modified time instead of just `!=` comparison. However, that could still not change the behavior because it evaluates both the files and if any of them is changed, it would reload the SSL context.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-906809781


   Thanks @jonmeredith and @tolbertam for the latest feedback on the usability aspect. Can you please point me to some test class you wrote and what detail on what configuration issue you faced on the first attempt? I assume that when you say some values defaulted from the ones in `EncryptionOptions` you meant the values like "conf/.keystore" and "conf/.truststore" right? (Because the FileBasedSslContextFactory doesn't have any dependency on the EncryptionOption class as such.)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r695046745



##########
File path: examples/security/build.xml
##########
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+-->
+
+<project default="jar" name="security-example">

Review comment:
       Sounds good. I started off like that but then I thought we can possibly keep a generic project for security related examples. However, I can see how keeping independent examples make it clearer and easy to understand/play-around for the intended audience.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r694641338



##########
File path: examples/security/test/unit/org/apache/cassandra/security/K8SecretsSslContextFactoryTest.java
##########
@@ -0,0 +1,224 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class K8SecretsSslContextFactoryTest
+{
+    private static final Logger logger = LoggerFactory.getLogger(K8SecretsSslContextFactoryTest.class);
+
+    private Map<String,Object> commonConfig = new HashMap<>();
+
+    private static class K8SecretsSslContextFactoryForTestOnly extends K8SecretsSslContextFactory {
+
+        public K8SecretsSslContextFactoryForTestOnly() {
+        }
+
+        public K8SecretsSslContextFactoryForTestOnly(Map<String,Object> config) {
+            super(config);
+        }
+
+        /*
+         * This is overriden to first give priority to the input map configuration since we should not be setting env
+         * variables from the unit tests. However, if the input map configuration doesn't have the value for the
+         * given key then fallback to loading from the real environment variables.
+         */
+        @Override
+        String getValueFromEnv(String envVarName, String defaultValue) {
+            String envVarValue = parameters.get(envVarName) != null ? parameters.get(envVarName).toString() : null;
+            if (StringUtils.isEmpty(envVarValue)) {
+                logger.info("Configuration doesn't have env variable {}. Will use parent's implementation", envVarName);
+                return super.getValueFromEnv(envVarName, defaultValue);
+            } else {
+                logger.info("Configuration has env variable {} with value {}. Will use that.",
+                            envVarName, envVarValue);
+                return envVarValue;
+            }
+        }
+    }
+
+    /*
+    private final String K8_SECRET_KEYSTORE_UPDATED_TIMESTAMP_ENV_VAR = "KEYSTORE_LAST_UPDATEDTIME";
+    private final String K8_SECRET_TRUSTSTORE_UPDATED_TIMESTAMP_ENV_VAR = "TRUSTSTORE_LAST_UPDATEDTIME";
+     */
+
+    @Before
+    public void setup()
+    {
+        commonConfig.put("TRUSTSTORE_PATH", "test/conf/cassandra_ssl_test.truststore");

Review comment:
       cant be this string a constant? similar for KEYSTORE_PATH, my idea is to somehow import it and reuse from K8SecretsSslContextFactory




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#issuecomment-933664771


   This work has been integrated as part of the [PR#1243](https://github.com/apache/cassandra/pull/1243). Closing this without requiring merge. Thanks everybody who participated.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688741302



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactory.java
##########
@@ -0,0 +1,203 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactory extends AbstractSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactory.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+
+    public DefaultSslContextFactory()
+    {
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+    }
+
+    public DefaultSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore() {
+        return keystore != null && new File(keystore).exists();
+    }
+
+    private boolean hasTruststore() {
+        return truststore != null && new File(truststore).exists();
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if (hasKeystore || hasTruststore) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    /**
+     * Builds required KeyManagerFactory from the file based keystore. It also checks for the PrivateKey's certificate's
+     * expiry and logs {@code warning} for each expired PrivateKey's certitificate.
+     *
+     * @return KeyManagerFactory built from the file based keystore.
+     * @throws SSLException if any issues encountered during the build process
+     */
+    protected KeyManagerFactory buildKeyManagerFactory() throws SSLException

Review comment:
       Good point and I thought about it before the change but because in the parent we had to keep it as protected we can't reduce the visibility scope.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] sumanth-pasupuleti commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
sumanth-pasupuleti commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r646663090



##########
File path: src/java/org/apache/cassandra/utils/FBUtilities.java
##########
@@ -687,6 +688,22 @@ public static IAuditLogger newAuditLogger(String className, Map<String, String>
         }
     }
 
+    public static ISslContextFactory newSslContextFactory(String className, Map<String,String> parameters) throws ConfigurationException
+    {
+        if (!className.contains("."))
+            className = "org.apache.cassandra.security." + className;
+
+        try
+        {
+            Class<?> sslContextFactoryClass = Class.forName(className);
+            return (ISslContextFactory) sslContextFactoryClass.getConstructor(Map.class).newInstance(parameters);
+        }
+        catch (Exception ex)
+        {
+            throw new ConfigurationException("Unable to create instance of ISslContextFactory for "+className, ex);

Review comment:
       nit: spacing
   for "+className --> for " + className

##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,108 @@
+/*
+ * 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.cassandra.security;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,String>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param options EncryptionOptions that could be used for the SSL context creation
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise
+     * @param cipherFilter to allow Netty's cipher suite filtering, e.g.
+     * {@link io.netty.handler.ssl.SslContextBuilder#ciphers(Iterable, CipherSuiteFilter)}
+     * @return
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType,
+                                     boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException;
+
+    /**
+     * Initializes hot reloading of the security keys/certs. The implementation must guarantee this to be thread safe.
+     * @param options Encryption options
+     * @throws SSLException
+     */
+    void initHotReloading(EncryptionOptions options) throws SSLException;
+
+    /**
+     * Returns if any changes require the reloading of the SSL context returned by this factory.
+     * This will be called by Cassandra's periodic polling for any potential changes that will reload the SSL context
+     * . However only newer connections established after the reload will use the reloaded SSL context.
+     * @return
+     */
+    boolean shouldReload();
+
+    /**
+     * Returns if this factory uses private keystore.
+     * @param options Encryption options
+     * @return {@code true} by default unless the implementation overrides this
+     */
+    default boolean hasKeystore(EncryptionOptions options) {
+        return true;
+    }
+
+    /**
+     * Indicates if the process holds the inbound/listening end of the socket ({@link SSLFactory.SocketType#SERVER})), or the
+     * outbound side ({@link SSLFactory.SocketType#CLIENT}).
+     */
+    enum SocketType {
+        SERVER, CLIENT;
+    }
+}

Review comment:
       I see that we now define SocketType enum in both SSLFactory and ISslContextFactory. We should probably consolidate




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] bereng commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
bereng commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r667697785



##########
File path: src/java/org/apache/cassandra/config/EncryptionOptions.java
##########
@@ -93,8 +117,13 @@ public EncryptionOptions()
         optional = null;
     }
 
-    public EncryptionOptions(String keystore, String keystore_password, String truststore, String truststore_password, List<String> cipher_suites, String protocol, List<String> accepted_protocols, String algorithm, String store_type, boolean require_client_auth, boolean require_endpoint_verification, Boolean enabled, Boolean optional)
+    public EncryptionOptions(ParameterizedClass ssl_context_factory, String keystore, String keystore_password,

Review comment:
       Formatting




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r694621603



##########
File path: examples/security/build.xml
##########
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+-->
+
+<project default="jar" name="security-example">

Review comment:
       could we rename this project and directory it lives in to something more descriptive? like "ssl-factory" or similar ... "security-example" is quite non-telling.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] smiklosovic commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
smiklosovic commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r694632766



##########
File path: src/java/org/apache/cassandra/security/FileBasedSslContextFactory.java
##########
@@ -0,0 +1,206 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract implementation for {@link ISslContextFactory} using file based, standard keystore format with the ability
+ * to hot-reload the files upon file changes (detected by the {@code last modified timestamp}).
+ *
+ * {@code CAUTION:} While this is a useful abstraction, please be careful if you need to modify this class
+ * given possible custom implementations out there!
+ */
+abstract public class FileBasedSslContextFactory extends AbstractSslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(FileBasedSslContextFactory.class);
+
+    @VisibleForTesting
+    protected volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    protected volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    protected String keystore;
+    protected String keystore_password;
+    protected String truststore;
+    protected String truststore_password;
+
+    public FileBasedSslContextFactory()
+    {
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+    }
+
+    public FileBasedSslContextFactory(Map<String, Object> parameters)
+    {
+        super(parameters);
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);
+    }
+
+    @Override
+    public boolean hasKeystore() {
+        return keystore != null && new File(keystore).exists();
+    }
+
+    private boolean hasTruststore() {
+        return truststore != null && new File(truststore).exists();
+    }
+
+    @Override
+    public synchronized void initHotReloading() {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if (hasKeystore || hasTruststore) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )

Review comment:
       nit: formatting, should be "if (hasKeystore)", it is same for hasTruststore below.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r682223362



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,248 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import org.apache.cassandra.config.EncryptionOptions;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    static volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private static volatile List<HotReloadableFile> hotReloadableFiles = ImmutableList.of();
+
+    private Map<String,String> parameters;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){}
+
+    public DefaultSslContextFactoryImpl(Map<String,String> parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(EncryptionOptions options, boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory(options).getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(EncryptionOptions options, boolean buildTruststore, SocketType socketType
+    , boolean useOpenSsl, CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. using the netty-tcnative static-boringssl
+            works fine with KeyManagerFactory. If we want to support all of the netty-tcnative options, we would need
+            to fall back to passing in a file reference for both a x509 and PKCS#8 private key file in PEM format (see
+            {@link SslContextBuilder#forServer(File, File, String)}). However, we are not supporting that now to keep
+            the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory(options);
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf);
+            builder.clientAuth(options.require_client_auth ? ClientAuth.REQUIRE : ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK);
+
+        builder.protocols(options.acceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (options.cipher_suites != null && !options.cipher_suites.isEmpty())
+            builder.ciphers(options.cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory(options));
+
+        SslContext sslContext;
+        try
+        {
+            sslContext = builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+        return sslContext;
+    }
+
+    @Override
+    public synchronized void initHotReloading(EncryptionOptions options) throws SSLException {
+
+        List<HotReloadableFile> fileList = new ArrayList<>();
+
+        if (options != null && options.tlsEncryptionPolicy() != EncryptionOptions.TlsEncryptionPolicy.UNENCRYPTED)
+        {

Review comment:
       Okay fixed both the points. Made the reloadable file list instance member and mutable and made the 'checkedExpiry' also an instance member with the corresponding check in the unit test.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r688283660



##########
File path: src/java/org/apache/cassandra/security/DefaultSslContextFactoryImpl.java
##########
@@ -0,0 +1,369 @@
+/*
+ * 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.cassandra.security;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+/**
+ * Cassandra's default implementation class for the configuration key {@code ssl_context_factory}. It uses
+ * file based keystores.
+ */
+public final class DefaultSslContextFactoryImpl implements ISslContextFactory
+{
+    private static final Logger logger = LoggerFactory.getLogger(DefaultSslContextFactoryImpl.class);
+
+    @VisibleForTesting
+    volatile boolean checkedExpiry = false;
+
+    /**
+     * List of files that trigger hot reloading of SSL certificates
+     */
+    private volatile List<HotReloadableFile> hotReloadableFiles = new ArrayList<>();
+
+    /* This list is substituted in configurations that have explicitly specified the original "TLS" default,
+     * by extracting it from the default "TLS" SSL Context instance
+     */
+    static private final List<String> TLS_PROTOCOL_SUBSTITUTION = SSLFactory.tlsInstanceProtocolSubstitution();
+
+    private final Map<String,Object> parameters;
+    private final String keystore;
+    private final String keystore_password;
+    private final String truststore;
+    private final String truststore_password;
+    private final List<String> cipher_suites;
+    private String protocol;
+    private List<String> accepted_protocols;
+    private final String algorithm;
+    private final String store_type;
+    private final boolean require_client_auth;
+    private final boolean require_endpoint_verification;
+    // ServerEncryptionOptions does not use the enabled flag at all instead using the existing
+    // internode_encryption option. So we force this private and expose through isEnabled
+    // so users of ServerEncryptionOptions can't accidentally use this when they should use isEnabled
+    // Long term we need to refactor ClientEncryptionOptions and ServerEncyrptionOptions to be separate
+    // classes so we can choose appropriate configuration for each.
+    // See CASSANDRA-15262 and CASSANDRA-15146
+    protected Boolean enabled;
+    protected Boolean optional;
+
+    /* For test only */
+    DefaultSslContextFactoryImpl(){
+        parameters = new HashMap<>();
+        keystore = "conf/.keystore";
+        keystore_password = "cassandra";
+        truststore = "conf/.truststore";
+        truststore_password = "cassandra";
+        cipher_suites = null;
+        protocol = null;
+        accepted_protocols = null;
+        algorithm = null;
+        store_type = "JKS";
+        require_client_auth = false;
+        require_endpoint_verification = false;
+        enabled = null;
+        optional = null;
+    }
+
+    public DefaultSslContextFactoryImpl(Map<String,Object> parameters) {
+        this.parameters = parameters;
+        keystore = getString("keystore");
+        keystore_password = getString("keystore_password");
+        truststore = getString("truststore");
+        truststore_password = getString("truststore_password");
+        cipher_suites = getStringList("cipher_suites");
+        protocol = getString("protocol");
+        accepted_protocols = getStringList("accepted_protocols");
+        algorithm = getString("algorithm");
+        store_type = getString("store_type", "JKS");
+        require_client_auth = getBoolean("require_client_auth", false);
+        require_endpoint_verification = getBoolean("require_endpoint_verification", false);
+        enabled = getBoolean("enabled");
+        this.optional = getBoolean("optional");
+    }
+
+    private String getString(String key, String defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (String)this.parameters.get(key);
+    }
+
+    private String getString(String key) {
+        return (String)this.parameters.get(key);
+    }
+
+    private List<String> getStringList(String key) {
+        return (List<String>)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key, boolean defaultValue) {
+        return this.parameters.get(key) == null ? defaultValue : (Boolean)this.parameters.get(key);
+    }
+
+    private Boolean getBoolean(String key) {
+        return (Boolean)this.parameters.get(key);
+    }
+
+    @Override
+    public SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException
+    {
+        TrustManager[] trustManagers = null;
+        if (buildTruststore)
+            trustManagers = buildTrustManagerFactory().getTrustManagers();
+
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+
+        try
+        {
+            SSLContext ctx = SSLContext.getInstance("TLS");
+            ctx.init(kmf.getKeyManagers(), trustManagers, null);
+            return ctx;
+        }
+        catch (Exception e)
+        {
+            throw new SSLException("Error creating/initializing the SSL Context", e);
+        }
+    }
+
+    @Override
+    public SslContext createNettySslContext(boolean buildTruststore, SocketType socketType, boolean useOpenSsl,
+                                            CipherSuiteFilter cipherFilter) throws SSLException
+    {
+        /*
+            There is a case where the netty/openssl combo might not support using KeyManagerFactory. Specifically,
+            I've seen this with the netty-tcnative dynamic openssl implementation. Using the netty-tcnative
+            static-boringssl works fine with KeyManagerFactory. If we want to support all of the netty-tcnative
+            options, we would need to fall back to passing in a file reference for both a x509 and PKCS#8 private
+            key file in PEM format (see {@link SslContextBuilder#forServer(File, File, String)}). However, we are
+            not supporting that now to keep the config/yaml API simple.
+         */
+        KeyManagerFactory kmf = buildKeyManagerFactory();
+        SslContextBuilder builder;
+        if (socketType == SocketType.SERVER)
+        {
+            builder = SslContextBuilder.forServer(kmf).clientAuth(this.require_client_auth ? ClientAuth.REQUIRE :
+                                                                  ClientAuth.NONE);
+        }
+        else
+        {
+            builder = SslContextBuilder.forClient().keyManager(kmf);
+        }
+
+        builder.sslProvider(useOpenSsl ? SslProvider.OPENSSL : SslProvider.JDK).protocols(getAcceptedProtocols());
+
+        // only set the cipher suites if the opertor has explicity configured values for it; else, use the default
+        // for each ssl implemention (jdk or openssl)
+        if (cipher_suites != null && !cipher_suites.isEmpty())
+            builder.ciphers(cipher_suites, cipherFilter);
+
+        if (buildTruststore)
+            builder.trustManager(buildTrustManagerFactory());
+
+        try
+        {
+            return builder.build();
+        }
+        catch (SSLException e)
+        {
+            throw new SSLException("failed to build the final SslContext object for secure connections", e);
+        }
+    }
+
+    @Override
+    public synchronized void initHotReloading() throws SSLException {
+        boolean hasKeystore = hasKeystore();
+        boolean hasTruststore = hasTruststore();
+
+        if ( hasKeystore || hasTruststore ) {
+            List<HotReloadableFile> fileList = new ArrayList<>();
+            if ( hasKeystore )
+            {
+                fileList.add(new HotReloadableFile(keystore));
+            }
+            if ( hasTruststore ) {
+                fileList.add(new HotReloadableFile(truststore));
+            }
+            hotReloadableFiles = fileList;
+        }
+    }
+
+    @Override
+    public boolean shouldReload()
+    {
+        return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload);

Review comment:
       The `initHotReloading` happens during the initialization from the DatabaseDescriptor. Let us say keystore and truststore both files exists at that time. Now question is - what happens when one of those files is removed and `shouldReload` is called? Since Java APIs doesn't throw any errors for [file.lastModified() ](https://docs.oracle.com/javase/8/docs/api/java/io/File.html#lastModified--) in case file doesn't exist, it would think it should reload the file and [log error](https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/security/SSLFactory.java#L366) silently and continue. However, based on the current code (with or without this PR) when it actually tries to build the key/trust manager factory it would fail. This is the existing behavior in SSLFactory. Since we are not changing that behavior I am not sure if we need to change anything here.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] jonmeredith commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
jonmeredith commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r701493528



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       Sorry I missed responding here. I think this is the last thing to resolve, everything else looked good to me.
   
   I think invalidating for the appropriate option is just fine.  I think your `shouldReload()` changes make sense, but will need to see the final commit to be sure.
   




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org


[GitHub] [cassandra] maulin-vasavada commented on a change in pull request #1027: [CEP-9] Make SSL Context creation pluggable

Posted by GitBox <gi...@apache.org>.
maulin-vasavada commented on a change in pull request #1027:
URL: https://github.com/apache/cassandra/pull/1027#discussion_r706317008



##########
File path: src/java/org/apache/cassandra/security/ISslContextFactory.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.cassandra.security;
+
+import java.util.List;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+
+import io.netty.handler.ssl.CipherSuiteFilter;
+import io.netty.handler.ssl.SslContext;
+
+/**
+ * The purpose of this interface is to provide pluggable mechanism for creating custom JSSE and Netty SSLContext
+ * objects. Please use the Cassandra configuration key {@code ssl_context_factory} as part of {@code
+ * client_encryption_options}/{@code server_encryption_options} and provide a custom class-name implementing this
+ * interface with parameters to be used to plugin a your own way to load the SSLContext.
+ *
+ * Implementation of this interface must have a constructor with argument of type {@code Map<String,Object>} to allow
+ * custom parameters, needed by the implementation, to be passed from the yaml configuration. Common SSL
+ * configurations like {@code protocol, algorithm, cipher_suites, accepted_protocols, require_client_auth,
+ * require_endpoint_verification, enabled, optional} will also be passed to that map by Cassanddra.
+ *
+ * Since on top of Netty, Cassandra is internally using JSSE SSLContext also for certain use-cases- this interface
+ * has methods for both.
+ *
+ * Below is an example of how to configure a custom implementation with parameters
+ * <pre>
+ * ssl_context_factory:
+ *       class_name: org.apache.cassandra.security.YourSslContextFactoryImpl
+ *       parameters:
+ *         key1: "value1"
+ *         key2: "value2"
+ *         key3: "value3"
+ * </pre>
+ */
+public interface ISslContextFactory
+{
+    /**
+     * Creates JSSE SSLContext.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @return JSSE's {@link SSLContext}
+     * @throws SSLException in case the Ssl Context creation fails for some reason
+     */
+    SSLContext createJSSESslContext(boolean buildTruststore) throws SSLException;
+
+    /**
+     * Creates Netty's SslContext object.
+     *
+     * @param buildTruststore {@code true} if the caller requires Truststore; {@code false} otherwise
+     * @param socketType {@link SocketType} for Netty's Inbound or Outbound channels
+     * @param useOpenSsl {@code true} if openSsl is enabled;{@code false} otherwise

Review comment:
       @jonmeredith I've made the changes on this PR based on the review in the self-PR :) Btw, thanks for that review before I could bring it here.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscribe@cassandra.apache.org
For additional commands, e-mail: pr-help@cassandra.apache.org