leeloo 0.0.16 → 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 +4 -4
- data/lib/leeloo.rb +3 -3
- data/lib/leeloo/command.rb +102 -130
- data/lib/leeloo/keystore.rb +145 -22
- data/lib/leeloo/output.rb +134 -0
- data/lib/leeloo/preferences.rb +87 -0
- data/lib/leeloo/secret.rb +78 -57
- data/lib/leeloo/version.rb +1 -1
- metadata +16 -30
- data/lib/leeloo/config.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 693f067bfe0f4e494ac28768cecf4d17c8ec7eabb9757d593fb2607236154a95
|
4
|
+
data.tar.gz: d86983d28080fdbc547b2856f2aa143d8be1846ac34e3a23760e6aab2a4f3ca4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: daacdabbfaefe03434138c3bd82625938196778e8a3c842492100fce084d6140d7eccf8d1df4e6afb8f0885e34c48c5b67920a44123f6adbe0c4b88e4f0b12d9
|
7
|
+
data.tar.gz: 5c8d5ad16d49c15825a8eab81358286391c57c6b34e31f206c6dc56a2afec52f883baf57e2c52c54e53244913182be0184dda6c212b2c5f672ab57b9fe8a70e3
|
data/lib/leeloo.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'leeloo/version'
|
2
2
|
require 'leeloo/command'
|
3
|
-
require 'leeloo/
|
3
|
+
require 'leeloo/preferences'
|
4
4
|
require 'leeloo/keystore'
|
5
|
-
require 'leeloo/
|
5
|
+
require 'leeloo/secret'
|
6
|
+
require 'leeloo/output'
|
6
7
|
|
7
8
|
module Leeloo
|
8
9
|
def self.start
|
9
|
-
Config.load
|
10
10
|
Command.new.run
|
11
11
|
end
|
12
12
|
end
|
data/lib/leeloo/command.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'commander/import'
|
2
2
|
require 'securerandom'
|
3
|
-
require 'clipboard'
|
4
|
-
|
5
3
|
|
6
4
|
class String
|
7
5
|
def truncate(max)
|
@@ -11,9 +9,29 @@ end
|
|
11
9
|
|
12
10
|
module Leeloo
|
13
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
|
+
|
14
28
|
class Command
|
15
29
|
include Commander::Methods
|
16
30
|
|
31
|
+
def initialize
|
32
|
+
@preferences = PrivateLocalFileSystemPreferences.new.load
|
33
|
+
end
|
34
|
+
|
17
35
|
def run
|
18
36
|
program :name, 'leeloo'
|
19
37
|
program :version, Leeloo::VERSION
|
@@ -27,106 +45,73 @@ module Leeloo
|
|
27
45
|
|
28
46
|
default_command :"list"
|
29
47
|
|
30
|
-
command :
|
31
|
-
c.syntax = 'leeloo
|
32
|
-
c.description = "
|
33
|
-
c.action do |args, options|
|
34
|
-
abort("a secret key PGP is mandatory") if Keystore::secret_key_empty?
|
35
|
-
Config::init
|
36
|
-
say "Initialization completed"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
command :"list-keystore" do |c|
|
41
|
-
c.syntax = 'leeloo keystore'
|
42
|
-
c.description = "Display keystores list"
|
48
|
+
command :list do |c|
|
49
|
+
c.syntax = 'leeloo list [options]'
|
50
|
+
c.description = "Display secrets list of keystore"
|
43
51
|
c.option '--ascii', nil, 'display secrets without unicode tree'
|
52
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
44
53
|
|
45
54
|
c.action do |args, options|
|
46
|
-
|
47
|
-
|
55
|
+
keystore = @preferences.keystore(options.keystore)
|
56
|
+
OutputFactory.create(options).render_secrets keystore.secrets
|
48
57
|
end
|
49
58
|
end
|
50
|
-
alias_command :keystore, :"list-keystore"
|
51
59
|
|
52
|
-
command :
|
53
|
-
c.syntax = 'leeloo
|
54
|
-
c.description = "Display
|
55
|
-
c.option '--keystore STRING', String, 'a selected keystore'
|
60
|
+
command :keystore do |c|
|
61
|
+
c.syntax = 'leeloo keystores'
|
62
|
+
c.description = "Display current keystores"
|
56
63
|
c.option '--ascii', nil, 'display secrets without unicode tree'
|
57
64
|
|
58
65
|
c.action do |args, options|
|
59
|
-
options.
|
60
|
-
|
61
|
-
Secret::list Config.get_keystore(options.keystore), options.ascii
|
66
|
+
OutputFactory.create(options).render_preferences @preferences
|
62
67
|
end
|
63
68
|
end
|
64
|
-
alias_command :list, :"list-secret"
|
65
|
-
alias_command :secrets, :"list-secret"
|
66
69
|
|
67
|
-
command
|
68
|
-
c.syntax = 'leeloo
|
69
|
-
c.description = "
|
70
|
+
command "keystore add" do |c|
|
71
|
+
c.syntax = 'leeloo keystore add <name> <path/to/keystore>'
|
72
|
+
c.description = "add a keystore"
|
70
73
|
|
71
74
|
c.action do |args, options|
|
75
|
+
abort "name or path is missing" unless args.length == 2
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
Keystore.add_keystore name, keystore
|
78
|
-
Config.add_keystore name, keystore
|
79
|
-
say "keystore #{name} added"
|
77
|
+
@preferences.add_keystore({"name" => args.first, "path" => args.last, "cypher" => "gpg", "vc" => "git"})
|
78
|
+
@preferences.keystore(args.first).init
|
79
|
+
OutputFactory.create(options).render_preferences @preferences
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
command
|
84
|
-
c.syntax =
|
85
|
-
c.description = "
|
86
|
-
c.option '--keystore STRING', String, 'a selected keystore'
|
83
|
+
command "keystore default" do |c|
|
84
|
+
c.syntax = 'leeloo keystore default name'
|
85
|
+
c.description = "set the default keystore"
|
87
86
|
|
88
87
|
c.action do |args, options|
|
89
|
-
abort "
|
90
|
-
repository = args.first
|
91
|
-
Keystore.add_remote Config.get_keystore(options.keystore), repository
|
92
|
-
say "remote added successfully"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
alias_command :remote, :"remote-keystore"
|
96
|
-
|
97
|
-
command :"sync-keystore" do |c|
|
98
|
-
c.syntax = "leeloo sync"
|
99
|
-
c.description = "sync secrets with git repository (if configured)"
|
100
|
-
c.option '--keystore STRING', String, 'a selected keystore'
|
88
|
+
abort "name is missing" unless args.length == 1
|
101
89
|
|
102
|
-
|
103
|
-
options.
|
104
|
-
synchronized = Keystore.sync_keystore Config.get_keystore(options.keystore)
|
105
|
-
if synchronized
|
106
|
-
say "secrets synchronized successfully"
|
107
|
-
else
|
108
|
-
abort "call remote-keystore before sync-keystore"
|
109
|
-
end
|
90
|
+
@preferences.set_default_keystore args.first
|
91
|
+
OutputFactory.create(options).render_preferences @preferences
|
110
92
|
end
|
111
93
|
end
|
112
|
-
alias_command :sync, :"sync-keystore"
|
113
94
|
|
114
|
-
command :
|
115
|
-
c.syntax = 'leeloo
|
116
|
-
c.description = "
|
95
|
+
command :read do |c|
|
96
|
+
c.syntax = 'leeloo read <name>'
|
97
|
+
c.description = "Display a secret from a keystore"
|
98
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
99
|
+
c.option '--clipboard', nil, 'copy to clipboard'
|
117
100
|
c.option '--keystore STRING', String, 'a selected keystore'
|
118
101
|
|
119
102
|
c.action do |args, options|
|
120
|
-
|
121
|
-
|
122
|
-
|
103
|
+
abort "name is missing" unless args.length == 1
|
104
|
+
name = args.first
|
105
|
+
|
106
|
+
keystore = @preferences.keystore(options.keystore)
|
107
|
+
secret = keystore.secret_from_name(name)
|
108
|
+
OutputFactory.create(options).render_secret secret
|
123
109
|
end
|
124
110
|
end
|
125
|
-
alias_command :sign, :"sign-secret"
|
126
111
|
|
127
|
-
command :
|
128
|
-
c.syntax = 'leeloo
|
129
|
-
c.description = "
|
112
|
+
command :write do |c|
|
113
|
+
c.syntax = 'leeloo write <name> <secret>'
|
114
|
+
c.description = "Write a secret from a keystore"
|
130
115
|
c.option '--keystore STRING', String, 'a selected keystore'
|
131
116
|
c.option '--generate INTEGER', Integer, 'a number of randomized characters'
|
132
117
|
c.option '--stdin', nil, 'secret given by stdin pipe'
|
@@ -135,87 +120,74 @@ module Leeloo
|
|
135
120
|
c.action do |args, options|
|
136
121
|
abort "name is missing" unless args.length == 1
|
137
122
|
name = args.first
|
123
|
+
phrase = nil
|
138
124
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
secret = nil
|
143
|
-
secret = STDIN.read if options.stdin
|
144
|
-
secret = SecureRandom.base64(32).truncate(options.generate.to_i) if options.generate
|
125
|
+
phrase = STDIN.read if options.stdin
|
126
|
+
phrase = SecureRandom.base64(32).truncate(options.generate.to_i) if options.generate
|
145
127
|
|
146
|
-
unless
|
147
|
-
|
148
|
-
|
149
|
-
|
128
|
+
unless phrase
|
129
|
+
phrase = password "secret"
|
130
|
+
confirm = password "confirm it"
|
131
|
+
abort "not the same secret" unless phrase == confirm
|
150
132
|
end
|
151
133
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
134
|
+
keystore = @preferences.keystore(options.keystore)
|
135
|
+
secret = keystore.secret_from_name(name)
|
136
|
+
secret.write(phrase)
|
137
|
+
|
138
|
+
OutputFactory.create(options).render_secret secret
|
156
139
|
end
|
157
140
|
end
|
158
|
-
alias_command :write, :"add-secret"
|
159
|
-
alias_command :add, :"add-secret"
|
160
|
-
alias_command :insert, :"add-secret"
|
161
|
-
alias_command :set, :"add-secret"
|
162
141
|
|
163
|
-
command :
|
164
|
-
c.syntax = 'leeloo
|
165
|
-
c.description = "
|
142
|
+
command :translate do |c|
|
143
|
+
c.syntax = 'leeloo translate'
|
144
|
+
c.description = "translate stdin by replacing key ${my/secret} by the current value"
|
166
145
|
c.option '--keystore STRING', String, 'a selected keystore'
|
167
|
-
c.option '--clipboard', nil, 'copy to clipboard'
|
168
|
-
c.option '--to /path/to/file', String, 'for binary file'
|
169
146
|
|
170
147
|
c.action do |args, options|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
148
|
+
keystore = @preferences.keystore(options.keystore)
|
149
|
+
text = STDIN.read
|
150
|
+
OutputFactory.create(options).render_translate keystore, text
|
151
|
+
end
|
152
|
+
end
|
176
153
|
|
177
|
-
|
178
|
-
|
154
|
+
command :remove do |c|
|
155
|
+
c.syntax = 'leeloo delete <name>'
|
156
|
+
c.description = "Delete a secret from a keystore"
|
157
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
179
158
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
else
|
184
|
-
say secret unless options.clipboard
|
185
|
-
Clipboard.copy secret if options.clipboard
|
186
|
-
end
|
159
|
+
c.action do |args, options|
|
160
|
+
abort "name is missing" unless args.length == 1
|
161
|
+
name = args.first
|
187
162
|
|
188
|
-
|
189
|
-
|
190
|
-
|
163
|
+
keystore = @preferences.keystore(options.keystore)
|
164
|
+
secret = keystore.secret_from_name(name)
|
165
|
+
secret.erase
|
191
166
|
end
|
192
|
-
alias_command :read, :"read-secret"
|
193
|
-
alias_command :get, :"read-secret"
|
194
167
|
end
|
195
168
|
|
196
|
-
command :
|
197
|
-
c.syntax = 'leeloo
|
198
|
-
c.description = "
|
169
|
+
command :sync do |c|
|
170
|
+
c.syntax = 'leeloo sync'
|
171
|
+
c.description = "Synchronize a keystore"
|
199
172
|
c.option '--keystore STRING', String, 'a selected keystore'
|
200
173
|
|
201
174
|
c.action do |args, options|
|
202
|
-
|
203
|
-
|
175
|
+
keystore = @preferences.keystore(options.keystore)
|
176
|
+
keystore.sync
|
177
|
+
end
|
178
|
+
end
|
204
179
|
|
205
|
-
|
206
|
-
|
180
|
+
command :init do |c|
|
181
|
+
c.syntax = 'leeloo init'
|
182
|
+
c.description = "Initialize a keystore"
|
183
|
+
c.option '--keystore STRING', String, 'a selected keystore'
|
207
184
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
rescue
|
212
|
-
abort "unable to find #{name}"
|
213
|
-
end
|
185
|
+
c.action do |args, options|
|
186
|
+
keystore = @preferences.keystore(options.keystore)
|
187
|
+
keystore.init
|
214
188
|
end
|
215
|
-
alias_command :remove, :"remove-secret"
|
216
|
-
alias_command :delete, :"remove-secret"
|
217
|
-
alias_command :erase, :"remove-secret"
|
218
189
|
end
|
190
|
+
|
219
191
|
end
|
220
192
|
end
|
221
193
|
end
|
data/lib/leeloo/keystore.rb
CHANGED
@@ -1,40 +1,163 @@
|
|
1
|
-
require 'fileutils'
|
2
1
|
require 'gpgme'
|
2
|
+
require 'fileutils'
|
3
3
|
require 'git'
|
4
4
|
|
5
5
|
module Leeloo
|
6
|
+
|
7
|
+
class KeystoreFactory
|
8
|
+
def self.create keystore
|
9
|
+
keystore_created = nil
|
10
|
+
case keystore["cypher"]
|
11
|
+
when "gpg"
|
12
|
+
keystore_created = GpgPrivateLocalFileSystemKeystore.new keystore["name"], keystore["path"]
|
13
|
+
else
|
14
|
+
keystore_created = PrivateLocalFileSystemKeystore.new keystore["name"], keystore["path"]
|
15
|
+
end
|
16
|
+
|
17
|
+
case keystore["vc"]
|
18
|
+
when "git"
|
19
|
+
GitKeystoreDecorator.new keystore_created
|
20
|
+
else
|
21
|
+
keystore_created
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
6
26
|
class Keystore
|
7
27
|
|
8
|
-
|
9
|
-
|
28
|
+
attr_reader :name
|
29
|
+
|
30
|
+
def initialize name
|
31
|
+
@name = name
|
10
32
|
end
|
11
33
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
FileUtils.mkdir_p "#{path}/keys/"
|
34
|
+
def secrets
|
35
|
+
# returns the secrets list
|
36
|
+
end
|
16
37
|
|
17
|
-
|
18
|
-
|
19
|
-
|
38
|
+
def secret_of path
|
39
|
+
# returns a secret object
|
40
|
+
end
|
20
41
|
|
21
|
-
|
22
|
-
|
23
|
-
g.commit "keystore #{path} added"
|
42
|
+
def secret_from_name name
|
43
|
+
# returns a secret object
|
24
44
|
end
|
25
45
|
|
26
|
-
def
|
27
|
-
|
28
|
-
g.add_remote 'origin', remote
|
46
|
+
def sync
|
47
|
+
# synchronizes the keystore
|
29
48
|
end
|
30
49
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
50
|
+
def init
|
51
|
+
# initialize the keystore
|
52
|
+
end
|
53
|
+
|
54
|
+
def == keystore
|
55
|
+
self.name == keystore.name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class PrivateLocalFileSystemKeystore < Keystore
|
60
|
+
|
61
|
+
attr_reader :path
|
62
|
+
|
63
|
+
def initialize name, path
|
64
|
+
super name
|
65
|
+
@path = path
|
66
|
+
FileUtils.mkdir_p "#{@path}/secrets"
|
67
|
+
end
|
68
|
+
|
69
|
+
def secrets
|
70
|
+
find_secrets "#{@path}/secrets"
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_secrets path
|
74
|
+
elements = []
|
75
|
+
Dir.glob("#{path}/**") do |element|
|
76
|
+
elements << secret_of(element) unless Dir.exist? element
|
77
|
+
elements << find_secrets(element) if Dir.exist? element
|
36
78
|
end
|
37
|
-
return
|
79
|
+
return elements.flatten
|
80
|
+
end
|
81
|
+
|
82
|
+
def == keystore
|
83
|
+
self.name == keystore.name && self.path == keystore.path
|
84
|
+
end
|
85
|
+
|
86
|
+
def secret_of path
|
87
|
+
name = path.gsub("#{@path}/secrets/", "")
|
88
|
+
LocalFileSystemSecret.new path, name
|
89
|
+
end
|
90
|
+
|
91
|
+
def secret_from_name name
|
92
|
+
secret_of "#{path}/secrets/#{name}"
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
class GpgPrivateLocalFileSystemKeystore < PrivateLocalFileSystemKeystore
|
98
|
+
|
99
|
+
def initialize name, path
|
100
|
+
super name, path
|
101
|
+
FileUtils.mkdir_p "#{@path}/keys"
|
102
|
+
|
103
|
+
@recipients = []
|
104
|
+
Dir.glob("#{path}/keys/*") { |key| @recipients << File.basename(key) }
|
105
|
+
@recipients.each { |key| GPGME::Key.import(File.open("#{path}/keys/#{key}")) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def init
|
109
|
+
super
|
110
|
+
GPGME::Key.find(:public, nil, ).each { |key| key.export(:output => File.open("#{path}/keys/#{key.uids.first.email}", "w+")) }
|
111
|
+
end
|
112
|
+
|
113
|
+
def secret_of path
|
114
|
+
name = path.gsub("#{@path}/secrets/", "").gsub(".gpg", "")
|
115
|
+
GpgLocalFileSystemSecret.new path, name, @recipients
|
116
|
+
end
|
117
|
+
|
118
|
+
def secret_from_name name
|
119
|
+
secret_of "#{path}/secrets/#{name}.gpg"
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class GitKeystoreDecorator < Keystore
|
125
|
+
def initialize keystore
|
126
|
+
@keystore = keystore
|
127
|
+
Git.init @keystore.path
|
128
|
+
@git = Git.open keystore.path
|
129
|
+
end
|
130
|
+
|
131
|
+
def secret_of element
|
132
|
+
GitSecretDecorator.new(@git, element)
|
133
|
+
end
|
134
|
+
|
135
|
+
def secret_from_name element
|
136
|
+
secret_of @keystore.secret_from_name(element)
|
137
|
+
end
|
138
|
+
|
139
|
+
def secrets
|
140
|
+
@keystore.secrets
|
141
|
+
end
|
142
|
+
|
143
|
+
def name
|
144
|
+
@keystore.name
|
145
|
+
end
|
146
|
+
|
147
|
+
def path
|
148
|
+
@keystore.path
|
149
|
+
end
|
150
|
+
|
151
|
+
def sync
|
152
|
+
@git.pull
|
153
|
+
@keystore.sync
|
154
|
+
@git.push
|
155
|
+
end
|
156
|
+
|
157
|
+
def init
|
158
|
+
@keystore.init
|
159
|
+
@git.add
|
160
|
+
@git.commit "keystore #{@keystore.name} added"
|
38
161
|
end
|
39
162
|
|
40
163
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'clipboard'
|
2
|
+
require 'tty-table'
|
3
|
+
require 'tty-tree'
|
4
|
+
|
5
|
+
module Leeloo
|
6
|
+
|
7
|
+
class Output
|
8
|
+
|
9
|
+
def render_preferences preferences
|
10
|
+
end
|
11
|
+
|
12
|
+
def render_secrets secrets
|
13
|
+
end
|
14
|
+
|
15
|
+
def render_secret secret
|
16
|
+
end
|
17
|
+
|
18
|
+
def render_translate keystore, text
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Ascii < Output
|
23
|
+
|
24
|
+
def render_preferences preferences
|
25
|
+
preferences.keystores.each { |keystore| puts keystore.name }
|
26
|
+
end
|
27
|
+
|
28
|
+
def render_secrets secrets
|
29
|
+
secrets.sort_by(&:name).each {|secret| puts secret.name}
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_secret secret
|
33
|
+
begin
|
34
|
+
puts secret.read
|
35
|
+
rescue => exception
|
36
|
+
puts "#{secret.name} doesn't exist"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def render_translate keystore, text
|
41
|
+
text.scan(/\$\{.*\}/).each do |secret|
|
42
|
+
begin
|
43
|
+
text.gsub! secret, (keystore.secret_from_name(secret[2..-2])).read.to_s.strip
|
44
|
+
rescue => exception
|
45
|
+
# silent
|
46
|
+
end
|
47
|
+
end
|
48
|
+
puts text
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Terminal < Ascii
|
53
|
+
|
54
|
+
def render_preferences preferences
|
55
|
+
rows = []
|
56
|
+
default_keystore = preferences.default
|
57
|
+
preferences.keystores.each do |keystore|
|
58
|
+
is_default = '*' if keystore.name == default_keystore
|
59
|
+
rows << [keystore.name, keystore.path, is_default ]
|
60
|
+
end
|
61
|
+
puts TTY::Table.new(header: ['Name', 'Path', 'Default'], rows: rows).render(:ascii)
|
62
|
+
end
|
63
|
+
|
64
|
+
def render_secrets secrets
|
65
|
+
hash = {:secrets => []}
|
66
|
+
secrets.sort_by(&:name).each { |secret| sort(hash[:secrets], secret.name) }
|
67
|
+
puts TTY::Tree.new(hash).render
|
68
|
+
end
|
69
|
+
|
70
|
+
def sort array, element
|
71
|
+
if element
|
72
|
+
e = element.split("/", 2)
|
73
|
+
if e.length > 1
|
74
|
+
found = false
|
75
|
+
array.each do |a|
|
76
|
+
if a.is_a? Hash
|
77
|
+
if a[e.first]
|
78
|
+
found = true
|
79
|
+
sort(a[e.first], e.last)
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
unless found
|
86
|
+
array << { e.first => sort([], e.last) }
|
87
|
+
end
|
88
|
+
else
|
89
|
+
array << e.last
|
90
|
+
end
|
91
|
+
end
|
92
|
+
array
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class ClipboardOutputDecorator < Output
|
97
|
+
|
98
|
+
def initialize output
|
99
|
+
@output = output
|
100
|
+
end
|
101
|
+
|
102
|
+
def render_preferences preferences
|
103
|
+
@output.render_preferences preferences
|
104
|
+
end
|
105
|
+
|
106
|
+
def render_secrets secrets
|
107
|
+
@output.render_secrets secrets
|
108
|
+
end
|
109
|
+
|
110
|
+
def render_translate keystore, text
|
111
|
+
@output.render_translate keystore, text
|
112
|
+
end
|
113
|
+
|
114
|
+
def render_secret secret
|
115
|
+
|
116
|
+
Signal.trap("INT") do
|
117
|
+
Clipboard.clear
|
118
|
+
abort "ciao"
|
119
|
+
end
|
120
|
+
|
121
|
+
Clipboard.copy secret.read
|
122
|
+
wait = Thread.new do
|
123
|
+
puts "cleaning in 30s"
|
124
|
+
30.times {
|
125
|
+
print "."
|
126
|
+
sleep 1
|
127
|
+
}
|
128
|
+
end
|
129
|
+
wait.join
|
130
|
+
Clipboard.clear
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Leeloo
|
4
|
+
class Preferences
|
5
|
+
|
6
|
+
attr_reader :default
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@keystores = []
|
10
|
+
@default = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def load
|
14
|
+
# this method loads all preferences
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_default_keystore name
|
19
|
+
@default = name
|
20
|
+
end
|
21
|
+
|
22
|
+
def keystore name=nil
|
23
|
+
keystores.find { |k| k.name == (name||@default) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def keystores
|
27
|
+
@keystores.map { |k| KeystoreFactory::create k }
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_keystore keystore
|
31
|
+
unless @keystores.include? keystore
|
32
|
+
@keystores << keystore
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class PrivateLocalFileSystemPreferences < Preferences
|
38
|
+
|
39
|
+
DEFAULT_PATH = "#{Dir.home}/.leeloo"
|
40
|
+
|
41
|
+
def load(path=DEFAULT_PATH)
|
42
|
+
@path = path
|
43
|
+
|
44
|
+
if File.exist? "#{path}/keystores"
|
45
|
+
@keystores = YAML.load_file "#{path}/keystores"
|
46
|
+
end
|
47
|
+
|
48
|
+
if File.exist? "#{path}/config"
|
49
|
+
config = YAML.load_file "#{path}/config"
|
50
|
+
set_default_keystore config["keystore"]
|
51
|
+
else
|
52
|
+
default_keystore = {
|
53
|
+
'name' => "private",
|
54
|
+
'path' => "#{path}/private",
|
55
|
+
'cypher' => "gpg",
|
56
|
+
'vc' => "git"
|
57
|
+
}
|
58
|
+
add_keystore default_keystore
|
59
|
+
set_default_keystore "private"
|
60
|
+
keystore_of("private").init
|
61
|
+
end
|
62
|
+
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def keystore_of keystore_name
|
67
|
+
keystore = @keystores.find { |keystore| keystore["name"] == keystore_name }
|
68
|
+
KeystoreFactory::create keystore
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_default_keystore name
|
72
|
+
super name
|
73
|
+
config = {
|
74
|
+
"keystore" => name
|
75
|
+
}
|
76
|
+
File.write("#{@path}/config", config.to_yaml)
|
77
|
+
end
|
78
|
+
|
79
|
+
def add_keystore keystore
|
80
|
+
super keystore
|
81
|
+
FileUtils.mkdir_p keystore["path"]
|
82
|
+
File.write("#{@path}/keystores", @keystores.to_yaml)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/lib/leeloo/secret.rb
CHANGED
@@ -1,84 +1,105 @@
|
|
1
1
|
require 'gpgme'
|
2
|
-
require 'tty-tree'
|
3
|
-
require 'git'
|
4
2
|
require 'fileutils'
|
5
3
|
|
6
4
|
module Leeloo
|
7
|
-
|
8
5
|
class Secret
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
.reject { |path| File.directory? path }
|
15
|
-
.each { |secret| puts secret.gsub(/#{keystore}\/secrets\//, '').gsub(/\.gpg/, '') }
|
16
|
-
else
|
17
|
-
puts TTY::Tree.new("#{keystore}/secrets").render.gsub(/\.gpg/, '')
|
18
|
-
end
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
def initialize name
|
10
|
+
@name = name
|
19
11
|
end
|
20
12
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
13
|
+
def == secret
|
14
|
+
@name == secret.name
|
15
|
+
end
|
24
16
|
|
25
|
-
|
17
|
+
def read
|
18
|
+
# returns the secret
|
19
|
+
end
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
:recipients => recipients
|
21
|
+
def write phrase
|
22
|
+
# write the secret
|
23
|
+
end
|
31
24
|
|
32
|
-
|
33
|
-
|
34
|
-
g.commit "secret #{name} added"
|
25
|
+
def erase
|
26
|
+
# erase the secret
|
35
27
|
end
|
36
28
|
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
end
|
30
|
+
|
31
|
+
class LocalFileSystemSecret < Secret
|
32
|
+
|
33
|
+
attr_reader :pathname
|
34
|
+
|
35
|
+
def initialize pathname, name
|
36
|
+
super name
|
37
|
+
@pathname = pathname
|
40
38
|
end
|
41
39
|
|
42
|
-
def
|
43
|
-
|
44
|
-
g.remove "#{keystore}/secrets/#{name}.gpg"
|
45
|
-
g.commit "secret #{name} removed"
|
40
|
+
def read
|
41
|
+
File.read @pathname
|
46
42
|
end
|
47
43
|
|
48
|
-
def
|
44
|
+
def write phrase
|
45
|
+
FileUtils.mkdir_p File.dirname @pathname
|
46
|
+
File.write @pathname, phrase
|
47
|
+
end
|
49
48
|
|
50
|
-
|
49
|
+
def erase
|
50
|
+
File.delete @pathname
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
Dir.foreach("#{keystore}/keys") do |key|
|
54
|
-
unless File.directory? key
|
55
|
-
recipients << File.basename(key, ".*")
|
56
|
-
GPGME::Key.import(File.open("#{keystore}/keys/#{key}"))
|
57
|
-
end
|
58
|
-
end
|
53
|
+
end
|
59
54
|
|
60
|
-
|
61
|
-
find_secrets("#{keystore}/secrets").each do |secret|
|
62
|
-
say "."
|
63
|
-
decrypted = crypto.decrypt File.open(secret)
|
64
|
-
crypto.encrypt decrypted,
|
65
|
-
:output => File.open(secret,"w+"),
|
66
|
-
:recipients => recipients
|
67
|
-
g.add secret
|
68
|
-
end
|
55
|
+
class GpgLocalFileSystemSecret < LocalFileSystemSecret
|
69
56
|
|
70
|
-
|
71
|
-
|
57
|
+
def initialize pathname, name, recipients
|
58
|
+
super pathname, name
|
59
|
+
@recipients = recipients
|
60
|
+
@crypto = GPGME::Crypto.new :always_trust => true
|
61
|
+
end
|
62
|
+
|
63
|
+
def read
|
64
|
+
@crypto.decrypt File.open(@pathname)
|
72
65
|
end
|
73
66
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
80
|
-
return elements.flatten
|
67
|
+
def write phrase
|
68
|
+
FileUtils.mkdir_p File.dirname @pathname
|
69
|
+
@crypto.encrypt phrase,
|
70
|
+
:output => File.open(@pathname,"w+"),
|
71
|
+
:recipients => @recipients
|
81
72
|
end
|
82
73
|
|
83
74
|
end
|
75
|
+
|
76
|
+
class GitSecretDecorator < Secret
|
77
|
+
|
78
|
+
def initialize git, secret
|
79
|
+
@git = git
|
80
|
+
@secret = secret
|
81
|
+
end
|
82
|
+
|
83
|
+
def name
|
84
|
+
@secret.name
|
85
|
+
end
|
86
|
+
|
87
|
+
def read
|
88
|
+
@secret.read
|
89
|
+
end
|
90
|
+
|
91
|
+
def write phrase
|
92
|
+
@secret.write phrase
|
93
|
+
@git.add @secret.pathname
|
94
|
+
@git.commit "secret #{@secret.name} added"
|
95
|
+
end
|
96
|
+
|
97
|
+
def erase
|
98
|
+
@secret.erase
|
99
|
+
@git.remove @secret.pathname
|
100
|
+
@git.commit "secret #{@secret.name} removed"
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
84
105
|
end
|
data/lib/leeloo/version.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.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sylvek
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: commander
|
@@ -53,89 +53,75 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.5'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: tty-table
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '0.10'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '0.10'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: tty-tree
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0.
|
75
|
+
version: '0.3'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0.
|
82
|
+
version: '0.3'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: clipboard
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '1.
|
89
|
+
version: '1.3'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '1.
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: ffi
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '1.9'
|
104
|
-
type: :runtime
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '1.9'
|
96
|
+
version: '1.3'
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
98
|
name: bundler
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
114
100
|
requirements:
|
115
101
|
- - "~>"
|
116
102
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
103
|
+
version: '2.0'
|
118
104
|
type: :development
|
119
105
|
prerelease: false
|
120
106
|
version_requirements: !ruby/object:Gem::Requirement
|
121
107
|
requirements:
|
122
108
|
- - "~>"
|
123
109
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
110
|
+
version: '2.0'
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
112
|
name: rake
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
128
114
|
requirements:
|
129
115
|
- - "~>"
|
130
116
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
117
|
+
version: '12.0'
|
132
118
|
type: :development
|
133
119
|
prerelease: false
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
135
121
|
requirements:
|
136
122
|
- - "~>"
|
137
123
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
124
|
+
version: '12.0'
|
139
125
|
- !ruby/object:Gem::Dependency
|
140
126
|
name: rspec
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,8 +147,9 @@ files:
|
|
161
147
|
- exe/leeloo
|
162
148
|
- lib/leeloo.rb
|
163
149
|
- lib/leeloo/command.rb
|
164
|
-
- lib/leeloo/config.rb
|
165
150
|
- lib/leeloo/keystore.rb
|
151
|
+
- lib/leeloo/output.rb
|
152
|
+
- lib/leeloo/preferences.rb
|
166
153
|
- lib/leeloo/secret.rb
|
167
154
|
- lib/leeloo/version.rb
|
168
155
|
homepage: https://github.com/sylvek
|
@@ -184,8 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
171
|
- !ruby/object:Gem::Version
|
185
172
|
version: '0'
|
186
173
|
requirements: []
|
187
|
-
|
188
|
-
rubygems_version: 2.7.7
|
174
|
+
rubygems_version: 3.0.4
|
189
175
|
signing_key:
|
190
176
|
specification_version: 4
|
191
177
|
summary: The easiest way to share securely your secrets
|
data/lib/leeloo/config.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
require 'terminal-table'
|
3
|
-
|
4
|
-
module Leeloo
|
5
|
-
class Config
|
6
|
-
|
7
|
-
PATH = "#{Dir.home}/.leeloo"
|
8
|
-
|
9
|
-
@@keystores = []
|
10
|
-
|
11
|
-
@@default = { "keystore" => "private" }
|
12
|
-
|
13
|
-
def self.default
|
14
|
-
@@default
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.init
|
18
|
-
Keystore::add_keystore "private", "#{PATH}/private"
|
19
|
-
Config::add_keystore "private", "#{PATH}/private"
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.list_keystores(ascii)
|
23
|
-
if ascii
|
24
|
-
@@keystores.each { |keystore| puts keystore['name'] }
|
25
|
-
else
|
26
|
-
rows = []
|
27
|
-
@@keystores.each do |keystore|
|
28
|
-
is_default = '*' if keystore['name'] == @@default['keystore']
|
29
|
-
rows << [keystore['name'], keystore['path'], is_default ]
|
30
|
-
end
|
31
|
-
say Terminal::Table.new :headings => ['Name', 'Path', 'Default'], :rows => rows
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.load
|
36
|
-
FileUtils.mkdir_p PATH
|
37
|
-
if File.exist? "#{PATH}/keystores"
|
38
|
-
@@keystores = YAML.load_file "#{PATH}/keystores"
|
39
|
-
end
|
40
|
-
if File.exist? "#{PATH}/config"
|
41
|
-
@@default = YAML.load_file "#{PATH}/config"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.add_keystore name, path
|
46
|
-
keystore = { 'name' => name, 'path' => path}
|
47
|
-
unless @@keystores.include? keystore
|
48
|
-
@@keystores << keystore
|
49
|
-
File.write("#{PATH}/keystores", @@keystores.to_yaml)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.get_keystore name
|
54
|
-
@@keystores.each do |keystore|
|
55
|
-
return keystore['path'] if keystore['name'] == name
|
56
|
-
end
|
57
|
-
|
58
|
-
raise "keystore not found"
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
62
|
-
end
|