Merge "Auto Generated INFO.yaml file"
[joid.git] / ci / setupproxy.sh
1 #!/bin/bash
2 ##############################################################################
3 # Copyright (c) 2017 Nokia and others.
4 #
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.
16 #
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).
20 #
21 # Usage: ./setupproxy.sh [-v] openstack
22 #        ./setupproxy.sh [-v] kubernetes
23 #        ./setupproxy.sh --help
24 # Options:
25 #   -v   verbose (xtrace)
26 #
27 # Author: Martin Kulhavy
28 ##############################################################################
29
30 # Imports
31 source common/tools.sh
32
33 # Halt on error
34 set -e
35
36 # CONFIGURATION
37
38 ## JOID
39 JOID_CONFIG_DIR=../../joid_config
40
41 ## Apache config directories
42 A2_DIR=/etc/apache2
43 A2_SSL_DIR=$A2_DIR/ssl/joid
44 A2_SITES_ENABLED_DIR=$A2_DIR/sites-enabled
45
46 ## Juju
47 JUJU_LOCAL_PORT=17070
48
49 ## OpenStack
50 OS_LOCAL_PORT=17080
51 OS_LOCAL_PORT_SSL=17443
52
53 # Kubernetes
54 KUBE_LOCAL_PORT=17080
55
56 # end of CONFIGURATION
57
58 # Other global vars
59 VERBOSE=false
60 MAAS_WUI_PATH='/MAAS'
61 MAAS_CREDENTIALS=('ubuntu' 'ubuntu')
62 SETUP_JUJU=true
63 SETUP_OPENSTACK=false
64 SETUP_KUBERNETES=false
65 JUJU_GUI_PATH='/gui'
66 JUJU_GUI_CREDENTIALS=()
67 OS_DB_CREDENTIALS=()
68 KUBE_DB_PATH='/ui'
69 KUBE_DB_CREDENTIALS=()
70 EXTERNAL_HOST=jumphost
71
72
73 # Print out usage information and exit.
74 # $1 - exit code [optional, default 0]
75 usage() {
76     # no xtrace output
77     { set +x; } 2> /dev/null
78
79     echo "Usage: $0 [-v] openstack"
80     echo "       $0 [-v] kubernetes"
81     echo "       $0 --help"
82     echo "Options:"
83     echo "  -v   verbose (xtrace)"
84     echo ""
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."
88     exit ${1-0}
89 }
90
91 # Parse the arguments of the script
92 # $@ - script arguments
93 parse_args() {
94     # Print usage help message if requested
95     if [ "$1" = "help" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
96         usage
97     fi
98
99     # Parse args
100     if [ "-v" = "$1" ]; then
101         VERBOSE=true
102         shift
103         set -x
104     fi
105
106     if [ "openstack" = "$1" ]; then
107         SETUP_OPENSTACK=true
108     elif [ "kubernetes" = "$1" ]; then
109         SETUP_KUBERNETES=true
110     else
111         usage 1
112     fi
113 }
114
115
116 # Get a value from a script exporting variables, i.e. consisting of lines
117 # in format `export VAR=value`.
118 # $1 - filename
119 # $2 - variable name
120 get_export_var_value() {
121     value=$(cat $1 | grep -Px "export $2=.+" | cut -d '=' -f 2)
122     echo "$value"
123 }
124
125
126 # Attempt to find the external IP address.
127 # Takes the source address for traffic on default route.
128 get_external_ip() {
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
133     fi
134 }
135
136
137 # Enable Apache mods needed for the proxy.
138 enable_mods() {
139     sudo a2enmod proxy
140     sudo a2enmod proxy_http
141     sudo a2enmod rewrite
142     sudo a2enmod deflate
143     sudo a2enmod headers
144     sudo a2enmod ssl
145 }
146
147
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
152     fi
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
160 }
161
162
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
168     fi
169 }
170
171
172 # Add a port for Apache to listen on. Only added if not yet present
173 # $1 - port number
174 add_listening_port() {
175     if [ -z "$1" ]; then
176         echo_error "No port to add specified"
177         exit 1
178     fi
179
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
183     fi
184 }
185
186
187 # Setup a proxy for requests to the Juju GUI.
188 setup_juju_gui_proxy() {
189     # Get Juju GUI info
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: .+' \
195                                                 | cut -d ' ' -f 2)
196     juju_gui_password=$(echo "$juju_gui_info" | grep -Po 'password: .+' \
197                                                 | cut -d ' ' -f 2)
198     JUJU_GUI_CREDENTIALS=("$juju_gui_username" "$juju_gui_password")
199
200     # Virtual host settings
201     sudo tee "${A2_DIR}/sites-enabled/juju-gui.conf" > /dev/null <<-EOF
202                 <VirtualHost *:${JUJU_LOCAL_PORT}>
203                     ServerName localhost
204                     ServerAlias *
205                     SSLEngine On
206                     SSLCertificateFile ${A2_SSL_DIR}/ca.crt
207                     SSLCertificateKeyFile ${A2_SSL_DIR}/ca.key
208                     RewriteEngine On
209                     RewriteCond %{HTTP:Connection} Upgrade [NC]
210                     RewriteCond %{HTTP:Upgrade} websocket [NC]
211                     RewriteRule /(.*) wss://${juju_socket}/\$1 [P,L]
212                     SSLProxyEngine on
213                     SSLProxyVerify none
214                     SSLProxyCheckPeerCN off
215                     SSLProxyCheckPeerName off
216                     SSLProxyCheckPeerExpire off
217                     ProxyPass / https://${juju_socket}/
218                     ProxyPassReverse / https://${juju_socket}/
219                 </VirtualHost>
220 EOF
221
222     # Add the local port to listen on
223     add_listening_port ${JUJU_LOCAL_PORT}
224 }
225
226
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?"
233         exit 1
234     fi
235
236     # Virtual host settings
237     sudo tee "${A2_DIR}/sites-enabled/openstack-dashboard.conf" > /dev/null \
238         <<-EOF
239                 <VirtualHost *:${OS_LOCAL_PORT}>
240                     ServerName localhost
241                     ServerAlias *
242                     ProxyPass / http://${os_ip}/
243                     ProxyPassReverse / http://${os_ip}/
244                 </VirtualHost>
245                 <VirtualHost *:${OS_LOCAL_PORT_SSL}>
246                     ServerName localhost
247                     ServerAlias *
248                     SSLEngine On
249                     SSLCertificateFile ${A2_SSL_DIR}/ca.crt
250                     SSLCertificateKeyFile ${A2_SSL_DIR}/ca.key
251                     SSLProxyEngine on
252                     SSLProxyVerify none
253                     SSLProxyCheckPeerCN off
254                     SSLProxyCheckPeerName off
255                     SSLProxyCheckPeerExpire off
256                     ProxyPass / https://${os_ip}/
257                     ProxyPassReverse / https://${os_ip}/
258                 </VirtualHost>
259 EOF
260
261     # Add the local ports to listen on
262     add_listening_port ${OS_LOCAL_PORT}
263     add_listening_port ${OS_LOCAL_PORT_SSL}
264
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')
270 }
271
272
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/
276
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?"
280         exit 1
281     fi
282
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
285 }
286
287
288 # Setup a proxy for requests to the Kubernetes dashboard.
289 setup_kubernetes_dashboard_proxy() {
290
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"
295
296     # Virtual host settings
297     sudo tee "${A2_DIR}/sites-enabled/kubernetes-dashboard.conf" > /dev/null \
298         <<-EOF
299                 <VirtualHost *:${KUBE_LOCAL_PORT}>
300                     ServerName localhost
301                     ServerAlias *
302                     ProxyPass / http://${kube_socket}/
303                     ProxyPassReverse / http://${kube_socket}/
304                 </VirtualHost>
305 EOF
306
307     # Add the local port to listen on
308     add_listening_port ${KUBE_LOCAL_PORT}
309 }
310
311
312 print_info_message() {
313     # no xtrace output
314     { set +x; } 2> /dev/null
315
316     echo ''
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}/"
320     echo ''
321
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]}"
328         echo ''
329     fi
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]}"
338         echo ''
339     fi
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='' &"
346         echo ''
347     fi
348
349 }
350
351
352 # Create a homepage for the jumphost with links to the dashboards
353 create_homepage() {
354     # Note: If this function is about to get any more complicated,
355     # it might be worth using template rendering instead.
356
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"
363
364     sudo tee "/var/www/html/index.html" > /dev/null <<EOF
365                 <!doctype html>
366                 <html lang="en">
367                 <head>
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" />
372                   <style>
373                     body { text-align: center; padding-top: 15%; line-height: 1.3;
374                            font-size: 14pt; font-family: Helvetica, Arial, sans-serif;
375                            color: #383a35; }
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 }
394                   </style>
395                 </head>
396                 <body>
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>
400 EOF
401
402     # MAAS info box
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>
414                     </tbody></table>
415                   </fieldset>
416 EOF
417
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>
430                     </table>
431                   </fieldset>
432 EOF
433     fi
434
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>
450                     </table>
451                   </fieldset>
452 EOF
453     fi
454
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>
463                     <div>
464                       <p>No credentials needed if started with command</p>
465                       <pre>kubectl proxy --address='' --accept-hosts='' &</pre>
466                     </div>
467                   </fieldset>
468 EOF
469     fi
470
471     sudo tee -a "/var/www/html/index.html" > /dev/null <<EOF
472                   <script>new Clipboard('button.copy');</script>
473                 </body>
474                 </html>
475 EOF
476 }
477
478
479 main() {
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"
483         exit 77
484     fi
485
486     parse_args "$@"
487
488     get_external_ip
489
490     echo_info "Enabling Apache mods"
491     enable_mods
492
493     echo_info "Generating SSL keys and certificates"
494     generate_ssl_keys_cert
495
496     remove_default_site
497
498     if [ "$SETUP_JUJU" = true ]; then
499         echo_info "Setting up proxy configuration for Juju GUI"
500         setup_juju_gui_proxy
501     fi
502     if [ "$SETUP_OPENSTACK" = true ]; then
503         echo_info "Setting up proxy configuration for OpenStack dashboard"
504         setup_openstack_dashboard_proxy
505     fi
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
511     fi
512
513     echo_info "Creating the homepage for jumphost"
514     create_homepage
515
516
517     echo_info "Restarting HTTP server"
518     sudo service apache2 restart
519
520     # Print info message
521     echo_info "Setup finished."
522     print_info_message
523 }
524
525 # Start the script with the main() function
526 main "$@"