trocla 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|