content: /*
   * 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 boolean checkedExpiry = false;
  
      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.keystoreContext.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";
      }
  }
  