blob: fb11c91e901babb93e902f09069053c769d491ec [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.security;
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;
/**
* 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>.
* <p>
* 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.
* The most obvious choice might be to just use the time in nano/milli-seconds precision 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 do not 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.
* <p>
* Defaults:
* <pre>
* private key password = cassandra
* keystore updated timestamp path = /etc/my-ssl-store/keystore-last-updatedtime
* truststore updated timestamp path = /etc/my-ssl-store/truststore-last-updatedtime
* </pre>
* <p>
* Customization: In order to customize the K8s 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.KubernetesSecretsPEMSslContextFactory
* parameters:
* PRIVATE_KEY_ENV_VAR: PRIVATE_KEY
* PRIVATE_KEY_PASSWORD_ENV_VAR: PRIVATE_KEY_PASSWORD
* KEYSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/keystore-last-updatedtime
* TRUSTED_CERTIFICATES_ENV_VAR: TRUSTED_CERTIFICATES
* TRUSTSTORE_UPDATED_TIMESTAMP_PATH: /etc/my-ssl-store/truststore-last-updatedtime
* </pre>
* <p>
* 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: PRIVATE_KEY
* valueFrom:
* secretKeyRef:
* name: my-ssl-store
* key: private-key
* - name: PRIVATE_KEY_PASSWORD
* valueFrom:
* secretKeyRef:
* name: my-ssl-store
* key: private-key-password
* - name: TRUSTED_CERTIFICATES
* valueFrom:
* secretKeyRef:
* name: my-ssl-store
* key: trusted-certificates
* volumeMounts:
* - name: my-ssl-store
* mountPath: "/etc/my-ssl-store"
* readOnly: true
* volumes:
* - name: my-ssl-store
* secret:
* secretName: my-ssl-store
* items:
* - key: keystore-last-updatedtime
* path: keystore-last-updatedtime
* - key: truststore-last-updatedtime
* path: truststore-last-updatedtime
* </pre>
*/
public class KubernetesSecretsPEMSslContextFactory extends KubernetesSecretsSslContextFactory
{
public static final String DEFAULT_PRIVATE_KEY = "";
public static final String DEFAULT_PRIVATE_KEY_PASSWORD = "";
public static final String DEFAULT_TRUSTED_CERTIFICATES = "";
@VisibleForTesting
static final String DEFAULT_PRIVATE_KEY_ENV_VAR_NAME = "PRIVATE_KEY";
@VisibleForTesting
static final String DEFAULT_PRIVATE_KEY_PASSWORD_ENV_VAR_NAME = "PRIVATE_KEY_PASSWORD";
@VisibleForTesting
static final String DEFAULT_TRUSTED_CERTIFICATES_ENV_VAR_NAME = "TRUSTED_CERTIFICATES";
private static final Logger logger = LoggerFactory.getLogger(KubernetesSecretsPEMSslContextFactory.class);
private String pemEncodedKey;
private String keyPassword;
private String pemEncodedCertificates;
private PEMBasedSslContextFactory pemBasedSslContextFactory;
public KubernetesSecretsPEMSslContextFactory()
{
pemBasedSslContextFactory = new PEMBasedSslContextFactory();
}
public KubernetesSecretsPEMSslContextFactory(Map<String, Object> parameters)
{
super(parameters);
pemEncodedKey = getValueFromEnv(getString(PEMConfigKey.PRIVATE_KEY_ENV_VAR, DEFAULT_PRIVATE_KEY_ENV_VAR_NAME),
DEFAULT_PRIVATE_KEY);
keyPassword = getValueFromEnv(getString(PEMConfigKey.PRIVATE_KEY_PASSWORD_ENV_VAR,
DEFAULT_PRIVATE_KEY_PASSWORD_ENV_VAR_NAME),
DEFAULT_PRIVATE_KEY_PASSWORD);
pemEncodedCertificates = getValueFromEnv(getString(PEMConfigKey.TRUSTED_CERTIFICATE_ENV_VAR, DEFAULT_TRUSTED_CERTIFICATES_ENV_VAR_NAME),
DEFAULT_TRUSTED_CERTIFICATES);
parameters.put(PEMBasedSslContextFactory.ConfigKey.ENCODED_KEY.getKeyName(), pemEncodedKey);
parameters.put(PEMBasedSslContextFactory.ConfigKey.KEY_PASSWORD.getKeyName(), keyPassword);
parameters.put(PEMBasedSslContextFactory.ConfigKey.ENCODED_CERTIFICATES.getKeyName(), pemEncodedCertificates);
pemBasedSslContextFactory = new PEMBasedSslContextFactory(parameters);
}
@Override
public synchronized void initHotReloading()
{
// No-op
}
@Override
public boolean hasKeystore()
{
return pemBasedSslContextFactory.hasKeystore();
}
@Override
protected KeyManagerFactory buildKeyManagerFactory() throws SSLException
{
KeyManagerFactory kmf = pemBasedSslContextFactory.buildKeyManagerFactory();
checkedExpiry = pemBasedSslContextFactory.checkedExpiry;
return kmf;
}
@Override
protected TrustManagerFactory buildTrustManagerFactory() throws SSLException
{
return pemBasedSslContextFactory.buildTrustManagerFactory();
}
public interface PEMConfigKey
{
String PRIVATE_KEY_ENV_VAR = "PRIVATE_KEY_ENV_VAR";
String PRIVATE_KEY_PASSWORD_ENV_VAR = "PRIVATE_KEY_PASSWORD_ENV_VAR";
String TRUSTED_CERTIFICATE_ENV_VAR = "TRUSTED_CERTIFICATE_ENV_VAR";
}
}