lib/tls
e263c82e
 #!/bin/bash
 #
c83a7e12
 # lib/tls
 # Functions to control the configuration and operation of the TLS proxy service
 
 # !! source _before_ any services that use ``SERVICE_HOST``
6a5aa7c6
 #
 # Dependencies:
 #
 # - ``functions`` file
 # - ``DEST``, ``DATA_DIR`` must be defined
 # - ``HOST_IP``, ``SERVICE_HOST``
 # - ``KEYSTONE_TOKEN_FORMAT`` must be defined
c83a7e12
 
 # Entry points:
6a5aa7c6
 #
 # - configure_CA
 # - init_CA
18d4778c
 # - cleanup_CA
c83a7e12
 
6a5aa7c6
 # - configure_proxy
 # - start_tls_proxy
c83a7e12
 
bd5dae06
 # - stop_tls_proxy
 # - cleanup_CA
 
2e0f0544
 # - make_root_CA
 # - make_int_CA
 # - make_cert ca-dir cert-name "common-name" ["alt-name" ...]
6a5aa7c6
 # - start_tls_proxy HOST_IP 5000 localhost 5000
bd24a8d0
 # - ensure_certificates
 # - is_ssl_enabled_service
18d4778c
 # - enable_mod_ssl
c83a7e12
 
dc97cb71
 
cc6b4435
 # Defaults
 # --------
 
c83a7e12
 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
 
18d4778c
 DEVSTACK_HOSTNAME=$(hostname -f)
c83a7e12
 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
aee18c74
 function configure_CA {
c83a7e12
     # 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
aee18c74
 function create_CA_base {
c83a7e12
     local ca_dir=$1
 
     if [[ -d $ca_dir ]]; then
         # Bail out it exists
         return 0
     fi
 
b1e3d0f2
     local i
c83a7e12
     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
aee18c74
 function create_CA_config {
c83a7e12
     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
aee18c74
 function create_signing_config {
c83a7e12
     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
 }
 
ca802171
 # Create root and intermediate CAs
c83a7e12
 # init_CA
 function init_CA {
1987fcc8
     fix_system_ca_bundle_path
c83a7e12
     # 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
18d4778c
     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
ca802171
 }
c83a7e12
 
ca802171
 # Create an initial server cert
 # init_cert
 function init_cert {
c83a7e12
     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" ...]
aee18c74
 function make_cert {
c83a7e12
     local ca_dir=$1
     local cert_name=$2
     local common_name=$3
     local alt_names=$4
 
2f69c6b8
     # 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
c83a7e12
 
2f69c6b8
         # 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
c83a7e12
 }
 
 # Make an intermediate CA to sign everything else
 # make_int_CA ca-dir signing-ca-dir
aee18c74
 function make_int_CA {
c83a7e12
     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
 
2f69c6b8
     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
c83a7e12
 }
 
 # Make a root CA to sign other CAs
 # make_root_CA ca-dir
aee18c74
 function make_root_CA {
c83a7e12
     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
 }
 
1987fcc8
 # If a non-system python-requests is installed then it will use the
 # built-in CA certificate store rather than the distro-specific
 # CA certificate store. Detect this and symlink to the correct
 # one. If the value for the CA is not rooted in /etc then we know
 # we need to change it.
 function fix_system_ca_bundle_path {
     if is_service_enabled tls-proxy || [ "$USE_SSL" == "True" ]; then
         local capath=$(python -c $'try:\n from requests import certs\n print certs.where()\nexcept ImportError: pass')
 
         if [[ ! $capath == "" && ! $capath =~ ^/etc/.* && ! -L $capath ]]; then
             if is_fedora; then
                 sudo rm -f $capath
                 sudo ln -s /etc/pki/tls/certs/ca-bundle.crt $capath
             elif is_ubuntu; then
                 sudo rm -f $capath
                 sudo ln -s /etc/ssl/certs/ca-certificates.crt $capath
             else
                 echo "Don't know how to set the CA bundle, expect the install to fail."
             fi
         fi
     fi
 }
 
c83a7e12
 
bd24a8d0
 # 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``
aee18c74
 function is_ssl_enabled_service {
f0bd8dbe
     local services=$@
     local service=""
18d4778c
     if [ "$USE_SSL" == "False" ]; then
         return 1
     fi
bd24a8d0
     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
dc97cb71
 # 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``.
18d4778c
 #
dc97cb71
 # If it does not find these certificates then the DevStack-issued server
18d4778c
 # certificate, key and CA certificate will be associated with the service.
 #
 # If only some of the variables are provided then the function will quit.
aee18c74
 function ensure_certificates {
bd24a8d0
     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}
 
18d4778c
     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
bd24a8d0
         die $LINENO "Missing either the ${cert_var} ${key_var} or ${ca_var}" \
                     "variable to enable SSL for ${service}"
     fi
 
     cat $ca >> $SSL_BUNDLE_FILE
 }
 
18d4778c
 # 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
 }
 
bd24a8d0
 
c83a7e12
 # Proxy Functions
 # ===============
 
 # Starts the TLS proxy for the given IP/ports
 # start_tls_proxy front-host front-port back-host back-port
aee18c74
 function start_tls_proxy {
c83a7e12
     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
 }
584d90ec
 
cc6b4435
 
bd5dae06
 # Cleanup Functions
3324f19f
 # =================
bd5dae06
 
 # 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"
 }
 
6a5aa7c6
 # Tell emacs to use shell-script-mode
 ## Local variables:
 ## mode: shell-script
 ## End: