leeloo 0.2.0 → 0.5.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/lib/leeloo/command.rb +163 -68
- 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 +15 -2
- 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 +40 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b19454b20e434ee86fcca39a6cdf98f48dec70b5c775daff8709baf9b7359c12
|
4
|
+
data.tar.gz: c06c7449acb7c866ef2ee5be22c78fa97ee0d61a530bd575a8059daa1edb6fad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 056f03b0d8e76f92555e6a0ff50ed3da651055c9e9a2f246fd8714d907c200739a0d2f1dd013824b037dc24e2034d4e0d8f31aec023d928aa253a52f9b67d470
|
7
|
+
data.tar.gz: b872f7ef01b45a69edcb686dbccc5e5e02affc49c518eebde9eae60e7085e44eea8bda720bb68aa9c0766ba69139fb33c0ef48a93db04e2ba7329fc56fc059bc
|
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,29 @@ 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.action do |args, options|
|
30
|
+
unless args == []
|
31
|
+
name = args.first
|
32
|
+
ctl = SecretController.new(options)
|
33
|
+
ctl.read(name)
|
34
|
+
ctl.display
|
35
|
+
else
|
36
|
+
SecretsController.new(options).display
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
47
40
|
|
48
41
|
command :list do |c|
|
49
42
|
c.syntax = 'leeloo list [options]'
|
50
|
-
c.description = "Display secrets list of keystore"
|
43
|
+
c.description = "Display secrets list of stored on a keystore"
|
51
44
|
c.option '--ascii', nil, 'display secrets without unicode tree'
|
52
45
|
c.option '--keystore STRING', String, 'a selected keystore'
|
53
46
|
|
54
47
|
c.action do |args, options|
|
55
|
-
|
56
|
-
OutputFactory.create(options).render_secrets keystore.secrets
|
48
|
+
SecretsController.new(options).display
|
57
49
|
end
|
58
50
|
end
|
59
51
|
|
@@ -66,10 +58,9 @@ module Leeloo
|
|
66
58
|
c.action do |args, options|
|
67
59
|
abort "name is missing" unless args.length == 1
|
68
60
|
name = args.first
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
OutputFactory.create(options).render_secrets secrets
|
61
|
+
ctl = SecretsController.new(options)
|
62
|
+
ctl.search(name)
|
63
|
+
ctl.display
|
73
64
|
end
|
74
65
|
end
|
75
66
|
|
@@ -79,7 +70,20 @@ module Leeloo
|
|
79
70
|
c.option '--ascii', nil, 'display secrets without unicode tree'
|
80
71
|
|
81
72
|
c.action do |args, options|
|
82
|
-
|
73
|
+
KeystoreController.new(options).display
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
command "keystore remove" do |c|
|
78
|
+
c.syntax = 'leeloo keystore remove <name>'
|
79
|
+
c.description = "remove a keystore (path/to/keystore is not destroyed)"
|
80
|
+
|
81
|
+
c.action do |args, options|args
|
82
|
+
abort "name is missing" unless args.length == 1
|
83
|
+
name = args.first
|
84
|
+
ctl = KeystoreController.new(options)
|
85
|
+
ctl.remove(name)
|
86
|
+
ctl.display
|
83
87
|
end
|
84
88
|
end
|
85
89
|
|
@@ -89,10 +93,11 @@ module Leeloo
|
|
89
93
|
|
90
94
|
c.action do |args, options|
|
91
95
|
abort "name or path is missing" unless args.length == 2
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
+
name = args.first
|
97
|
+
path = args.last
|
98
|
+
ctl = KeystoreController.new(options)
|
99
|
+
ctl.add(name, path)
|
100
|
+
ctl.display
|
96
101
|
end
|
97
102
|
end
|
98
103
|
|
@@ -102,9 +107,62 @@ module Leeloo
|
|
102
107
|
|
103
108
|
c.action do |args, options|
|
104
109
|
abort "name is missing" unless args.length == 1
|
110
|
+
name = args.first
|
111
|
+
ctl = KeystoreController.new(options)
|
112
|
+
ctl.set_default(name)
|
113
|
+
ctl.display
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
command :key do |c|
|
118
|
+
c.syntax = 'leeloo keys'
|
119
|
+
c.description = "list keys from this keystore"
|
120
|
+
c.option '--ascii', nil, 'display secrets without unicode tree'
|
121
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
122
|
+
|
123
|
+
c.action do |args, options|
|
124
|
+
ctl = KeysController.new(options)
|
125
|
+
ctl.display
|
126
|
+
end
|
127
|
+
end
|
105
128
|
|
106
|
-
|
107
|
-
|
129
|
+
command "key sync" do |c|
|
130
|
+
c.syntax = 'leeloo keys sync'
|
131
|
+
c.description = "synchronize secrets with keys"
|
132
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
133
|
+
|
134
|
+
c.action do |args, options|
|
135
|
+
ctl = KeysController.new(options)
|
136
|
+
ctl.sync
|
137
|
+
ctl.display
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
command "key add" do |c|
|
142
|
+
c.syntax = 'leeloo key add <email>'
|
143
|
+
c.description = "add a dedicated key"
|
144
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
145
|
+
|
146
|
+
c.action do |args, options|
|
147
|
+
abort "email is missing" unless args.length == 1
|
148
|
+
email = args.first
|
149
|
+
ctl = KeysController.new(options)
|
150
|
+
ctl.add_key(email)
|
151
|
+
ctl.display
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
command "key remove" do |c|
|
156
|
+
c.syntax = 'leeloo key remove <email>'
|
157
|
+
c.description = "remove a dedicated key"
|
158
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
159
|
+
|
160
|
+
c.action do |args, options|
|
161
|
+
abort "email is missing" unless args.length == 1
|
162
|
+
email = args.first
|
163
|
+
ctl = KeysController.new(options)
|
164
|
+
ctl.remove_key(email)
|
165
|
+
ctl.display
|
108
166
|
end
|
109
167
|
end
|
110
168
|
|
@@ -118,10 +176,9 @@ module Leeloo
|
|
118
176
|
c.action do |args, options|
|
119
177
|
abort "name is missing" unless args.length == 1
|
120
178
|
name = args.first
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
OutputFactory.create(options).render_secret secret
|
179
|
+
ctl = SecretController.new(options)
|
180
|
+
ctl.read(name)
|
181
|
+
ctl.display
|
125
182
|
end
|
126
183
|
end
|
127
184
|
|
@@ -136,22 +193,9 @@ module Leeloo
|
|
136
193
|
c.action do |args, options|
|
137
194
|
abort "name is missing" unless args.length == 1
|
138
195
|
name = args.first
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
phrase = SecureRandom.base64(32).truncate(options.generate.to_i) if options.generate
|
143
|
-
|
144
|
-
unless phrase
|
145
|
-
phrase = password "secret"
|
146
|
-
confirm = password "confirm it"
|
147
|
-
abort "not the same secret" unless phrase == confirm
|
148
|
-
end
|
149
|
-
|
150
|
-
keystore = @preferences.keystore(options.keystore)
|
151
|
-
secret = keystore.secret_from_name(name)
|
152
|
-
secret.write(phrase)
|
153
|
-
|
154
|
-
OutputFactory.create(options).render_secret secret
|
196
|
+
ctl = SecretController.new(options)
|
197
|
+
ctl.write(name)
|
198
|
+
ctl.display
|
155
199
|
end
|
156
200
|
end
|
157
201
|
|
@@ -161,9 +205,9 @@ module Leeloo
|
|
161
205
|
c.option '--keystore STRING', String, 'a selected keystore'
|
162
206
|
|
163
207
|
c.action do |args, options|
|
164
|
-
|
165
|
-
|
166
|
-
|
208
|
+
ctl = TranslateController.new(options)
|
209
|
+
ctl.translate
|
210
|
+
ctl.display
|
167
211
|
end
|
168
212
|
end
|
169
213
|
|
@@ -175,32 +219,83 @@ module Leeloo
|
|
175
219
|
c.action do |args, options|
|
176
220
|
abort "name is missing" unless args.length == 1
|
177
221
|
name = args.first
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
secret.erase
|
222
|
+
ctl = SecretController.new(options)
|
223
|
+
ctl.remove(name)
|
224
|
+
ctl.display
|
182
225
|
end
|
183
226
|
end
|
184
227
|
|
185
|
-
command
|
228
|
+
command "keystore sync" do |c|
|
186
229
|
c.syntax = 'leeloo sync'
|
187
230
|
c.description = "Synchronize a keystore"
|
188
231
|
c.option '--keystore STRING', String, 'a selected keystore'
|
189
232
|
|
190
233
|
c.action do |args, options|
|
191
|
-
|
192
|
-
|
234
|
+
ctl = KeystoreController.new(options)
|
235
|
+
ctl.sync
|
236
|
+
ctl.display
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
command "keystore export" do |c|
|
241
|
+
c.syntax = 'leeloo export'
|
242
|
+
c.description = "Export all secrets from a keystore"
|
243
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
244
|
+
|
245
|
+
c.action do |args, options|
|
246
|
+
ctl = ExportController.new(options)
|
247
|
+
ctl.display
|
193
248
|
end
|
194
249
|
end
|
195
250
|
|
196
|
-
command
|
251
|
+
command "keystore init" do |c|
|
197
252
|
c.syntax = 'leeloo init'
|
198
253
|
c.description = "Initialize a keystore"
|
199
254
|
c.option '--keystore STRING', String, 'a selected keystore'
|
200
255
|
|
201
256
|
c.action do |args, options|
|
202
|
-
|
203
|
-
|
257
|
+
ctl = KeystoreController.new(options)
|
258
|
+
ctl.init
|
259
|
+
ctl.display
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
command :share do |c|
|
264
|
+
c.syntax = 'leeloo share <name>'
|
265
|
+
c.description = "share a secret with someone"
|
266
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
267
|
+
|
268
|
+
c.action do |args, options|
|
269
|
+
abort "name is missing" unless args.length == 1
|
270
|
+
name = args.first
|
271
|
+
ctl = ShareController.new(options)
|
272
|
+
ctl.token(name)
|
273
|
+
ctl.display
|
274
|
+
ctl.start_server
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
command :token do |c|
|
279
|
+
c.syntax = 'leeloo token <name>'
|
280
|
+
c.description = "generate an access token for a given secret"
|
281
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
282
|
+
|
283
|
+
c.action do |args, options|
|
284
|
+
abort "name is missing" unless args.length == 1
|
285
|
+
name = args.first
|
286
|
+
ctl = ShareController.new(options)
|
287
|
+
ctl.token(name)
|
288
|
+
ctl.display
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
command :server do |c|
|
293
|
+
c.syntax = 'leeloo server'
|
294
|
+
c.description = "start a server access token"
|
295
|
+
|
296
|
+
c.action do |args, options|
|
297
|
+
ctl = ShareController.new(options)
|
298
|
+
ctl.start_server
|
204
299
|
end
|
205
300
|
end
|
206
301
|
|
@@ -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
@@ -32,6 +32,14 @@ module Leeloo
|
|
32
32
|
@keystores << keystore
|
33
33
|
end
|
34
34
|
end
|
35
|
+
|
36
|
+
def remove_keystore name
|
37
|
+
abort "you can not remove default keystore" if name == @default
|
38
|
+
keystore = @keystores.find { |k| k["name"] == name }
|
39
|
+
if keystore != nil
|
40
|
+
@keystores.delete keystore
|
41
|
+
end
|
42
|
+
end
|
35
43
|
end
|
36
44
|
|
37
45
|
class PrivateLocalFileSystemPreferences < Preferences
|
@@ -63,8 +71,8 @@ module Leeloo
|
|
63
71
|
self
|
64
72
|
end
|
65
73
|
|
66
|
-
def keystore_of
|
67
|
-
keystore = @keystores.find { |keystore| keystore["name"] ==
|
74
|
+
def keystore_of name
|
75
|
+
keystore = @keystores.find { |keystore| keystore["name"] == name }
|
68
76
|
KeystoreFactory::create keystore
|
69
77
|
end
|
70
78
|
|
@@ -82,6 +90,11 @@ module Leeloo
|
|
82
90
|
File.write("#{@path}/keystores", @keystores.to_yaml)
|
83
91
|
end
|
84
92
|
|
93
|
+
def remove_keystore name
|
94
|
+
super name
|
95
|
+
File.write("#{@path}/keystores", @keystores.to_yaml)
|
96
|
+
end
|
97
|
+
|
85
98
|
end
|
86
99
|
|
87
100
|
end
|
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.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- sylvek
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-12-31 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
|
-
homepage: https://github.com/sylvek
|
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.2.32
|
205
|
+
signing_key:
|
176
206
|
specification_version: 4
|
177
207
|
summary: The easiest way to share securely your secrets
|
178
208
|
test_files: []
|