From b1a42465613ccc74d5b5c5a96412388fe9fb88b4 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss <erdnaxe@crans.org> Date: Tue, 14 Apr 2020 12:52:11 +0200 Subject: [PATCH] Update server config --- cpasswords/server.py | 101 ++++++++++++++--------------- docs/serverconfig.example.py | 120 +++++------------------------------ 2 files changed, 64 insertions(+), 157 deletions(-) diff --git a/cpasswords/server.py b/cpasswords/server.py index 2c61ea6..4b64fc2 100755 --- a/cpasswords/server.py +++ b/cpasswords/server.py @@ -11,9 +11,6 @@ Authors : Daniel Stan <daniel.stan@crans.org> SPDX-License-Identifier: GPL-3.0-or-later """ -from __future__ import print_function -import serverconfig - import glob import os import pwd @@ -22,30 +19,35 @@ import json import datetime import socket import subprocess -import itertools +import logging from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart +# Configuration loading +# Guess name as we do not have config +bootstrap_cmd_name = os.path.split(sys.argv[0])[1].replace("-server", "") +default_config_path = "/etc/" + bootstrap_cmd_name +config_path = os.getenv( + "CRANSPASSWORDS_SERVER_CONFIG_DIR", default_config_path) try: - from cpasswords import clientlib -except ImportError: - print("Couldn't import clientlib. Remote sync may not work", file=sys.stderr) - -# Même problème que pour le client, il faut bootstraper le nom de la commande -# Pour accéder à la config -conf_path = os.getenv('CRANSPASSWORDS_SERVER_CONFIG_DIR', None) -if not conf_path: - cmd_name = os.path.split(sys.argv[0])[1].replace("-server", "") - conf_path = "/etc/%s/" % (cmd_name,) - -sys.path.append(conf_path) - + sys.path.append(config_path) + import serverconfig +except ModuleNotFoundError: + # If config could not be imported, display an error if required + # Do not use logger as it has not been initialized yet + print("%s/serverconfig.py could not be found or read.\n" + "Please copy `docs/serverconfig.example.py` from the source " + "repository and customize." % config_path) + exit(1) + +# Local logger +log = logging.getLogger(__name__) + +# Get user name that launch the server MYUID = pwd.getpwuid(os.getuid())[0] if MYUID == 'root': MYUID = os.environ['SUDO_USER'] -# Fonctions internes au serveur - def validate(roles, mode='r'): """Vérifie que l'appelant appartient bien aux roles précisés @@ -54,27 +56,26 @@ def validate(roles, mode='r'): for role in roles: if mode == 'w': role += '-w' - if serverconfig.ROLES.has_key(role) and MYUID in serverconfig.ROLES[role]: + if role in serverconfig.ROLES.keys() and MYUID in serverconfig.ROLES[role]: return True return False def getpath(filename, backup=False): """Récupère le chemin du fichier ``filename``""" - assert(isinstance(filename, unicode)) filename = filename.encode('utf-8') return os.path.join(serverconfig.STORE, '%s.%s' % (filename, 'bak' if backup else 'json')) def writefile(filename, contents): """Écrit le fichier avec les bons droits UNIX""" - os.umask(0077) + os.umask(0o077) f = open(filename, 'w') f.write(contents.encode("utf-8")) f.close() -class server_command(object): +class ServerCommand(object): """ Une instance est un décorateur pour la fonction servant de commande externe du même nom""" @@ -104,14 +105,14 @@ class server_command(object): self.name = name self.stdin_input = stdin_input self.write = write - server_command.by_name[name] = self + ServerCommand.by_name[name] = self def __call__(self, fun): self.decorated = fun return fun # Fonction exposées par le serveur -@server_command('keep-alive') +@ServerCommand('keep-alive') def keepalive(): """ Commande permettant de réaliser un tunnel json (un datagramme par ligne) Un message entre le client et le serveur consiste en l'échange de dico @@ -128,7 +129,7 @@ def keepalive(): try: # Une action du protocole = de l'ascii action = data['action'].encode('ascii') - content = server_command.by_name[action].decorated(*data['args']) + content = ServerCommand.by_name[action].decorated(*data['args']) status = u'ok' except Exception as e: status = u'error' @@ -141,25 +142,25 @@ def keepalive(): sys.stdout.flush() -@server_command('listroles') +@ServerCommand('listroles') def listroles(): """Liste des roles existant et de leurs membres. Renvoie également un rôle particulier ``"whoami"``, contenant l'username de l'utilisateur qui s'est connecté.""" d = serverconfig.ROLES - if d.has_key("whoami"): + if "whoami" in d.keys(): raise ValueError('La rôle "whoami" ne devrait pas exister') d["whoami"] = MYUID return d -@server_command('listkeys') +@ServerCommand('listkeys') def listkeys(): """Liste les usernames et les (mail, fingerprint) correspondants""" return serverconfig.KEYS -@server_command('listfiles') +@ServerCommand('listfiles') def listfiles(): """Liste les fichiers dans l'espace de stockage, et les roles qui peuvent y accéder""" os.chdir(serverconfig.STORE) @@ -173,7 +174,7 @@ def listfiles(): return files -@server_command('restorefiles') +@ServerCommand('restorefiles') def restorefiles(): """Si un fichier a été corrompu, on restore son dernier backup valide""" os.chdir(serverconfig.STORE) @@ -204,7 +205,7 @@ def restorefiles(): return files -@server_command('getfile') +@ServerCommand('getfile') def getfile(filename): """Récupère le fichier ``filename``""" filepath = getpath(filename) @@ -218,7 +219,7 @@ def getfile(filename): return [False, u"Le fichier %s n'existe pas." % filename] -@server_command('getfiles', stdin_input=True) +@ServerCommand('getfiles', stdin_input=True) def getfiles(filenames): """Récupère plusieurs fichiers, lit la liste des filenames demandés sur stdin""" return [getfile(f) for f in filenames] @@ -243,10 +244,6 @@ def _putfile(filename, roles, contents): notification(u"Modification", filename, MYUID) filepath = getpath(filename) - if type(contents) != unicode: - return [False, u"Erreur: merci de patcher votre cpasswords !" - + "(contents should be encrypted str)"] - # Or fuck yourself writefile(filepath, json.dumps({'roles': roles, 'contents': contents})) @@ -257,7 +254,7 @@ def _putfile(filename, roles, contents): return [True, u"Modification effectuée."] -@server_command('putfile', stdin_input=True, write=True) +@ServerCommand('putfile', stdin_input=True, write=True) def putfile(filename, parsed_stdin): """Écrit le fichier ``filename`` avec les données reçues sur stdin.""" try: @@ -268,7 +265,7 @@ def putfile(filename, parsed_stdin): return _putfile(filename, roles, contents) -@server_command('putfiles', stdin_input=True, write=True) +@ServerCommand('putfiles', stdin_input=True, write=True) def putfiles(parsed_stdin): """Écrit plusieurs fichiers. Lit les filenames sur l'entrée standard avec le reste.""" @@ -285,7 +282,7 @@ def putfiles(parsed_stdin): return results -@server_command('rmfile', write=True) +@ServerCommand('rmfile', write=True) def rmfile(filename): """Supprime le fichier filename après avoir vérifié les droits sur le fichier""" gotit, old = getfile(filename) @@ -305,7 +302,7 @@ def rmfile(filename): # TODO monter plus haut def backup(corps, fname, old): """Backupe l'ancienne version du fichier""" - os.umask(0077) + os.umask(0o077) back = open(getpath(fname, backup=True), 'a') back.write(json.dumps(old)) back.write('\n') @@ -344,30 +341,30 @@ def notification_mail(): actions = set(task[1] for task in _notif_todo) msg = MIMEMultipart(_charset="utf-8") - msg['Subject'] = u"Modification de la base (%s)" % (', '.join(actions)) + msg['Subject'] = "Modification de la base (%s)" % (', '.join(actions)) msg['X-Mailer'] = serverconfig.cmd_name.decode() msg['From'] = frommail msg['To'] = tomail - msg.preamble = u"%s report" % (serverconfig.cmd_name.decode(),) + msg.preamble = "%s report" % (serverconfig.cmd_name.decode(),) - liste = (u" * %s de %s par %s" % task for task in _notif_todo) + liste = (" * %s de %s par %s" % task for task in _notif_todo) - info = MIMEText(u"Des modifications ont été faites:\n" + - u"\n".join(liste) + - u"\n\nDes sauvegardes ont été réalisées." + - u"\n\nModification effectuée sur %s." % socket.gethostname() + - u"\n\n-- \nCranspasswords.py", _charset="utf-8") + info = MIMEText("Des modifications ont été faites:\n" + + "\n".join(liste) + + "\n\nDes sauvegardes ont été réalisées." + + "\n\nModification effectuée sur %s." % socket.gethostname() + + "\n\n-- \nCranspasswords.py", _charset="utf-8") msg.attach(info) - mailProcess = subprocess.Popen( + mail_process = subprocess.Popen( [serverconfig.sendmail_cmd, "-t"], stdin=subprocess.PIPE) - mailProcess.communicate(msg.as_string()) + mail_process.communicate(msg.as_string()) def main(): argv = sys.argv[0:] command_name = argv[1] - command = server_command.by_name[command_name] + command = ServerCommand.by_name[command_name] if serverconfig.READONLY and command.write: raise IOError("Ce serveur est read-only.") diff --git a/docs/serverconfig.example.py b/docs/serverconfig.example.py index b49cbbd..3982252 100755 --- a/docs/serverconfig.example.py +++ b/docs/serverconfig.example.py @@ -1,13 +1,8 @@ -#!/usr/bin/env python2 -# -*- encoding: utf-8 -*- - -""" Configuration Serveur de cranspasswords. +""" +Configuration Serveur de cranspasswords. Sont définis ici les utilisateurs et les rôles associés. -Ce fichier est donné à titre d'exemple, mais n'est PAS -utilisé lors du fonctionnement en mode client. - -Dans le futur, pourra être remplacé par une connexion ldap. +Ce fichier est donné à titre d'exemple. """ #: Pour override le nom si vous voulez renommer la commande @@ -23,125 +18,40 @@ STORE = '/var/lib/%s/db/' % (cmd_name,) READONLY = False #: Expéditeur du mail de notification -CRANSP_MAIL = u"%s <root@crans.org>" % (cmd_name,) +CRANSP_MAIL = "%s <root@crans.org>" % (cmd_name,) #: Destinataire du mail de notification -DEST_MAIL = u"root@crans.org" +DEST_MAIL = "root@crans.org" #: Mapping des utilisateurs et de leurs (mail, fingerprint GPG) KEYS = { - u'aza-vallina': (u'Damien.Aza-Vallina@crans.org', None), - u'becue': (u'becue@crans.org', u'9AE04D986400E3B67528F4930D442664194974E2'), - u'blockelet': (u'blockelet@crans.org', u'550A057BC913EA4637D250495314C173AF087A52'), - u'boilard': (u'boilard@crans.org', u'E73A648AAB5E81BE38038350C1690AB9C39EB6F4'), - u'cauderlier': (u'cauderlier@crans.org', u'4D302A566C2956334DF6AD8E1B199227E5ABC502'), - u'chambart': (u'pierre.chambart@crans.org', u'085D0DFB66EAF9448C42979C43680A46F2530FCE'), - u'dandrimont': (u'nicolas.dandrimont@crans.org', u'791F12396630DD71FD364375B8E5087766475AAF'), - u'dimino': (u'jdimino@dptinfo.ens-cachan.fr', u'2C938EAC93A16F8129F807C81E8A30532127F85A'), - u'dstan': (u'daniel.stan@crans.org', u'90520CFDE846E7651A1B751FBC9BF8456E1C820B'), - u'durand-gasselin': (u'adg@crans.org', u'B3EA34ED8A4EA3B5C3E6C04D30F01C448E96ACDA'), - u'glondu': (u'Stephane.Glondu@crans.org', u'58EB0999C64E897EE894B8037853DA4D49881AD3'), - u'huber': (u'olivier.huber@crans.org', u'3E9473AF796C530F9C4DE7DB1EF81A95E0DCF376'), - u'iffrig': (u'iffrig@crans.org', u'26A210E2584208FEF6BE8F3718068DEA354B0045'), - u'lagorce': (u'xavier.lagorce@crans.org', u'08C26F5AABC5570E5E2F52B39D9D7CE70BF3708E'), - u'lajus': (u'lajus@crans.org', None), - u'legallic': (u'legallic@crans.org', u'4BDD2DC3F10C26B9BC3B0BD93602E1C9A94025B0'), - u'lerisson': (u'lerisson@crans.org', None), - u'maioli': (u'maioli@crans.org', None), - u'parret-freaud': (u'apf@crans.org', u'41049BA24909F12F958386A336381C4C2CBB08AA'), - u'samir': (u'samir@crans.org', u'C7B8823E96E8DC2798970340C86AD2AA41C2B76B'), - u'tvincent': (u'vincent.thomas@crans.org', u'DFB04CE4394B1115C587AE101C6BE33AC5C4ACC0'), -#Autogen - u'besson': (u'lbesson@ens-cachan.fr', None),#u'BF105A8DC75491B9D6EDAC5D01AACDB9C108F8A0', - u'tilquin': (u'tilquin@crans.org', None), - u'pvincent': (u'pvincent@crans.org', None), - u'pommeret': (u'pommeret@crans.org', u'8D9C890BD2B783A052DBE71405504FF0CF875FE1'), - u'lasseri': (u'lasseri@crans.org', u'31EF775095485A1CA4CC7CAAA2A902AE80403321'), - u'moisy-mabille': (u'moisy-mabille@crans.org', None), - u'guiraud': (u'guiraud@crans.org', u'8C8F34952DCBA75CD2963A4C33ECE62B57DA1CD4'), - u'soret': (u'soret@crans.org', u'C244290074A0A4A8C05FCA1ACF25D25F17DA8589'), - u'serrano': (u'serrano@crans.org', u'64ABC0C087EDAA14B79F5F7DEDE22762F030FDC5'), - u'kherouf': (u'kherouf@crans.org', None), - u'baste': (u'baste@crans.org', None), - u'quelennec': (u'quelennec@crans.org', None), - u'grande': (u'grande@crans.org', None), - u'gstalter': (u'gstalter@crans.org', None), - u'duplouy': (u'duplouy@crans.org', None), - u'randazzo': (u'randazzo@crans.org', None), - u'epalle': (u'epalle@crans.org', None), - u'bonaque': (u'bonaque@crans.org', None), - u'kviard': (u'kviard@crans.org', None), - u'delorme': (u'jordy@crans.org', u'E2250729CF7D2C801748D33F8219ECBA6B1DD5EF'), + 'dstan': (u'daniel.stan@crans.org', u'90520CFDE846E7651A1B751FBC9BF8456E1C820B'), + 'legallic': (u'legallic@crans.org', u'4BDD2DC3F10C26B9BC3B0BD93602E1C9A94025B0'), } #: Les variables suivantes sont utilisées pour définir le dictionnaire des #: rôles. RTC = [ - u'samir' - ] + 'dstan' +] #: Liste des usernames des nounous NOUNOUS = RTC + [ - u'blockelet', - u'becue', - u'dstan', - u'chambart', - u'dimino', - u'durand-gasselin', - u'glondu', - u'huber', - u'lagorce', - u'parret-freaud', - u'cauderlier', - u'maioli', - u'iffrig', - u'boilard', - u'legallic', - u'pommeret', - u'serrano', - u'lasseri', - u'delorme', - ] + 'legallic', +] # Autogen: #: Liste des usernames des apprentis APPRENTIS = [ - u'grande', - u'bonaque', - u'moisy-mabille', - u'baste', - u'duplouy', - u'besson', - u'pvincent', - u'quelennec', - u'guiraud', - u'kherouf', - u'randazzo', - u'tilquin', - u'epalle', - u'soret', - u'gstalter', - u'kviard'] +] #: Liste des usernames des membres du CA CA = [ - u'becue', - u'duplouy', - u'epalle', - u'guiraud', - u'lajus', - u'lasseri', - u'lerisson', - u'randazzo', - u'soret', - ] +] #: Liste des trésoriers TRESORERIE = RTC + [ - u'soret', - u'guiraud', - u'randazzo', - ] +] #: Les roles utilisés pour savoir qui a le droit le lire/écrire quoi ROLES = { @@ -153,4 +63,4 @@ ROLES = { "apprentis-w": NOUNOUS, "tresorerie": TRESORERIE, "tresorerie-w": TRESORERIE, - } +} -- GitLab