- 
                Notifications
    
You must be signed in to change notification settings  - Fork 657
 
auth: Add support for multiple ZAP authenticators in same process #2139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Enable running multiple ZAP authenticators concurrently within a single process by allowing custom socket addresses in the start() method. This exposes functionality available in libzmq's C API that was previously inaccessible from Python. Previously, all authenticators were hardcoded to bind to the single address "inproc://zeromq.zap.01", preventing multiple authenticators from coexisting. This limitation made it impossible to apply different authentication policies to different socket groups in the same process. I've been using this patch privately for years, because my code needs it. Time to send it upstream. Changes: - Add optional socket_addr parameter to Authenticator.start() - Add optional socket_addr parameter to AsyncioAuthenticator.start() - Add optional socket_addr parameter to ThreadAuthenticator.start() - All parameters default to "inproc://zeromq.zap.01" for backward compatibility - Add documentation with usage examples This change is fully backward compatible - existing code continues to work without modification.
b2459ad    to
    50290c7      
    Compare
  
    | 
           Interesting, thanks for the PR! Can you give an example of how you've been using this? The spec says that ZAP SHALL always be on  
 Not per process, but rather per Context.  Here's a script using two authenticators in one process, concurrently: import zmq
from zmq.auth.thread import ThreadAuthenticator
ctx_1 = zmq.Context()
auth_1 = ThreadAuthenticator(ctx_1)
auth_1.start()
auth_1.allow("127.0.0.1")
ctx_2 = zmq.Context()
auth_2 = ThreadAuthenticator(ctx_2)
auth_2.start()
auth_2.allow("127.0.1.1")
auth_1.stop()
auth_2.stop()
ctx_1.term()
ctx_2.term()Don't worry about the weird CI failures for now, I'll deal with that in another PR.  | 
    
| 
           Here's how I have been using it. I have had a central "service hub", which has two ROUTER connections -- one internal-facing and one external-facing. The internal services (DEALER) which connect to the service hub use ZAP and use CURVE keys for authentication, on an internal (non-public) ROUTER connection. Then I also have clients/agents (DEALER) on the Internet which connect to the second ROUTER on the service hub, via a public-facing port, also using ZAP. Both services and clients use ZAP, but they each have their own separate CURVE key stores. They should not be intermingled, as the internal services and clients/agents have different security scopes and privileges. The service hub enforces a strict communications policy between clients/agents and services. When implementing this model, I ran into an issue where I was unable to spin up these two ROUTER connections for the service hub, due to conflicting names. This patch allows me to specify a second, non-conflicting ZAP path which allows this model to work well. This allows me to have two Python classes, each implementing a ROUTER -- one internal, one external. For one, I manually specify a ZAP authenticator address for the second ROUTER, thus allowing them to both run simultaneously in-process ("inproc") without a namespace conflict. This allows trouble-free use of this pattern. I have used this pattern for about a decade in production when operating the funtoo.org services, which has been using ZeroMQ as a communications fabric for integrating backend and frontend services.  | 
    
| 
           @minrk regarding your recommended use of contexts -- maybe this will work and make my patch unnecessary? It depends on a few things: Is configure_curve_callback() context-specific? 
 Are the callback invocations truly isolated? 
 Can file-based and callback-based auth coexist? Can  Basically, does the Context fully disentangle multiple ZAP authenticators from another even if they are using the same endpoint? Example code demonstrating what would need to be supported with contexts for the multi-zap patch to not be needed: import zmq
from zmq.auth.asyncio import AsyncioAuthenticator
# Context 1: Device registry with callback-based authentication
ctx_1 = zmq.asyncio.Context()
auth_1 = AsyncioAuthenticator(ctx_1)
auth_1.start()
auth_1.allow("127.0.0.1")
def device_registry_callback(domain, z85_public_key):
    print(f"Auth1 callback: {z85_public_key}")
    # Query device registry, return True/False
    return True
auth_1.configure_curve_callback(callback=device_registry_callback)
# Context 2: Legacy file-based authentication
ctx_2 = zmq.asyncio.Context()
auth_2 = AsyncioAuthenticator(ctx_2)
auth_2.start()
auth_2.allow("127.0.0.1")
auth_2.configure_curve(domain='*', location='/path/to/authorized_keys')I have not tried this myself. Let me know if it should work and if there are any concerns with doing this. If this is fully supported and doesn't create any potential security concerns with inter-mingling of two separate security contexts, then my patch is probably not needed and I can simply update my code :)  | 
    
Enable running multiple ZAP authenticators concurrently within a single process by allowing custom socket addresses in the start() method. This exposes functionality available in libzmq's C API that was previously inaccessible from Python.
Previously, all authenticators were hardcoded to bind to the single address "inproc://zeromq.zap.01", preventing multiple authenticators from coexisting. This limitation made it impossible to apply different authentication policies to different socket groups in the same process.
I've been using this patch privately for years, because my code needs it. Time to send it upstream.
Changes:
This change is fully backward compatible - existing code continues to work without modification.