Rewrite plinth to use sudo and action scripts instead of exmachina for privileged accesss.

This commit is contained in:
Petter Reinholdtsen 2013-09-23 09:37:04 +02:00
parent 0954d9d383
commit 444365ec78
10 changed files with 52 additions and 65 deletions

View File

@ -29,9 +29,9 @@ install: default
mkdir -p $(DESTDIR)$(PYDIR) $(DESTDIR)$(DATADIR) $(DESTDIR)/usr/bin \ mkdir -p $(DESTDIR)$(PYDIR) $(DESTDIR)$(DATADIR) $(DESTDIR)/usr/bin \
$(DESTDIR)/usr/share/doc/plinth $(DESTDIR)/usr/share/man/man1 $(DESTDIR)/usr/share/doc/plinth $(DESTDIR)/usr/share/man/man1
cp -a static themes $(DESTDIR)$(DATADIR)/ cp -a static themes $(DESTDIR)$(DATADIR)/
cp -a actions $(DESTDIR)$(DATADIR)/
cp -a sudoers.d $(DESTDIR)/etc/sudoers.d
cp -a *.py modules templates $(DESTDIR)$(PYDIR)/ cp -a *.py modules templates $(DESTDIR)$(PYDIR)/
mkdir -p $(DESTDIR)$(PYDIR)/exmachina
cp -a vendor/exmachina/exmachina.py $(DESTDIR)$(PYDIR)/exmachina/.
cp share/init.d/plinth $(DESTDIR)/etc/init.d cp share/init.d/plinth $(DESTDIR)/etc/init.d
install plinth $(DESTDIR)/usr/bin/ install plinth $(DESTDIR)/usr/bin/
mkdir -p $(DESTDIR)/var/lib/plinth/cherrypy_sessions $(DESTDIR)/var/log/plinth $(DESTDIR)/var/run mkdir -p $(DESTDIR)/var/lib/plinth/cherrypy_sessions $(DESTDIR)/var/log/plinth $(DESTDIR)/var/run

3
README
View File

@ -43,9 +43,6 @@ get down into the details and configure things the average user never
thinks about. For example, experts can turn off ntp or switch ntp thinks about. For example, experts can turn off ntp or switch ntp
servers. Basic users should never even know those options exist. servers. Basic users should never even know those options exist.
See comments in exmachina/exmachina.py for more details about the
configuration management process seperation scheme.
## Getting Started ## Getting Started
See the INSTALL file for additional details. Run: See the INSTALL file for additional details. Run:

10
actions/hostname-change Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh
hostname="$1"
echo "$hostname" > /etc/hostname
if [ -x /etc/init.d/hostname.sh ] ; then
service hostname.sh start
else
service hostname start
fi

14
actions/timezone-change Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
zonename="$1"
tzpath="/usr/share/zoneinfo/$zonename"
if [ -e "$tzpath" ] ; then
cp "$tzpath" /etc/localtime
echo "$zonename" > /etc/timezone
exit 0
else
echo "timezone not valid" 1>&2
exit 1
fi

View File

