achetronic / predoxy

A TCP proxy with middleware plugins to process the messages on-the-fly
3 stars 0 forks source link

Locking authenticated user down to single database #1

Open WyriHaximus opened 2 years ago

WyriHaximus commented 2 years ago

For my projects I use https://artifacthub.io/packages/helm/wyrihaximusnet/redis-db-assignment-operator to get a database assigned them. (Sometimes a project uses multiple databases.) And used a shared cluster as backend. The problem with that is is that everyone can access every database. For now that is fine as I build everything that uses that, however when I would introduce a third party application that takes a lot of trust to still do the same thing.

What I'm looking for is a way to authenticate to a proxy and only have access to the designated database. If possible I would even integrate it with the project listed above.

achetronic commented 2 years ago

@WyriHaximus

Hello there 🤓

thanks for contribute with more use cases! I think it is doable. The idea behind this proxy is to be as transparent as possible.

By the moment (it was just born) it works detecting patterns on the request, and changing them before sending to Redis, and then analyzing the response. For example converts MULTI command into a simple ECHO, that goes and comes from Redis with a special format to detect and send the response to the user.

This is only one of the functionalities, because i integrated some others, but the thing you are requesting could include to detect AUTH and SELECT into the cache (the last is already done), and force check both when a flag is set, something like --force-auth-db or something like that. What were your ideas about?

WyriHaximus commented 2 years ago

Hey @achetronic !

This is only one of the functionalities, because i integrated some others, but the thing you are requesting could include to detect AUTH and SELECT into the cache (the last is already done), and force check both when a flag is set, something like --force-auth-db or something like that. What were your ideas about?

How would configuring multiple users and databases work? Can I supply a config file with user + password for a specific database on a specific redis cluster? Does it support TLS of remote clusters out of the box? If I make a change to the config file, will it be picked up without requiring a restart?

WyriHaximus commented 2 years ago

thanks for contribute with more use cases! I think it is doable. The idea behind this proxy is to be as transparent as possible.

By the moment (it was just born) it works detecting patterns on the request, and changing them before sending to Redis, and then analyzing the response. For example converts MULTI command into a simple ECHO, that goes and comes from Redis with a special format to detect and send the response to the user.

P.S., not ignoring the above, looking forward to the result of that as I trust you know better than I do ;).

achetronic commented 2 years ago

Hey @achetronic !

This is only one of the functionalities, because i integrated some others, but the thing you are requesting could include to detect AUTH and SELECT into the cache (the last is already done), and force check both when a flag is set, something like --force-auth-db or something like that. What were your ideas about?

How would configuring multiple users and databases work? Can I supply a config file with user + password for a specific database on a specific redis cluster? Does it support TLS of remote clusters out of the box? If I make a change to the config file, will it be picked up without requiring a restart?

I think we could extend the config.yaml to support that kind of options, or just use repeatable flags. We have to use a flag, at least to enable that feature, just like --force-db-auth and then get the information or from the flags or from a file.

About the way to implement it, I think if that flag is enabled, is a good moment to cache the source, the SELECT and the AUTH command. If not cached the db for that origin, just emit an error for all the commands as a response, only select allowed until cached, and then only auth accepted until cached. If you ask me about the auth implementation, i dunno about it yet :)

Another possibility to implement this is only to be able to protect DB's > 0, this way we can block the commands only if a db is selected, or something like that

About hot reload or TLS, I think we could implement hot-reload first because of the ease. No tls from proxy to the backends until the project is a bit more mature, but for sure this is a good issue to open in the near months.

About hot reload itself, i think it is doable. The problem I see is not how to watch the config, but to reload it into the proxy without exploding it. I will do some testing about it, becuase it could be easy, due to I am already using pointers for almost everything, and the struct related to handling the type TCPProxy is implemented in the way that Config is a pointer to the config spec, so let's check

Do you have some proposal about these topics?

Now, i am reimplementing several things, one of them is the way that messages can be processed in both ways, so you could implement a processor for the messages going and coming from Redis in an easy way in some days without having to touch the proxy itself. It is like a middleware, but uglier but the moment.

In the other side, implementation for supporting giant command forwarding like COMMAND DOCS is happening right now :)

achetronic commented 1 year ago

@WyriHaximus Hello there!

After a whole refactor, I have a response for you. Now the whole project can do whatever you need 😄 I need to implement the plugin-available cache, to be able to store things between the requests, but easy to do.

WDYT?

WyriHaximus commented 1 year ago

Hey @achetronic this refactor looks amazing opening a lot of options. A few questions tho, and I haven't looked into the code yet just docs and examples.

achetronic commented 1 year ago

