init
This commit is contained in:
commit
a3d0fee0df
2 changed files with 333 additions and 0 deletions
177
cranspasswords-server.py
Executable file
177
cranspasswords-server.py
Executable file
|
@ -0,0 +1,177 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
"""cranspasswords-server.py: Serveur pour cranspasswords"""
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import pwd
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
MYUID = pwd.getpwuid(os.getuid())[0]
|
||||||
|
if MYUID == 'root':
|
||||||
|
MYUID = os.environ['SUDO_USER']
|
||||||
|
|
||||||
|
KEYS = {
|
||||||
|
"aza-vallina": ("Damien.Aza-Vallina@crans.org", None),
|
||||||
|
"dandrimont": ("nicolas.dandrimont@crans.org", "66475AAF"),
|
||||||
|
"nicolasd": (None, None),
|
||||||
|
"blockelet": ("blockelet@crans.org", "AF087A52"),
|
||||||
|
"chambart": ("pierre.chambart@crans.org", "F2530FCE"),
|
||||||
|
"dimino": ("jdimino@dptinfo.ens-cachan.fr", "2127F85A"),
|
||||||
|
"durand-gasselin": ("adg@crans.org", "8E96ACDA"),
|
||||||
|
"glondu": ("Stephane.Glondu@crans.org", "49881AD3"),
|
||||||
|
"huber": ("olivier.huber@crans.org", "E0DCF376"),
|
||||||
|
"lagorce": ("xavier.lagorce@crans.org", "0BF3708E"),
|
||||||
|
"parret-freaud": ("parret-freaud@crans.org", "7D980513"),
|
||||||
|
"tvincent": ("vincent.thomas@crans.org", "C5C4ACC0"),
|
||||||
|
}
|
||||||
|
|
||||||
|
ROLES = {
|
||||||
|
"bureau": [
|
||||||
|
"aza-vallina",
|
||||||
|
],
|
||||||
|
"ca": [
|
||||||
|
"aza-vallina",
|
||||||
|
"blockelet",
|
||||||
|
"durand-gasselin",
|
||||||
|
"lagorce",
|
||||||
|
],
|
||||||
|
"rtc": [
|
||||||
|
"dandrimont",
|
||||||
|
"nicolasd",
|
||||||
|
],
|
||||||
|
"nounou": [
|
||||||
|
"blockelet",
|
||||||
|
"chambart",
|
||||||
|
"dandrimont",
|
||||||
|
"dimino",
|
||||||
|
"durand-gasselin",
|
||||||
|
"glondu",
|
||||||
|
"huber",
|
||||||
|
"lagorce",
|
||||||
|
"parret-freaud",
|
||||||
|
"tvincent",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
MYDIR = '/var/local/cranspasswords/'
|
||||||
|
STORE = MYDIR + 'store/'
|
||||||
|
|
||||||
|
def validate(roles):
|
||||||
|
"""Valide que l'appelant appartient bien aux roles précisés"""
|
||||||
|
for role in roles:
|
||||||
|
if MYUID in ROLES[role]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getpath(filename):
|
||||||
|
"""Récupère le chemin du fichier `filename'"""
|
||||||
|
return os.path.join(STORE, '%s.json' % filename)
|
||||||
|
|
||||||
|
def writefile(filename, contents):
|
||||||
|
"""Écrit le fichier de manière sécure"""
|
||||||
|
os.umask(0077)
|
||||||
|
f = open(filename, 'w')
|
||||||
|
f.write(contents)
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def listroles():
|
||||||
|
"""Liste des roles existant et de leurs membres"""
|
||||||
|
return ROLES
|
||||||
|
|
||||||
|
def listkeys():
|
||||||
|
"""Liste les uid et les clés correspondantes"""
|
||||||
|
return KEYS
|
||||||
|
|
||||||
|
def listfiles():
|
||||||
|
"""Liste les fichiers dans l'espace de stockage, et les roles qui peuvent y accéder"""
|
||||||
|
os.chdir(STORE)
|
||||||
|
|
||||||
|
filenames = glob.glob('*.json')
|
||||||
|
|
||||||
|
files = {}
|
||||||
|
|
||||||
|
for filename in filenames:
|
||||||
|
file_dict = json.loads(open(filename).read())
|
||||||
|
files[filename[:-5]] = file_dict["roles"]
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
def getfile(filename):
|
||||||
|
"""Récupère le fichier `filename'"""
|
||||||
|
|
||||||
|
filepath = getpath(filename)
|
||||||
|
try:
|
||||||
|
return json.loads(open(filepath).read())
|
||||||
|
except IOError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def putfile(filename):
|
||||||
|
"""Écrit le fichier `filename' avec les données reçues sur stdin."""
|
||||||
|
|
||||||
|
filepath = getpath(filename)
|
||||||
|
|
||||||
|
stdin = sys.stdin.read()
|
||||||
|
parsed_stdin = json.loads(stdin)
|
||||||
|
|
||||||
|
try:
|
||||||
|
roles = parsed_stdin['roles']
|
||||||
|
contents = parsed_stdin['contents']
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
oldroles = getfile(filename)['roles']
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if not validate(oldroles):
|
||||||
|
return False
|
||||||
|
|
||||||
|
writefile(filepath, json.dumps({'roles': roles, 'contents': contents}))
|
||||||
|
return True
|
||||||
|
|
||||||
|
def rmfile(filename):
|
||||||
|
"""Supprime le fichier filename après avoir vérifié les droits sur le fichier"""
|
||||||
|
try:
|
||||||
|
roles = getfile(filename)['roles']
|
||||||
|
except TypeError:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if validate(roles):
|
||||||
|
os.remove(getpath(filename))
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
if len(argv) not in [1, 2]:
|
||||||
|
sys.exit(1)
|
||||||
|
command = argv[0]
|
||||||
|
filename = None
|
||||||
|
try:
|
||||||
|
filename = argv[1]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if command == "listroles":
|
||||||
|
print json.dumps(listroles())
|
||||||
|
elif command == "listkeys":
|
||||||
|
print json.dumps(listkeys())
|
||||||
|
elif command == "listfiles":
|
||||||
|
print json.dumps(listfiles())
|
||||||
|
else:
|
||||||
|
if not filename:
|
||||||
|
sys.exit(1)
|
||||||
|
if command == "getfile":
|
||||||
|
print json.dumps(getfile(filename))
|
||||||
|
elif command == "putfile":
|
||||||
|
print json.dumps(putfile(filename))
|
||||||
|
elif command == "rmfile":
|
||||||
|
print json.dumps(rmfile(filename))
|
||||||
|
else:
|
||||||
|
sys.exit(1)
|
||||||
|
|
156
cranspasswords.py
Executable file
156
cranspasswords.py
Executable file
|
@ -0,0 +1,156 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
"""cranspasswords: gestion des mots de passe du Cr@ns"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
|
||||||
|
######
|
||||||
|
## GPG Definitions
|
||||||
|
|
||||||
|
GPG = '/usr/bin/gpg'
|
||||||
|
GPG_ARGS = {
|
||||||
|
'decrypt': ['-d'],
|
||||||
|
'encrypt': ['--armor', '-es'],
|
||||||
|
'fingerprint': ['--fingerprint'],
|
||||||
|
'receive-keys': ['--recv-keys'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def gpg(command, args = None):
|
||||||
|
"""Lance gpg pour la commande donnée avec les arguments
|
||||||
|
donnés. Renvoie son entrée standard et sa sortie standard."""
|
||||||
|
full_command = [GPG]
|
||||||
|
full_command.extend(GPG_ARGS[command])
|
||||||
|
if args:
|
||||||
|
full_command.extend(args)
|
||||||
|
proc = subprocess.Popen(full_command,
|
||||||
|
stdin = subprocess.PIPE,
|
||||||
|
stdout = subprocess.PIPE,
|
||||||
|
stderr = sys.stderr,
|
||||||
|
close_fds = True)
|
||||||
|
return proc.stdin, proc.stdout
|
||||||
|
|
||||||
|
######
|
||||||
|
## Remote commands
|
||||||
|
|
||||||
|
SSH = '/usr/bin/ssh'
|
||||||
|
SSH_HOST = 'localhost'
|
||||||
|
REMOTE_COMMAND = ['/home/nicolasd/cranspasswords-server.py']
|
||||||
|
|
||||||
|
def ssh(command, arg = None):
|
||||||
|
"""Lance ssh avec les arguments donnés. Renvoie son entrée
|
||||||
|
standard et sa sortie standard."""
|
||||||
|
full_command = [SSH, SSH_HOST]
|
||||||
|
full_command.extend(REMOTE_COMMAND)
|
||||||
|
full_command.append(command)
|
||||||
|
if arg:
|
||||||
|
full_command.append(arg)
|
||||||
|
proc = subprocess.Popen(full_command,
|
||||||
|
stdin = subprocess.PIPE,
|
||||||
|
stdout = subprocess.PIPE,
|
||||||
|
stderr = sys.stderr,
|
||||||
|
close_fds = True)
|
||||||
|
return proc.stdin, proc.stdout
|
||||||
|
|
||||||
|
def remote_command(command, arg = None, stdin_contents = None):
|
||||||
|
"""Exécute la commande distante, et retourne la sortie de cette
|
||||||
|
commande"""
|
||||||
|
|
||||||
|
sshin, sshout = ssh(command, arg)
|
||||||
|
if stdin_contents:
|
||||||
|
sshin.write(json.dumps(stdin_contents))
|
||||||
|
sshin.close()
|
||||||
|
return json.loads(sshout.read())
|
||||||
|
|
||||||
|
def all_keys():
|
||||||
|
"""Récupère les clés du serveur distant"""
|
||||||
|
return remote_command("listkeys")
|
||||||
|
|
||||||
|
def all_roles():
|
||||||
|
"""Récupère les roles du serveur distant"""
|
||||||
|
return remote_command("listroles")
|
||||||
|
|
||||||
|
def all_files():
|
||||||
|
"""Récupère les fichiers du serveur distant"""
|
||||||
|
return remote_command("listfiles")
|
||||||
|
|
||||||
|
def get_file(filename):
|
||||||
|
"""Récupère le contenu du fichier distant"""
|
||||||
|
return remote_command("getfile", filename)
|
||||||
|
|
||||||
|
def put_file(filename, roles, contents):
|
||||||
|
"""Dépose le fichier sur le serveur distant"""
|
||||||
|
return remote_command("putfile", filename, {'roles': roles,
|
||||||
|
'contents': contents})
|
||||||
|
def rm_file(filename):
|
||||||
|
"""Supprime le fichier sur le serveur distant"""
|
||||||
|
return remote_command("rmfile", filename)
|
||||||
|
|
||||||
|
######
|
||||||
|
## Local commands
|
||||||
|
|
||||||
|
def update_keys():
|
||||||
|
"""Met à jour les clés existantes"""
|
||||||
|
|
||||||
|
keys = all_keys()
|
||||||
|
|
||||||
|
_, stdout = gpg("receive-keys", [key for _, key in keys.values() if key])
|
||||||
|
return stdout.read()
|
||||||
|
|
||||||
|
def check_keys():
|
||||||
|
"""Vérifie les clés existantes"""
|
||||||
|
|
||||||
|
keys = all_keys()
|
||||||
|
|
||||||
|
for mail, key in keys.values():
|
||||||
|
if key:
|
||||||
|
_, stdout = gpg("fingerprint", [key])
|
||||||
|
if "<%s>" % mail.lower() not in stdout.read().lower():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def encrypt(roles, contents):
|
||||||
|
"""Chiffre le contenu pour les roles donnés"""
|
||||||
|
|
||||||
|
recipients = set()
|
||||||
|
allroles = all_roles()
|
||||||
|
allkeys = all_keys()
|
||||||
|
|
||||||
|
email_recipients = []
|
||||||
|
for role in roles:
|
||||||
|
for recipient in allroles[role]:
|
||||||
|
recipients.add(recipient)
|
||||||
|
for recipient in recipients:
|
||||||
|
email, key = allkeys[recipient]
|
||||||
|
if key:
|
||||||
|
email_recipients.append("-r")
|
||||||
|
email_recipients.append(email)
|
||||||
|
|
||||||
|
stdin, stdout = gpg("encrypt", email_recipients)
|
||||||
|
stdin.write(contents)
|
||||||
|
stdin.close()
|
||||||
|
return stdout.read()
|
||||||
|
|
||||||
|
def decrypt(contents):
|
||||||
|
"""Déchiffre le contenu"""
|
||||||
|
stdin, stdout = gpg("decrypt")
|
||||||
|
stdin.write(contents)
|
||||||
|
stdin.close()
|
||||||
|
return stdout.read()
|
||||||
|
|
||||||
|
def put_password(name, roles, contents):
|
||||||
|
"""Dépose le mot de passe après l'avoir chiffré pour les
|
||||||
|
destinataires donnés"""
|
||||||
|
enc_pwd = encrypt(roles, contents)
|
||||||
|
return put_file(name, roles, enc_pwd)
|
||||||
|
|
||||||
|
def get_password(name):
|
||||||
|
"""Récupère le mot de passe donné par name"""
|
||||||
|
remotefile = get_file(name)
|
||||||
|
return decrypt(remotefile['contents'])
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue