Skip to content
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

AuthZ/permissions -- restrict data access to tree namespace user or device is in #268

Open
cbrake opened this issue Sep 21, 2021 · 25 comments
Labels
enhancement improvements on existing functionality research Looking into new ideas

Comments

@cbrake
Copy link
Member

cbrake commented Sep 21, 2021

right now, any user or device can access any point data in the system. Would be nice to if a user/device could only access data for the segment of the tree they are in.

@cbrake cbrake changed the title AuthN -- restrict data access to tree namespace user or device is in AuthZ -- restrict data access to tree namespace user or device is in Sep 21, 2021
@cbrake
Copy link
Member Author

cbrake commented Sep 21, 2021

NATS Leaf nodes may be applicable. However, it is not clear if leaf/nodes accounts have a completely separate subject namespace, or if a leaf node/account can access a portion of the subject namespace.

@cbrake
Copy link
Member Author

cbrake commented Sep 21, 2021

NATS accounts gives you true multi-tenancy, where everything is isolated. Users can have permission maps that restrict a user to specific subject space. It seems we want simple users, not accounts.

@cbrake
Copy link
Member Author

cbrake commented Sep 21, 2021

Node topology looks something like this:

image

thinking of arranging the subjects by node IDs: /rootID/dev1ID/A

on device #1, if I publish to /dev1ID/A, this would automatically be mapped to /rootID/dev1ID/A when the message is published upstream

devices may be located anywhere in the tree, ex: /rootID/group1/group2/dev3ID

so user /rootID/group1/group2/user1 would have access to /rootID/group1/group2/> subjects, so could access dev3ID's data.

the idea is devices or users can access parent node and all sibling nodes/descendants.

@cbrake
Copy link
Member Author

cbrake commented Sep 21, 2021

NATS accounts allows remapping using from/to:

https://docs.nats.io/nats-server/configuration/securing_nats/accounts#import-configuration-map

Seems like that has potential.

@cbrake
Copy link
Member Author

cbrake commented Sep 22, 2021

seems just plan users/authz makes sense for initial implementation:

https://docs.nats.io/nats-server/configuration/securing_nats/authorization

accounts/leaf nodes seems too complex for this application where the NATS api is relatively small, so manually prefixing upstream traffic is not a big deal.

@cbrake
Copy link
Member Author

cbrake commented Sep 24, 2021

Apparently the restriction of using not using wildcards in mapping account services has been lifted in recent versions of nats-server, from slack:

image

https://natsio.slack.com/archives/C069GSYFP/p1632331335086300

Will need to think about this some more ...

@gedw99
Copy link

gedw99 commented Sep 24, 2021

Thanks.

should clarify that the current GUI tends to make it look like the server is doing authz because of the tree but it’s only doing auth ?

@cbrake
Copy link
Member Author

cbrake commented Sep 24, 2021

correct, the current UI simply fetches and displays the nodes and trees the user is child of, but there is nothing to prevent the frontend from fetching all nodes. So we only display what the user has access to, but don't prevent him from accessing everything. Works for now as I focus on features, but is not production grade.

@gedw99
Copy link

gedw99 commented Sep 24, 2021

correct, the current UI simply fetches and displays the nodes and trees the user is child of, but there is nothing to prevent the frontend from fetching all nodes. So we only display what the user has access to, but don't prevent him from accessing everything. Works for now as I focus on features, but is not production grade.

Makes sense . thanks for confirming.

@gedw99
Copy link

gedw99 commented Sep 26, 2021

My own 2 cents on this.

What if you just did not use any of the NATS authz aspects and enforced all authz above nats ?

Basically we have to wrap nats in general. It is already wrapped by the SIOT admin gui using rest or WS.
SO then once you accept that you have to wrap nats, then you can put your authz layer above NATS too

Good

  • in the siot admin gui, you can easily add authz policies .
  • The authz polifices for NATS will be stored in Genji which is wrapped by NATS. Yeah its a dog chasing its own tail pattern, but its elegant IMHO.
  • Not restricted to the NATS way of doing it.
    • it’s all data driven authz, just like how siot works currently. It’s sympathetic to the inherent siot design.

Bad

  • if you screw up, you have left nats open to the world.

Example of how to do it:

Cerbos for example is a Authz policy system. It can work with anything.
It uses github.com/open-policy-agent/opa, which is a open authz policy foundation.

Its very simple to use.

https://github.com/cerbos/demo-rest/blob/main/service/service.go#L222 shows exactly how you use it.

Policies can be anywhere. File, memory, db. Does not matter. Here is the ones for the example: https://github.com/cerbos/demo-rest/blob/main/cerbos/policies/order_resource.yaml

I have tried this out and was able to get it working.

Can be used for HTTP, WS, NATS, Anything to wrap access control

so all we would need to do is write a siot gui control to express how cervix policies work to the degree we need and then enforce them in the backend based out the route . And we could do them at the WS Layer.

@cbrake
Copy link
Member Author

cbrake commented Sep 27, 2021

Very interesting projects -- amazing all the stuff out there.

For devices, we don't have to wrap NATS -- they currently speak native NATs.

It seems we must do auth at the NATs level. Devices will subscribe/post to a NATS topic:

root.group1.device1

If device had NATS access to root, then it could subscribe/post to root.group2.

Adding an auth mechanism on top of NATs (would require wrapping NATs) would be possible, but then we loose most of the benefits of using NATS in the first place:

  • Synadia NGS
  • clients already exist in most langages
  • a single model for communicating with the system (NATS)
  • users now need to familiar with multiple technologies
  • Jetstream -- how does a wrapper handle all this?
  • syncronization -- if we add more types/mechanisms, then we also need to sync these new types of data.

I'm trying to avoid another mechanism for configuring Auth -- I want the location in the node tree to specify permissions explicitly. This saves a ton of overhead and complexity as a programmer and user.

That all said, my use case (very distributed systems) may be different than yours, so I don't want to discourage experimentation. A simple node tree may not provide enough dimensions for some use cases.

The approach I've taken with SIOT is to focus on solving real world problems. I still need to write up articles on some of the applications I've implemented using SIOT recently. It is easy for me to get deep in the weeds of implementing cool architectures, but what really helps me pull things forward is solving real world problems. This is why I've not worried about authZ until now -- I did not have a need in the projects I was working on. It might help to share some details on the type of problems you are trying to solve with SIOT. Feel free to email me directly.

Another nagging concern with wrapping NATS is seems we really need to prove the NATS auth model is inadequate first before justifying adding another layer of abstraction and complexity. Lets try to solve the immediate problems we have now and see what does not work.

Again, I don't want to discourage exploration, but I also want to be make sure our visions are aligned. It is easier to sort this out now vs after working together for 6mo and discover we are heading in different directions..

@gedw99
Copy link

gedw99 commented Sep 28, 2021

auth

Very interesting projects -- amazing all the stuff out there.

For devices, we don't have to wrap NATS -- they currently speak native NATs.

Yes thats what i also assumed... and you raise a good point.

SO yes if you use Cerbos, how will you secure the IOT endpoints ? I was thinking of wrapping the NATS connection on the IOT devices. Just like we wrap the GUI with WS.

I naturally lean towards wrapping NATS.. Its great but if NATS just does not quite do what you want later, your going to wish you wrapped it. And its not hard because the wrapper does not need to hold state. Sure it needs to hold a connection, but as soon as state goes across the wire, it just pushes it into NATS on the server.
This would mean you DONT have to have the nats.go imported into your IOT devices.
I know ow MQTT and COAP, but not enough to comment. I think that are complex but again dont knwo enough..
NATS have QUIC on their roadmap BTW:
https://nats.io/about/
Maybe QUIC which is UDP based and has good golang supprot makes sense on the IOT devices.
https://github.com/lucas-clemente/quic-go

It seems we must do auth at the NATs level. Devices will subscribe/post to a NATS topic:

root.group1.device1

If device had NATS access to root, then it could subscribe/post to root.group2.

Adding an auth mechanism on top of NATs (would require wrapping NATs) would be possible, but then we loose most of the benefits of using NATS in the first place:

  • Synadia NGS
  • clients already exist in most langages
  • a single model for communicating with the system (NATS)
  • users now need to familiar with multiple technologies
  • Jetstream -- how does a wrapper handle all this?
  • syncronization -- if we add more types/mechanisms, then we also need to sync these new types of data.