Hey @achetronic this refactor looks amazing opening a lot of options. A few questions tho, and I haven't looked into the code yet just docs and examples.

* By using plugins this proxy is handing off any custom behavior to the plugins so you can compose your own application with it, correct?

* How does one go from go files to `.so` into a deployable Docker image? (Having an easy copy + paste GHA would be very welcome.)

* Given the example is a k8s CRD, is this becoming an operator and will it be installable through a Helm chart?

* How does one can dynamic configuration in a plugin one creates?

* How does one write a plugin?

Hey @WyriHaximus Thanks for your questions!! for sure they are valuable to find the right path for the project 😄

I will answer them in order, let's see:

WyriHaximus commented 1 year ago
  • It is correct. The point for this is to be able to implement even crazy things. Like a pipeline of lambda functions

Sweet!

  • By the moment i created the Dockerfile but have not created the image itself until I polish some things these days. My intention with this, now that we have an idea, is to create a repo for contributed plugins (or they can live directly here in a directory) with a GHA to compile them all, matching the version of predoxy binary and the plugin's one. This matching is a requirement of Golang for runtime plugins, for security reasons.

Perfect! Yeah that makes perfect sense as all of that has to match up.

  • No, no, I literally copied the style of the config, but it is not a CRD. Did not think about an operator yet. I think this can be useful inside K8s, so we could create a Docker image and a Helm release. After that, may be we have a better big picture to do a useful operator over them, WDYT? :)

Ah ok cool, was just wondering. It also raises the question of the scope you have in mind for this project. IMHO this project isn't an operator but a service in k8s terms. Docker image is the first place I'd start and maybe an optional Helm chart but not at this stage yet.

  • Due to plugins are loaded in memory on startup, the best way is to watch a file with something like fsnotify to watch an specific file of config for the plugin. This could be useful, but we should implement some mechanism for that. One way i think is possible is to load the config inside the OnReceive() function and just check cache with some conditional. But I see this is not the cleanest way and I accept proposals for sure!

That would work, I can just mount a configmap into the pod's container and those get automatically updated when you update the configmap. So fsnotify would be perfect here 🥂 .

  • IMHO the best way to write a plugin is just to copy one of the examples and implement the functions with all you need. From today you can cache things (the latest release is some minutes old, lol) and I updated the Readme with the commands. Please ask all you don't understand from that file, or directly open a PR to correct it. All the help is more than welcome!

Will do a deep dive in the last week of December and the first week of January (when I'm off 😎 ). Love to see the readme and example get expanded every time I come back here to respond <3

achetronic commented 1 year ago
  • It is correct. The point for this is to be able to implement even crazy things. Like a pipeline of lambda functions

Sweet!

  • By the moment i created the Dockerfile but have not created the image itself until I polish some things these days. My intention with this, now that we have an idea, is to create a repo for contributed plugins (or they can live directly here in a directory) with a GHA to compile them all, matching the version of predoxy binary and the plugin's one. This matching is a requirement of Golang for runtime plugins, for security reasons.

Perfect! Yeah that makes perfect sense as all of that has to match up.

  • No, no, I literally copied the style of the config, but it is not a CRD. Did not think about an operator yet. I think this can be useful inside K8s, so we could create a Docker image and a Helm release. After that, may be we have a better big picture to do a useful operator over them, WDYT? :)

Ah ok cool, was just wondering. It also raises the question of the scope you have in mind for this project. IMHO this project isn't an operator but a service in k8s terms. Docker image is the first place I'd start and maybe an optional Helm chart but not at this stage yet.

  • Due to plugins are loaded in memory on startup, the best way is to watch a file with something like fsnotify to watch an specific file of config for the plugin. This could be useful, but we should implement some mechanism for that. One way i think is possible is to load the config inside the OnReceive() function and just check cache with some conditional. But I see this is not the cleanest way and I accept proposals for sure!

That would work, I can just mount a configmap into the pod's container and those get automatically updated when you update the configmap. So fsnotify would be perfect here 🥂 .

  • IMHO the best way to write a plugin is just to copy one of the examples and implement the functions with all you need. From today you can cache things (the latest release is some minutes old, lol) and I updated the Readme with the commands. Please ask all you don't understand from that file, or directly open a PR to correct it. All the help is more than welcome!

Will do a deep dive in the last week of December and the first week of January (when I'm off 😎 ). Love to see the readme and example get expanded every time I come back here to respond <3

Hello again! @WyriHaximus

I have been thinking about the proposal and the thoughts :)

Please, enjoy the vacation! 🍹😄 After a good relaxing time, ping me when you test all these things. For sure i will keep thinking about this config problem possible solutions 🚀