valvet 0.1.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/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/Rakefile +17 -0
- data/exe/valvet +6 -0
- data/lib/valvet/cli.rb +98 -0
- data/lib/valvet/crypto.rb +40 -0
- data/lib/valvet/encrypted.rb +5 -0
- data/lib/valvet/error.rb +17 -0
- data/lib/valvet/public_key.rb +3 -0
- data/lib/valvet/rails.rb +24 -0
- data/lib/valvet/store.rb +86 -0
- data/lib/valvet/version.rb +5 -0
- data/lib/valvet/yaml.rb +30 -0
- data/lib/valvet.rb +36 -0
- data/log/test.log +0 -0
- data/sig/valvet.rbs +111 -0
- metadata +108 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 33f178cef255bdb28655e535b3a1b8901611fca9473c7f972df42302775eedd4
|
|
4
|
+
data.tar.gz: 0e03ef112f57112481ffbc7beadd99c9894851961a3fa992eac4a6273851cb11
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a84ac8a7b64f0674755c258e9af9317446b77fe16c99568ef2b2b200402efb8069f14f019d79f5de51be30ed0bd3df29206ef9e4ca739bdccd395af4357d5db7
|
|
7
|
+
data.tar.gz: d6a5a0ca17f859749437b5fd2e9ae6be14cf2668849d555668cc078720ac082048f867f0722be7ae12293a4eae1792615cfcec253ce8e23ebb19714a9cdc211f
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"valvet" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["kim@burgestrand.se"](mailto:"kim@burgestrand.se").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kim Burgestrand
|
|
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,69 @@
|
|
|
1
|
+
# Valvet
|
|
2
|
+
|
|
3
|
+
Keep secrets in your config files. Valvet encrypts sensitive values while leaving everything else readable, so you can check the whole file into version control, just like Rails credentials.
|
|
4
|
+
|
|
5
|
+
It uses asymmetric encryption ([NaCl sealed boxes](https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes) via [RbNaCl](https://github.com/RbNaCl/rbnacl)), which means anyone on the team can add new secrets using the public key — without ever needing the private key.
|
|
6
|
+
|
|
7
|
+
Give each environment its own keypair and they can all live in one file, each unable to decrypt the other's secrets.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
Generate a keypair (public key to stdout, private key to stderr):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
valvet keypair # prints both, labeled
|
|
17
|
+
valvet keypair > public.key 2> private.key # saves each to a file
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Encrypt and decrypt individual values:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
valvet encrypt "my secret" --key public.key
|
|
24
|
+
valvet decrypt "ciphertext..." --key private.key
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Config files
|
|
28
|
+
|
|
29
|
+
Put your config in a YAML file with `!public_key` and `!encrypted` tags:
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
development:
|
|
33
|
+
public_key: !public_key cWgtEYZ3bDXYgiHGbanGr+vl4HQrDgiAmOlBRc+hhgo=
|
|
34
|
+
secret_key_base: !encrypted 7oc+QbPyIa3Oe4ty09tNWRhgkAvlFjyWcggfeRAz8olE5VRF3lNvJEVGs8xgGquoXvItXPWOfus0ntQaUphFEsANyAE=
|
|
35
|
+
database:
|
|
36
|
+
password: !encrypted 3ad2zo3NKQU1EZm7XB2uFiqE6wAWjPPqjvPTD8rUB3m+ya2xrE5YvEuxDYkswgxqMjRfm5V1OHyz4cnez0GLUTif1+0=
|
|
37
|
+
|
|
38
|
+
production:
|
|
39
|
+
public_key: !public_key R7ljpsyGiOm2Yz3ElbQwCDDapAMEhCHl6WYWS/ep4vM=
|
|
40
|
+
secret_key_base: !encrypted HWZrJnY+AxBW5+W71Ar6DHkW2clqmtccrcCy5iek+9H4BfYTS1VR5/P3p+YhNzIgd7+MTTJxSTXmGeG9k0CQfoUpnn0=
|
|
41
|
+
database:
|
|
42
|
+
password: !encrypted 8Z2QiPc7gKWqTmjs6mlNiKWPJ997ftzgpiMO/NdEEjzAvXAAckYIqeyOxem+OeCsDHsCwM8j2x8XPH/7VM383dmw+jA=
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Rails
|
|
46
|
+
|
|
47
|
+
Place your config in `config/valvet.yml` and set `VALVET_PRIVATE_KEY` in your environment. The Railtie is loaded automatically and exposes `Rails.configuration.valvet`:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
Rails.configuration.valvet.secret_key_base
|
|
51
|
+
Rails.configuration.valvet.database.password
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle add valvet
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bin/setup
|
|
64
|
+
rake # runs tests, linting, and RBS validation
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
[MIT](LICENSE.txt)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "minitest/test_task"
|
|
5
|
+
|
|
6
|
+
Minitest::TestTask.create do |t|
|
|
7
|
+
t.test_prelude = 'require "test_helper"'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require "standard/rake"
|
|
11
|
+
|
|
12
|
+
desc "Validate RBS type signatures"
|
|
13
|
+
task "rbs:validate" do
|
|
14
|
+
sh "bundle exec rbs -I sig validate"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
task default: %i[test standard rbs:validate]
|
data/exe/valvet
ADDED
data/lib/valvet/cli.rb
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module Valvet
|
|
6
|
+
class CLI
|
|
7
|
+
def self.start(argv, ...)
|
|
8
|
+
new(...).run(argv.dup)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(out: $stdout, err: $stderr)
|
|
12
|
+
@out = out
|
|
13
|
+
@err = err
|
|
14
|
+
@commands = {
|
|
15
|
+
"keypair" => method(:keypair), "g" => method(:keypair),
|
|
16
|
+
"encrypt" => method(:encrypt), "e" => method(:encrypt),
|
|
17
|
+
"decrypt" => method(:decrypt), "d" => method(:decrypt),
|
|
18
|
+
"help" => method(:help)
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run(argv)
|
|
23
|
+
command = @commands.fetch(argv.shift.to_s) { method(:help) }
|
|
24
|
+
command.call(argv) || 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def keypair(_argv)
|
|
30
|
+
decryptor = Crypto.generate
|
|
31
|
+
|
|
32
|
+
if @out.tty?
|
|
33
|
+
@out.puts "public_key: #{decryptor.public_key}"
|
|
34
|
+
else
|
|
35
|
+
@out.puts decryptor.public_key
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if @err.tty?
|
|
39
|
+
@err.puts "private_key: #{decryptor}"
|
|
40
|
+
@err.puts "\e[33mKeep the private key secret. Store it in an environment variable, not in version control.\e[0m"
|
|
41
|
+
else
|
|
42
|
+
@err.puts decryptor
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def encrypt(argv)
|
|
47
|
+
key = nil
|
|
48
|
+
parser = OptionParser.new do |opts|
|
|
49
|
+
opts.banner = "Usage: valvet encrypt VALUE --key PUBLIC_KEY"
|
|
50
|
+
opts.on("-k", "--key KEY", "Public key (base64 or file path)") { |k| key = k }
|
|
51
|
+
end
|
|
52
|
+
parser.parse!(argv)
|
|
53
|
+
|
|
54
|
+
value = argv.shift
|
|
55
|
+
if value.nil? || key.nil?
|
|
56
|
+
@err.puts parser.to_s
|
|
57
|
+
return 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@out.puts Crypto::Encryptor.new(read_key(key)).encrypt(value)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def decrypt(argv)
|
|
64
|
+
key = nil
|
|
65
|
+
parser = OptionParser.new do |opts|
|
|
66
|
+
opts.banner = "Usage: valvet decrypt CIPHERTEXT --key PRIVATE_KEY"
|
|
67
|
+
opts.on("-k", "--key KEY", "Private key (base64 or file path)") { |k| key = k }
|
|
68
|
+
end
|
|
69
|
+
parser.parse!(argv)
|
|
70
|
+
|
|
71
|
+
ciphertext = argv.shift
|
|
72
|
+
if ciphertext.nil? || key.nil?
|
|
73
|
+
@err.puts parser.to_s
|
|
74
|
+
return 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@out.puts Crypto::Decryptor.new(read_key(key)).decrypt(ciphertext)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def read_key(key)
|
|
81
|
+
File.exist?(key) ? File.binread(key).strip : key
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def help(_argv)
|
|
85
|
+
@out.puts <<~HELP
|
|
86
|
+
Usage: valvet COMMAND [OPTIONS]
|
|
87
|
+
|
|
88
|
+
Commands:
|
|
89
|
+
keypair, g Generate a new keypair
|
|
90
|
+
encrypt, e Encrypt a value (requires --key)
|
|
91
|
+
decrypt, d Decrypt a value (requires --key)
|
|
92
|
+
help Show this help
|
|
93
|
+
|
|
94
|
+
Run `valvet COMMAND --help` for more information.
|
|
95
|
+
HELP
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Valvet
|
|
2
|
+
module Crypto
|
|
3
|
+
def self.encode(data) = Base64.strict_encode64(data)
|
|
4
|
+
def self.decode(data) = Base64.decode64(data)
|
|
5
|
+
|
|
6
|
+
def self.generate
|
|
7
|
+
key = encode(RbNaCl::PrivateKey.generate.to_s)
|
|
8
|
+
Decryptor.new(key)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Encryptor
|
|
12
|
+
def initialize(public_key)
|
|
13
|
+
@public_key = RbNaCl::PublicKey.new(Crypto.decode(public_key))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s = Crypto.encode(@public_key.to_s)
|
|
17
|
+
|
|
18
|
+
def encrypt(plaintext)
|
|
19
|
+
box = RbNaCl::Boxes::Sealed.from_public_key(@public_key)
|
|
20
|
+
Crypto.encode(box.encrypt(plaintext))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Decryptor
|
|
25
|
+
def initialize(private_key)
|
|
26
|
+
@private_key = RbNaCl::PrivateKey.new(Crypto.decode(private_key))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_s = Crypto.encode(@private_key.to_s)
|
|
30
|
+
def public_key = Crypto.encode(@private_key.public_key.to_s)
|
|
31
|
+
|
|
32
|
+
def encryptor = Encryptor.new(public_key)
|
|
33
|
+
|
|
34
|
+
def decrypt(ciphertext)
|
|
35
|
+
box = RbNaCl::Boxes::Sealed.from_private_key(@private_key)
|
|
36
|
+
box.decrypt(Crypto.decode(ciphertext))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/valvet/error.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Valvet
|
|
2
|
+
module Error
|
|
3
|
+
class Base < StandardError
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class NoDecryptorError < Base
|
|
7
|
+
attr_reader :public_key
|
|
8
|
+
attr_reader :ciphertext
|
|
9
|
+
|
|
10
|
+
def initialize(public_key:, ciphertext:)
|
|
11
|
+
@public_key = public_key
|
|
12
|
+
@ciphertext = ciphertext
|
|
13
|
+
super("No decryptor for public key #{public_key}")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/valvet/rails.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "valvet"
|
|
4
|
+
require "rails"
|
|
5
|
+
|
|
6
|
+
module Valvet
|
|
7
|
+
class Railtie < ::Rails::Railtie
|
|
8
|
+
config.valvet = ActiveSupport::OrderedOptions.new
|
|
9
|
+
config.valvet.private_keys = [ENV["VALVET_PRIVATE_KEY"]].compact
|
|
10
|
+
|
|
11
|
+
initializer "valvet.register_yaml_tags", before: :load_environment_config do
|
|
12
|
+
Valvet::YAML.register
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
initializer "valvet.configure" do |app|
|
|
16
|
+
valvet_path = app.root.join("config", "valvet.yml")
|
|
17
|
+
|
|
18
|
+
if valvet_path.exist?
|
|
19
|
+
hash = app.config_for(:valvet).to_h.deep_stringify_keys
|
|
20
|
+
app.config.valvet = Valvet.new(hash, private_keys: app.config.valvet.private_keys)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/valvet/store.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Valvet
|
|
2
|
+
class Store
|
|
3
|
+
Decrypt = Data.define(:public_key, :ciphertext)
|
|
4
|
+
Encrypt = Data.define(:public_key, :plaintext)
|
|
5
|
+
|
|
6
|
+
def initialize(hash, public_key: nil, &handler)
|
|
7
|
+
@hash = hash
|
|
8
|
+
@handler = handler
|
|
9
|
+
@public_key = find_public_key(hash)&.key || public_key
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def key?(key) = @hash.key?(key)
|
|
13
|
+
|
|
14
|
+
def each(path: [], &block)
|
|
15
|
+
@hash.each do |key, value|
|
|
16
|
+
case value
|
|
17
|
+
when Valvet::PublicKey
|
|
18
|
+
next
|
|
19
|
+
when Valvet::Encrypted
|
|
20
|
+
yield path + [key], value, self
|
|
21
|
+
when Hash
|
|
22
|
+
child_scope(value).each(path: path + [key], &block)
|
|
23
|
+
else
|
|
24
|
+
yield path + [key], value, self
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_h
|
|
30
|
+
@hash.each_with_object({}) do |(key, value), result|
|
|
31
|
+
next if value.is_a?(Valvet::PublicKey)
|
|
32
|
+
result[key] = resolve(key)
|
|
33
|
+
result[key] = result[key].to_h if result[key].is_a?(self.class)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def resolve(key)
|
|
38
|
+
value = @hash[key]
|
|
39
|
+
case value
|
|
40
|
+
when Valvet::Encrypted then decrypt(value)
|
|
41
|
+
when Hash then child_scope(value)
|
|
42
|
+
else value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def method_missing(name, ...)
|
|
47
|
+
key = name.to_s
|
|
48
|
+
if key.end_with?("=")
|
|
49
|
+
assign(key.chomp("="), ...)
|
|
50
|
+
elsif @hash.key?(key)
|
|
51
|
+
resolve(key)
|
|
52
|
+
else
|
|
53
|
+
super
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def respond_to_missing?(name, ...)
|
|
58
|
+
key = name.to_s
|
|
59
|
+
@hash.key?(key) || (key.end_with?("=") && @hash.key?(key.chomp("="))) || super
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def assign(key, value)
|
|
65
|
+
if @hash[key].is_a?(Valvet::Encrypted)
|
|
66
|
+
ciphertext = @handler.call(Encrypt.new(@public_key, value))
|
|
67
|
+
@hash[key] = Valvet::Encrypted.new(ciphertext:)
|
|
68
|
+
else
|
|
69
|
+
@hash[key] = value
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def decrypt(encrypted)
|
|
74
|
+
@handler.call(Decrypt.new(@public_key, encrypted.ciphertext))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def child_scope(hash)
|
|
78
|
+
self.class.new(hash, public_key: @public_key, &@handler)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def find_public_key(hash)
|
|
82
|
+
hash.each_value { |v| return v if v.is_a?(Valvet::PublicKey) }
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/valvet/yaml.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module Valvet
|
|
4
|
+
module YAML
|
|
5
|
+
def self.register
|
|
6
|
+
::YAML.add_tag("!encrypted", Valvet::Encrypted)
|
|
7
|
+
::YAML.add_tag("!public_key", Valvet::PublicKey)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Encrypted
|
|
12
|
+
def init_with(coder) = initialize(ciphertext: coder.scalar)
|
|
13
|
+
|
|
14
|
+
def encode_with(coder)
|
|
15
|
+
coder.scalar = ciphertext
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class PublicKey
|
|
20
|
+
def init_with(coder) = initialize(key: coder.scalar)
|
|
21
|
+
|
|
22
|
+
def encode_with(coder)
|
|
23
|
+
coder.scalar = key
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Store
|
|
28
|
+
def to_yaml(...) = ::YAML.dump(@hash, ...)
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/valvet.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "valvet/version"
|
|
4
|
+
|
|
5
|
+
require "rbnacl"
|
|
6
|
+
require "base64"
|
|
7
|
+
|
|
8
|
+
require "zeitwerk"
|
|
9
|
+
loader = Zeitwerk::Loader.for_gem
|
|
10
|
+
loader.inflector.inflect("yaml" => "YAML")
|
|
11
|
+
loader.ignore("#{__dir__}/valvet/version.rb")
|
|
12
|
+
loader.ignore("#{__dir__}/valvet/cli.rb")
|
|
13
|
+
loader.ignore("#{__dir__}/valvet/rails.rb")
|
|
14
|
+
loader.setup
|
|
15
|
+
|
|
16
|
+
module Valvet
|
|
17
|
+
def self.new(hash, private_keys: [])
|
|
18
|
+
decryptors = private_keys.to_h do |private_key|
|
|
19
|
+
decryptor = Crypto::Decryptor.new(private_key)
|
|
20
|
+
[decryptor.public_key, decryptor]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Store.new(hash) do |intent|
|
|
24
|
+
case intent
|
|
25
|
+
in Store::Decrypt(public_key:, ciphertext:)
|
|
26
|
+
decryptors.fetch(public_key) {
|
|
27
|
+
raise Error::NoDecryptorError.new(public_key:, ciphertext:)
|
|
28
|
+
}.decrypt(ciphertext)
|
|
29
|
+
in Store::Encrypt(public_key:, plaintext:)
|
|
30
|
+
Crypto::Encryptor.new(public_key).encrypt(plaintext)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
require "valvet/rails" if defined?(Rails::Railtie)
|
data/log/test.log
ADDED
|
File without changes
|
data/sig/valvet.rbs
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module Valvet
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
type base64_encoded = String
|
|
5
|
+
|
|
6
|
+
def self.new: (Hash[String, untyped] hash, ?private_keys: Array[base64_encoded]) -> Store
|
|
7
|
+
|
|
8
|
+
class Encrypted
|
|
9
|
+
attr_reader ciphertext: base64_encoded
|
|
10
|
+
|
|
11
|
+
def self.new: (ciphertext: base64_encoded) -> Encrypted
|
|
12
|
+
|
|
13
|
+
def empty?: () -> bool
|
|
14
|
+
|
|
15
|
+
def init_with: (untyped coder) -> void
|
|
16
|
+
|
|
17
|
+
def encode_with: (untyped coder) -> void
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class PublicKey
|
|
21
|
+
attr_reader key: base64_encoded
|
|
22
|
+
|
|
23
|
+
def self.new: (key: base64_encoded) -> PublicKey
|
|
24
|
+
|
|
25
|
+
def init_with: (untyped coder) -> void
|
|
26
|
+
|
|
27
|
+
def encode_with: (untyped coder) -> void
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module Error
|
|
31
|
+
class Base < StandardError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class NoDecryptorError < Base
|
|
35
|
+
attr_reader public_key: base64_encoded
|
|
36
|
+
attr_reader ciphertext: base64_encoded
|
|
37
|
+
|
|
38
|
+
def initialize: (public_key: base64_encoded, ciphertext: base64_encoded) -> void
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module Crypto
|
|
43
|
+
def self.encode: (String data) -> base64_encoded
|
|
44
|
+
|
|
45
|
+
def self.decode: (base64_encoded data) -> String
|
|
46
|
+
|
|
47
|
+
def self.generate: () -> Decryptor
|
|
48
|
+
|
|
49
|
+
class Encryptor
|
|
50
|
+
def initialize: (base64_encoded public_key) -> void
|
|
51
|
+
|
|
52
|
+
def to_s: () -> base64_encoded
|
|
53
|
+
|
|
54
|
+
def encrypt: (String plaintext) -> base64_encoded
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Decryptor
|
|
58
|
+
def initialize: (base64_encoded private_key) -> void
|
|
59
|
+
|
|
60
|
+
def to_s: () -> base64_encoded
|
|
61
|
+
|
|
62
|
+
def public_key: () -> base64_encoded
|
|
63
|
+
|
|
64
|
+
def encryptor: () -> Encryptor
|
|
65
|
+
|
|
66
|
+
def decrypt: (base64_encoded ciphertext) -> String
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Store
|
|
71
|
+
class Decrypt
|
|
72
|
+
attr_reader public_key: base64_encoded
|
|
73
|
+
attr_reader ciphertext: base64_encoded
|
|
74
|
+
|
|
75
|
+
def self.new: (base64_encoded public_key, base64_encoded ciphertext) -> Decrypt
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class Encrypt
|
|
79
|
+
attr_reader public_key: base64_encoded
|
|
80
|
+
attr_reader plaintext: String
|
|
81
|
+
|
|
82
|
+
def self.new: (base64_encoded public_key, String plaintext) -> Encrypt
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def initialize: (Hash[String, untyped] hash, ?public_key: base64_encoded?) { (Store::Decrypt | Store::Encrypt) -> String } -> void
|
|
86
|
+
|
|
87
|
+
def key?: (String key) -> bool
|
|
88
|
+
|
|
89
|
+
def each: (?path: Array[String]) { (Array[String], untyped, Store) -> void } -> void
|
|
90
|
+
|
|
91
|
+
def to_h: () -> Hash[String, untyped]
|
|
92
|
+
|
|
93
|
+
def resolve: (String key) -> (String | Store | untyped)
|
|
94
|
+
|
|
95
|
+
def to_yaml: (*untyped) -> String
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def assign: (String key, untyped value) -> void
|
|
100
|
+
|
|
101
|
+
def decrypt: (Encrypted encrypted) -> String
|
|
102
|
+
|
|
103
|
+
def child_scope: (Hash[String, untyped] hash) -> Store
|
|
104
|
+
|
|
105
|
+
def find_public_key: (Hash[String, untyped] hash) -> PublicKey?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
module YAML
|
|
109
|
+
def self.register: () -> void
|
|
110
|
+
end
|
|
111
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: valvet
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kim Burgestrand
|
|
8
|
+
- Varvet
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rbnacl
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: zeitwerk
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
description: Keep secrets in your config files. Valvet encrypts sensitive values while
|
|
56
|
+
leaving everything else readable, so you can check the whole file into version control.
|
|
57
|
+
Uses NaCl sealed boxes via RbNaCl.
|
|
58
|
+
email:
|
|
59
|
+
- kim@burgestrand.se
|
|
60
|
+
executables:
|
|
61
|
+
- valvet
|
|
62
|
+
extensions: []
|
|
63
|
+
extra_rdoc_files: []
|
|
64
|
+
files:
|
|
65
|
+
- CHANGELOG.md
|
|
66
|
+
- CODE_OF_CONDUCT.md
|
|
67
|
+
- LICENSE.txt
|
|
68
|
+
- README.md
|
|
69
|
+
- Rakefile
|
|
70
|
+
- exe/valvet
|
|
71
|
+
- lib/valvet.rb
|
|
72
|
+
- lib/valvet/cli.rb
|
|
73
|
+
- lib/valvet/crypto.rb
|
|
74
|
+
- lib/valvet/encrypted.rb
|
|
75
|
+
- lib/valvet/error.rb
|
|
76
|
+
- lib/valvet/public_key.rb
|
|
77
|
+
- lib/valvet/rails.rb
|
|
78
|
+
- lib/valvet/store.rb
|
|
79
|
+
- lib/valvet/version.rb
|
|
80
|
+
- lib/valvet/yaml.rb
|
|
81
|
+
- log/test.log
|
|
82
|
+
- sig/valvet.rbs
|
|
83
|
+
homepage: https://github.com/varvet/valvet
|
|
84
|
+
licenses:
|
|
85
|
+
- MIT
|
|
86
|
+
metadata:
|
|
87
|
+
allowed_push_host: https://rubygems.org
|
|
88
|
+
homepage_uri: https://github.com/varvet/valvet
|
|
89
|
+
source_code_uri: https://github.com/varvet/valvet
|
|
90
|
+
changelog_uri: https://github.com/varvet/valvet/blob/main/CHANGELOG.md
|
|
91
|
+
rdoc_options: []
|
|
92
|
+
require_paths:
|
|
93
|
+
- lib
|
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: 3.2.0
|
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
requirements: []
|
|
105
|
+
rubygems_version: 4.0.3
|
|
106
|
+
specification_version: 4
|
|
107
|
+
summary: Encrypt secrets in your config files using asymmetric encryption
|
|
108
|
+
test_files: []
|