By Tcoch

Using Symfony EnvVarProcessor to inject secrets into your app


Why this article ?

I recently had to inject a password into an app, which was already defined inside my Secret Manager. I though: do I really need to copy this inside a .env file? Or even as a Symfony Secret?
Well, the short answer is: no! Let's find out how we can manage this with Symfony's environment variables processor.

What is an environment variable processor ?

Basically, a Symfony's Environment Variable Processors is a service that allows to transform the original content of a given environment variable into something else: a new string reformated, an int, ...
By implementing the EnvVarProcessorInterface, everyone is able to define their own service / their own processor. An example is given in the official documentation, where the service is used to always render the lower-case version of a string. Well, isn't this nice? :)
But how does that help me?

Processing variable through a service

Since we can create a service that will implement EnvVarProcessorInterface, we can also beneficiate of services injection. This means we can provide a service that will make an API call to an exterior service!
Example below.
# src/Service/GetMySecretProcessor.php

use App\Service\Secrets\Secret;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;

class GetMySecretProcessor implements EnvVarProcessorInterface
{
    public function __construct(private VaultApiService $vaultApiService)
    {
    }

    public function getEnv(string $prefix, string $name, \Closure $getEnv): string
    {
        // Do something to retrieve your secret value
        // Probably something like :
        // return $this->vaultApiService->resolve($name);
    }

    public static function getProvidedTypes(): array
    {
        return ['get_my_secret' => 'string'];
    }
}
In this example:
  • VaultApiService is your service, your logic, that will do what needs to be done to interrogate your secret manager (typically an API call).
  • getProvidedTypes() defines which 'tag' is resolved by your service. We'll see this in action in a second.
  • getEnv() defines the logic to retrieve the value of the password.

Using this service to get a secret's value

Now that we have created our own environment variable processor, how to use it? Simple!
Simply use Symfony autowiring to inject your variable, as an environment variable, in a service that requires this sensitive value.
On the plus side, this will work both in PHP files ... And in yaml files too!

For example:
# src/Controller/Controller.php

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class Controller extends AbstractController
{
    #[Route('/', name: 'index')]
    public function index(#[Autowire(env: 'get_my_secret:my_github_access_token')] string $myGithubAccessToken): Response
    {
        // $myGithubAccessToken now contains the value of your token, automatically fetched.
    }
}
# packages/framework.yaml
framework:
    http_client:
        scoped_clients:
            github.client:
                scope: 'https://api\.github\.com'
                headers:
                    Authorization: 'token %env(get_my_secret:my_github_access_token)%'
A quick precision on what has been defined for the example above:
  • get_my_secret is the 'tag' used to link your newly defined environment variable processor. It is referenced in the getProvidedTypes() method.
  • my_github_access_token is the $name that will be passed to your service that implements the EnvVarProcessorInterface, to the getEnv() function.
Congratulations! With this approach, you managed to get a secret value inside your application, without ever exposing it in a .env file or even as a Symfony Secret (that would be a duplicate of your original definition in your Secret Manager).
Don't forget however that:
  • This approach still requires you to store credentials to your Secret Manager somewhere in your Symfony app.
  • API requests (in most cases) will be sent every time you need a password injected to your services. Try implementing some cache for your secret value.