8000 [DI] Add support for secrets by dunglas · Pull Request #23621 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[DI] Add support for secrets #23621

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

Closed
wants to merge 1 commit into from
Closed

[DI] Add support for secrets #23621

wants to merge 1 commit into from

Conversation

dunglas
Copy link
Member
@dunglas dunglas commented Jul 22, 2017
Q A
Branch? 3.4
Bug fix? ni
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes/no
Fixed tickets symfony/flex#50
License MIT
Doc PR todo

The goal of this PR is to allow to use a low level secret management system such as Docker Secrets, Kubernetes Secrets with Symfony.

What this PR basically does is allowing to inject dynamically the content of a file (because Docker and Kubernetes secrets are mounted as files in containers) in the container. It can be used like the following:

services:
   App\Database: {'$username': '%env(DATABASE_USERNAME)%', '$password': '%secret(/path/to/a/password/file)%'}

The path can also be defined as an environment variable (useful to create applications working with different secret management systems):

services:
   App\Database: {'$username': '%env(DATABASE_USERNAME)%', '$password': '%secret(env(DATABASE_PASSWORD_FILE))%'}

It can also resolve relative paths (it will look for a file relative to kernel.project_dir if this parameter exist, or to the container file if it doesn't):

services:
   App\Database: {'$username': '%env(DATABASE_USERNAME)%', '$password': '%secret(secrets/database_password)%'}

This PR doesn't deal at all with secret encryption and storage (it should be delegated to a lower level system).
It doesn't allow, for instance, to store passwords in a Git repository.
However, it handles dynamic secrets (for instance Kubernetes secrets can be changed at runtime) the same way dynamic environment variables are already handled.

This PR is not ready yet, I've opened it for early reviews.

< 8000 circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none" />

@javiereguiluz
Copy link
Member

Thanks for working on such an important feature.

However, creating a PR directly instead of an issue, and not providing a description makes this very difficult to review. Honestly, I don't care about the technical implementation of this feature. I only care about being easy to learn and simple to use. Please, show us how do you propose to use this feature:

  • How are secrets added, edited and removed?
  • Can you have multiple secret files (per environment, per "feature" or package, etc.)? If so, how?
  • How do you set, edit and change the key that encrypts the secrets?
  • What about deployment? How do you manage the encryption key deployment?
  • How does this feature integrates with everything else? Cotnainer parameters, runtime env vars, console commands that display the value of paramenters, etc.
  • How does this integrate with Docker, VMs and other virtual setups?
  • Can you show us a full example of how to use this? Config needed, commands to execute to setup, etc. You can use the Symfony Demo app as an example.

I want to carefully think about all this before implementing the feature. Otherwise, we can make the same mistakes as RubyOnRails, which added secret support in 5.1, but forgot to add a command to display the secret values and they are adding it in their upcoming 5.2 version. Thanks!

@dunglas
Copy link
Member Author
dunglas commented Jul 22, 2017

@javiereguiluz I'm writing a blog post, but it will not be ready before I go in vacation. As stated in the description, some people have requested access to my POC, it's why I've opened this PR. If you think it hurt, feel free to close it for now.

This PR doesn't deal with encryption, it only load files at runtime. It's better to use a lower level layer (such as Docker or Kubernetes secrets) for encryption. The purpose of this PR is just to give access to secrets exposed as files. Tests should be self-explanatory.

8000

@fabpot
Copy link
Member
fabpot commented Jul 22, 2017

For the record, and because @javiereguiluz talked about RubyOnRails, I don't see which problem it solves. Ot put another way, it's far from being a complete solution. I do understand that it allows passwords to be safely stored in a Git repository, but the master password still needs to be stored in a file or an env var on machines. So, it does not solve the most important issue to me: how to avoid passwords to be stored permanently on servers. Solutions like Hashicorp Vault (and many others) lets you "fix" that, but then why not use those solutions to store all passwords.

So, regarding this pull request, I think that a good question could be: how do Symfony integrates with solutions like Vault?

@dunglas
Copy link
Member Author
dunglas commented Jul 22, 2017

To clarify, this PR is not related at all with the secret feature of RoR.

I've updated the PR description with more information. I don't know how Hashicorp Vault works, but if it can expose secrets as files, it is compatible with this PR.

@dunglas
Copy link
Member Author
dunglas commented Jul 22, 2017

To summarize:

How are secrets added, edited and removed?

It's not in the scope of this PR, use Docker or Kubernetes secrets (or anything else) for that.

Can you have multiple secret files (per environment, per "feature" or package, etc.)? If so, how?

Yes. One %secret()% per file.

How do you set, edit and change the key that encrypts the secrets?

It's not in the scope of this PR, use Docker or Kubernetes secrets (or anything else) for that.

What about deployment? How do you manage the encryption key deployment?

It's not in the scope of this PR, use Docker or Kubernetes secrets (or anything else) for that.

How does this feature integrates with everything else? Cotnainer parameters, runtime env vars, console commands that display the value of paramenters, etc.

It's the main goal of this PR, it basically works the same way that '%env()%'. There is probably some more work to do here to not display secrets in debugging tools and logs.

How does this integrate with Docker, VMs and other virtual setups?

Well, it's the main goad of this PR.

Can you show us a full example of how to use this? Config needed, commands to execute to setup, etc. You can use the Symfony Demo app as an example.

See the updated description. I'll publish a blog post and a demo app in some weeks.

@marfillaster
Copy link
Contributor
%secret(env(DATABASE_PASSWORD_FILE))%

How about these syntax?
%secret(filepath)%
%secret(ENV_VARIABLE, env)% # same as %env()%
%secret(somekey, resolver.service_name)%

Second parameter is for whatever resolver.

@nicolas-grekas
Copy link
Member
nicolas-grekas commented Jul 22, 2017

The new "secret()" prefix allows fetching the content of a file at runtime. Is there anything specific to "secret" management? I mean, the name could be "file_content()" or some similar alternative that convey what this does actually, isn't it? Not suggesting yet this is what we should do, just wondering :)

This PR duplicates a lot of logic from env() vars management. I think there is another way to reuse the existing code infrastructure in place. I'd suggest to keep using the "env()" syntax, but add special behavior for what's inside the brackets. "env()" would refer to a generic context, defaulting to env vars, but that we could point to another source for contextual data, like local files.

I tried something similar in #20276. Not sure the syntax nor the feature then are exactly what we want to have now, but I think it could be a good starting point.

@dkarlovi
Copy link
Contributor

the master password still needs to be stored in a file or an env var on machines

This is actually done by the underlying system, Kubernetes allows you to inject "secrets" inside your running container via a pseudo-filesystem. It's basically a way to do the same thing env vars do, but in a much more secure way as env variables leak out of your app.

@dkarlovi
Copy link
Contributor

@dunglas props for pushing this! \o/

As @marfillaster mentioned, it seems important not having to hardcode path to secret but inject the path via env. This allows ops more flexibility with their setup.

@nicolas-grekas
Copy link
Member
nicolas-grekas commented Jul 23, 2017

I think the management of "env()" inside "secret()" should be removed because it adds complexity, yet looks unneeded if you allow configuring the relative prefix of paths (ie make that "kernel.project_dir" default configurable). For more advanced use cases, they should be rare (all vaults put secrets in the same root, isn't it?), then admins could use symlinks.

@ro0NL
Copy link
Contributor
ro0NL commented Jul 23, 2017

I like the simplicity of %secret(/path/to/file)% without forcing env vars;

parameters:
    secret: '%secret(/path/to/file.ext)%

I mean, the name could be "file_content()" or some similar alternative that convey what this does actually, isn't it?

Looks like it :)

@dkarlovi
Copy link
Contributor

@nicolas-grekas

unneeded if you allow configuring the relative prefix of paths (ie make that "kernel.project_dir" default configurable)

This is reasonable, if it's per Symfony environment, you could run development with Compose and production with Kube, having different prefix on each.

For more advanced use cases, they should be rare (all vaults put secrets in the same root, isn't it?), then admins could use symlinks.

They actually can't ("shouldn't need to", to be precise) because the symlink would need to be inside the container, complicating your whole runtime. You don't want the app image to need any tweaks before it gets run, it should be ready-set-go as much as possible.

For example, if you downloaded an Android APK and needed to switch a few bits around with a hex editor before running it, it wouldn't make a great UX.

@nicolas-grekas
Copy link
Member

Closing in favor of #23901
Thanks for pushing this @dunglas.

fabpot added a commit that referenced this pull request Sep 7, 2017
This PR was merged into the 3.4 branch.

Discussion
----------

[DI] Allow processing env vars

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | see description
| License       | MIT
| Doc PR        | -

This PR is an updated version of #20276 ~~(it embeds #23899 for now.)~~

It superscedes/closes:
- [DI] Add support for secrets #23621 ping @dunglas
- Runtime container parameter not found event filter #23669 ping @marfillaster
- [DependencyInjection] [DX] Support for JSON string environment variables #23823 ping @Pierstoval
- add support for composite environment variables #17689 ping @greg0ire
- [DI] ENV parameters at runtime with PHP 7 strict types not working properly #20434 ping @sandrokeil
- Issue when using a SQLite database and the DATABASE_URL env var #23527 ping @javiereguiluz

#22151 is another story, so not fixed here.

The way it works is via `%env(foo:BAR)%` prefixes, where "foo" can be bound to any services you'd like.
By default, the following prefixes are supported:
- `bool`, `int`, `float`, `string`, `base64`
- `const` (for referencing PHP constants)
- `json` (supporting only json **arrays** for type predictability)
- `file` (eg for access to secrets stored in files.)
- `resolve` (for processing parameters inside env vars.)

New prefixes can be added by implementing the new `EnvProviderInterface`, and tagging with `container.env_provider` (see `Rot13EnvProvider` in tests.)

Prefixes can be combined to chain processing, eg.
`%env(json:base64:file:FOO)%` will be roughly equivalent to
`json_decode(base64_decode(file_get_content(getenv('FOO'))))`.

Commits
-------

1f92e45 [DI] Allow processing env vars
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants
0