trocla 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +24 -0
- data/.rspec +1 -1
- data/CHANGELOG.md +23 -0
- data/Gemfile +10 -36
- data/README.md +64 -5
- data/bin/trocla +57 -40
- 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 +42 -0
- data/lib/trocla/formats/wireguard.rb +45 -0
- data/lib/trocla/formats/x509.rb +56 -46
- data/lib/trocla/formats.rb +15 -5
- data/lib/trocla/store.rb +24 -13
- data/lib/trocla/stores/memory.rb +26 -10
- data/lib/trocla/stores/moneta.rb +53 -17
- data/lib/trocla/stores/vault.rb +78 -0
- 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 +51 -41
- data/spec/spec_helper.rb +13 -1
- data/spec/trocla/formats/pgsql_spec.rb +25 -0
- data/spec/trocla/formats/sshkey_spec.rb +52 -0
- data/spec/trocla/formats/x509_spec.rb +8 -1
- data/spec/trocla_spec.rb +43 -0
- data/trocla.gemspec +29 -26
- metadata +58 -12
- data/.travis.yml +0 -11
@@ -0,0 +1,78 @@
|
|
1
|
+
# the default vault based store
|
2
|
+
class Trocla::Stores::Vault < Trocla::Store
|
3
|
+
attr_reader :vault, :mount, :destroy
|
4
|
+
|
5
|
+
def initialize(config, trocla)
|
6
|
+
super(config, trocla)
|
7
|
+
require 'vault'
|
8
|
+
@mount = (config.delete(:mount) || 'kv')
|
9
|
+
@destroy = (config.delete(:destroy) || false)
|
10
|
+
# load expire support by default
|
11
|
+
@vault = Vault::Client.new(config)
|
12
|
+
end
|
13
|
+
|
14
|
+
def close; end
|
15
|
+
|
16
|
+
def get(key, format)
|
17
|
+
read(key)[format.to_sym]
|
18
|
+
end
|
19
|
+
|
20
|
+
def formats(key)
|
21
|
+
read(key).keys
|
22
|
+
end
|
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
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def read(key)
|
40
|
+
k = vault.kv(mount).read(key)
|
41
|
+
k.nil? ? {} : k.data
|
42
|
+
end
|
43
|
+
|
44
|
+
def write(key, value, options = {})
|
45
|
+
vault.kv(mount).write_metadata(key, convert_metadata(options)) unless options.empty?
|
46
|
+
vault.kv(mount).write(key, value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_plain(key, value, options)
|
50
|
+
set_format(key, 'plain', value, options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_format(key, format, value, options)
|
54
|
+
write(
|
55
|
+
key,
|
56
|
+
read(key).merge({ format.to_sym => value }),
|
57
|
+
options
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete_all(key)
|
62
|
+
destroy ? vault.kv(mount).destroy(key) : vault.kv(mount).delete(key)
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete_format(key, format)
|
66
|
+
old = read(key)
|
67
|
+
new = old.reject { |k, _| k == format.to_sym }
|
68
|
+
new.empty? ? delete_all(key) : write(key, new)
|
69
|
+
old[format.to_sym]
|
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
|
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)
|
data/lib/trocla/util.rb
CHANGED
@@ -1,30 +1,30 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
class Trocla
|
3
|
+
# Utils
|
3
4
|
class Util
|
4
5
|
class << self
|
5
|
-
def random_str(length=12, charset='default')
|
6
|
-
|
7
|
-
|
6
|
+
def random_str(length = 12, charset = 'default')
|
7
|
+
char = charsets[charset] || charsets['default']
|
8
|
+
charsets_size = char.size
|
9
|
+
(1..length).collect { |_| char[rand_num(charsets_size)] }.join.to_s
|
8
10
|
end
|
9
11
|
|
10
|
-
def salt(length=8)
|
11
|
-
|
12
|
-
(1..length).collect{|a| alphanumeric[SecureRandom.random_number(alphanumeric_size)] }.join.to_s
|
12
|
+
def salt(length = 8)
|
13
|
+
random_str(length, 'alphanumeric')
|
13
14
|
end
|
14
15
|
|
15
16
|
private
|
16
17
|
|
18
|
+
def rand_num(n)
|
19
|
+
SecureRandom.random_number(n)
|
20
|
+
end
|
21
|
+
|
17
22
|
def charsets
|
18
23
|
@charsets ||= begin
|
19
24
|
h = {
|
20
|
-
'default'
|
21
|
-
'
|
22
|
-
'
|
23
|
-
'windowssafe' => windowssafe,
|
24
|
-
'numeric' => numeric,
|
25
|
-
'hexadecimal' => hexadecimal,
|
26
|
-
'consolesafe' => consolesafe,
|
27
|
-
'typesafe' => typesafe,
|
25
|
+
'default' => chars, 'alphanumeric' => alphanumeric, 'shellsafe' => shellsafe,
|
26
|
+
'windowssafe' => windowssafe, 'numeric' => numeric, 'hexadecimal' => hexadecimal,
|
27
|
+
'consolesafe' => consolesafe, 'typesafe' => typesafe
|
28
28
|
}
|
29
29
|
h.each { |k, v| h[k] = v.uniq }
|
30
30
|
end
|
@@ -33,36 +33,47 @@ class Trocla
|
|
33
33
|
def chars
|
34
34
|
@chars ||= shellsafe + special_chars
|
35
35
|
end
|
36
|
+
|
36
37
|
def shellsafe
|
37
38
|
@shellsafe ||= alphanumeric + shellsafe_chars
|
38
39
|
end
|
40
|
+
|
39
41
|
def windowssafe
|
40
42
|
@windowssafe ||= alphanumeric + windowssafe_chars
|
41
43
|
end
|
44
|
+
|
42
45
|
def consolesafe
|
43
46
|
@consolesafe ||= alphanumeric + consolesafe_chars
|
44
47
|
end
|
48
|
+
|
45
49
|
def hexadecimal
|
46
50
|
@hexadecimal ||= numeric + ('a'..'f').to_a
|
47
51
|
end
|
52
|
+
|
48
53
|
def alphanumeric
|
49
54
|
@alphanumeric ||= ('a'..'z').to_a + ('A'..'Z').to_a + numeric
|
50
55
|
end
|
56
|
+
|
51
57
|
def numeric
|
52
58
|
@numeric ||= ('0'..'9').to_a
|
53
59
|
end
|
60
|
+
|
54
61
|
def typesafe
|
55
62
|
@typesafe ||= ('a'..'x').to_a - ['i'] - ['l'] + ('A'..'X').to_a - ['I'] - ['L'] + ('1'..'9').to_a
|
56
63
|
end
|
64
|
+
|
57
65
|
def special_chars
|
58
|
-
@special_chars ||=
|
66
|
+
@special_chars ||= '*()&![]{}-'.split(//)
|
59
67
|
end
|
68
|
+
|
60
69
|
def shellsafe_chars
|
61
|
-
@shellsafe_chars ||=
|
70
|
+
@shellsafe_chars ||= '+%/@=?_.,:'.split(//)
|
62
71
|
end
|
72
|
+
|
63
73
|
def windowssafe_chars
|
64
|
-
@windowssafe_chars ||=
|
74
|
+
@windowssafe_chars ||= '+%/@=?_.,'.split(//)
|
65
75
|
end
|
76
|
+
|
66
77
|
def consolesafe_chars
|
67
78
|
@consolesafe_chars ||= '+.-,_'.split(//)
|
68
79
|
end
|
data/lib/trocla/version.rb
CHANGED
@@ -1,20 +1,23 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
|
2
3
|
class Trocla
|
4
|
+
# VERSION
|
3
5
|
class VERSION
|
4
6
|
version = {}
|
5
7
|
File.read(File.join(File.dirname(__FILE__), '../', 'VERSION')).each_line do |line|
|
6
|
-
type, value = line.chomp.split(
|
8
|
+
type, value = line.chomp.split(':')
|
7
9
|
next if type =~ /^\s+$/ || value =~ /^\s+$/
|
10
|
+
|
8
11
|
version[type] = value
|
9
12
|
end
|
10
|
-
|
13
|
+
|
11
14
|
MAJOR = version['major']
|
12
15
|
MINOR = version['minor']
|
13
16
|
PATCH = version['patch']
|
14
17
|
BUILD = version['build']
|
15
|
-
|
18
|
+
|
16
19
|
STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
|
17
|
-
|
20
|
+
|
18
21
|
def self.version
|
19
22
|
STRING
|
20
23
|
end
|
data/lib/trocla.rb
CHANGED
@@ -4,16 +4,17 @@ require 'trocla/formats'
|
|
4
4
|
require 'trocla/encryptions'
|
5
5
|
require 'trocla/stores'
|
6
6
|
|
7
|
+
# Trocla class
|
7
8
|
class Trocla
|
8
|
-
def initialize(config_file=nil)
|
9
|
+
def initialize(config_file = nil)
|
9
10
|
if config_file
|
10
11
|
@config_file = File.expand_path(config_file)
|
11
|
-
elsif File.
|
12
|
+
elsif File.exist?(def_config_file = File.expand_path('~/.troclarc.yaml')) || File.exist?(def_config_file = File.expand_path('/etc/troclarc.yaml'))
|
12
13
|
@config_file = def_config_file
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
def self.open(config_file=nil)
|
17
|
+
def self.open(config_file = nil)
|
17
18
|
trocla = Trocla.new(config_file)
|
18
19
|
|
19
20
|
if block_given?
|
@@ -24,70 +25,76 @@ class Trocla
|
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
27
|
-
def password(key,format,options={})
|
28
|
+
def password(key, format, options={})
|
28
29
|
# respect a default profile, but let the
|
29
30
|
# profiles win over the default options
|
30
31
|
options['profiles'] ||= config['options']['profiles']
|
31
|
-
if options['profiles']
|
32
|
-
options = merge_profiles(options['profiles']).merge(options)
|
33
|
-
end
|
32
|
+
options = merge_profiles(options['profiles']).merge(options) if options['profiles']
|
34
33
|
options = config['options'].merge(options)
|
35
34
|
|
36
35
|
raise "Format #{format} is not supported! Supported formats: #{Trocla::Formats.all.join(', ')}" unless Trocla::Formats::available?(format)
|
37
36
|
|
38
|
-
unless (password=get_password(key,format,options)).nil?
|
37
|
+
unless (password = get_password(key, format, options)).nil?
|
39
38
|
return password
|
40
39
|
end
|
41
40
|
|
42
|
-
plain_pwd = get_password(key,'plain',options)
|
41
|
+
plain_pwd = get_password(key, 'plain', options)
|
43
42
|
if options['random'] && plain_pwd.nil?
|
44
|
-
plain_pwd = Trocla::Util.random_str(options['length'].to_i,options['charset'])
|
45
|
-
set_password(key,'plain',plain_pwd,options) unless format == 'plain'
|
43
|
+
plain_pwd = Trocla::Util.random_str(options['length'].to_i, options['charset'])
|
44
|
+
set_password(key, 'plain', plain_pwd, options) unless format == 'plain'
|
46
45
|
elsif !options['random'] && plain_pwd.nil?
|
47
46
|
raise "Password must be present as plaintext if you don't want a random password"
|
48
47
|
end
|
49
|
-
pwd = self.formats(format).format(plain_pwd,options)
|
48
|
+
pwd = self.formats(format).format(plain_pwd, options)
|
50
49
|
# it's possible that meanwhile another thread/process was faster in
|
51
50
|
# formating the password. But we want todo that second lookup
|
52
51
|
# only for expensive formats
|
53
52
|
if self.formats(format).expensive?
|
54
|
-
get_password(key,format,options) || set_password(key, format, pwd, options)
|
53
|
+
get_password(key, format, options) || set_password(key, format, pwd, options)
|
55
54
|
else
|
56
55
|
set_password(key, format, pwd, options)
|
57
56
|
end
|
58
57
|
end
|
59
58
|
|
60
|
-
def get_password(key, format, options={})
|
61
|
-
render(format,decrypt(store.get(key,format)),options)
|
59
|
+
def get_password(key, format, options = {})
|
60
|
+
render(format, decrypt(store.get(key, format)), options)
|
62
61
|
end
|
63
62
|
|
64
|
-
def reset_password(key,format,options={})
|
65
|
-
|
66
|
-
password(key,format,options)
|
63
|
+
def reset_password(key, format, options = {})
|
64
|
+
delete_password(key, format)
|
65
|
+
password(key, format, options)
|
67
66
|
end
|
68
67
|
|
69
|
-
def delete_password(key,format=nil,options={})
|
70
|
-
v = store.delete(key,format)
|
68
|
+
def delete_password(key, format = nil, options = {})
|
69
|
+
v = store.delete(key, format)
|
71
70
|
if v.is_a?(Hash)
|
72
|
-
Hash[*v.map do |f,encrypted_value|
|
73
|
-
[f,render(format,decrypt(encrypted_value),options)]
|
71
|
+
Hash[*v.map do |f, encrypted_value|
|
72
|
+
[f, render(format, decrypt(encrypted_value), options)]
|
74
73
|
end.flatten]
|
75
74
|
else
|
76
|
-
render(format,decrypt(v),options)
|
75
|
+
render(format, decrypt(v), options)
|
77
76
|
end
|
78
77
|
end
|
79
78
|
|
80
|
-
def set_password(key,format,password,options={})
|
81
|
-
store.set(key,format,encrypt(password),options)
|
82
|
-
render(format,password,options)
|
79
|
+
def set_password(key, format, password, options = {})
|
80
|
+
store.set(key, format, encrypt(password), options)
|
81
|
+
render(format, password, options)
|
82
|
+
end
|
83
|
+
|
84
|
+
def available_format(key, options = {})
|
85
|
+
render(false, store.formats(key), options)
|
86
|
+
end
|
87
|
+
|
88
|
+
def search_key(key, options = {})
|
89
|
+
render(false, store.search(key), options)
|
83
90
|
end
|
84
91
|
|
85
92
|
def formats(format)
|
86
|
-
(@format_cache||={})[format] ||= Trocla::Formats[format].new(self)
|
93
|
+
(@format_cache ||= {})[format] ||= Trocla::Formats[format].new(self)
|
87
94
|
end
|
88
95
|
|
89
96
|
def encryption
|
90
|
-
@encryption ||= Trocla::Encryptions[config['encryption']].new(config['encryption_options'],self)
|
97
|
+
@encryption ||= Trocla::Encryptions[config['encryption']].new(config['encryption_options'], self)
|
91
98
|
end
|
92
99
|
|
93
100
|
def config
|
@@ -99,6 +106,7 @@ class Trocla
|
|
99
106
|
end
|
100
107
|
|
101
108
|
private
|
109
|
+
|
102
110
|
def store
|
103
111
|
@store ||= build_store
|
104
112
|
end
|
@@ -106,19 +114,20 @@ class Trocla
|
|
106
114
|
def build_store
|
107
115
|
s = config['store']
|
108
116
|
clazz = if s.is_a?(Symbol)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
clazz.new(config['store_options'],self)
|
117
|
+
Trocla::Stores[s]
|
118
|
+
else
|
119
|
+
require config['store_require'] if config['store_require']
|
120
|
+
eval(s)
|
121
|
+
end
|
122
|
+
clazz.new(config['store_options'], self)
|
115
123
|
end
|
116
124
|
|
117
125
|
def read_config
|
118
126
|
if @config_file.nil?
|
119
127
|
default_config
|
120
128
|
else
|
121
|
-
raise "Configfile #{@config_file} does not exist!" unless File.
|
129
|
+
raise "Configfile #{@config_file} does not exist!" unless File.exist?(@config_file)
|
130
|
+
|
122
131
|
c = default_config.merge(YAML.load(File.read(@config_file)))
|
123
132
|
c['profiles'] = default_config['profiles'].merge(c['profiles'])
|
124
133
|
# migrate all options to new store options
|
@@ -136,12 +145,13 @@ class Trocla
|
|
136
145
|
|
137
146
|
def decrypt(value)
|
138
147
|
return nil if value.nil?
|
148
|
+
|
139
149
|
encryption.decrypt(value)
|
140
150
|
end
|
141
151
|
|
142
|
-
def render(format,output,options={})
|
143
|
-
if format && output && f=self.formats(format)
|
144
|
-
f.render(output,options['render']||{})
|
152
|
+
def render(format, output, options = {})
|
153
|
+
if format && output && f = self.formats(format)
|
154
|
+
f.render(output, options['render'] || {})
|
145
155
|
else
|
146
156
|
output
|
147
157
|
end
|
@@ -149,14 +159,14 @@ class Trocla
|
|
149
159
|
|
150
160
|
def default_config
|
151
161
|
require 'yaml'
|
152
|
-
YAML.load(File.read(File.expand_path(File.join(File.dirname(__FILE__),'trocla','default_config.yaml'))))
|
162
|
+
YAML.load(File.read(File.expand_path(File.join(File.dirname(__FILE__), 'trocla', 'default_config.yaml'))))
|
153
163
|
end
|
154
164
|
|
155
165
|
def merge_profiles(profiles)
|
156
|
-
Array(profiles).inject({}) do |res,profile|
|
166
|
+
Array(profiles).inject({}) do |res, profile|
|
157
167
|
raise "No such profile #{profile} defined" unless profile_hash = config['profiles'][profile]
|
168
|
+
|
158
169
|
profile_hash.merge(res)
|
159
170
|
end
|
160
171
|
end
|
161
|
-
|
162
172
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'jruby' if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
|
3
4
|
require 'rspec'
|
4
5
|
require 'rspec/pending_for'
|
5
6
|
require 'yaml'
|
@@ -35,6 +36,11 @@ RSpec.shared_examples "encryption_basics" do
|
|
35
36
|
expect(@trocla.get_password('some_pass', 'plain')).to eql('super secret')
|
36
37
|
end
|
37
38
|
|
39
|
+
it "resets passwords" do
|
40
|
+
@trocla.set_password('some_pass', 'plain', 'super secret')
|
41
|
+
expect(@trocla.reset_password('some_pass', 'plain')).not_to eql('super secret')
|
42
|
+
end
|
43
|
+
|
38
44
|
end
|
39
45
|
describe 'deleting' do
|
40
46
|
it "plain" do
|
@@ -225,7 +231,13 @@ RSpec.shared_examples 'store_validation' do |store|
|
|
225
231
|
end
|
226
232
|
|
227
233
|
def default_config
|
228
|
-
@default_config ||=
|
234
|
+
@default_config ||= begin
|
235
|
+
config_path = [
|
236
|
+
File.expand_path(base_dir+'/lib/trocla/default_config.yaml'),
|
237
|
+
File.expand_path(File.dirname($LOADED_FEATURES.grep(/trocla.rb/)[0])+'/trocla/default_config.yaml'),
|
238
|
+
].find { |p| File.exists?(p) }
|
239
|
+
YAML.load(File.read(config_path))
|
240
|
+
end
|
229
241
|
end
|
230
242
|
|
231
243
|
def test_config
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe 'Trocla::Format::Pgsql' do
|
4
|
+
before(:each) do
|
5
|
+
expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config)
|
6
|
+
@trocla = Trocla.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'default pgsql' do
|
10
|
+
it 'create a pgsql password keypair without options in sha256' do
|
11
|
+
pass = @trocla.password('pgsql_password_sh256', 'pgsql', {})
|
12
|
+
expect(pass).to match(/^SCRAM-SHA-256\$(.*):(.*)\$(.*):/)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'pgsql in md5 encode' do
|
17
|
+
it 'create a pgsql password in md5 encode' do
|
18
|
+
pass = @trocla.password(
|
19
|
+
'pgsql_password_md5', 'pgsql',
|
20
|
+
{ 'username' => 'toto', 'encode' => 'md5' }
|
21
|
+
)
|
22
|
+
expect(pass).to match(/^md5/)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe "Trocla::Format::Sshkey" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config)
|
7
|
+
@trocla = Trocla.new
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:sshkey_options) do
|
11
|
+
{
|
12
|
+
'type' => 'rsa',
|
13
|
+
'bits' => 4096,
|
14
|
+
'comment' => 'My ssh key'
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "sshkey" do
|
19
|
+
it "is able to create an ssh keypair without options" do
|
20
|
+
sshkey = @trocla.password('my_ssh_keypair', 'sshkey', {})
|
21
|
+
expect(sshkey).to start_with('-----BEGIN RSA PRIVATE KEY-----')
|
22
|
+
expect(sshkey).to match(/ssh-/)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "is able to create an ssh keypair with options" do
|
26
|
+
sshkey = @trocla.password('my_ssh_keypair', 'sshkey', sshkey_options)
|
27
|
+
expect(sshkey).to start_with('-----BEGIN RSA PRIVATE KEY-----')
|
28
|
+
expect(sshkey).to match(/ssh-/)
|
29
|
+
expect(sshkey).to end_with('My ssh key')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'supports fetching only the priv key' do
|
33
|
+
sshkey = @trocla.password('my_ssh_keypair', 'sshkey', { 'render' => {'privonly' => true }})
|
34
|
+
expect(sshkey).to start_with('-----BEGIN RSA PRIVATE KEY-----')
|
35
|
+
expect(sshkey).not_to match(/ssh-/)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'supports fetching only the pub key' do
|
39
|
+
sshkey = @trocla.password('my_ssh_keypair', 'sshkey', { 'render' => {'pubonly' => true }})
|
40
|
+
expect(sshkey).to start_with('ssh-rsa')
|
41
|
+
expect(sshkey).not_to match(/-----BEGIN RSA PRIVATE KEY-----/)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "is able to create an ssh keypair with a passphrase" do
|
45
|
+
sshkey = @trocla.password('my_ssh_keypair', 'sshkey', { 'passphrase' => 'spec' })
|
46
|
+
expect(sshkey).to start_with('-----BEGIN RSA PRIVATE KEY-----')
|
47
|
+
expect(sshkey).to match(/ssh-/)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -44,7 +44,9 @@ describe "Trocla::Format::X509" do
|
|
44
44
|
expect(cert.public_key.n.num_bytes * 8).to eq(4096)
|
45
45
|
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
|
46
46
|
# it's a self signed cert and NOT a CA
|
47
|
-
|
47
|
+
# Change of behavior on openssl side: https://github.com/openssl/openssl/issues/15146
|
48
|
+
validates_self_even_if_no_ca = Gem::Version.new(%x{openssl version}.split(' ')[1]) > Gem::Version.new('1.1.1g')
|
49
|
+
expect(verify(cert,cert)).to be validates_self_even_if_no_ca
|
48
50
|
|
49
51
|
v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
|
50
52
|
expect(v).to eq('CA:FALSE')
|
@@ -136,6 +138,11 @@ describe "Trocla::Format::X509" do
|
|
136
138
|
expect(cert_str).not_to match(/-----BEGIN CERTIFICATE-----/)
|
137
139
|
expect(cert_str).to match(/-----BEGIN RSA PRIVATE KEY-----/)
|
138
140
|
end
|
141
|
+
it 'supports fetching only the publickey' do
|
142
|
+
pkey_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'publickeyonly' => true }))
|
143
|
+
expect(pkey_str).not_to match(/-----BEGIN CERTIFICATE-----/)
|
144
|
+
expect(pkey_str).to match(/-----BEGIN PUBLIC KEY-----/)
|
145
|
+
end
|
139
146
|
it 'supports fetching only the cert' do
|
140
147
|
cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'certonly' => true }))
|
141
148
|
expect(cert_str).to match(/-----BEGIN CERTIFICATE-----/)
|
data/spec/trocla_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -- encoding : utf-8 --
|
1
2
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
3
|
|
3
4
|
describe "Trocla" do
|
@@ -71,6 +72,9 @@ describe "Trocla" do
|
|
71
72
|
expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
|
72
73
|
end
|
73
74
|
it 'is possible to combine profiles but first profile wins 3' do
|
75
|
+
# mysql profile uses a 32 long random pwd with shell safe characters
|
76
|
+
# and we want to use a fixed random str here https://github.com/duritong/trocla/issues/55
|
77
|
+
allow(Trocla::Util).to receive(:random_str).with(32,'shellsafe') { "jmNi6+7dsUn@H?vfbXCq=ULEGPW,u:hu" }
|
74
78
|
pwd = @trocla.password('some_test3','plain', 'profiles' => ['mysql','login'])
|
75
79
|
expect(pwd).not_to be_empty
|
76
80
|
expect(pwd.length).to eq(32)
|
@@ -99,6 +103,11 @@ describe "Trocla" do
|
|
99
103
|
expect(@trocla.get_password('set_test2','md5crypt')).to eq(md5crypt)
|
100
104
|
expect(@trocla.get_password('set_test2','plain')).to eq(plain)
|
101
105
|
end
|
106
|
+
|
107
|
+
it 'is able to set password with umlauts and other UTF-8 charcters' do
|
108
|
+
expect(myumlaut = @trocla.set_password('set_test_umlaut','plain','Tütü')).to eql('Tütü')
|
109
|
+
expect(@trocla.get_password('set_test_umlaut','plain','Tütü')).to eql('Tütü')
|
110
|
+
end
|
102
111
|
end
|
103
112
|
|
104
113
|
describe "reset_password" do
|
@@ -120,6 +129,40 @@ describe "Trocla" do
|
|
120
129
|
end
|
121
130
|
end
|
122
131
|
|
132
|
+
describe "search_key" do
|
133
|
+
it "search a specific key" do
|
134
|
+
keys = ['search_key','search_key1','key_search','key_search2']
|
135
|
+
keys.each do |k|
|
136
|
+
@trocla.password(k,'plain')
|
137
|
+
end
|
138
|
+
expect(@trocla.search_key('search_key1').length).to eq(1)
|
139
|
+
end
|
140
|
+
it "ensure search regex is ok" do
|
141
|
+
keys = ['search_key2','search_key3','key_search2','key_search4']
|
142
|
+
keys.each do |k|
|
143
|
+
@trocla.password(k,'plain')
|
144
|
+
end
|
145
|
+
expect(@trocla.search_key('key').length).to eq(4)
|
146
|
+
expect(@trocla.search_key('^search').length).to eq(2)
|
147
|
+
expect(@trocla.search_key('ch.*3').length).to eq(1)
|
148
|
+
expect(@trocla.search_key('ch.*[3-4]$').length).to eq(2)
|
149
|
+
expect(@trocla.search_key('ch.*1')).to be_nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "list_format" do
|
154
|
+
it "list available formats for key" do
|
155
|
+
formats = ['plain','mysql']
|
156
|
+
formats.each do |f|
|
157
|
+
@trocla.password('list_key',f)
|
158
|
+
end
|
159
|
+
expect(@trocla.available_format('list_key')).to eq(formats)
|
160
|
+
end
|
161
|
+
it "no return if key doesn't exist" do
|
162
|
+
expect(@trocla.available_format('list_key1')).to be_nil
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
123
166
|
describe "delete_password" do
|
124
167
|
it "deletes all passwords if no format is given" do
|
125
168
|
expect(@trocla.password('delete_test1','mysql')).not_to be_nil
|