leeloo 0.3.0 → 0.5.1
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/lib/leeloo/command.rb +161 -72
- data/lib/leeloo/controller.rb +152 -0
- data/lib/leeloo/keystore.rb +94 -1
- data/lib/leeloo/output.rb +56 -15
- data/lib/leeloo/preferences.rb +4 -3
- data/lib/leeloo/secret.rb +15 -0
- data/lib/leeloo/server.rb +37 -0
- data/lib/leeloo/version.rb +1 -1
- data/lib/leeloo.rb +2 -0
- metadata +38 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 236ca14fba0124365c37165ecd103e9342bc8c981ef4ed23bbf4afb73752e7b4
|
4
|
+
data.tar.gz: 53d104d94f393fb44e2532f7e59b91164461b14710d504b71730d5a8e549c8d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82d0d1f608c8adece27b0ff8e40c6eb01a953b1d46cdc3e28f7bfa86031a73b4989f11c9519491b6a9c1db7b3a05bd8d15f32ddb3b42aad805652fd459bb115a
|
7
|
+
data.tar.gz: 12b643a433090504e471723ed2aa519c4aee77f5fb70151a9766f78a7f62b52b6db909b6c377a3cb4762fb2959b93ea5715fbe42887e782353cc68f4a0674aca
|
data/lib/leeloo/command.rb
CHANGED
@@ -9,29 +9,9 @@ end
|
|
9
9
|
|
10
10
|
module Leeloo
|
11
11
|
|
12
|
-
class OutputFactory
|
13
|
-
def self.create options
|
14
|
-
output = nil
|
15
|
-
if options.ascii
|
16
|
-
output = Ascii.new
|
17
|
-
else
|
18
|
-
output = Terminal.new
|
19
|
-
end
|
20
|
-
if options.clipboard
|
21
|
-
ClipboardOutputDecorator.new output
|
22
|
-
else
|
23
|
-
output
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
12
|
class Command
|
29
13
|
include Commander::Methods
|
30
14
|
|
31
|
-
def initialize
|
32
|
-
@preferences = PrivateLocalFileSystemPreferences.new.load
|
33
|
-
end
|
34
|
-
|
35
15
|
def run
|
36
16
|
program :name, 'leeloo'
|
37
17
|
program :version, Leeloo::VERSION
|
@@ -43,17 +23,34 @@ module Leeloo
|
|
43
23
|
program :help, 'GitHub', 'https://github.com/sylvek'
|
44
24
|
program :help_formatter, :compact
|
45
25
|
|
46
|
-
default_command :
|
26
|
+
default_command :wrapper
|
27
|
+
|
28
|
+
command :wrapper do |c|
|
29
|
+
c.syntax = 'leeloo [options]'
|
30
|
+
c.description = "Display secrets"
|
31
|
+
c.option '--ascii', nil, 'display secrets without unicode tree'
|
32
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
33
|
+
c.option '--clipboard', nil, 'copy to clipboard'
|
34
|
+
c.action do |args, options|
|
35
|
+
unless args == []
|
36
|
+
name = args.first
|
37
|
+
ctl = SecretController.new(options)
|
38
|
+
ctl.read(name)
|
39
|
+
ctl.display
|
40
|
+
else
|
41
|
+
SecretsController.new(options).display
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
47
45
|
|
48
46
|
command :list do |c|
|
49
47
|
c.syntax = 'leeloo list [options]'
|
50
|
-
c.description = "Display secrets list of keystore"
|
48
|
+
c.description = "Display secrets list of stored on a keystore"
|
51
49
|
c.option '--ascii', nil, 'display secrets without unicode tree'
|
52
50
|
c.option '--keystore STRING', String, 'a selected keystore'
|
53
51
|
|
54
52
|
c.action do |args, options|
|
55
|
-
|
56
|
-
OutputFactory.create(options).render_secrets keystore.secrets
|
53
|
+
SecretsController.new(options).display
|
57
54
|
end
|
58
55
|
end
|
59
56
|
|
@@ -66,10 +63,9 @@ module Leeloo
|
|
66
63
|
c.action do |args, options|
|
67
64
|
abort "name is missing" unless args.length == 1
|
68
65
|
name = args.first
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
OutputFactory.create(options).render_secrets secrets
|
66
|
+
ctl = SecretsController.new(options)
|
67
|
+
ctl.search(name)
|
68
|
+
ctl.display
|
73
69
|
end
|
74
70
|
end
|
75
71
|
|
@@ -79,7 +75,7 @@ module Leeloo
|
|
79
75
|
c.option '--ascii', nil, 'display secrets without unicode tree'
|
80
76
|
|
81
77
|
c.action do |args, options|
|
82
|
-
|
78
|
+
KeystoreController.new(options).display
|
83
79
|
end
|
84
80
|
end
|
85
81
|
|
@@ -87,10 +83,12 @@ module Leeloo
|
|
87
83
|
c.syntax = 'leeloo keystore remove <name>'
|
88
84
|
c.description = "remove a keystore (path/to/keystore is not destroyed)"
|
89
85
|
|
90
|
-
c.action do |args, options|
|
86
|
+
c.action do |args, options|args
|
91
87
|
abort "name is missing" unless args.length == 1
|
92
|
-
|
93
|
-
|
88
|
+
name = args.first
|
89
|
+
ctl = KeystoreController.new(options)
|
90
|
+
ctl.remove(name)
|
91
|
+
ctl.display
|
94
92
|
end
|
95
93
|
end
|
96
94
|
|
@@ -100,10 +98,11 @@ module Leeloo
|
|
100
98
|
|
101
99
|
c.action do |args, options|
|
102
100
|
abort "name or path is missing" unless args.length == 2
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
name = args.first
|
102
|
+
path = args.last
|
103
|
+
ctl = KeystoreController.new(options)
|
104
|
+
ctl.add(name, path)
|
105
|
+
ctl.display
|
107
106
|
end
|
108
107
|
end
|
109
108
|
|
@@ -113,9 +112,62 @@ module Leeloo
|
|
113
112
|
|
114
113
|
c.action do |args, options|
|
115
114
|
abort "name is missing" unless args.length == 1
|
115
|
+
name = args.first
|
116
|
+
ctl = KeystoreController.new(options)
|
117
|
+
ctl.set_default(name)
|
118
|
+
ctl.display
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
command :key do |c|
|
123
|
+
c.syntax = 'leeloo keys'
|
124
|
+
c.description = "list keys from this keystore"
|
125
|
+
c.option '--ascii', nil, 'display secrets without unicode tree'
|
126
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
127
|
+
|
128
|
+
c.action do |args, options|
|
129
|
+
ctl = KeysController.new(options)
|
130
|
+
ctl.display
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
command "key sync" do |c|
|
135
|
+
c.syntax = 'leeloo keys sync'
|
136
|
+
c.description = "synchronize secrets with keys"
|
137
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
138
|
+
|
139
|
+
c.action do |args, options|
|
140
|
+
ctl = KeysController.new(options)
|
141
|
+
ctl.sync
|
142
|
+
ctl.display
|
143
|
+
end
|
144
|
+
end
|
116
145
|
|
117
|
-
|
118
|
-
|
146
|
+
command "key add" do |c|
|
147
|
+
c.syntax = 'leeloo key add <email>'
|
148
|
+
c.description = "add a dedicated key"
|
149
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
150
|
+
|
151
|
+
c.action do |args, options|
|
152
|
+
abort "email is missing" unless args.length == 1
|
153
|
+
email = args.first
|
154
|
+
ctl = KeysController.new(options)
|
155
|
+
ctl.add_key(email)
|
156
|
+
ctl.display
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
command "key remove" do |c|
|
161
|
+
c.syntax = 'leeloo key remove <email>'
|
162
|
+
c.description = "remove a dedicated key"
|
163
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
164
|
+
|
165
|
+
c.action do |args, options|
|
166
|
+
abort "email is missing" unless args.length == 1
|
167
|
+
email = args.first
|
168
|
+
ctl = KeysController.new(options)
|
169
|
+
ctl.remove_key(email)
|
170
|
+
ctl.display
|
119
171
|
end
|
120
172
|
end
|
121
173
|
|
@@ -124,15 +176,13 @@ module Leeloo
|
|
124
176
|
c.description = "Display a secret from a keystore"
|
125
177
|
c.option '--keystore STRING', String, 'a selected keystore'
|
126
178
|
c.option '--clipboard', nil, 'copy to clipboard'
|
127
|
-
c.option '--keystore STRING', String, 'a selected keystore'
|
128
179
|
|
129
180
|
c.action do |args, options|
|
130
181
|
abort "name is missing" unless args.length == 1
|
131
182
|
name = args.first
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
OutputFactory.create(options).render_secret secret
|
183
|
+
ctl = SecretController.new(options)
|
184
|
+
ctl.read(name)
|
185
|
+
ctl.display
|
136
186
|
end
|
137
187
|
end
|
138
188
|
|
@@ -147,22 +197,9 @@ module Leeloo
|
|
147
197
|
c.action do |args, options|
|
148
198
|
abort "name is missing" unless args.length == 1
|
149
199
|
name = args.first
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
phrase = SecureRandom.base64(32).truncate(options.generate.to_i) if options.generate
|
154
|
-
|
155
|
-
unless phrase
|
156
|
-
phrase = password "secret"
|
157
|
-
confirm = password "confirm it"
|
158
|
-
abort "not the same secret" unless phrase == confirm
|
159
|
-
end
|
160
|
-
|
161
|
-
keystore = @preferences.keystore(options.keystore)
|
162
|
-
secret = keystore.secret_from_name(name)
|
163
|
-
secret.write(phrase)
|
164
|
-
|
165
|
-
OutputFactory.create(options).render_secret secret
|
200
|
+
ctl = SecretController.new(options)
|
201
|
+
ctl.write(name)
|
202
|
+
ctl.display
|
166
203
|
end
|
167
204
|
end
|
168
205
|
|
@@ -172,9 +209,9 @@ module Leeloo
|
|
172
209
|
c.option '--keystore STRING', String, 'a selected keystore'
|
173
210
|
|
174
211
|
c.action do |args, options|
|
175
|
-
|
176
|
-
|
177
|
-
|
212
|
+
ctl = TranslateController.new(options)
|
213
|
+
ctl.translate
|
214
|
+
ctl.display
|
178
215
|
end
|
179
216
|
end
|
180
217
|
|
@@ -186,32 +223,84 @@ module Leeloo
|
|
186
223
|
c.action do |args, options|
|
187
224
|
abort "name is missing" unless args.length == 1
|
188
225
|
name = args.first
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
226
|
+
ctl = SecretController.new(options)
|
227
|
+
ctl.remove(name)
|
228
|
+
#ctl.display
|
229
|
+
puts "done."
|
193
230
|
end
|
194
231
|
end
|
195
232
|
|
196
|
-
command
|
233
|
+
command "keystore sync" do |c|
|
197
234
|
c.syntax = 'leeloo sync'
|
198
235
|
c.description = "Synchronize a keystore"
|
199
236
|
c.option '--keystore STRING', String, 'a selected keystore'
|
200
237
|
|
201
238
|
c.action do |args, options|
|
202
|
-
|
203
|
-
|
239
|
+
ctl = KeystoreController.new(options)
|
240
|
+
ctl.sync
|
241
|
+
ctl.display
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
command "keystore export" do |c|
|
246
|
+
c.syntax = 'leeloo export'
|
247
|
+
c.description = "Export all secrets from a keystore"
|
248
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
249
|
+
|
250
|
+
c.action do |args, options|
|
251
|
+
ctl = ExportController.new(options)
|
252
|
+
ctl.display
|
204
253
|
end
|
205
254
|
end
|
206
255
|
|
207
|
-
command
|
256
|
+
command "keystore init" do |c|
|
208
257
|
c.syntax = 'leeloo init'
|
209
258
|
c.description = "Initialize a keystore"
|
210
259
|
c.option '--keystore STRING', String, 'a selected keystore'
|
211
260
|
|
212
261
|
c.action do |args, options|
|
213
|
-
|
214
|
-
|
262
|
+
ctl = KeystoreController.new(options)
|
263
|
+
ctl.init
|
264
|
+
ctl.display
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
command :share do |c|
|
269
|
+
c.syntax = 'leeloo share <name>'
|
270
|
+
c.description = "share a secret with someone"
|
271
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
272
|
+
|
273
|
+
c.action do |args, options|
|
274
|
+
abort "name is missing" unless args.length == 1
|
275
|
+
name = args.first
|
276
|
+
ctl = ShareController.new(options)
|
277
|
+
ctl.token(name)
|
278
|
+
ctl.display
|
279
|
+
ctl.start_server
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
command :token do |c|
|
284
|
+
c.syntax = 'leeloo token <name>'
|
285
|
+
c.description = "generate an access token for a given secret"
|
286
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
287
|
+
|
288
|
+
c.action do |args, options|
|
289
|
+
abort "name is missing" unless args.length == 1
|
290
|
+
name = args.first
|
291
|
+
ctl = ShareController.new(options)
|
292
|
+
ctl.token(name)
|
293
|
+
ctl.display
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
command :server do |c|
|
298
|
+
c.syntax = 'leeloo server'
|
299
|
+
c.description = "start a server access token"
|
300
|
+
|
301
|
+
c.action do |args, options|
|
302
|
+
ctl = ShareController.new(options)
|
303
|
+
ctl.start_server
|
215
304
|
end
|
216
305
|
end
|
217
306
|
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Leeloo
|
2
|
+
class OutputFactory
|
3
|
+
def self.create options
|
4
|
+
output = nil
|
5
|
+
if options.ascii
|
6
|
+
output = Ascii.new
|
7
|
+
else
|
8
|
+
output = Terminal.new
|
9
|
+
end
|
10
|
+
if options.clipboard
|
11
|
+
ClipboardOutputDecorator.new output
|
12
|
+
else
|
13
|
+
output
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Controller
|
19
|
+
def display
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class PrivateLocalFileSystemController < Controller
|
24
|
+
def initialize options
|
25
|
+
@preferences = PrivateLocalFileSystemPreferences.new.load
|
26
|
+
@keystore = @preferences.keystore(options.keystore)
|
27
|
+
@output = OutputFactory.create(options)
|
28
|
+
@options = options
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class SecretsController < PrivateLocalFileSystemController
|
33
|
+
def initialize options
|
34
|
+
super options
|
35
|
+
@secrets = @keystore.secrets
|
36
|
+
end
|
37
|
+
def search name
|
38
|
+
@secrets = @secrets.select { |secret| secret.name.downcase.include? name.downcase } || []
|
39
|
+
end
|
40
|
+
def list
|
41
|
+
@secrets
|
42
|
+
end
|
43
|
+
def display
|
44
|
+
@output.render_secrets @secrets
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ExportController < PrivateLocalFileSystemController
|
49
|
+
def display
|
50
|
+
@keystore.secrets.each do |secret|
|
51
|
+
@output.render_name_and_secret(secret.name, @keystore.secret_from_name(secret.name))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class KeysController < PrivateLocalFileSystemController
|
57
|
+
def add_key email
|
58
|
+
@keystore.add_key(email)
|
59
|
+
end
|
60
|
+
def remove_key email
|
61
|
+
@keystore.remove_key(email)
|
62
|
+
end
|
63
|
+
def sync
|
64
|
+
@keystore.secrets.each do |secret|
|
65
|
+
phrase = @keystore.secret_from_name(secret.name).read
|
66
|
+
@keystore.secret_from_name(secret.name).write(phrase)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def display
|
70
|
+
@keys = @keystore.keys
|
71
|
+
@output.render_keys @keys
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class SecretController < PrivateLocalFileSystemController
|
76
|
+
def read name
|
77
|
+
@secret = @keystore.secret_from_name(name)
|
78
|
+
end
|
79
|
+
def write name
|
80
|
+
phrase = nil
|
81
|
+
|
82
|
+
phrase = STDIN.read if @options.stdin
|
83
|
+
phrase = SecureRandom.base64(32).truncate(@options.generate.to_i) if @options.generate
|
84
|
+
|
85
|
+
unless phrase
|
86
|
+
phrase = password "secret"
|
87
|
+
confirm = password "confirm it"
|
88
|
+
abort "not the same secret" unless phrase == confirm
|
89
|
+
end
|
90
|
+
|
91
|
+
@secret = @keystore.secret_from_name(name)
|
92
|
+
@secret.write(phrase)
|
93
|
+
end
|
94
|
+
def remove name
|
95
|
+
@secret = @keystore.secret_from_name(name)
|
96
|
+
@secret.erase
|
97
|
+
end
|
98
|
+
def display
|
99
|
+
@output.render_secret @secret
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class TranslateController < PrivateLocalFileSystemController
|
104
|
+
def translate
|
105
|
+
@text = STDIN.read
|
106
|
+
@text.scan(/\$\{.*\}/).each do |secret|
|
107
|
+
begin
|
108
|
+
@text.gsub! secret, (@keystore.secret_from_name(secret[2..-2])).read.to_s.strip
|
109
|
+
rescue => exception
|
110
|
+
# silent
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
def display
|
115
|
+
@output.render_text @text
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class KeystoreController < PrivateLocalFileSystemController
|
120
|
+
def add name, path
|
121
|
+
@preferences.add_keystore({"name" => name, "path" => path, "cypher" => "gpg", "vc" => "git"})
|
122
|
+
@preferences.keystore(name).init
|
123
|
+
end
|
124
|
+
def remove name
|
125
|
+
@preferences.remove_keystore name
|
126
|
+
end
|
127
|
+
def set_default name
|
128
|
+
@preferences.set_default_keystore name
|
129
|
+
end
|
130
|
+
def sync
|
131
|
+
@keystore.sync
|
132
|
+
end
|
133
|
+
def init
|
134
|
+
@keystore.init
|
135
|
+
end
|
136
|
+
def display
|
137
|
+
@output.render_preferences @preferences
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class ShareController < PrivateLocalFileSystemController
|
142
|
+
def token name
|
143
|
+
@footprint = @keystore.footprint_of(name)
|
144
|
+
end
|
145
|
+
def start_server
|
146
|
+
Server.new.start @preferences
|
147
|
+
end
|
148
|
+
def display
|
149
|
+
@output.render_footprint @footprint
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/leeloo/keystore.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'gpgme'
|
2
2
|
require 'fileutils'
|
3
3
|
require 'git'
|
4
|
+
require 'base64'
|
4
5
|
|
5
6
|
module Leeloo
|
6
7
|
|
@@ -51,6 +52,14 @@ module Leeloo
|
|
51
52
|
# initialize the keystore
|
52
53
|
end
|
53
54
|
|
55
|
+
def footprint name
|
56
|
+
# footprint a given secret path
|
57
|
+
end
|
58
|
+
|
59
|
+
def secret_from_footprint footprint
|
60
|
+
# returns a secret object
|
61
|
+
end
|
62
|
+
|
54
63
|
def == keystore
|
55
64
|
self.name == keystore.name
|
56
65
|
end
|
@@ -70,6 +79,10 @@ module Leeloo
|
|
70
79
|
find_secrets "#{@path}/secrets"
|
71
80
|
end
|
72
81
|
|
82
|
+
def keys
|
83
|
+
[]
|
84
|
+
end
|
85
|
+
|
73
86
|
def find_secrets path
|
74
87
|
elements = []
|
75
88
|
Dir.glob("#{path}/**") do |element|
|
@@ -92,6 +105,19 @@ module Leeloo
|
|
92
105
|
secret_of "#{path}/secrets/#{name}"
|
93
106
|
end
|
94
107
|
|
108
|
+
def footprint_of name
|
109
|
+
secret = secret_from_name name
|
110
|
+
{ "footprint" => secret.footprint, "keystore" => self.name, "secret" => secret.name }
|
111
|
+
end
|
112
|
+
|
113
|
+
def secret_from_footprint footprint
|
114
|
+
secret = secret_from_name footprint["secret"]
|
115
|
+
unless secret.footprint == footprint["footprint"]
|
116
|
+
raise "footprint is not valid"
|
117
|
+
end
|
118
|
+
secret
|
119
|
+
end
|
120
|
+
|
95
121
|
end
|
96
122
|
|
97
123
|
class GpgPrivateLocalFileSystemKeystore < PrivateLocalFileSystemKeystore
|
@@ -99,7 +125,10 @@ module Leeloo
|
|
99
125
|
def initialize name, path
|
100
126
|
super name, path
|
101
127
|
FileUtils.mkdir_p "#{@path}/keys"
|
128
|
+
populate_recipients
|
129
|
+
end
|
102
130
|
|
131
|
+
def populate_recipients
|
103
132
|
@recipients = []
|
104
133
|
Dir.glob("#{path}/keys/*") { |key| @recipients << File.basename(key) }
|
105
134
|
@recipients.each { |key| GPGME::Key.import(File.open("#{path}/keys/#{key}")) }
|
@@ -107,7 +136,30 @@ module Leeloo
|
|
107
136
|
|
108
137
|
def init
|
109
138
|
super
|
110
|
-
|
139
|
+
File.write("#{@path}/keys/do_not_remove_me", "do not remove me")
|
140
|
+
end
|
141
|
+
|
142
|
+
def keys
|
143
|
+
available = GPGME::Key.find(:public, nil, ).map { |key| key.email }
|
144
|
+
actual = Dir.glob("#{@path}/keys/**").map { |path| path.split('/').last }
|
145
|
+
available.map { |email| actual.include?(email) ? "#{email}::true" : "#{email}::false" }
|
146
|
+
end
|
147
|
+
|
148
|
+
def add_key email
|
149
|
+
paths = []
|
150
|
+
GPGME::Key.find(:public, email).each do |key|
|
151
|
+
key.export(:output => File.open("#{path}/keys/#{key.uids.first.email}", "w+"))
|
152
|
+
paths << "#{path}/keys/#{key.uids.first.email}"
|
153
|
+
end
|
154
|
+
return paths
|
155
|
+
end
|
156
|
+
|
157
|
+
def remove_key email
|
158
|
+
if File.exist?("#{path}/keys/#{email}")
|
159
|
+
File.delete("#{path}/keys/#{email}")
|
160
|
+
return "#{path}/keys/#{email}"
|
161
|
+
end
|
162
|
+
return nil
|
111
163
|
end
|
112
164
|
|
113
165
|
def secret_of path
|
@@ -119,6 +171,20 @@ module Leeloo
|
|
119
171
|
secret_of "#{path}/secrets/#{name}.gpg"
|
120
172
|
end
|
121
173
|
|
174
|
+
def footprint_of name
|
175
|
+
footprint = super name
|
176
|
+
footprint["sign"] = Base64.strict_encode64 GPGME::Crypto.new.sign(footprint["footprint"]).to_s
|
177
|
+
footprint
|
178
|
+
end
|
179
|
+
|
180
|
+
def secret_from_footprint footprint
|
181
|
+
data = GPGME::Crypto.new.verify(Base64.strict_decode64 footprint["sign"]) { |signature| signature.valid? }
|
182
|
+
if data.read == footprint["footprint"]
|
183
|
+
super footprint
|
184
|
+
else
|
185
|
+
raise "signature is not valid"
|
186
|
+
end
|
187
|
+
end
|
122
188
|
end
|
123
189
|
|
124
190
|
class GitKeystoreDecorator < Keystore
|
@@ -136,6 +202,25 @@ module Leeloo
|
|
136
202
|
secret_of @keystore.secret_from_name(element)
|
137
203
|
end
|
138
204
|
|
205
|
+
def keys
|
206
|
+
@keystore.keys
|
207
|
+
end
|
208
|
+
|
209
|
+
def add_key email
|
210
|
+
@keystore.add_key(email).each do |path|
|
211
|
+
@git.add path
|
212
|
+
@git.commit "#{email} added"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def remove_key email
|
217
|
+
deleted = @keystore.remove_key email
|
218
|
+
if deleted
|
219
|
+
@git.add deleted
|
220
|
+
@git.commit "#{email} removed"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
139
224
|
def secrets
|
140
225
|
@keystore.secrets
|
141
226
|
end
|
@@ -148,6 +233,14 @@ module Leeloo
|
|
148
233
|
@keystore.path
|
149
234
|
end
|
150
235
|
|
236
|
+
def footprint_of element
|
237
|
+
@keystore.footprint_of element
|
238
|
+
end
|
239
|
+
|
240
|
+
def secret_from_footprint footprint
|
241
|
+
@keystore.secret_from_footprint footprint
|
242
|
+
end
|
243
|
+
|
151
244
|
def sync
|
152
245
|
@git.pull
|
153
246
|
@keystore.sync
|
data/lib/leeloo/output.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'clipboard'
|
2
2
|
require 'tty-table'
|
3
3
|
require 'tty-tree'
|
4
|
+
require 'json'
|
5
|
+
require 'base64'
|
6
|
+
require 'socket'
|
4
7
|
|
5
8
|
module Leeloo
|
6
9
|
|
@@ -15,7 +18,19 @@ module Leeloo
|
|
15
18
|
def render_secret secret
|
16
19
|
end
|
17
20
|
|
18
|
-
def
|
21
|
+
def render_text text
|
22
|
+
end
|
23
|
+
|
24
|
+
def render_name_and_secret name, secret
|
25
|
+
end
|
26
|
+
|
27
|
+
def render_keys keys
|
28
|
+
end
|
29
|
+
|
30
|
+
def render_footprint footprint
|
31
|
+
end
|
32
|
+
|
33
|
+
def render_share footprint
|
19
34
|
end
|
20
35
|
end
|
21
36
|
|
@@ -37,16 +52,24 @@ module Leeloo
|
|
37
52
|
end
|
38
53
|
end
|
39
54
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
55
|
+
def render_name_and_secret name, secret
|
56
|
+
self.render_text name
|
57
|
+
self.render_secret secret
|
58
|
+
self.render_text '------'
|
59
|
+
end
|
60
|
+
|
61
|
+
def render_text text
|
48
62
|
puts text
|
49
63
|
end
|
64
|
+
|
65
|
+
def render_keys keys
|
66
|
+
self.render_text keys
|
67
|
+
end
|
68
|
+
|
69
|
+
def render_footprint footprint
|
70
|
+
puts "token:"
|
71
|
+
puts Base64.strict_encode64 footprint.to_json
|
72
|
+
end
|
50
73
|
end
|
51
74
|
|
52
75
|
class Terminal < Ascii
|
@@ -62,11 +85,21 @@ module Leeloo
|
|
62
85
|
end
|
63
86
|
|
64
87
|
def render_secrets secrets
|
65
|
-
hash = {
|
66
|
-
secrets.sort_by(&:name).each { |secret| sort(hash[
|
88
|
+
hash = {'Password Store' => []}
|
89
|
+
secrets.sort_by(&:name).each { |secret| sort(hash['Password Store'], secret.name) }
|
67
90
|
puts TTY::Tree.new(hash).render
|
68
91
|
end
|
69
92
|
|
93
|
+
def render_keys keys
|
94
|
+
rows = []
|
95
|
+
keys.each do |key|
|
96
|
+
splitted = key.split('::')
|
97
|
+
is_present = '*' if splitted[1] == 'true'
|
98
|
+
rows << [splitted[0], is_present]
|
99
|
+
end
|
100
|
+
puts TTY::Table.new(header: ['Email', 'Selected'], rows: rows).render(:ascii)
|
101
|
+
end
|
102
|
+
|
70
103
|
def sort array, element
|
71
104
|
if element
|
72
105
|
e = element.split("/", 2)
|
@@ -107,16 +140,16 @@ module Leeloo
|
|
107
140
|
@output.render_secrets secrets
|
108
141
|
end
|
109
142
|
|
110
|
-
def
|
111
|
-
@output.
|
143
|
+
def render_text text
|
144
|
+
@output.render_text text
|
112
145
|
end
|
113
146
|
|
114
147
|
def render_secret secret
|
115
148
|
|
116
149
|
Signal.trap("INT") do
|
117
150
|
Clipboard.clear
|
118
|
-
abort "
|
119
|
-
|
151
|
+
abort "cleared"
|
152
|
+
end
|
120
153
|
|
121
154
|
Clipboard.copy secret.read
|
122
155
|
wait = Thread.new do
|
@@ -129,6 +162,14 @@ module Leeloo
|
|
129
162
|
wait.join
|
130
163
|
Clipboard.clear
|
131
164
|
end
|
165
|
+
|
166
|
+
def render_footprint footprint
|
167
|
+
@output.render_footprint footprint
|
168
|
+
end
|
169
|
+
|
170
|
+
def render_share footprint
|
171
|
+
@output.render_share footprint
|
172
|
+
end
|
132
173
|
end
|
133
174
|
|
134
175
|
end
|
data/lib/leeloo/preferences.rb
CHANGED
@@ -34,8 +34,9 @@ module Leeloo
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def remove_keystore name
|
37
|
+
abort "you can not remove default keystore" if name == @default
|
37
38
|
keystore = @keystores.find { |k| k["name"] == name }
|
38
|
-
if keystore !=nil
|
39
|
+
if keystore != nil
|
39
40
|
@keystores.delete keystore
|
40
41
|
end
|
41
42
|
end
|
@@ -70,8 +71,8 @@ module Leeloo
|
|
70
71
|
self
|
71
72
|
end
|
72
73
|
|
73
|
-
def keystore_of
|
74
|
-
keystore = @keystores.find { |keystore| keystore["name"] ==
|
74
|
+
def keystore_of name
|
75
|
+
keystore = @keystores.find { |keystore| keystore["name"] == name }
|
75
76
|
KeystoreFactory::create keystore
|
76
77
|
end
|
77
78
|
|
data/lib/leeloo/secret.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'gpgme'
|
2
2
|
require 'fileutils'
|
3
|
+
require 'digest'
|
3
4
|
|
4
5
|
module Leeloo
|
5
6
|
class Secret
|
@@ -26,6 +27,9 @@ module Leeloo
|
|
26
27
|
# erase the secret
|
27
28
|
end
|
28
29
|
|
30
|
+
def footprint
|
31
|
+
# a footprint is a token proving the authenticity of a secret
|
32
|
+
end
|
29
33
|
end
|
30
34
|
|
31
35
|
class LocalFileSystemSecret < Secret
|
@@ -50,6 +54,13 @@ module Leeloo
|
|
50
54
|
File.delete @pathname
|
51
55
|
end
|
52
56
|
|
57
|
+
def footprint
|
58
|
+
secret = File.read @pathname
|
59
|
+
md5 = Digest::MD5.new
|
60
|
+
md5 << secret
|
61
|
+
md5.hexdigest
|
62
|
+
end
|
63
|
+
|
53
64
|
end
|
54
65
|
|
55
66
|
class GpgLocalFileSystemSecret < LocalFileSystemSecret
|
@@ -88,6 +99,10 @@ module Leeloo
|
|
88
99
|
@secret.read
|
89
100
|
end
|
90
101
|
|
102
|
+
def footprint
|
103
|
+
@secret.footprint
|
104
|
+
end
|
105
|
+
|
91
106
|
def write phrase
|
92
107
|
@secret.write phrase
|
93
108
|
@git.add @secret.pathname
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
class Server
|
6
|
+
|
7
|
+
def start preferences
|
8
|
+
|
9
|
+
puts """
|
10
|
+
Please share this url :
|
11
|
+
http://your_ip:8000\?q=YOUR_TOKEN
|
12
|
+
|
13
|
+
run ssh -R:localhost:8000 ssh.localhost.run
|
14
|
+
if you want to share your password through tunneling
|
15
|
+
"""
|
16
|
+
|
17
|
+
server = WEBrick::HTTPServer.new :Port => 8000
|
18
|
+
server.mount_proc '/' do |req, res|
|
19
|
+
query = req.query()["q"] || req.body()
|
20
|
+
if query
|
21
|
+
begin
|
22
|
+
body = JSON.parse(Base64.strict_decode64 query)
|
23
|
+
key = body["body"] ? JSON.parse(body["body"]) : body
|
24
|
+
res.body = preferences.keystore(key["keystore"]).secret_from_footprint(key).read.to_s
|
25
|
+
rescue => exception
|
26
|
+
puts exception
|
27
|
+
res.status = 400
|
28
|
+
end
|
29
|
+
else
|
30
|
+
res.status = 400
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
trap 'INT' do server.shutdown end
|
35
|
+
server.start
|
36
|
+
end
|
37
|
+
end
|
data/lib/leeloo/version.rb
CHANGED
data/lib/leeloo.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leeloo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sylvek
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: commander
|
@@ -94,20 +94,34 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '1.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webrick
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.7'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.7'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: bundler
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
115
|
- - "~>"
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version: '2
|
117
|
+
version: '2'
|
104
118
|
type: :development
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version: '2
|
124
|
+
version: '2'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: rake
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +150,20 @@ dependencies:
|
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '3.0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rspec_junit_formatter
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0.4'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0.4'
|
139
167
|
description: The easiest way to share securely your secrets
|
140
168
|
email:
|
141
169
|
- smaucourt@gmail.com
|
@@ -147,16 +175,18 @@ files:
|
|
147
175
|
- exe/leeloo
|
148
176
|
- lib/leeloo.rb
|
149
177
|
- lib/leeloo/command.rb
|
178
|
+
- lib/leeloo/controller.rb
|
150
179
|
- lib/leeloo/keystore.rb
|
151
180
|
- lib/leeloo/output.rb
|
152
181
|
- lib/leeloo/preferences.rb
|
153
182
|
- lib/leeloo/secret.rb
|
183
|
+
- lib/leeloo/server.rb
|
154
184
|
- lib/leeloo/version.rb
|
155
185
|
homepage: https://github.com/sylvek/leeloo
|
156
186
|
licenses:
|
157
187
|
- MIT
|
158
188
|
metadata: {}
|
159
|
-
post_install_message:
|
189
|
+
post_install_message:
|
160
190
|
rdoc_options: []
|
161
191
|
require_paths:
|
162
192
|
- lib
|
@@ -171,8 +201,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
201
|
- !ruby/object:Gem::Version
|
172
202
|
version: '0'
|
173
203
|
requirements: []
|
174
|
-
rubygems_version: 3.
|
175
|
-
signing_key:
|
204
|
+
rubygems_version: 3.3.3
|
205
|
+
signing_key:
|
176
206
|
specification_version: 4
|
177
207
|
summary: The easiest way to share securely your secrets
|
178
208
|
test_files: []
|