leeloo 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f395de66b0d5e6550921e66339d5dcd2df759bc38662d09b4bc650a510cb4dd8
4
- data.tar.gz: 9b311918a29972cd5bca9dc0c856bf37e8bacf59db8b6b954ff1ebd5d4790eeb
3
+ metadata.gz: 45be4ecf01ce08cd7317d3c6f9d8a612d999b972efdcbfe322a964a06f77a303
4
+ data.tar.gz: bd3a8767c2c1d70cdc21856b180cb7f3651c9777471f70a35af810130650d4ef
5
5
  SHA512:
6
- metadata.gz: 36703d60d92d086540d33d16171d1aa290595f4d408db707ac22fdeb11077f8d393ffd861c25aaafa331441586b27b98a8b4f16ac33f61da654c6e27b992ff3f
7
- data.tar.gz: dde8201181fbfb863f091accb97bd2fea285e1cdb7bc75c7ec94ee2c9ce4c1783b8b0c55d593b55b5446506c3826862f32d989657a64873205d4052dbba2ea1b
6
+ metadata.gz: 1ec00409e8ac173872aac71537aad50d40f93a24634b1b101b6f46b59d658696c2480d6e5157fd7cd1d864acc4cc6ec5e421eed6ff74f560c4e25f57ecd7bd27
7
+ data.tar.gz: 1d259f7fc29192bcf6d1cd3a7f7f92d8e9244d79e11741f2fce8f82f8aab0d7cfd5c96ad120c16aa018b8c69540e937e6986a8f18a297a7d137d748e5e19511d
@@ -1,9 +1,11 @@
1
1
  require 'leeloo/version'
2
+ require 'leeloo/controller'
2
3
  require 'leeloo/command'
3
4
  require 'leeloo/preferences'
4
5
  require 'leeloo/keystore'
5
6
  require 'leeloo/secret'
6
7
  require 'leeloo/output'
8
+ require 'leeloo/server'
7
9
 
8
10
  module Leeloo
9
11
  def self.start
@@ -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
@@ -52,8 +32,7 @@ module Leeloo
52
32
  c.option '--keystore STRING', String, 'a selected keystore'
53
33
 
54
34
  c.action do |args, options|
55
- keystore = @preferences.keystore(options.keystore)
56
- OutputFactory.create(options).render_secrets keystore.secrets
35
+ SecretsController.new(options).display
57
36
  end
58
37
  end
59
38
 
@@ -66,10 +45,9 @@ module Leeloo
66
45
  c.action do |args, options|
67
46
  abort "name is missing" unless args.length == 1
68
47
  name = args.first
69
-
70
- keystore = @preferences.keystore(options.keystore)
71
- secrets = keystore.secrets.select { |secret| secret.name.downcase.include? name.downcase } || []
72
- OutputFactory.create(options).render_secrets secrets
48
+ ctl = SecretsController.new(options)
49
+ ctl.search(name)
50
+ ctl.display
73
51
  end
74
52
  end
75
53
 
@@ -79,7 +57,7 @@ module Leeloo
79
57
  c.option '--ascii', nil, 'display secrets without unicode tree'
80
58
 
81
59
  c.action do |args, options|
82
- OutputFactory.create(options).render_preferences @preferences
60
+ KeystoreController.new(options).display
83
61
  end
84
62
  end
85
63
 
@@ -87,10 +65,12 @@ module Leeloo
87
65
  c.syntax = 'leeloo keystore remove <name>'
88
66
  c.description = "remove a keystore (path/to/keystore is not destroyed)"
89
67
 
90
- c.action do |args, options|
68
+ c.action do |args, options|args
91
69
  abort "name is missing" unless args.length == 1
92
- @preferences.remove_keystore args.first
93
- OutputFactory.create(options).render_preferences @preferences
70
+ name = args.first
71
+ ctl = KeystoreController.new(options)
72
+ ctl.remove(name)
73
+ ctl.display
94
74
  end
95
75
  end
96
76
 
@@ -100,10 +80,11 @@ module Leeloo
100
80
 
101
81
  c.action do |args, options|
102
82
  abort "name or path is missing" unless args.length == 2
