diff options
author | Martin Kulhavy <martin.kulhavy@nokia.com> | 2017-08-21 00:30:40 +0300 |
---|---|---|
committer | Martin Kulhavy <martin.kulhavy@nokia.com> | 2017-08-21 00:57:22 +0300 |
commit | 232992676a3a5bff9d4e3e98c42fd1b5c6d6d578 (patch) | |
tree | e714e3aec06d4e390d2402af76b988fb2b54ebfd | |
parent | 8bdde6b0cd0a47002d56b501fc317220faae959e (diff) |
Add a script to create proxies to dashboards
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.
Change-Id: I2c9e17a95fa480095a4baba6bd990628bc73e053
Signed-off-by: Martin Kulhavy <martin.kulhavy@nokia.com>
-rw-r--r-- | ci/setupproxy.sh | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/ci/setupproxy.sh b/ci/setupproxy.sh new file mode 100644 index 00000000..61eabcd9 --- /dev/null +++ b/ci/setupproxy.sh @@ -0,0 +1,526 @@ +#!/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 + <VirtualHost *:${JUJU_LOCAL_PORT}> + 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}/ + </VirtualHost> +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 + <VirtualHost *:${OS_LOCAL_PORT}> + ServerName localhost + ServerAlias * + ProxyPass / http://${os_ip}/ + ProxyPassReverse / http://${os_ip}/ + </VirtualHost> + <VirtualHost *:${OS_LOCAL_PORT_SSL}> + 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}/ + </VirtualHost> +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 + <VirtualHost *:${KUBE_LOCAL_PORT}> + ServerName localhost + ServerAlias * + ProxyPass / http://${kube_socket}/ + ProxyPassReverse / http://${kube_socket}/ + </VirtualHost> +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 <<EOF + <!doctype html> + <html lang="en"> + <head> + <meta charset="utf-8"> + <title>OPNFV - deployed with JOID</title> + <script src="https://cdn.rawgit.com/zenorocha/clipboard.js/v1.7.1/dist/clipboard.min.js"></script> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.css" /> + <style> + body { text-align: center; padding-top: 15%; line-height: 1.3; + font-size: 14pt; font-family: Helvetica, Arial, sans-serif; + color: #383a35; } + * { box-sizing: border-box; } + #logo { max-width: 600px; margin: auto; } + fieldset { display: inline-block; width: 400px; min-height: 150pt; + text-align: center; border: 2px solid #383a35; + vertical-align: top; } + legend { font-size: 16pt; font-weight: bold; padding: 0 5pt; } + table { width: 100%; } + a { font-weight: bold; text-decoration: none; display: block; + padding: 5px; margin: 10px; } + a:hover, a:active { background-color: #eef; } + th { width: 40%; text-align: right; } + td { width: 60%; text-align: left; } + input { width: 170px; height: 16pt; color: #000; background: #fff; + border: 1px solid #ddd; vertical-align: bottom; } + button.copy { width: 16pt; height: 16pt; vertical-align: bottom; + background: white url('https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/svg/clippy.svg') no-repeat center; } + p { font-size: 12pt; text-align: left; } + pre { font-size: 10pt } + </style> + </head> + <body> + <img src="https://www.opnfv.org/wp-content/uploads/sites/12/2016/11/opnfv_logo_wp.png" + id="logo" alt="OPNFV logo" /> + <h1>Deployed with JOID</h1> +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 <<EOF + <fieldset><legend>MAAS dashboard</legend> + <a href="${url}" target="_blank" title="Open MAAS dashboard">${origin}</a> + <table><tr><th>Username:</th><td><input type="text" id="maas-user" value="${user}" + /><button class="copy" data-clipboard-target="#maas-user"></button></td></tr> + <tr><th>Password:</th><td><input type="text" id="maas-pass" value="${pass}" + /><button class="copy" data-clipboard-target="#maas-pass"></button></td></tr> + </tbody></table> + </fieldset> +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 <<EOF + <fieldset><legend>Juju GUI</legend> + <a href="${url}" target="_blank" title="Open Juju GUI">${origin}</a> + <table><tr><th>Username:</th><td><input type="text" id="juju-user" value="${user}" + /><button class="copy" data-clipboard-target="#juju-user"></button></td></tr> + <tr><th>Password:</th><td><input type="text" id="juju-pass" value="${pass}" + /><button class="copy" data-clipboard-target="#juju-pass"></button></td></tr> + </table> + </fieldset> +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 <<EOF + <fieldset><legend>OpenStack dashboard</legend> + <a href="${url}" target="_blank" title="Open OpenStack dashboard">${origin}</a> + <table><tr><th>Domain:</th><td><input type="text" id="os-domain" value="${domain}" + /><button class="copy" data-clipboard-target="#os-domain"></button></td></tr> + <tr><th>User Name:</th><td><input type="text" id="os-user" value="${user}" + /><button class="copy" data-clipboard-target="#os-user"></button></td></tr> + <tr><th>Password:</th><td><input type="text" id="os-pass" value="${pass}" + /><button class="copy" data-clipboard-target="#os-pass"></button></td></tr> + </table> + </fieldset> +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 <<EOF + <fieldset><legend>Kubernetes dashboard</legend> + <a href="${url}" target="_blank" title="Open Kubernetes dashboard">${origin}</a> + <div> + <p>No credentials needed if started with command</p> + <pre>kubectl proxy --address='' --accept-hosts='' &</pre> + </div> + </fieldset> +EOF + fi + + sudo tee -a "/var/www/html/index.html" > /dev/null <<EOF + <script>new Clipboard('button.copy');</script> + </body> + </html> +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 "$@" |