Draft VOMS integration using voms_helper lib
authorJohn Giannelos <johngian@grnet.gr>
Mon, 22 Oct 2012 13:57:56 +0000 (16:57 +0300)
committerJohn Giannelos <johngian@grnet.gr>
Mon, 22 Oct 2012 13:59:10 +0000 (16:59 +0300)
snfOCCI/voms/__init__.py [new file with mode: 0644]
snfOCCI/voms/voms_helper.py [new file with mode: 0644]

diff --git a/snfOCCI/voms/__init__.py b/snfOCCI/voms/__init__.py
new file mode 100644 (file)
index 0000000..238ebf4
--- /dev/null
@@ -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 (file)
index 0000000..511c402
--- /dev/null
@@ -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))