diff --git a/datafiles/schema-delete.sql b/datafiles/schema-delete.sql index 0f55000b483381b7b8073b2389c270aa70cfd6c7..1dda6d5b4d7a5c96e68c64effa8df6bb90a04152 100644 --- a/datafiles/schema-delete.sql +++ b/datafiles/schema-delete.sql @@ -5,6 +5,7 @@ DROP TABLE generic_backend_connections; DROP TABLE sub_connections; DROP TABLE service_connections; DROP TYPE directionality; +DROP TYPE security_attribute; DROP TYPE parameter; DROP TYPE label; diff --git a/datafiles/schema.sql b/datafiles/schema.sql index 150aa836e46fe550e1152e7029e541194436080b..6239ff33aa5c90f56c6daebe0845e696e262ba5f 100644 --- a/datafiles/schema.sql +++ b/datafiles/schema.sql @@ -12,6 +12,11 @@ CREATE TYPE parameter AS ( label_value text ); +CREATE TYPE security_attribute AS ( + attribute_type text, + atribute_value text +); + CREATE TYPE directionality AS ENUM ('Bidirectional', 'Unidirectional'); @@ -40,6 +45,7 @@ CREATE TABLE service_connections ( directionality directionality NOT NULL, bandwidth integer NOT NULL, -- mbps parameter parameter[], + security_attributes security_attribute[], connection_trace text[] ); diff --git a/onsa b/onsa index b181816a1d69ec1d77fc3aa42c123831fe778290..71feeb44536d0616eb63ac7c747cb59f76ea9d89 100755 --- a/onsa +++ b/onsa @@ -105,7 +105,7 @@ def doMain(): global_id = config.subOptions[options.GLOBAL_ID] or defaults.get(options.GLOBAL_ID) # can only be specified on command line for now - security_attributes = config.subOptions[options.SECURITY_ATTRIBUTES] + security_attributes = [ nsa.SecurityAttribute(type_, value) for type_, value in config.subOptions[options.SECURITY_ATTRIBUTES] ] if topology_file and network: from opennsa.topology import gole @@ -129,7 +129,7 @@ def doMain(): log.msg("Requester URL: %s" % requester_url) - nsi_header = nsa.NSIHeader(client_nsa.urn(), provider_nsa.urn(), reply_to=provider_nsa.endpoint, session_security_attrs=security_attributes) + nsi_header = nsa.NSIHeader(client_nsa.urn(), provider_nsa.urn(), reply_to=provider_nsa.endpoint, security_attributes=security_attributes) # setup ssl context public_key = config.subOptions[options.CERTIFICATE] or defaults.get(options.CERTIFICATE) diff --git a/opennsa/aggregator.py b/opennsa/aggregator.py index 914cf33b64cb4bc2234a41e2e45793a4fa8f9b78..997deae771bb0fa94151fed00978a4e23883b02f 100644 --- a/opennsa/aggregator.py +++ b/opennsa/aggregator.py @@ -191,6 +191,11 @@ class Aggregator: log.msg('Rejecting reserve request without connection trace') raise error.ConnectionCreateError('This NSA (%s) requires a connection trace in the header to create a reservation.' % self.nsa_.urn() ) + user_attrs = [ sa for sa in header.security_attributes if sa.type_ == 'user' ] + if not user_attrs: + log.msg('Rejecting reserve request without user security attribute') + raise error.ConnectionCreateError('This NSA (%s) requires a user security attribute in the header to create a reservation.' % self.nsa_.urn() ) + connection_id = self.conn_prefix + ''.join( [ random.choice(string.hexdigits[:16]) for _ in range(12) ] ) sd = criteria.service_def @@ -223,7 +228,8 @@ class Aggregator: source_network=source_stp.network, source_port=source_stp.port, source_label=source_stp.label, dest_network=dest_stp.network, dest_port=dest_stp.port, dest_label=dest_stp.label, start_time=criteria.schedule.start_time, end_time=criteria.schedule.end_time, - symmetrical=sd.symmetric, directionality=sd.directionality, bandwidth=sd.capacity, connection_trace=header.connection_trace) + symmetrical=sd.symmetric, directionality=sd.directionality, bandwidth=sd.capacity, + security_attributes=header.security_attributes, connection_trace=header.connection_trace) yield conn.save() # Here we should return / callback and spawn off the path creation @@ -332,7 +338,7 @@ class Aggregator: else: provider_urn = cnt.URN_OGF_PREFIX + self.route_vectors.getProvider( cnt.URN_OGF_PREFIX + link.network ) - c_header = nsa.NSIHeader(self.nsa_.urn(), provider_urn, session_security_attrs=header.session_security_attrs, connection_trace=conn_trace) + c_header = nsa.NSIHeader(self.nsa_.urn(), provider_urn, security_attributes=header.security_attributes, connection_trace=conn_trace) # this has to be done more generic sometime sd = nsa.Point2PointService(nsa.STP(link.network, link.src_port, link.src_label), @@ -392,7 +398,7 @@ class Aggregator: for (sc_id, provider_urn) in reserved_connections: provider = self.getProvider(provider_urn) - t_header = nsa.NSIHeader(self.nsa_.urn(), provider_urn, session_security_attrs=header.session_security_attrs) + t_header = nsa.NSIHeader(self.nsa_.urn(), provider_urn, security_attributes=header.security_attributes) d = provider.terminate(t_header, sc_id) d.addCallbacks( @@ -427,7 +433,7 @@ class Aggregator: for sc in sub_connections: # we assume a provider is available provider = self.getProvider(sc.provider_nsa) - req_header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, session_security_attrs=header.session_security_attrs) + req_header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, security_attributes=header.security_attributes) # we should probably mark as committing before sending message... d = provider.reserveCommit(req_header, sc.connection_id) defs.append(d) @@ -465,7 +471,7 @@ class Aggregator: for sc in sub_connections: save_defs.append( state.reserveAbort(sc) ) provider = self.getProvider(sc.provider_nsa) - header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, session_security_attrs=header.session_security_attrs) + header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, security_attributes=header.security_attributes) d = provider.reserveAbort(header, sc.connection_id) defs.append(d) @@ -508,7 +514,7 @@ class Aggregator: for sc in sub_connections: provider = self.getProvider(sc.provider_nsa) - header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, session_security_attrs=header.session_security_attrs) + header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, security_attributes=header.security_attributes) d = provider.provision(header, sc.connection_id) defs.append(d) @@ -547,7 +553,7 @@ class Aggregator: for sc in sub_connections: provider = self.getProvider(sc.provider_nsa) - header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, session_security_attrs=header.session_security_attrs) + header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, security_attributes=header.security_attributes) d = provider.release(header, sc.connection_id) defs.append(d) @@ -584,7 +590,7 @@ class Aggregator: for sc in sub_connections: # we assume a provider is available provider = self.getProvider(sc.provider_nsa) - header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, session_security_attrs=header.session_security_attrs) + header = nsa.NSIHeader(self.nsa_.urn(), sc.provider_nsa, security_attributes=header.security_attributes) d = provider.terminate(header, sc.connection_id) defs.append(d) diff --git a/opennsa/cli/parser.py b/opennsa/cli/parser.py index 2ff1b1b70e616fe8f5f57631827161aaab3642df..4bf69e2fdc372edb335fae580de21ba251581dde 100644 --- a/opennsa/cli/parser.py +++ b/opennsa/cli/parser.py @@ -108,14 +108,14 @@ class EndTimeOption(usage.Options): class SecurityAttributeOptions(usage.Options): optParameters = [ [ options.SECURITY_ATTRIBUTES, 'j', None, 'Security attributes (format attr1=value1,attr2=value2)'] ] def postOptions(self): - key_values = [] + sats = [] if self[options.SECURITY_ATTRIBUTES]: for kv_split in self[options.SECURITY_ATTRIBUTES].split(','): if not '=' in kv_split: raise usage.UsageError('No = in key-value attribute %s' % kv_split) key, value = kv_split.split('=',1) - key_values.append( (key, [value]) ) - self[options.SECURITY_ATTRIBUTES] = key_values + sats.append( (key, value) ) + self[options.SECURITY_ATTRIBUTES] = sats class BandwidthOption(usage.Options): optParameters = [ [ options.BANDWIDTH, 'b', None, 'Bandwidth (Megabits)'] ] diff --git a/opennsa/database.py b/opennsa/database.py index 8a590f842f2070a0ffde528c1caee8f0857ff24f..3f0a7b615a4316c25e4d98a5b2f5f5f4b2380a98 100644 --- a/opennsa/database.py +++ b/opennsa/database.py @@ -31,11 +31,15 @@ LOG_SYSTEM = 'opennsa.Database' def adaptLabel(label): return AsIs("(%s, %s)::label" % (adapt(label.type_), adapt(label.labelValue()))) +def adaptSecuritAttribute(label): + return AsIs("(%s, %s)::security_attribute" % (adapt(label.type_), adapt(label.value))) + def adaptDatetime(dt): return AsIs("%s" % adapt(dt.isoformat())) register_adapter(nsa.Label, adaptLabel) +register_adapter(nsa.SecurityAttribute, adaptSecuritAttribute) register_adapter(datetime.datetime, adaptDatetime) @@ -44,6 +48,11 @@ class LabelComposite(CompositeCaster): return nsa.Label(*values) +class SecuritAttributeComposite(CompositeCaster): + def make(self, values): + return nsa.SecurityAttribute(*values) + + def castDatetime(value, cur): return parser.parse(value) @@ -57,6 +66,7 @@ def setupDatabase(database, user, password=None): conn = psycopg2.connect(user=user, password=password, database=database) cur = conn.cursor() register_composite('label', cur, globally=True, factory=LabelComposite) + register_composite('security_attribute', cur, globally=True, factory=SecuritAttributeComposite) cur.execute("SELECT oid FROM pg_type WHERE typname = 'timestamptz';") timestamptz_oid = cur.fetchone()[0] diff --git a/opennsa/nsa.py b/opennsa/nsa.py index ce7cf175f3cb555fee7f5f146e253021729f3344..8de5e90c271b4c7b1a566ea2e3395d8f57ff1dd1 100644 --- a/opennsa/nsa.py +++ b/opennsa/nsa.py @@ -28,12 +28,12 @@ BIDIRECTIONAL = 'Bidirectional' class NSIHeader(object): - def __init__(self, requester_nsa, provider_nsa, correlation_id=None, reply_to=None, session_security_attrs=None, connection_trace=None): + def __init__(self, requester_nsa, provider_nsa, correlation_id=None, reply_to=None, security_attributes=None, connection_trace=None): self.requester_nsa = requester_nsa self.provider_nsa = provider_nsa self.correlation_id = correlation_id or self._createCorrelationId() self.reply_to = reply_to - self.session_security_attrs = session_security_attrs + self.security_attributes = security_attributes or [] self.connection_trace = connection_trace def _createCorrelationId(self): @@ -44,7 +44,21 @@ class NSIHeader(object): def __repr__(self): - return '<NSIHeader: %s, %s, %s, %s, %s>' % (self.requester_nsa, self.provider_nsa, self.correlation_id, self.reply_to, self.session_security_attrs) + return '<NSIHeader: %s, %s, %s, %s, %s, %s>' % (self.requester_nsa, self.provider_nsa, self.correlation_id, self.reply_to, self.security_attributes, self.connection_trace) + + + +class SecurityAttribute(object): + # a better name would be AuthZAttribute, but we are keeping the NSI lingo + + def __init__(self, type_, value): + assert type(type_) is str, 'SecurityAttribute type must be a string' + assert type(value) is str, 'SecurityAttribute value must be a string' + self.type_ = type_ + self.value = value + + def __repr__(self): + return '<SecurityAttribute: %s, %s>' % (self.type_, self.value) diff --git a/opennsa/protocols/nsi2/helper.py b/opennsa/protocols/nsi2/helper.py index a9e93c21049f7ce8da50ed658544f318dd38c5de..d04764e729fc349e2850eb655f5aafcc123cd082 100644 --- a/opennsa/protocols/nsi2/helper.py +++ b/opennsa/protocols/nsi2/helper.py @@ -39,26 +39,31 @@ LABEL_MAP = { -def createProviderHeader(requester_nsa_urn, provider_nsa_urn, reply_to=None, correlation_id=None, session_security_attributes=None, connection_trace=None): - return _createHeader(requester_nsa_urn, provider_nsa_urn, reply_to, correlation_id, session_security_attributes, connection_trace, protocol_type=cnt.CS2_PROVIDER) +def createProviderHeader(requester_nsa_urn, provider_nsa_urn, reply_to=None, correlation_id=None, security_attributes=None, connection_trace=None): + return _createHeader(requester_nsa_urn, provider_nsa_urn, reply_to, correlation_id, security_attributes, connection_trace, protocol_type=cnt.CS2_PROVIDER) -def createRequesterHeader(requester_nsa_urn, provider_nsa_urn, reply_to=None, correlation_id=None, session_security_attributes=None, connection_trace=None): - return _createHeader(requester_nsa_urn, provider_nsa_urn, reply_to, correlation_id, session_security_attributes, connection_trace, protocol_type=cnt.CS2_REQUESTER) +def createRequesterHeader(requester_nsa_urn, provider_nsa_urn, reply_to=None, correlation_id=None, security_attributes=None, connection_trace=None): + return _createHeader(requester_nsa_urn, provider_nsa_urn, reply_to, correlation_id, security_attributes, connection_trace, protocol_type=cnt.CS2_REQUESTER) -def _createHeader(requester_nsa_urn, provider_nsa_urn, reply_to=None, correlation_id=None, session_security_attributes=None, connection_trace=None, protocol_type=None): +def _createHeader(requester_nsa_urn, provider_nsa_urn, reply_to=None, correlation_id=None, security_attributes=None, connection_trace=None, protocol_type=None): if protocol_type is None: raise AssertionError('Requester or provider protocol type must be specified') - ssats = [] - if session_security_attributes: - for at, avs in session_security_attributes: - at = nsiframework.AttributeType(at, None, None, avs) - ssats.append( nsiframework.SessionSecurityAttrType( [ at ] ) ) + sat = [] + if security_attributes: + # group by name to adhere to gns spec + grouped_sats = {} + for sa in security_attributes: + grouped_sats.setdefault(sa.type_, []).append(sa.value) - header = nsiframework.CommonHeaderType(protocol_type, correlation_id, requester_nsa_urn, provider_nsa_urn, reply_to, ssats, connection_trace) + for name, values in grouped_sats.items(): + at = nsiframework.AttributeType(name, None, None, values ) + sat.append( nsiframework.SessionSecurityAttrType( [ at ] ) ) + + header = nsiframework.CommonHeaderType(protocol_type, correlation_id, requester_nsa_urn, provider_nsa_urn, reply_to, sat, connection_trace) header_element = header.xml(nsiframework.nsiHeader) return header_element @@ -134,7 +139,8 @@ def parseRequest(soap_data): if header.sessionSecurityAttr: for ssa in header.sessionSecurityAttr: for attr in ssa.Attributes: - security_attributes.append( (attr.Name, attr.AttributeValue) ) + for av in attr.AttributeValue: + security_attributes.append( nsa.SecurityAttribute(attr.Name, av) ) #if header.protocolVersion not in [ cnt.CS2_REQUESTER, cnt.CS2_PROVIDER ]: # raise ValueError('Invalid protocol "%s". Only %s supported' % (header.protocolVersion, cnt.CS2_SERVICE_TYPE)) @@ -147,7 +153,7 @@ def parseRequest(soap_data): body = [ nsiconnection.parseElement(b) for b in bodies ] nsi_header = nsa.NSIHeader(header.requesterNSA, header.providerNSA, header.correlationId, header.replyTo, - session_security_attrs=security_attributes, connection_trace=header.connectionTrace) + security_attributes=security_attributes, connection_trace=header.connectionTrace) return nsi_header, body diff --git a/opennsa/protocols/nsi2/requesterclient.py b/opennsa/protocols/nsi2/requesterclient.py index 44adaab7bea625783aefcfc5167f7df4200c60ae..00f2f0a6675e028fbda42516a9d49b46e0c997cb 100644 --- a/opennsa/protocols/nsi2/requesterclient.py +++ b/opennsa/protocols/nsi2/requesterclient.py @@ -51,7 +51,7 @@ class RequesterClient: def _createGenericRequestType(self, body_element_name, header, connection_id): header_element = helper.createProviderHeader(header.requester_nsa, header.provider_nsa, self.reply_to, header.correlation_id, - header.session_security_attrs, header.connection_trace) + header.security_attributes, header.connection_trace) body_element = nsiconnection.GenericRequestType(connection_id).xml(body_element_name) @@ -100,7 +100,7 @@ class RequesterClient: # payload construction header_element = helper.createProviderHeader(header.requester_nsa, header.provider_nsa, self.reply_to, header.correlation_id, - header.session_security_attrs, header.connection_trace) + header.security_attributes, header.connection_trace) schedule = criteria.schedule sd = criteria.service_def @@ -197,7 +197,7 @@ class RequesterClient: self._checkHeader(header) header_element = helper.createProviderHeader(header.requester_nsa, header.provider_nsa, reply_to=self.reply_to, correlation_id=header.correlation_id, - session_security_attributes=header.session_security_attrs, connection_trace=header.connection_trace) + security_attributes=header.security_attributes, connection_trace=header.connection_trace) query_type = nsiconnection.QueryType(connection_ids, global_reservation_ids) body_element = query_type.xml(nsiconnection.querySummary) @@ -217,7 +217,7 @@ class RequesterClient: # don't need to check header here header_element = helper.createProviderHeader(header.requester_nsa, header.provider_nsa, reply_to=self.reply_to, correlation_id=header.correlation_id, - session_security_attributes=header.session_security_attrs, connection_trace=header.connection_trace) + security_attributes=header.security_attributes, connection_trace=header.connection_trace) query_type = nsiconnection.QueryType(connection_ids, global_reservation_ids) body_element = query_type.xml(nsiconnection.querySummarySync) diff --git a/test/test_providers.py b/test/test_providers.py index e73734d2a94b31c7094e1636704908e244866de9..827015a657a951460ff26ae637d777f92fa3bb5a 100644 --- a/test/test_providers.py +++ b/test/test_providers.py @@ -520,7 +520,8 @@ class AggregatorTest(GenericProviderTest, unittest.TestCase): requester_agent = nsa.NetworkServiceAgent('test-requester:nsa', 'dud_endpoint1') provider_agent = nsa.NetworkServiceAgent(GenericProviderTest.base + ':nsa', 'dud_endpoint2') - header = nsa.NSIHeader(requester_agent.urn(), provider_agent.urn(), connection_trace= [ requester_agent.urn() + ':1' ]) + header = nsa.NSIHeader(requester_agent.urn(), provider_agent.urn(), connection_trace= [ requester_agent.urn() + ':1' ], + security_attributes = [ nsa.SecurityAttribute('user', 'testuser') ] ) def setUp(self): @@ -579,7 +580,8 @@ class RemoteProviderTest(GenericProviderTest, unittest.TestCase): requester_agent = nsa.NetworkServiceAgent('test-requester:nsa', 'http://localhost:%i/NSI/services/RequesterService2' % REQUESTER_PORT) provider_agent = nsa.NetworkServiceAgent(GenericProviderTest.base + ':nsa', 'http://localhost:%i/NSI/services/CS2' % PROVIDER_PORT) - header = nsa.NSIHeader(requester_agent.urn(), provider_agent.urn(), reply_to=requester_agent.endpoint, connection_trace=requester_agent.urn() + ':1') + header = nsa.NSIHeader(requester_agent.urn(), provider_agent.urn(), reply_to=requester_agent.endpoint, connection_trace=requester_agent.urn() + ':1', + security_attributes = [ nsa.SecurityAttribute('user', 'testuser') ] ) def setUp(self): from twisted.web import resource, server