Merge "Allow deploying keystone with SSL certificates"
diff --git a/lib/cinder b/lib/cinder
index 96d2505..9288685 100644
--- a/lib/cinder
+++ b/lib/cinder
@@ -209,6 +209,7 @@
     inicomment $CINDER_API_PASTE_INI filter:authtoken auth_host
     inicomment $CINDER_API_PASTE_INI filter:authtoken auth_port
     inicomment $CINDER_API_PASTE_INI filter:authtoken auth_protocol
+    inicomment $CINDER_API_PASTE_INI filter:authtoken cafile
     inicomment $CINDER_API_PASTE_INI filter:authtoken admin_tenant_name
     inicomment $CINDER_API_PASTE_INI filter:authtoken admin_user
     inicomment $CINDER_API_PASTE_INI filter:authtoken admin_password
@@ -219,6 +220,7 @@
     iniset $CINDER_CONF keystone_authtoken auth_host $KEYSTONE_AUTH_HOST
     iniset $CINDER_CONF keystone_authtoken auth_port $KEYSTONE_AUTH_PORT
     iniset $CINDER_CONF keystone_authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
+    iniset $CINDER_CONF keystone_authtoken cafile $KEYSTONE_SSL_CA
     iniset $CINDER_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
     iniset $CINDER_CONF keystone_authtoken admin_user cinder
     iniset $CINDER_CONF keystone_authtoken admin_password $SERVICE_PASSWORD
diff --git a/lib/glance b/lib/glance
index f40b1a7..2e29a8f 100644
--- a/lib/glance
+++ b/lib/glance
@@ -82,6 +82,7 @@
     iniset $GLANCE_REGISTRY_CONF keystone_authtoken auth_host $KEYSTONE_AUTH_HOST
     iniset $GLANCE_REGISTRY_CONF keystone_authtoken auth_port $KEYSTONE_AUTH_PORT
     iniset $GLANCE_REGISTRY_CONF keystone_authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
+    iniset $GLANCE_REGISTRY_CONF keystone_authtoken cafile $KEYSTONE_SSL_CA
     iniset $GLANCE_REGISTRY_CONF keystone_authtoken auth_uri $KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:$KEYSTONE_SERVICE_PORT/
     iniset $GLANCE_REGISTRY_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
     iniset $GLANCE_REGISTRY_CONF keystone_authtoken admin_user glance
@@ -99,6 +100,7 @@
     iniset $GLANCE_API_CONF keystone_authtoken auth_host $KEYSTONE_AUTH_HOST
     iniset $GLANCE_API_CONF keystone_authtoken auth_port $KEYSTONE_AUTH_PORT
     iniset $GLANCE_API_CONF keystone_authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
+    iniset $GLANCE_API_CONF keystone_authtoken cafile $KEYSTONE_SSL_CA
     iniset $GLANCE_API_CONF keystone_authtoken auth_uri $KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:$KEYSTONE_SERVICE_PORT/
     iniset $GLANCE_API_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
     iniset $GLANCE_API_CONF keystone_authtoken admin_user glance
diff --git a/lib/heat b/lib/heat
index 7a9ef0d..e44a618 100644
--- a/lib/heat
+++ b/lib/heat
@@ -96,6 +96,7 @@
     iniset $HEAT_CONF keystone_authtoken auth_port $KEYSTONE_AUTH_PORT
     iniset $HEAT_CONF keystone_authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
     iniset $HEAT_CONF keystone_authtoken auth_uri $KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:$KEYSTONE_SERVICE_PORT/v2.0
+    iniset $HEAT_CONF keystone_authtoken cafile $KEYSTONE_SSL_CA
     iniset $HEAT_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
     iniset $HEAT_CONF keystone_authtoken admin_user heat
     iniset $HEAT_CONF keystone_authtoken admin_password $SERVICE_PASSWORD
diff --git a/lib/ironic b/lib/ironic
index 9f86e84..099746a 100644
--- a/lib/ironic
+++ b/lib/ironic
@@ -98,6 +98,7 @@
     iniset $IRONIC_CONF_FILE keystone_authtoken auth_host $KEYSTONE_AUTH_HOST
     iniset $IRONIC_CONF_FILE keystone_authtoken auth_port $KEYSTONE_AUTH_PORT
     iniset $IRONIC_CONF_FILE keystone_authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
+    iniset $IRONIC_CONF_FILE keystone_authtoken cafile $KEYSTONE_SSL_CA
     iniset $IRONIC_CONF_FILE keystone_authtoken auth_uri $KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:$KEYSTONE_SERVICE_PORT/
     iniset $IRONIC_CONF_FILE keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
     iniset $IRONIC_CONF_FILE keystone_authtoken admin_user ironic
