[utils/ldapcertfs] Programme fuse pour explorer les ceetificats ldap d'une machine
Il y a en gros trois modes : * On n'est pas root, ça monte nos machines avec nos ui et gid * On est root est on ne donne pas de ldap_filter, ça monte les machines correspondant aux ips des interfaces (probablement machine.crans.org et machine.adm.crans.org) * On est root et on donne un ldap_filter, ça monte les machines retournée par une recharche avec ce filtre.
This commit is contained in:
parent
a01ba77a8e
commit
13d839eecd
1 changed files with 421 additions and 0 deletions
421
utils/ldapcertfs.py
Executable file
421
utils/ldapcertfs.py
Executable file
|
@ -0,0 +1,421 @@
|
|||
#!/bin/bash /usr/scripts/python.sh
|
||||
#-*- coding: utf-8 -*-
|
||||
# ----------------------------------------------------------------------------
|
||||
# Copyright (c) 2010, Matteo Bertozzi <theo.bertozzi@gmail.com>
|
||||
# Copyright (c) 2014, Valentin Samir <valentin.samir@crans.org>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the Matteo Bertozzi nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY Matteo Bertozzi ``AS IS'' AND ANY
|
||||
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL Matteo Bertozzi BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
# ----------------------------------------------------------------------------
|
||||
# How to mount the File-System:
|
||||
# python ldapcertfs mnt [ldap_filter]
|
||||
#
|
||||
# How to umount the File-System?
|
||||
# fusermount -u mnt
|
||||
|
||||
import os
|
||||
import sys
|
||||
import ssl
|
||||
import stat
|
||||
import time
|
||||
import fuse
|
||||
import errno
|
||||
import getpass
|
||||
|
||||
from OpenSSL import crypto
|
||||
|
||||
import lc_ldap.shortcuts
|
||||
import gestion.secrets_new as secrets
|
||||
from gestion.gen_confs.populate_sshFingerprint import get_machines
|
||||
# Specify what Fuse API use: 0.2
|
||||
fuse.fuse_python_api = (0, 2)
|
||||
|
||||
import logging
|
||||
LOG_FILENAME = 'ldapcertfs.log'
|
||||
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)
|
||||
|
||||
class Pass:
|
||||
pass
|
||||
|
||||
class Item(object):
|
||||
"""
|
||||
An Item is an Object on Disk, it can be a Directory, File, Symlink, ...
|
||||
"""
|
||||
def __init__(self, mode, uid, gid):
|
||||
# ----------------------------------- Metadata --
|
||||
self.atime = time.time() # time of last acces
|
||||
self.mtime = self.atime # time of last modification
|
||||
self.ctime = self.atime # time of last status change
|
||||
|
||||
self.dev = 0 # device ID (if special file)
|
||||
self.mode = mode # protection and file-type
|
||||
self.uid = uid # user ID of owner
|
||||
self.gid = gid # group ID of owner
|
||||
|
||||
# ------------------------ Extended Attributes --
|
||||
self.xattr = {}
|
||||
|
||||
# --------------------------------------- Data --
|
||||
if stat.S_ISDIR(mode):
|
||||
self.data = set()
|
||||
else:
|
||||
self.data = ''
|
||||
|
||||
def read(self, offset, length):
|
||||
return self.data[offset:offset+length]
|
||||
|
||||
def write(self, offset, data):
|
||||
length = len(data)
|
||||
self.data = self.data[:offset] + data + self.data[offset+length:]
|
||||
return length
|
||||
|
||||
def truncate(self, length):
|
||||
if len(self.data) > length:
|
||||
self.data = self.data[:length]
|
||||
else:
|
||||
self.data += '\x00'# (length - len(self.data))
|
||||
|
||||
def zstat(stat):
|
||||
stat.st_mode = 0
|
||||
stat.st_ino = 0
|
||||
stat.st_dev = 0
|
||||
stat.st_nlink = 2
|
||||
stat.st_uid = 0
|
||||
stat.st_gid = 0
|
||||
stat.st_size = 0
|
||||
stat.st_atime = 0
|
||||
stat.st_mtime = 0
|
||||
stat.st_ctime = 0
|
||||
return stat
|
||||
|
||||
class LdapCertFS(fuse.Fuse):
|
||||
def __init__(self, ldap_filter, nopkey=False, decrypt=False, *args, **kwargs):
|
||||
fuse.Fuse.__init__(self, *args, **kwargs)
|
||||
|
||||
self.uid = os.getuid()
|
||||
self.gid = os.getgid()
|
||||
|
||||
self.nopkey=nopkey
|
||||
self.decrypt = decrypt
|
||||
self.ldap_filter = ldap_filter
|
||||
|
||||
# dictionnnaire CN => certificat pour construire la chaine de certificat
|
||||
# Il n'est utile ici que de renseigner des CN de CA intermédiaires
|
||||
self.chain = {
|
||||
'CAcert Class 3 Root' : open('/etc/ssl/certs/cacert-chain.pem').read(),
|
||||
}
|
||||
# Les fichers certificats que l'on veux créer. fil est une liste représentant
|
||||
# la concaténation des attributs ldap (bien formaté). chain est un joker pour
|
||||
# construire la chaine de certificat
|
||||
self.files = {
|
||||
'crt.pem' : {'file':['certificat'], 'mode':0444},
|
||||
'key.pem' : {'file':['privatekey'], 'mode':0400},
|
||||
'csr.pem' : {'file':['csr'], 'mode':0444},
|
||||
'chain.pem' : {'file':['chain'], 'mode':0444},
|
||||
'key_cert_chain.pem' : {'file':['privatekey', 'certificat', 'chain'], 'mode':0400},
|
||||
'cert_chain.pem' : {'file':['certificat', 'chain'], 'mode':0400},
|
||||
}
|
||||
|
||||
self._storage = {'/': Item(0755 | stat.S_IFDIR, self.uid, self.gid)}
|
||||
|
||||
self.build_tree()
|
||||
|
||||
def build_tree(self):
|
||||
"""Construit l'arborescence du système de fichier"""
|
||||
self._storage = {'/': Item(0755 | stat.S_IFDIR, self.uid, self.gid)}
|
||||
self.passphrase = {}
|
||||
conn = lc_ldap.shortcuts.lc_ldap_readonly()
|
||||
if self.ldap_filter == 'self':
|
||||
machines = conn.search(u'mid=*', dn=conn.dn, scope=1)
|
||||
elif self.ldap_filter:
|
||||
machines = conn.search("(&(%s)(mid=*))" % self.ldap_filter, sizelimit=8000)
|
||||
else:
|
||||
machines = get_machines()
|
||||
|
||||
for machine in machines:
|
||||
if not machine.certificats():
|
||||
continue
|
||||
if 'aid' in machine.dn and "cransAccount" in machine.proprio()['objectClass']:
|
||||
uid = int(machine.proprio()["uidNumber"][0])
|
||||
gid = int(machine.proprio()["gidNumber"][0])
|
||||
else:
|
||||
uid = 0
|
||||
gid = 0
|
||||
mpath = "/%s" % machine['host'][0]
|
||||
self._storage[mpath]=Item(0755 | stat.S_IFDIR, uid, gid)
|
||||
self._add_to_parent_dir(mpath)
|
||||
for cert in machine.certificats():
|
||||
path = '%s/xid=%s' % (mpath, cert["xid"][0])
|
||||
self._storage[path]=Item(0755 | stat.S_IFDIR, uid, gid)
|
||||
self._add_to_parent_dir(path)
|
||||
for file, file_data in self.files.items():
|
||||
data = self._file_data(machine, cert, file)
|
||||
if data:
|
||||
fpath = '%s/%s' % (path, file)
|
||||
self._storage[fpath]=Item(file_data['mode'] | stat.S_IFREG, uid, gid)
|
||||
self._storage[fpath].data = data
|
||||
self._add_to_parent_dir(fpath)
|
||||
|
||||
for machine in machines:
|
||||
if not machine.certificats():
|
||||
continue
|
||||
mpath = "/%s" % machine['host'][0]
|
||||
for cert in machine.certificats():
|
||||
path = 'xid=%s' % (cert["xid"][0])
|
||||
if cert['info']:
|
||||
item = Item(0644 | stat.S_IFLNK, self.uid, self.gid)
|
||||
item.data = path
|
||||
newpath = '%s/%s' % (mpath, cert['info'][0])
|
||||
indice = 1
|
||||
while newpath in self._storage:
|
||||
newpath = '/%s (%s)' % (cert['info'][0], indice)
|
||||
indice+=1
|
||||
self._storage[newpath] = item
|
||||
self._add_to_parent_dir(newpath)
|
||||
|
||||
|
||||
def _file_data(self, machine, cert, file):
|
||||
"""Construit le contenue du fichier file utilisant le certificat cert de machine"""
|
||||
data = ""
|
||||
for dtype in self.files[file]['file']:
|
||||
if dtype == "chain":
|
||||
if cert['issuerCN'][0] in self.chain:
|
||||
data+=self.chain[str(cert['issuerCN'][0])]
|
||||
else:
|
||||
return None
|
||||
elif dtype == "certificat":
|
||||
data+=ssl.DER_cert_to_PEM_cert(str(cert['certificat'][0]))
|
||||
elif dtype == "privatekey":
|
||||
if "privateKey" in cert['objectClass'] and cert['privatekey'] and not self.nopkey:
|
||||
if self.decrypt:
|
||||
while True:
|
||||
if not cert['xid'][0] in self.passphrase:
|
||||
if cert['encrypted'][0]:
|
||||
if "machineCrans" in machine["objectClass"]:
|
||||
passphrase=secrets.get('privatekey_passphrase')
|
||||
else:
|
||||
print "Passphrase de la clef %s de %s" % (cert['info'][0] if cert['info'] else ('xid=%s' % cert["xid"][0]), machine['host'][0])
|
||||
try:
|
||||
passphrase = getpass.getpass()
|
||||
except KeyboardInterrupt:
|
||||
print "On passe la clef"
|
||||
self.passphrase[cert['xid'][0]]=Pass()
|
||||
return None
|
||||
else:
|
||||
passphrase=None
|
||||
self.passphrase[cert['xid'][0]]=passphrase
|
||||
elif isinstance(self.passphrase[cert['xid'][0]], Pass):
|
||||
return None
|
||||
try:
|
||||
if self.passphrase[cert['xid'][0]]:
|
||||
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, str(cert['privatekey'][0]), self.passphrase[cert['xid'][0]])
|
||||
else:
|
||||
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, str(cert['privatekey'][0]), "")
|
||||
break
|
||||
except crypto.Error:
|
||||
print "mauvais mot de pass"
|
||||
del(self.passphrase[cert['xid'][0]])
|
||||
data+=str(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
else:
|
||||
data+=str(cert['privatekey'][0])
|
||||
else:
|
||||
return None
|
||||
elif cert[dtype]:
|
||||
data+=str(cert[dtype][0])
|
||||
else:
|
||||
return None
|
||||
return data
|
||||
|
||||
# --- Metadata -----------------------------------------------------------
|
||||
def getattr(self, path):
|
||||
if not path in self._storage:
|
||||
return -errno.ENOENT
|
||||
|
||||
# Lookup Item and fill the stat struct
|
||||
item = self._storage[path]
|
||||
st = zstat(fuse.Stat())
|
||||
st.st_mode = item.mode
|
||||
st.st_uid = item.uid
|
||||
st.st_gid = item.gid
|
||||
st.st_dev = item.dev
|
||||
st.st_atime = item.atime
|
||||
st.st_mtime = item.mtime
|
||||
st.st_ctime = item.ctime
|
||||
st.st_size = len(item.data)
|
||||
return st
|
||||
|
||||
def chmod(self, path, mode):
|
||||
return -errno.EPERM
|
||||
|
||||
def chown(self, path, uid, gid):
|
||||
return -errno.EPERM
|
||||
|
||||
def utime(self, path, times):
|
||||
item = self._storage[path]
|
||||
item.ctime = item.mtime = times[0]
|
||||
|
||||
# --- Namespace ----------------------------------------------------------
|
||||
def unlink(self, path):
|
||||
return -errno.EPERM
|
||||
|
||||
def rename(self, oldpath, newpath):
|
||||
return -errno.EPERM
|
||||
|
||||
# --- Links --------------------------------------------------------------
|
||||
def symlink(self, path, newpath):
|
||||
return -errno.EPERM
|
||||
|
||||
def readlink(self, path):
|
||||
return self._storage[path].data
|
||||
|
||||
# --- Extra Attributes ---------------------------------------------------
|
||||
def setxattr(self, path, name, value, flags):
|
||||
return -errno.EPERM
|
||||
|
||||
def getxattr(self, path, name, size):
|
||||
value = self._storage[path].xattr.get(name, '')
|
||||
if size == 0: # We are asked for size of the value
|
||||
return len(value)
|
||||
return value
|
||||
|
||||
def listxattr(self, path, size):
|
||||
attrs = self._storage[path].xattr.keys()
|
||||
if size == 0:
|
||||
return len(attrs) + len(''.join(attrs))
|
||||
return attrs
|
||||
|
||||
def removexattr(self, path, name):
|
||||
return -errno.EPERM
|
||||
|
||||
# --- Files --------------------------------------------------------------
|
||||
def mknod(self, path, mode, dev):
|
||||
return -errno.EPERM
|
||||
|
||||
def create(self, path, flags, mode):
|
||||
return -errno.EPERM
|
||||
|
||||
def truncate(self, path, len):
|
||||
return -errno.EPERM
|
||||
|
||||
def read(self, path, size, offset):
|
||||
return self._storage[path].read(offset, size)
|
||||
|
||||
def write(self, path, buf, offset):
|
||||
return -errno.EPERM
|
||||
|
||||
# --- Directories --------------------------------------------------------
|
||||
def mkdir(self, path, mode):
|
||||
return -errno.EPERM
|
||||
|
||||
def rmdir(self, path):
|
||||
return -errno.EPERM
|
||||
|
||||
def readdir(self, path, offset):
|
||||
dir_items = self._storage[path].data
|
||||
for item in dir_items:
|
||||
yield fuse.Direntry(item)
|
||||
|
||||
def _add_to_parent_dir(self, path):
|
||||
parent_path = os.path.dirname(path)
|
||||
filename = os.path.basename(path)
|
||||
self._storage[parent_path].data.add(filename)
|
||||
|
||||
def _remove_from_parent_dir(self, path):
|
||||
parent_path = os.path.dirname(path)
|
||||
filename = os.path.basename(path)
|
||||
self._storage[parent_path].data.remove(filename)
|
||||
|
||||
def main(usage):
|
||||
# valeurs par defaut des options
|
||||
decrypt=False
|
||||
nopkey=False
|
||||
ldap_filter = 'self'
|
||||
|
||||
# Récupération de l'option decrypt
|
||||
if '--decrypt' in sys.argv[2:]:
|
||||
decrypt=True
|
||||
del(sys.argv[sys.argv.index('--decrypt')])
|
||||
|
||||
# Récupération de l'option nopkey
|
||||
if '--nopkey' in sys.argv[2:]:
|
||||
nopkey=True
|
||||
del(sys.argv[sys.argv.index('--nopkey')])
|
||||
|
||||
# Récupération de l'option ldap-filter
|
||||
try:
|
||||
ldap_filter = unicode(sys.argv[sys.argv.index('--ldap-filter') + 1])
|
||||
if os.getuid() != 0:
|
||||
raise EnvironmentError("Il faut être root pour choisir le filtre ldap")
|
||||
del(sys.argv[sys.argv.index('--ldap-filter') + 1])
|
||||
except (IndexError, ValueError):
|
||||
if os.getuid() == 0:
|
||||
ldap_filter = None
|
||||
finally:
|
||||
try: del(sys.argv[sys.argv.index('--ldap-filter')])
|
||||
except (IndexError, ValueError): pass
|
||||
|
||||
# Vérification que le point de montage est bien un dossier
|
||||
end_option = False
|
||||
for item in sys.argv[1:]:
|
||||
if end_option or not item.startswith('-'):
|
||||
if not os.path.isdir(item):
|
||||
raise EnvironmentError("%s is not a dir" % item)
|
||||
if item == '--':
|
||||
end_option=True
|
||||
|
||||
# Instanciation du FS
|
||||
server = LdapCertFS(ldap_filter, nopkey=nopkey, decrypt=decrypt, version="%prog " + fuse.__version__,
|
||||
usage=usage,
|
||||
dash_s_do='setsingle')
|
||||
|
||||
server.parse(errex=1)
|
||||
server.main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
usage="""
|
||||
LdapCertFS - Ldap Certificate File System
|
||||
Les obtions spécifiques sont :
|
||||
* --decrypt : pour déchiffrer les clefs privées (un prompt demande le
|
||||
mot de passe si nécessaire.
|
||||
* --nopkey : exclure les clefs privées lors de la construction du FS.
|
||||
* --ldap-filter filtre : selectionner les machines à utiliser pour
|
||||
construire le FS avec un filtre ldap. Nécéssite les droits root.
|
||||
|
||||
Si --ldap-filter n'est pas spécifier :
|
||||
* Si le programme est appelé par root, on utilises les machines
|
||||
correspondants aux ips des interfaces de la machine physique.
|
||||
* Sinon, on utilise les machines de l'utilisateur dans la base
|
||||
de donnée.
|
||||
|
||||
""" + fuse.Fuse.fusage
|
||||
# On force à fornir au moint un paramètre (il faut au moins un point de montage)
|
||||
if len(sys.argv)<2:
|
||||
sys.stderr.write("%s\n" % usage.replace('%prog', sys.argv[0]))
|
||||
sys.exit(1)
|
||||
# On appel main et on affiche les exceptions EnvironmentError
|
||||
try:
|
||||
main(usage)
|
||||
except (EnvironmentError) as e:
|
||||
sys.stderr.write("Error: %s\n" % e)
|
||||
sys.exit(1)
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue