All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cassandra.security.FileBasedSslContextFactory Maven / Gradle / Ivy

Go to download

The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.

There is a newer version: 5.0.0
Show newest version
/*
 * 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.InputStream;
import java.nio.file.Files;
import java.security.KeyStore;
import java.security.KeyStoreException;
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 org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.cassandra.io.util.File;
import org.apache.cassandra.utils.Clock;

/**
 * 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! */ public abstract class FileBasedSslContextFactory extends AbstractSslContextFactory { private static final Logger logger = LoggerFactory.getLogger(FileBasedSslContextFactory.class); protected FileBasedStoreContext keystoreContext; protected FileBasedStoreContext outboundKeystoreContext; protected FileBasedStoreContext trustStoreContext; /** * List of files that trigger hot reloading of SSL certificates */ protected volatile List hotReloadableFiles = new ArrayList<>(); public FileBasedSslContextFactory() { keystoreContext = new FileBasedStoreContext("conf/.keystore", "cassandra"); outboundKeystoreContext = new FileBasedStoreContext("conf/.keystore", "cassandra"); trustStoreContext = new FileBasedStoreContext("conf/.truststore", "cassandra"); } public FileBasedSslContextFactory(Map parameters) { super(parameters); keystoreContext = new FileBasedStoreContext(getString("keystore"), getString("keystore_password")); outboundKeystoreContext = new FileBasedStoreContext(StringUtils.defaultString(getString("outbound_keystore"), keystoreContext.filePath), StringUtils.defaultString(getString("outbound_keystore_password"), keystoreContext.password)); trustStoreContext = new FileBasedStoreContext(getString("truststore"), getString("truststore_password")); } @Override public boolean shouldReload() { return hotReloadableFiles.stream().anyMatch(HotReloadableFile::shouldReload); } @Override public boolean hasKeystore() { return keystoreContext.hasKeystore(); } @Override public boolean hasOutboundKeystore() { return outboundKeystoreContext.hasKeystore(); } private boolean hasTruststore() { return trustStoreContext.filePath != null && new File(trustStoreContext.filePath).exists(); } @Override public synchronized void initHotReloading() { boolean hasKeystore = hasKeystore(); boolean hasOutboundKeystore = hasOutboundKeystore(); boolean hasTruststore = hasTruststore(); if (hasKeystore || hasOutboundKeystore || hasTruststore) { List fileList = new ArrayList<>(); if (hasKeystore) { fileList.add(new HotReloadableFile(keystoreContext.filePath)); } if (hasOutboundKeystore) { fileList.add(new HotReloadableFile(outboundKeystoreContext.filePath)); } if (hasTruststore) { fileList.add(new HotReloadableFile(trustStoreContext.filePath)); } hotReloadableFiles = fileList; } } /** * Validates the given keystore password. * * @param isOutboundKeystore {@code true} for the {@code outbound_keystore_password};{@code false} otherwise * @param password value * @throws IllegalArgumentException if the {@code password} is null */ protected void validatePassword(boolean isOutboundKeystore, String password) { if (password == null) { String keyName = isOutboundKeystore ? "outbound_" : ""; final String msg = String.format("'%skeystore_password' must be specified", keyName); throw new IllegalArgumentException(msg); } } /** * 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 * @throws IllegalArgumentException if the validation for the {@code keystore_password} fails * @see #validatePassword(boolean, String) */ @Override protected KeyManagerFactory buildKeyManagerFactory() throws SSLException { /* * Validation of the password is delayed until this point to allow nullable keystore passwords * for other use-cases (CASSANDRA-18124). */ validatePassword(false, keystoreContext.password); return getKeyManagerFactory(keystoreContext); } @Override protected KeyManagerFactory buildOutboundKeyManagerFactory() throws SSLException { /* * Validation of the password is delayed until this point to allow nullable keystore passwords * for other use-cases (CASSANDRA-18124). */ validatePassword(true, outboundKeystoreContext.password); return getKeyManagerFactory(outboundKeystoreContext); } /** * Builds TrustManagerFactory from the file based truststore. * * @return TrustManagerFactory from the file based truststore * @throws SSLException if any issues encountered during the build process */ @Override protected TrustManagerFactory buildTrustManagerFactory() throws SSLException { try (InputStream tsf = Files.newInputStream(File.getPath(trustStoreContext.filePath))) { final String algorithm = this.algorithm == null ? TrustManagerFactory.getDefaultAlgorithm() : this.algorithm; TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); KeyStore ts = KeyStore.getInstance(store_type); final char[] truststorePassword = StringUtils.isEmpty(trustStoreContext.password) ? null : trustStoreContext.password.toCharArray(); ts.load(tsf, truststorePassword); tmf.init(ts); return tmf; } catch (Exception e) { throw new SSLException("failed to build trust manager store for secure connections", e); } } private KeyManagerFactory getKeyManagerFactory(final FileBasedStoreContext context) throws SSLException { try (InputStream ksf = Files.newInputStream(File.getPath(context.filePath))) { final String algorithm = this.algorithm == null ? KeyManagerFactory.getDefaultAlgorithm() : this.algorithm; KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); KeyStore ks = KeyStore.getInstance(store_type); final char[] password = context.password.toCharArray(); ks.load(ksf, password); if (!context.checkedExpiry) { checkExpiredCerts(ks); context.checkedExpiry = true; } kmf.init(ks, password); return kmf; } catch (Exception e) { throw new SSLException("failed to build key manager store for secure connections", e); } } protected boolean checkExpiredCerts(KeyStore ks) throws KeyStoreException { boolean hasExpiredCerts = false; final Date now = new Date(Clock.Global.currentTimeMillis()); for (Enumeration 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(now)) { hasExpiredCerts = true; logger.warn("Certificate for {} expired on {}", alias, expires); } } } return hasExpiredCerts; } /** * Helper class for hot reloading SSL Contexts */ protected 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 + '}'; } } protected static class FileBasedStoreContext { public volatile boolean checkedExpiry = false; public String filePath; public String password; public FileBasedStoreContext(String keystore, String keystorePassword) { this.filePath = keystore; this.password = keystorePassword; } protected boolean hasKeystore() { return filePath != null && new File(filePath).exists(); } protected boolean passwordMatchesIfPresent(String keyPassword) { return StringUtils.isEmpty(password) || keyPassword.equals(password); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy