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

Proposal: Introduce Feature Flags for Modular Build Configuration #3777

Open
joelonsql opened this issue Nov 10, 2024 · 10 comments
Open

Proposal: Introduce Feature Flags for Modular Build Configuration #3777

joelonsql opened this issue Nov 10, 2024 · 10 comments
Labels
idea Needs of discussion to become an enhancement, not ready for implementation

Comments

@joelonsql
Copy link
Contributor

Hello fellow PostgREST hackers,

I'm interested in exploring the possibility of introducing feature flags in PostgREST to allow users to exclude specific existing and future features at build time, similar to Rust's feature flags or C's ./configure --without-[feature]. I believe this could make PostgREST more modular and customizable, enabling users to build it with only the functionalities they require. This approach could enhance security by reducing the attack surface and improve performance through lower memory consumption.

I'm curious if Haskell supports this kind of conditional compilation. I've read a bit about Cabal Flags but am unsure whether they would just disable code or actually exclude it from being built. I've also learned that Haskell can use the C Preprocessor (CPP) for conditional compilation. I'm not certain which approach would be best and would greatly appreciate any input or suggestions!

Personally, I'm particularly interested in building an RPC-only version of PostgREST. I've successfully built production systems that accessed PostgreSQL strictly via database functions, without needing the following functionalities:

  • Resource-oriented REST API (Tables/Views)
  • Query Parameter Operations
  • Computed Fields
  • Pagination and Count
  • Resource Embedding
  • Aggregate Functions
  • JWT

I'm eager to contribute to this effort and would love to hear your thoughts on its feasibility and value to the project.

Thank you!

Joel Jacobson

@wolfgangwalther
Copy link
Member

A few first thoughts on the topic:

  • That's a lot of stuff that you want to have disabled :)
  • I'm not keen on having conditionals all over the place - so I think to get there, the first step would need to be to modularize the code base in a way, so that those features can be easily toggled on and off in some central place.
  • The one thing that I would like to be able to disable right now is the OpenAPI output at the root endpoint. This would allow us to continue working on some cross-compiling things that are currently blocked by the related dependencies. This would only be temporary, because we are working on replacing the whole OpenAPI thing entirely anyway. But, the OpenAPI module is fairly isolated already, so it could maybe serve as a testcase for how to set up the flags etc.
  • If were able to disable parts of PostgREST, we could probably also switch between different implementations for certain parts. This would allow us to add something like a separate implementation for the query language, see Improvements to query language #2066.

TLDR: While I am not really interested in being able to turn off all the features you mentioned above for myself, I can see a lot of upside in going in that direction for the changes required to get there.

That being said - I think figuring out how to work with cabal flags, the C preprocessor etc. would be the simple part. It will be much harder to split the code-base up nicely, so that this actually works. It would require major refactoring - something that @monacoremo started a long time ago in #1804, but hasn't been continued in a while.

@wolfgangwalther
Copy link
Member

I've read a bit about Cabal Flags but am unsure whether they would just disable code or actually exclude it from being built. I've also learned that Haskell can use the C Preprocessor (CPP) for conditional compilation. I'm not certain which approach would be best and would greatly appreciate any input or suggestions!

FWIW, I think both would be used together. You set a cabal flag, which you can then use in CPP to conditionally compile.

@wolfgangwalther wolfgangwalther added the idea Needs of discussion to become an enhancement, not ready for implementation label Nov 10, 2024
@wolfgangwalther
Copy link
Member

I've successfully built production systems that accessed PostgreSQL strictly via database functions

I assume the intent here is to separate your data layer from the "access layer" or whatever you call it. So those database functions would be that layer. Note that, to have a nice HTTP api, it's much more convenient to actually use VIEWs for that layer as well and not limit yourself to RPCs. For RPCs you only have GET and POST, while for views, you have GET, POST, PATCH, DELETE - but you can still fully control all the logic in INSTEAD OF triggers. Those are kind of similar to your RPCs, but give you a cleaner interface.

We call this Schema Isolation. I strongly suggest you think about that, too.

This approach could enhance security by reducing the attack surface and improve performance through lower memory consumption.
[...]
without needing the following functionalities:
[...]

  • JWT

Could you elaborate on how you are securing access to this with a focus on security but without JWT support? Curious! :)

@steve-chavez
Copy link
Member

that accessed PostgreSQL strictly via database functions, without needing the following functionalities:
That's a lot of stuff that you want to have disabled :)

Hm, yeah a lot to disable. @joelonsql Maybe we should start from what you actually need? Is it just to be able to call /func and specify arguments on the query string /func?x=1&y=2?

@steve-chavez
Copy link
Member

This package was shared before on the subject of conditional compilation: https://hackage.haskell.org/package/plugins

@wolfgangwalther
Copy link
Member

This package was shared before on the subject of conditional compilation: https://hackage.haskell.org/package/plugins

At first glance this seems very outdated, no action on github for the last two years, no supported GHC version in the 9.x series.

@joelonsql
Copy link
Contributor Author

that accessed PostgreSQL strictly via database functions, without needing the following functionalities:
That's a lot of stuff that you want to have disabled :)

Hm, yeah a lot to disable. @joelonsql Maybe we should start from what you actually need? Is it just to be able to call /func and specify arguments on the query string /func?x=1&y=2?

Yes, only need calling functions, passing arguments as a POST data in JSON.

@joelonsql
Copy link
Contributor Author

Could you elaborate on how you are securing access to this with a focus on security but without JWT support? Curious! :)

Using the postgrest.conf feature pre-request = "public.auth" and then good ol' Role-based access control (RBAC).
Here is a proof-of-concept: https://github.com/truthly/uniphant/blob/master/FUNCTIONS/auth.sql

I haven't worked on the uniphant repo for quite some time, so it might not be working, but should be enough to give an idea on the concept.

@joelonsql
Copy link
Contributor Author

joelonsql commented Dec 5, 2024

I assume the intent here is to separate your data layer from the "access layer" or whatever you call it. So those database functions would be that layer. Note that, to have a nice HTTP api, it's much more convenient to actually use VIEWs for that layer as well and not limit yourself to RPCs. For RPCs you only have GET and POST, while for views, you have GET, POST, PATCH, DELETE - but you can still fully control all the logic in INSTEAD OF triggers. Those are kind of similar to your RPCs, but give you a cleaner interface.

We call this Schema Isolation. I strongly suggest you think about that, too.

Yes! I forgot to mention, I actually use VIEWs also, but only for read operations, as I believe write operations are better handled via functions. When I need user-filtered data, I'm using the user_id GUC set by my RBAC implementation upon successful authentication and authorization, and then using that to filter VIEWs. Here is an example:

CREATE OR REPLACE VIEW api.current_user WITH (security_barrier) AS
SELECT
user_id,
username
FROM users
WHERE user_id = user_id();

I prefer this explicit filtering, which makes it obvious when reading the view definition that it's being filtered, rather than relying on RLS.

@joelonsql
Copy link
Contributor Author

In my RBAC implementation, functions and views are two different resource_type's:

https://github.com/truthly/uniphant/blob/master/TABLES/resources.sql#L5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
idea Needs of discussion to become an enhancement, not ready for implementation
Development

No branches or pull requests

3 participants