From b6ff01a72e91dc3a7086a656b4d561a6eb098b82 Mon Sep 17 00:00:00 2001 From: Soner Sevinc Date: Thu, 1 May 2008 01:30:58 +0000 Subject: [PATCH] the changed files in PLC and SSL --- changes/AddNode.py | 98 ++++++++++++++++++++ changes/AddPerson.py | 45 +++++++++ changes/AddPersonToSite.py | 87 ++++++++++++++++++ changes/AddRoleToPerson.py | 86 +++++++++++++++++ changes/AddSite.py | 43 +++++++++ changes/AddSlice.py | 106 +++++++++++++++++++++ changes/Checker.py | 183 +++++++++++++++++++++++++++++++++++++ changes/README | 5 + 8 files changed, 653 insertions(+) create mode 100755 changes/AddNode.py create mode 100755 changes/AddPerson.py create mode 100755 changes/AddPersonToSite.py create mode 100755 changes/AddRoleToPerson.py create mode 100755 changes/AddSite.py create mode 100755 changes/AddSlice.py create mode 100644 changes/Checker.py create mode 100644 changes/README diff --git a/changes/AddNode.py b/changes/AddNode.py new file mode 100755 index 00000000..999ddd79 --- /dev/null +++ b/changes/AddNode.py @@ -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 index 00000000..655ba703 --- /dev/null +++ b/changes/AddPerson.py @@ -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 index 00000000..9ea2494e --- /dev/null +++ b/changes/AddPersonToSite.py @@ -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 index 00000000..b95b47b2 --- /dev/null +++ b/changes/AddRoleToPerson.py @@ -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 index 00000000..d06952d6 --- /dev/null +++ b/changes/AddSite.py @@ -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 index 00000000..c729387e --- /dev/null +++ b/changes/AddSlice.py @@ -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 index 00000000..d33397a1 --- /dev/null +++ b/changes/Checker.py @@ -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 index 00000000..20474730 --- /dev/null +++ b/changes/README @@ -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 -- 2.43.0