devstack/lib/tls
Rob Crittenden 18d4778cf7 Configure endpoints to use SSL natively or via proxy
Configure nova, cinder, glance, swift and neutron to use SSL
on the endpoints using either SSL natively or via a TLS proxy
using stud.

To enable SSL via proxy, in local.conf add

ENABLED_SERVICES+=,tls-proxy

This will create a new test root CA, a subordinate CA and an SSL
server cert. It uses the value of hostname -f for the certificate
subject. The CA certicates are also added to the system CA bundle.

To enable SSL natively, in local.conf add:

USE_SSL=True

Native SSL by default will also use the devstack-generate root and
subordinate CA.

You can override this on a per-service basis by setting

<SERVICE>_SSL_CERT=/path/to/cert
<SERVICE>_SSL_KEY=/path/to/key
<SERVICE>_SSL_PATH=/path/to/ca

You should also set SERVICE_HOST to the FQDN of the host. This
value defaults to the host IP address.

Change-Id: I36fe56c063ca921131ad98439bd452cb135916ac
Closes-Bug: 1328226
2014-09-24 18:36:37 -04:00

455 lines
12 KiB
Plaintext

# lib/tls
# Functions to control the configuration and operation of the TLS proxy service
# !! source _before_ any services that use ``SERVICE_HOST``
#
# Dependencies:
#
# - ``functions`` file
# - ``DEST``, ``DATA_DIR`` must be defined
# - ``HOST_IP``, ``SERVICE_HOST``
# - ``KEYSTONE_TOKEN_FORMAT`` must be defined
# Entry points:
#
# - configure_CA
# - init_CA
# - cleanup_CA
# - configure_proxy
# - start_tls_proxy
# - stop_tls_proxy
# - cleanup_CA
# - make_root_CA
# - make_int_CA
# - make_cert ca-dir cert-name "common-name" ["alt-name" ...]
# - start_tls_proxy HOST_IP 5000 localhost 5000
# - ensure_certificates
# - is_ssl_enabled_service
# - enable_mod_ssl
# Defaults
# --------
if is_service_enabled tls-proxy; then
# TODO(dtroyer): revisit this below after the search for HOST_IP has been done
TLS_IP=${TLS_IP:-$SERVICE_IP}
fi
DEVSTACK_HOSTNAME=$(hostname -f)
DEVSTACK_CERT_NAME=devstack-cert
DEVSTACK_CERT=$DATA_DIR/$DEVSTACK_CERT_NAME.pem
# CA configuration
ROOT_CA_DIR=${ROOT_CA_DIR:-$DATA_DIR/CA/root-ca}
INT_CA_DIR=${INT_CA_DIR:-$DATA_DIR/CA/int-ca}
ORG_NAME="OpenStack"
ORG_UNIT_NAME="DevStack"
# Stud configuration
STUD_PROTO="--tls"
STUD_CIPHERS='TLSv1+HIGH:!DES:!aNULL:!eNULL:@STRENGTH'
# CA Functions
# ============
# There may be more than one, get specific
OPENSSL=${OPENSSL:-/usr/bin/openssl}
# Do primary CA configuration
function configure_CA {
# build common config file
# Verify ``TLS_IP`` is good
if [[ -n "$HOST_IP" && "$HOST_IP" != "$TLS_IP" ]]; then
# auto-discover has changed the IP
TLS_IP=$HOST_IP
fi
}
# Creates a new CA directory structure
# create_CA_base ca-dir
function create_CA_base {
local ca_dir=$1
if [[ -d $ca_dir ]]; then
# Bail out it exists
return 0
fi
local i
for i in certs crl newcerts private; do
mkdir -p $ca_dir/$i
done
chmod 710 $ca_dir/private
echo "01" >$ca_dir/serial
cp /dev/null $ca_dir/index.txt
}
# Create a new CA configuration file
# create_CA_config ca-dir common-name
function create_CA_config {
local ca_dir=$1
local common_name=$2
echo "
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = $ca_dir
policy = policy_match
database = \$dir/index.txt
serial = \$dir/serial
certs = \$dir/certs
crl_dir = \$dir/crl
new_certs_dir = \$dir/newcerts
certificate = \$dir/cacert.pem
private_key = \$dir/private/cacert.key
RANDFILE = \$dir/private/.rand
default_md = default
[ req ]
default_bits = 1024
default_md = sha1
prompt = no
distinguished_name = ca_distinguished_name
x509_extensions = ca_extensions
[ ca_distinguished_name ]
organizationName = $ORG_NAME
organizationalUnitName = $ORG_UNIT_NAME Certificate Authority
commonName = $common_name
[ policy_match ]
countryName = optional
stateOrProvinceName = optional
organizationName = match
organizationalUnitName = optional
commonName = supplied
[ ca_extensions ]
basicConstraints = critical,CA:true
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
keyUsage = cRLSign, keyCertSign
" >$ca_dir/ca.conf
}
# Create a new signing configuration file
# create_signing_config ca-dir
function create_signing_config {
local ca_dir=$1
echo "
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = $ca_dir
policy = policy_match
database = \$dir/index.txt
serial = \$dir/serial
certs = \$dir/certs
crl_dir = \$dir/crl
new_certs_dir = \$dir/newcerts
certificate = \$dir/cacert.pem
private_key = \$dir/private/cacert.key
RANDFILE = \$dir/private/.rand
default_md = default
[ req ]
default_bits = 1024
default_md = sha1
prompt = no
distinguished_name = req_distinguished_name
x509_extensions = req_extensions
[ req_distinguished_name ]
organizationName = $ORG_NAME
organizationalUnitName = $ORG_UNIT_NAME Server Farm
[ policy_match ]
countryName = optional
stateOrProvinceName = optional
organizationName = match
organizationalUnitName = optional
commonName = supplied
[ req_extensions ]
basicConstraints = CA:false
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
keyUsage = digitalSignature, keyEncipherment, keyAgreement
extendedKeyUsage = serverAuth, clientAuth
subjectAltName = \$ENV::SUBJECT_ALT_NAME
" >$ca_dir/signing.conf
}
# Create root and intermediate CAs
# init_CA
function init_CA {
# Ensure CAs are built
make_root_CA $ROOT_CA_DIR
make_int_CA $INT_CA_DIR $ROOT_CA_DIR
# Create the CA bundle
cat $ROOT_CA_DIR/cacert.pem $INT_CA_DIR/cacert.pem >>$INT_CA_DIR/ca-chain.pem
cat $INT_CA_DIR/ca-chain.pem >> $SSL_BUNDLE_FILE
if is_fedora; then
sudo cp $INT_CA_DIR/ca-chain.pem /usr/share/pki/ca-trust-source/anchors/devstack-chain.pem
sudo update-ca-trust
elif is_ubuntu; then
sudo cp $INT_CA_DIR/ca-chain.pem /usr/local/share/ca-certificates/devstack-int.crt
sudo cp $ROOT_CA_DIR/cacert.pem /usr/local/share/ca-certificates/devstack-root.crt
sudo update-ca-certificates
fi
}
# Clean up the CA files
# cleanup_CA
function cleanup_CA {
if is_fedora; then
sudo rm -f /usr/share/pki/ca-trust-source/anchors/devstack-chain.pem
sudo update-ca-trust
elif is_ubuntu; then
sudo rm -f /usr/local/share/ca-certificates/devstack-int.crt
sudo rm -f /usr/local/share/ca-certificates/devstack-root.crt
sudo update-ca-certificates
fi
}
# Create an initial server cert
# init_cert
function init_cert {
if [[ ! -r $DEVSTACK_CERT ]]; then
if [[ -n "$TLS_IP" ]]; then
# Lie to let incomplete match routines work
TLS_IP="DNS:$TLS_IP"
fi
make_cert $INT_CA_DIR $DEVSTACK_CERT_NAME $DEVSTACK_HOSTNAME "$TLS_IP"
# Create a cert bundle
cat $INT_CA_DIR/private/$DEVSTACK_CERT_NAME.key $INT_CA_DIR/$DEVSTACK_CERT_NAME.crt $INT_CA_DIR/cacert.pem >$DEVSTACK_CERT
fi
}
# make_cert creates and signs a new certificate with the given commonName and CA
# make_cert ca-dir cert-name "common-name" ["alt-name" ...]
function make_cert {
local ca_dir=$1
local cert_name=$2
local common_name=$3
local alt_names=$4
# Only generate the certificate if it doesn't exist yet on the disk
if [ ! -r "$ca_dir/$cert_name.crt" ]; then
# Generate a signing request
$OPENSSL req \
-sha1 \
-newkey rsa \
-nodes \
-keyout $ca_dir/private/$cert_name.key \
-out $ca_dir/$cert_name.csr \
-subj "/O=${ORG_NAME}/OU=${ORG_UNIT_NAME} Servers/CN=${common_name}"
if [[ -z "$alt_names" ]]; then
alt_names="DNS:${common_name}"
else
alt_names="DNS:${common_name},${alt_names}"
fi
# Sign the request valid for 1 year
SUBJECT_ALT_NAME="$alt_names" \
$OPENSSL ca -config $ca_dir/signing.conf \
-extensions req_extensions \
-days 365 \
-notext \
-in $ca_dir/$cert_name.csr \
-out $ca_dir/$cert_name.crt \
-subj "/O=${ORG_NAME}/OU=${ORG_UNIT_NAME} Servers/CN=${common_name}" \
-batch
fi
}
# Make an intermediate CA to sign everything else
# make_int_CA ca-dir signing-ca-dir
function make_int_CA {
local ca_dir=$1
local signing_ca_dir=$2
# Create the root CA
create_CA_base $ca_dir
create_CA_config $ca_dir 'Intermediate CA'
create_signing_config $ca_dir
if [ ! -r "$ca_dir/cacert.pem" ]; then
# Create a signing certificate request
$OPENSSL req -config $ca_dir/ca.conf \
-sha1 \
-newkey rsa \
-nodes \
-keyout $ca_dir/private/cacert.key \
-out $ca_dir/cacert.csr \
-outform PEM
# Sign the intermediate request valid for 1 year
$OPENSSL ca -config $signing_ca_dir/ca.conf \
-extensions ca_extensions \
-days 365 \
-notext \
-in $ca_dir/cacert.csr \
-out $ca_dir/cacert.pem \
-batch
fi
}
# Make a root CA to sign other CAs
# make_root_CA ca-dir
function make_root_CA {
local ca_dir=$1
# Create the root CA
create_CA_base $ca_dir
create_CA_config $ca_dir 'Root CA'
# Create a self-signed certificate valid for 5 years
$OPENSSL req -config $ca_dir/ca.conf \
-x509 \
-nodes \
-newkey rsa \
-days 21360 \
-keyout $ca_dir/private/cacert.key \
-out $ca_dir/cacert.pem \
-outform PEM
}
# Certificate Input Configuration
# ===============================
# check to see if the service(s) specified are to be SSL enabled.
#
# Multiple services specified as arguments are ``OR``'ed together; the test
# is a short-circuit boolean, i.e it returns on the first match.
#
# Uses global ``SSL_ENABLED_SERVICES``
function is_ssl_enabled_service {
local services=$@
local service=""
if [ "$USE_SSL" == "False" ]; then
return 1
fi
for service in ${services}; do
[[ ,${SSL_ENABLED_SERVICES}, =~ ,${service}, ]] && return 0
done
return 1
}
# Ensure that the certificates for a service are in place. This function does
# not check that a service is SSL enabled, this should already have been
# completed.
#
# The function expects to find a certificate, key and CA certificate in the
# variables {service}_SSL_CERT, {service}_SSL_KEY and {service}_SSL_CA. For
# example for keystone this would be KEYSTONE_SSL_CERT, KEYSTONE_SSL_KEY and
# KEYSTONE_SSL_CA.
#
# If it does not find these certificates then the devstack-issued server
# certificate, key and CA certificate will be associated with the service.
#
# If only some of the variables are provided then the function will quit.
function ensure_certificates {
local service=$1
local cert_var="${service}_SSL_CERT"
local key_var="${service}_SSL_KEY"
local ca_var="${service}_SSL_CA"
local cert=${!cert_var}
local key=${!key_var}
local ca=${!ca_var}
if [[ -z "$cert" && -z "$key" && -z "$ca" ]]; then
local cert="$INT_CA_DIR/$DEVSTACK_CERT_NAME.crt"
local key="$INT_CA_DIR/private/$DEVSTACK_CERT_NAME.key"
local ca="$INT_CA_DIR/ca-chain.pem"
eval ${service}_SSL_CERT=\$cert
eval ${service}_SSL_KEY=\$key
eval ${service}_SSL_CA=\$ca
return # the CA certificate is already in the bundle
elif [[ -z "$cert" || -z "$key" || -z "$ca" ]]; then
die $LINENO "Missing either the ${cert_var} ${key_var} or ${ca_var}" \
"variable to enable SSL for ${service}"
fi
cat $ca >> $SSL_BUNDLE_FILE
}
# Enable the mod_ssl plugin in Apache
function enable_mod_ssl {
echo "Enabling mod_ssl"
if is_ubuntu; then
sudo a2enmod ssl
elif is_fedora; then
# Fedora enables mod_ssl by default
:
fi
if ! sudo `which httpd || which apache2ctl` -M | grep -w -q ssl_module; then
die $LINENO "mod_ssl is not enabled in apache2/httpd, please check for it manually and run stack.sh again"
fi
}
# Proxy Functions
# ===============
# Starts the TLS proxy for the given IP/ports
# start_tls_proxy front-host front-port back-host back-port
function start_tls_proxy {
local f_host=$1
local f_port=$2
local b_host=$3
local b_port=$4
stud $STUD_PROTO -f $f_host,$f_port -b $b_host,$b_port $DEVSTACK_CERT 2>/dev/null
}
# Cleanup Functions
# ===============
# Stops all stud processes. This should be done only after all services
# using tls configuration are down.
function stop_tls_proxy {
killall stud
}
# Remove CA along with configuration, as well as the local server certificate
function cleanup_CA {
rm -rf "$DATA_DIR/CA" "$DEVSTACK_CERT"
}
# Tell emacs to use shell-script-mode
## Local variables:
## mode: shell-script
## End: