2 ##############################################################################
3 # Copyright (c) 2017 Nokia and others.
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 ##############################################################################
10 # A script to create virtual hosts in Apache2 to proxy communication
11 # to the web dashboards/consoles which might be on private networks. In case
12 # of frequent access to these services, this approach is simpler than using
13 # SSH tunneling each time.
14 # Additionally, this script creates a customized homepage for the jumphost
15 # with links to the dashboards and information about the credentials.
17 # Note that this script is meant for test deployments and might pose
18 # security risks for other uses (the SSL certificates are not validated,
19 # passwords are displayed in plaintext etc).
21 # Usage: ./setupproxy.sh [-v] openstack
22 # ./setupproxy.sh [-v] kubernetes
23 # ./setupproxy.sh --help
27 # Author: Martin Kulhavy
28 ##############################################################################
31 source common/tools.sh
39 JOID_CONFIG_DIR=../../joid_config
41 ## Apache config directories
43 A2_SSL_DIR=$A2_DIR/ssl/joid
44 A2_SITES_ENABLED_DIR=$A2_DIR/sites-enabled
51 OS_LOCAL_PORT_SSL=17443
56 # end of CONFIGURATION
61 MAAS_CREDENTIALS=('ubuntu' 'ubuntu')
64 SETUP_KUBERNETES=false
66 JUJU_GUI_CREDENTIALS=()
69 KUBE_DB_CREDENTIALS=()
70 EXTERNAL_HOST=jumphost
73 # Print out usage information and exit.
74 # $1 - exit code [optional, default 0]
77 { set +x; } 2> /dev/null
79 echo "Usage: $0 [-v] openstack"
80 echo " $0 [-v] kubernetes"
83 echo " -v verbose (xtrace)"
85 echo "Sets up Apache proxy to the Juju and OpenStack or Kubernetes "
86 echo "dashboards, so that they are accessible through the jumphost, "
87 echo "even when on private networks."
91 # Parse the arguments of the script
92 # $@ - script arguments
94 # Print usage help message if requested
95 if [ "$1" = "help" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
100 if [ "-v" = "$1" ]; then
106 if [ "openstack" = "$1" ]; then
108 elif [ "kubernetes" = "$1" ]; then
109 SETUP_KUBERNETES=true
116 # Get a value from a script exporting variables, i.e. consisting of lines
117 # in format `export VAR=value`.
120 get_export_var_value() {
121 value=$(cat $1 | grep -Px "export $2=.+" | cut -d '=' -f 2)
126 # Attempt to find the external IP address.
127 # Takes the source address for traffic on default route.
129 # Look for the source IP when trying to request outside address
130 ext_ip=$(ip route get 8.8.8.8 | awk '/src/{print $7}')
131 if [ -n "ext_ip" ]; then
132 EXTERNAL_HOST=$ext_ip
137 # Enable Apache mods needed for the proxy.
140 sudo a2enmod proxy_http
148 # Generate SSL keys and certificate to allow serving content over https.
149 generate_ssl_keys_cert() {
150 if [ ! -e $A2_SSL_DIR ]; then
151 sudo mkdir -p $A2_SSL_DIR
153 sudo openssl genrsa -out $A2_SSL_DIR/ca.key 2048
154 sudo openssl req -nodes -new \
155 -subj "/C=OS/ST=None/L=None/O=OS/CN=localhost" \
156 -key $A2_SSL_DIR/ca.key -out $A2_SSL_DIR/ca.csr
157 sudo openssl x509 -req -days 365 \
158 -in $A2_SSL_DIR/ca.csr -signkey $A2_SSL_DIR/ca.key \
159 -out $A2_SSL_DIR/ca.crt
163 # Remove the Apache configuration file for the default virtual host.
164 remove_default_site() {
165 def_site_conf=$A2_SITES_ENABLED_DIR/000-default.conf
166 if [ -e $def_site_conf ]; then
167 sudo rm $def_site_conf
172 # Add a port for Apache to listen on. Only added if not yet present
174 add_listening_port() {
176 echo_error "No port to add specified"
180 # Add the port only if not already added
181 if [ $(cat $A2_DIR/ports.conf | grep -Fx "Listen $1" | wc -l) -eq 0 ]; then
182 echo "Listen $1" | sudo tee -a $A2_DIR/ports.conf
187 # Setup a proxy for requests to the Juju GUI.
188 setup_juju_gui_proxy() {
190 juju_gui_info=$(juju gui 2>&1)
191 juju_gui_url=$(echo "$juju_gui_info" | grep -Po 'https://[^\s]+')
192 juju_socket=$(echo "$juju_gui_url" | grep -Po 'https://\K[^/]+')
193 JUJU_GUI_PATH=$(echo "$juju_gui_url" | grep -Po 'https://[^/]+\K/.+')
194 juju_gui_username=$(echo "$juju_gui_info" | grep -Po 'username: .+' \
196 juju_gui_password=$(echo "$juju_gui_info" | grep -Po 'password: .+' \
198 JUJU_GUI_CREDENTIALS=("$juju_gui_username" "$juju_gui_password")
200 # Virtual host settings
201 sudo tee "${A2_DIR}/sites-enabled/juju-gui.conf" > /dev/null <<-EOF
202 <VirtualHost *:${JUJU_LOCAL_PORT}>
206 SSLCertificateFile ${A2_SSL_DIR}/ca.crt
207 SSLCertificateKeyFile ${A2_SSL_DIR}/ca.key
209 RewriteCond %{HTTP:Connection} Upgrade [NC]
210 RewriteCond %{HTTP:Upgrade} websocket [NC]
211 RewriteRule /(.*) wss://${juju_socket}/\$1 [P,L]
214 SSLProxyCheckPeerCN off
215 SSLProxyCheckPeerName off
216 SSLProxyCheckPeerExpire off
217 ProxyPass / https://${juju_socket}/
218 ProxyPassReverse / https://${juju_socket}/
222 # Add the local port to listen on
223 add_listening_port ${JUJU_LOCAL_PORT}
227 # Setup a proxy for requests to the OpenStack dashboard.
228 setup_openstack_dashboard_proxy() {
229 # Get OpenStack dashboard info
230 os_ip=$(juju status | awk '/openstack-dashboard\/0/ {print $5}')
231 if [ -z "$os_ip" ]; then
232 echo_error "Unable to find unit openstack-dashboard/0. Is this an OpenStack deployment?"
236 # Virtual host settings
237 sudo tee "${A2_DIR}/sites-enabled/openstack-dashboard.conf" > /dev/null \
239 <VirtualHost *:${OS_LOCAL_PORT}>
242 ProxyPass / http://${os_ip}/
243 ProxyPassReverse / http://${os_ip}/
245 <VirtualHost *:${OS_LOCAL_PORT_SSL}>
249 SSLCertificateFile ${A2_SSL_DIR}/ca.crt
250 SSLCertificateKeyFile ${A2_SSL_DIR}/ca.key
253 SSLProxyCheckPeerCN off
254 SSLProxyCheckPeerName off
255 SSLProxyCheckPeerExpire off
256 ProxyPass / https://${os_ip}/
257 ProxyPassReverse / https://${os_ip}/
261 # Add the local ports to listen on
262 add_listening_port ${OS_LOCAL_PORT}
263 add_listening_port ${OS_LOCAL_PORT_SSL}
265 # Collect login credentials
266 openrc=${JOID_CONFIG_DIR}/admin-openrc
267 OS_DB_CREDENTIALS[0]=$(get_export_var_value $openrc 'OS_USERNAME')
268 OS_DB_CREDENTIALS[1]=$(get_export_var_value $openrc 'OS_PASSWORD')
269 OS_DB_CREDENTIALS[2]=$(get_export_var_value $openrc 'OS_USER_DOMAIN_NAME')
273 # Attempt to start the Kubernetes Web UI (Dashboard)
274 start_kubernetes_dashboard() {
275 # See docs: https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/
277 machine_num=$(juju status | awk '/kubernetes-master\/0/ {print $4}')
278 if [ -z "$machine_num" ]; then
279 echo_error "Unable to find unit kubernetes-master/0. Is this a Kubernetes deployment?"
283 echo "Attempting to start Kubernetes Web UI proxy. A timeout error can be expected here."
284 juju run --machine="$machine_num" --timeout=5s "kubectl proxy --address='' --accept-hosts='' &" || true
288 # Setup a proxy for requests to the Kubernetes dashboard.
289 setup_kubernetes_dashboard_proxy() {
291 # Get Kubernetes master ip (where dashboard is running)
292 kube_ip=$(juju status | awk '/kubernetes-master\/0/ {print $5}')
293 # Note: Maybe the port discovery can be automated. Port 8001 is default.
294 kube_socket="$kube_ip:8001"
296 # Virtual host settings
297 sudo tee "${A2_DIR}/sites-enabled/kubernetes-dashboard.conf" > /dev/null \
299 <VirtualHost *:${KUBE_LOCAL_PORT}>
302 ProxyPass / http://${kube_socket}/
303 ProxyPassReverse / http://${kube_socket}/
307 # Add the local port to listen on
308 add_listening_port ${KUBE_LOCAL_PORT}
312 print_info_message() {
314 { set +x; } 2> /dev/null
317 echo_info -n "JOID deployment overview page";
318 echo " is now accessible on the following url (jumphost):"
319 echo -n " Address: "; echo_info "http://${EXTERNAL_HOST}/"
322 if [ "$SETUP_JUJU" = true ]; then
323 echo_info -n "Juju GUI";
324 echo " is now accessible with the following url and credentials:"
325 echo -n " Address: "; echo_info "https://${EXTERNAL_HOST}:${JUJU_LOCAL_PORT}${JUJU_GUI_PATH}"
326 echo -n " Username: "; echo_info "${JUJU_GUI_CREDENTIALS[0]}"
327 echo -n " Password: "; echo_info "${JUJU_GUI_CREDENTIALS[1]}"
330 if [ "$SETUP_OPENSTACK" = true ]; then
331 echo_info -n "OpenStack dashboard"
332 echo " is now accessible with the following url and credentials:"
333 echo -n " Address: "; echo_info -n "https://${EXTERNAL_HOST}:${OS_LOCAL_PORT_SSL}/";
334 echo -n " or "; echo_info "http://${EXTERNAL_HOST}:${OS_LOCAL_PORT}/"
335 echo -n " Domain: "; echo_info "${OS_DB_CREDENTIALS[2]}"
336 echo -n " User Name: "; echo_info "${OS_DB_CREDENTIALS[0]}"
337 echo -n " Password: "; echo_info "${OS_DB_CREDENTIALS[1]}"
340 if [ "$SETUP_KUBERNETES" = true ]; then
341 echo_info -n "Kubernetes dashboard"
342 echo " is now accessible with the following url and credentials:"
343 echo -n " Address: "; echo_info "http://${EXTERNAL_HOST}:${KUBE_LOCAL_PORT}${KUBE_DB_PATH}";
344 echo " No credentials needed if started on kubernetes-master/0 with command:"
345 echo " kubectl proxy --address='' --accept-hosts='' &"
352 # Create a homepage for the jumphost with links to the dashboards
354 # Note: If this function is about to get any more complicated,
355 # it might be worth using template rendering instead.
357 juju_origin="10.21.19.100:17070"
358 juju_url="https://10.21.19.100:17070/gui/u/admin/default"
359 os_origin="https://10.21.19.100:17443/"
360 os_url="10.21.19.100:17443"
361 kube_origin="https://10.21.19.100:17443/"
362 kube_url="10.21.19.100:17443"
364 sudo tee "/var/www/html/index.html" > /dev/null <<EOF
368 <meta charset="utf-8">
369 <title>OPNFV - deployed with JOID</title>
370 <script src="https://cdn.rawgit.com/zenorocha/clipboard.js/v1.7.1/dist/clipboard.min.js"></script>
371 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.css" />
373 body { text-align: center; padding-top: 15%; line-height: 1.3;
374 font-size: 14pt; font-family: Helvetica, Arial, sans-serif;
376 * { box-sizing: border-box; }
377 #logo { max-width: 600px; margin: auto; }
378 fieldset { display: inline-block; width: 400px; min-height: 150pt;
379 text-align: center; border: 2px solid #383a35;
380 vertical-align: top; }
381 legend { font-size: 16pt; font-weight: bold; padding: 0 5pt; }
382 table { width: 100%; }
383 a { font-weight: bold; text-decoration: none; display: block;
384 padding: 5px; margin: 10px; }
385 a:hover, a:active { background-color: #eef; }
386 th { width: 40%; text-align: right; }
387 td { width: 60%; text-align: left; }
388 input { width: 170px; height: 16pt; color: #000; background: #fff;
389 border: 1px solid #ddd; vertical-align: bottom; }
390 button.copy { width: 16pt; height: 16pt; vertical-align: bottom;
391 background: white url('https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/svg/clippy.svg') no-repeat center; }
392 p { font-size: 12pt; text-align: left; }
393 pre { font-size: 10pt }
397 <img src="https://www.opnfv.org/wp-content/uploads/sites/12/2016/11/opnfv_logo_wp.png"
398 id="logo" alt="OPNFV logo" />
399 <h1>Deployed with JOID</h1>
403 origin="${EXTERNAL_HOST}:80"
404 url="http://${origin}${MAAS_WUI_PATH}"
405 user="${MAAS_CREDENTIALS[0]}"
406 pass="${MAAS_CREDENTIALS[1]}"
407 sudo tee -a "/var/www/html/index.html" > /dev/null <<EOF
408 <fieldset><legend>MAAS dashboard</legend>
409 <a href="${url}" target="_blank" title="Open MAAS dashboard">${origin}</a>
410 <table><tr><th>Username:</th><td><input type="text" id="maas-user" value="${user}"
411 /><button class="copy" data-clipboard-target="#maas-user"></button></td></tr>
412 <tr><th>Password:</th><td><input type="text" id="maas-pass" value="${pass}"
413 /><button class="copy" data-clipboard-target="#maas-pass"></button></td></tr>
418 if [ "$SETUP_JUJU" = true ]; then
419 origin="${EXTERNAL_HOST}:${JUJU_LOCAL_PORT}"
420 url="https://${origin}${JUJU_GUI_PATH}"
421 user="${JUJU_GUI_CREDENTIALS[0]}"
422 pass="${JUJU_GUI_CREDENTIALS[1]}"
423 sudo tee -a "/var/www/html/index.html" > /dev/null <<EOF
424 <fieldset><legend>Juju GUI</legend>
425 <a href="${url}" target="_blank" title="Open Juju GUI">${origin}</a>
426 <table><tr><th>Username:</th><td><input type="text" id="juju-user" value="${user}"
427 /><button class="copy" data-clipboard-target="#juju-user"></button></td></tr>
428 <tr><th>Password:</th><td><input type="text" id="juju-pass" value="${pass}"
429 /><button class="copy" data-clipboard-target="#juju-pass"></button></td></tr>
435 if [ "$SETUP_OPENSTACK" = true ]; then
436 origin="${EXTERNAL_HOST}:${OS_LOCAL_PORT_SSL}"
437 url="https://${origin}/"
438 user="${OS_DB_CREDENTIALS[0]}"
439 pass="${OS_DB_CREDENTIALS[1]}"
440 domain="${OS_DB_CREDENTIALS[2]}"
441 sudo tee -a "/var/www/html/index.html" > /dev/null <<EOF
442 <fieldset><legend>OpenStack dashboard</legend>
443 <a href="${url}" target="_blank" title="Open OpenStack dashboard">${origin}</a>
444 <table><tr><th>Domain:</th><td><input type="text" id="os-domain" value="${domain}"
445 /><button class="copy" data-clipboard-target="#os-domain"></button></td></tr>
446 <tr><th>User Name:</th><td><input type="text" id="os-user" value="${user}"
447 /><button class="copy" data-clipboard-target="#os-user"></button></td></tr>
448 <tr><th>Password:</th><td><input type="text" id="os-pass" value="${pass}"
449 /><button class="copy" data-clipboard-target="#os-pass"></button></td></tr>
455 if [ "$SETUP_KUBERNETES" = true ]; then
456 origin="${EXTERNAL_HOST}:${KUBE_LOCAL_PORT}"
457 url="http://${origin}${KUBE_DB_PATH}"
458 user="${KUBE_DB_CREDENTIALS[0]}"
459 pass="${KUBE_DB_CREDENTIALS[1]}"
460 sudo tee -a "/var/www/html/index.html" > /dev/null <<EOF
461 <fieldset><legend>Kubernetes dashboard</legend>
462 <a href="${url}" target="_blank" title="Open Kubernetes dashboard">${origin}</a>
464 <p>No credentials needed if started with command</p>
465 <pre>kubectl proxy --address='' --accept-hosts='' &</pre>
471 sudo tee -a "/var/www/html/index.html" > /dev/null <<EOF
472 <script>new Clipboard('button.copy');</script>
480 # Do not run script as root (causes later permission issues with Juju)
481 if [ "$(id -u)" == "0" ]; then
482 echo_error "Must not be run with sudo or by root"
490 echo_info "Enabling Apache mods"
493 echo_info "Generating SSL keys and certificates"
494 generate_ssl_keys_cert
498 if [ "$SETUP_JUJU" = true ]; then
499 echo_info "Setting up proxy configuration for Juju GUI"
502 if [ "$SETUP_OPENSTACK" = true ]; then
503 echo_info "Setting up proxy configuration for OpenStack dashboard"
504 setup_openstack_dashboard_proxy
506 if [ "$SETUP_KUBERNETES" = true ]; then
507 echo_info "Starting Kubernetes dashboard"
508 start_kubernetes_dashboard
509 echo_info "Setting up proxy configuration for Kubernetes dashboard"
510 setup_kubernetes_dashboard_proxy
513 echo_info "Creating the homepage for jumphost"
517 echo_info "Restarting HTTP server"
518 sudo service apache2 restart
521 echo_info "Setup finished."
525 # Start the script with the main() function