0
# Authentication and Security
1
2
Authentication mechanisms and security features for securing RPyC connections and services. Includes SSL/TLS authentication, custom authenticators, and security configuration options for protecting distributed applications.
3
4
## Capabilities
5
6
### SSL/TLS Authentication
7
8
SSL/TLS-based authentication providing encrypted communication and client certificate verification.
9
10
```python { .api }
11
class SSLAuthenticator:
12
"""
13
SSL/TLS authenticator providing certificate-based authentication.
14
Supports client certificate verification and encrypted communication.
15
"""
16
17
def __init__(self, keyfile, certfile, ca_certs=None, cert_reqs=None,
18
ssl_version=None, ciphers=None):
19
"""
20
Initialize SSL authenticator.
21
22
Parameters:
23
- keyfile (str): Path to private key file
24
- certfile (str): Path to certificate file
25
- ca_certs (str): Path to CA certificates file (optional)
26
- cert_reqs: Certificate requirements (ssl.CERT_NONE, ssl.CERT_OPTIONAL, ssl.CERT_REQUIRED)
27
- ssl_version: SSL/TLS version to use
28
- ciphers (str): Cipher suites to allow
29
"""
30
31
def __call__(self, sock):
32
"""
33
Authenticate connection using SSL/TLS.
34
35
Parameters:
36
- sock: Socket to authenticate
37
38
Returns:
39
tuple: (wrapped_socket, credentials) or raises AuthenticationError
40
"""
41
```
42
43
### Custom Authentication
44
45
Base classes and utilities for implementing custom authentication mechanisms.
46
47
```python { .api }
48
class Authenticator:
49
"""
50
Base class for custom authenticators.
51
Subclass to implement custom authentication logic.
52
"""
53
54
def __call__(self, sock):
55
"""
56
Authenticate connection.
57
58
Parameters:
59
- sock: Socket to authenticate
60
61
Returns:
62
tuple: (socket, credentials) or raises AuthenticationError
63
64
Note: Must be implemented by subclasses
65
"""
66
raise NotImplementedError()
67
68
def create_authenticator(auth_func):
69
"""
70
Create authenticator from function.
71
72
Parameters:
73
- auth_func (callable): Function taking socket, returning (socket, credentials)
74
75
Returns:
76
Authenticator: Callable authenticator object
77
"""
78
```
79
80
### Security Configuration
81
82
Security-related configuration options and utilities.
83
84
```python { .api }
85
# Security configuration constants
86
SECURE_DEFAULT_CONFIG = {
87
'allow_all_attrs': False, # Restrict attribute access
88
'allow_pickle': False, # Disable pickle serialization
89
'allow_setattr': False, # Disable attribute setting
90
'allow_delattr': False, # Disable attribute deletion
91
'allow_public_attrs': True, # Allow public attributes only
92
'exposed_prefix': 'exposed_', # Required prefix for exposed methods
93
'safe_attrs': frozenset([ # Safe attributes whitelist
94
'__abs__', '__add__', '__and__', '__bool__', '__cmp__',
95
'__contains__', '__div__', '__divmod__', '__doc__', '__eq__',
96
'__float__', '__floordiv__', '__ge__', '__getitem__',
97
'__gt__', '__hash__', '__hex__', '__iadd__', '__iand__',
98
'__idiv__', '__ifloordiv__', '__ilshift__', '__imod__',
99
'__imul__', '__int__', '__invert__', '__ior__', '__ipow__',
100
'__irshift__', '__isub__', '__iter__', '__itruediv__',
101
'__ixor__', '__le__', '__len__', '__long__', '__lshift__',
102
'__lt__', '__mod__', '__mul__', '__ne__', '__neg__',
103
'__next__', '__oct__', '__or__', '__pos__', '__pow__',
104
'__radd__', '__rand__', '__rdiv__', '__rdivmod__',
105
'__repr__', '__rfloordiv__', '__rlshift__', '__rmod__',
106
'__rmul__', '__ror__', '__rpow__', '__rrshift__',
107
'__rshift__', '__rsub__', '__rtruediv__', '__rxor__',
108
'__setitem__', '__str__', '__sub__', '__truediv__',
109
'__xor__', 'next'
110
]),
111
'instantiate_custom_exceptions': False,
112
'propagate_SystemExit_locally': False,
113
'propagate_KeyboardInterrupt_locally': False,
114
}
115
116
def create_secure_config(additional_options=None):
117
"""
118
Create secure configuration for RPyC connections.
119
120
Parameters:
121
- additional_options (dict): Additional configuration options
122
123
Returns:
124
dict: Secure configuration dictionary
125
"""
126
```
127
128
### Access Control Utilities
129
130
Utilities for implementing fine-grained access control.
131
132
```python { .api }
133
def restricted_service(service_class, allowed_methods=None, denied_methods=None):
134
"""
135
Create restricted version of service class.
136
137
Parameters:
138
- service_class: Service class to restrict
139
- allowed_methods (set): Set of allowed method names
140
- denied_methods (set): Set of denied method names
141
142
Returns:
143
class: Restricted service class
144
"""
145
146
class SecurityPolicy:
147
"""
148
Security policy for controlling access to service methods and attributes.
149
"""
150
151
def __init__(self, allowed_methods=None, denied_methods=None,
152
allowed_attrs=None, denied_attrs=None):
153
"""
154
Initialize security policy.
155
156
Parameters:
157
- allowed_methods (set): Allowed method names
158
- denied_methods (set): Denied method names
159
- allowed_attrs (set): Allowed attribute names
160
- denied_attrs (set): Denied attribute names
161
"""
162
163
def check_method_access(self, method_name):
164
"""
165
Check if method access is allowed.
166
167
Parameters:
168
- method_name (str): Method name to check
169
170
Returns:
171
bool: True if access allowed
172
"""
173
174
def check_attr_access(self, attr_name, is_write=False):
175
"""
176
Check if attribute access is allowed.
177
178
Parameters:
179
- attr_name (str): Attribute name to check
180
- is_write (bool): True for write access, False for read
181
182
Returns:
183
bool: True if access allowed
184
"""
185
```
186
187
## Examples
188
189
### SSL Server with Client Authentication
190
191
```python
192
import rpyc
193
from rpyc.utils.server import ThreadedServer
194
from rpyc.utils.authenticators import SSLAuthenticator
195
import ssl
196
197
class SecureService(rpyc.Service):
198
@rpyc.exposed
199
def get_secret_data(self):
200
return "This is confidential information"
201
202
@rpyc.exposed
203
def process_secure_request(self, data):
204
return f"Securely processed: {data}"
205
206
# Create SSL authenticator with client certificate verification
207
authenticator = SSLAuthenticator(
208
keyfile='server.key',
209
certfile='server.crt',
210
ca_certs='ca.crt',
211
cert_reqs=ssl.CERT_REQUIRED # Require client certificates
212
)
213
214
# Create secure server
215
server = ThreadedServer(
216
SecureService,
217
port=18821, # Standard SSL port
218
authenticator=authenticator
219
)
220
221
print("Secure SSL server with client authentication started")
222
server.start()
223
```
224
225
### SSL Client Connection
226
227
```python
228
import rpyc
229
230
# Connect with SSL client certificate
231
conn = rpyc.ssl_connect(
232
'secure-server.com', 18821,
233
keyfile='client.key',
234
certfile='client.crt',
235
ca_certs='ca.crt'
236
)
237
238
try:
239
# Use secure connection
240
secret = conn.root.get_secret_data()
241
result = conn.root.process_secure_request("sensitive data")
242
print(f"Secret: {secret}")
243
print(f"Result: {result}")
244
finally:
245
conn.close()
246
```
247
248
### Custom Authentication
249
250
```python
251
import rpyc
252
from rpyc.utils.server import ThreadedServer
253
from rpyc.utils.authenticators import AuthenticationError
254
import hashlib
255
import time
256
257
class TokenAuthenticator:
258
"""Custom token-based authenticator"""
259
260
def __init__(self, valid_tokens):
261
self.valid_tokens = set(valid_tokens)
262
263
def __call__(self, sock):
264
"""Authenticate using token exchange"""
265
try:
266
# Receive token from client
267
token_data = sock.recv(1024).decode('utf-8')
268
token_parts = token_data.split(':')
269
270
if len(token_parts) != 2:
271
raise AuthenticationError("Invalid token format")
272
273
username, token = token_parts
274
275
# Validate token
276
if token not in self.valid_tokens:
277
raise AuthenticationError("Invalid token")
278
279
# Send confirmation
280
sock.send(b"AUTH_OK")
281
282
# Return socket and credentials
283
credentials = {'username': username, 'authenticated': True}
284
return sock, credentials
285
286
except Exception as e:
287
raise AuthenticationError(f"Authentication failed: {e}")
288
289
class AuthenticatedService(rpyc.Service):
290
def on_connect(self, conn):
291
print(f"User {conn._config.get('credentials', {}).get('username')} connected")
292
293
@rpyc.exposed
294
def get_user_data(self):
295
# Access credentials from connection
296
username = self.exposed_get_username()
297
return f"Data for user: {username}"
298
299
@rpyc.exposed
300
def get_username(self):
301
# Would access from connection context in real implementation
302
return "authenticated_user"
303
304
# Create server with custom authenticator
305
valid_tokens = ['token123', 'secret456', 'auth789']
306
authenticator = TokenAuthenticator(valid_tokens)
307
308
server = ThreadedServer(
309
AuthenticatedService,
310
port=12345,
311
authenticator=authenticator
312
)
313
314
print("Server with custom token authentication started")
315
server.start()
316
```
317
318
### Secure Configuration
319
320
```python
321
import rpyc
322
from rpyc.utils.authenticators import SSLAuthenticator
323
import ssl
324
325
# Create highly secure configuration
326
secure_config = {
327
'allow_all_attrs': False, # No attribute access
328
'allow_pickle': False, # No pickle serialization
329
'allow_setattr': False, # No attribute setting
330
'allow_delattr': False, # No attribute deletion
331
'allow_public_attrs': False, # No public attributes
332
'exposed_prefix': 'exposed_', # Require exposed prefix
333
'instantiate_custom_exceptions': False,
334
'propagate_SystemExit_locally': True,
335
'propagate_KeyboardInterrupt_locally': True,
336
'sync_request_timeout': 10, # Short timeout
337
}
338
339
class HighSecurityService(rpyc.Service):
340
"""Service with minimal attack surface"""
341
342
@rpyc.exposed
343
def safe_operation(self, data):
344
# Only basic operations on validated data
345
if isinstance(data, (int, float, str)):
346
return f"Processed: {data}"
347
else:
348
raise ValueError("Only basic types allowed")
349
350
@rpyc.exposed
351
def get_public_info(self):
352
return "This is public information"
353
354
# SSL authenticator with strict settings
355
authenticator = SSLAuthenticator(
356
keyfile='server.key',
357
certfile='server.crt',
358
ca_certs='ca.crt',
359
cert_reqs=ssl.CERT_REQUIRED,
360
ssl_version=ssl.PROTOCOL_TLS,
361
ciphers='ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS'
362
)
363
364
# Connect with secure configuration
365
conn = rpyc.ssl_connect(
366
'secure-server.com', 18821,
367
keyfile='client.key',
368
certfile='client.crt',
369
ca_certs='ca.crt',
370
config=secure_config
371
)
372
373
try:
374
# Only allowed operations work
375
result = conn.root.safe_operation("test data")
376
info = conn.root.get_public_info()
377
print(f"Result: {result}")
378
print(f"Info: {info}")
379
380
# These would fail due to security restrictions:
381
# conn.root.some_attr # Would raise AttributeError
382
# conn.root.unsafe_method() # Would not be accessible
383
384
finally:
385
conn.close()
386
```
387
388
### Role-Based Access Control
389
390
```python
391
import rpyc
392
from rpyc.utils.server import ThreadedServer
393
from rpyc.utils.authenticators import AuthenticationError
394
395
class RoleBasedAuthenticator:
396
"""Authenticator that assigns roles based on credentials"""
397
398
def __init__(self, user_roles):
399
self.user_roles = user_roles # {'username': ['role1', 'role2']}
400
401
def __call__(self, sock):
402
# Simple username/password authentication
403
credentials_data = sock.recv(1024).decode('utf-8')
404
username, password = credentials_data.split(':')
405
406
# In real implementation, verify password against database
407
if username in self.user_roles:
408
sock.send(b"AUTH_OK")
409
roles = self.user_roles[username]
410
return sock, {'username': username, 'roles': roles}
411
else:
412
raise AuthenticationError("Invalid credentials")
413
414
class RoleProtectedService(rpyc.Service):
415
"""Service with role-based method protection"""
416
417
def __init__(self):
418
self._connection = None
419
420
def on_connect(self, conn):
421
self._connection = conn
422
print(f"User connected: {self.get_username()} with roles: {self.get_roles()}")
423
424
def get_username(self):
425
return self._connection._config.get('credentials', {}).get('username', 'unknown')
426
427
def get_roles(self):
428
return self._connection._config.get('credentials', {}).get('roles', [])
429
430
def require_role(self, required_role):
431
"""Check if user has required role"""
432
user_roles = self.get_roles()
433
if required_role not in user_roles:
434
raise PermissionError(f"Role '{required_role}' required")
435
436
@rpyc.exposed
437
def public_method(self):
438
return "This method is available to all authenticated users"
439
440
@rpyc.exposed
441
def admin_method(self):
442
self.require_role('admin')
443
return "This method requires admin role"
444
445
@rpyc.exposed
446
def user_method(self):
447
self.require_role('user')
448
return f"Hello {self.get_username()}, you have user access"
449
450
@rpyc.exposed
451
def manager_method(self):
452
self.require_role('manager')
453
return "This method requires manager role"
454
455
# Setup users with roles
456
user_roles = {
457
'alice': ['user', 'admin'],
458
'bob': ['user'],
459
'charlie': ['manager', 'user']
460
}
461
462
authenticator = RoleBasedAuthenticator(user_roles)
463
server = ThreadedServer(
464
RoleProtectedService,
465
port=12345,
466
authenticator=authenticator
467
)
468
469
print("Role-based access control server started")
470
server.start()
471
```
472
473
### Connection Security Monitoring
474
475
```python
476
import rpyc
477
from rpyc.utils.server import ThreadedServer
478
import logging
479
import time
480
481
class SecurityMonitoringService(rpyc.Service):
482
"""Service that logs security events"""
483
484
def __init__(self):
485
self.logger = logging.getLogger('rpyc.security')
486
self.connection_log = {}
487
488
def on_connect(self, conn):
489
client_info = {
490
'connect_time': time.time(),
491
'remote_addr': getattr(conn._channel.stream.sock, 'getpeername', lambda: 'unknown')(),
492
'requests': 0
493
}
494
self.connection_log[id(conn)] = client_info
495
self.logger.info(f"Client connected: {client_info['remote_addr']}")
496
497
def on_disconnect(self, conn):
498
conn_id = id(conn)
499
if conn_id in self.connection_log:
500
info = self.connection_log[conn_id]
501
duration = time.time() - info['connect_time']
502
self.logger.info(f"Client disconnected after {duration:.2f}s, "
503
f"{info['requests']} requests")
504
del self.connection_log[conn_id]
505
506
def log_request(self, method_name):
507
"""Log method invocation"""
508
# In real implementation, get connection from context
509
self.logger.info(f"Method called: {method_name}")
510
511
@rpyc.exposed
512
def secure_operation(self, data):
513
self.log_request('secure_operation')
514
# Validate input
515
if not isinstance(data, str) or len(data) > 1000:
516
self.logger.warning("Invalid input rejected")
517
raise ValueError("Invalid input")
518
return f"Processed: {data}"
519
520
@rpyc.exposed
521
def get_stats(self):
522
self.log_request('get_stats')
523
return {
524
'active_connections': len(self.connection_log),
525
'server_uptime': time.time()
526
}
527
528
# Setup security logging
529
logging.basicConfig(
530
level=logging.INFO,
531
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
532
handlers=[
533
logging.FileHandler('rpyc_security.log'),
534
logging.StreamHandler()
535
]
536
)
537
538
server = ThreadedServer(SecurityMonitoringService, port=12345)
539
print("Security monitoring server started")
540
server.start()
541
```
542
543
## Constants
544
545
```python { .api }
546
DEFAULT_SSL_PORT = 18821 # Default SSL server port
547
SSL_CIPHER_SUITES = 'ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS'
548
AUTH_TIMEOUT = 30 # Authentication timeout (seconds)
549
MAX_AUTH_ATTEMPTS = 3 # Maximum authentication attempts
550
```
551
552
## Exceptions
553
554
```python { .api }
555
class AuthenticationError(Exception):
556
"""Raised when authentication fails"""
557
558
class SecurityError(Exception):
559
"""Base exception for security-related errors"""
560
561
class PermissionDeniedError(SecurityError):
562
"""Raised when access is denied due to insufficient permissions"""
563
564
class InvalidCredentialsError(AuthenticationError):
565
"""Raised when provided credentials are invalid"""
566
```