@ -9,6 +9,7 @@ from gettext import gettext as _
from filedict import FileDict from filedict import FileDict
from modules.auth import require from modules.auth import require
from plugin_mount import PagePlugin, FormPlugin from plugin_mount import PagePlugin, FormPlugin
from privilegedactions import privilegedaction_run
import cfg import cfg
from forms import Form from forms import Form
from model import User from model import User
@ -48,20 +49,14 @@ def get_hostname():
def set_hostname(hostname): def set_hostname(hostname):
"Sets machine hostname to hostname" "Sets machine hostname to hostname"
cfg.log.info("Writing '%s' to /etc/hostname with exmachina" % hostname) cfg.log.info("Changing hostname to '%s'" % hostname)
try: try:
cfg.exmachina.augeas.set("/files/etc/hostname/*", hostname) privilegedaction_run("hostname-change", [hostname])
cfg.exmachina.augeas.save()
# don't persist/cache change unless it was saved successfuly # don't persist/cache change unless it was saved successfuly
sys_store = filedict_con(cfg.store_file, 'sys') sys_store = filedict_con(cfg.store_file, 'sys')
sys_store['hostname'] = hostname sys_store['hostname'] = hostname
if platform.linux_distribution()[0]=="Ubuntu" :
cfg.exmachina.service.start("hostname")
else:
cfg.exmachina.initd.start("hostname.sh") # is hostname.sh debian-only?
except OSError, e: except OSError, e:
raise cherrypy.HTTPError(500, "Hostname restart failed: %s" % e) raise cherrypy.HTTPError(500, "Updating hostname failed: %s" % e)
class general(FormPlugin, PagePlugin): class general(FormPlugin, PagePlugin):
url = ["/sys/config"] url = ["/sys/config"]
@ -79,7 +74,7 @@ class general(FormPlugin, PagePlugin):
return '<p>' + _('Only members of the expert group are allowed to see and modify the system setup.') + '</p>' return '<p>' + _('Only members of the expert group are allowed to see and modify the system setup.') + '</p>'
sys_store = filedict_con(cfg.store_file, 'sys') sys_store = filedict_con(cfg.store_file, 'sys')
hostname = cfg.exmachina.augeas.get("/files/etc/hostname/*") hostname = get_hostname()
# this layer of persisting configuration in sys_store could/should be # this layer of persisting configuration in sys_store could/should be
# removed -BLN # removed -BLN
defaults = {'time_zone': "slurp('/etc/timezone').rstrip()", defaults = {'time_zone': "slurp('/etc/timezone').rstrip()",
@ -139,10 +134,10 @@ class general(FormPlugin, PagePlugin):
raise raise
else: else:
message += msg message += msg
time_zone = time_zone.strip()
if time_zone != sys_store['time_zone']: if time_zone != sys_store['time_zone']:
src = os.path.join("/usr/share/zoneinfo", time_zone)
cfg.log.info("Setting timezone to %s" % time_zone) cfg.log.info("Setting timezone to %s" % time_zone)
cfg.exmachina.misc.set_timezone(time_zone) privilegedaction_run("timezone-change", [time_zone])
sys_store['time_zone'] = time_zone sys_store['time_zone'] = time_zone
return message or "Settings updated." return message or "Settings updated."

View File

@ -18,7 +18,6 @@ from logger import Logger
#from modules.auth import AuthController, require, member_of, name_is #from modules.auth import AuthController, require, member_of, name_is
from withsqlite.withsqlite import sqlite_db from withsqlite.withsqlite import sqlite_db
from exmachina.exmachina import ExMachinaClient
import socket import socket
__version__ = "0.2.14" __version__ = "0.2.14"
@ -84,8 +83,6 @@ def parse_arguments():
parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.') parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.')
parser.add_argument('--pidfile', default="", parser.add_argument('--pidfile', default="",
help='specify a file in which the server may write its pid') help='specify a file in which the server may write its pid')
parser.add_argument('--listen-exmachina-key', default=False, action='store_true',
help='listen for JSON-RPC shared secret key on stdin at startup')
args=parser.parse_args() args=parser.parse_args()
if args.pidfile: if args.pidfile:
cfg.pidfile = args.pidfile cfg.pidfile = args.pidfile
@ -96,13 +93,6 @@ def parse_arguments():
except AttributeError: except AttributeError:
cfg.pidfile = "plinth.pid" cfg.pidfile = "plinth.pid"
if args.listen_exmachina_key:
# this is where we optionally try to read in a shared secret key to
# authenticate connections to exmachina
cfg.exmachina_secret_key = sys.stdin.readline().strip()
else:
cfg.exmachina_secret_key = None
def setup(): def setup():
parse_arguments() parse_arguments()
@ -113,19 +103,6 @@ def setup():
except AttributeError: except AttributeError:
pass pass
try:
from vendor.exmachina.exmachina import ExMachinaClient
except ImportError:
cfg.exmachina = None
print "unable to import exmachina client library, but continuing anyways..."
else:
try:
cfg.exmachina = ExMachinaClient(
secret_key=cfg.exmachina_secret_key or None)
except socket.error:
cfg.exmachina = None
print "couldn't connect to exmachina daemon, but continuing anyways..."
os.chdir(cfg.python_root) os.chdir(cfg.python_root)
cherrypy.config.update({'error_page.404': error_page_404}) cherrypy.config.update({'error_page.404': error_page_404})
cherrypy.config.update({'error_page.500': error_page_500}) cherrypy.config.update({'error_page.500': error_page_500})

15
privilegedactions.py Normal file
View File

@ -0,0 +1,15 @@
import sys
import subprocess
import cfg
def privilegedaction_run(action, options):
cmd = ['sudo', '-n', "/usr/share/plinth/actions/%s" % action]
if options:
cmd.extend(options)
cfg.log.info('running: %s ' % ' '.join(cmd))
output, error = \
subprocess.Popen(cmd,
stdout = subprocess.PIPE,
stderr= subprocess.PIPE).communicate()
return output, error

View File

@ -7,21 +7,17 @@
# Default-Stop: 0 1 6 # Default-Stop: 0 1 6
# Short-Description: plinth web frontend # Short-Description: plinth web frontend
# Description: # Description:
# Control the exmachina privileged execution daemon and the plinth # Control the plinth web frontend.
# web frontend.
### END INIT INFO ### END INIT INFO
# This file is /etc/init.d/plinth # This file is /etc/init.d/plinth
DAEMON=/usr/local/bin/plinth.py DAEMON=/usr/local/bin/plinth.py
EXMACHINA_DAEMON=/usr/local/bin/exmachina.py
PID_FILE=/var/run/plinth.pid PID_FILE=/var/run/plinth.pid
EXMACHINA_PID_FILE=/var/run/exmachina.pid
PLINTH_USER=www-data PLINTH_USER=www-data
PLINTH_GROUP=www-data PLINTH_GROUP=www-data
test -x $DAEMON || exit 0 test -x $DAEMON || exit 0
test -x $EXMACHINA_DAEMON || exit 0
set -e set -e
@ -31,17 +27,9 @@ start_plinth (){
if [ -f $PID_FILE ]; then if [ -f $PID_FILE ]; then
echo Already running with a pid of `cat $PID_FILE`. echo Already running with a pid of `cat $PID_FILE`.
else else
if [ -f $EXMACHINA_PID_FILE ]; then
echo exmachina was already running with a pid of `cat $EXMACHINA_PID_FILE`.
kill -15 `cat $EXMACHINA_PID_FILE`
rm -rf $EXMACHINA_PID_FILE
fi
SHAREDKEY=`$EXMACHINA_DAEMON --random-key`
touch $PID_FILE touch $PID_FILE
chown $PLINTH_USER:$PLINTH_GROUP $PID_FILE chown $PLINTH_USER:$PLINTH_GROUP $PID_FILE
echo $SHAREDKEY | $EXMACHINA_DAEMON --pidfile=$EXMACHINA_PID_FILE --group=$PLINTH_GROUP || rm $PID_FILE sudo -u $PLINTH_USER -g $PLINTH_GROUP $DAEMON --pidfile=$PID_FILE
sleep 0.5
echo $SHAREDKEY | sudo -u $PLINTH_USER -g $PLINTH_GROUP $DAEMON --pidfile=$PID_FILE
fi fi
} }
@ -53,13 +41,6 @@ stop_plinth () {
else else
echo "No pid file at $PID_FILE suggests plinth is not running." echo "No pid file at $PID_FILE suggests plinth is not running."
fi fi
if [ -f $EXMACHINA_PID_FILE ]; then
kill -15 `cat $EXMACHINA_PID_FILE` || true
rm -rf $EXMACHINA_PID_FILE
echo "killed exmachina"
else
echo "No pid file at $EXMACHINA_PID_FILE suggests exmachina is not running."
fi
} }
test -x $DAEMON || exit 0 test -x $DAEMON || exit 0

View File

@ -1,10 +1,7 @@
#! /bin/sh #! /bin/sh
#PYTHONPATH=vendor/exmachina:$PYTHONPATH #PYTHONPATH=$PYTHONPATH
export PYTHONPATH export PYTHONPATH
sudo killall exmachina.py
sudo vendor/exmachina/exmachina.py -v &
python plinth.py python plinth.py
sudo killall exmachina.py

1
sudoers.d/plinth Normal file
View File

@ -0,0 +1 @@
plinth ALL=(ALL:ALL) NOPASSWD:/usr/share/plinth/actions/*