-
Notifications
You must be signed in to change notification settings - Fork 172
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
Cross-origin protection #375
Conversation
ws-server/src/server.rs
Outdated
/// ``` | ||
/// | ||
/// By default allows any `Origin`. | ||
pub fn set_allowed_origins<Origin, List>(mut self, list: List) -> Self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe instead of accepting a list, we should have a method that will push
onto an internal vec, and construct Arc<String>
on build
?
I'd like to avoid a situation where an empty list is set, which then rejects all connections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with the current function myself (I'd just add a note in the comment that setting an empty list will deny all origins). Perhaps if we added an allow_all_origins
function to toggle to AllowAny
it would become a little clearer by contrast? This would also allow one to programmatically undo the effect of calling set_allowed_origins
, which doesn't feel super necessary, but for the sake of completeness it feels nice to be able to toggle between the full range of possible values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(To elaborate a little; if we had an add_allowed_origins
instead that pushed to the list, it would feel slightly odd to me that you could add an origin to go from allowing everything (nothing added yet) to only allowing what's been added. Whereas, "set_allowed_origins" feels clear to me and doesn't have that slightly odd transition in my mind)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My two cents: having a single set_
method is enough. We do not expect the list to change once the server is started, and even if it is technically possible to dynamically add/remove allowed origins, I think it's fine to be opinionated here.
Wrt to the semantics of setting the allowed origins to the empty set, I think we should pick one of these:
- empty list => everyone can connect, i.e. switch to
Cors::AllowAny
- empty list => return error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be more in favour of 2: I wouldn't be super keen on an empty list (perhaps by mistake) loosening restrictions and allowing all origins, so getting an error back feels more helpful.
ws-server/src/server.rs
Outdated
if let (Cors::AllowList(list), Some(origin)) = (self, origin) { | ||
if !list.iter().any(|o| o.as_bytes() == origin) { | ||
let error = format!("Origin denied: {}", String::from_utf8_lossy(origin)); | ||
log::warn!("{}", error); | ||
return Err(Error::Request(error)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want/need to handle "*" patterns as well in the allowed origins?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I think so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are 3 possible states here:
- There is no
Origin
header, which equates to the request being done either on the domain (no cross-origin shenanigans), or the request isn't coming from a browser at all (in which case the client can spoof the header to whatever it wants). We always allow these. - The header has a
protocol://hostname
value, if so we check it against the list (unless we allow all origins). - The header has a
null
value, which can be explicitly allowed by the list, but is generally advised against.
There is no *
origin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you explained this in a previous comment which makes sense to me now :)
My initial thinking was that CORS responses can contain "*" in allowed origins, so perhaps we need to handle something like this.
I did a little reading about WebSockets (since I didn't really know anything about the WebSocket upgrade protocol) and, indeed, there is no such thing as CORS really when talking about WS connections by the sounds of it, and so the origin checking here can essentially take whatever form we like.
Given that, I have no problem with not handling * in an allowed origin; it could potentially be a future enhancement (so that you can say configure this to allow eg any connections from "https://*.mydomain.com") but I guess there's no need for that sort of thing in the first cut!
Updated the comment on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good to me; thanks for explaining the Origins stuff!
A couple of nits, but happy to approve :)
hmm, I don't really follow are you referring to this in substrate? when I did this the request got denied for bad host in the |
Ha, curious. I didn't see it in the CLI when I checked it, but you might be right. I'll double check to make sure. |
Will do a follow-up PR with |
* master: (21 commits) New proc macro (#387) Streaming RpcParams parsing (#401) Set allowed Host header values (#399) Synchronization-less async connections in ws-server (#388) [ws server]: terminate already established connection(s) when the server is stopped (#396) feat: customizable JSON-RPC error codes via new enum variant on `CallErrror` (#394) [ci]: test each individual crate's manifest (#392) Add a way to stop servers (#386) [jsonrpsee types]: unify a couple of types + more tests (#389) Update roadmap link in readme (#390) Cross-origin protection (#375) Method aliases + RpcModule: Clone (#383) Use criterion's async bencher (#385) Async/subscription benches (#372) send text (#374) Fix link to ws server in README.md (#373) Concat -> simple push (#370) Add missing `rt` feature (#369) Release prep for v0.2 (#368) chore(scripts): publish script (#354) ...
Depends on paritytech/soketto#35
No filters for
Host
, Substrate doesn't seem to care, it's more of interest to the likes of nginx reverse proxy for setting up virtual hosts. IIRC it was always confusing inparity-ethereum
?