trocla 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +24 -0
- data/.rspec +1 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile +10 -9
- data/README.md +18 -2
- data/bin/trocla +42 -42
- data/lib/VERSION +1 -1
- data/lib/trocla/default_config.yaml +3 -4
- data/lib/trocla/encryptions/none.rb +0 -1
- data/lib/trocla/encryptions/ssl.rb +11 -11
- data/lib/trocla/encryptions.rb +12 -5
- data/lib/trocla/formats/bcrypt.rb +2 -2
- data/lib/trocla/formats/md5crypt.rb +2 -2
- data/lib/trocla/formats/mysql.rb +2 -2
- data/lib/trocla/formats/pgsql.rb +50 -3
- data/lib/trocla/formats/plain.rb +1 -3
- data/lib/trocla/formats/sha1.rb +1 -1
- data/lib/trocla/formats/sha256crypt.rb +2 -2
- data/lib/trocla/formats/sha512crypt.rb +2 -2
- data/lib/trocla/formats/ssha.rb +3 -3
- data/lib/trocla/formats/sshkey.rb +4 -8
- data/lib/trocla/formats/wireguard.rb +45 -0
- data/lib/trocla/formats/x509.rb +54 -46
- data/lib/trocla/formats.rb +15 -5
- data/lib/trocla/store.rb +16 -15
- data/lib/trocla/stores/memory.rb +17 -10
- data/lib/trocla/stores/moneta.rb +29 -19
- data/lib/trocla/stores/vault.rb +42 -14
- data/lib/trocla/stores.rb +3 -2
- data/lib/trocla/util.rb +28 -17
- data/lib/trocla/version.rb +7 -4
- data/lib/trocla.rb +47 -45
- data/spec/trocla/formats/pgsql_spec.rb +25 -0
- data/spec/trocla_spec.rb +3 -0
- data/trocla.gemspec +17 -13
- metadata +30 -14
- data/.travis.yml +0 -6
data/lib/trocla/formats/x509.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'openssl'
|
2
|
+
|
3
|
+
# Trocla::Formats::X509
|
2
4
|
class Trocla::Formats::X509 < Trocla::Formats::Base
|
3
5
|
expensive true
|
4
6
|
def format(plain_password,options={})
|
5
|
-
|
6
7
|
if plain_password.match(/-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/m)
|
7
8
|
# just an import, don't generate any new keys
|
8
9
|
return plain_password
|
@@ -11,17 +12,17 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
11
12
|
cn = nil
|
12
13
|
if options['subject']
|
13
14
|
subject = options['subject']
|
14
|
-
if cna = OpenSSL::X509::Name.parse(subject).to_a.find{|e| e[0] == 'CN' }
|
15
|
+
if cna = OpenSSL::X509::Name.parse(subject).to_a.find { |e| e[0] == 'CN' }
|
15
16
|
cn = cna[1]
|
16
17
|
end
|
17
18
|
elsif options['CN']
|
18
19
|
subject = ''
|
19
20
|
cn = options['CN']
|
20
|
-
['C','ST','L','O','OU','CN','emailAddress'].each do |field|
|
21
|
+
['C', 'ST', 'L', 'O', 'OU', 'CN', 'emailAddress'].each do |field|
|
21
22
|
subject << "/#{field}=#{options[field]}" if options[field]
|
22
23
|
end
|
23
24
|
else
|
24
|
-
raise
|
25
|
+
raise 'You need to pass "subject" or "CN" as an option to use this format'
|
25
26
|
end
|
26
27
|
hash = options['hash'] || 'sha2'
|
27
28
|
sign_with = options['ca']
|
@@ -33,17 +34,17 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
33
34
|
key_usages = Array(key_usages) if key_usages
|
34
35
|
|
35
36
|
altnames = if become_ca || (an = options['altnames']) && Array(an).empty?
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
37
|
+
[]
|
38
|
+
else
|
39
|
+
# ensure that we have the CN with us, but only if it
|
40
|
+
# it's like a hostname.
|
41
|
+
# This might have to be improved.
|
42
|
+
if cn.include?(' ')
|
43
|
+
Array(an).collect { |v| "DNS:#{v}" }.join(', ')
|
44
|
+
else
|
45
|
+
(["DNS:#{cn}"] + Array(an).collect { |v| "DNS:#{v}" }).uniq.join(', ')
|
46
|
+
end
|
47
|
+
end
|
47
48
|
|
48
49
|
begin
|
49
50
|
key = mkkey(keysize)
|
@@ -55,7 +56,7 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
55
56
|
cert = nil
|
56
57
|
if sign_with # certificate signed with CA
|
57
58
|
begin
|
58
|
-
ca_str = trocla.get_password(sign_with,'x509')
|
59
|
+
ca_str = trocla.get_password(sign_with, 'x509')
|
59
60
|
ca = OpenSSL::X509::Certificate.new(ca_str)
|
60
61
|
cakey = OpenSSL::PKey::RSA.new(ca_str)
|
61
62
|
caserial = getserial(sign_with)
|
@@ -72,8 +73,10 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
72
73
|
end
|
73
74
|
|
74
75
|
begin
|
75
|
-
cert = mkcert(
|
76
|
-
|
76
|
+
cert = mkcert(
|
77
|
+
caserial, request.subject, ca, request.public_key, days,
|
78
|
+
altnames, key_usages, name_constraints, become_ca
|
79
|
+
)
|
77
80
|
cert.sign(cakey, signature(hash))
|
78
81
|
addserial(sign_with, caserial)
|
79
82
|
rescue Exception => e
|
@@ -82,8 +85,10 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
82
85
|
else # self-signed certificate
|
83
86
|
begin
|
84
87
|
subj = OpenSSL::X509::Name.parse(subject)
|
85
|
-
cert = mkcert(
|
86
|
-
|
88
|
+
cert = mkcert(
|
89
|
+
getserial(subj), subj, nil, key.public_key, days,
|
90
|
+
altnames, key_usages, name_constraints, become_ca
|
91
|
+
)
|
87
92
|
cert.sign(key, signature(hash))
|
88
93
|
rescue Exception => e
|
89
94
|
raise "Self-signed certificate #{subject} creation failed: #{e.message}"
|
@@ -92,7 +97,7 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
92
97
|
key.to_pem + cert.to_pem
|
93
98
|
end
|
94
99
|
|
95
|
-
def render(output,render_options={})
|
100
|
+
def render(output, render_options = {})
|
96
101
|
if render_options['keyonly']
|
97
102
|
OpenSSL::PKey::RSA.new(output).to_pem
|
98
103
|
elsif render_options['certonly']
|
@@ -100,26 +105,26 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
100
105
|
elsif render_options['publickeyonly']
|
101
106
|
OpenSSL::PKey::RSA.new(output).public_key.to_pem
|
102
107
|
else
|
103
|
-
super(output,render_options)
|
108
|
+
super(output, render_options)
|
104
109
|
end
|
105
110
|
end
|
106
111
|
|
107
112
|
private
|
108
|
-
# nice help: https://gist.github.com/mitfik/1922961
|
109
113
|
|
114
|
+
# nice help: https://gist.github.com/mitfik/1922961
|
110
115
|
def signature(hash = 'sha2')
|
111
116
|
if hash == 'sha1'
|
112
|
-
|
117
|
+
OpenSSL::Digest::SHA1.new
|
113
118
|
elsif hash == 'sha224'
|
114
|
-
|
119
|
+
OpenSSL::Digest::SHA224.new
|
115
120
|
elsif hash == 'sha2' || hash == 'sha256'
|
116
|
-
|
121
|
+
OpenSSL::Digest::SHA256.new
|
117
122
|
elsif hash == 'sha384'
|
118
|
-
|
123
|
+
OpenSSL::Digest::SHA384.new
|
119
124
|
elsif hash == 'sha512'
|
120
|
-
|
125
|
+
OpenSSL::Digest::SHA512.new
|
121
126
|
else
|
122
|
-
|
127
|
+
raise "Unrecognized hash: #{hash}"
|
123
128
|
end
|
124
129
|
end
|
125
130
|
|
@@ -127,7 +132,7 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
127
132
|
OpenSSL::PKey::RSA.generate(len)
|
128
133
|
end
|
129
134
|
|
130
|
-
def mkreq(subject,public_key)
|
135
|
+
def mkreq(subject, public_key)
|
131
136
|
request = OpenSSL::X509::Request.new
|
132
137
|
request.subject = subject
|
133
138
|
request.public_key = public_key
|
@@ -135,9 +140,9 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
135
140
|
request
|
136
141
|
end
|
137
142
|
|
138
|
-
def mkcert(serial,subject,issuer,public_key,days,altnames, key_usages = nil, name_constraints = [], become_ca = false)
|
143
|
+
def mkcert(serial, subject, issuer, public_key, days, altnames, key_usages = nil, name_constraints = [], become_ca = false)
|
139
144
|
cert = OpenSSL::X509::Certificate.new
|
140
|
-
issuer = cert if issuer
|
145
|
+
issuer = cert if issuer.nil?
|
141
146
|
cert.subject = subject
|
142
147
|
cert.issuer = issuer.subject
|
143
148
|
cert.not_before = Time.now
|
@@ -149,36 +154,36 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
149
154
|
ef = OpenSSL::X509::ExtensionFactory.new
|
150
155
|
ef.subject_certificate = cert
|
151
156
|
ef.issuer_certificate = issuer
|
152
|
-
cert.extensions = [
|
157
|
+
cert.extensions = [ef.create_extension('subjectKeyIdentifier', 'hash')]
|
153
158
|
|
154
159
|
if become_ca
|
155
|
-
cert.add_extension ef.create_extension(
|
160
|
+
cert.add_extension ef.create_extension('basicConstraints', 'CA:TRUE', true)
|
156
161
|
unless (ku = key_usages || ca_key_usages).empty?
|
157
|
-
cert.add_extension ef.create_extension(
|
162
|
+
cert.add_extension ef.create_extension('keyUsage', ku.join(', '), true)
|
158
163
|
end
|
159
164
|
if name_constraints && !name_constraints.empty?
|
160
|
-
cert.add_extension ef.create_extension(
|
165
|
+
cert.add_extension ef.create_extension('nameConstraints', "permitted;DNS:#{name_constraints.join(',permitted;DNS:')}", true)
|
161
166
|
end
|
162
167
|
else
|
163
|
-
cert.add_extension ef.create_extension(
|
164
|
-
cert.add_extension ef.create_extension(
|
168
|
+
cert.add_extension ef.create_extension('subjectAltName', altnames, true) unless altnames.empty?
|
169
|
+
cert.add_extension ef.create_extension('basicConstraints', 'CA:FALSE', true)
|
165
170
|
unless (ku = key_usages || cert_key_usages).empty?
|
166
|
-
cert.add_extension ef.create_extension(
|
171
|
+
cert.add_extension ef.create_extension('keyUsage', ku.join(', '), true)
|
167
172
|
end
|
168
173
|
end
|
169
|
-
cert.add_extension ef.create_extension(
|
174
|
+
cert.add_extension ef.create_extension('authorityKeyIdentifier', 'keyid:always,issuer:always')
|
170
175
|
|
171
176
|
cert
|
172
177
|
end
|
173
178
|
|
174
179
|
def getserial(ca)
|
175
|
-
newser = Trocla::Util.random_str(20,'hexadecimal').to_i(16)
|
180
|
+
newser = Trocla::Util.random_str(20, 'hexadecimal').to_i(16)
|
176
181
|
all_serials(ca).include?(newser) ? getserial(ca) : newser
|
177
182
|
end
|
178
183
|
|
179
184
|
def all_serials(ca)
|
180
|
-
if allser = trocla.get_password("#{ca}_all_serials",'plain')
|
181
|
-
YAML.
|
185
|
+
if allser = trocla.get_password("#{ca}_all_serials", 'plain')
|
186
|
+
YAML.safe_load(allser)
|
182
187
|
else
|
183
188
|
[]
|
184
189
|
end
|
@@ -186,14 +191,17 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
186
191
|
|
187
192
|
def addserial(ca,serial)
|
188
193
|
serials = all_serials(ca) << serial
|
189
|
-
trocla.set_password("#{ca}_all_serials",'plain',YAML.dump(serials))
|
194
|
+
trocla.set_password("#{ca}_all_serials", 'plain', YAML.dump(serials))
|
190
195
|
end
|
191
196
|
|
192
197
|
def cert_key_usages
|
193
198
|
['nonRepudiation', 'digitalSignature', 'keyEncipherment']
|
194
199
|
end
|
200
|
+
|
195
201
|
def ca_key_usages
|
196
|
-
[
|
197
|
-
'
|
202
|
+
[
|
203
|
+
'keyCertSign', 'cRLSign', 'nonRepudiation',
|
204
|
+
'digitalSignature', 'keyEncipherment'
|
205
|
+
]
|
198
206
|
end
|
199
207
|
end
|
data/lib/trocla/formats.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Trocla::Formats
|
4
|
+
class Trocla::Formats
|
5
|
+
# Base
|
3
6
|
class Base
|
4
7
|
attr_reader :trocla
|
8
|
+
|
5
9
|
def initialize(trocla)
|
6
10
|
@trocla = trocla
|
7
11
|
end
|
8
|
-
|
12
|
+
|
13
|
+
def render(output, render_options = {})
|
9
14
|
output
|
10
15
|
end
|
16
|
+
|
11
17
|
def expensive?
|
12
18
|
self.class.expensive?
|
13
19
|
end
|
@@ -15,6 +21,7 @@ class Trocla::Formats
|
|
15
21
|
def expensive(is_expensive)
|
16
22
|
@expensive = is_expensive
|
17
23
|
end
|
24
|
+
|
18
25
|
def expensive?
|
19
26
|
@expensive == true
|
20
27
|
end
|
@@ -27,7 +34,9 @@ class Trocla::Formats
|
|
27
34
|
end
|
28
35
|
|
29
36
|
def all
|
30
|
-
Dir[File.expand_path(
|
37
|
+
Dir[File.expand_path(
|
38
|
+
File.join(File.dirname(__FILE__), 'formats', '*.rb')
|
39
|
+
)].collect { |f| File.basename(f, '.rb').downcase }
|
31
40
|
end
|
32
41
|
|
33
42
|
def available?(format)
|
@@ -35,10 +44,11 @@ class Trocla::Formats
|
|
35
44
|
end
|
36
45
|
|
37
46
|
private
|
47
|
+
|
38
48
|
def formats
|
39
49
|
@@formats ||= Hash.new do |hash, format|
|
40
50
|
format = format.downcase
|
41
|
-
if File.
|
51
|
+
if File.exist?(path(format))
|
42
52
|
require "trocla/formats/#{format}"
|
43
53
|
hash[format] = (eval "Trocla::Formats::#{format.capitalize}")
|
44
54
|
else
|
@@ -48,7 +58,7 @@ class Trocla::Formats
|
|
48
58
|
end
|
49
59
|
|
50
60
|
def path(format)
|
51
|
-
File.expand_path(File.join(File.dirname(__FILE__),'formats',"#{format}.rb"))
|
61
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'formats', "#{format}.rb"))
|
52
62
|
end
|
53
63
|
end
|
54
64
|
end
|
data/lib/trocla/store.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# implements the default store behavior
|
2
2
|
class Trocla::Store
|
3
3
|
attr_reader :store_config, :trocla
|
4
|
-
|
4
|
+
|
5
|
+
def initialize(config, trocla)
|
5
6
|
@store_config = config
|
6
7
|
@trocla = trocla
|
7
8
|
end
|
@@ -9,14 +10,13 @@ class Trocla::Store
|
|
9
10
|
# closes the store
|
10
11
|
# when called do whatever "closes" your
|
11
12
|
# store, e.g. close database connections.
|
12
|
-
def close
|
13
|
-
end
|
13
|
+
def close; end
|
14
14
|
|
15
15
|
# should return value for key & format
|
16
16
|
# returns nil if nothing or a nil value
|
17
17
|
# was found.
|
18
18
|
# If a key is expired it must return nil.
|
19
|
-
def get(key,format)
|
19
|
+
def get(key, format)
|
20
20
|
raise 'not implemented'
|
21
21
|
end
|
22
22
|
|
@@ -33,11 +33,11 @@ class Trocla::Store
|
|
33
33
|
# amount of seconds a key can live with.
|
34
34
|
# This mechanism is expected to be
|
35
35
|
# be implemented by the backend.
|
36
|
-
def set(key,format,value,options={})
|
36
|
+
def set(key, format, value, options = {})
|
37
37
|
if format == 'plain'
|
38
|
-
set_plain(key,value,options)
|
38
|
+
set_plain(key, value, options)
|
39
39
|
else
|
40
|
-
set_format(key,format,value,options)
|
40
|
+
set_format(key, format, value, options)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -46,30 +46,31 @@ class Trocla::Store
|
|
46
46
|
# returns value of format or hash of
|
47
47
|
# format => value # if everything is
|
48
48
|
# deleted.
|
49
|
-
def delete(key,format=nil)
|
50
|
-
format.nil? ? (delete_all(key)||{}) : delete_format(key,format)
|
49
|
+
def delete(key, format = nil)
|
50
|
+
format.nil? ? (delete_all(key) || {}) : delete_format(key, format)
|
51
51
|
end
|
52
52
|
|
53
53
|
# returns all formats for a key
|
54
|
-
def formats(
|
54
|
+
def formats(_)
|
55
55
|
raise 'not implemented'
|
56
56
|
end
|
57
57
|
|
58
58
|
# def searches for a key
|
59
|
-
def search(
|
59
|
+
def search(_)
|
60
60
|
raise 'not implemented'
|
61
61
|
end
|
62
62
|
|
63
63
|
private
|
64
|
+
|
64
65
|
# sets a new plain value
|
65
66
|
# *must* invalidate all
|
66
67
|
# other formats
|
67
|
-
def set_plain(key,value,options)
|
68
|
+
def set_plain(key, value, options)
|
68
69
|
raise 'not implemented'
|
69
70
|
end
|
70
71
|
|
71
72
|
# sets a value of a format
|
72
|
-
def set_format(key,format,value,options)
|
73
|
+
def set_format(key, format, value, options)
|
73
74
|
raise 'not implemented'
|
74
75
|
end
|
75
76
|
|
@@ -77,14 +78,14 @@ class Trocla::Store
|
|
77
78
|
# and returns a hash with all
|
78
79
|
# formats and values
|
79
80
|
# or nil if nothing is found
|
80
|
-
def delete_all(
|
81
|
+
def delete_all(_)
|
81
82
|
raise 'not implemented'
|
82
83
|
end
|
83
84
|
|
84
85
|
# deletes the value of the passed
|
85
86
|
# key & format and returns the
|
86
87
|
# value.
|
87
|
-
def delete_format(key,format)
|
88
|
+
def delete_format(key, format)
|
88
89
|
raise 'not implemented'
|
89
90
|
end
|
90
91
|
end
|
data/lib/trocla/stores/memory.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# a simple in memory store just as an example
|
2
2
|
class Trocla::Stores::Memory < Trocla::Store
|
3
3
|
attr_reader :memory
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
def initialize(config, trocla)
|
6
|
+
super(config, trocla)
|
6
7
|
@memory = Hash.new({})
|
7
8
|
end
|
8
9
|
|
9
|
-
def get(key,format)
|
10
|
+
def get(key, format)
|
10
11
|
unless expired?(key)
|
11
12
|
memory[key][format]
|
12
13
|
else
|
@@ -14,9 +15,10 @@ class Trocla::Stores::Memory < Trocla::Store
|
|
14
15
|
nil
|
15
16
|
end
|
16
17
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
|
19
|
+
def set(key, format, value, options = {})
|
20
|
+
super(key, format, value, options)
|
21
|
+
set_expires(key, options['expires'])
|
20
22
|
end
|
21
23
|
|
22
24
|
def formats(key)
|
@@ -29,25 +31,29 @@ class Trocla::Stores::Memory < Trocla::Store
|
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
32
|
-
|
34
|
+
|
35
|
+
def set_plain(key, value, _)
|
33
36
|
memory[key] = { 'plain' => value }
|
34
37
|
end
|
35
38
|
|
36
|
-
def set_format(key,format,value,
|
39
|
+
def set_format(key, format, value, _)
|
37
40
|
memory[key].merge!({ format => value })
|
38
41
|
end
|
39
42
|
|
40
43
|
def delete_all(key)
|
41
44
|
memory.delete(key)
|
42
45
|
end
|
43
|
-
|
46
|
+
|
47
|
+
def delete_format(key, format)
|
44
48
|
old_val = (h = memory[key]).delete(format)
|
45
49
|
h.empty? ? memory.delete(key) : memory[key] = h
|
46
50
|
set_expires(key,nil)
|
47
51
|
old_val
|
48
52
|
end
|
53
|
+
|
49
54
|
private
|
50
|
-
|
55
|
+
|
56
|
+
def set_expires(key, expires)
|
51
57
|
expires = memory[key]['_expires'] if expires.nil?
|
52
58
|
if expires && expires > 0
|
53
59
|
memory[key]['_expires'] = expires
|
@@ -57,6 +63,7 @@ class Trocla::Stores::Memory < Trocla::Store
|
|
57
63
|
memory[key].delete('_expires_at')
|
58
64
|
end
|
59
65
|
end
|
66
|
+
|
60
67
|
def expired?(key)
|
61
68
|
memory.key?(key) &&
|
62
69
|
(a = memory[key]['_expires_at']).is_a?(Time) && \
|
data/lib/trocla/stores/moneta.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
# the default moneta based store
|
2
2
|
class Trocla::Stores::Moneta < Trocla::Store
|
3
3
|
attr_reader :moneta
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
def initialize(config, trocla)
|
6
|
+
super(config, trocla)
|
6
7
|
require 'moneta'
|
7
8
|
# load expire support by default
|
8
|
-
adapter_options = {
|
9
|
-
|
10
|
-
|
9
|
+
adapter_options = {
|
10
|
+
:expires => true
|
11
|
+
}.merge(store_config['adapter_options'] || {})
|
12
|
+
@moneta = Moneta.new(store_config['adapter'], adapter_options)
|
11
13
|
end
|
12
14
|
|
13
15
|
def close
|
14
16
|
moneta.close
|
15
17
|
end
|
16
18
|
|
17
|
-
def get(key,format)
|
19
|
+
def get(key, format)
|
18
20
|
moneta.fetch(key, {})[format]
|
19
21
|
end
|
20
22
|
|
@@ -25,14 +27,16 @@ class Trocla::Stores::Moneta < Trocla::Store
|
|
25
27
|
|
26
28
|
def search(key)
|
27
29
|
raise 'The search option is not available for any adapter other than Sequel or YAML' unless store_config['adapter'] == :Sequel || store_config['adapter'] == :YAML
|
30
|
+
|
28
31
|
r = search_keys(key)
|
29
32
|
r.empty? ? nil : r
|
30
33
|
end
|
31
34
|
|
32
35
|
private
|
33
|
-
|
36
|
+
|
37
|
+
def set_plain(key, value, options)
|
34
38
|
h = { 'plain' => value }
|
35
|
-
mo = moneta_options(key,options)
|
39
|
+
mo = moneta_options(key, options)
|
36
40
|
if options['expires'] && options['expires'] > 0
|
37
41
|
h['_expires'] = options['expires']
|
38
42
|
else
|
@@ -40,24 +44,28 @@ class Trocla::Stores::Moneta < Trocla::Store
|
|
40
44
|
# expires if nothing is set.
|
41
45
|
mo[:expires] = false
|
42
46
|
end
|
43
|
-
moneta.store(key,h,mo)
|
47
|
+
moneta.store(key, h, mo)
|
44
48
|
end
|
45
49
|
|
46
|
-
def set_format(key,format,value,options)
|
47
|
-
moneta.store(
|
48
|
-
|
49
|
-
|
50
|
+
def set_format(key, format, value, options)
|
51
|
+
moneta.store(
|
52
|
+
key,
|
53
|
+
moneta.fetch(key, {}).merge({ format => value }),
|
54
|
+
moneta_options(key, options)
|
55
|
+
)
|
50
56
|
end
|
51
57
|
|
52
58
|
def delete_all(key)
|
53
59
|
moneta.delete(key)
|
54
60
|
end
|
55
|
-
|
56
|
-
|
57
|
-
|
61
|
+
|
62
|
+
def delete_format(key, format)
|
63
|
+
old_val = (h = moneta.fetch(key, {})).delete(format)
|
64
|
+
h.empty? ? moneta.delete(key) : moneta.store(key, h, moneta_options(key, {}))
|
58
65
|
old_val
|
59
66
|
end
|
60
|
-
|
67
|
+
|
68
|
+
def moneta_options(key, options)
|
61
69
|
res = {}
|
62
70
|
if options.key?('expires')
|
63
71
|
res[:expires] = options['expires']
|
@@ -66,11 +74,13 @@ class Trocla::Stores::Moneta < Trocla::Store
|
|
66
74
|
end
|
67
75
|
res
|
68
76
|
end
|
77
|
+
|
69
78
|
def search_keys(key)
|
70
|
-
_moneta = Moneta.new(store_config['adapter'], (store_config['adapter_options']||{}).merge({ :expires => false }))
|
79
|
+
_moneta = Moneta.new(store_config['adapter'], (store_config['adapter_options'] || {}).merge({ :expires => false }))
|
71
80
|
a = []
|
81
|
+
|
72
82
|
if store_config['adapter'] == :Sequel
|
73
|
-
keys = _moneta.adapter.backend[:trocla].select_order_map {from_base64(:k)}
|
83
|
+
keys = _moneta.adapter.backend[:trocla].select_order_map { from_base64(:k) }
|
74
84
|
elsif store_config['adapter'] == :YAML
|
75
85
|
keys = _moneta.adapter.backend.transaction(true) { _moneta.adapter.backend.roots }
|
76
86
|
end
|
data/lib/trocla/stores/vault.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
# the default vault based store
|
2
2
|
class Trocla::Stores::Vault < Trocla::Store
|
3
|
-
attr_reader :vault, :mount
|
4
|
-
|
5
|
-
|
3
|
+
attr_reader :vault, :mount, :destroy
|
4
|
+
|
5
|
+
def initialize(config, trocla)
|
6
|
+
super(config, trocla)
|
6
7
|
require 'vault'
|
7
8
|
@mount = (config.delete(:mount) || 'kv')
|
9
|
+
@destroy = (config.delete(:destroy) || false)
|
8
10
|
# load expire support by default
|
9
11
|
@vault = Vault::Client.new(config)
|
10
12
|
end
|
11
13
|
|
12
|
-
def close
|
13
|
-
end
|
14
|
+
def close; end
|
14
15
|
|
15
|
-
def get(key,format)
|
16
|
+
def get(key, format)
|
16
17
|
read(key)[format.to_sym]
|
17
18
|
end
|
18
19
|
|
@@ -20,31 +21,58 @@ class Trocla::Stores::Vault < Trocla::Store
|
|
20
21
|
read(key).keys
|
21
22
|
end
|
22
23
|
|
24
|
+
def search(key)
|
25
|
+
arr = key.split('/')
|
26
|
+
regexp = Regexp.new(arr.pop(1)[0].to_s)
|
27
|
+
path = arr.join('/')
|
28
|
+
list = vault.kv(mount).list(path)
|
29
|
+
list.map! do |l|
|
30
|
+
if regexp.match(l)
|
31
|
+
path.empty? ? l : [path, l].join('/')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
list.compact
|
35
|
+
end
|
36
|
+
|
23
37
|
private
|
38
|
+
|
24
39
|
def read(key)
|
25
40
|
k = vault.kv(mount).read(key)
|
26
41
|
k.nil? ? {} : k.data
|
27
42
|
end
|
28
43
|
|
29
|
-
def write(key, value)
|
44
|
+
def write(key, value, options = {})
|
45
|
+
vault.kv(mount).write_metadata(key, convert_metadata(options)) unless options.empty?
|
30
46
|
vault.kv(mount).write(key, value)
|
31
47
|
end
|
32
48
|
|
33
|
-
def set_plain(key,value,options)
|
34
|
-
set_format(key,'plain',value,options)
|
49
|
+
def set_plain(key, value, options)
|
50
|
+
set_format(key, 'plain', value, options)
|
35
51
|
end
|
36
52
|
|
37
|
-
def set_format(key,format,value,options)
|
38
|
-
write(
|
53
|
+
def set_format(key, format, value, options)
|
54
|
+
write(
|
55
|
+
key,
|
56
|
+
read(key).merge({ format.to_sym => value }),
|
57
|
+
options
|
58
|
+
)
|
39
59
|
end
|
40
60
|
|
41
61
|
def delete_all(key)
|
42
|
-
vault.kv(mount).delete(key)
|
62
|
+
destroy ? vault.kv(mount).destroy(key) : vault.kv(mount).delete(key)
|
43
63
|
end
|
44
64
|
|
45
|
-
def delete_format(key,format)
|
65
|
+
def delete_format(key, format)
|
46
66
|
old = read(key)
|
47
|
-
|
67
|
+
new = old.reject { |k, _| k == format.to_sym }
|
68
|
+
new.empty? ? delete_all(key) : write(key, new)
|
48
69
|
old[format.to_sym]
|
49
70
|
end
|
71
|
+
|
72
|
+
def convert_metadata(metadatas)
|
73
|
+
metadatas.transform_keys!(&:to_sym)
|
74
|
+
metadatas[:delete_version_after] = metadatas.delete(:expire) if metadatas[:expire]
|
75
|
+
%i[random profiles expires length].each { |k| metadatas.delete(k) }
|
76
|
+
metadatas
|
77
|
+
end
|
50
78
|
end
|
data/lib/trocla/stores.rb
CHANGED
@@ -7,7 +7,7 @@ class Trocla::Stores
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def all
|
10
|
-
@all ||= Dir[
|
10
|
+
@all ||= Dir[path '*'].collect do |store|
|
11
11
|
File.basename(store, '.rb').downcase
|
12
12
|
end
|
13
13
|
end
|
@@ -17,10 +17,11 @@ class Trocla::Stores
|
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
20
|
+
|
20
21
|
def stores
|
21
22
|
@@stores ||= Hash.new do |hash, store|
|
22
23
|
store = store.to_s.downcase
|
23
|
-
if File.
|
24
|
+
if File.exist?(path(store))
|
24
25
|
require "trocla/stores/#{store}"
|
25
26
|
class_name = "Trocla::Stores::#{store.capitalize}"
|
26
27
|
hash[store] = (eval class_name)
|