proxy-wasm-rust-sdk
proxy-wasm-rust-sdk copied to clipboard
Suspending and re-dispatching HTTP requests which require external authorization
Hi,
Sorry for the long request, I am not sure if this will end up as a bug report, feature request or guidance, so I'm giving enough context on the problem.
I am looking to build an Envoy extension in Rust to enable external authorization of requests in sessions. Below is a simplified description of this authorization.
The first request in a session requires external authorization. However subsequent requests should be authorized locally (without an external call), for efficiency.
Further, we need to be able to support an asynchronous method to de-authorize a session.
In order to implement this set of flows I think I need the following:

- An HTTP Worker which handles each HTTP request. (called RP Worker above)
- An Authz Callback Worker which handles the async session updates. (Out of scope for this.)
- An Authz Service extension which maintains the session state and coordinates initial authz of each session.
The core problem I have is how the HTTP worker can pause a request and restart it when it gets the authorization response from the Authz Service extension (delivered using a queue).
(Note: I am new to Rust, so pardon any terminology slips!)
I'd like the following flow:
-
HTTP Worker/Root Context handler creates the HTTP Context (in create_http_context()), capturing a copy in a Hashmap, indexed by context ID. (can't get this working due to ownership issues)
-
HTTP Worker/HTTP Context: Intercepts request using on_http_request_headers(), sends a message to the Authz Service extension to validate the authz state of the session. (working)
-
Authz Service: receive and process the authz request (either locally or by calling externally). Respond by sending a message to a queue (named in the authz request - owned by the Root Context). (working)
-
HTTP Worker/Root Context: Use on_queue_ready() to detect the available response. It should then look up the suspended HTTP Context and dispatch it. (not working, as I can't get the hashmap from (1) working)
-
HTTP Worker/HTTP Context: handle the authz response, either rejecting the HTTP request, or resuming it with resume_http_request(). (I assume this will work, untested)
The core problem is in step 1... capturing the HTTP Context for later re-dispatch.
At present create_http_context() is expecting a Box<dyn HttpContext> to be returned, so there is no scope of sharing access to this object.
So... options:
-
Should the signature of create_http_context() be changed to use RefCell (or similar) instead of Box to enable implementations to capture and dynamically share the resulting HTTP Context?
-
Is there another way for the Root Context on_queue_ready() handler to look up HTTP Contexts so we can dispatch them?
The Dispatcher is already maintaining a table of context_id -> HTTP Context, can we get hold of this from the Root Context?
-
Is there a completely different way to achieve this goal which I have missed?
Below is outline code for this... which does not work due to ownership issues.
Any input/suggestions on this would be gratefully received.
Thanks,
Frank.
...
struct AuthzWorkerRoot {
_context_id: u32,
http_ctxs: RefCell<HashMap<u32, Box<dyn ContinuableAuthzRequest>>>,
}
impl RootContext for AuthzWorkerRoot {
fn get_type(&self) -> Option<ContextType> {
Some(ContextType::HttpContext)
}
fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> {
let hc = Box::new(AuthzWorkerHttp { context_id: context_id }); // XXX RefCell or other?
self.http_ctxs.borrow_mut().insert(context_id, hc);
Some(hc) // Does not work due to borrow in the .insert above.
}
fn on_queue_ready(&mut self, qid: u32) {
let rbs = match self.dequeue_shared_queue(qid) { ... handle de-queue of message ... }
let msg = json::parse(str::from_utf8(&rbs).unwrap()).unwrap();
match self.http_ctxs.borrow_mut().remove(msg.conext_id) {
Some(hc): hc.borrow_mut().handle_authz_response(msg.result),
None: ...
}
}
}
struct AuthzWorkerHttp {
context_id: u32,
}
trait ContinuableAuthzRequest {
fn handle_authz_response(&mut self, r: bool);
}
impl HttpContext for AuthzWorkerHttp {
fn on_http_request_headers(&mut self, _: usize) -> Action {
... look up Authz Service message queue and prepare authz request message ...
self.enqueue_shared_queue(service_queue, Some(json::stringify(msg).as_bytes()))
...
}
}
impl ContinuableAuthzRequest for AuthzWorkerHttp {
fn handle_authz_response(&mut self, r: bool) {
if r {
self.resume_http_request();
} else {
self.send_http_response(
403,
vec![("Powered-By", "AuthzWorkerHttp")],
Some(b"Access denied by policy.\n"),
);
}
}
}
Hi. Can anyone comment on how I might suspend an HTTP request and re-dispatch it on response to a message received through a shared queue?
Many thanks.