THis sort of gets to my point. If we dont use NATS authz, then all these questions above become Moot.
You already have a decent Auth system in SIOT. Its delegatable too.
If we jsut use something like Cerbos, then it will be easy to add, and all the whatIf of NATS GO away.

I'm trying to avoid another mechanism for configuring Auth -- I want the location in the node tree to specify permissions explicitly. This saves a ton of overhead and complexity as a programmer and user.

That all said, my use case (very distributed systems) may be different than yours, so I don't want to discourage experimentation. A simple node tree may not provide enough dimensions for some use cases.

Very distributed is exactly what i like about SIOT. Its a really neat approach.. Wrapping genjo with NATS is something i was thinking about for a few years and you went and did it :) I think that SIOT's usefulness is not apparent unless you have been through the wars of running other complex systems... I have been through some of those wars :)

The approach I've taken with SIOT is to focus on solving real world problems. I still need to write up articles on some of the applications I've implemented using SIOT recently. It is easy for me to get deep in the weeds of implementing cool architectures, but what really helps me pull things forward is solving real world problems. This is why I've not worried about authZ until now -- I did not have a need in the projects I was working on. It might help to share some details on the type of problems you are trying to solve with SIOT. Feel free to email me directly.

my Bucketlist of what SIOT is good for:

  • IOT of course.
  • IOT GUI ( gio being my favourite way to give a gui to the IOT system)
  • Any project where you need a flexible DB and you need a Real time GUI to the system, with auth and authz being able to be delegated. And you want a single binary. SIOT is IMHO a highly useful pattern of how to build real time apps.
  • Its also a perfect system for building GIO app, because its all golang.

Another nagging concern with wrapping NATS is seems we really need to prove the NATS auth model is inadequate first before justifying adding another layer of abstraction and complexity. Lets try to solve the immediate problems we have now and see what does not work.

