#!/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 common/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 "$@"