Add a script to create proxies to dashboards 81/39681/3
authorMartin Kulhavy <martin.kulhavy@nokia.com>
Sun, 20 Aug 2017 21:30:40 +0000 (00:30 +0300)
committerMartin Kulhavy <martin.kulhavy@nokia.com>
Sun, 20 Aug 2017 21:57:22 +0000 (00:57 +0300)
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>
ci/setupproxy.sh [new file with mode: 0644]

diff --git a/ci/setupproxy.sh b/ci/setupproxy.sh
new file mode 100644 (file)
index 0000000..61eabcd
--- /dev/null
@@ -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 "$@"