SignalK / specification

Signal K is a JSON-based format for storing and sharing marine data from different sources (e.g. nmea 0183, 2000, seatalk, etc)
Other
91 stars 68 forks source link

feature: define Signal K security protocols #506

Closed sbender9 closed 5 years ago

sbender9 commented 5 years ago

This is my initial brain dump for security. It is based on the current node server implementation.

I intend to leave server details like how roles are used and defined or things like access control lists. These are server implementation details.

We should define the minimum need for a client and server to provide security

And yes, there's a lot left to do. I'm sure the list will get bigger.

TODO:

rob42 commented 5 years ago

The base url for login/logout/validate should be a subdir of /signalk/v1, eg /signalk/v1/auth`. This will make it easier to control access by task, rather than managing specific urls in the base dir.

validate can perform two functions, validate a provided token, and renew it if its expiring/expired (server configured). It should return the existing or new token, or error codes as per login.

panaaj commented 5 years ago

From an application perspective it would be good to have a way to determine if auth is needed. Maybe in the /signalk response there could be something that says server requires auth.

For WebApps hosted on the Signal K server this is not a big issue, as getting access to the app in the first place would be restricted by authentications anyway, but if I have a native app or app hosted at another source looking to connect to a delta stream it would allow the app to be more proactive in getting credentials from the user or even caching them.

rob42 commented 5 years ago

505

sbender9 commented 5 years ago

@rob42 Not sure I like the auto renew of a token. I would think that if a token is expired, then the client must re-authenticate.

For example, with #505, if I give approve a buddies device for 24 hours, then it should stop working after 24 hours. He would have to request access again to get a new token.

sbender9 commented 5 years ago

I do like validate, I will add that and adjust the paths to be under auth

rob42 commented 5 years ago

auto-renew is the only really practical way to handle the token expiry. You dont want your instruments popping up a login at the critical moment :-)
My code checks if the token is valid, and if the expiry is <10 min it autorenews. Bit like DHCP.

The case of 24 hours access only is better handled by having a login expiry time. So it auto-renews until time/age x, then requires login again. like max password age limits.

eg - if a new login would be valid, autorenew, otherwise login.

sbender9 commented 5 years ago

Definitely does not work for me. Why give out a token that expires if you're not going to let it expire.

I would never do this in the real world (Financial Services in my case), would not pass a security audit.

For an instrument on your boat, give out a token that never expires.

rob42 commented 5 years ago

How long is the max token age in your system? Mine is 1 hour. If you connect within 1 hour it auto-renews, if not you login again. In financial services (I do that too) I set age at 30 min and a max-renew at 12 hours so a login is required twice a day anyway, or anytime you are idle more than an 30 min. It avoids long-lived tokens, and constant logins.

sbender9 commented 5 years ago

This is exactly the kind of thing I don't think we should get into here. This should be up to the server.

I've specifically separated out user login and device access. I think of device access more like giving API access to a client. In this case you would give out long living tokens. There's no "login" here, no username and password. The only way to get a token is for the device to ask for one and get approved.

Also not sure I understand how the client is supposed to use validate. I'd rather the client did not have to worry about this. If we do this right, it should not be necessary.

For HTTP clients, you can alway renew their token if you want. Just send a new cookie.

The same with WS (or other protocol clients). They should always be prepared to get a message with a new token without having to make a special call.

rob42 commented 5 years ago

I agree the handling of token expiry and renewal is a server implementation issue. Separating user access and device access is good too, different use cases and capabilities. Sending an updated token in a reply should cause the client to use that instead of the old token, which allows the server to renew tokens at will.

I use the validate() call when I have an app that needs to maintain access but is not actively sending anything, eg an anchormonitor widget. It periodically checks/renews its token by making a validate call. I found that after a browser app subscribes it makes no tx messages, only rx via the ws. Then the server times out its token, and stops sending updates. Not sure if its entirely necessary, there may be better ways.

sbender9 commented 5 years ago

Had not though of case of a read-only WS client. But why not just close the socket when a token expires. A ws client should have reconnect logic anyway (server restarts, etc.) Socket gets closed, then reconnect and re-authorize.

rob42 commented 5 years ago

In my case the token is created by the user (who logs in) so reconnect causes the login to pop again. I found that the browser has to do a kind of keep-alive to make it work nicely.

Its tied up with the situation where you cant tell if a browser has gone away or is just quiet in some communication error cases.

tkurki commented 5 years ago

What features here are specifically related to JWT? Like, if you leave out the word JWT and say token would everything work?

tkurki commented 5 years ago

I have a hard time grasping why you would automatically renew the original authorization when it is timing out.

Why not just issue a longer lived token?

Is the use case "idle timeout", eg. invalidate the authorization if the client goes inactive for a certain period?

rob42 commented 5 years ago

we should always use token instead of JWT. The token is expected to be JWT, but could be anything - its server implementation dependent.

The reason I use auto-renew isidle timeout - because I dont know how long the client requires the token when I issue it, and I dont want valid tokens hanging around if they will not be used. This way the tokens only renew for active users. A more specific use case: You log in on your laptop, get a 4 hour token. You go away from the boat and the laptop is stolen. The next day the thief tries to access your boat. A long lived token (12 months) will allow them in, my token will be expired and they will need to login. But if I dont auto-renew they will need to login every 4 hours while working, which is a nuisance.

tkurki commented 5 years ago

@rob42 your scenario and the need to refresh the token assumes that the validity period is embedded in the token and the server is stateless. If you just use a random token and the server keeps track of issued tokens and their last used time you can achieve what you want at will. Even with validity period embedded in the token you can achieve this with serverside last use bookkeeping and blacklisting. Kinda silly to choose to do so, but it would work, with no extra functional requirements on the client.

BTW logout implies that a server has a way to invalidate the token. If the server is otherwise stateless, like with JWT, this means blacklisting.

Note that the spec says nothing about validity periods and if we do away with JWT there is even less implied logic related to the token.

So I think a better way to think about this is do we need to build in a token exchange/update mechanism, whereby the server may issue a new token and the client must update its token.

For http cookies this is sort of easy - if the client is active you can update the cookie on the fly. For ws less so, unless you force the client to reconnect, which is a nuisance and may cause an otherwise ok client to loose some updates.

I don't like calling /validate being the client's responsibility - how would a client know how often it should call that? Every 10 seconds? Every hour?

The question is: do we absolutely need token exchange? It does complicate compliant client implementation.

sbender9 commented 5 years ago

Just thinking out loud, but perhaps having something like a ping or keep-alive message for things like ws makes sense. Even outside of security. It could be optional and we could either specify the time interval in the spec or put the time interval in the welcome message.

This could allow the server to clean up idle connections and update security tokens if needed.

Clients could implement this or be prepared to reconnect (which they should do anyway)

sbender9 commented 5 years ago

Something that was was brought up in the node server security PR is giving the client some information about security requirements. We could include something in the welcome message.

"authenticationRequired" = "always" | "forwrite" | "never"

tkurki commented 5 years ago

Tcp is not my forte, but isn't it so that as long as the server is sending stuff and the connection is working the client is sending back acknowledgement tcp packets. A truly idle connection may go stale, but if the server keeps sending something a client just disappering will eventually result in the terminating the connection in timeout / unreachable or something along those lines.

We actually have discussed heartbeat, to be sent in absence of other messages, and it is referenced in https://github.com/SignalK/specification/blob/master/gitbook-docs/streaming_api.md#streaming-websocket-api-signalkversionstream.

rob42 commented 5 years ago

My server is stateless and does issue tokens with short validity time. That avoids having to track tokens, except as you said when logging out (hadnt considered that). Since my tokens have a short validity and auto-renew they self-expire quickly anyway.

I dont call validate() at the client side, its called by the server on any incoming token, and cookie updated for REST calls, but a client could call it. There was a mention somewhere of a way for a client to find its auth status so I suggested we expose it.

So in my case client auth expires unless they remain active. This fails in ws subscription as the server sends but the client just passively receives.

ws is based on TCP and TCP uses FIN packet to close the connection. In the case of sudden loss of Internet connection, both the ws server and the browser are unware of the already dead TCP connection because no FIN packet was sent. To address the issue, TCP has a mechanism called keepalive, but its bound with the OS etc, and I found its not dependable for closing the ws connection in a reasonable time span (can be quite long).

Given my server is async and queues messages for the client I found I end up with large queues to dead clients as I cant tell if they are dead or just slow. I know that the queue is an implementation issue but the underlying problem remains, how long do you wait for a client to accept a message before assuming they are dead. On a busy server how many such connections (resource leaks) do you tolerate?

A signalk level heart-beat message over ws or other transport would solve this, effectively renewing the token, eg its basically a call to validate(), that would remove the need to expose the validate() method. (The page referenced above is a heartbeat sent by the server)

sbender9 commented 5 years ago

@rob42 The only problem I see with the heartbeat is that we probably need to make it optional, since requiring it would break existing clients. I don't think this is enough to go to v2 of the spec. In v2 we could require it.

Are you ok with adding info on this to the welcome message? Something like:

"A server may include a hearbeatRequired value in the welcome message. The value is the rate in seconds that a client should send a heartbeat message.. A server may choose to disconnect a client if it has not received a heartbeat."

rob42 commented 5 years ago

Needs thought about ws vs http. The welcome message can just be heartbeatPeriod with a value in secs. If its not there its not required.

sbender9 commented 5 years ago

I don’t see why it would be needed for http.

tkurki commented 5 years ago

Could you explain the need for the heartbeat message? What problem is it solving? How is it related to security?

I just tried with the node server dropping wifi connection with an active ws connection and checked with Wireshark: the packets are retried per tcp and in about 100 seconds the connection is closed and the app is notified. Rob's anecdotal report of problems is more related to that particular implementation than the protocol we use.

As long as the server is sending something the outgoing packets will timeout if the recipient can not be reached and the connection will close. Let's rely on tcp and not build mechanisms that exist in the transport layer into the application layer.

fabdrol commented 5 years ago

Just read through the discussion; I have three things (cents) to add.

  1. Auto-renewal or "token refreshing": I think auto-renewal on the server side is a bad idea - it opens up all kinds of risks because it moves the burden of de-authentication to the client. I am, however, sensitive to @rob42 's argument regarding client access "cutting out" at the critical moment. Having said that, why don't we recommend OAuth 2.0 style refresh tokens? They can be used by the client to indefinitely extend a token-based login, whilst still preserving the behaviour of the server invalidating tokens after they expire.

  2. Why do we want to include HTTP cookie/session based login at all? It does exactly the same as token-based login, but it's intended for clients that log-in using a request/response flow. First of all, I don't see why clients would do that, and if that so, why bother at all? Besides, strictly speaking it would require clients to pop up a cookie notification here in the EU, and that's a pain in the ass.

  3. The proposal states: [d]evices that don't have any user interaction, like sensors with no input mechanisms, should acquire a token using Access Requests: Appendix E: Access Requests. This may be me not being native English speaker, but isn't this definition slightly narrow? IMHO the access request mechanism works fine for devices that offer UI, think of a touch screen instrument or even a phone app. For me it would be much faster if I had one (web) app that is used to receive and approve/deny access requests, which I have logged in with, and everything else on-board logs in using the access request mechanism.

tkurki commented 5 years ago

Why do we want to include HTTP cookie/session based login at all?

To make it easy to create webapps. The server accepts form based login credentials and sets a cookie, nothing else required from the webapp developer.

tkurki commented 5 years ago

Why do we need to be able to do in band login over ws? Ws connection authentication can use cookie and http header based authentication and login via http.

I think there is value in recommending/specifying only one way for doing things, not several. Also less code in the implementation.

fabdrol commented 5 years ago

I think @tkurki makes a good point here - less spec is usually better. Another upside to leaving all that out in favour for just HTTP-based login/auth is that it'll be much easier to re-use existing authentication layers both in client applications and server implementations.

For client that want to request access via WS, the access request mechanism can be used as well.

sbender9 commented 5 years ago

Why do we need to be able to do in band login over ws? Ws connection authentication can use cookie and http header based authentication and login via http.

I think @tkurki makes a good point here - less spec is usually better. Another upside to leaving all that out in favour for just HTTP-based login/auth is that it'll be much easier to re-use existing authentication layers both in client applications and server implementations.

For client that want to request access via WS, the access request mechanism can be used as well.

As a client developer, I like having the options. For example, if I can do everything I want to do over WS, then the user's perceived performance is much better.

Now that I have login and PUT request support over WS in node server, the time from opening the app to getting data is much better. And the experience of say flipping a switch is also much better because I don't have to poll for results any more.

tkurki commented 5 years ago

Agreed, there is clear advantage in ws for all async actions, no polling needed.

So how does a client discover that it should request access or provide authentication or login credentials over ws? We need to allow opening the ws without authentication to make this work. From client’s perspective the ws opens ok, there is just no data and subscriptions do not result in any response or data. Only put requests will result in responses.

sbender9 commented 5 years ago

This works pretty nicely now in node server (https://github.com/SignalK/signalk-server-node/pull/620)

The client can connect to a secure server, it won't see any data. Once it either does a login or goes through the access request process, data starts flowing.

I proposed above that we add something like the following to the welcome message:

"authenticationRequired" = "always" | "forwrite" | "never"

rob42 commented 5 years ago

I agree with "authenticationRequired" = "always" | "forwrite" | "never" in the welcome message, or in a reply to a request that cannot be fulfilled due to auth. In artemis their is a public role (default role) which provides similar data to ais. So anonymous users can see basic data anyway.