This is also what i am worried about. NATS Auth has its own way of working ( its like configuring a Enterprise router in many ways, but i don't want to digress right now ), and anything that it cant do will cause friction. the old 80/20 rule. This is why i suggested holding all the Auth and Autz in Genji, and then apply the Guard / Checking just above NATS.
I have only used NATS auth / authz things briefly. Its good for sure.
But oyu also need to be able to reflect on the Authz to for example hide certain things from the USer in the GUI, or do logic checks. The JWT that NATS gives you SHOULD be enough to do that.
Cerbos is based on a open standard that was designed to make authz flexible for amyn scenarios.
it uses github.com/open-policy-agent/opa.
Have a play here: https://www.openpolicyagent.org/

Cerbos can use the file system of badgerDB, and so holding the Open Policy rules inside genji would be very easy.

Again, I don't want to discourage exploration, but I also want to be make sure our visions are aligned. It is easier to sort this out now vs after working together for 6mo and discover we are heading in different directions..

I totally agree that Both approaches should be looked at NATS and Cerbos...

@cbrake cbrake changed the title AuthZ -- restrict data access to tree namespace user or device is in AuthZ/permissions -- restrict data access to tree namespace user or device is in Apr 7, 2022
@cbrake
Copy link
Member Author

cbrake commented Apr 7, 2022

Another possibility is that each nats client that connects authenticates and is given a random secure message space that it can subscribe/publish to. The server keeps a map of node hierarchy locations and can then map messages to it. If the node is moved around on the server, then this would need to update dynamically, but the client would not have to know anything. Advantage of this is the client does not need to know anything about the server node hierarchy other than its node ID, or user/auth credentials.

This may allow us to bypass the entire NATS auth mechanism.

cc: @bminer

@cbrake cbrake added enhancement improvements on existing functionality research Looking into new ideas labels Apr 8, 2022
@cbrake
Copy link
Member Author

cbrake commented May 5, 2022

another scheme might be that a user or device do everything in their own
subject namespace: /a/<node id>/..

Where the remote user/device only has permission to read/write to /a/<node id>/*.

The leading /a stands for authenticated.

Any time a point is received, it travels up the tree. If we encounter users or devices, we re-broadcast the message to the /a/<node id>/... subject for the remote user/device to consume. If nobody is subscribed to the /a/... subjects, this rebroadcast should not cost very much.

One challenge with this is we now have multiple subjects to keep track of, so we need to be sure not to send the message back to the remote client that sent it. Perhaps when we receive a point, we keep track of who it came from and then as we walk up the graph, we never re-transmit back to that device/user.

@gedw99
Copy link

gedw99 commented May 6, 2022

Or just add an origin meta field, and check it each time you are about to send a message.

@gedw99
Copy link

gedw99 commented May 6, 2022

This is kind of related : https://github.com/aperturerobotics/controllerbus

it’s multi protocol way of doing distributed messaging and can run inside a browser or not. If can use NATS too.

mit can also do golang wasm to golang communication.

You could simulate a huge network of IOT devices in the böse with each device being a web worker and file system and db backing it too.

See hack-pad/hackpad#11 and the hackpadfs repo

@cbrake
Copy link
Member Author

cbrake commented Jul 23, 2022

Or just add an origin meta field, and check it each time you are about to send a message.

I like this idea. Long term, we'll need to track who modified each point anyway for config change auditing, so I think adding an origin field to the point data structure makes sense. Perhaps for if the point originated from the node owning the point, we could leave this field blank to save on data bandwidth for sensor data, which will be most of the data in the system, and if its blank, we assume the owning node is the author. Anything else, we set the origin.

Having an audit trail of who changed what is important in these systems -- even just for debugging rules -- which rule last changed this point ...

cc: @bminer

@bminer
Copy link
Contributor

bminer commented Jul 26, 2022

The way the auth spec is currently designed basically uses JWT tokens to restrict how NATS clients can publish and subscribe to various subjects.

Anonymous NATS clients can only publish to auth subject and await replies. Logged in NATS clients can only publish and subcribe to subjects prepended with the user node ID.

The auth request consists of a username/password of a user node in the SIOT tree. If login is valid, the singleton "main" NATS client will respond to an auth request with a user NKey and a JWT token for the logged in user (and does other magic behind the scenes to dynamically register that user with the NATS server via accounts, generates JWT, etc.). The user's NATS client will then reconnect and authenticate using the user JWT and signing a server nonce with the user's NKey. Once authenticated, the user's JWT will give the logged in NATS client permission to publish and subscribe for subjects prepended with the user's node ID.

@cbrake
Copy link
Member Author

cbrake commented Jul 26, 2022

sounds good to me. I think with the addition of the Origin Point field to prevent echos, the idea of a custom subject namespace for each authenticated user (downstream device, client, user, etc). will work pretty well. We could still have an echo if a user on a downstream node updates a point. NATS messages can also have headers, which could be the ID of the instance that sent the message (downstream device, client process, browser, etc). This is side channel information that is not stored as part of the message content. This needs more thought, but I think we have some good options to make this all work. The fundamental difference between SIOT and most streaming/event systems is we offer strong support for multiple actors to modify a single piece of data with CRDT properties -- data can flow in any direction. Most event/streaming are structured for data always flowing the same direction.

@cbrake
Copy link
Member Author

cbrake commented Jul 26, 2022

Updated https://docs.simpleiot.org/docs/ref/client.html#message-echo with some thoughts on echo problem.

@bminer
Copy link
Contributor

bminer commented Jul 27, 2022

I think that, in general, echo would be desired. What do you think?

EDIT: In other words, perhaps it is something each NATS client should deal with.

@cbrake
Copy link
Member Author

cbrake commented Jul 27, 2022

sketched a few things out to think through this -- I think it will be simpler than I imagined as the "Mux client" can have NoEcho set, and that should prevent any echo problems when muxing (is there a better name) messages between the main NATS subject
namespace and the Authenticated subject namespaces.

The Mux/Store Client is responsible for storing everything in the database and based on the node graph, replicating messages on upstream nodes (that rules, etc) may subscribe to as well as auth subject namespaces for authenticated clients. I'd like to get to the point eventually where no processing happens in the store other than muxing messages, storing the points changes in the DB, and responding to NATS DB requests such as node, and child node requests.

image

@cbrake
Copy link
Member Author

cbrake commented Aug 2, 2022

NATS by example has some interesting auth examples with private inbox:

https://natsbyexample.com/

@cbrake
Copy link
Member Author

cbrake commented Aug 8, 2022

@gedw99
Copy link

gedw99 commented Aug 10, 2022

I was going to suggest the same example :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement improvements on existing functionality research Looking into new ideas
Projects
None yet
Development

No branches or pull requests

3 participants