def new_from_migration(self, actor_type, state, prev_connections=None, callback=None): """Instantiate an actor of type 'actor_type' and apply the 'state' to the actor.""" try: _log.analyze(self.node.id, "+", state) subject_attributes = state.pop('subject_attributes', None) migration_info = state.pop('migration_info', None) try: state['_managed'].remove('subject_attributes') state['_managed'].remove('migration_info') except: pass if security_enabled(): security = Security(self.node) security.set_subject_attributes(subject_attributes) else: security = None actor_def, signer = self.lookup_and_verify(actor_type, security) requirements = actor_def.requires if hasattr(actor_def, "requires") else [] self.check_requirements_and_sec_policy(requirements, security, state['id'], signer, migration_info, CalvinCB(self.new, actor_type, None, state, prev_connections, callback=callback, actor_def=actor_def, security=security)) except Exception: # Still want to create shadow actor. self.new(actor_type, None, state, prev_connections, callback=callback, shadow_actor=True)
def _new_actor(self, actor_type, class_=None, actor_id=None, security=None, access_decision=None, shadow_actor=False): """Return a 'bare' actor of actor_type, raises an exception on failure.""" if security_enabled() and not access_decision: _log.debug( "Security policy check for actor failed, access_decision={}". format(access_decision)) shadow_actor = True if shadow_actor: class_ = ShadowActor if class_ is None: try: class_, signer = self.lookup_and_verify(actor_type, security) except Exception: class_ = ShadowActor try: # Create a 'bare' instance of the actor a = class_(actor_type, actor_id=actor_id, security=security) except Exception as e: _log.error("The actor %s(%s) can't be instantiated." % (actor_type, class_.__init__)) raise (e) if isinstance(access_decision, tuple): # Authorization checks needed if access_decision is a tuple. a.set_authorization_checks(access_decision[1]) return a
def _new_actor(self, actor_type, class_=None, actor_id=None, security=None, access_decision=None, shadow_actor=False): """Return a 'bare' actor of actor_type, raises an exception on failure.""" if security_enabled() and not access_decision: try: _log.debug("Security policy check for actor failed") raise Exception("Security policy check for actor failed") except: shadow_actor = True if shadow_actor: class_ = ShadowActor if class_ is None: try: class_, signer = self.lookup_and_verify(actor_type, security) except Exception: class_ = ShadowActor try: # Create a 'bare' instance of the actor a = class_(actor_type, actor_id=actor_id, security=security) except Exception as e: _log.error("The actor %s(%s) can't be instantiated." % (actor_type, class_.__init__)) raise(e) a._calvinsys = self.node.calvinsys() if isinstance(access_decision, tuple): # Authorization checks needed if access_decision is a tuple. a.set_authorization_checks(access_decision[1]) return a
def add_node(self, node, cb=None): """ Add node to storage """ self.set(prefix="node-", key=node.id, value={"uri": node.external_uri, "control_uri": node.external_control_uri, "authz_server": node.authorization.authz_server_id, "attributes": {'public': node.attributes.get_public(), 'indexed_public': node.attributes.get_indexed_public(as_list=False)}}, cb=cb) self._add_node_index(node) # Store all actors on this node in storage GlobalStore(node=node, security=Security(node) if security_enabled() else None, verify=False).export()
def check_requirements_and_sec_policy(self, requirements, security=None, actor_id=None, signer=None, decision_from_migration=None, callback=None): """Check requirements and security policy for actor.""" # Check if node has the capabilities required by the actor. for req in requirements: if not self.node.calvinsys().has_capability(req): raise Exception("Actor requires %s" % req) if security_enabled(): # Check if access is permitted for the actor by the security policy. # Will continue directly with callback if authorization is not enabled. security.check_security_policy(callback, "actor", actor_id, ['runtime'] + requirements, signer, decision_from_migration) else: callback()
def check_requirements_and_sec_policy(self, requirements, security=None, actor_id=None, signer=None, decision_from_migration=None, callback=None): for req in requirements: if not get_calvinsys().has_capability(req) and not get_calvinlib().has_capability(req): raise Exception("Actor requires %s" % req) if security_enabled(): # Check if access is permitted for the actor by the security policy. # Will continue directly with callback if authorization is not enabled. security.check_security_policy(callback, actor_id=actor_id, requires=['runtime'] + requirements, element_type="actor", element_value=signer, decision_from_migration=decision_from_migration) else: callback()
def wrapper(self, handle, connection, match, data, hdr): def _unauthorized_response(): self.send_response(handle, connection, None, status=calvinresponse.UNAUTHORIZED) def _handle_authentication_decision(authentication_decision): _log.debug("control_apis.authentication::_handle_authentication_decision, authentication_decision={}".format(authentication_decision)) if not authentication_decision: _unauthorized_response() return try: self.security.check_security_policy( CalvinCB(_handle_policy_decision), element_type="control_interface", element_value=arguments['func'].__name__ ) except Exception as exc: _log.exception("Failed to check security policy, exc={}".format(exc)) _unauthorized_response() def _handle_policy_decision(access_decision): if not access_decision: _unauthorized_response() return # Yay! We made it! func(self, handle, connection, match, data, hdr) # # Exit early if security disabled # if not security_enabled(): func(self, handle, connection, match, data, hdr) return # # Verify the request against credentials and policy # credentials = None arguments={'func':func, 'self':self, 'handle':handle, 'connection':connection, 'match':match, 'data':data, 'hdr':hdr} try: if 'authorization' in hdr: cred = b64decode(hdr['authorization'].strip('Basic ')).split(':') credentials ={'user':cred[0], 'password':cred[1]} except TypeError as err: _log.error("_verify_permission: Missing or wrongly formatted credentials in request header") # Continue without credentials, policy might allow access try: self.security.authenticate_subject(credentials, callback=CalvinCB(_handle_authentication_decision)) except Exception as exc: _log.exception("Failed to authenticate the subject, exc={}".format(exc)) _unauthorized_response()
def new_from_migration(self, actor_type, state, prev_connections=None, connection_list=None, callback=None): """Instantiate an actor of type 'actor_type' and apply the 'state' to the actor.""" try: _log.analyze(self.node.id, "+", state) subject_attributes = state['security'].pop('_subject_attributes', None) migration_info = state['private'].pop('_migration_info', None) try: state['security'].remove('_subject_attributes') state['private'].remove('_migration_info') except: pass if security_enabled(): security = Security(self.node) security.set_subject_attributes(subject_attributes) else: security = None actor_def, signer = self.lookup_and_verify(actor_type, security) requirements = actor_def.requires if hasattr( actor_def, "requires") else [] self.check_requirements_and_sec_policy( requirements, security, state['private']['_id'], signer, migration_info, CalvinCB(self.new, actor_type, None, state, prev_connections=prev_connections, connection_list=connection_list, callback=callback, actor_def=actor_def, security=security)) except Exception: # Still want to create shadow actor. self.new(actor_type, None, state, prev_connections=prev_connections, connection_list=connection_list, callback=callback, shadow_actor=True)
def check_requirements_and_sec_policy(self, requirements, security=None, actor_id=None, signer=None, decision_from_migration=None, callback=None): for req in requirements: if not get_calvinsys().has_capability( req) and not get_calvinlib().has_capability(req): raise Exception("Actor requires %s" % req) if security_enabled(): # Check if access is permitted for the actor by the security policy. # Will continue directly with callback if authorization is not enabled. security.check_security_policy( callback, actor_id=actor_id, requires=['runtime'] + requirements, element_type="actor", element_value=signer, decision_from_migration=decision_from_migration) else: callback()
def compile_script_check_security(data, filename, cb, security=None, content=None, verify=True, node=None, signature=None): """ Compile a script and return a tuple (deployable, errors, warnings). 'credentials' are optional security credentials(?) 'verify' is deprecated and will be removed 'node' is the runtime performing security check(?) 'cb' is a CalvinCB callback N.B. If callback 'cb' is given, this method calls cb(deployable, errors, warnings) and returns None N.B. If callback 'cb' is given, and method runs to completion, cb is called with additional parameter 'security' (?) """ def _exit_with_error(callback): """Helper method to generate a proper error""" it = IssueTracker() it.add_error("UNAUTHORIZED", info={'status': 401}) callback({}, it) return def _handle_policy_decision(data, appname, verify, access_decision, org_cb, security=None): if not access_decision: _log.error("Access denied") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) return if 'app_info' not in data and 'script' in data: deployable, issuetracker = compile_script(data['script'], appname) elif 'app_info' in data: deployable = data['app_info'] issuetracker = IssueTracker() else: _log.error("Neither app_info or script supplied") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) return org_cb(deployable, issuetracker, security=security) # # Actual code for compile_script # appname = appname_from_filename(filename) # FIXME: if node is None we bypass security even if enabled. Is that the intention? if node is not None and security_enabled(): # FIXME: If cb is None, we will return from this method with None instead of a tuple, failing silently if security: sec = security else: sec = Security(node) verified, signer = sec.verify_signature_content(content, "application") if not verified: # Verification not OK if sign or cert not OK. _log.error("Failed application verification") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(cb) return sec.check_security_policy(CalvinCB(_handle_policy_decision, data, appname, verify, security=security, org_cb=cb), element_type="application", element_value=signer) return # # We get here if node is None, or security is disabled # # This used to be # _handle_policy_decision(data, filename, verify, access_decision=True, security=None, org_cb=cb) # but since _handle_policy_decision is called with access_decision=True, security=None only compile_script would be called if 'app_info' not in data and 'script' in data: deployable, issuetracker = compile_script(data['script'], appname) elif 'app_info' in data: deployable = data['app_info'] issuetracker = IssueTracker() else: _log.error("Neither app_info or script supplied") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) return cb(deployable, issuetracker, security=None)
def compile_script_check_security(data, filename, cb, security=None, content=None, verify=True, node=None, signature=None): """ Compile a script and return a tuple (deployable, errors, warnings). 'credentials' are optional security credentials(?) 'verify' is deprecated and will be removed 'node' is the runtime performing security check(?) 'cb' is a CalvinCB callback N.B. If callback 'cb' is given, this method calls cb(deployable, errors, warnings) and returns None N.B. If callback 'cb' is given, and method runs to completion, cb is called with additional parameter 'security' (?) """ def _exit_with_error(callback): """Helper method to generate a proper error""" it = IssueTracker() it.add_error("UNAUTHORIZED", info={'status':401}) callback({}, it) return def _handle_policy_decision(data, appname, verify, access_decision, org_cb, security=None): if not access_decision: _log.error("Access denied") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) return if 'app_info' not in data and 'script' in data: deployable, issuetracker = compile_script(data['script'], appname) elif 'app_info' in data: deployable = data['app_info'] issuetracker = IssueTracker() else: _log.error("Neither app_info or script supplied") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) return org_cb(deployable, issuetracker, security=security) # # Actual code for compile_script # appname = appname_from_filename(filename) # FIXME: if node is None we bypass security even if enabled. Is that the intention? if node is not None and security_enabled(): # FIXME: If cb is None, we will return from this method with None instead of a tuple, failing silently if security: sec = security else: sec = Security(node) verified, signer = sec.verify_signature_content(content, "application") if not verified: # Verification not OK if sign or cert not OK. _log.error("Failed application verification") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(cb) return sec.check_security_policy( CalvinCB(_handle_policy_decision, data, appname, verify, security=security, org_cb=cb), element_type = "application", element_value = signer ) return # # We get here if node is None, or security is disabled # # This used to be # _handle_policy_decision(data, filename, verify, access_decision=True, security=None, org_cb=cb) # but since _handle_policy_decision is called with access_decision=True, security=None only compile_script would be called if 'app_info' not in data and 'script' in data: deployable, issuetracker = compile_script(data['script'], appname) elif 'app_info' in data: deployable = data['app_info'] issuetracker = IssueTracker() else: _log.error("Neither app_info or script supplied") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) return cb(deployable, issuetracker, security=None)
def handle_deploy(self, handle, connection, match, data, hdr): """ POST /deploy Compile and deploy a calvin script to this calvin node Apply deployment requirements to actors of an application and initiate migration of actors accordingly Body: { "name": <application name>, "script": <calvin script> # alternativly "app_info" "app_info": <compiled script as app_info> # alternativly "script" "sec_sign": {<cert hash>: <security signature of script>, ...} # optional and only with "script" "sec_credentials": <security credentials of user> # optional "deploy_info": {"groups": {"<group 1 name>": ["<actor instance 1 name>", ...]}, # TODO not yet implemented "requirements": { "<actor instance 1 name>": [ {"op": "<matching rule name>", "kwargs": {<rule param key>: <rule param value>, ...}, "type": "+" or "-" for set intersection or set removal, respectively }, ... ], ... } } } Note that either a script or app_info must be supplied. Optionally security verification of application script can be made. Also optionally user credentials can be supplied, some runtimes are configured to require credentials. The credentials takes for example the following form: {"user": <username>, "password": <password>, "role": <role>, "group": <group>, ... } The matching rules are implemented as plug-ins, intended to be extended. The type "+" is "and"-ing rules together (actually the intersection of all possible nodes returned by the rules.) The type "-" is explicitly removing the nodes returned by this rule from the set of possible nodes. Note that only negative rules will result in no possible nodes, i.e. there is no implied "all but these." A special matching rule exist, to first form a union between matching rules, i.e. alternative matches. This is useful for e.g. alternative namings, ownerships or specifying either of two specific nodes. {"op": "union_group", "requirements": [list as above of matching rules but without type key] "type": "+" } Other matching rules available is current_node, all_nodes and node_attr_match which takes an index param which is attribute formatted, e.g. {"op": "node_attr_match", "kwargs": {"index": ["node_name", {"organization": "org.testexample", "name": "testNode1"}]} "type": "+" } Response status code: OK, CREATED, BAD_REQUEST, UNAUTHORIZED or INTERNAL_ERROR Response: {"application_id": <application-id>, "actor_map": {<actor name with namespace>: <actor id>, ...} "placement": {<actor_id>: <node_id>, ...}, "requirements_fulfilled": True/False} Failure response: {'errors': <compilation errors>, 'warnings': <compilation warnings>, 'exception': <exception string>} """ try: _log.analyze(self.node.id, "+", data) if 'app_info' not in data: kwargs = {} credentials = "" # Supply security verification data when available content = None if "sec_credentials" in data: credentials = data['sec_credentials'] content = {} if not "sec_sign" in data: data['sec_sign'] = {} content = { 'file': data["script"], 'sign': { h: s.decode('hex_codec') for h, s in data['sec_sign'].iteritems() } } compiler.compile_script_check_security( data["script"], filename=data["name"], security=self.security, content=content, node=self.node, verify=(data["check"] if "check" in data else True), cb=CalvinCB(self.handle_deploy_cont, handle=handle, connection=connection, data=data), **kwargs) else: # Supplying app_info is for backward compatibility hence abort if node configured security # Main user is csruntime when deploying script at the same time and some tests used # via calvin.Tools.deployer (the Deployer below is the new in appmanager) # TODO rewrite these users to send the uncompiled script as cscontrol does. if security_enabled(): _log.error( "Can't combine compiled script with runtime having security" ) self.send_response(handle, connection, None, status=calvinresponse.UNAUTHORIZED) return app_info = data['app_info'] issuetracker = IssueTracker() self.handle_deploy_cont(app_info, issuetracker, handle, connection, data) except Exception as e: _log.exception("Deployer failed, e={}".format(e)) self.send_response(handle, connection, json.dumps({'exception': str(e)}), status=calvinresponse.INTERNAL_ERROR)
def compile_script_check_security(source_text, filename, cb, credentials=None, verify=True, node=None): """ Compile a script and return a tuple (deployable, errors, warnings). 'credentials' are optional security credentials(?) 'verify' is deprecated and will be removed 'node' is the runtime performing security check(?) 'cb' is a CalvinCB callback N.B. If callback 'cb' is given, this method calls cb(deployable, errors, warnings) and returns None N.B. If callback 'cb' is given, and method runs to completion, cb is called with additional parameter 'security' (?) """ def _exit_with_error(callback): """Helper method to generate a proper error""" it = IssueTracker() it.add_error("UNAUTHORIZED", info={'status': 401}) callback({}, it) def _handle_authentication_decision(source_text, appname, verify, authentication_decision, security, org_cb, content=None): if not authentication_decision: _log.error("Authentication failed") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) verified, signer = security.verify_signature_content( content, "application") if not verified: # Verification not OK if sign or cert not OK. _log.error("Failed application verification") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) security.check_security_policy(CalvinCB(_handle_policy_decision, source_text, appname, verify, security=security, org_cb=org_cb), "application", signer=signer) def _handle_policy_decision(source_text, appname, verify, access_decision, org_cb, security=None): if not access_decision: _log.error("Access denied") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) deployable, issutracker = compile_script(source_text, appname) org_cb(deployable, issutracker, security=security) # # Actual code for compile_script # appname = appname_from_filename(filename) # FIXME: if node is None we bypass security even if enabled. Is that the intention? if node is not None and security_enabled(): if credentials: content = Security.verify_signature_get_files(filename, skip_file=True) # content is ALWAYS a dict if skip_file is True content['file'] = source_text else: content = None # FIXME: If cb is None, we will return from this method with None instead of a tuple, failing silently sec = Security(node) sec.authenticate_subject(credentials, callback=CalvinCB( _handle_authentication_decision, source_text, appname, verify, security=sec, org_cb=cb, content=content)) return # # We get here if node is None, or security is disabled # # This used to be # _handle_policy_decision(source_text, filename, verify, access_decision=True, security=None, org_cb=cb) # but since _handle_policy_decision is called with access_decision=True, security=None only compile_script would be called deployable, issuetracker = compile_script(source_text, appname) cb(deployable, issuetracker, security=None)
def wrapper(self, handle, connection, match, data, hdr): def _unauthorized_response(): self.send_response(handle, connection, None, status=calvinresponse.UNAUTHORIZED) def _handle_authentication_decision(authentication_decision): _log.debug( "control_apis.authentication::_handle_authentication_decision, authentication_decision={}" .format(authentication_decision)) if not authentication_decision: _unauthorized_response() return try: self.security.check_security_policy( CalvinCB(_handle_policy_decision), element_type="control_interface", element_value=arguments['func'].__name__) except Exception as exc: _log.exception( "Failed to check security policy, exc={}".format(exc)) _unauthorized_response() def _handle_policy_decision(access_decision): if not access_decision: _unauthorized_response() return # Yay! We made it! func(self, handle, connection, match, data, hdr) # # Exit early if security disabled # if not security_enabled(): func(self, handle, connection, match, data, hdr) return # # Verify the request against credentials and policy # credentials = None arguments = { 'func': func, 'self': self, 'handle': handle, 'connection': connection, 'match': match, 'data': data, 'hdr': hdr } try: if 'authorization' in hdr: cred = b64decode( hdr['authorization'].strip('Basic ')).split(':') credentials = {'user': cred[0], 'password': cred[1]} except TypeError as err: _log.error( "_verify_permission: Missing or wrongly formatted credentials in request header" ) # Continue without credentials, policy might allow access try: self.security.authenticate_subject( credentials, callback=CalvinCB(_handle_authentication_decision)) except Exception as exc: _log.exception( "Failed to authenticate the subject, exc={}".format(exc)) _unauthorized_response()
def compile_script_check_security(source_text, filename, cb, credentials=None, verify=True, node=None): """ Compile a script and return a tuple (deployable, errors, warnings). 'credentials' are optional security credentials(?) 'verify' is deprecated and will be removed 'node' is the runtime performing security check(?) 'cb' is a CalvinCB callback N.B. If callback 'cb' is given, this method calls cb(deployable, errors, warnings) and returns None N.B. If callback 'cb' is given, and method runs to completion, cb is called with additional parameter 'security' (?) """ def _exit_with_error(callback): """Helper method to generate a proper error""" it = IssueTracker() it.add_error("UNAUTHORIZED", info={'status':401}) callback({}, it) def _handle_authentication_decision(source_text, appname, verify, authentication_decision, security, org_cb, content=None): if not authentication_decision: _log.error("Authentication failed") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) verified, signer = security.verify_signature_content(content, "application") if not verified: # Verification not OK if sign or cert not OK. _log.error("Failed application verification") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) security.check_security_policy( CalvinCB(_handle_policy_decision, source_text, appname, verify, security=security, org_cb=org_cb), "application", signer=signer ) def _handle_policy_decision(source_text, appname, verify, access_decision, org_cb, security=None): if not access_decision: _log.error("Access denied") # This error reason is detected in calvin control and gives proper REST response _exit_with_error(org_cb) deployable, issutracker = compile_script(source_text, appname) org_cb(deployable, issutracker, security=security) # # Actual code for compile_script # appname = appname_from_filename(filename) # FIXME: if node is None we bypass security even if enabled. Is that the intention? if node is not None and security_enabled(): if credentials: content = Security.verify_signature_get_files(filename, skip_file=True) # content is ALWAYS a dict if skip_file is True content['file'] = source_text else: content = None # FIXME: If cb is None, we will return from this method with None instead of a tuple, failing silently sec = Security(node) sec.authenticate_subject( credentials, callback=CalvinCB(_handle_authentication_decision, source_text, appname, verify, security=sec, org_cb=cb, content=content) ) return # # We get here if node is None, or security is disabled # # This used to be # _handle_policy_decision(source_text, filename, verify, access_decision=True, security=None, org_cb=cb) # but since _handle_policy_decision is called with access_decision=True, security=None only compile_script would be called deployable, issuetracker = compile_script(source_text, appname) cb(deployable, issuetracker, security=None)