diff --git a/lib/keystone b/lib/keystone
index c1fa0af..6d0c1cd 100644
--- a/lib/keystone
+++ b/lib/keystone
@@ -4,6 +4,7 @@
 # Dependencies:
 #
 # - ``functions`` file
+# - ``tls`` file
 # - ``DEST``, ``STACK_USER``
 # - ``IDENTITY_API_VERSION``
 # - ``BASE_SQL_CONN``
@@ -79,6 +80,13 @@
 # valid assignment backends as per dir keystone/identity/backends
 KEYSTONE_VALID_ASSIGNMENT_BACKENDS=kvs,ldap,sql
 
+# if we are running with SSL use https protocols
+if is_ssl_enabled_service "key"; then
+    KEYSTONE_AUTH_PROTOCOL="https"
+    KEYSTONE_SERVICE_PROTOCOL="https"
+fi
+
+
 # Functions
 # ---------
 # cleanup_keystone() - Remove residual data files, anything left over from previous
@@ -172,6 +180,15 @@
     iniset $KEYSTONE_CONF DEFAULT public_endpoint "$KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:%(public_port)s/"
     iniset $KEYSTONE_CONF DEFAULT admin_endpoint "$KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:%(admin_port)s/"
 
+    # Register SSL certificates if provided
+    if is_ssl_enabled_service key; then
+        ensure_certificates KEYSTONE
+
+        iniset $KEYSTONE_CONF ssl enable True
+        iniset $KEYSTONE_CONF ssl certfile $KEYSTONE_SSL_CERT
+        iniset $KEYSTONE_CONF ssl keyfile $KEYSTONE_SSL_KEY
+    fi
+
     if is_service_enabled tls-proxy; then
         # Set the service ports for a proxy to take the originals
         iniset $KEYSTONE_CONF DEFAULT public_port $KEYSTONE_SERVICE_PORT_INT
@@ -386,7 +403,7 @@
     fi
 
     echo "Waiting for keystone to start..."
-    if ! timeout $SERVICE_TIMEOUT sh -c "while ! curl --noproxy '*' -s http://$SERVICE_HOST:$service_port/v$IDENTITY_API_VERSION/ >/dev/null; do sleep 1; done"; then
+    if ! timeout $SERVICE_TIMEOUT sh -c "while ! curl --noproxy '*' -s $KEYSTONE_AUTH_PROTOCOL://$SERVICE_HOST:$service_port/v$IDENTITY_API_VERSION/ >/dev/null; do sleep 1; done"; then
         die $LINENO "keystone did not start"
     fi
 
diff --git a/lib/nova b/lib/nova
index 6ab2000..5fd0beb 100644
--- a/lib/nova
+++ b/lib/nova
@@ -225,6 +225,7 @@
         inicomment $NOVA_API_PASTE_INI filter:authtoken auth_host
         inicomment $NOVA_API_PASTE_INI filter:authtoken auth_protocol
         inicomment $NOVA_API_PASTE_INI filter:authtoken admin_tenant_name
+        inicomment $NOVA_API_PASTE_INI filter:authtoken cafile
         inicomment $NOVA_API_PASTE_INI filter:authtoken admin_user
         inicomment $NOVA_API_PASTE_INI filter:authtoken admin_password
     fi
@@ -399,6 +400,7 @@
         iniset $NOVA_CONF keystone_authtoken auth_host $KEYSTONE_AUTH_HOST
         iniset $NOVA_CONF keystone_authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
         iniset $NOVA_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
+        iniset $NOVA_CONF keystone_authtoken cafile $KEYSTONE_SSL_CA
         iniset $NOVA_CONF keystone_authtoken admin_user nova
         iniset $NOVA_CONF keystone_authtoken admin_password $SERVICE_PASSWORD
     fi
diff --git a/lib/swift b/lib/swift
index 40722ab..8a1489b 100644
--- a/lib/swift
+++ b/lib/swift
@@ -316,6 +316,7 @@
     iniset ${SWIFT_CONFIG_PROXY_SERVER} filter:authtoken auth_host $KEYSTONE_AUTH_HOST
     iniset ${SWIFT_CONFIG_PROXY_SERVER} filter:authtoken auth_port $KEYSTONE_AUTH_PORT
     iniset ${SWIFT_CONFIG_PROXY_SERVER} filter:authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
+    iniset ${SWIFT_CONFIG_PROXY_SERVER} filter:authtoken cafile $KEYSTONE_SSL_CA
     iniset ${SWIFT_CONFIG_PROXY_SERVER} filter:authtoken auth_uri $KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:$KEYSTONE_SERVICE_PORT/
     iniset ${SWIFT_CONFIG_PROXY_SERVER} filter:authtoken admin_tenant_name $SERVICE_TENANT_NAME
     iniset ${SWIFT_CONFIG_PROXY_SERVER} filter:authtoken admin_user swift
@@ -339,6 +340,7 @@
 auth_port = ${KEYSTONE_AUTH_PORT}
 auth_host = ${KEYSTONE_AUTH_HOST}
 auth_protocol = ${KEYSTONE_AUTH_PROTOCOL}
+cafile = ${KEYSTONE_SSL_CA}
 auth_token = ${SERVICE_TOKEN}
 admin_token = ${SERVICE_TOKEN}
 
diff --git a/lib/tls b/lib/tls
index a1a7fdd..6134fa1 100644
--- a/lib/tls
+++ b/lib/tls
@@ -22,7 +22,8 @@
 # - make_int_ca
 # - new_cert $INT_CA_DIR int-server "abc"
 # - start_tls_proxy HOST_IP 5000 localhost 5000
-
+# - ensure_certificates
+# - is_ssl_enabled_service
 
 # Defaults
 # --------
@@ -309,6 +310,53 @@
 }
 
 
+# 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() {
+    services=$@
+    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 the program 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 [[ !($cert && $key && $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
+}
+
+
 # Proxy Functions
 # ===============
 
diff --git a/lib/trove b/lib/trove
index c40006b..5ba4de5 100644
--- a/lib/trove
+++ b/lib/trove
@@ -29,7 +29,6 @@
 TROVECLIENT_DIR=$DEST/python-troveclient
 TROVE_CONF_DIR=/etc/trove
 TROVE_LOCAL_CONF_DIR=$TROVE_DIR/etc/trove
-TROVE_AUTH_ENDPOINT=$KEYSTONE_AUTH_PROTOCOL://$KEYSTONE_AUTH_HOST:$KEYSTONE_AUTH_PORT//v$IDENTITY_API_VERSION
 TROVE_AUTH_CACHE_DIR=${TROVE_AUTH_CACHE_DIR:-/var/cache/trove}
 TROVE_BIN_DIR=/usr/local/bin
 
@@ -102,6 +101,7 @@
     iniset $TROVE_API_PASTE_INI filter:tokenauth auth_host $KEYSTONE_AUTH_HOST
     iniset $TROVE_API_PASTE_INI filter:tokenauth auth_port $KEYSTONE_AUTH_PORT
     iniset $TROVE_API_PASTE_INI filter:tokenauth auth_protocol $KEYSTONE_AUTH_PROTOCOL
+    iniset $TROVE_API_PASTE_INI filter:tokenauth cafile $KEYSTONE_SSL_CA
     iniset $TROVE_API_PASTE_INI filter:tokenauth admin_tenant_name $SERVICE_TENANT_NAME
     iniset $TROVE_API_PASTE_INI filter:tokenauth admin_user trove
     iniset $TROVE_API_PASTE_INI filter:tokenauth admin_password $SERVICE_PASSWORD
@@ -123,6 +123,8 @@
 
     # (Re)create trove taskmanager conf file if needed
     if is_service_enabled tr-tmgr; then
+        TROVE_AUTH_ENDPOINT=$KEYSTONE_AUTH_PROTOCOL://$KEYSTONE_AUTH_HOST:$KEYSTONE_AUTH_PORT//v$IDENTITY_API_VERSION
+
         iniset $TROVE_CONF_DIR/trove-taskmanager.conf DEFAULT rabbit_password $RABBIT_PASSWORD
         iniset $TROVE_CONF_DIR/trove-taskmanager.conf DEFAULT sql_connection `database_connection_url trove`
         iniset $TROVE_CONF_DIR/trove-taskmanager.conf DEFAULT taskmanager_manager trove.taskmanager.manager.Manager
diff --git a/openrc b/openrc
index 804bb3f..784b00e 100644
--- a/openrc
+++ b/openrc
@@ -58,6 +58,7 @@
 HOST_IP=${HOST_IP:-127.0.0.1}
 SERVICE_HOST=${SERVICE_HOST:-$HOST_IP}
 SERVICE_PROTOCOL=${SERVICE_PROTOCOL:-http}
+KEYSTONE_AUTH_PROTOCOL=${KEYSTONE_AUTH_PROTOCOL:-$SERVICE_PROTOCOL}
 
 # Some exercises call glance directly.  On a single-node installation, Glance
 # should be listening on HOST_IP.  If its running elsewhere, it can be set here
@@ -71,10 +72,10 @@
 # the user/tenant has access to - including nova, glance, keystone, swift, ...
 # We currently recommend using the 2.0 *identity api*.
 #
-export OS_AUTH_URL=$SERVICE_PROTOCOL://$SERVICE_HOST:5000/v${OS_IDENTITY_API_VERSION}
+export OS_AUTH_URL=$KEYSTONE_AUTH_PROTOCOL://$SERVICE_HOST:5000/v${OS_IDENTITY_API_VERSION}
 
 # Set the pointer to our CA certificate chain.  Harmless if TLS is not used.
-export OS_CACERT=$INT_CA_DIR/ca-chain.pem
+export OS_CACERT=${OS_CACERT:-$INT_CA_DIR/ca-chain.pem}
 
 # Currently novaclient needs you to specify the *compute api* version.  This
 # needs to match the config of your catalog returned by Keystone.
diff --git a/stack.sh b/stack.sh
index 825373e..a2ef679 100755
--- a/stack.sh
+++ b/stack.sh
@@ -290,6 +290,10 @@
 # Service startup timeout
 SERVICE_TIMEOUT=${SERVICE_TIMEOUT:-60}
 
+# Reset the bundle of CA certificates
+SSL_BUNDLE_FILE="$DATA_DIR/ca-bundle.pem"
+rm -f $SSL_BUNDLE_FILE
+
 
 # Configure Projects
 # ==================
@@ -799,6 +803,17 @@
 restart_rpc_backend
 
 
+# Export Certicate Authority Bundle
+# ---------------------------------
+
+# If certificates were used and written to the SSL bundle file then these
+# should be exported so clients can validate their connections.
+
+if [ -f $SSL_BUNDLE_FILE ]; then
+    export OS_CACERT=$SSL_BUNDLE_FILE
+fi
+
+
 # Configure database
 # ------------------
 
@@ -1146,6 +1161,7 @@
     start_trove
 fi
 
+
 # Create account rc files
 # =======================
 
@@ -1154,7 +1170,13 @@
 # which is helpful in image bundle steps.
 
 if is_service_enabled nova && is_service_enabled key; then
-    $TOP_DIR/tools/create_userrc.sh -PA --target-dir $TOP_DIR/accrc
+    USERRC_PARAMS="-PA --target-dir $TOP_DIR/accrc"
+
+    if [ -f $SSL_BUNDLE_FILE ]; then
+        USERRC_PARAMS="$USERRC_PARAMS --os-cacert $SSL_BUNDLE_FILE"
+    fi
+
+    $TOP_DIR/tools/create_userrc.sh $USERRC_PARAMS
 fi
 
 
@@ -1230,7 +1252,7 @@
 CURRENT_RUN_TIME=$(date "+$TIMESTAMP_FORMAT")
 echo "# $CURRENT_RUN_TIME" >$TOP_DIR/.stackenv
 for i in BASE_SQL_CONN ENABLED_SERVICES HOST_IP LOGFILE \
-    SERVICE_HOST SERVICE_PROTOCOL STACK_USER TLS_IP; do
+    SERVICE_HOST SERVICE_PROTOCOL STACK_USER TLS_IP KEYSTONE_AUTH_PROTOCOL OS_CACERT; do
     echo $i=${!i} >>$TOP_DIR/.stackenv
 done
 
diff --git a/tools/create_userrc.sh b/tools/create_userrc.sh
index 8383fe7..5f4c486 100755
--- a/tools/create_userrc.sh
+++ b/tools/create_userrc.sh
@@ -43,6 +43,7 @@
 --os-tenant-name <tenant_name>
 --os-tenant-id <tenant_id>
 --os-auth-url <auth_url>
+--os-cacert <cert file>
 --target-dir <target_directory>
 --skip-tenant <tenant-name>
 --debug
@@ -53,7 +54,7 @@
 EOF
 }
 
-if ! options=$(getopt -o hPAp:u:r:C: -l os-username:,os-password:,os-tenant-name:,os-tenant-id:,os-auth-url:,target-dir:,skip-tenant:,help,debug -- "$@")
+if ! options=$(getopt -o hPAp:u:r:C: -l os-username:,os-password:,os-tenant-name:,os-tenant-id:,os-auth-url:,target-dir:,skip-tenant:,os-cacert:,help,debug -- "$@")
 then
     #parse error
     display_help
@@ -80,6 +81,7 @@
     --os-tenant-id) export OS_TENANT_ID=$2; shift ;;
     --skip-tenant) SKIP_TENANT="$SKIP_TENANT$2,"; shift ;;
     --os-auth-url) export OS_AUTH_URL=$2; shift ;;
+    --os-cacert) export OS_CACERT=$2; shift ;;
     --target-dir) ACCOUNT_DIR=$2; shift ;;
     --debug) set -o xtrace ;;
     -u) MODE=${MODE:-one};  USER_NAME=$2; shift ;;
@@ -201,6 +203,7 @@
 # Openstack Tenant ID = $tenant_id
 export OS_TENANT_NAME="$tenant_name"
 export OS_AUTH_URL="$OS_AUTH_URL"
+export OS_CACERT="$OS_CACERT"
 export EC2_CERT="$ec2_cert"
 export EC2_PRIVATE_KEY="$ec2_private_key"
 export EC2_USER_ID=42 #not checked by nova (can be a 12-digit id)