framework icon indicating copy to clipboard operation
framework copied to clipboard

[9.x] New `env:encrypt` and `env:decrypt` commands

Open joedixon opened this issue 1 year ago • 7 comments

This PR proposes two new Artisan commands; env:encrypt and env:decrypt which provide a mechanism for encrypting and decrypting .env files.

Inspiration for this was taken from Rails who have had similar functionality since Rails 5.1 released in 2017.

The biggest benefit of this is that the encrypted environment files can be committed to version control which opens up a number of possibilities.

  1. Production and staging environment variables can be committed to version control and automatically decrypted as part of a deployment. This prevents the need to remember to add/update environment variables prior to/during a deployment.
  2. Local environments can be shared between development teams. If a new environment variable is added as part of a pull request, there is no longer a need to communicate this information. Instead, simply ensure the environment is decrypted after pulling down the latest updates.
  3. CI environments can be updated without manual intervention.
  4. Environment files for all environments can be encrypted with different keys meaning all can reside in the project, but the relevant keys only need to be shared with those that require it.
  5. The environment can become a living, breathing part of an application.

Real world examples where these commands can be used include ecosystem tools such as Forge, Vapor and Envoyer.

With Forge and Envoyer, it would be possible to decrypt the environment as part of the deployment script. With Vapor, this would help solve an issue with the limit on the number of environment variables allowed by Lambda and the cost associated with decrypting secrets from KMS.

Decryption can be carried out either by passing a --key option to the command or by setting the LARAVEL_ENV_ENCRYPTION_KEY environment variable. The affordance of the environment variable means it would be use in conjunction with secrets in a service like GitHub Actions.


env:encrypt

Running php artisan env:encrypt will look for a .env file at the root of the project, grab the contents of the file, encrypt it with a new key and save it as .env.encrypted.

The decryption key is displayed in the output of the command along with the cipher used and the path of the encrypted file.

image

You may utilise your own key by using the --key option.

php artisan env:encrypt --key=h9kAPUmxdZ8ZbwT3

Similarly, you may specify any of the ciphers supported by Laravel’s [Encrypter](https://github.com/laravel/framework/blob/9.x/src/Illuminate/Encryption/Encrypter.php#L32-L37) class using the --cipher option. If no key is passed when using this option, the command will generate a key of the correct length for the cipher.

php artisan env:encrypt --cipher=aes-256-cbc

You may also utilise the --env option to tell the command which environment you wish to encrypt.

php artisan env:encrypt --env=production

The above command will look for an environment file called .env.production. If the file exists, the contents will be encrypted and stored in a file called .env.production.encrypted.

If an encrypted file already exists at the location where the command is attempting to store it, it will not be overwritten by default. Of course, you may choose to do so using the --force option.

php artisan env:encrypt --force

env:decrypt

The env:decrypt command decrypts the contents of the encrypted environment file and writes the output to the .env of the chosen environment.

A decryption key is required to run this command which can be obtained from one of two places.

  1. Passing the --key options
  2. The command will look for the presence of an environment variable called LARAVEL_ENV_ENCRYPTION_KEY

The --key option takes precedence over the environment variable. The purpose of the environment variable is to make the decryption process simple and secret during deployment/CI.

php artisan env:decrypt --key=h9kAPUmxdZ8ZbwT3

Like the encrypt command, you may also pass the cipher used to encrypt the file.

php artisan env:decrypt --key=h9kAPUmxdZ8ZbwT3 --cipher=aes-128-gcm

In the decrypt command, passing the --env option will result in the command looking for a file called .env.[environment].encrypted to decrypt which, if found and decrypted successfully, will be written to .env.[environment]. This format is already supported by Laravel.

Screenshot 2022-09-06 at 15 03 26

If a file already exists where the command attempts to write the file to, it will not be overwritten. This behaviour can be forced with the --force option.

Sometimes, for example if you were running the command as part of a forge deployment, you may wish to decrypt the contents of file to a different filename. You may do this with the --filename option.

php artisan env:decrypt --key=h9kAPUmxdZ8ZbwT3 --env=production --filename=.env"

Running the command above would find the encrypted file .env.production.encrypted and write the decrypted contents to .env.

joedixon avatar Sep 07 '22 12:09 joedixon

This is something that should be a package used as part of app deployment flow, if people have any use for it.

Few fundamental issues come to mind with this:

  • What's the long term issue if there was a security vulnerability discovered with one of the ciphers? That env and all it's contents would sit long-lived in your version control system. There really is no need to have sensitive information committed to version control, encrypted or not.

  • What if you accidentally deploy .env file locally that should only be used in production? Suddenly one of your developers is sending out live emails to Amazon SES and uploading files to S3 because that's how the production env configured it. .env files are very specific to the environment they are intended to be used in and should be configured explicitly.

  • If you have to use a key to decrypt the file, is there really much difference as compared to sharing the contents of the .env file itself?

Overall this seems like a solution looking for a problem. The .env file itself is a simple paradigm used for years by many applications, meant to live outside your version control, already decrypted with sensitive information.

garygreen avatar Sep 07 '22 15:09 garygreen

  • What's the long term issue if there was a security vulnerability discovered with one of the ciphers?

Just adding here that a leaked key has the same problem. Even keys that are no longer used, and might not be realised to be sensitive anymore, still decrypts the version of the encrypted env file at the point in time it was used. The git history would have to be altered for this to be resolved.

Krisell avatar Sep 08 '22 06:09 Krisell

This is a nope for me. You're moving the secrets problematic from the deployment server back to the source code, where it has more surface of attack in case of leakage.

DarkGhostHunter avatar Sep 10 '22 19:09 DarkGhostHunter

This PR takes inspiration from Rail's own version of this feature, Secrets.

@joedixon has simplified Laravel's implementation of this since keys are typically stored within a .env file. So long as you don't store the key within the repository, your encrypted environment file is safe.

For comparison, the Rails implementation of this can be found at https://github.com/rails/rails/pull/28038

jbrooksuk avatar Sep 13 '22 07:09 jbrooksuk

Symfony also has its "vault" / secret security management system so that the sensitive environment variable values can be committed to version control -> https://symfony.com/doc/current/configuration/secrets.html

It uses X25519 asymmetric cryptographic keys which are IMO safer than the symmetric encryption that this PR provides. The Sodium PHP extension is being used for that purpose and it does not allow you to use different (potentially weaker) algorithms/ciphers.

https://github.com/symfony/symfony/blob/v6.1.4/src/Symfony/Bundle/FrameworkBundle/Secrets/SodiumVault.php#L68-L69

Also it has a default convention as to where it stores the public key and the encrypted file (which takes into account the application environment so it knows when to create the encrypted file in for example config/secrets/dev and when to create it in config/secrets/prod) and then it knows how to read the encrypted file also based on the environment so it's a lot harder to shoot yourself in the foot and use for example the local env file in prod or vice versa. It also provides nice utilities (CLI commands) for listing the secrets (and to reveal their values if you have the decryption key) and for rotating the keys (which is really useful when a developer employee leaves the company).

TLDR; IMO the feature itself is totally valid (other frameworks like Rails and Symfony have this too), but I think that in its current state it needs a bit of polish.

X-Coder264 avatar Sep 13 '22 23:09 X-Coder264

@X-Coder264 could you elaborate how using a different encryption scheme would be safer? What is unsafe about the approach proposed?

taylorotwell avatar Sep 16 '22 18:09 taylorotwell

@X-Coder264 could you elaborate how using a different encryption scheme would be safer? What is unsafe about the approach proposed?

I think he refers that is safer because is asym. To me, passing the public key is not far off of using a symmetric key, which in turn is not far off of using a list of secrets.

Now that I put more thought into it, I think is not bad, you're just now adding all eggs into one basket.

Asym has the same surface of attack that sym to decode the keys, and asym only adds more friction to the implementation. If approved, caution should be advised, because it will work as a keychain.

DarkGhostHunter avatar Sep 16 '22 20:09 DarkGhostHunter

I like the convenience this provides as it allows you to keep all your configuration in the same repo as your application code. I have my own custom solution to encrypt values in my .env files and it has been a great developer experience.

I would suggest that rather than encrypting the entire .env file, only the secret values should be encrypted or else store the encrypted values in a separate file. It looks like Rails and Symfony store the encrypted values in a separate file from the main configuration. This is helpful because then non-sensitive values are easier to view and work with.

I took inspiration from how APP_KEY=base64:<app key goes here> has a base64 prefix and added support for ENCRYPT and ENCRYPTED prefixes. If I want to add a new encrypted value to my .env then I simply add it like

MYKEY=ENCRYPT:foo

then run a command that will look for any lines starting with ENCRYPT then encrypt the value and replacing it with

MYKEY=ENCRYPTED:<encrypted value>

I have a service provider that is responsible for decrypting the encrypted values and updating the config. It may be possible to use a vlucas/phpdotenv custom loader to handle decrypting the encrypted values.

ejunker avatar Sep 23 '22 22:09 ejunker

The benefits of asymmetric keys are that you can have the production server and senior DevOps people have possession of the private key, whereas everyone including the junior devs can have possession of the public key. And then even junior devs could add to the encrypted env file, but they couldn't read from it, thus they couldn't do any damage; assuming junior devs aren't yet trusted with sensitive production secrets.

amcsi avatar Oct 02 '22 12:10 amcsi