fn
fn copied to clipboard
Fn http api reveals values of "fn config app" variables to anyone, which may reveal secrets
I've been building an Fn application with functions that have to hold authentication info to connect to a central object store. I passed the login secrets to the Fn functions using "fn config app APP {name_of_secret} {value_of_secret}", and then retrieved them in the function from environment variables. This all worked great :-)
However, while I was exploring the Fn-related API of the app I saw that the config variables are given in plain text to anyone who can connect to the Fn server, via http://{server_url}/v2/apps. Example output is;
{"items":[{"id":"XXXXXX","name":"XXXXX","config":{"SECRET":"XXXXXX"},"created_at":"2018-08-16T10:52:01.187Z","updated_at":"2018-08-16T19:13:04.270Z"} etc. etc.
You can also get the config data using the app's ID, e.g. via http://{server_url}/v2/apps/{APP_ID}
Is this expected behaviour? It surprised me that the config data of a function would be so publicly visible, as this leaks useful information for bad actors.
If this is expected, then what is recommended way to store secrets in functions (e.g. passwords for object stores or databases)? I'd rather not embed them in the containers themselves as then I'd have to use a private container registry and worry about the containers leaking. Using "fn config app" works really well, as it is easy to change after deployment, thereby supporting easy automated password/key rotation.
(this is also the case for the V1 API - I updated everything to V2 to see if it was already fixed, and have scoured docs and issues to see if this has been discussed before and whether I am handling secrets incorrectly)
Hi @chryswoods, yea, this is expected. Agree, this would be a useful feature - of course, we expect most fn deployments to add some kind of auth on top of the API to attenuate permissions on endpoints like getting apps (using extensions, ala https://github.com/fnproject/ext-auth - I think this won't build atm, but the code gives a good idea). We have discussed allowing users to upload encryption keys, and we're discussing how to integrate with KMS (Key Management Service) now too, which seems related - am not sure there's any public links to this discussion at this time (mega :((( ). It does seem like uploading encryption keys to fn on an app (and not showing on GET) and encrypting config / decrypting to hand off to function would satisfy your use case. If you have any thoughts on what you would like, feedback would be great! thanks
Thanks @rdallman - really useful reply. My user-authentication is built on top of OCI object store and the secret is the private key needed to read and write to that store. I agree that the best solution would be encrypting my config and then uploading the decryption key to the functions. Looking at ext-auth (an apologies in advance as my go is basic) it looks like the extension adds in support for holding a secret that is copied from an environment variable (SIMPLE_SECRET) which is copied into the function during "fn deploy". The presence of this secret is checked for in "Setup" (lines 39+40 in simple.go), but nothing is then done with it?
The "Setup" function then continues to initialise a database with rows to hold usernames and passwords (line 90, s.ds.GetDataBase()). How does the function authenticate with this database? Where is the secret held that allows the function calling GetDataBase() to prove that it has read/write access to this database?
(and to follow up on this, where are the environment variables stored for a function? Are they stored with the config, stored inside the container, or is there another location in the Fn server that holds the variables and supplies them when the function is called?)
Regarding using something like an S3-compatible store, worth saying that you still can create presigned URLs that you may feed to your function, therefore, there's no need in auth (unless your usecase is way more complex).
Way more complex I'm afraid :-(
Functions can create new buckets, adjust permissions and provide an interface that sits over the object store as part of an "access, authorization and accounting infrastructure" for running HPC applications on demand as Fn functions. The presentation here gives an overview of what I am building... (https://drive.google.com/file/d/1VB6I-Eu04uszcTojPeOLpZGlsIEdK0yS/view). I have the identity and accounting service working so that I can log in from Jupyter notebooks and perform accounting for data uploads / simulation compute, but then noticed what I thought was secret wasn't...
A presigned URL is a solution, but I then need something that can feed that URL to the function. As the presigned URL is not known by and must not be accessible to the user calling the function, then it would have to be secretly provided by another service, or the function would have to pull it from another service (which then brings me back to needing the function to possess a secret that lets it authenticate itself with that "signed URL" providing service).
In its simplest form, I have functions that modify the state of shared data. They need privileged access to that shared data so that they can modify its state. Thus they need a secret that only they know that provides them with that privileged access.