Doc sphinx-like serveur

This commit is contained in:
Vincent Le Gallic 2013-04-12 20:57:48 +02:00
parent 44e7b1a635
commit 167e988782
4 changed files with 43 additions and 41 deletions

View file

@ -1,8 +1,12 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
"""Configuration du client cranspasswords"""
import os import os
#: Serveurs distants utilisables,
#: avec la commande distante à exécuter et l'username sur le serveur
servers = { servers = {
'default': { 'default': {
'server_cmd': ['/usr/bin/ssh', 'vert.adm.crans.org',\ 'server_cmd': ['/usr/bin/ssh', 'vert.adm.crans.org',\
@ -17,4 +21,3 @@ servers = {
# n'ayant pas le même login sur leur pc # n'ayant pas le même login sur leur pc
} }
} }

1
server
View file

@ -1,2 +1,3 @@
#!/bin/bash #!/bin/bash
# sudo-wrapper pour exécuter cranspasswords côté serveur
sudo /root/cranspasswords/server.py $* sudo /root/cranspasswords/server.py $*

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
"""cranspasswords-server.py: Serveur pour cranspasswords"""
"""Serveur pour cranspasswords"""
import glob import glob
import os import os
@ -18,22 +19,23 @@ MYUID = pwd.getpwuid(os.getuid())[0]
if MYUID == 'root': if MYUID == 'root':
MYUID = os.environ['SUDO_USER'] MYUID = os.environ['SUDO_USER']
def validate(roles,mode='r'): def validate(roles, mode='r'):
"""Valide que l'appelant appartient bien aux roles précisés """Vérifie que l'appelant appartient bien aux roles précisés
Si mode mode='w', recherche un rôle en écriture Si mode mode='w', recherche un rôle en écriture
""" """
for role in roles: for role in roles:
if mode == 'w': role+='-w' if mode == 'w':
role += '-w'
if ROLES.has_key(role) and MYUID in ROLES[role]: if ROLES.has_key(role) and MYUID in ROLES[role]:
return True return True
return False return False
def getpath(filename,backup=False): def getpath(filename, backup=False):
"""Récupère le chemin du fichier `filename'""" """Récupère le chemin du fichier ``filename``"""
return os.path.join(STORE, '%s.%s' % (filename,'bak' if backup else 'json')) return os.path.join(STORE, '%s.%s' % (filename, 'bak' if backup else 'json'))
def writefile(filename, contents): def writefile(filename, contents):
"""Écrit le fichier de manière sécure""" """Écrit le fichier avec les bons droits UNIX"""
os.umask(0077) os.umask(0077)
f = open(filename, 'w') f = open(filename, 'w')
f.write(contents) f.write(contents)
@ -44,26 +46,22 @@ def listroles():
return ROLES return ROLES
def listkeys(): def listkeys():
"""Liste les uid et les clés correspondantes""" """Liste les usernames et les (mail, fingerprint) correspondants"""
return KEYS return KEYS
def listfiles(): def listfiles():
"""Liste les fichiers dans l'espace de stockage, et les roles qui peuvent y accéder""" """Liste les fichiers dans l'espace de stockage, et les roles qui peuvent y accéder"""
os.chdir(STORE) os.chdir(STORE)
filenames = glob.glob('*.json')
files = {}
filenames = glob.glob('*.json')
files = {}
for filename in filenames: for filename in filenames:
file_dict = json.loads(open(filename).read()) file_dict = json.loads(open(filename).read())
files[filename[:-5]] = file_dict["roles"] files[filename[:-5]] = file_dict["roles"]
return files return files
def getfile(filename): def getfile(filename):
"""Récupère le fichier `filename'""" """Récupère le fichier ``filename``"""
filepath = getpath(filename) filepath = getpath(filename)
try: try:
obj = json.loads(open(filepath).read()) obj = json.loads(open(filepath).read())
@ -75,19 +73,16 @@ def getfile(filename):
def putfile(filename): def putfile(filename):
"""Écrit le fichier `filename' avec les données reçues sur stdin.""" """Écrit le fichier ``filename`` avec les données reçues sur stdin."""
filepath = getpath(filename) filepath = getpath(filename)
stdin = sys.stdin.read() stdin = sys.stdin.read()
parsed_stdin = json.loads(stdin) parsed_stdin = json.loads(stdin)
try: try:
roles = parsed_stdin['roles'] roles = parsed_stdin['roles']
contents = parsed_stdin['contents'] contents = parsed_stdin['contents']
except KeyError: except KeyError:
return False return False
try: try:
old = getfile(filename) old = getfile(filename)
oldroles = old['roles'] oldroles = old['roles']
@ -98,9 +93,9 @@ def putfile(filename):
if not validate(oldroles,'w'): if not validate(oldroles,'w'):
return False return False
notification("Modification de %s" % filename,\ notification("Modification de %s" % filename,
"Le fichier %s a été modifié par %s." %\ "Le fichier %s a été modifié par %s." % (filename, MYUID),
(filename,MYUID),filename,old) filename, old)
writefile(filepath, json.dumps({'roles': roles, 'contents': contents})) writefile(filepath, json.dumps({'roles': roles, 'contents': contents}))
@ -123,22 +118,22 @@ def rmfile(filename):
return False return False
return True return True
def notification(subject,corps,fname,old): def backup(fname, old):
back = open(getpath(fname,True),'a') """Backupe l'ancienne version du fichier"""
back = open(getpath(fname, backup=True), 'a')
back.write(json.dumps(old)) back.write(json.dumps(old))
back.write('\n') back.write('\n')
back.write('* %s: %s\n' % (str(datetime.datetime.now()),corps)) back.write('* %s: %s\n' % (str(datetime.datetime.now()),corps))
back.close() back.close()
# Puis envoi du message def notification(subject, corps, fname, old):
"""Envoie par mail une notification de changement de fichier"""
conn = smtplib.SMTP('localhost') conn = smtplib.SMTP('localhost')
frommail = CRANSP_MAIL frommail = CRANSP_MAIL
tomail = DEST_MAIL tomail = DEST_MAIL
msg = MIMEMultipart(_charset="utf-8") msg = MIMEMultipart(_charset="utf-8")
msg['Subject'] = subject msg['Subject'] = subject
msg['X-Mailer'] = "cranspasswords" msg['X-Mailer'] = "cranspasswords"
# me == the sender's email address
# family = the list of all recipients' email addresses
msg['From'] = CRANSP_MAIL msg['From'] = CRANSP_MAIL
msg['To'] = DEST_MAIL msg['To'] = DEST_MAIL
msg.preamble = "cranspasswords report" msg.preamble = "cranspasswords report"
@ -167,7 +162,7 @@ if __name__ == "__main__":
filename = argv[1] filename = argv[1]
except IndexError: except IndexError:
pass pass
if command == "listroles": if command == "listroles":
print json.dumps(listroles()) print json.dumps(listroles())
elif command == "listkeys": elif command == "listkeys":
@ -185,4 +180,3 @@ if __name__ == "__main__":
print json.dumps(rmfile(filename)) print json.dumps(rmfile(filename))
else: else:
sys.exit(1) sys.exit(1)

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
""" Configuration Serveur de cranspasswords. """ Configuration Serveur de cranspasswords.
Sont définis ici les utilisateurs et les rôles associés. Sont définis ici les utilisateurs et les rôles associés.
Ce fichier est donné à titre d'exemple, mais n'est PAS Ce fichier est donné à titre d'exemple, mais n'est PAS
@ -7,19 +8,19 @@ utilisé lorsque fonctionnement en mode client.
Dans le futur, sera remplacé par une connexion ldap. Dans le futur, sera remplacé par une connexion ldap.
""" """
#: Répertoire de stockage des mots de passe
STORE = '/root/cranspasswords/db/' STORE = '/root/cranspasswords/db/'
""" Répertoire de stockage """
#: Ce serveur est-il read-only (on ne peut pas y modifier les mots de passe)
READONLY = False READONLY = False
""" Ce serveur est-il read-only (on ne peut pas y modifier les mots de passe) """
#: Expéditeur du mail de notification
CRANSP_MAIL = "cranspasswords <root@crans.org>" CRANSP_MAIL = "cranspasswords <root@crans.org>"
""" Expéditeur du mail de notification """
#: Destinataire du mail de notification
DEST_MAIL = "root@crans.org" DEST_MAIL = "root@crans.org"
""" Destinataire du mail de notification """
#: Mapping des utilisateurs et de leurs (mail, fingerprint GPG)
KEYS = { KEYS = {
'aza-vallina': ('Damien.Aza-Vallina@crans.org', None), 'aza-vallina': ('Damien.Aza-Vallina@crans.org', None),
'becue': ('becue@crans.org', '9AE04D986400E3B67528F4930D442664194974E2'), 'becue': ('becue@crans.org', '9AE04D986400E3B67528F4930D442664194974E2'),
@ -62,11 +63,12 @@ KEYS = {
'kviard': ('kviard@crans.org', None) 'kviard': ('kviard@crans.org', None)
} }
# Les variables suivantes sont utilisées pour définir le dictionnaire des #: Les variables suivantes sont utilisées pour définir le dictionnaire des
# rôles. #: rôles.
RTC=[ RTC=[
"iffrig" "iffrig"
] ]
#: Liste des usernames des nounous
NOUNOUS=RTC+[ NOUNOUS=RTC+[
"blockelet", "blockelet",
"becue", "becue",
@ -86,12 +88,14 @@ NOUNOUS=RTC+[
] ]
# Autogen: # Autogen:
#: Liste des usernames des apprentis
APPRENTIS=['grande', 'bonaque', 'moisy-mabille', 'baste', 'duplouy', 'besson', 'pvincent', 'quelennec', 'pommeret', 'guiraud', 'serrano', 'kherouf', 'randazzo', 'tilquin', 'lasseri', 'epalle', 'soret', 'gstalter', 'kviard'] APPRENTIS=['grande', 'bonaque', 'moisy-mabille', 'baste', 'duplouy', 'besson', 'pvincent', 'quelennec', 'pommeret', 'guiraud', 'serrano', 'kherouf', 'randazzo', 'tilquin', 'lasseri', 'epalle', 'soret', 'gstalter', 'kviard']
#: Liste des usernames des membres du CA
CA=[ CA=[
] ]
## Les vrais rôles ! #: Les roles utilisés pour savoir qui a le droit le lire/écrire quoi
ROLES = { ROLES = {
"ca": CA, "ca": CA,
"ca-w": CA, "ca-w": CA,