blob: 2fd72b90d9ba792a8470c96cb89da94fcfc17df6 [file] [log] [blame]
/*
* 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.clients;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.apache.cassandra.spark.utils.MapUtils;
import org.jetbrains.annotations.Nullable;
/**
* Encapsulates SSL configuration for Sidecar
*/
public class SslConfig implements Serializable
{
private static final long serialVersionUID = -3844712192096436932L;
private static final Logger LOGGER = LoggerFactory.getLogger(SslConfig.class);
public static final String KEYSTORE_PATH = "KEYSTORE_PATH";
public static final String KEYSTORE_BASE64_ENCODED = "KEYSTORE_BASE64_ENCODED";
public static final String KEYSTORE_PASSWORD = "KEYSTORE_PASSWORD";
public static final String KEYSTORE_TYPE = "KEYSTORE_TYPE";
public static final String DEFAULT_KEYSTORE_TYPE = "PKCS12";
public static final String TRUSTSTORE_PATH = "TRUSTSTORE_PATH";
public static final String TRUSTSTORE_BASE64_ENCODED = "TRUSTSTORE_BASE64_ENCODED";
public static final String TRUSTSTORE_PASSWORD = "TRUSTSTORE_PASSWORD";
public static final String TRUSTSTORE_TYPE = "TRUSTSTORE_TYPE";
public static final String DEFAULT_TRUSTSTORE_TYPE = "JKS";
protected String keyStorePath;
protected String base64EncodedKeyStore;
protected String keyStorePassword;
protected String keyStoreType;
protected String trustStorePath;
protected String base64EncodedTrustStore;
protected String trustStorePassword;
protected String trustStoreType;
protected SslConfig(Builder<?> builder)
{
keyStorePath = builder.keyStorePath;
base64EncodedKeyStore = builder.base64EncodedKeyStore;
keyStorePassword = builder.keyStorePassword;
keyStoreType = builder.keyStoreType;
trustStorePath = builder.trustStorePath;
base64EncodedTrustStore = builder.base64EncodedTrustStore;
trustStorePassword = builder.trustStorePassword;
trustStoreType = builder.trustStoreType;
}
/**
* @return an input stream to the keystore source
* @throws IOException when an IO exception occurs
*/
public InputStream keyStoreInputStream() throws IOException
{
if (keyStorePath != null)
{
return Files.newInputStream(Paths.get(keyStorePath));
}
else if (base64EncodedKeyStore != null)
{
return new ByteArrayInputStream(Base64.getDecoder().decode(base64EncodedKeyStore));
}
else
{
// It should never reach here
throw new RuntimeException("keyStorePath or encodedKeyStore must be provided");
}
}
/**
* @return the path to the key store file
*/
public String keyStorePath()
{
return keyStorePath;
}
/**
* @return the encoded key store
*/
public String base64EncodedKeyStore()
{
return base64EncodedKeyStore;
}
/**
* @return the key store password
*/
public String keyStorePassword()
{
return keyStorePassword;
}
/**
* @return the key store type
*/
public String keyStoreType()
{
return keyStoreType != null ? keyStoreType : DEFAULT_KEYSTORE_TYPE;
}
/**
* @return an input stream to the truststore source
* @throws IOException when an IO exception occurs
*/
public InputStream trustStoreInputStream() throws IOException
{
if (trustStorePath != null)
{
return Files.newInputStream(Paths.get(trustStorePath));
}
else if (base64EncodedTrustStore != null)
{
return new ByteArrayInputStream(Base64.getDecoder().decode(base64EncodedTrustStore));
}
return null;
}
/**
* @return the path to the trust store
*/
public String trustStorePath()
{
return trustStorePath;
}
/**
* @return the encoded trust store
*/
public String base64EncodedTrustStore()
{
return base64EncodedTrustStore;
}
/**
* @return the trust store password
*/
public String trustStorePassword()
{
return trustStorePassword;
}
/**
* @return the trust store type
*/
public String trustStoreType()
{
return trustStoreType != null ? trustStoreType : DEFAULT_TRUSTSTORE_TYPE;
}
// JDK Serialization
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
LOGGER.warn("Falling back to JDK deserialization");
this.keyStorePath = readNullableString(in);
this.base64EncodedKeyStore = readNullableString(in);
this.keyStorePassword = readNullableString(in);
this.keyStoreType = readNullableString(in);
this.trustStorePath = readNullableString(in);
this.base64EncodedTrustStore = readNullableString(in);
this.trustStorePassword = readNullableString(in);
this.trustStoreType = readNullableString(in);
}
private void writeObject(ObjectOutputStream out) throws IOException
{
LOGGER.warn("Falling back to JDK serialization");
writeNullableString(out, keyStorePath);
writeNullableString(out, base64EncodedKeyStore);
writeNullableString(out, keyStorePassword);
writeNullableString(out, keyStoreType);
writeNullableString(out, trustStorePath);
writeNullableString(out, base64EncodedTrustStore);
writeNullableString(out, trustStorePassword);
writeNullableString(out, trustStoreType);
}
private String readNullableString(ObjectInputStream in) throws IOException
{
return in.readBoolean() ? in.readUTF() : null;
}
private void writeNullableString(ObjectOutputStream out, String string) throws IOException
{
if (string != null)
{
out.writeBoolean(true);
out.writeUTF(string);
}
else
{
out.writeBoolean(false);
}
}
// Kryo
public static class Serializer extends com.esotericsoftware.kryo.Serializer<SslConfig>
{
public SslConfig read(Kryo kryo, Input in, Class type)
{
return new Builder<>()
.keyStorePath(in.readString())
.base64EncodedKeyStore(in.readString())
.keyStorePassword(in.readString())
.keyStoreType(in.readString())
.trustStorePath(in.readString())
.base64EncodedTrustStore(in.readString())
.trustStorePassword(in.readString())
.trustStoreType(in.readString())
.build();
}
public void write(Kryo kryo, Output out, SslConfig config)
{
out.writeString(config.keyStorePath);
out.writeString(config.base64EncodedKeyStore);
out.writeString(config.keyStorePassword);
out.writeString(config.keyStoreType);
out.writeString(config.trustStorePath);
out.writeString(config.base64EncodedTrustStore);
out.writeString(config.trustStorePassword);
out.writeString(config.trustStoreType);
}
}
@Nullable
public static SslConfig create(Map<String, String> options)
{
String keyStorePath = MapUtils.getOrDefault(options, KEYSTORE_PATH, null);
String encodedKeyStore = MapUtils.getOrDefault(options, KEYSTORE_BASE64_ENCODED, null);
String keyStorePassword = MapUtils.getOrDefault(options, KEYSTORE_PASSWORD, null);
String keyStoreType = MapUtils.getOrDefault(options, KEYSTORE_TYPE, null);
String trustStorePath = MapUtils.getOrDefault(options, TRUSTSTORE_PATH, null);
String encodedTrustStore = MapUtils.getOrDefault(options, TRUSTSTORE_BASE64_ENCODED, null);
String trustStorePassword = MapUtils.getOrDefault(options, TRUSTSTORE_PASSWORD, null);
String trustStoreType = MapUtils.getOrDefault(options, TRUSTSTORE_TYPE, null);
// If any of the values are provided we try to create a valid SecretsConfig object
if (keyStorePath != null
|| encodedKeyStore != null
|| keyStorePassword != null
|| keyStoreType != null
|| trustStorePath != null
|| encodedTrustStore != null
|| trustStorePassword != null
|| trustStoreType != null)
{
Builder<?> validatedConfig = new Builder<>()
.keyStorePath(keyStorePath)
.base64EncodedKeyStore(encodedKeyStore)
.keyStorePassword(keyStorePassword)
.keyStoreType(keyStoreType)
.trustStorePath(trustStorePath)
.base64EncodedTrustStore(encodedTrustStore)
.trustStorePassword(trustStorePassword)
.trustStoreType(trustStoreType)
.validate();
LOGGER.info("Valid SSL configuration");
return validatedConfig.build();
}
LOGGER.warn("No SSL configured");
return null;
}
/**
* {@code SslConfig} builder static inner class
*
* @param <T> itself
*/
public static class Builder<T extends SslConfig.Builder<T>>
{
protected String keyStorePath;
protected String base64EncodedKeyStore;
protected String keyStorePassword;
protected String keyStoreType;
protected String trustStorePath;
protected String base64EncodedTrustStore;
protected String trustStorePassword;
protected String trustStoreType;
/**
* @return a reference to itself
*/
@SuppressWarnings("unchecked")
protected T self()
{
return (T) this;
}
/**
* Sets the {@code keyStorePath} and returns a reference to this Builder enabling method chaining
*
* @param keyStorePath the {@code keyStorePath} to set
* @return a reference to this Builder
*/
public T keyStorePath(String keyStorePath)
{
this.keyStorePath = keyStorePath;
return self();
}
/**
* Sets the {@code base64EncodedKeyStore} and returns a reference to this Builder enabling method chaining
*
* @param base64EncodedKeyStore the {@code base64EncodedKeyStore} to set
* @return a reference to this Builder
*/
public T base64EncodedKeyStore(String base64EncodedKeyStore)
{
this.base64EncodedKeyStore = base64EncodedKeyStore;
return self();
}
/**
* Sets the {@code keyStorePassword} and returns a reference to this Builder enabling method chaining
*
* @param keyStorePassword the {@code keyStorePassword} to set
* @return a reference to this Builder
*/
public T keyStorePassword(String keyStorePassword)
{
this.keyStorePassword = keyStorePassword;
return self();
}
/**
* Sets the {@code keyStoreType} and returns a reference to this Builder enabling method chaining
*
* @param keyStoreType the {@code keyStoreType} to set
* @return a reference to this Builder
*/
public T keyStoreType(String keyStoreType)
{
this.keyStoreType = keyStoreType;
return self();
}
/**
* Sets the {@code trustStorePath} and returns a reference to this Builder enabling method chaining
*
* @param trustStorePath the {@code trustStorePath} to set
* @return a reference to this Builder
*/
public T trustStorePath(String trustStorePath)
{
this.trustStorePath = trustStorePath;
return self();
}
/**
* Sets the {@code base64EncodedTrustStore} and returns a reference to this Builder enabling method chaining
*
* @param base64EncodedTrustStore the {@code base64EncodedTrustStore} to set
* @return a reference to this Builder
*/
public T base64EncodedTrustStore(String base64EncodedTrustStore)
{
this.base64EncodedTrustStore = base64EncodedTrustStore;
return self();
}
/**
* Sets the {@code trustStorePassword} and returns a reference to this Builder enabling method chaining
*
* @param trustStorePassword the {@code trustStorePassword} to set
* @return a reference to this Builder
*/
public T trustStorePassword(String trustStorePassword)
{
this.trustStorePassword = trustStorePassword;
return self();
}
/**
* Sets the {@code trustStoreType} and returns a reference to this Builder enabling method chaining
*
* @param trustStoreType the {@code trustStoreType} to set
* @return a reference to this Builder
*/
public T trustStoreType(String trustStoreType)
{
this.trustStoreType = trustStoreType;
return self();
}
/**
* Returns a {@code SslConfig} built from the parameters previously set
*
* @return a {@code SslConfig} built with parameters of this {@code SslConfig.Builder}
*/
public SslConfig build()
{
return new SslConfig(this);
}
protected T validate()
{
if (keyStorePath != null && base64EncodedKeyStore != null)
{
throw new IllegalArgumentException(
String.format("Both '%s' and '%s' options were provided. Only one of the options can be provided",
KEYSTORE_PATH, KEYSTORE_BASE64_ENCODED));
}
if (keyStorePassword != null)
{
// When the keystore password is provided, either the key store path or
// the encoded key store must be provided
if (keyStorePath == null && base64EncodedKeyStore == null)
{
throw new IllegalArgumentException(
String.format("One of the '%s' or '%s' options must be provided when the '%s' option is provided",
KEYSTORE_PATH, KEYSTORE_BASE64_ENCODED, KEYSTORE_PASSWORD));
}
}
else
{
throw new IllegalArgumentException(
String.format("The '%s' option must be provided when either the '%s' or '%s' options are provided",
KEYSTORE_PASSWORD, KEYSTORE_PATH, KEYSTORE_BASE64_ENCODED));
}
if (trustStorePassword != null)
{
// Only one of trust store path or encoded trust store must be provided, not both
if (trustStorePath != null && base64EncodedTrustStore != null)
{
throw new IllegalArgumentException(
String.format("Both '%s' and '%s' options were provided. Only one of the options can be provided",
TRUSTSTORE_PATH, TRUSTSTORE_BASE64_ENCODED));
}
// When the trust store password is provided, either the trust store
// path or the encoded trust store must be provided
if (trustStorePath == null && base64EncodedTrustStore == null)
{
throw new IllegalArgumentException(
String.format("One of the '%s' or '%s' options must be provided when the '%s' option is provided",
TRUSTSTORE_PATH, TRUSTSTORE_BASE64_ENCODED, TRUSTSTORE_PASSWORD));
}
}
else if (trustStorePath != null || base64EncodedTrustStore != null)
{
throw new IllegalArgumentException(
String.format("The '%s' option must be provided when either the '%s' or '%s' options are provided",
TRUSTSTORE_PASSWORD, TRUSTSTORE_PATH, TRUSTSTORE_BASE64_ENCODED));
}
return self();
}
}
}