- use the API SSL private key for signing tickets
[myplc.git] / guest.init
1 #!/bin/bash
2 #
3 # plc   Manages all PLC services on this machine
4 #
5 # chkconfig: 2345 5 99
6 #
7 # description:  Manages all PLC services on this machine
8 #
9 # $Id: guest.init,v 1.3 2006/03/27 22:03:38 mlhuang Exp $
10 #
11
12 PATH=/sbin:/bin:/usr/bin:/usr/sbin
13
14 # Source function library.
15 . /etc/init.d/functions
16
17 # Verbosity
18 verbose=0
19
20 # Keep in order! All steps should be idempotent. This means that you
21 # should be able to run them multiple times without depending on
22 # anything previously being run. The idea is that when the
23 # configuration changes, "service plc restart" is called, all
24 # dependencies are fixed up, and everything just works.
25 steps=(
26 network
27 syslog
28 postgresql
29 ssl
30 gpg
31 ssh
32 apache
33 api
34 cron
35 )
36 nsteps=${#steps[@]}
37
38 gethostbyname ()
39 {
40     perl -MSocket -e '($a,$b,$c,$d,@addrs) = gethostbyname($ARGV[0]); print inet_ntoa($addrs[0]) . "\n";' $1 2>/dev/null
41 }
42
43 # Regenerate configuration files
44 reload ()
45 {
46     # Load configuration
47     plc-config --shell >/etc/planetlab/plc_config
48     . /etc/planetlab/plc_config
49
50     # Generate various defaults
51     if [ -z "$PLC_DB_PASSWORD" ] ; then
52         PLC_DB_PASSWORD=$(uuidgen)
53         plc-config --category=plc_db --variable=password --value="$PLC_DB_PASSWORD" --save
54     fi
55
56     if [ -z "$PLC_API_MAINTENANCE_PASSWORD" ] ; then
57         PLC_API_MAINTENANCE_PASSWORD=$(uuidgen)
58         plc-config --category=plc_api --variable=maintenance_password --value="$PLC_API_MAINTENANCE_PASSWORD" --save
59     fi
60
61     if [ -z "$PLC_API_MAINTENANCE_SOURCES" ] ; then
62         for server in API BOOT WWW ; do
63             eval hostname=\${PLC_${server}_HOST}
64             ip=$(gethostbyname $hostname)
65             if [ -n "$ip" ] ; then
66                 if [ -n "$PLC_API_MAINTENANCE_SOURCES" ] ; then
67                     PLC_API_MAINTENANCE_SOURCES="$PLC_API_MAINTENANCE_SOURCES $ip"
68                 else
69                     PLC_API_MAINTENANCE_SOURCES=$ip
70                 fi
71             fi
72         done
73         plc-config --category=plc_api --variable=maintenance_sources --value="$PLC_API_MAINTENANCE_SOURCES" --save
74     fi
75
76     # Save configuration
77     mkdir -p /etc/planetlab/php
78     plc-config --php >/etc/planetlab/php/plc_config.php
79     plc-config --shell >/etc/planetlab/plc_config
80
81     # For backward compatibility, until we can convert all code to use
82     # the now standardized variable names.
83
84     # DB constants are all named the same
85     ln -sf plc_config /etc/planetlab/plc_db
86
87     # API constants
88     cat >/etc/planetlab/plc_api <<EOF
89 PL_API_SERVER='$PLC_API_HOST'
90 PL_API_PATH='$PLC_API_PATH'
91 PL_API_PORT=$PLC_API_PORT
92 PL_API_CAPABILITY_AUTH_METHOD='capability'
93 PL_API_CAPABILITY_PASS='$PLC_API_MAINTENANCE_PASSWORD'
94 PL_API_CAPABILITY_USERNAME='$PLC_API_MAINTENANCE_USER'
95 PL_API_TICKET_KEY_FILE='$PLC_API_SSL_KEY'
96 PLANETLAB_SUPPORT_EMAIL='$PLC_MAIL_SUPPORT_ADDRESS'
97 BOOT_MESSAGES_EMAIL='$PLC_MAIL_BOOT_ADDRESS'
98 WWW_BASE='$PLC_WWW_HOST'
99 BOOT_BASE='$PLC_BOOT_HOST'
100 EOF
101
102     # The format is
103     #
104     # ip:max_role_id:organization_id:password
105     #
106     # It is unlikely that we will let federated sites use the
107     # maintenance account to access each others' APIs, so we always
108     # set organization_id to -1.
109     (
110         echo -n "PL_API_CAPABILITY_SOURCES='"
111         first=1
112         for ip in $PLC_API_MAINTENANCE_SOURCES ; do
113             if [ $first -ne 1 ] ; then
114                 echo -n " "
115             fi
116             first=0
117             echo -n "$ip:-1:-1:$PLC_API_MAINTENANCE_PASSWORD"
118         done
119         echo "'"
120     ) >>/etc/planetlab/plc_api
121
122     cat >/etc/planetlab/php/site_constants.php <<"EOF"
123 <?php
124 include('plc_config.php');
125
126 DEFINE('PL_API_SERVER', PLC_API_HOST);
127 DEFINE('PL_API_PATH', PLC_API_PATH);
128 DEFINE('PL_API_PORT', PLC_API_PORT);
129 DEFINE('PL_API_CAPABILITY_AUTH_METHOD', 'capability');
130 DEFINE('PL_API_CAPABILITY_PASS', PLC_API_MAINTENANCE_PASSWORD);
131 DEFINE('PL_API_CAPABILITY_USERNAME', PLC_API_MAINTENANCE_USER);
132 DEFINE('WWW_BASE', PLC_WWW_HOST);
133 DEFINE('BOOT_BASE', PLC_BOOT_HOST);
134 DEFINE('DEBUG', PLC_WWW_DEBUG);
135 DEFINE('API_CALL_DEBUG', PLC_API_DEBUG);
136 DEFINE('SENDMAIL', PLC_MAIL_ENABLED);
137 DEFINE('PLANETLAB_SUPPORT_EMAIL', PLC_NAME . 'Support <' . PLC_MAIL_SUPPORT_ADDRESS . '>');
138 DEFINE('PLANETLAB_SUPPORT_EMAIL_ONLY', PLC_MAIL_SUPPORT_ADDRESS);
139 ?>
140 EOF
141 }
142
143 config_network ()
144 {
145     case "$1" in
146         start)
147             # Minimal /etc/hosts
148             (
149                 echo "127.0.0.1 localhost.localdomain localhost"
150                 for server in API BOOT WWW ; do
151                     eval hostname=\${PLC_${server}_HOST}
152                     ip=$(gethostbyname $hostname)
153                     if [ -n "$ip" ] ; then
154                         echo "$ip       $hostname"
155                     fi
156                 done
157             ) >/etc/hosts
158
159             # Set up nameservers
160             (
161                 [ -n "$PLC_NET_DNS1" ] && echo "nameserver $PLC_NET_DNS1"
162                 [ -n "$PLC_NET_DNS2" ] && echo "nameserver $PLC_NET_DNS2"
163             ) >/etc/resolv.conf
164             ;;
165     esac
166 }
167
168 config_syslog ()
169 {
170     service syslog $1
171     RETVAL=$?
172 }
173
174 config_postgresql ()
175 {
176     # Default locations
177     PGDATA=/var/lib/pgsql/data
178     postgresql_conf=$PGDATA/postgresql.conf
179     pghba_conf=$PGDATA/pg_hba.conf
180
181     case "$1" in
182         start)
183             if [ "$PLC_DB_ENABLED" != "1" ] ; then
184                 return 0
185             fi
186
187             # Set data directory and redirect startup output to /var/log/pgsql
188             mkdir -p /etc/sysconfig/pgsql
189             (
190                 echo "PGDATA=$PGDATA"
191                 echo "PGLOG=/var/log/pgsql"
192             ) >>/etc/sysconfig/pgsql/postgresql
193
194             # Fix ownership (rpm installation may have changed it)
195             chown -R -H postgres:postgres $(dirname $PGDATA)
196
197             # PostgreSQL must be started at least once to bootstrap
198             # /var/lib/pgsql/data
199             if [ ! -f $postgresql_conf ] ; then
200                 service postgresql start
201                 service postgresql stop
202             fi
203
204             # Enable DB server. PostgreSQL >=8.0 defines listen_addresses,
205             # PostgreSQL 7.x uses tcpip_socket.
206             if grep -q listen_addresses $postgresql_conf ; then
207                 sed -i -e '/^listen_addresses/d' $postgresql_conf
208                 echo "listen_addresses = '*'" >>$postgresql_conf
209             elif grep -q tcpip_socket $postgresql_conf ; then
210                 sed -i -e '/^tcpip_socket/d' $postgresql_conf
211                 echo "tcpip_socket = true" >>$postgresql_conf
212             fi
213
214             # Disable access to all DBs from all hosts
215             sed -i -e '/^\(host\|local\)/d' $pghba_conf
216
217             # Enable passwordless localhost access
218             echo "local all all trust" >>$pghba_conf
219
220             # Enable access from the API and web servers
221             PLC_API_IP=$(gethostbyname $PLC_API_HOST)
222             PLC_WWW_IP=$(gethostbyname $PLC_WWW_HOST)
223             (
224                 echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_API_IP/32 password"
225                 echo "host $PLC_DB_NAME $PLC_DB_USER $PLC_WWW_IP/32 password"
226             ) >>$pghba_conf
227
228             # Fix ownership (sed -i changes it)
229             chown postgres:postgres $postgresql_conf $pghba_conf
230
231             # Start up the server
232             service postgresql start
233             # /etc/init.d/postgresql always returns 0, even on failure
234             status postmaster
235             RETVAL=$?
236
237             # Create/update the unprivileged database user and password
238             if ! psql -U $PLC_DB_USER -c "" template1 >/dev/null 2>&1 ; then
239                 psql -U postgres -c "CREATE USER $PLC_DB_USER PASSWORD '$PLC_DB_PASSWORD'" template1
240             else
241                 psql -U postgres -c "ALTER USER $PLC_DB_USER WITH PASSWORD '$PLC_DB_PASSWORD'" template1
242             fi
243
244             # Create the database if necessary
245             if ! psql -U $PLC_DB_USER -c "" $PLC_DB_NAME >/dev/null 2>&1 ; then
246                 createdb -U postgres $PLC_DB_NAME
247                 psql -U $PLC_DB_USER -f /usr/share/pl_db/plc_schema_3.sql $PLC_DB_NAME
248             fi
249             ;;
250
251         stop)
252             # Drop the current user in case the username changes
253             psql -U postgres -c "DROP USER $PLC_DB_USER" template1
254
255             # WARNING: If the DB name changes, the old DB will be left
256             # intact and a new one will be created. If it changes
257             # back, the old DB will not be re-created.
258
259             # Shut down the server
260             service postgresql stop
261             RETVAL=$?
262             ;;
263     esac
264 }
265
266 # Generate GPG keys
267 config_gpg ()
268 {
269     case "$1" in
270         start)
271             # Generate GPG keyrings
272             if [ ! -f $PLC_ROOT_GPG_KEY_PUB -o ! -f $PLC_ROOT_GPG_KEY ] ; then
273                 mkdir -p $(dirname $PLC_ROOT_GPG_KEY_PUB)
274                 mkdir -p $(dirname $PLC_ROOT_GPG_KEY)
275
276                 # Temporarily replace /dev/random with /dev/urandom to
277                 # avoid running out of entropy.
278                 rm -f /dev/random
279                 mknod /dev/random c 1 9
280                 gpg --homedir=/root --batch --gen-key <<EOF
281 Key-Type: DSA
282 Key-Length: 1024
283 Subkey-Type: ELG-E
284 Subkey-Length: 1024
285 Name-Real: $PLC_NAME Central
286 Name-Comment: http://$PLC_WWW_HOST/
287 Name-Email: $PLC_MAIL_SUPPORT_ADDRESS
288 Expire-Date: 0
289 %pubring $PLC_ROOT_GPG_KEY_PUB
290 %secring $PLC_ROOT_GPG_KEY
291 %commit
292 EOF
293                 RETVAL=$?
294                 rm -f /dev/random
295                 mknod /dev/random c 1 8
296                 chmod 600 $PLC_ROOT_GPG_KEY_PUB $PLC_ROOT_GPG_KEY
297             fi
298             ;;
299     esac
300 }
301
302 symlink ()
303 {
304     mkdir -p $(dirname $2)
305     rm -f $2
306     ln -s $1 $2
307 }
308
309 # Generate SSL certificates
310 config_ssl ()
311 {
312     case "$1" in
313         start)
314             # Generate a self-signed SSL certificate. These nice
315             # commands came from the mod_ssl spec file for Fedora Core
316             # 2. We generate only a single certificate for the web
317             # server, then make copies for the API and boot
318             # servers. As always, these certificates may be overridden
319             # later.
320
321             # Generate SSL private key
322             if [ ! -f $PLC_WWW_SSL_KEY ] ; then
323                 mkdir -p $(dirname $PLC_WWW_SSL_KEY)
324                 openssl genrsa -rand /proc/apm:/proc/cpuinfo:/proc/dma:/proc/filesystems:/proc/interrupts:/proc/ioports:/proc/pci:/proc/rtc:/proc/uptime 1024 >$PLC_WWW_SSL_KEY
325                 RETVAL=$(($RETVAL+$?))
326                 chmod 600 $PLC_WWW_SSL_KEY
327             fi
328
329             # Generate self-signed certificate
330             if [ ! -f $PLC_WWW_SSL_CRT ] ; then
331                 mkdir -p $(dirname $PLC_WWW_SSL_CRT)
332                 openssl req -new -x509 -days 365 -set_serial $RANDOM \
333                     -key $PLC_WWW_SSL_KEY -out $PLC_WWW_SSL_CRT <<EOF
334 --
335 State
336 City
337 Organization
338 $PLC_NAME Central
339 $PLC_WWW_HOST
340 $PLC_MAIL_SUPPORT_ADDRESS
341 EOF
342                 RETVAL=$(($RETVAL+$?))
343                 chmod 644 $PLC_WWW_SSL_CRT
344             fi
345
346             # Make copies for the API and boot servers
347             if [ ! -f $PLC_API_SSL_KEY ] ; then
348                 cp -a $PLC_WWW_SSL_KEY $PLC_API_SSL_KEY
349             fi
350             if [ ! -f $PLC_API_SSL_KEY_PUB ] ; then
351                 openssl rsa -pubout <$PLC_API_SSL_KEY >$PLC_API_SSL_KEY_PUB
352                 RETVAL=$(($RETVAL+$?))
353             fi
354             if [ ! -f $PLC_API_SSL_CRT ] ; then
355                 cp -a $PLC_WWW_SSL_CRT $PLC_API_SSL_CRT
356             fi
357             if [ ! -f $PLC_API_SSL_KEY ] ; then
358                 cp -a $PLC_WWW_SSL_KEY $PLC_API_SSL_KEY
359             fi
360             if [ ! -f $PLC_API_SSL_CRT ] ; then
361                 cp -a $PLC_WWW_SSL_CRT $PLC_API_SSL_CRT
362             fi
363
364             # Install into both /etc/pki (Fedora Core 4) and
365             # /etc/httpd/conf (Fedora Core 2). If the API, boot, and
366             # web servers are all running on the same machine, the web
367             # server certificate takes precedence.
368             for server in API BOOT WWW ; do
369                 eval enabled=\${PLC_${server}_ENABLED}
370                 if [ "$enabled" != "1" ] ; then
371                     continue
372                 fi
373                 eval ssl_crt=\${PLC_${server}_SSL_CRT}
374                 eval ssl_key=\${PLC_${server}_SSL_KEY}
375
376                 symlink $ssl_crt /etc/pki/tls/certs/localhost.crt
377                 symlink $ssl_key /etc/pki/tls/private/localhost.key
378                 symlink $ssl_crt /etc/httpd/conf/ssl.crt/server.crt
379                 symlink $ssl_key /etc/httpd/conf/ssl.key/server.key
380             done
381             ;;
382     esac
383 }
384
385 # Generate SSH keys
386 config_ssh ()
387 {
388     # XXX Could make these configurable
389     KEY_TYPE_ROOT=rsa
390     KEY_LEN_ROOT=1024
391     KEY_TYPE_DEBUG=rsa
392     KEY_LEN_DEBUG=2048  
393
394     case "$1" in
395         start)
396             tmp=$(mktemp -d /tmp/ssh.XXXXXX)
397
398             # Generate root SSH key
399             if [ ! -f $PLC_ROOT_SSH_KEY_PUB -o ! -f $PLC_ROOT_SSH_KEY ] ; then
400                 ssh-keygen -N "" -C "$PLC_NAME Central <$PLC_MAIL_SUPPORT_ADDRESS>" \
401                     -b $KEY_LEN_ROOT -t $KEY_TYPE_ROOT -f $tmp/root
402                 RETVAL=$(($RETVAL+$?))
403                 install -D -m 600 $tmp/root $PLC_ROOT_SSH_KEY
404                 install -D -m 600 $tmp/root.pub $PLC_ROOT_SSH_KEY_PUB
405             fi
406
407             # Generate debug SSH key
408             if [ ! -f $PLC_DEBUG_SSH_KEY_PUB -o ! -f $PLC_DEBUG_SSH_KEY ] ; then
409                 ssh-keygen -N "" -C "$PLC_NAME Central <$PLC_MAIL_SUPPORT_ADDRESS>" \
410                     -b $KEY_LEN_DEBUG -t $KEY_TYPE_DEBUG -f $tmp/debug
411                 RETVAL=$(($RETVAL+$?))
412                 install -D -m 600 $tmp/debug $PLC_DEBUG_SSH_KEY
413                 install -D -m 600 $tmp/debug.pub $PLC_DEBUG_SSH_KEY_PUB
414             fi
415
416             rm -rf $tmp
417             ;;
418     esac
419 }
420
421 # Configure Apache web server
422 config_apache ()
423 {
424     # Default locations
425     DocumentRoot=/var/www/html
426     php_ini=/etc/php.ini
427     httpd_conf=/etc/httpd/conf/httpd.conf
428     ssl_conf=/etc/httpd/conf.d/ssl.conf
429     plc_conf=/etc/httpd/conf.d/plc.conf
430
431     case "$1" in
432         start)
433             if [ "$PLC_API_ENABLED" != "1" -a \
434                  "$PLC_BOOT_ENABLED" != "1" -a \
435                  "$PLC_WWW_ENABLED" != "1" ] ; then
436                 return 0
437             fi
438
439             # Set the default include path
440             include_path=".:$DocumentRoot/includes:$DocumentRoot/generated:/etc/planetlab/php"
441             sed -i -e "s@;include_path = \"\.:.*\"@include_path = \"$include_path\"@" $php_ini
442
443             # Set the port numbers. If the API, boot, and web servers
444             # are all running on the same machine, the web server port
445             # numbers take precedence.
446             for server in API BOOT WWW ; do
447                 eval enabled=\${PLC_${server}_ENABLED}
448                 if [ "$enabled" != "1" ] ; then
449                     continue
450                 fi
451                 eval http_port=\${PLC_${server}_PORT}
452                 eval https_port=\${PLC_${server}_SSL_PORT}
453
454                 if [ -n "$http_port" ] ; then
455                     sed -i -e "s/^Listen .*/Listen $http_port/" $httpd_conf
456                 fi
457                 if [ -n "$https_port" ] ; then
458                     sed -i \
459                         -e "s/^Listen .*/Listen $https_port/" \
460                         -e "s/<VirtualHost _default_:.*>/<VirtualHost _default_:$https_port>/" \
461                         $ssl_conf
462                 fi
463             done
464                     
465             # Set custom Apache directives
466             (
467                 if [ "$PLC_API_ENABLED" = "1" ] ; then
468                     cat <<EOF
469 <Location $PLC_API_PATH>
470     SetHandler python-program
471     PythonPath "sys.path + ['/usr/share/plc_api']"
472     PythonHandler mod_pythonXMLRPC
473 </Location>
474 EOF
475                 fi
476
477                 cat <<EOF
478 <VirtualHost $PLC_WWW_HOST:$PLC_WWW_PORT>
479     Redirect /db https://$PLC_WWW_HOST:$PLC_WWW_SSL_PORT/db
480 </VirtualHost>
481 EOF
482             ) >$plc_conf
483
484             # Make alpina-logs directory writable for bootmanager log upload
485             chown apache:apache $DocumentRoot/alpina-logs/nodes
486
487             service httpd start
488             RETVAL=$?
489             ;;
490
491         stop)
492             service httpd stop
493             RETVAL=$?
494             ;;
495     esac
496 }
497
498 config_api ()
499 {
500     case "$1" in
501         start)
502             if [ "$PLC_API_ENABLED" -ne 1 ] ; then
503                 return
504             fi
505
506             # Update the maintenance account username. This can't be
507             # done through the api-config script since it uses the
508             # maintenance account to access the API. The maintenance
509             # account should be person_id 1 since it is created by the
510             # DB schema itself.
511             psql -U $PLC_DB_USER -c "UPDATE persons SET email='$PLC_API_MAINTENANCE_USER' WHERE person_id=1" $PLC_DB_NAME
512
513             # Bootstrap the DB
514             api-config
515             RETVAL=$?
516             ;;
517     esac
518 }
519
520 config_cron ()
521 {
522     case "$1" in
523         start)
524             cat >/etc/cron.d/plc.cron <<EOF
525 SHELL=/bin/bash
526 PATH=/sbin:/bin:/usr/sbin:/usr/bin
527 MAILTO=$PLC_MAIL_SUPPORT_ADDRESS
528 HOME=/
529
530 # minute hour day-of-month month day-of-week user command
531 */5 * * * * root gen-slices-xml-05.py
532 */15 * * * * root gen-sites-xml.py
533 */15 * * * * root gen-static-content.py
534 EOF
535
536             # Run them once at startup
537             gen-slices-xml-05.py
538             gen-sites-xml.py
539             gen-static-content.py
540
541             service crond start
542             RETVAL=$?
543             ;;
544
545         stop)
546             service crond stop
547             RETVAL=$?
548             ;;
549     esac
550 }
551
552 usage()
553 {
554     echo "Usage: $0 [OPTION]... [COMMAND]"
555     echo "      -v              Be verbose"
556     echo "      -h              This message"
557     echo
558     echo "Commands:"
559     echo "      start           Start all PLC subsystems"
560     echo "      stop            Stop all PLC subsystems"
561     echo "      reload          Regenerate configuration files"
562     echo "      restart         Restart all PLC subsystems"
563     exit 1
564 }
565
566 # Get options
567 while getopts "vh" opt ; do
568     case $opt in
569         v)
570             verbose=1
571             set -x
572             ;;
573         h|*)
574             usage
575             ;;
576     esac
577 done
578
579 shift $(($OPTIND - 1))
580 if [ -z "$1" ] ; then
581     usage
582 fi
583
584 exec 3>&1
585 exec 4>&2
586 if [ $verbose -eq 0 ] ; then
587     exec 1>>/var/log/boot.log
588     exec 2>>/var/log/boot.log
589 fi
590
591 # Generate and load configuration
592 reload
593 . /etc/planetlab/plc_config
594
595 RETVAL=0
596
597 start ()
598 {
599     for step in "${steps[@]}" ; do
600         echo -n $"PLC: Starting $step: " >&3
601         RETVAL=0
602         config_$step start
603         if [ $RETVAL -eq 0 ] ; then
604             success $"PLC: $step startup" >&3
605         else
606             failure $"PLC: $step startup" >&3
607         fi
608         echo >&3
609     done
610 }
611
612 stop ()
613 {
614     for i in $(seq 1 $nsteps) ; do
615         step=${steps[$(($nsteps - $i))]}
616         echo -n $"PLC: Shutting down $step: " >&3
617         RETVAL=0
618         config_$step stop
619         if [ $RETVAL -eq 0 ] ; then
620             success $"PLC: $step shutdown" >&3
621         else
622             failure $"PLC: $step shutdown" >&3
623         fi
624         echo >&3
625     done
626 }
627
628 case "$1" in
629     start|stop)
630         $1
631         ;;
632
633     restart)
634         stop
635         start
636         ;;
637
638     reload)
639         ;;
640
641     *)
642         usage >&3
643         ;;
644 esac
645
646 exit $RETVAL