Skip to content

feat: add environment file encryption and decryption commands#896

Merged
hwbrzzl merged 21 commits intogoravel:masterfrom
kuafuRace:master
Feb 20, 2025
Merged

feat: add environment file encryption and decryption commands#896
hwbrzzl merged 21 commits intogoravel:masterfrom
kuafuRace:master

Conversation

@kuafuRace
Copy link
Contributor

@kuafuRace kuafuRace commented Feb 18, 2025

📑 Description

https://laravel.com/docs/11.x/configuration#encryption

  • Add env:encrypt command to encrypt .env and save as .env.encrypted
  • Add env:decrypt command to decrypt .env.encrypted and save as .env

The 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 --key or --k option when calling the command. such as

go run . artisan env.encrypt 
or
go run . artisan env.encrypt --key BgcELROHL8sAV568T7Fiki7krjLHOkUc

image

image

The content of .env.encrypted is as follows

VjdseGhVYldYbXVWV2l6SpaimwCS0/0Ov9fP8PK/mvFcgLBJnln98rikPeYb/hKsU49V+rbiDn404zEOb1faxlZ2t7uPL2rng6OFMzQuMMhH2im0kF5JUOlhLmH0kLdoZx3bbrjqckb7oa7wkXFZLq9gybj6SyLxCpfK/bYEhVHxZFmuusc8/WCy7n5Z2OyHGO3auZWiW11vtjQqIVxw9+a4vYO82rYzv4xkm3hG8df4cokyNHyPNVf6EucrVa6rZMB/B8Dck2mB5MMlFcBcumxKljnVdwqzUSerBh2jsJVaaoxL7f/Ql4C2LZT33P9OX0Yaa7WF0WKQtzQ/FlUpyKV7spIlqvD0fA/zYc/LCWNjZ0c0zX+HXVnXgb8iGaSRpohu4jwUiqDh65M8LU3glECmPWy5ljNITkEAb3bdCKqxb6eQi8C+h6ow140F+M1S2vrE/IB1gaWwyevMUOMrJiDtAqvYjNYl807L0XgD3OTfePOPl629tl4eS7ymPOoOTIe4p7jZeAWXR3i+Jyp0SB0wmyQCE6xHi6Yg2oUcMVH78TYSkiFqwP7/BFbtqjo1JPw8Ne02eePqRD+BdMyrVIKGHzCHRnR9PuRB7YRBd8F91yT6Q4XmGsbUaZbWRewSBks+343SFhlm/kzRz01i9ERyVb6mamvHykmYbOTBiyy08IlGcOFAVU/8c4MXMmElVvz/atmAA7opcrWQX/7pe8n2x5FTTb1HMsDxYlooH0Zu+upgxeqtduZu7bAiLnZnZ8Bfql106p5U+vInleuO21DzPTAGX/ofhr8v+LAlC/ouk9SBa0QpKE6G0bkP04BBQ4kWukD5Qt2M2GixQP+tZqpsIr/f7oY/YJ8Wc3CriAb8ASR+6q3lB8vL1La9mJPE0zwd+PRqDv3KJ1Acgk0kTzwWG82lSxbvEHzCdBIymKkTFms8qgRdEkZQGKiVYiqzaZiKOagu9rLoKUG1rSMqz76yupsjMFyi8u1M/39FM6LMKGwhXg7NpOgYkXmSaYImyl/InraD2n2Eu7unHLW4g+wf6flpYjSouptzskwLwgY1DXNAfOGQ0ULZw+OrZhj9Xff+6RcGD7y696m9tlJrJ8n6/qNgziL9fDpkdFt23vOTA3nofHXPP9isT30M2E84KV69L/KYoxOTsGb1RWUTdwCBNxpgbPYawGXE8ruJRiEmZ2p4X9nl7XTzNSEKSIlxTPOaWX5ylIuZoIPUOjTySmVrEzWgYkTdHzi0+bLPAHA=

You can safely commit .env.encrypted to the repository and set environment variables :

# Linux
export  GORAVEL_ENV_ENCRYPTION_KEY="Your encryption key"

# Windows (PowerShell)
$env:GORAVEL_ENV_ENCRYPTION_KEY="Your encryption key"

go run . artisan env.decrypt 

You can also directly specify the key

go run . artisan env.decrypt --key BgcELROHL8sAV568T7Fiki7krjLHOkUc

