From 40b834fa788c5ce1704e92fed1a19462655cc214 Mon Sep 17 00:00:00 2001 From: John Giannelos Date: Mon, 22 Oct 2012 16:57:56 +0300 Subject: [PATCH] Draft VOMS integration using voms_helper lib --- snfOCCI/voms/__init__.py | 132 +++++++++++++++++++++++++++++++++++++++++++ snfOCCI/voms/voms_helper.py | 108 +++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 snfOCCI/voms/__init__.py create mode 100644 snfOCCI/voms/voms_helper.py diff --git a/snfOCCI/voms/__init__.py b/snfOCCI/voms/__init__.py new file mode 100644 index 0000000..238ebf4 --- /dev/null +++ b/snfOCCI/voms/__init__.py @@ -0,0 +1,132 @@ +import collections +import commands +import ctypes +import os +import tempfile + +import M2Crypto + +import snfOCCI.config +import voms_helper + +SSL_CLIENT_S_DN_ENV = "SSL_CLIENT_S_DN" +SSL_CLIENT_CERT_ENV = "SSL_CLIENT_CERT" +SSL_CLIENT_CERT_CHAIN_0_ENV = "SSL_CLIENT_CERT_CHAIN_0" + + +class VomsError(exception.Error): + """Voms credential management error""" + + errors = { + 0: ('none', None), + 1: ('nosocket', 'Socket problem'), + 2: ('noident', 'Cannot identify itself (certificate problem)'), + 3: ('comm', 'Server problem'), + 4: ('param', 'Wrong parameters'), + 5: ('noext', 'VOMS extension missing'), + 6: ('noinit', 'Initialization error'), + 7: ('time', 'Error in time checking'), + 8: ('idcheck', 'User data in extension different from the real'), + 9: ('extrainfo', 'VO name and URI missing'), + 10: ('format', 'Wrong data format'), + 11: ('nodata', 'Empty extension'), + 12: ('parse', 'Parse error'), + 13: ('dir', 'Directory error'), + 14: ('sign', 'Signature error'), + 15: ('server', 'Unidentifiable VOMS server'), + 16: ('mem', 'Memory problems'), + 17: ('verify', 'Generic verification error'), + 18: ('type', 'Returned data of unknown type'), + 19: ('order', 'Ordering different than required'), + 20: ('servercode', 'Error from the server'), + 21: ('notavail', 'Method not available'), + } + + http_codes = { + 5: (400, "Bad Request"), + 11: (400, "Bad Request"), + 14: (401, "Not Authorized"), + } + + +def _get_cert_chain(ssl_info): + """Return certificate and chain from the ssl info in M2Crypto format""" + + cert = ssl_info.get(SSL_CLIENT_CERT_ENV, "") + chain = ssl_info.get(SSL_CLIENT_CERT_CHAIN_0_ENV, "") + cert = M2Crypto.X509.load_cert_string(cert) + aux = M2Crypto.X509.load_cert_string(chain) + chain = M2Crypto.X509.X509_Stack() + chain.push(aux) + return cert, chain + + +def _get_voms_info(self, ssl_info): + """Extract voms info from ssl_info and return dict with it.""" + + try: + cert, chain = self._get_cert_chain(ssl_info) + except M2Crypto.X509.X509Error: + print "Error getting certificate chain" + + with voms_helper.VOMS(VOMS_CONFIG["vomsdir_path"], + VOMS_CONFIG["ca_path"], VOMS_CONFIG["vomsapi_lib"] as v: + if self._no_verify: + v.set_no_verify() + voms_data = v.retrieve(cert, chain) + if not voms_data: + raise VomsError(v.error.value) + + d = {} + for attr in ('user', 'userca', 'server', 'serverca', + 'voname', 'uri', 'version', 'serial', + ('not_before', 'date1'), ('not_after', 'date2')): + if isinstance(attr, basestring): + d[attr] = getattr(voms_data, attr) + else: + d[attr[0]] = getattr(voms_data, attr[1]) + + d["fqans"] = [] + for fqan in iter(voms_data.fqan): + if fqan is None: + break + d["fqans"].append(fqan) + + return d + + +def _split_fqan(fqan): + """ + gets a fqan and returns a tuple containing + (vo/groups, role, capability) + """ + l = fqan.split("/") + capability = l.pop().split("=")[-1] + role = l.pop().split("=")[-1] + vogroup = "/".join(l) + return (vogroup, role, capability) + + +def _split_fqan(fqan): + """ + gets a fqan and returns a tuple containing + (vo/groups, role, capability) + """ + l = fqan.split("/") + capability = l.pop().split("=")[-1] + role = l.pop().split("=")[-1] + vogroup = "/".join(l) + return (vogroup, role, capability) + + +def authenticate(self, ssl_data): + try: + voms_info = self._get_voms_info(ssl_data) + except VomsError as e: + raise e + + user_dn = voms_info["user"] + user_vo = voms_info["voname"] + user_fqans = voms_info["fqans"] + + return user_dn, user_vo, user_fqans diff --git a/snfOCCI/voms/voms_helper.py b/snfOCCI/voms/voms_helper.py new file mode 100644 index 0000000..511c402 --- /dev/null +++ b/snfOCCI/voms/voms_helper.py @@ -0,0 +1,108 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import commands +import ctypes +import os + +import M2Crypto + + +class _voms(ctypes.Structure): + _fields_ = [ + ("siglen", ctypes.c_int32), + ("signature", ctypes.c_char_p), + ("user", ctypes.c_char_p), + ("userca", ctypes.c_char_p), + ("server", ctypes.c_char_p), + ("serverca", ctypes.c_char_p), + ("voname", ctypes.c_char_p), + ("uri", ctypes.c_char_p), + ("date1", ctypes.c_char_p), + ("date2", ctypes.c_char_p), + ("type", ctypes.c_int32), + ("std", ctypes.c_void_p), + ("custom", ctypes.c_char_p), + ("datalen", ctypes.c_int32), + ("version", ctypes.c_int32), + ("fqan", ctypes.POINTER(ctypes.c_char_p)), + ("serial", ctypes.c_char_p), + ("ac", ctypes.c_void_p), + ("holder", ctypes.c_void_p), + ] + + +class _vomsdata(ctypes.Structure): + _fields_ = [ + ("cdir", ctypes.c_char_p), + ("vdir", ctypes.c_char_p), + ("data", ctypes.POINTER(ctypes.POINTER(_voms))), + ("workvo", ctypes.c_char_p), + ("extra_data", ctypes.c_char_p), + ("volen", ctypes.c_int32), + ("extralen", ctypes.c_int32), + ("real", ctypes.c_void_p), + ] + + +class VOMS(object): + """Context Manager for VOMS handling""" + + def __init__(self, vomsdir_path, ca_path, vomsapi_lib): + self.VOMSApi = ctypes.CDLL(vomsapi_lib) + self.VOMSApi.VOMS_Init.restype = ctypes.POINTER(_vomsdata) + + self.VOMSDIR = vomsdir_path + self.CADIR = ca_path + + self.vd = None + + def __enter__(self): + self.vd = self.VOMSApi.VOMS_Init(self.VOMSDIR, self.CADIR).contents + return self + + def set_no_verify(self): + """Skip verification of AC. + + This method skips the AC signature verification, this it should + only be used for debugging and tests. + """ + + error = ctypes.c_int32(0) + self.VOMSApi.VOMS_SetVerificationType(0x040, + ctypes.byref(self.vd), + ctypes.byref(error)) + + def retrieve(self, cert, chain): + """Retrieve VOMS credentials from a certificate and chain.""" + + self.error = ctypes.c_int32(0) + + cert_ptr = ctypes.cast(long(cert._ptr()), ctypes.c_void_p) + chain_ptr = ctypes.cast(long(chain._ptr()), ctypes.c_void_p) + + res = self.VOMSApi.VOMS_Retrieve(cert_ptr, + chain_ptr, + 0, + ctypes.byref(self.vd), + ctypes.byref(self.error)) + if res == 0: + return None + else: + return self.vd.data.contents.contents + + def __exit__(self, type, value, tb): + self.VOMSApi.VOMS_Destroy(ctypes.byref(self.vd)) -- 1.7.10.4