Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure SSLOptions and JKS Via Config #2

Closed
ctoestreich opened this issue Oct 18, 2019 · 1 comment · Fixed by #403
Closed

Configure SSLOptions and JKS Via Config #2

ctoestreich opened this issue Oct 18, 2019 · 1 comment · Fixed by #403
Assignees
Labels
type: enhancement New feature or request

Comments

@ctoestreich
Copy link

This is sort of a feature request/todo. We needed to add JKS support and wanted to do it via config so we did the below. Can make PR if have time.

Config

import io.micronaut.context.annotation.EachProperty;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;

@EachProperty(value = "cassandra", primary = "default")
@Getter
@Setter
public class CassandraConfigurationProperties {

    @NonNull
    private String[] contactPoints = new String[]{"127.0.0.1"};
    private int port = 9042;
    @NonNull
    private String clusterName = "local-cluster";
    @NonNull
    private String keyspaceName = "keyspace";
    @NonNull
    private String userName = "admin";
    @NonNull
    private String password = "admin";
    private boolean sslEnabled = false;
    private String keystoreLocation;
    private String keystorePassword;
    private String keyPassword;
    private String truststoreLocation;
    private String truststorePassword;
    private String consistencyLevel = "QUORUM";
}

Beans

import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.RemoteEndpointAwareJdkSSLOptions;
import com.datastax.driver.core.SSLOptions;
import com.datastax.driver.core.Session;
import com.datastax.driver.mapping.MappingManager;
import CassandraConfigurationProperties;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.event.BeanCreatedEvent;
import io.micronaut.context.event.BeanCreatedEventListener;
import io.micronaut.core.util.StringUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Locale;
import java.util.Optional;

@Factory
@AllArgsConstructor
@Slf4j
public class CassandraBeanFactory implements BeanCreatedEventListener<Cluster.Builder> {

    private static final String JKS = "JKS";
    private static final String TLS = "TLS";
    private final CassandraConfigurationProperties cassandraConfigurationProperties;

    @Bean
    public Session session(final Cluster cluster) {
        final Session session = cluster.connect();
        session.execute(String.format(Locale.ROOT, "USE %s", cassandraConfigurationProperties.getKeyspaceName()));
        return session;
    }

    @Bean
    public MappingManager mappingManager(final Session session) {
        return new MappingManager(session);
    }

    /**
     * Datastax uses codahale metrics, which is the old metrics library.  Needs to support micrometer then we can re-enable.
     *
     * @param event The bean to configure
     * @return the same bean, but with more sprinkles
     */
    @Override
    public Cluster.Builder onCreated(final BeanCreatedEvent<Cluster.Builder> event) {
        Cluster.Builder bean = event.getBean();
        if (cassandraConfigurationProperties.isSslEnabled()) {
            bean.withSSL(getSSLOptions().orElse(RemoteEndpointAwareJdkSSLOptions.builder().build()));
        }
        if (StringUtils.isNotEmpty(cassandraConfigurationProperties.getPassword())
                && StringUtils.isNotEmpty(cassandraConfigurationProperties.getUserName())) {
            bean.withCredentials(cassandraConfigurationProperties.getUserName(), cassandraConfigurationProperties.getPassword());
        }
        return bean
                .withoutJMXReporting()
                .withoutMetrics();
    }

    private Optional<SSLOptions> getSSLOptions() {
        if (cassandraConfigurationProperties.isSslEnabled() &&
                StringUtils.isNotEmpty(cassandraConfigurationProperties.getKeystoreLocation()) &&
                StringUtils.isNotEmpty(cassandraConfigurationProperties.getKeystorePassword()) &&
                StringUtils.isNotEmpty(cassandraConfigurationProperties.getKeyPassword()) &&
                StringUtils.isNotEmpty(cassandraConfigurationProperties.getTruststoreLocation()) &&
                StringUtils.isNotEmpty(cassandraConfigurationProperties.getTruststorePassword())) {
            try {

                KeyStore keyStore = KeyStore.getInstance(JKS);
                keyStore.load(new FileInputStream(cassandraConfigurationProperties.getKeystoreLocation()), cassandraConfigurationProperties.getKeystorePassword().toCharArray());
                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                keyManagerFactory.init(keyStore, cassandraConfigurationProperties.getKeyPassword().toCharArray());

                KeyStore trustStore = KeyStore.getInstance(JKS);
                trustStore.load(new FileInputStream(cassandraConfigurationProperties.getTruststoreLocation()), cassandraConfigurationProperties.getTruststorePassword().toCharArray());
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustManagerFactory.init(trustStore);

                SSLContext sslContext = SSLContext.getInstance(TLS);
                sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());

//                return new RemoteEndpointAwareNettySSLOptions(sslContext);

                return Optional.of(RemoteEndpointAwareJdkSSLOptions.builder()
                        .withSSLContext(sslContext)
                        .build());


            } catch (NoSuchAlgorithmException | KeyStoreException | IOException | CertificateException | UnrecoverableKeyException | KeyManagementException e) {
                log.error("Unable to initialize SSL for Cassandra connection.", e);
                throw e;
            }
        } else {
            log.warn("Cassandra SSL not enabled or keystore/truststore properties are missing.");
        }

        return Optional.empty();
    }
}
@ilopmar ilopmar added the type: enhancement New feature or request label Jul 13, 2020
@pollend
Copy link
Contributor

pollend commented Aug 7, 2020

the config properties should be covered. It probably would help to have this covered in the documentation.

cassandra.*.advanced.ssl-engine-factory.class 
cassandra.*.advanced.ssl-engine-factory.cipher-suites 
cassandra.*.advanced.ssl-engine-factory.hostname-validation 
cassandra.*.advanced.ssl-engine-factory.keystore-path 
cassandra.*.advanced.ssl-engine-factory.keystore-password 
cassandra.*.advanced.ssl-engine-factory.truststore-path 
cassandra.*.advanced.ssl-engine-factory.truststore-password 

@wetted wetted moved this to Todo in 4.2.0 Release Oct 2, 2023
@sdelamo sdelamo removed this from 4.2.0 Release Nov 12, 2023
wetted added a commit that referenced this issue Jan 18, 2024
@wetted wetted moved this to In Progress in 4.3.0 Release Jan 18, 2024
@wetted wetted moved this from In Progress to Ready for Review in 4.3.0 Release Jan 25, 2024
@github-project-automation github-project-automation bot moved this from Ready for Review to Done in 4.3.0 Release Jan 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement New feature or request
Projects
No open projects
Status: Done
4 participants