secvault 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +23 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +209 -0
- data/Rakefile +10 -0
- data/lib/secvault/generators/secrets_generator.rb +94 -0
- data/lib/secvault/railtie.rb +21 -0
- data/lib/secvault/secrets.rb +100 -0
- data/lib/secvault/secrets_helper.rb +44 -0
- data/lib/secvault/tasks.rake +102 -0
- data/lib/secvault/version.rb +5 -0
- data/lib/secvault.rb +29 -0
- data/sig/secvault.rbs +4 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 67a57fa1ebe481bab8c9f5f4d9f6d0af27250e4d2c145641c125cd61f88018d0
|
|
4
|
+
data.tar.gz: 8613ed6d87bf067ad69bde7d80d8623a30b6dfb36280ddfe431b236198e63e2c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3013b492cec7b62296815ea239dc398de08806532ca995de383324a62fd9689e44ae62c744365dbcd29e1158b94b204f2b1522986c5e2d9b1e0d024cab3c871d
|
|
7
|
+
data.tar.gz: 2c74fa262a228253393a7b5818cb6d59554b67fb6ecbfac37d018c271fea9d6ca38d6c8b2cd84d253e16c42d4b28144c6059a1ee5e297d950db2954de6a5285b
|
data/.rspec
ADDED
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [1.0.0] - 2025-09-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial release of Secvault gem
|
|
8
|
+
- Rails secrets.yml functionality for Rails 7.2+
|
|
9
|
+
- Encrypted secrets.yml support using Rails' built-in encryption
|
|
10
|
+
- Environment-specific secrets management
|
|
11
|
+
- ERB template support in secrets files
|
|
12
|
+
- Rake tasks for secrets management:
|
|
13
|
+
- `rake secvault:setup` - Create encrypted secrets file
|
|
14
|
+
- `rake secvault:edit` - Edit encrypted secrets
|
|
15
|
+
- `rake secvault:show` - Display decrypted secrets
|
|
16
|
+
- Rails generator for creating secrets files
|
|
17
|
+
- Automatic integration with Rails.application.secrets
|
|
18
|
+
- Support for both encrypted and plain YAML secrets files
|
|
19
|
+
- Key management with config/secrets.yml.key
|
|
20
|
+
- Environment variable fallback for encryption key
|
|
21
|
+
- Comprehensive error handling for missing/invalid keys
|
|
22
|
+
- Full test coverage with RSpec
|
|
23
|
+
- Detailed documentation and usage examples
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
+
community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
+
any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
|
35
|
+
without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official email address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
[INSERT CONTACT METHOD].
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
|
86
|
+
actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
|
93
|
+
ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
113
|
+
community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.1, available at
|
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by
|
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
123
|
+
|
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
|
127
|
+
|
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Unnikrishnan KP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Secvault
|
|
2
|
+
|
|
3
|
+
**Secvault** restores the classic Rails `secrets.yml` functionality that was removed in Rails 7.2, allowing you to manage encrypted secrets using the familiar YAML-based approach.
|
|
4
|
+
|
|
5
|
+
## Why Secvault?
|
|
6
|
+
|
|
7
|
+
Rails 7.2 removed the `secrets.yml` functionality completely in favor of credentials. However, many teams prefer the simplicity and familiarity of `secrets.yml` for managing environment-specific secrets. Secvault brings this functionality back with modern encryption support.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🔐 **Encrypted secrets.yml** - Uses Rails' built-in encryption system
|
|
12
|
+
- 🔑 **Key management** - Secure key generation and management
|
|
13
|
+
- 🌍 **Environment-specific** - Different secrets for development, test, and production
|
|
14
|
+
- 📝 **ERB support** - Use ERB templates in your secrets files
|
|
15
|
+
- 🛠️ **Rake tasks** - Easy management with built-in rake tasks
|
|
16
|
+
- 🚀 **Rails 7.2+ compatible** - Works seamlessly with modern Rails
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Add this line to your application's Gemfile:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
gem 'secvault'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
And then execute:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
$ bundle install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Setup
|
|
33
|
+
|
|
34
|
+
### 1. Generate secrets file
|
|
35
|
+
|
|
36
|
+
Run the setup task to create your encrypted `secrets.yml`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
$ rake secvault:setup
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This will:
|
|
43
|
+
- Generate `config/secrets.yml.key` (keep this secure!)
|
|
44
|
+
- Create an encrypted `config/secrets.yml` with default content
|
|
45
|
+
- Remind you to add the key file to `.gitignore`
|
|
46
|
+
|
|
47
|
+
Alternatively, use the Rails generator:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
$ rails generate secvault:secrets
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Add key to .gitignore
|
|
54
|
+
|
|
55
|
+
Ensure your encryption key is not committed to version control:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
echo "/config/secrets.yml.key" >> .gitignore
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
### Editing secrets
|
|
64
|
+
|
|
65
|
+
Edit your encrypted secrets file:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
$ rake secvault:edit
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This opens the decrypted file in your `$EDITOR`.
|
|
72
|
+
|
|
73
|
+
### Viewing secrets
|
|
74
|
+
|
|
75
|
+
View the decrypted content:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
$ rake secvault:show
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Accessing secrets in your application
|
|
82
|
+
|
|
83
|
+
Secrets are automatically loaded into `Rails.application.secrets`:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
# In your Rails application
|
|
87
|
+
Rails.application.secrets.secret_key_base
|
|
88
|
+
Rails.application.secrets.api_key
|
|
89
|
+
Rails.application.secrets.database_password
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Example secrets.yml structure
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
# config/secrets.yml (encrypted)
|
|
96
|
+
development:
|
|
97
|
+
secret_key_base: your_development_secret
|
|
98
|
+
api_key: dev_api_key
|
|
99
|
+
database_password: dev_password
|
|
100
|
+
|
|
101
|
+
test:
|
|
102
|
+
secret_key_base: your_test_secret
|
|
103
|
+
api_key: test_api_key
|
|
104
|
+
database_password: test_password
|
|
105
|
+
|
|
106
|
+
production:
|
|
107
|
+
secret_key_base: <%= ENV['SECRET_KEY_BASE'] %>
|
|
108
|
+
api_key: <%= ENV['API_KEY'] %>
|
|
109
|
+
database_password: <%= ENV['DATABASE_PASSWORD'] %>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Environment variable fallback
|
|
113
|
+
|
|
114
|
+
You can set the encryption key via environment variable:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
export RAILS_SECRETS_KEY=your_encryption_key
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Production Deployment
|
|
121
|
+
|
|
122
|
+
### Option 1: Environment Variable
|
|
123
|
+
|
|
124
|
+
Set the encryption key as an environment variable:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
export RAILS_SECRETS_KEY=your_encryption_key
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Option 2: Key File
|
|
131
|
+
|
|
132
|
+
Securely copy `config/secrets.yml.key` to your production server.
|
|
133
|
+
|
|
134
|
+
### Docker
|
|
135
|
+
|
|
136
|
+
For Docker deployments, you can pass the key as an environment variable:
|
|
137
|
+
|
|
138
|
+
```dockerfile
|
|
139
|
+
ENV RAILS_SECRETS_KEY=your_encryption_key
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Rake Tasks
|
|
143
|
+
|
|
144
|
+
| Task | Description |
|
|
145
|
+
|------|-------------|
|
|
146
|
+
| `rake secvault:setup` | Create encrypted secrets.yml and key |
|
|
147
|
+
| `rake secvault:edit` | Edit the encrypted secrets file |
|
|
148
|
+
| `rake secvault:show` | Display decrypted secrets content |
|
|
149
|
+
|
|
150
|
+
## Migration from Rails < 7.2
|
|
151
|
+
|
|
152
|
+
If you're upgrading from an older Rails version that had `secrets.yml`:
|
|
153
|
+
|
|
154
|
+
1. Install secvault: `bundle add secvault`
|
|
155
|
+
2. Encrypt existing secrets: `rake secvault:setup`
|
|
156
|
+
3. Copy your existing secrets content using `rake secvault:edit`
|
|
157
|
+
4. Remove the old plain-text `config/secrets.yml`
|
|
158
|
+
|
|
159
|
+
## Security Best Practices
|
|
160
|
+
|
|
161
|
+
- ✅ **Never commit** `config/secrets.yml.key` to version control
|
|
162
|
+
- ✅ **Use environment variables** for production secrets when possible
|
|
163
|
+
- ✅ **Rotate keys** periodically
|
|
164
|
+
- ✅ **Use strong, unique keys** for each environment
|
|
165
|
+
- ✅ **Limit access** to key files in production
|
|
166
|
+
|
|
167
|
+
## Troubleshooting
|
|
168
|
+
|
|
169
|
+
### Missing Key Error
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
Missing encryption key to decrypt secrets.yml
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Solution**: Ensure `config/secrets.yml.key` exists or set `RAILS_SECRETS_KEY` environment variable.
|
|
176
|
+
|
|
177
|
+
### Invalid Key Error
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
Invalid encryption key for secrets.yml
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Solution**: The key doesn't match the encrypted file. Verify you're using the correct key.
|
|
184
|
+
|
|
185
|
+
### File Not Found
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
Secrets file doesn't exist
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Solution**: Run `rake secvault:setup` to create the secrets file.
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
|
196
|
+
|
|
197
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
198
|
+
|
|
199
|
+
## Contributing
|
|
200
|
+
|
|
201
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/unnitallman/secvault.
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
206
|
+
|
|
207
|
+
## Code of Conduct
|
|
208
|
+
|
|
209
|
+
Everyone interacting in the Secvault project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/secvault/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
require "active_support/encrypted_file"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
|
|
7
|
+
module Secvault
|
|
8
|
+
module Generators
|
|
9
|
+
class SecretsGenerator < Rails::Generators::Base
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
|
|
12
|
+
desc "Creates a secrets.yml file for encrypted secrets management"
|
|
13
|
+
|
|
14
|
+
class_option :force, type: :boolean, default: false, desc: "Overwrite existing secrets.yml"
|
|
15
|
+
|
|
16
|
+
def create_secrets_file
|
|
17
|
+
secrets_path = Rails.root.join("config/secrets.yml")
|
|
18
|
+
key_path = Rails.root.join("config/secrets.yml.key")
|
|
19
|
+
|
|
20
|
+
if secrets_path.exist? && !options[:force]
|
|
21
|
+
say "Secrets file already exists at config/secrets.yml", :yellow
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Generate encryption key
|
|
26
|
+
unless key_path.exist?
|
|
27
|
+
key = ActiveSupport::EncryptedFile.generate_key
|
|
28
|
+
File.write(key_path, key)
|
|
29
|
+
say "Generated encryption key in config/secrets.yml.key", :green
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Create encrypted secrets file with template
|
|
33
|
+
encrypted_file = ActiveSupport::EncryptedFile.new(
|
|
34
|
+
content_path: secrets_path,
|
|
35
|
+
key_path: key_path,
|
|
36
|
+
env_key: "RAILS_SECRETS_KEY",
|
|
37
|
+
raise_if_missing_key: true
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Write default content
|
|
41
|
+
default_content = generate_default_secrets
|
|
42
|
+
encrypted_file.write(default_content)
|
|
43
|
+
|
|
44
|
+
say "Created encrypted secrets.yml file", :green
|
|
45
|
+
say "Add config/secrets.yml.key to your .gitignore file", :yellow
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def add_to_gitignore
|
|
49
|
+
gitignore_path = Rails.root.join(".gitignore")
|
|
50
|
+
key_entry = "/config/secrets.yml.key"
|
|
51
|
+
|
|
52
|
+
if gitignore_path.exist?
|
|
53
|
+
gitignore_content = File.read(gitignore_path)
|
|
54
|
+
unless gitignore_content.include?(key_entry)
|
|
55
|
+
File.open(gitignore_path, "a") do |f|
|
|
56
|
+
f.puts "\n# Ignore encrypted secrets key"
|
|
57
|
+
f.puts key_entry
|
|
58
|
+
end
|
|
59
|
+
say "Added secrets key to .gitignore", :green
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def generate_default_secrets
|
|
67
|
+
<<~YAML
|
|
68
|
+
# Be sure to restart your server when you modify this file.
|
|
69
|
+
|
|
70
|
+
# Your secret key is used for verifying the integrity of signed cookies.
|
|
71
|
+
# If you change this key, all old signed cookies will become invalid!
|
|
72
|
+
|
|
73
|
+
# Make sure the secret is at least 30 characters and all random,
|
|
74
|
+
# no regular words or you'll be exposed to dictionary attacks.
|
|
75
|
+
# You can use `rails secret` to generate a secure secret key.
|
|
76
|
+
|
|
77
|
+
# Make sure the secrets in this file are kept private
|
|
78
|
+
# if you're sharing your code publicly.
|
|
79
|
+
|
|
80
|
+
development:
|
|
81
|
+
secret_key_base: #{SecureRandom.hex(64)}
|
|
82
|
+
|
|
83
|
+
test:
|
|
84
|
+
secret_key_base: #{SecureRandom.hex(64)}
|
|
85
|
+
|
|
86
|
+
# Do not keep production secrets in the repository,
|
|
87
|
+
# instead read values from the environment.
|
|
88
|
+
production:
|
|
89
|
+
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
|
90
|
+
YAML
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module Secvault
|
|
6
|
+
class Railtie < Rails::Railtie
|
|
7
|
+
railtie_name :secvault
|
|
8
|
+
|
|
9
|
+
initializer "secvault.initialize" do |app|
|
|
10
|
+
Secvault::Secrets.setup(app)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
generators do
|
|
14
|
+
require "secvault/generators/secrets_generator"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
rake_tasks do
|
|
18
|
+
load "secvault/tasks.rake"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/encrypted_file"
|
|
4
|
+
require "active_support/core_ext/hash/keys"
|
|
5
|
+
require "active_support/core_ext/object/blank"
|
|
6
|
+
require "pathname"
|
|
7
|
+
require "erb"
|
|
8
|
+
require "yaml"
|
|
9
|
+
|
|
10
|
+
module Secvault
|
|
11
|
+
class Secrets
|
|
12
|
+
class << self
|
|
13
|
+
def setup(app)
|
|
14
|
+
secrets_path = app.root.join("config/secrets.yml")
|
|
15
|
+
key_path = app.root.join("config/secrets.yml.key")
|
|
16
|
+
|
|
17
|
+
if secrets_path.exist?
|
|
18
|
+
app.config.before_configuration do
|
|
19
|
+
# Set up secrets if they exist
|
|
20
|
+
secrets = read_secrets(secrets_path, key_path, Rails.env)
|
|
21
|
+
Rails.application.secrets.merge!(secrets) if secrets
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse(paths, env:)
|
|
27
|
+
configs = paths.collect do |path|
|
|
28
|
+
if path.exist?
|
|
29
|
+
content = encrypted?(path) ? decrypt(path) : path.read
|
|
30
|
+
YAML.safe_load(ERB.new(content).result, aliases: true) || {}
|
|
31
|
+
else
|
|
32
|
+
{}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
configs.reverse.reduce do |config, overrides|
|
|
37
|
+
config.deep_merge(overrides)
|
|
38
|
+
end[env] || {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def read_secrets(secrets_path, key_path, env)
|
|
42
|
+
if secrets_path.exist?
|
|
43
|
+
all_secrets = if key_path.exist? || encrypted?(secrets_path)
|
|
44
|
+
# Handle encrypted secrets.yml
|
|
45
|
+
decrypt_secrets(secrets_path, key_path)
|
|
46
|
+
else
|
|
47
|
+
# Handle plain YAML secrets.yml
|
|
48
|
+
YAML.safe_load(ERB.new(secrets_path.read).result, aliases: true)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
env_secrets = all_secrets[env.to_s]
|
|
52
|
+
return env_secrets.deep_symbolize_keys if env_secrets
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
{}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def encrypted?(path)
|
|
59
|
+
# Simple heuristic to detect if file is encrypted
|
|
60
|
+
content = path.read
|
|
61
|
+
# Encrypted files typically contain non-printable characters
|
|
62
|
+
!content.valid_encoding? || content.bytes.any? { |b| b < 32 && b != 10 && b != 13 }
|
|
63
|
+
rescue
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def decrypt_secrets(secrets_path, key_path)
|
|
70
|
+
encrypted_file = ActiveSupport::EncryptedFile.new(
|
|
71
|
+
content_path: secrets_path,
|
|
72
|
+
key_path: key_path,
|
|
73
|
+
env_key: "RAILS_SECRETS_KEY",
|
|
74
|
+
raise_if_missing_key: true
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
content = encrypted_file.read
|
|
78
|
+
YAML.safe_load(ERB.new(content).result, aliases: true) if content.present?
|
|
79
|
+
rescue ActiveSupport::EncryptedFile::MissingKeyError
|
|
80
|
+
raise MissingKeyError,
|
|
81
|
+
"Missing encryption key to decrypt secrets.yml. " \
|
|
82
|
+
"Ask your team for your secrets key and put it in config/secrets.yml.key"
|
|
83
|
+
rescue ActiveSupport::EncryptedFile::InvalidMessage
|
|
84
|
+
raise InvalidKeyError,
|
|
85
|
+
"Invalid encryption key for secrets.yml."
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def decrypt(path)
|
|
89
|
+
key_path = Pathname.new("#{path}.key")
|
|
90
|
+
encrypted_file = ActiveSupport::EncryptedFile.new(
|
|
91
|
+
content_path: path,
|
|
92
|
+
key_path: key_path,
|
|
93
|
+
env_key: "RAILS_SECRETS_KEY",
|
|
94
|
+
raise_if_missing_key: true
|
|
95
|
+
)
|
|
96
|
+
encrypted_file.read
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Secvault
|
|
4
|
+
class SecretsHelper
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
@secrets_path = app.root.join("config/secrets.yml")
|
|
8
|
+
@key_path = app.root.join("config/secrets.yml.key")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def secrets
|
|
12
|
+
@secrets ||= load_secrets
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def [](key)
|
|
16
|
+
secrets[key.to_sym]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fetch(key, default = nil)
|
|
20
|
+
secrets.fetch(key.to_sym, default)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def key?(key)
|
|
24
|
+
secrets.key?(key.to_sym)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def empty?
|
|
28
|
+
secrets.empty?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_h
|
|
32
|
+
secrets.dup
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def load_secrets
|
|
38
|
+
return {} unless @secrets_path.exist?
|
|
39
|
+
|
|
40
|
+
env_secrets = Secvault::Secrets.read_secrets(@secrets_path, @key_path, Rails.env)
|
|
41
|
+
env_secrets || {}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/encrypted_file"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
namespace :secvault do
|
|
7
|
+
desc "Setup encrypted secrets.yml file"
|
|
8
|
+
task setup: :environment do
|
|
9
|
+
secrets_path = Rails.root.join("config/secrets.yml")
|
|
10
|
+
key_path = Rails.root.join("config/secrets.yml.key")
|
|
11
|
+
|
|
12
|
+
if secrets_path.exist?
|
|
13
|
+
puts "Secrets file already exists at #{secrets_path}"
|
|
14
|
+
else
|
|
15
|
+
# Generate key if it doesn't exist
|
|
16
|
+
unless key_path.exist?
|
|
17
|
+
key = ActiveSupport::EncryptedFile.generate_key
|
|
18
|
+
File.write(key_path, key)
|
|
19
|
+
puts "Generated encryption key in #{key_path}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Create encrypted file with default content
|
|
23
|
+
encrypted_file = ActiveSupport::EncryptedFile.new(
|
|
24
|
+
content_path: secrets_path,
|
|
25
|
+
key_path: key_path,
|
|
26
|
+
env_key: "RAILS_SECRETS_KEY",
|
|
27
|
+
raise_if_missing_key: true
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
default_content = <<~YAML
|
|
31
|
+
# Be sure to restart your server when you modify this file.
|
|
32
|
+
|
|
33
|
+
development:
|
|
34
|
+
secret_key_base: #{SecureRandom.hex(64)}
|
|
35
|
+
|
|
36
|
+
test:
|
|
37
|
+
secret_key_base: #{SecureRandom.hex(64)}
|
|
38
|
+
|
|
39
|
+
production:
|
|
40
|
+
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
|
41
|
+
YAML
|
|
42
|
+
|
|
43
|
+
encrypted_file.write(default_content)
|
|
44
|
+
puts "Created encrypted secrets.yml file"
|
|
45
|
+
puts "Add #{key_path} to your .gitignore file"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc "Edit encrypted secrets.yml file"
|
|
50
|
+
task edit: :environment do
|
|
51
|
+
secrets_path = Rails.root.join("config/secrets.yml")
|
|
52
|
+
key_path = Rails.root.join("config/secrets.yml.key")
|
|
53
|
+
|
|
54
|
+
unless secrets_path.exist?
|
|
55
|
+
puts "Secrets file doesn't exist. Run 'rake secvault:setup' first."
|
|
56
|
+
exit 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
encrypted_file = ActiveSupport::EncryptedFile.new(
|
|
60
|
+
content_path: secrets_path,
|
|
61
|
+
key_path: key_path,
|
|
62
|
+
env_key: "RAILS_SECRETS_KEY",
|
|
63
|
+
raise_if_missing_key: true
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
encrypted_file.change do |tmp_path|
|
|
67
|
+
system("#{ENV["EDITOR"] || "vi"} #{tmp_path}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
puts "Updated #{secrets_path}"
|
|
71
|
+
rescue ActiveSupport::EncryptedFile::MissingKeyError
|
|
72
|
+
puts "Missing encryption key to decrypt secrets.yml."
|
|
73
|
+
puts "Ask your team for your secrets key and put it in #{key_path}"
|
|
74
|
+
rescue ActiveSupport::EncryptedFile::InvalidMessage
|
|
75
|
+
puts "Invalid encryption key for secrets.yml."
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
desc "Show decrypted secrets.yml content"
|
|
79
|
+
task show: :environment do
|
|
80
|
+
secrets_path = Rails.root.join("config/secrets.yml")
|
|
81
|
+
key_path = Rails.root.join("config/secrets.yml.key")
|
|
82
|
+
|
|
83
|
+
unless secrets_path.exist?
|
|
84
|
+
puts "Secrets file doesn't exist. Run 'rake secvault:setup' first."
|
|
85
|
+
exit 1
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
encrypted_file = ActiveSupport::EncryptedFile.new(
|
|
89
|
+
content_path: secrets_path,
|
|
90
|
+
key_path: key_path,
|
|
91
|
+
env_key: "RAILS_SECRETS_KEY",
|
|
92
|
+
raise_if_missing_key: true
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
puts encrypted_file.read
|
|
96
|
+
rescue ActiveSupport::EncryptedFile::MissingKeyError
|
|
97
|
+
puts "Missing encryption key to decrypt secrets.yml."
|
|
98
|
+
puts "Ask your team for your secrets key and put it in #{key_path}"
|
|
99
|
+
rescue ActiveSupport::EncryptedFile::InvalidMessage
|
|
100
|
+
puts "Invalid encryption key for secrets.yml."
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/secvault.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "erb"
|
|
6
|
+
require "active_support/encrypted_file"
|
|
7
|
+
require "active_support/core_ext/hash/keys"
|
|
8
|
+
require "zeitwerk"
|
|
9
|
+
|
|
10
|
+
require_relative "secvault/version"
|
|
11
|
+
|
|
12
|
+
loader = Zeitwerk::Loader.for_gem
|
|
13
|
+
loader.setup
|
|
14
|
+
|
|
15
|
+
module Secvault
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
class MissingKeyError < Error; end
|
|
18
|
+
class InvalidKeyError < Error; end
|
|
19
|
+
|
|
20
|
+
extend self
|
|
21
|
+
|
|
22
|
+
def install!
|
|
23
|
+
return if Rails.env.test? || defined?(Rails::Railtie).nil?
|
|
24
|
+
|
|
25
|
+
require "secvault/railtie"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Secvault.install! if defined?(Rails)
|
data/sig/secvault.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: secvault
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Unnikrishnan KP
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-09-22 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 7.2.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 7.2.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: zeitwerk
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.6'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.6'
|
|
41
|
+
description: Secvault restores the classic Rails secrets.yml functionality that was
|
|
42
|
+
removed in Rails 7.2, allowing you to manage encrypted secrets using the familiar
|
|
43
|
+
YAML-based approach.
|
|
44
|
+
email:
|
|
45
|
+
- unnikrishnan.kp@bigbinary.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- ".rspec"
|
|
51
|
+
- ".standard.yml"
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- CODE_OF_CONDUCT.md
|
|
54
|
+
- LICENSE.txt
|
|
55
|
+
- README.md
|
|
56
|
+
- Rakefile
|
|
57
|
+
- lib/secvault.rb
|
|
58
|
+
- lib/secvault/generators/secrets_generator.rb
|
|
59
|
+
- lib/secvault/railtie.rb
|
|
60
|
+
- lib/secvault/secrets.rb
|
|
61
|
+
- lib/secvault/secrets_helper.rb
|
|
62
|
+
- lib/secvault/tasks.rake
|
|
63
|
+
- lib/secvault/version.rb
|
|
64
|
+
- sig/secvault.rbs
|
|
65
|
+
homepage: https://github.com/unnitallman/secvault
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata:
|
|
69
|
+
allowed_push_host: https://rubygems.org
|
|
70
|
+
homepage_uri: https://github.com/unnitallman/secvault
|
|
71
|
+
source_code_uri: https://github.com/unnitallman/secvault
|
|
72
|
+
changelog_uri: https://github.com/unnitallman/secvault/blob/main/CHANGELOG.md
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.0.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 3.5.10
|
|
89
|
+
signing_key:
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: Rails secrets.yml functionality for Rails 7.2+
|
|
92
|
+
test_files: []
|