feat: add environment file encryption and decryption commands#896
feat: add environment file encryption and decryption commands#896hwbrzzl merged 21 commits intogoravel:masterfrom kuafuRace:master
Conversation
- Add `env:encrypt` command to generate `.env.encrypted` - Add `env:decrypt` command to generate `.env` from `.env.encrypted`
|
Important Review skippedAuto reviews are limited to specific labels. 🏷️ Labels to auto review (1)
Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #896 +/- ##
==========================================
+ Coverage 67.65% 67.83% +0.18%
==========================================
Files 152 154 +2
Lines 10050 10194 +144
==========================================
+ Hits 6799 6915 +116
- Misses 2925 2943 +18
- Partials 326 336 +10 ☔ View full report in Codecov by Sentry. |
- Add `env:encrypt` command to generate `.env.encrypted` - Add `env:decrypt` command to generate `.env` from `.env.encrypted`
- Add `env:encrypt` command to generate `.env.encrypted` - Add `env:decrypt` command to generate `.env` from `.env.encrypted`
…t encryption key
hwbrzzl
left a comment
There was a problem hiding this comment.
Great feature 👍 Could you add some test cases for the new logic? It should be very simple via AI. And could you add a screenshot for the .env.encrypt file content in the PR description?
| ctx.TwoColumnDetail("Key", key) | ||
| ctx.TwoColumnDetail("Cipher", "AES-256-CBC") | ||
| ctx.TwoColumnDetail("Encrypted file", ".env.encrypted") | ||
| return err |
There was a problem hiding this comment.
| return err | |
| return nil |
| plaintext, err := os.ReadFile(".env") | ||
| if err != nil { | ||
| ctx.Error("Environment file not found.") | ||
| return |
There was a problem hiding this comment.
| return | |
| return nil |
| Negative: "No", | ||
| }) | ||
| if !ok { | ||
| return |
There was a problem hiding this comment.
| return | |
| return nil |
| encryptedData, err := os.ReadFile(".env.encrypted") | ||
| if err != nil { | ||
| ctx.Error("Encrypted environment file not found.") | ||
| return |
There was a problem hiding this comment.
| return | |
| return nil |
| Negative: "No", | ||
| }) | ||
| if !ok { | ||
| return |
There was a problem hiding this comment.
| return | |
| return nil |
| } | ||
| padding := int(data[length-1]) | ||
| if padding < 1 || padding > aes.BlockSize { | ||
| return nil, errors.New("invalid padding") |
| return nil, errors.New("invalid padding") | ||
| } | ||
| if length < padding { | ||
| return nil, errors.New("data shorter than padding") |
| } | ||
| ciphertext, err := encrypt(plaintext, []byte(key)) | ||
| if err != nil { | ||
| panic(err) |
| base64Data := base64.StdEncoding.EncodeToString(ciphertext) | ||
| err = os.WriteFile(".env.encrypted", []byte(base64Data), 0644) | ||
| if err != nil { | ||
| panic(err) |
|
|
||
| func encrypt(plaintext []byte, key []byte) ([]byte, error) { | ||
| if len(key) == 0 { | ||
| return nil, errors.New("A encryption key is required. ") |
|
…ile.PutContent` method
….PutContent` method
….PutContent` method
…d decryption commands
hwbrzzl
left a comment
There was a problem hiding this comment.
Great, there are basic test cases now, we can optimize them deeply.
| "github.com/goravel/framework/support/file" | ||
| "os" | ||
| "reflect" | ||
| "testing" | ||
|
|
||
| "github.com/goravel/framework/contracts/console" | ||
| "github.com/goravel/framework/contracts/console/command" | ||
| mocksconsole "github.com/goravel/framework/mocks/console" | ||
| "github.com/stretchr/testify/suite" |
There was a problem hiding this comment.
Sort it:
system packages
external packages
internal packages
| "github.com/goravel/framework/support/file" | |
| "os" | |
| "reflect" | |
| "testing" | |
| "github.com/goravel/framework/contracts/console" | |
| "github.com/goravel/framework/contracts/console/command" | |
| mocksconsole "github.com/goravel/framework/mocks/console" | |
| "github.com/stretchr/testify/suite" | |
| "os" | |
| "reflect" | |
| "testing" | |
| "github.com/goravel/framework/contracts/console" | |
| "github.com/goravel/framework/contracts/console/command" | |
| "github.com/goravel/framework/support/file" | |
| mocksconsole "github.com/goravel/framework/mocks/console" | |
| "github.com/stretchr/testify/suite" |
| _, err := os.ReadFile(".env.encrypted") | ||
| if err != nil { | ||
| // mockContext.EXPECT().Error("Encrypted environment file not found.").Once() | ||
| encryptCommandTestSuite := &EnvEncryptCommandTestSuite{} | ||
| TestEnvEncryptCommandTestSuite(s.T()) | ||
| encryptCommandTestSuite.SetupTest() | ||
| } |
There was a problem hiding this comment.
You can create the .env.encrypted file in the SetupSuite method, and remove the file in the TearDownSuite method.
| env, err := os.ReadFile(".env") | ||
| if err == nil { | ||
| mockContext.EXPECT().Confirm("Environment file already exists, are you sure to overwrite?", console.ConfirmOption{ | ||
| Default: true, | ||
| Affirmative: "Yes", | ||
| Negative: "No", | ||
| }).Return(true, nil).Once() | ||
| s.Require().Equal("APP_KEY=12345\n", string(env)) | ||
| } |
There was a problem hiding this comment.
I don't find the .env file is created. Can these lines be removed?
| mockContext.EXPECT().Option("key").Return(key).Once() | ||
| mockContext.EXPECT().Success("Encrypted environment successfully decrypted.").Once() | ||
| s.Nil(envDecryptCommand.Handle(mockContext)) | ||
| s.Nil(file.Remove(".env.encrypted")) |
There was a problem hiding this comment.
I think the .env file should also be removed (in the TearDownSuite method).
| return nil | ||
| } | ||
|
|
||
| func decrypt(ciphertext []byte, key []byte) ([]byte, error) { |
There was a problem hiding this comment.
Created a single test case for this method.
| } | ||
| } | ||
|
|
||
| func (s *EnvDecryptCommandTestSuite) TestHandle() { |
There was a problem hiding this comment.
Only covered the success flow, we need to cover more necessary error flows.
| } | ||
| } | ||
|
|
||
| func (s *EnvEncryptCommandTestSuite) TestHandle() { |
|
FYI, about the test case, different test cases should not affort each other. |
There was a problem hiding this comment.
The sort is correct, should not change this.
| if key == "" { | ||
| ctx.Error("A decryption key is required.") | ||
| return nil | ||
| } |
There was a problem hiding this comment.
This cannot be removed because the key must be passed during decryption
| "github.com/goravel/framework/contracts/console/command" | ||
| mocksconsole "github.com/goravel/framework/mocks/console" | ||
| "github.com/goravel/framework/support/file" | ||
| "github.com/stretchr/testify/suite" |
There was a problem hiding this comment.
Ditto, should:
system packages
third packages
internal packages
| suite.Run(t, new(EnvDecryptCommandTestSuite)) | ||
| } | ||
|
|
||
| func (s *EnvDecryptCommandTestSuite) SetupTest() { |
There was a problem hiding this comment.
| func (s *EnvDecryptCommandTestSuite) SetupTest() { | |
| func (s *EnvDecryptCommandTestSuite) SetupSuite() { |
| "github.com/goravel/framework/contracts/console/command" | ||
| mocksconsole "github.com/goravel/framework/mocks/console" | ||
| "github.com/goravel/framework/support/file" | ||
| "github.com/stretchr/testify/suite" |
| _, err := os.ReadFile(".env") | ||
| if err != nil { | ||
| mockContext.EXPECT().Error("Environment file not found.").Once() | ||
| } | ||
|
|
||
| _, err = os.ReadFile(".env.encrypted") | ||
| if err == nil { | ||
| mockContext.EXPECT().Confirm("Encrypted environment file already exists, are you sure to overwrite?", console.ConfirmOption{ | ||
| Default: true, | ||
| Affirmative: "Yes", | ||
| Negative: "No", | ||
| }).Return(true, nil).Once() | ||
| } | ||
|
|
| _, err := os.ReadFile(".env.encrypted") | ||
| if err != nil { | ||
| mockContext.EXPECT().Error("Encrypted environment file not found.").Once() | ||
| } | ||
|
|
||
| env, err := os.ReadFile(".env") | ||
| if err == nil { | ||
| mockContext.EXPECT().Confirm("Environment file already exists, are you sure to overwrite?", console.ConfirmOption{ | ||
| Default: true, | ||
| Affirmative: "Yes", | ||
| Negative: "No", | ||
| }).Return(true, nil).Once() | ||
| s.Require().Equal("APP_KEY=12345", string(env)) | ||
| } |
There was a problem hiding this comment.
In the current logic, these cases cannot be triggered. If you are not familiar with how to optimize it, I can create another PR for example after this one is merged.
📑 Description
https://laravel.com/docs/11.x/configuration#encryption
env:encryptcommand to encrypt.envand save as.env.encryptedenv:decryptcommand to decrypt.env.encryptedand save as.envThe length of the key provided should match the key length required by the encryption cipher being used. By default,
encryption and decryption use the AES-256-CBC cipher which requires a 32-character key. You can customize the key by passing
--keyor--koption when calling the command. such asThe content of
.env.encryptedis as followsYou can safely commit
.env.encryptedto the repository and set environment variables :You can also directly specify the key