image
image

- Add `env:encrypt` command to generate `.env.encrypted`
- Add `env:decrypt` command to generate `.env` from `.env.encrypted`
@kuafuRace kuafuRace requested a review from a team as a code owner February 18, 2025 13:54
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2025

Important

Review skipped

Auto reviews are limited to specific labels.

🏷️ Labels to auto review (1)
  • 🚀 Review Ready

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codecov
Copy link

codecov bot commented Feb 18, 2025

Codecov Report

Attention: Patch coverage is 69.28105% with 47 lines in your changes missing coverage. Please review.

Project coverage is 67.83%. Comparing base (0839829) to head (b4610dc).
Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
foundation/console/env_decrypt_command.go 62.96% 21 Missing and 9 partials ⚠️
foundation/console/env_encrypt_command.go 79.71% 10 Missing and 4 partials ⚠️
foundation/application.go 0.00% 2 Missing ⚠️
foundation/console/package_make_command.go 0.00% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

@kuafuRace kuafuRace changed the title feat: Add environment file encryption and decryption commands feat: add environment file encryption and decryption commands Feb 18, 2025
- 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`
Copy link
Contributor

@hwbrzzl hwbrzzl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return err
return nil

plaintext, err := os.ReadFile(".env")
if err != nil {
ctx.Error("Environment file not found.")
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return
return nil

Negative: "No",
})
if !ok {
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return
return nil

encryptedData, err := os.ReadFile(".env.encrypted")
if err != nil {
ctx.Error("Encrypted environment file not found.")
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return
return nil

Negative: "No",
})
if !ok {
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return
return nil

}
padding := int(data[length-1])
if padding < 1 || padding > aes.BlockSize {
return nil, errors.New("invalid padding")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

return nil, errors.New("invalid padding")
}
if length < padding {
return nil, errors.New("data shorter than padding")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

}
ciphertext, err := encrypt(plaintext, []byte(key))
if err != nil {
panic(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

base64Data := base64.StdEncoding.EncodeToString(ciphertext)
err = os.WriteFile(".env.encrypted", []byte(base64Data), 0644)
if err != nil {
panic(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto


func encrypt(plaintext []byte, key []byte) ([]byte, error) {
if len(key) == 0 {
return nil, errors.New("A encryption key is required. ")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

@kuafuRace
Copy link
Contributor Author

kuafuRace commented Feb 19, 2025

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?
Added in the PR description, others are being adjusted

Copy link
Contributor

@hwbrzzl hwbrzzl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, there are basic test cases now, we can optimize them deeply.

Comment on lines +4 to +12
"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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort it:

system packages 

external packages

internal packages
Suggested change
"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"

Comment on lines +78 to +84
_, err := os.ReadFile(".env.encrypted")
if err != nil {
// mockContext.EXPECT().Error("Encrypted environment file not found.").Once()
encryptCommandTestSuite := &EnvEncryptCommandTestSuite{}
TestEnvEncryptCommandTestSuite(s.T())
encryptCommandTestSuite.SetupTest()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can create the .env.encrypted file in the SetupSuite method, and remove the file in the TearDownSuite method.

Comment on lines +86 to +94
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))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the .env file should also be removed (in the TearDownSuite method).

return nil
}

func decrypt(ciphertext []byte, key []byte) ([]byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a single test case for this method.

}
}

func (s *EnvDecryptCommandTestSuite) TestHandle() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only covered the success flow, we need to cover more necessary error flows.

}
}

func (s *EnvEncryptCommandTestSuite) TestHandle() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

@hwbrzzl
Copy link
Contributor

hwbrzzl commented Feb 20, 2025

FYI, about the test case, different test cases should not affort each other.

Copy link
Contributor

@hwbrzzl hwbrzzl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great PR 👍 Just some nitpicks due to it being your first PR. I can create another PR to optimize them and let you know.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sort is correct, should not change this.

Comment on lines +53 to +56
if key == "" {
ctx.Error("A decryption key is required.")
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, should:

system packages

third packages

internal packages

suite.Run(t, new(EnvDecryptCommandTestSuite))
}

func (s *EnvDecryptCommandTestSuite) SetupTest() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Comment on lines +86 to +99
_, 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()
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Comment on lines +84 to +97
_, 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))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@hwbrzzl hwbrzzl merged commit 34c641e into goravel:master Feb 20, 2025
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants