async def start(self): """ Start offering and selling data encryption keys in the background. """ assert self._run_loop is None self.log.info( 'Starting key rotation every {interval} seconds for api_id="{api_id}" ..', interval=hl(self._interval), api_id=hl(uuid.UUID(bytes=self._api_id))) self.running = True self._run_loop = LoopingCall( lambda: ensureDeferred(self._rotate())) self._started = self._run_loop.start(self._interval) return self._started
async def start(self): """ Start offering and selling data encryption keys in the background. """ assert not self.running self.log.info( 'Starting key rotation every {interval} seconds for api_id="{api_id}" ..', interval=hl(self._interval), api_id=hl(uuid.UUID(bytes=self._api_id))) self.running = True async def rotate_with_interval(): while self.running: await self._rotate() await asyncio.sleep(self._interval) asyncio.create_task(rotate_with_interval())
async def authenticate(realm, authid, details): """ this is our dynamic authenticator procedure that will be called by Crossbar.io when a session is authenticating """ log.info( 'authenticate(realm="{realm}", authid="{authid}", details={details}) {func}', realm=hl(realm), authid=hl(authid), details=details, func=hltype(create_rlink_authenticator), ) assert ('authmethod' in details) assert (details['authmethod'] == 'cryptosign') assert ('authextra' in details) assert ('pubkey' in details['authextra']) pubkey = details['authextra']['pubkey'] log.info( 'authenticating session using realm="{realm}", pubkey={pubkey} .. {func}', realm=hl(realm), pubkey=hl(pubkey), func=hltype(create_rlink_authenticator), ) if pubkey in pubkey_to_principals: principal = pubkey_to_principals[pubkey] auth = { 'pubkey': pubkey, 'realm': principal['realm'], 'authid': principal['authid'], 'role': principal['role'], 'extra': principal['extra'], 'cache': True } # Note: with WAMP-cryptosign, even though a client may or may not request a `realm`, but in any case, the # effective realm the client is authenticated will be returned in the principal `auth['role']` (!) effective_realm = auth['realm'] log.info( 'found valid principal authid="{authid}", authrole="{authrole}", realm="{realm}" matching given client public key {func}', func=hltype(create_rlink_authenticator), authid=hl(auth['authid']), authrole=hl(auth['role']), realm=hl(effective_realm), ) # only now that we know the effective realm a client is to be joined to (see above), maybe active (start) # the desired application realm to let the client join to subsequently # await _maybe_activate_realm(controller, effective_realm) return auth else: msg = 'no principal with matching public key 0x{}'.format(pubkey) log.warn(msg) raise ApplicationError('com.example.no_such_user', msg)
class RLinkRemoteSession(BridgeSession): """ This session is the remote leg of the router-to-router link. """ log = make_logger() IS_REMOTE_LEG = True # directory in which events are flowing (published via this session DIR = hl('from local to remote', color='yellow', bold=True) def __init__(self, config): BridgeSession.__init__(self, config) # import here to resolve import dependency issues from crossbar.worker.router import RouterController self._subs = {} self._rlink_manager: RLinkManager = self.config.extra['rlink_manager'] self._router_controller: RouterController = self._rlink_manager.controller # FIXME: async? see below def onConnect(self): self.log.info('{func}() ...', func=hltype(self.onConnect)) authid = self.config.extra.get('authid', None) authrole = self.config.extra.get('authrole', None) authextra = self.config.extra.get('authextra', {}) # FIXME: use cryptosign-proxy authmethods = ['cryptosign'] # use WorkerController.get_public_key to call node controller # FIXME: the following does _not_ work with onConnect (?!) # _public_key = await self._router_controller.get_public_key() def actually_join(_public_key): authextra.update({ # forward the client pubkey: this allows us to omit authid as # the router can identify us with the pubkey already 'pubkey': _public_key, # not yet implemented. a public key the router should provide # a trustchain for its public key. the trustroot can eg be # hard-coded in the client, or come from a command line option. 'trustroot': None, # not yet implemented. for authenticating the router, this # challenge will need to be signed by the router and send back # in AUTHENTICATE for client to verify. A string with a hex # encoded 32 bytes random value. 'challenge': None, # https://tools.ietf.org/html/rfc5929 'channel_binding': 'tls-unique' }) self.log.info( '{func} joining with realm="{realm}", authmethods={authmethods}, authid="{authid}", authrole="{authrole}", authextra={authextra}', func=hltype(self.onConnect), realm=hlval(self.config.realm), authmethods=hlval(authmethods), authid=hlval(authid), authrole=hlval(authrole), authextra=authextra) self.join(self.config.realm, authmethods=authmethods, authid=authid, authrole=authrole, authextra=authextra) res = self._rlink_manager._controller.get_public_key() res.addCallback(actually_join) self.log.info('{func}() done (res={res}).', func=hltype(self.onConnect), res=res) return res # FIXME: async? see below def onChallenge(self, challenge): self.log.debug('{func}(challenge={challenge})', func=hltype(self.onChallenge), challenge=challenge) if challenge.method == 'cryptosign': # alright, we've got a challenge from the router. # sign the challenge with our private key. channel_id_type = 'tls-unique' channel_id_map = self._router_controller._transport.transport_details.channel_id if channel_id_type in channel_id_map: channel_id = channel_id_map[channel_id_type] else: channel_id = None channel_id_type = None # use WorkerController.get_public_key to call node controller # FIXME: await? signed_challenge = self._router_controller.sign_challenge( challenge, channel_id, channel_id_type) # send back the signed challenge for verification return signed_challenge else: raise Exception( 'internal error: we asked to authenticate using wamp-cryptosign, but now received a challenge for {}' .format(challenge.method)) @inlineCallbacks def onJoin(self, details): self.log.debug('{klass}.onJoin(details={details})', klass=self.__class__.__name__, details=details) assert self.config.extra and 'on_ready' in self.config.extra assert self.config.extra and 'other' in self.config.extra local = self.config.extra['other'] assert isinstance(local, RLinkLocalSession) local._tracker.connected = True self._exclude_authid = self.config.extra.get('exclude_authid', None) self._exclude_authrole = self.config.extra.get('exclude_authrole', None) # setup remote->local event forwarding forward_events = self.config.extra.get('forward_events', False) if forward_events: yield self._setup_event_forwarding(local) # setup remote->local invocation forwarding forward_invocations = self.config.extra.get('forward_invocations', False) if forward_invocations: yield self._setup_invocation_forwarding(local) self.log.info( '{klass}.onJoin(): rlink remote session ready (forward_events={forward_events}, forward_invocations={forward_invocations}, realm={realm}, authid={authid}, authrole={authrole}, session={session}) {method}', klass=self.__class__.__name__, method=hltype(RLinkRemoteSession.onJoin), forward_events=hluserid(forward_events), forward_invocations=hluserid(forward_invocations), realm=hluserid(details.realm), authid=hluserid(details.authid), authrole=hluserid(details.authrole), session=hlid(details.session)) # we are ready! on_ready = self.config.extra.get('on_ready', None) if on_ready and not on_ready.called: self.config.extra['on_ready'].callback(self) @inlineCallbacks def onLeave(self, details): # When the rlink is going down, make sure to unsubscribe to # all events that are subscribed on the local-leg. # This avoids duplicate events that would otherwise arrive # See: https://github.com/crossbario/crossbar/issues/1916 for k, v in self._subs.items(): if v['sub'].active: yield v['sub'].unsubscribe() self._subs = {} self.config.extra['other']._tracker.connected = False self.log.warn( '{klass}.onLeave(): rlink remote session left! (realm={realm}, authid={authid}, authrole={authrole}, session={session}, details={details}) {method}', klass=self.__class__.__name__, method=hltype(RLinkLocalSession.onLeave), realm=hluserid(self.config.realm), authid=hluserid(self._authid), authrole=hluserid(self._authrole), session=hlid(self._session_id), details=details) BridgeSession.onLeave(self, details)
class RLinkLocalSession(BridgeSession): """ This session is the local leg of the router-to-router link and runs embedded inside the local router. """ log = make_logger() IS_REMOTE_LEG = False # direction in which events are flowing (published) via this session DIR = hl('from remote to local', color='yellow', bold=True) def onConnect(self): self.log.info('{klass}.onConnect()', klass=self.__class__.__name__) # _BridgeSession.onConnect(self) authextra = {'rlink': self.config.extra['rlink']} self.join(self.config.realm, authid=self.config.extra['rlink'], authextra=authextra) self._tracker = self.config.extra['tracker'] @inlineCallbacks def onJoin(self, details): assert self.config.extra and 'on_ready' in self.config.extra assert self.config.extra and 'other' in self.config.extra remote = self.config.extra['other'] assert isinstance(remote, RLinkRemoteSession) self._exclude_authid = self.config.extra.get('exclude_authid', None) self._exclude_authrole = self.config.extra.get('exclude_authrole', None) # setup local->remote event forwarding forward_events = self.config.extra.get('forward_events', False) if forward_events: yield self._setup_event_forwarding(remote) # setup local->remote invocation forwarding forward_invocations = self.config.extra.get('forward_invocations', False) if forward_invocations: yield self._setup_invocation_forwarding(remote) self.log.debug( 'Router link local session ready (forward_events={forward_events}, forward_invocations={forward_invocations}, realm={realm}, authid={authid}, authrole={authrole}, session={session}) {method}', method=hltype(RLinkLocalSession.onJoin), forward_events=hluserid(forward_events), forward_invocations=hluserid(forward_invocations), realm=hluserid(details.realm), authid=hluserid(details.authid), authrole=hluserid(details.authrole), session=hlid(details.session)) on_ready = self.config.extra.get('on_ready', None) if on_ready and not on_ready.called: self.config.extra['on_ready'].callback(self) def onLeave(self, details): self.log.warn( 'Router link local session down! (realm={realm}, authid={authid}, authrole={authrole}, session={session}, details={details}) {method}', method=hltype(RLinkLocalSession.onLeave), realm=hluserid(self.config.realm), authid=hluserid(self._authid), authrole=hluserid(self._authrole), details=details, session=hlid(self._session_id)) BridgeSession.onLeave(self, details)