103
-
104
- @preferences.add_keystore({"name" => args.first, "path" => args.last, "cypher" => "gpg", "vc" => "git"})
105
- @preferences.keystore(args.first).init
106
- OutputFactory.create(options).render_preferences @preferences
83
+ name = args.first
84
+ path = args.last
85
+ ctl = KeystoreController.new(options)
86
+ ctl.add(name, path)
87
+ ctl.display
107
88
  end
108
89
  end
109
90
 
@@ -113,9 +94,10 @@ module Leeloo
113
94
 
114
95
  c.action do |args, options|
115
96
  abort "name is missing" unless args.length == 1
116
-
117
- @preferences.set_default_keystore args.first
118
- OutputFactory.create(options).render_preferences @preferences
97
+ name = args.first
98
+ ctl = KeystoreController.new(options)
99
+ ctl.set_default(name)
100
+ ctl.display
119
101
  end
120
102
  end
121
103
 
@@ -129,10 +111,9 @@ module Leeloo
129
111
  c.action do |args, options|
130
112
  abort "name is missing" unless args.length == 1
131
113
  name = args.first
132
-
133
- keystore = @preferences.keystore(options.keystore)
134
- secret = keystore.secret_from_name(name)
135
- OutputFactory.create(options).render_secret secret
114
+ ctl = SecretController.new(options)
115
+ ctl.read(name)
116
+ ctl.display
136
117
  end
137
118
  end
138
119
 
@@ -147,22 +128,9 @@ module Leeloo
147
128
  c.action do |args, options|
148
129
  abort "name is missing" unless args.length == 1
149
130
  name = args.first
150
- phrase = nil
151
-
152
- phrase = STDIN.read if options.stdin
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
131
+ ctl = SecretController.new(options)
132
+ ctl.write(name)
133
+ ctl.display
166
134
  end
167
135
  end
168
136
 
@@ -172,9 +140,9 @@ module Leeloo
172
140
  c.option '--keystore STRING', String, 'a selected keystore'
173
141
 
174
142
  c.action do |args, options|
175
- keystore = @preferences.keystore(options.keystore)
176
- text = STDIN.read
177
- OutputFactory.create(options).render_translate keystore, text
143
+ ctl = TranslateController.new(options)
144
+ ctl.translate
145
+ ctl.display
178
146
  end
179
147
  end
180
148
 
@@ -186,10 +154,9 @@ module Leeloo
186
154
  c.action do |args, options|
187
155
  abort "name is missing" unless args.length == 1
188
156
  name = args.first
189
-
190
- keystore = @preferences.keystore(options.keystore)
191
- secret = keystore.secret_from_name(name)
192
- secret.erase
157
+ ctl = SecretController.new(options)
158
+ ctl.remove(name)
159
+ ctl.display
193
160
  end
194
161
  end
195
162
 
@@ -199,8 +166,9 @@ module Leeloo
199
166
  c.option '--keystore STRING', String, 'a selected keystore'
200
167
 
201
168
  c.action do |args, options|
202
- keystore = @preferences.keystore(options.keystore)
203
- keystore.sync
169
+ ctl = KeystoreController.new(options)
170
+ ctl.sync
171
+ ctl.display
204
172
  end
205
173
  end
206
174
 
@@ -210,8 +178,48 @@ module Leeloo
210
178
  c.option '--keystore STRING', String, 'a selected keystore'
211
179
 
212
180
  c.action do |args, options|
