#!/bin/bash
##############################################################################
# Copyright (c) 2017 Nokia and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
# A script to create virtual hosts in Apache2 to proxy communication
# to the web dashboards/consoles which might be on private networks. In case
# of frequent access to these services, this approach is simpler than using
# SSH tunneling each time.
# Additionally, this script creates a customized homepage for the jumphost
# with links to the dashboards and information about the credentials.
#
# Note that this script is meant for test deployments and might pose
# security risks for other uses (the SSL certificates are not validated,
# passwords are displayed in plaintext etc).
#
# Usage: ./setupproxy.sh [-v] openstack
# ./setupproxy.sh [-v] kubernetes
# ./setupproxy.sh --help
# Options:
# -v verbose (xtrace)
#
# Author: Martin Kulhavy
##############################################################################
# Imports
source tools.sh
# Halt on error
set -e
# CONFIGURATION
## JOID
JOID_CONFIG_DIR=../../joid_config
## Apache config directories
A2_DIR=/etc/apache2
A2_SSL_DIR=$A2_DIR/ssl/joid
A2_SITES_ENABLED_DIR=$A2_DIR/sites-enabled
## Juju
JUJU_LOCAL_PORT=17070
## OpenStack
OS_LOCAL_PORT=17080
OS_LOCAL_PORT_SSL=17443
# Kubernetes
KUBE_LOCAL_PORT=17080
# end of CONFIGURATION
# Other global vars
VERBOSE=false
MAAS_WUI_PATH='/MAAS'
MAAS_CREDENTIALS=('ubuntu' 'ubuntu')
SETUP_JUJU=true
SETUP_OPENSTACK=false
SETUP_KUBERNETES=false
JUJU_GUI_PATH='/gui'
JUJU_GUI_CREDENTIALS=()
OS_DB_CREDENTIALS=()
KUBE_DB_PATH='/ui'
KUBE_DB_CREDENTIALS=()
EXTERNAL_HOST=jumphost
# Print out usage information and exit.
# $1 - exit code [optional, default 0]
usage() {
# no xtrace output
{ set +x; } 2> /dev/null
echo "Usage: $0 [-v] openstack"
echo " $0 [-v] kubernetes"
echo " $0 --help"
echo "Options:"
echo " -v verbose (xtrace)"
echo ""
echo "Sets up Apache proxy to the Juju and OpenStack or Kubernetes "
echo "dashboards, so that they are accessible through the jumphost, "
echo "even when on private networks."
exit ${1-0}
}
# Parse the arguments of the script
# $@ - script arguments
parse_args() {
# Print usage help message if requested
if [ "$1" = "help" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
fi
# Parse args
if [ "-v" = "$1" ]; then
VERBOSE=true
shift
set -x
fi
if [ "openstack" = "$1" ]; then
SETUP_OPENSTACK=true
elif [ "kubernetes" = "$1" ]; then
SETUP_KUBERNETES=true
else
usage 1
fi
}
# Get a value from a script exporting variables, i.e. consisting of lines
# in format `export VAR=value`.
# $1 - filename
# $2 - variable name
get_export_var_value() {
value=$(cat $1 | grep -Px "export $2=.+" | cut -d '=' -f 2)
echo "$value"
}
# Attempt to find the external IP address.
# Takes the source address for traffic on default route.
get_external_ip() {
# Look for the source IP when trying to request outside address
ext_ip=$(ip route get 8.8.8.8 | awk '/src/{print $7}')
if [ -n "ext_ip" ]; then
EXTERNAL_HOST=$ext_ip
fi
}
# Enable Apache mods needed for the proxy.
enable_mods() {
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod rewrite
sudo a2enmod deflate
sudo a2enmod headers
sudo a2enmod ssl
}
# Generate SSL keys and certificate to allow serving content over https.
generate_ssl_keys_cert() {
if [ ! -e $A2_SSL_DIR ]; then
sudo mkdir -p $A2_SSL_DIR
fi
sudo openssl genrsa -out $A2_SSL_DIR/ca.key 2048
sudo openssl req -nodes -new \
-subj "/C=OS/ST=None/L=None/O=OS/CN=localhost" \
-key $A2_SSL_DIR/ca.key -out $A2_SSL_DIR/ca.csr
sudo openssl x509 -req -days 365 \
-in $A2_SSL_DIR/ca.csr -signkey $A2_SSL_DIR/ca.key \
-out $A2_SSL_DIR/ca.crt
}
# Remove the Apache configuration file for the default virtual host.
remove_default_site() {
def_site_conf=$A2_SITES_ENABLED_DIR/000-default.conf
if [ -e $def_site_conf ]; then
sudo rm $def_site_conf
fi
}
# Add a port for Apache to listen on. Only added if not yet present
# $1 - port number
add_listening_port() {
if [ -z "$1" ]; then
echo_error "No port to add specified"
exit 1
fi
# Add the port only if not already added
if [ $(cat $A2_DIR/ports.conf | grep -Fx "Listen $1" | wc -l) -eq 0 ]; then
echo "Listen $1" | sudo tee -a $A2_DIR/ports.conf
fi
}
# Setup a proxy for requests to the Juju GUI.
setup_juju_gui_proxy() {
# Get Juju GUI info
juju_gui_info=$(juju gui 2>&1)
juju_gui_url=$(echo "$juju_gui_info" | grep -Po 'https://[^\s]+')
juju_socket=$(echo "$juju_gui_url" | grep -Po 'https://\K[^/]+')
JUJU_GUI_PATH=$(echo "$juju_gui_url" | grep -Po 'https://[^/]+\K/.+')
juju_gui_username=$(echo "$juju_gui_info" | grep -Po 'username: .+' \
| cut -d ' ' -f 2)
juju_gui_password=$(echo "$juju_gui_info" | grep -Po 'password: .+' \
| cut -d ' ' -f 2)
JUJU_GUI_CREDENTIALS=("$juju_gui_username" "$juju_gui_password")
# Virtual host settings
sudo tee "${A2_DIR}/sites-enabled/juju-gui.conf" > /dev/null <<-EOF
ServerName localhost
ServerAlias *
SSLEngine On
SSLCertificateFile ${A2_SSL_DIR}/ca.crt
SSLCertificateKeyFile ${A2_SSL_DIR}/ca.key
RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /(.*) wss://${juju_socket}/\$1 [P,L]
SSLProxyEngine on
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off
ProxyPass / https://${juju_socket}/
ProxyPassReverse / https://${juju_socket}/
EOF
# Add the local port to listen on
add_listening_port ${JUJU_LOCAL_PORT}
}
# Setup a proxy for requests to the OpenStack dashboard.
setup_openstack_dashboard_proxy() {
# Get OpenStack dashboard info
os_ip=$(juju status | awk '/openstack-dashboard\/0/ {print $5}')
if [ -z "$os_ip" ]; then
echo_error "Unable to find unit openstack-dashboard/0. Is this an OpenStack deployment?"
exit 1
fi
# Virtual host settings
sudo tee "${A2_DIR}/sites-enabled/openstack-dashboard.conf" > /dev/null \
<<-EOF
ServerName localhost
ServerAlias *
ProxyPass / http://${os_ip}/
ProxyPassReverse / http://${os_ip}/
ServerName localhost
ServerAlias *
SSLEngine On
SSLCertificateFile ${A2_SSL_DIR}/ca.crt
SSLCertificateKeyFile ${A2_SSL_DIR}/ca.key
SSLProxyEngine on
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProxyCheckPeerExpire off
ProxyPass / https://${os_ip}/
ProxyPassReverse / https://${os_ip}/
EOF
# Add the local ports to listen on
add_listening_port ${OS_LOCAL_PORT}
add_listening_port ${OS_LOCAL_PORT_SSL}
# Collect login credentials
openrc=${JOID_CONFIG_DIR}/admin-openrc
OS_DB_CREDENTIALS[0]=$(get_export_var_value $openrc 'OS_USERNAME')
OS_DB_CREDENTIALS[1]=$(get_export_var_value $openrc 'OS_PASSWORD')
OS_DB_CREDENTIALS[2]=$(get_export_var_value $openrc 'OS_USER_DOMAIN_NAME')
}
# Attempt to start the Kubernetes Web UI (Dashboard)
start_kubernetes_dashboard() {
# See docs: https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/
machine_num=$(juju status | awk '/kubernetes-master\/0/ {print $4}')
if [ -z "$machine_num" ]; then
echo_error "Unable to find unit kubernetes-master/0. Is this a Kubernetes deployment?"
exit 1
fi
echo "Attempting to start Kubernetes Web UI proxy. A timeout error can be expected here."
juju run --machine="$machine_num" --timeout=5s "kubectl proxy --address='' --accept-hosts='' &" || true
}
# Setup a proxy for requests to the Kubernetes dashboard.
setup_kubernetes_dashboard_proxy() {
# Get Kubernetes master ip (where dashboard is running)
kube_ip=$(juju status | awk '/kubernetes-master\/0/ {print $5}')
# Note: Maybe the port discovery can be automated. Port 8001 is default.
kube_socket="$kube_ip:8001"
# Virtual host settings
sudo tee "${A2_DIR}/sites-enabled/kubernetes-dashboard.conf" > /dev/null \
<<-EOF
ServerName localhost
ServerAlias *
ProxyPass / http://${kube_socket}/
ProxyPassReverse / http://${kube_socket}/
EOF
# Add the local port to listen on
add_listening_port ${KUBE_LOCAL_PORT}
}
print_info_message() {
# no xtrace output
{ set +x; } 2> /dev/null
echo ''
echo_info -n "JOID deployment overview page";
echo " is now accessible on the following url (jumphost):"
echo -n " Address: "; echo_info "http://${EXTERNAL_HOST}/"
echo ''
if [ "$SETUP_JUJU" = true ]; then
echo_info -n "Juju GUI";
echo " is now accessible with the following url and credentials:"
echo -n " Address: "; echo_info "https://${EXTERNAL_HOST}:${JUJU_LOCAL_PORT}${JUJU_GUI_PATH}"
echo -n " Username: "; echo_info "${JUJU_GUI_CREDENTIALS[0]}"
echo -n " Password: "; echo_info "${JUJU_GUI_CREDENTIALS[1]}"
echo ''
fi
if [ "$SETUP_OPENSTACK" = true ]; then
echo_info -n "OpenStack dashboard"
echo " is now accessible with the following url and credentials:"
echo -n " Address: "; echo_info -n "https://${EXTERNAL_HOST}:${OS_LOCAL_PORT_SSL}/";
echo -n " or "; echo_info "http://${EXTERNAL_HOST}:${OS_LOCAL_PORT}/"
echo -n " Domain: "; echo_info "${OS_DB_CREDENTIALS[2]}"
echo -n " User Name: "; echo_info "${OS_DB_CREDENTIALS[0]}"
echo -n " Password: "; echo_info "${OS_DB_CREDENTIALS[1]}"
echo ''
fi
if [ "$SETUP_KUBERNETES" = true ]; then
echo_info -n "Kubernetes dashboard"
echo " is now accessible with the following url and credentials:"
echo -n " Address: "; echo_info "http://${EXTERNAL_HOST}:${KUBE_LOCAL_PORT}${KUBE_DB_PATH}";
echo " No credentials needed if started on kubernetes-master/0 with command:"
echo " kubectl proxy --address='' --accept-hosts='' &"
echo ''
fi
}
# Create a homepage for the jumphost with links to the dashboards
create_homepage() {
# Note: If this function is about to get any more complicated,
# it might be worth using template rendering instead.
juju_origin="10.21.19.100:17070"
juju_url="https://10.21.19.100:17070/gui/u/admin/default"
os_origin="https://10.21.19.100:17443/"
os_url="10.21.19.100:17443"
kube_origin="https://10.21.19.100:17443/"
kube_url="10.21.19.100:17443"
sudo tee "/var/www/html/index.html" > /dev/null <OPNFV - deployed with JOID
Deployed with JOID
EOF
# MAAS info box
origin="${EXTERNAL_HOST}:80"
url="http://${origin}${MAAS_WUI_PATH}"
user="${MAAS_CREDENTIALS[0]}"
pass="${MAAS_CREDENTIALS[1]}"
sudo tee -a "/var/www/html/index.html" > /dev/null <${origin}
Username:
Password:
EOF
if [ "$SETUP_JUJU" = true ]; then
origin="${EXTERNAL_HOST}:${JUJU_LOCAL_PORT}"
url="https://${origin}${JUJU_GUI_PATH}"
user="${JUJU_GUI_CREDENTIALS[0]}"
pass="${JUJU_GUI_CREDENTIALS[1]}"
sudo tee -a "/var/www/html/index.html" > /dev/null <${origin}
Username:
Password:
EOF
fi
if [ "$SETUP_OPENSTACK" = true ]; then
origin="${EXTERNAL_HOST}:${OS_LOCAL_PORT_SSL}"
url="https://${origin}/"
user="${OS_DB_CREDENTIALS[0]}"
pass="${OS_DB_CREDENTIALS[1]}"
domain="${OS_DB_CREDENTIALS[2]}"
sudo tee -a "/var/www/html/index.html" > /dev/null <${origin}
Domain:
User Name:
Password:
EOF
fi
if [ "$SETUP_KUBERNETES" = true ]; then
origin="${EXTERNAL_HOST}:${KUBE_LOCAL_PORT}"
url="http://${origin}${KUBE_DB_PATH}"
user="${KUBE_DB_CREDENTIALS[0]}"
pass="${KUBE_DB_CREDENTIALS[1]}"
sudo tee -a "/var/www/html/index.html" > /dev/null <${origin}
No credentials needed if started with command
kubectl proxy --address='' --accept-hosts='' &
EOF
fi
sudo tee -a "/var/www/html/index.html" > /dev/null <new Clipboard('button.copy');
EOF
}
main() {
# Do not run script as root (causes later permission issues with Juju)
if [ "$(id -u)" == "0" ]; then
echo_error "Must not be run with sudo or by root"
exit 77
fi
parse_args "$@"
get_external_ip
echo_info "Enabling Apache mods"
enable_mods
echo_info "Generating SSL keys and certificates"
generate_ssl_keys_cert
remove_default_site
if [ "$SETUP_JUJU" = true ]; then
echo_info "Setting up proxy configuration for Juju GUI"
setup_juju_gui_proxy
fi
if [ "$SETUP_OPENSTACK" = true ]; then
echo_info "Setting up proxy configuration for OpenStack dashboard"
setup_openstack_dashboard_proxy
fi
if [ "$SETUP_KUBERNETES" = true ]; then
echo_info "Starting Kubernetes dashboard"
start_kubernetes_dashboard
echo_info "Setting up proxy configuration for Kubernetes dashboard"
setup_kubernetes_dashboard_proxy
fi
echo_info "Creating the homepage for jumphost"
create_homepage
echo_info "Restarting HTTP server"
sudo service apache2 restart
# Print info message
echo_info "Setup finished."
print_info_message
}
# Start the script with the main() function
main "$@"