1

I am trying to secure my RpyC server connections through username and password. The documentation indeed shows an example, but it is too brief. No details were given on how exactly the password is passed from the client-side. Anyone figured out how to do that? Thanks in advance.

m0hithreddy
  • 1,752
  • 1
  • 10
  • 17
  • Hi, I have a similar need now. Have you came up with any solution/suggestion? – ylcnky Mar 25 '22 at 21:09
  • @ylcnky I put that on hold since I am the only consumer of my rpyc service. You can look at [SSL based authenticators](https://rpyc.readthedocs.io/en/latest/api/utils_authenticators.html#rpyc.utils.authenticators.SSLAuthenticator). But at high level I feel we need to override some rpyc internal methods on the client side and send the auth info. For example this might be a qualified [entry point](https://github.com/tomerfiliba-org/rpyc/blob/7ea2d24ab70840fb1324c0d5c8b052fcaca7f635/rpyc/core/stream.py#L121) since ``rpyc.connect`` creates a SocketStream object. – m0hithreddy Mar 26 '22 at 10:33
  • @ylcnky I got curious and tested me theory, I posted an answer. I hope it will help you. – m0hithreddy Mar 27 '22 at 14:03

1 Answers1

5

Answering my own question:

I had to override some internal methods of RPyC on the client side to achieve the desired behaviour. I don't know if much cleaner solution exists, but this seems to be a plausible one.

Server:

import rpyc
from rpyc.utils.authenticators import AuthenticationError

def magic_word_authenticator(sock):
    if sock.recv(5).decode() != "Ma6ik":
        raise AuthenticationError("wrong magic word")
    return sock, None

class SecuredService(rpyc.Service):

    def exposed_secured_op(self):
        return 'Secret String'

rpyc.ThreadedServer(
    service=SecuredService, hostname='localhost',
    port=18812, authenticator=magic_word_authenticator
).start()

Client:

import rpyc
import traceback


class AuthSocketStream(rpyc.SocketStream):

    @classmethod
    def connect(cls, *args, authorizer=None, **kwargs):
        stream_obj =  super().connect(*args, **kwargs)

        if callable(authorizer):
            authorizer(stream_obj.sock)

        return stream_obj


def rpyc_connect(host, port, service=rpyc.VoidService, config={}, ipv6=False, keepalive=False, authorizer=None):
    s = AuthSocketStream.connect(
            host, port, ipv6=ipv6, keepalive=keepalive,
            authorizer=authorizer
    )

    return rpyc.connect_stream(s, service, config)

print('With correct authorizer')

conn1 = rpyc_connect(
        'localhost', 18812, authorizer=lambda sock: sock.send('Ma6ik'.encode())
)

print(conn1.root.secured_op())

print('With wrong authorizer')

conn2 = rpyc_connect(
        'localhost', 18812, authorizer=lambda sock: sock.send('Invalid'.encode())
)

try:
    conn2.root
except Exception:
    print(traceback.format_exc())


print('With no authorizer')

conn3 = rpyc_connect(
        'localhost', 18812
)

try:
    conn3.root
except Exception:
    print(traceback.format_exc())

Client Console Log:

With correct authorizer
Secret String
With wrong authorizer
Traceback (most recent call last):
  File "/home/client.py", line 40, in <module>
    conn2.root
  File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 507, in root
    self._remote_root = self.sync_request(consts.HANDLE_GETROOT)
  File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 474, in sync_request
    return self.async_request(handler, *args, timeout=timeout).value
  File "/usr/lib/python3.10/site-packages/rpyc/core/async_.py", line 101, in value
    self.wait()
  File "/usr/lib/python3.10/site-packages/rpyc/core/async_.py", line 48, in wait
    self._conn.serve(self._ttl)
  File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 387, in serve
    data = self._channel.poll(timeout) and self._channel.recv()
  File "/usr/lib/python3.10/site-packages/rpyc/core/channel.py", line 55, in recv
    header = self.stream.read(self.FRAME_HEADER.size)
  File "/usr/lib/python3.10/site-packages/rpyc/core/stream.py", line 260, in read
    raise EOFError("connection closed by peer")
EOFError: connection closed by peer

With no authorizer
Traceback (most recent call last):
  File "/home/client.py", line 52, in <module>
    conn3.root
  File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 507, in root
    self._remote_root = self.sync_request(consts.HANDLE_GETROOT)
  File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 474, in sync_request
    return self.async_request(handler, *args, timeout=timeout).value
  File "/usr/lib/python3.10/site-packages/rpyc/core/async_.py", line 101, in value
    self.wait()
  File "/usr/lib/python3.10/site-packages/rpyc/core/async_.py", line 48, in wait
    self._conn.serve(self._ttl)
  File "/usr/lib/python3.10/site-packages/rpyc/core/protocol.py", line 387, in serve
    data = self._channel.poll(timeout) and self._channel.recv()
  File "/usr/lib/python3.10/site-packages/rpyc/core/channel.py", line 55, in recv
    header = self.stream.read(self.FRAME_HEADER.size)
  File "/usr/lib/python3.10/site-packages/rpyc/core/stream.py", line 260, in read
    raise EOFError("connection closed by peer")
EOFError: connection closed by peer
m0hithreddy
  • 1,752
  • 1
  • 10
  • 17