213
- keystore = @preferences.keystore(options.keystore)
214
- keystore.init
181
+ ctl = KeystoreController.new(options)
182
+ ctl.init
183
+ ctl.display
184
+ end
185
+ end
186
+
187
+ command :share do |c|
188
+ c.syntax = 'leeloo share <name>'
189
+ c.description = "share a secret with someone"
190
+ c.option '--keystore STRING', String, 'a selected keystore'
191
+
192
+ c.action do |args, options|
193
+ abort "name is missing" unless args.length == 1
194
+ name = args.first
195
+ ctl = ShareController.new(options)
196
+ ctl.token(name)
197
+ ctl.display
198
+ ctl.start_server
199
+ end
200
+ end
201
+
202
+ command :token do |c|
203
+ c.syntax = 'leeloo token <name>'
204
+ c.description = "generate an access token for a given secret"
205
+ c.option '--keystore STRING', String, 'a selected keystore'
206
+
207
+ c.action do |args, options|
208
+ abort "name is missing" unless args.length == 1
209
+ name = args.first
210
+ ctl = ShareController.new(options)
211
+ ctl.token(name)
212
+ ctl.display
213
+ end
214
+ end
215
+
216
+ command :server do |c|
217
+ c.syntax = 'leeloo server'
218
+ c.description = "start a server access token"
219
+
220
+ c.action do |args, options|
221
+ ctl = ShareController.new(options)
222
+ ctl.start_server
215
223
  end
216
224
  end
217
225
 
@@ -0,0 +1,122 @@
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 = @preferences.keystore(@options.keystore).secrets
36
+ end
37
+ def search name
38
+ @secrets = @secrets.select { |secret| secret.name.downcase.include? name.downcase } || []
39
+ end
40
+ def display
41
+ @output.render_secrets @secrets
42
+ end
43
+ end
44
+
45
+ class SecretController < PrivateLocalFileSystemController
46
+ def read name
47
+ @secret = @keystore.secret_from_name(name)
48
+ end
49
+ def write name
50
+ phrase = nil
51
+
52
+ phrase = STDIN.read if @options.stdin
53
+ phrase = SecureRandom.base64(32).truncate(@options.generate.to_i) if @options.generate
54
+
55
+ unless phrase
56
+ phrase = password "secret"
57
+ confirm = password "confirm it"
58
+ abort "not the same secret" unless phrase == confirm
59
+ end
60
+
61
+ @secret = @keystore.secret_from_name(name)
62
+ @secret.write(phrase)
63
+ end
64
+ def remove name
65
+ @secret = @keystore.secret_from_name(name)
66
+ @secret.erase
67
+ end
68
+ def display
69
+ @output.render_secret @secret
70
+ end
71
+ end
72
+
73
+ class TranslateController < PrivateLocalFileSystemController
74
+ def translate
75
+ @text = STDIN.read
76
+ @text.scan(/\$\{.*\}/).each do |secret|
77
+ begin
78
+ @text.gsub! secret, (@keystore.secret_from_name(secret[2..-2])).read.to_s.strip
79
+ rescue => exception
80
+ # silent
81
+ end
82
+ end
83
+ end
84
+ def display
85
+ @output.render_text @text
86
+ end
87
+ end
88
+
89
+ class KeystoreController < PrivateLocalFileSystemController
90
+ def add name, path
91
+ @preferences.add_keystore({"name" => name, "path" => path, "cypher" => "gpg", "vc" => "git"})
92
+ @preferences.keystore(name).init
93
+ end
94
+ def remove name
95
+ @preferences.remove_keystore name
96
+ end
97
+ def set_default name
98
+ @preferences.set_default_keystore name
99
+ end
100
+ def sync
101
+ @keystore.sync
102
+ end
103
+ def init
104
+ @keystore.init
105
+ end
106
+ def display
107
+ @output.render_preferences @preferences
108
+ end
109
+ end
110
+
111
+ class ShareController < PrivateLocalFileSystemController
112
+ def token name
113
+ @footprint = @keystore.footprint_of(name)
114
+ end
115
+ def start_server
116
+ Server.new.start @preferences
117
+ end
118
+ def display
119
+ @output.render_footprint @footprint
120
+ end
121
+ end
122
+ end
@@ -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
@@ -92,6 +101,19 @@ module Leeloo
92
101
  secret_of "#{path}/secrets/#{name}"
93
102
  end
94
103
 
104
+ def footprint_of name
105
+ secret = secret_from_name name
106
+ { "footprint" => secret.footprint, "keystore" => self.name, "secret" => secret.name }
107
+ end
108
+
109
+ def secret_from_footprint footprint
110
+ secret = secret_from_name footprint["secret"]
111
+ unless secret.footprint == footprint["footprint"]
112
+ raise "footprint is not valid"
113
+ end
114
+ secret
115
+ end
116
+
95
117
  end
