the changed files in PLC and SSL
authorSoner Sevinc <ssevinc@cs.princeton.edu>
Thu, 1 May 2008 01:30:58 +0000 (01:30 +0000)
committerSoner Sevinc <ssevinc@cs.princeton.edu>
Thu, 1 May 2008 01:30:58 +0000 (01:30 +0000)
changes/AddNode.py [new file with mode: 0755]
changes/AddPerson.py [new file with mode: 0755]
changes/AddPersonToSite.py [new file with mode: 0755]
changes/AddRoleToPerson.py [new file with mode: 0755]
changes/AddSite.py [new file with mode: 0755]
changes/AddSlice.py [new file with mode: 0755]
changes/Checker.py [new file with mode: 0644]
changes/README [new file with mode: 0644]

diff --git a/changes/AddNode.py b/changes/AddNode.py
new file mode 100755 (executable)
index 0000000..999ddd7
--- /dev/null
@@ -0,0 +1,98 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Nodes import Node, Nodes
+from PLC.NodeGroups import NodeGroup, NodeGroups
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+import uuid ##################################soners
+import sys
+sys.path.append('../../../../util')
+from pl_to_geni import *
+from util import *
+from db import *
+
+can_update = lambda (field, value): field in \
+             ['hostname', 'boot_state', 'model', 'version', 'uuid']
+
+class AddNode(Method):
+    """
+    Adds a new node. Any values specified in node_fields are used,
+    otherwise defaults are used.
+
+    PIs and techs may only add nodes to their own sites. Admins may
+    add nodes to any site.
+
+    Returns the new node_id (> 0) if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech']
+
+    node_fields = dict(filter(can_update, Node.fields.items()))
+
+    accepts = [
+        Auth(),
+        Mixed(Site.fields['site_id'],
+              Site.fields['login_base']),
+        node_fields
+        ]
+
+    returns = Parameter(int, 'New node_id (> 0) if successful')
+
+    def call(self, auth, site_id_or_login_base, node_fields):
+        node_fields = dict(filter(can_update, node_fields.items()))
+
+        # Get site information
+        sites = Sites(self.api, [site_id_or_login_base])
+        if not sites:
+            raise PLCInvalidArgument, "No such site"
+
+        site = sites[0]
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # If we are not an admin, make sure that the caller is a
+        # member of the site.
+        if 'admin' not in self.caller['roles']:
+            if site['site_id'] not in self.caller['site_ids']:
+                assert self.caller['person_id'] not in site['person_ids']
+                raise PLCPermissionDenied, "Not allowed to add nodes to specified site"
+            else:
+                assert self.caller['person_id'] in site['person_ids']
+
+        node = Node(self.api, node_fields)
+        node['site_id'] = site['site_id']
+       node['uuid'] = str(uuid.uuid4().int)###############################soners
+        node.sync()
+
+       self.event_objects = {'Site': [site['site_id']],
+                            'Node': [node['node_id']]} 
+       self.message = "Node %s created" % node['node_id']
+
+       #insert the record into GENI tables  ###############################soner
+       (global_sr_tree, global_cr_tree) = get_tree_globals()
+       (site_id, site_hrn) = site_to_auth(site['site_id'])
+       dbinfo = determine_dbinfo(site_hrn, global_cr_tree)
+       if dbinfo == None:
+               raise PLCInvalidArgument, "No GENI authority corresponding to the site "+site['name']
+       cnx = dbinfo[0]
+       tablename = dbinfo[1]
+       
+       new_hrn = plnode_to_node(node['hostname'], 0)
+       existing = cnx.query("SELECT * FROM "+tablename+" WHERE hrn = '"+new_hrn+"'; ")
+       if existing != None:
+               new_hrn = plnode_to_node(node['hostname'], 1)
+               existing = cnx.query("SELECT * FROM "+tablename+" WHERE hrn = '"+new_hrn+"'; ")
+               if existing != None:
+                       new_hrn = plnode_to_node(node['hostname'], 2)
+               
+       geni_record = {'hrn':''}
+       geni_record["hrn"] = new_hrn
+       geni_record["type"] = 'node'
+       geni_record['pointer'] = node['node_id']
+       
+        querystr = generate_querystr('INSERT', tablename, geni_record)
+        cnx.query(querystr)
+       
+        return node['node_id']
diff --git a/changes/AddPerson.py b/changes/AddPerson.py
new file mode 100755 (executable)
index 0000000..655ba70
--- /dev/null
@@ -0,0 +1,45 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+import uuid ##################################soners
+
+can_update = lambda (field, value): field in \
+             ['first_name', 'last_name', 'title',
+              'email', 'password', 'phone', 'url', 'bio']
+
+class AddPerson(Method):
+    """
+    Adds a new account. Any fields specified in person_fields are
+    used, otherwise defaults are used.
+
+    Accounts are disabled by default. To enable an account, use
+    UpdatePerson().
+
+    Returns the new person_id (> 0) if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi']
+
+    person_fields = dict(filter(can_update, Person.fields.items()))
+
+    accepts = [
+        Auth(),
+        person_fields
+        ]
+
+    returns = Parameter(int, 'New person_id (> 0) if successful')
+
+    def call(self, auth, person_fields):
+        person_fields = dict(filter(can_update, person_fields.items()))
+       person_fields['uuid'] = str(uuid.uuid4().int)###############################soners
+        person_fields['enabled'] = False
+        person = Person(self.api, person_fields)
+        person.sync()
+
+       # Logging variables
+       self.event_objects = {'Person': [person['person_id']]}
+       self.message = 'Person %d added' % person['person_id']  
+
+        return person['person_id']
diff --git a/changes/AddPersonToSite.py b/changes/AddPersonToSite.py
new file mode 100755 (executable)
index 0000000..9ea2494
--- /dev/null
@@ -0,0 +1,87 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+import sys             ###########################soner
+sys.path.append('../../../../util')
+from pl_to_geni import *
+from util import *
+from db import *
+
+class AddPersonToSite(Method):
+    """
+    Adds the specified person to the specified site. If the person is
+    already a member of the site, no errors are returned. Does not
+    change the person's primary site.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        Auth(),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email']),
+        Mixed(Site.fields['site_id'],
+              Site.fields['login_base'])
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, person_id_or_email, site_id_or_login_base):
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+        person = persons[0]
+
+        if person['peer_id'] is not None:
+            raise PLCInvalidArgument, "Not a local account"
+
+        # Get site information
+        sites = Sites(self.api, [site_id_or_login_base])
+        if not sites:
+            raise PLCInvalidArgument, "No such site"
+        site = sites[0]
+
+        if site['peer_id'] is not None:
+            raise PLCInvalidArgument, "Not a local site"
+
+        if site['site_id'] not in person['site_ids']:
+            site.add_person(person)
+
+       # Logging variables
+       self.event_objects = {'Site': [site['site_id']],
+                             'Person': [person['person_id']]}
+       self.message = 'Person %d added to site %d' % \
+               (person['person_id'], site['site_id'])
+               
+       #insert the record into GENI tables        ###################soner
+       (global_sr_tree, global_cr_tree) = get_tree_globals()
+       (site_id, site_hrn) = site_to_auth(site_id_or_login_base)
+       dbinfo = determine_dbinfo(site_hrn, global_sr_tree)
+       if dbinfo == None:
+               raise PLCInvalidArgument, "No GENI authority corresponding to the site "+site['name']
+       cnx = dbinfo[0]
+       tablename = dbinfo[1]
+       
+       new_hrn = person_to_user(person['email'])
+       existing = cnx.query("SELECT * FROM "+tablename+" WHERE hrn = '"+new_hrn+"'; ")
+       if existing != None:
+               new_hrn = person_to_user(person['email'], 1)
+               existing = cnx.query("SELECT * FROM "+tablename+" WHERE hrn = '"+new_hrn+"'; ")
+               if existing != None:
+                       new_hrn = person_to_user(person['email'], 2)
+       
+       geni_record = {'hrn':''}
+       geni_record["hrn"] = new_hrn
+       geni_record["type"] = 'user'
+       geni_record['pointer'] = person['person_id']
+       
+        querystr = generate_querystr('INSERT', tablename, geni_record)
+        cnx.query(querystr)
+       
+       return 1
diff --git a/changes/AddRoleToPerson.py b/changes/AddRoleToPerson.py
new file mode 100755 (executable)
index 0000000..b95b47b
--- /dev/null
@@ -0,0 +1,86 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Persons import Person, Persons
+from PLC.Auth import Auth
+from PLC.Roles import Role, Roles
+import sys     ##################################soners
+sys.path.append('../../../../util')
+from pl_to_geni import *
+from util import *
+from db import *
+
+class AddRoleToPerson(Method):
+    """
+    Grants the specified role to the person.
+    
+    PIs can only grant the tech and user roles to users and techs at
+    their sites. Admins can grant any role to any user.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi']
+
+    accepts = [
+        Auth(),
+        Mixed(Role.fields['role_id'],
+              Role.fields['name']),
+        Mixed(Person.fields['person_id'],
+              Person.fields['email']),
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, role_id_or_name, person_id_or_email):
+        # Get role
+        roles = Roles(self.api, [role_id_or_name])
+        if not roles:
+            raise PLCInvalidArgument, "Invalid role '%s'" % unicode(role_id_or_name)
+        role = roles[0]
+
+        # Get account information
+        persons = Persons(self.api, [person_id_or_email])
+        if not persons:
+            raise PLCInvalidArgument, "No such account"
+        person = persons[0]
+
+        if person['peer_id'] is not None:
+            raise PLCInvalidArgument, "Not a local account"
+
+        # Authenticated function
+        assert self.caller is not None
+
+        # Check if we can update this account
+        if not self.caller.can_update(person):
+            raise PLCPermissionDenied, "Not allowed to update specified account"
+
+        # Can only grant lesser (higher) roles to others
+        if 'admin' not in self.caller['roles'] and \
+           role['role_id'] <= min(self.caller['role_ids']):
+            raise PLCInvalidArgument, "Not allowed to grant that role"
+
+        if role['role_id'] not in person['role_ids']:
+            person.add_role(role)
+
+       self.event_objects = {'Person': [person['person_id']],
+                             'Role': [role['role_id']]}
+       self.message = "Role %d granted to person %d" % \
+                       (role['role_id'], person['person_id'])
+
+       #erase the GENI rights so that PL will not be imcompatible with GENI ############################soners
+       (global_sr_tree, global_cr_tree) = get_tree_globals()
+       cnx = get_plDB_conn()
+       site_ids = cnx.query("SELECT site_id FROM person_slice WHERE person_id = "+person['person_id'])
+       for sid in site_ids:
+               (site_id, site_hrn) = site_to_auth(sid)
+               dbinfo = determine_dbinfo(site_hrn, global_sr_tree)
+               if dbinfo == None:
+                       raise PLCInvalidArgument, "No GENI authority corresponding to the site"
+               cnx = dbinfo[0]
+               tablename = dbinfo[1]
+               
+               querystr = "UPDATE "+tablename+" SET rights = '' WHERE pointer = "+person['person_id']
+               cnx.query(querystr)
+
+        return 1
diff --git a/changes/AddSite.py b/changes/AddSite.py
new file mode 100755 (executable)
index 0000000..d06952d
--- /dev/null
@@ -0,0 +1,43 @@
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Sites import Site, Sites
+from PLC.Auth import Auth
+import uuid ##################################soners
+
+can_update = lambda (field, value): field in \
+             ['name', 'abbreviated_name', 'login_base',
+              'is_public', 'latitude', 'longitude', 'url',
+              'max_slices', 'max_slivers', 'enabled', 'uuid']
+
+class AddSite(Method):
+    """
+    Adds a new site, and creates a node group for that site. Any
+    fields specified in site_fields are used, otherwise defaults are
+    used.
+
+    Returns the new site_id (> 0) if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    site_fields = dict(filter(can_update, Site.fields.items()))
+
+    accepts = [
+        Auth(),
+        site_fields
+        ]
+
+    returns = Parameter(int, 'New site_id (> 0) if successful')
+
+    def call(self, auth, site_fields):
+        site_fields = dict(filter(can_update, site_fields.items()))
+        site = Site(self.api, site_fields)
+       site['uuid'] = str(uuid.uuid4().int)###############################soners
+        site.sync()
+       
+       # Logging variables 
+       self.event_objects = {'Site': [site['site_id']]}
+        self.message = 'Site %d created' % site['site_id']
+       
+       return site['site_id']
diff --git a/changes/AddSlice.py b/changes/AddSlice.py
new file mode 100755 (executable)
index 0000000..c729387
--- /dev/null
@@ -0,0 +1,106 @@
+import re
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Slices import Slice, Slices
+from PLC.Auth import Auth
+from PLC.Sites import Site, Sites
+import uuid ##################################soners
+import sys
+sys.path.append('../../../../util')
+from pl_to_geni import *
+from util import *
+from db import *
+
+can_update = lambda (field, value): field in \
+             ['name', 'instantiation', 'url', 'description', 'max_nodes', 'uuid']
+
+class AddSlice(Method):
+    """
+    Adds a new slice. Any fields specified in slice_fields are used,
+    otherwise defaults are used.
+
+    Valid slice names are lowercase and begin with the login_base
+    (slice prefix) of a valid site, followed by a single
+    underscore. Thereafter, only letters, numbers, or additional
+    underscores may be used.
+
+    PIs may only add slices associated with their own sites (i.e.,
+    slice prefixes must always be the login_base of one of their
+    sites).
+
+    Returns the new slice_id (> 0) if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi']
+
+    slice_fields = dict(filter(can_update, Slice.fields.items()))
+
+    accepts = [
+        Auth(),
+        slice_fields
+        ]
+
+    returns = Parameter(int, 'New slice_id (> 0) if successful')
+
+    def call(self, auth, slice_fields):
+        slice_fields = dict(filter(can_update, slice_fields.items()))
+
+        # 1. Lowercase.
+        # 2. Begins with login_base (letters or numbers).
+        # 3. Then single underscore after login_base.
+        # 4. Then letters, numbers, or underscores.
+        name = slice_fields['name']
+        good_name = r'^[a-z0-9]+_[a-zA-Z0-9_]+$'
+        if not name or \
+           not re.match(good_name, name):
+            raise PLCInvalidArgument, "Invalid slice name"
+
+        # Get associated site details
+        login_base = name.split("_")[0]
+        sites = Sites(self.api, [login_base])
+        if not sites:
+            raise PLCInvalidArgument, "Invalid slice prefix %s in %s"%(login_base,name)
+        site = sites[0]
+
+        if 'admin' not in self.caller['roles']:
+            if site['site_id'] not in self.caller['site_ids']:
+                raise PLCPermissionDenied, "Slice prefix %s must be the same as the login_base of one of your sites"%login_base
+
+        if len(site['slice_ids']) >= site['max_slices']:
+            raise PLCInvalidArgument, "Site %s has reached (%d) its maximum allowable slice count (%d)"%(site['name'],
+                                                                                                         len(site['slice_ids']),
+                                                                                                         site['max_slices'])
+
+       if not site['enabled']:
+           raise PLCInvalidArgument, "Site %s is disabled can cannot create slices" % (site['name'])
+        
+        slice = Slice(self.api, slice_fields)
+        slice['creator_person_id'] = self.caller['person_id']
+        slice['site_id'] = site['site_id']
+       slice['uuid'] = str(uuid.uuid4().int)###############################soner
+       
+        slice.sync()
+
+       self.event_objects = {'Slice': [slice['slice_id']]}
+
+       #insert the record into GENI tables  ###############################soner
+       (global_sr_tree, global_cr_tree) = get_tree_globals()
+       (site_id, site_hrn) = site_to_auth(site['site_id'])
+       dbinfo = determine_dbinfo(site_hrn, global_sr_tree)
+       if dbinfo == None:
+               raise PLCInvalidArgument, "No GENI authority corresponding to the site "+site['name']
+       cnx = dbinfo[0]
+       tablename = dbinfo[1]
+       
+       new_hrn = plslice_to_slice(slice['name'])       
+       geni_record = {'hrn':''}
+       geni_record["hrn"] = new_hrn
+       geni_record["type"] = 'slice'
+       geni_record['pointer'] = slice['slice_id']
+       
+        querystr = generate_querystr('INSERT', tablename, geni_record)
+        cnx.query(querystr)
+       
+        return slice['slice_id']
diff --git a/changes/Checker.py b/changes/Checker.py
new file mode 100644 (file)
index 0000000..d33397a
--- /dev/null
@@ -0,0 +1,183 @@
+"""
+M2Crypto.SSL.Checker
+
+Copyright (c) 2004-2005 Open Source Applications Foundation.
+All rights reserved.
+"""
+
+from M2Crypto import util, EVP
+import re
+
+class SSLVerificationError(Exception):
+    pass
+
+class NoCertificate(SSLVerificationError):
+    pass
+
+class WrongCertificate(SSLVerificationError):
+    pass
+
+class WrongHost(SSLVerificationError):
+    def __init__(self, expectedHost, actualHost, fieldName='commonName'):
+        """
+        This exception will be raised if the certificate returned by the
+        peer was issued for a different host than we tried to connect to.
+        This could be due to a server misconfiguration or an active attack.
+        
+        @param expectedHost: The name of the host we expected to find in the
+                             certificate.
+        @param actualHost:   The name of the host we actually found in the
+                             certificate.
+        @param fieldName:    The field name where we noticed the error. This
+                             should be either 'commonName' or 'subjectAltName'.
+        """
+        if fieldName not in ('commonName', 'subjectAltName'):
+            raise ValueError('Unknown fieldName, should be either commonName or subjectAltName')
+        
+        SSLVerificationError.__init__(self)
+        self.expectedHost = expectedHost
+        self.actualHost = actualHost
+        self.fieldName = fieldName
+        
+    def __str__(self):
+        s = 'Peer certificate %s does not match host, expected %s, got %s' \
+               % (self.fieldName, self.expectedHost, self.actualHost)
+        if isinstance(s, unicode):
+            s = s.encode('utf8')
+        return s
+
+
+class Checker:
+    def __init__(self, host=None, peerCertHash=None, peerCertDigest='sha1'):
+        self.host = host
+        self.fingerprint = peerCertHash
+        self.digest = peerCertDigest
+        self.numericIpMatch = re.compile('^[0-9]+(\.[0-9]+)*$')
+
+    def __call__(self, peerCert, host=None):
+        if peerCert is None:
+            raise NoCertificate('peer did not return certificate')
+
+        if host is not None:
+            self.host = host
+        
+        if self.fingerprint:
+            if self.digest not in ('sha1', 'md5'):
+                raise ValueError('unsupported digest "%s"' %(self.digest))
+
+            if (self.digest == 'sha1' and len(self.fingerprint) != 40) or \
+               (self.digest == 'md5' and len(self.fingerprint) != 32):
+                raise WrongCertificate('peer certificate fingerprint length does not match')
+            
+            der = peerCert.as_der()
+            md = EVP.MessageDigest(self.digest)
+            md.update(der)
+            digest = md.final()
+            if util.octx_to_num(digest) != int(self.fingerprint, 16):
+                raise WrongCertificate('peer certificate fingerprint does not match')
+
+        if self.host:
+            hostValidationPassed = False
+
+            # XXX subjectAltName might contain multiple fields
+            # subjectAltName=DNS:somehost
+            try:
+                subjectAltName = peerCert.get_ext('subjectAltName').get_value()
+                if not self._match(self.host, subjectAltName, True):
+                    raise WrongHost(expectedHost=self.host, 
+                                    actualHost=subjectAltName,
+                                    fieldName='subjectAltName')
+                hostValidationPassed = True
+            except LookupError:
+                pass
+
+            # commonName=somehost
+
+
+##-----by Soner, comment outed
+
+##            if not hostValidationPassed:
+##                try:
+##                    commonName = peerCert.get_subject().CN
+##                    if not self._match(self.host, commonName):
+##                        raise WrongHost(expectedHost=self.host,
+##                                        actualHost=commonName,
+##                                        fieldName='commonName')
+##                except AttributeError:
+##                    raise WrongCertificate('no commonName in peer certificate')
+
+##-----/by Soner
+
+        return True
+
+    def _match(self, host, certHost, subjectAltName=False):
+        """
+        >>> check = Checker()
+        >>> check._match(host='my.example.com', certHost='DNS:my.example.com', subjectAltName=True)
+        True
+        >>> check._match(host='my.example.com', certHost='DNS:*.example.com', subjectAltName=True)
+        True
+        >>> check._match(host='my.example.com', certHost='DNS:m*.example.com', subjectAltName=True)
+        True
+        >>> check._match(host='my.example.com', certHost='DNS:m*ample.com', subjectAltName=True)
+        False
+        >>> check._match(host='my.example.com', certHost='my.example.com')
+        True
+        >>> check._match(host='my.example.com', certHost='*.example.com')
+        True
+        >>> check._match(host='my.example.com', certHost='m*.example.com')
+        True
+        >>> check._match(host='my.example.com', certHost='m*.EXAMPLE.com')
+        True
+        >>> check._match(host='my.example.com', certHost='m*ample.com')
+        False
+        >>> check._match(host='my.example.com', certHost='*.*.com')
+        False
+        >>> check._match(host='1.2.3.4', certHost='1.2.3.4')
+        True
+        >>> check._match(host='1.2.3.4', certHost='*.2.3.4')
+        False
+        >>> check._match(host='1234', certHost='1234')
+        True
+        """
+        # XXX See RFC 2818 and 3280 for matching rules, this is not
+        # XXX yet complete.
+
+        host = host.lower()
+        certHost = certHost.lower()
+
+        if subjectAltName:
+            if certHost[:4] != 'dns:':
+                return False
+            certHost = certHost[4:]
+        
+        if host == certHost:
+            return True
+
+        if certHost.count('*') > 1:
+            # Not sure about this, but being conservative
+            return False
+
+        if self.numericIpMatch.match(host) or \
+               self.numericIpMatch.match(certHost.replace('*', '')):
+            # Not sure if * allowed in numeric IP, but think not.
+            return False
+
+        if certHost.find('\\') > -1:
+            # Not sure about this, maybe some encoding might have these.
+            # But being conservative for now, because regex below relies
+            # on this.
+            return False
+
+        # Massage certHost so that it can be used in regex
+        certHost = certHost.replace('.', '\.')
+        certHost = certHost.replace('*', '[^\.]*')
+        if re.compile('^%s$' %(certHost)).match(host):
+            return True
+
+        return False
+
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
diff --git a/changes/README b/changes/README
new file mode 100644 (file)
index 0000000..2047473
--- /dev/null
@@ -0,0 +1,5 @@
+The PLCAPI changes are in the directory "PLCAPI/trunk/PLC/Methods".
+The SSL change is in the M2Crypto directory, at "/var/lib/python-support/python2.5/M2Crypto/SSL".
+
+Soner Sevinc
+04/30/08