BOINC / boinc

Open-source software for volunteer computing and grid computing.
https://boinc.berkeley.edu
GNU Lesser General Public License v3.0
2.01k stars 446 forks source link

Enable GUI RPC via HTTP #3190

Closed davidpanderson closed 5 years ago

davidpanderson commented 5 years ago

Goal: expose GUI RPCs via HTTP, thereby making it easier to implement GUIs in Javascript (for example, in Electron).

The client already has code for parsing HTTP request headers and sending reply headers. This works for unauthenticated operations like get_state. It doesn't work for authenticated operations because the authentication scheme (auth1/auth2) assumes that a sequence of RPCs arrive on a single TCP connection, and HTTP uses one connection per operation (at least, the basic JS APIs like XMLHttpRequest do). So we need an authentication scheme that spans connections.

My proposal: Add a new get_auth_id() RPC. This returns an integer "authentication ID", to be passed in subsequent requests.

The BOINC client stores a list of these; each is associated with a sequence number of the last request.

In subsequent GUI RPCs, the GUI includes an HTTP header item consisting of (authentication ID, SHA1(password + seqno) and increments the seqno.

To authenticate a request, the BOINC client 1) checks that the seqno is > previous seqno 2) computes SHA1(password + seqno) and checks it against the request

This protocol (I think) prevents spoofing even if bad guys read network traffic.

TheAspens commented 5 years ago

I would suggest that we look at doing this in a more standard approach (especially since the first item on the Electron security checklist is to only use https and not http https://electronjs.org/docs/tutorial/security#1-only-load-secure-content).

I would suggest that we look to secure the GUI RPCs using HTTPS rather than HTTP. One way this could be done is as follows:

This advantage of this approach is as follows:

I am not familiar enough with electron in order to be able to say how to add a trusted authority during a connection but hopefully it is possible.

davidpanderson commented 5 years ago

That's probably better long-term. My short-term proposal is a minor change and is - I think - equivalent to the current non-HTTP protocol from a security point of view - is that correct?

adamradocz commented 5 years ago

You can use the Node net API in Electron to implement a TCP client.

TheAspens commented 5 years ago

Currently the non-HTTP protocol is a custom made, non-standard interface. This has prevented much development against it. By changing it to become essentially a REST API server you are now going to see significantly greater development against it. It would be best to move fully to supporting standard features expected of a REST server (like using https and using standard authentication schemes). As time goes by, it will be harder to change things if they aren't present at the start.

However, in the absence of doing that I would suggest the following:

Issue 1 Right now someone listening to the wire can watch the communications between client and the GUI. The values produced by the hash snprintf(buf, sizeof(buf), "%d%s", seqno, gstate.gui_rpcs.password); will always be the same sequence of values. As a result, someone watching simply needs to watch the initial call to get_auth_id and the following messages to build up a list of valid hashes to send. Then they can replay the call to get_auth_id and send the sequence of messages and make any calls they want.

Recommendation: I would recommend that get_auth_id returns both the auth_id and an auth_string where auth_string is a long string of random characters that is unique for each auth_id. The auth_string should then be incorporated into the hash generated by snprintf(buf, sizeof(buf), "%d%s", seqno, gstate.gui_rpcs.password); perhaps something like snprintf(buf, sizeof(buf), "%d%s%s", seqno, gstate.gui_rpcs.password, auth_string); This will make each hash unique to only one particular auth_id and thus cannot be reused by another client.

Issue 2 Right now if someone is able to intercept the communications and then pass them to the client, then they have ability to replace one of the calls with their own call This allows the man in the middle to attach the client to a new project and thus run arbitrary code on the client.

Recommendation: I would recommend that the hash not be sent but instead be used as a key to sign the message. This will ensure that a man in the middle cannot alter the message.

Issue 3 Once significant development occurs around this interface, it will be hard to maintain compatibility with that development when changes are required.

Recommendation: As a result an API version number should be added to the messages as part of this in order to allow for easier backwards compatibility when things change in the API. The version number should be present on inbound requests and sent in replies. Since this is the first time adding it, inbound requests without a version number would be assumed to be version 1. The version number will be important since clients should also start developing knowing that the API response could change and handle it accordingly.

Issue 4 Developing on the web is easier against JSON than XML.

Recommendation: You should provide JSON responses to these calls (or allow the client to specify the format) as it will make it easier for the clients to develop against. If you take the approach of allowing the client to specify, you should use the standard HTTP request header Accept: application/json or Accept: application/xml.

Ferroin commented 5 years ago

@davidpanderson While the security benefits mentioned @TheAspens are big, I'd argue that there's a much bigger advantage to providing a standard REST-style API (even if it doesn't have HTTPS support natively (once you've got HTTP, HTTPS is easy to add externally if you need it)) in that it suddenly makes development against the API exponentially easier for pretty much everyone. The existing XML interface is, honestly, completely bonkers (never uses attributes even when they make sense, often returns flat structures instead of proper nested grouping like it should have, etc), is probably the single biggest hurdle to developing software that works with the BOINC client in some way, and is honestly a serious pain in the arse to deal with from JavaScript (I've tried, the fact that it's a custom protocol is not hard to work around, but it's a serious pain to parse XML from JavaScript when it's not sensibly structured).

Given this, I would suggest instead just going straight for a basic REST API (using HTTP Basic Authentication, without TLS in the first iteration (TLS is relatively easy to add after the fact, and it's also not hard for an end-user who really needs it to encapsulate an existing HTTP connection themselves)).

davidpanderson commented 5 years ago

I wrote a proof-of-concept Electron GUI for BOINC: https://github.com/BOINC/boinc/tree/dpa_gui_rpc_http/samples/electron_gui See the comments in windows.js to run it. You'll need to use a client built from that branch. It does GUI RPCs using JS's XMLHttpRequest class.

The non-HTTP API wasn't that big a barrier; people developed bindings for several languages. But for Javascript it makes sense to use HTTP.