localvault 1.6.2 → 1.8.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 +4 -4
- data/README.md +39 -10
- data/bin/localvault +2 -1
- data/lib/localvault/cli/error_presenter.rb +177 -0
- data/lib/localvault/cli.rb +266 -36
- data/lib/localvault/env_projection.rb +138 -0
- data/lib/localvault/group_catalog.rb +67 -0
- data/lib/localvault/input_validation.rb +80 -0
- data/lib/localvault/key_lookup.rb +30 -0
- data/lib/localvault/mcp/exec_command_builder.rb +77 -0
- data/lib/localvault/mcp/server.rb +12 -69
- data/lib/localvault/mcp/tools.rb +172 -16
- data/lib/localvault/session_cache.rb +29 -3
- data/lib/localvault/vault.rb +20 -72
- data/lib/localvault/vault_resolver.rb +92 -0
- data/lib/localvault/version.rb +1 -1
- data/lib/localvault.rb +3 -0
- metadata +8 -1
data/lib/localvault/vault.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "shellwords"
|
|
3
|
+
require_relative "env_projection"
|
|
3
4
|
|
|
4
5
|
module LocalVault
|
|
5
6
|
# Encrypted key-value store backed by a single JSON blob.
|
|
@@ -143,44 +144,15 @@ module LocalVault
|
|
|
143
144
|
# @example
|
|
144
145
|
# vault.export_env(project: "myapp")
|
|
145
146
|
# # => "export DB_URL=postgres%3A//..."
|
|
146
|
-
def export_env(project: nil, on_skip: nil)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
on_skip&.call(k)
|
|
156
|
-
nil
|
|
157
|
-
end
|
|
158
|
-
end.join("\n")
|
|
159
|
-
else
|
|
160
|
-
secrets.flat_map do |k, v|
|
|
161
|
-
if v.is_a?(Hash)
|
|
162
|
-
unless shell_safe_key?(k)
|
|
163
|
-
on_skip&.call(k)
|
|
164
|
-
next []
|
|
165
|
-
end
|
|
166
|
-
v.filter_map do |sk, sv|
|
|
167
|
-
if shell_safe_key?(sk)
|
|
168
|
-
"export #{k.upcase}__#{sk}=#{Shellwords.escape(sv.to_s)}"
|
|
169
|
-
else
|
|
170
|
-
on_skip&.call("#{k}.#{sk}")
|
|
171
|
-
nil
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
else
|
|
175
|
-
if shell_safe_key?(k)
|
|
176
|
-
["export #{k}=#{Shellwords.escape(v.to_s)}"]
|
|
177
|
-
else
|
|
178
|
-
on_skip&.call(k)
|
|
179
|
-
[]
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
end.join("\n")
|
|
183
|
-
end
|
|
147
|
+
def export_env(project: nil, on_skip: nil, only: nil, except: nil, map: nil, profile: nil)
|
|
148
|
+
EnvProjection.entries(all,
|
|
149
|
+
project: project,
|
|
150
|
+
only: only,
|
|
151
|
+
except: except,
|
|
152
|
+
map: map,
|
|
153
|
+
profile: profile,
|
|
154
|
+
on_skip: on_skip
|
|
155
|
+
).map { |entry| "export #{entry.env_name}=#{Shellwords.escape(entry.value)}" }.join("\n")
|
|
184
156
|
end
|
|
185
157
|
|
|
186
158
|
# Returns a flat hash suitable for env injection.
|
|
@@ -195,40 +167,16 @@ module LocalVault
|
|
|
195
167
|
# @example
|
|
196
168
|
# vault.env_hash(project: "myapp")
|
|
197
169
|
# # => {"DB_URL" => "postgres://...", "SECRET" => "abc"}
|
|
198
|
-
def env_hash(project: nil, on_skip: nil)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
else
|
|
211
|
-
secrets.each_with_object({}) do |(k, v), h|
|
|
212
|
-
if v.is_a?(Hash)
|
|
213
|
-
unless shell_safe_key?(k)
|
|
214
|
-
on_skip&.call(k)
|
|
215
|
-
next
|
|
216
|
-
end
|
|
217
|
-
v.each do |sk, sv|
|
|
218
|
-
if shell_safe_key?(sk)
|
|
219
|
-
h["#{k.upcase}__#{sk}"] = sv.to_s
|
|
220
|
-
else
|
|
221
|
-
on_skip&.call("#{k}.#{sk}")
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
else
|
|
225
|
-
if shell_safe_key?(k)
|
|
226
|
-
h[k] = v.to_s
|
|
227
|
-
else
|
|
228
|
-
on_skip&.call(k)
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
end
|
|
170
|
+
def env_hash(project: nil, on_skip: nil, only: nil, except: nil, map: nil, profile: nil)
|
|
171
|
+
EnvProjection.entries(all,
|
|
172
|
+
project: project,
|
|
173
|
+
only: only,
|
|
174
|
+
except: except,
|
|
175
|
+
map: map,
|
|
176
|
+
profile: profile,
|
|
177
|
+
on_skip: on_skip
|
|
178
|
+
).each_with_object({}) do |entry, hash|
|
|
179
|
+
hash[entry.env_name] = entry.value
|
|
232
180
|
end
|
|
233
181
|
end
|
|
234
182
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require "base64"
|
|
2
|
+
|
|
3
|
+
module LocalVault
|
|
4
|
+
module VaultResolver
|
|
5
|
+
Result = Struct.new(:vault, :active_vault, :active_vault_source, :session_vault, keyword_init: true)
|
|
6
|
+
|
|
7
|
+
def self.resolve(name = nil)
|
|
8
|
+
active_vault, source = active_vault_name(name)
|
|
9
|
+
session_name, session_key = session_credentials
|
|
10
|
+
|
|
11
|
+
if session_name == active_vault && session_key
|
|
12
|
+
vault = build_vault(active_vault, session_key)
|
|
13
|
+
return Result.new(vault: vault, active_vault: active_vault, active_vault_source: source, session_vault: session_name) if vault
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if (master_key = SessionCache.get(active_vault))
|
|
17
|
+
vault = build_vault(active_vault, master_key)
|
|
18
|
+
return Result.new(vault: vault, active_vault: active_vault, active_vault_source: source, session_vault: session_name) if vault
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Result.new(vault: nil, active_vault: active_vault, active_vault_source: source, session_vault: session_name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.status(name = nil)
|
|
25
|
+
result = resolve(name)
|
|
26
|
+
{
|
|
27
|
+
"localvault_home" => Config.root_path,
|
|
28
|
+
"active_vault" => result.active_vault,
|
|
29
|
+
"active_vault_source" => result.active_vault_source,
|
|
30
|
+
"session_vault" => result.session_vault,
|
|
31
|
+
"unlocked_vaults" => unlocked_vault_names,
|
|
32
|
+
"active_vault_unlocked" => !result.vault.nil?
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.readiness_status(name = nil)
|
|
37
|
+
result = resolve(name)
|
|
38
|
+
{
|
|
39
|
+
"localvault_home" => Config.root_path,
|
|
40
|
+
"active_vault" => result.active_vault,
|
|
41
|
+
"active_vault_source" => result.active_vault_source,
|
|
42
|
+
"session_vault" => result.session_vault,
|
|
43
|
+
"active_vault_unlocked" => !result.vault.nil?
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.active_vault_name(name = nil)
|
|
48
|
+
return [name, "argument"] if name && !name.empty?
|
|
49
|
+
return [ENV["LOCALVAULT_VAULT"], "env"] if ENV["LOCALVAULT_VAULT"] && !ENV["LOCALVAULT_VAULT"].empty?
|
|
50
|
+
|
|
51
|
+
[Config.default_vault, "config"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.session_vault_name
|
|
55
|
+
session_credentials.first
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.unlocked_vault_names
|
|
59
|
+
names = []
|
|
60
|
+
session_name, session_key = session_credentials
|
|
61
|
+
names << session_name if session_name && session_key && build_vault(session_name, session_key)
|
|
62
|
+
|
|
63
|
+
Store.list_vaults.each do |vault_name|
|
|
64
|
+
next if names.include?(vault_name)
|
|
65
|
+
names << vault_name if SessionCache.get(vault_name)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
names.sort
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.session_credentials
|
|
72
|
+
token = ENV["LOCALVAULT_SESSION"]
|
|
73
|
+
return [nil, nil] unless token
|
|
74
|
+
|
|
75
|
+
decoded = Base64.strict_decode64(token)
|
|
76
|
+
vault_name, key_b64 = decoded.split(":", 2)
|
|
77
|
+
return [nil, nil] unless vault_name && key_b64
|
|
78
|
+
|
|
79
|
+
[vault_name, Base64.strict_decode64(key_b64)]
|
|
80
|
+
rescue ArgumentError
|
|
81
|
+
[nil, nil]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.build_vault(name, master_key)
|
|
85
|
+
vault = Vault.new(name: name, master_key: master_key)
|
|
86
|
+
vault.all
|
|
87
|
+
vault
|
|
88
|
+
rescue Crypto::DecryptionError, Store::InvalidVaultName
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/localvault/version.rb
CHANGED
data/lib/localvault.rb
CHANGED
|
@@ -2,6 +2,9 @@ require_relative "localvault/version"
|
|
|
2
2
|
require_relative "localvault/crypto"
|
|
3
3
|
require_relative "localvault/config"
|
|
4
4
|
require_relative "localvault/store"
|
|
5
|
+
require_relative "localvault/env_projection"
|
|
6
|
+
require_relative "localvault/key_lookup"
|
|
7
|
+
require_relative "localvault/vault_resolver"
|
|
5
8
|
require_relative "localvault/vault"
|
|
6
9
|
require_relative "localvault/identity"
|
|
7
10
|
require_relative "localvault/share_crypto"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: localvault
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nauman Tariq
|
|
@@ -108,14 +108,20 @@ files:
|
|
|
108
108
|
- lib/localvault.rb
|
|
109
109
|
- lib/localvault/api_client.rb
|
|
110
110
|
- lib/localvault/cli.rb
|
|
111
|
+
- lib/localvault/cli/error_presenter.rb
|
|
111
112
|
- lib/localvault/cli/keys.rb
|
|
112
113
|
- lib/localvault/cli/sync.rb
|
|
113
114
|
- lib/localvault/cli/team.rb
|
|
114
115
|
- lib/localvault/cli/team_helpers.rb
|
|
115
116
|
- lib/localvault/config.rb
|
|
116
117
|
- lib/localvault/crypto.rb
|
|
118
|
+
- lib/localvault/env_projection.rb
|
|
119
|
+
- lib/localvault/group_catalog.rb
|
|
117
120
|
- lib/localvault/identity.rb
|
|
121
|
+
- lib/localvault/input_validation.rb
|
|
122
|
+
- lib/localvault/key_lookup.rb
|
|
118
123
|
- lib/localvault/key_slot.rb
|
|
124
|
+
- lib/localvault/mcp/exec_command_builder.rb
|
|
119
125
|
- lib/localvault/mcp/server.rb
|
|
120
126
|
- lib/localvault/mcp/tools.rb
|
|
121
127
|
- lib/localvault/session_cache.rb
|
|
@@ -124,6 +130,7 @@ files:
|
|
|
124
130
|
- lib/localvault/sync_bundle.rb
|
|
125
131
|
- lib/localvault/sync_state.rb
|
|
126
132
|
- lib/localvault/vault.rb
|
|
133
|
+
- lib/localvault/vault_resolver.rb
|
|
127
134
|
- lib/localvault/version.rb
|
|
128
135
|
homepage: https://github.com/inventlist/localvault
|
|
129
136
|
licenses:
|