96
118
 
97
119
  class GpgPrivateLocalFileSystemKeystore < PrivateLocalFileSystemKeystore
@@ -119,6 +141,20 @@ module Leeloo
119
141
  secret_of "#{path}/secrets/#{name}.gpg"
120
142
  end
121
143
 
144
+ def footprint_of name
145
+ footprint = super name
146
+ footprint["sign"] = Base64.strict_encode64 GPGME::Crypto.new.sign(footprint["footprint"]).to_s
147
+ footprint
148
+ end
149
+
150
+ def secret_from_footprint footprint
151
+ data = GPGME::Crypto.new.verify(Base64.strict_decode64 footprint["sign"]) { |signature| signature.valid? }
152
+ if data.read == footprint["footprint"]
153
+ super footprint
154
+ else
155
+ raise "signature is not valid"
156
+ end
157
+ end
122
158
  end
123
159
 
124
160
  class GitKeystoreDecorator < Keystore
@@ -148,6 +184,14 @@ module Leeloo
148
184
  @keystore.path
149
185
  end
150
186
 
187
+ def footprint_of element
188
+ @keystore.footprint_of element
189
+ end
190
+
191
+ def secret_from_footprint footprint
192
+ @keystore.secret_from_footprint footprint
193
+ end
194
+
151
195
  def sync
152
196
  @git.pull
153
197
  @keystore.sync
@@ -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,13 @@ module Leeloo
15
18
  def render_secret secret
16
19
  end
17
20
 
18
- def render_translate keystore, text
21
+ def render_text text
22
+ end
23
+
24
+ def render_footprint footprint
25
+ end
26
+
27
+ def render_share footprint
19
28
  end
20
29
  end
21
30
 
@@ -37,16 +46,14 @@ module Leeloo
37
46
  end
38
47
  end
39
48
 
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
49
+ def render_text text
48
50
  puts text
49
51
  end
52
+
53
+ def render_footprint footprint
54
+ puts "token:"
55
+ puts Base64.strict_encode64 footprint.to_json
56
+ end
50
57
  end
51
58
 
52
59
  class Terminal < Ascii
@@ -107,16 +114,16 @@ module Leeloo
107
114
  @output.render_secrets secrets
108
115
  end
109
116
 
110
- def render_translate keystore, text
111
- @output.render_translate keystore, text
117
+ def render_text text
118
+ @output.render_text text
112
119
  end
113
120
 
114
121
  def render_secret secret
115
122
 
116
123
  Signal.trap("INT") do
117
124
  Clipboard.clear
118
- abort "ciao"
119
- end
125
+ abort "cleared"
126
+ end
120
127
 
121
128
  Clipboard.copy secret.read
122
129
  wait = Thread.new do
@@ -129,6 +136,14 @@ module Leeloo
129
136
  wait.join
130
137
  Clipboard.clear
131
138
  end
139
+
140
+ def render_footprint footprint
141
+ @output.render_footprint footprint
142
+ end
143
+
144
+ def render_share footprint
145
+ @output.render_share footprint
146
+ end
132
147
  end
133
148
 
134
149
  end
@@ -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 keystore_name
74
- keystore = @keystores.find { |keystore| keystore["name"] == 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
 
@@ -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
@@ -1,4 +1,4 @@
1
1
  module Leeloo
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  DESCRIPTION = "The easiest way to share securely your secrets".freeze
4
4
  end
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sylvek
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-09-15 00:00:00.000000000 Z
11
+ date: 2019-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -147,10 +147,12 @@ files:
147
147
  - exe/leeloo
148
148
  - lib/leeloo.rb
149
149
  - lib/leeloo/command.rb
150
+ - lib/leeloo/controller.rb
150
151
  - lib/leeloo/keystore.rb
151
152
  - lib/leeloo/output.rb
152
153
  - lib/leeloo/preferences.rb
153
154
  - lib/leeloo/secret.rb
155
+ - lib/leeloo/server.rb
154
156
  - lib/leeloo/version.rb
155
157
  homepage: https://github.com/sylvek/leeloo
156
158
  licenses: