trocla-ruby2 0.4.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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.document +4 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +10 -0
  5. data/CHANGELOG.md +71 -0
  6. data/Gemfile +51 -0
  7. data/LICENSE.txt +15 -0
  8. data/README.md +351 -0
  9. data/Rakefile +53 -0
  10. data/bin/trocla +148 -0
  11. data/ext/redhat/rubygem-trocla.spec +120 -0
  12. data/lib/VERSION +4 -0
  13. data/lib/trocla.rb +162 -0
  14. data/lib/trocla/default_config.yaml +47 -0
  15. data/lib/trocla/encryptions.rb +54 -0
  16. data/lib/trocla/encryptions/none.rb +10 -0
  17. data/lib/trocla/encryptions/ssl.rb +51 -0
  18. data/lib/trocla/formats.rb +54 -0
  19. data/lib/trocla/formats/bcrypt.rb +7 -0
  20. data/lib/trocla/formats/md5crypt.rb +6 -0
  21. data/lib/trocla/formats/mysql.rb +6 -0
  22. data/lib/trocla/formats/pgsql.rb +7 -0
  23. data/lib/trocla/formats/plain.rb +7 -0
  24. data/lib/trocla/formats/sha1.rb +7 -0
  25. data/lib/trocla/formats/sha256crypt.rb +6 -0
  26. data/lib/trocla/formats/sha512crypt.rb +6 -0
  27. data/lib/trocla/formats/ssha.rb +9 -0
  28. data/lib/trocla/formats/sshkey.rb +46 -0
  29. data/lib/trocla/formats/x509.rb +197 -0
  30. data/lib/trocla/store.rb +80 -0
  31. data/lib/trocla/stores.rb +39 -0
  32. data/lib/trocla/stores/memory.rb +56 -0
  33. data/lib/trocla/stores/moneta.rb +58 -0
  34. data/lib/trocla/util.rb +71 -0
  35. data/lib/trocla/version.rb +22 -0
  36. data/spec/data/.keep +0 -0
  37. data/spec/spec_helper.rb +290 -0
  38. data/spec/trocla/encryptions/none_spec.rb +22 -0
  39. data/spec/trocla/encryptions/ssl_spec.rb +26 -0
  40. data/spec/trocla/formats/x509_spec.rb +375 -0
  41. data/spec/trocla/store/memory_spec.rb +6 -0
  42. data/spec/trocla/store/moneta_spec.rb +6 -0
  43. data/spec/trocla/util_spec.rb +54 -0
  44. data/spec/trocla_spec.rb +248 -0
  45. data/trocla-ruby2.gemspec +104 -0
  46. metadata +202 -0
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+
15
+ require 'jeweler'
16
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
17
+ require 'trocla'
18
+ Jeweler::Tasks.new do |gem|
19
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
20
+ gem.name = "trocla"
21
+ gem.homepage = "https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/"
22
+ gem.license = "GPLv3"
23
+ gem.summary = "Trocla a simple password generator and storage"
24
+ gem.description = "Trocla helps you to generate random passwords and to store them in various formats (plain, MD5, bcrypt) for later retrival."
25
+ gem.email = "mh+trocla@immerda.ch"
26
+ gem.authors = ["mh"]
27
+ gem.version = Trocla::VERSION::STRING
28
+ # dependencies defined in Gemfile
29
+ end
30
+ Jeweler::RubygemsDotOrgTasks.new
31
+
32
+ require 'rspec/core'
33
+ require 'rspec/core/rake_task'
34
+ RSpec::Core::RakeTask.new(:spec) do |spec|
35
+ spec.pattern = FileList['spec/**/*_spec.rb']
36
+ end
37
+
38
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
39
+ spec.pattern = 'spec/**/*_spec.rb'
40
+ spec.rcov = true
41
+ end
42
+
43
+ task :default => :spec
44
+
45
+ gem 'rdoc'
46
+ require 'rdoc/task'
47
+ RDoc::Task.new do |rdoc|
48
+ version = Trocla::VERSION::STRING
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "trocla #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/bin/trocla ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env ruby
2
+ # CLI client for Trocla.
3
+ #
4
+ require 'rubygems'
5
+ require 'trocla'
6
+ require 'optparse'
7
+ require 'yaml'
8
+
9
+ options = { :config_file => nil, :ask_password => true, :trace => false }
10
+
11
+ OptionParser.new do |opts|
12
+ opts.on('--version', '-V', 'Version information') do
13
+ puts Trocla::VERSION::STRING
14
+ exit
15
+ end
16
+
17
+ opts.on('--config CONFIG', '-c', 'Configuration file') do |v|
18
+ if File.exist?(v)
19
+ options[:config_file] = v
20
+ else
21
+ STDERR.puts "Cannot find config file: #{v}"
22
+ exit 1
23
+ end
24
+ end
25
+
26
+ opts.on('--trace', 'Show stack trace on failure') do
27
+ options[:trace] = true
28
+ end
29
+
30
+ opts.on('--no-random', 'Do not generate a random password if there is no plain text password available') do
31
+ options['random'] = false
32
+ end
33
+
34
+ opts.on('--no-format', 'Do not format a password when setting it using `set`') do
35
+ options['no_format'] = true
36
+ end
37
+
38
+ opts.on('--length LENGTH', 'Length for a randomly created password') do |v|
39
+ options['length'] = v.to_i
40
+ end
41
+
42
+ opts.on('--password [PASSWORD]', '-p', 'Provide password at command line or STDIN') do |pass|
43
+ options[:ask_password] = false
44
+ options[:password] = pass
45
+ end
46
+
47
+ end.parse!
48
+
49
+ def create(options)
50
+ [ Trocla.new(options.delete(:config_file)).password(
51
+ options.delete(:trocla_key),
52
+ options.delete(:trocla_format),
53
+ options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{})
54
+ ) , 0 ]
55
+ end
56
+
57
+ def get(options)
58
+ res = Trocla.new(options.delete(:config_file)).get_password(
59
+ options.delete(:trocla_key),
60
+ options.delete(:trocla_format),
61
+ options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{})
62
+ )
63
+ [ res, res.nil? ? 1 : 0 ]
64
+ end
65
+ def set(options)
66
+ if options.delete(:ask_password)
67
+ require 'highline/import'
68
+ password = ask('Enter your password: ') { |q| q.echo = 'x' }.to_s
69
+ pwd2 = ask('Repeat password: ') { |q| q.echo = 'x' }.to_s
70
+ unless password == pwd2
71
+ STDERR.puts 'Passwords did not match, exiting!'
72
+ return [ nil, 1 ]
73
+ end
74
+ else
75
+ password = options.delete(:password) || STDIN.read.chomp
76
+ end
77
+ format = options.delete(:trocla_format)
78
+ no_format = options.delete('no_format')
79
+ trocla = Trocla.new(options.delete(:config_file))
80
+ value = if no_format
81
+ password
82
+ else
83
+ trocla.formats(format).format(password, (YAML.load(options.delete(:other_options).shift.to_s)||{}))
84
+ end
85
+ trocla.set_password(
86
+ options.delete(:trocla_key),
87
+ format,
88
+ value
89
+ )
90
+ [ '', 0 ]
91
+ end
92
+
93
+ def reset(options)
94
+ [ Trocla.new(options.delete(:config_file)).reset_password(
95
+ options.delete(:trocla_key),
96
+ options.delete(:trocla_format),
97
+ options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{})
98
+ ), 0 ]
99
+ end
100
+
101
+ def delete(options)
102
+ [ Trocla.new(options.delete(:config_file)).delete_password(
103
+ options.delete(:trocla_key),
104
+ options.delete(:trocla_format)
105
+ ), 0 ]
106
+ end
107
+
108
+ def formats(options)
109
+ "Available formats: #{Trocla::Formats.all.join(', ')}"
110
+ end
111
+
112
+ def check_format(format_name)
113
+ if format_name.nil?
114
+ STDERR.puts 'Missing format, exiting...'
115
+ exit 1
116
+ elsif !Trocla::Formats.available?(format_name)
117
+ STDERR.puts "Error: The format #{format_name} is not available"
118
+ exit 1
119
+ end
120
+ end
121
+
122
+ actions=['create','get','set','reset','delete', 'formats' ]
123
+
124
+ if (action=ARGV.shift) && actions.include?(action)
125
+ options[:trocla_key] = ARGV.shift
126
+ options[:trocla_format] = ARGV.shift
127
+ options[:other_options] = ARGV
128
+ check_format(options[:trocla_format]) unless ['delete','formats'].include?(action)
129
+ begin
130
+ result, excode = send(action,options)
131
+ if result
132
+ puts result.is_a?(String) ? result : result.inspect
133
+ end
134
+ rescue Exception => e
135
+ unless e.message == 'exit'
136
+ STDERR.puts "Action failed with the following message: #{e.message}"
137
+ STDERR.puts '(See full trace by running task with --trace)'
138
+ end
139
+ raise e if options[:trace]
140
+ exit 1
141
+ end
142
+ exit excode.nil? ? 0 : excode
143
+ else
144
+ STDERR.puts "Please supply one of the following actions: #{actions.join(', ')}"
145
+ STDERR.puts "Use #{$0} --help to get a list of options for these actions"
146
+ exit 1
147
+ end
148
+
@@ -0,0 +1,120 @@
1
+ # Generated from trocla-0.1.2.gem by gem2rpm -*- rpm-spec -*-
2
+ %global gem_name trocla
3
+
4
+ Name: rubygem-%{gem_name}
5
+ Version: 0.3.0
6
+ Release: 1%{?dist}
7
+ Summary: Trocla a simple password generator and storage
8
+ Group: Development/Languages
9
+ License: GPLv3
10
+ URL: https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/
11
+ Source0: https://rubygems.org/gems/%{gem_name}-%{version}.gem
12
+ Requires: rubygem-moneta
13
+ Requires: rubygem-bcrypt
14
+ Requires: rubygem-highline
15
+ BuildRequires: rubygem-moneta = 0.7.20
16
+ BuildRequires: rubygem-bcrypt
17
+ BuildRequires: rubygem-highline
18
+ %if 0%{?rhel} >= 7
19
+ BuildRequires: ruby(release)
20
+ %endif
21
+ BuildRequires: rubygems-devel
22
+ BuildRequires: ruby
23
+ # BuildRequires: rubygem(mocha)
24
+ # BuildRequires: rubygem(rspec) => 2.4
25
+ # BuildRequires: rubygem(rspec) < 3
26
+ # BuildRequires: rubygem(jeweler) => 1.6
27
+ # BuildRequires: rubygem(jeweler) < 2
28
+ BuildArch: noarch
29
+
30
+ %description
31
+ Trocla helps you to generate random passwords and to store them in various
32
+ formats (plain, MD5, bcrypt) for later retrival.
33
+
34
+
35
+ %package doc
36
+ Summary: Documentation for %{name}
37
+ Group: Documentation
38
+ Requires: %{name} = %{version}-%{release}
39
+ BuildArch: noarch
40
+
41
+ %description doc
42
+ Documentation for %{name}.
43
+
44
+ %prep
45
+ gem unpack %{SOURCE0}
46
+
47
+ %setup -q -D -T -n %{gem_name}-%{version}
48
+
49
+ gem spec %{SOURCE0} -l --ruby > %{gem_name}.gemspec
50
+
51
+ %build
52
+ # Create the gem as gem install only works on a gem file
53
+ gem build %{gem_name}.gemspec
54
+
55
+ # %%gem_install compiles any C extensions and installs the gem into ./%%gem_dir
56
+ # by default, so that we can move it into the buildroot in %%install
57
+ %gem_install
58
+
59
+ %install
60
+ mkdir -p %{buildroot}%{gem_dir}
61
+ cp -a .%{gem_dir}/* \
62
+ %{buildroot}%{gem_dir}/
63
+
64
+
65
+ mkdir -p %{buildroot}%{_bindir}
66
+ mkdir -p %{buildroot}%{_sysconfdir}
67
+ mkdir -p %{buildroot}/%{_sharedstatedir}/%{gem_name}
68
+ touch %{buildroot}/%{_sharedstatedir}/%{gem_name}/%{gem_name}_data.yaml
69
+
70
+ cp -pa .%{_bindir}/* \
71
+ %{buildroot}%{_bindir}/
72
+
73
+ chmod a+x %{buildroot}%{gem_instdir}/bin/%{gem_name}
74
+
75
+ cat <<EOF > %{buildroot}/%{_sysconfdir}/%{gem_name}rc.yaml
76
+ ---
77
+ store: :moneta
78
+ store_options:
79
+ adapter: :YAML
80
+ adapter_options:
81
+ :file: '%{_sharedstatedir}/%{gem_name}/%{gem_name}_data.yaml'
82
+ EOF
83
+
84
+ # Run the test suite
85
+ %check
86
+ pushd .%{gem_instdir}
87
+
88
+ popd
89
+
90
+ %files
91
+ %dir %{gem_instdir}
92
+ %{_bindir}/trocla
93
+ %{gem_instdir}/.rspec
94
+ %exclude %{gem_instdir}/.travis.yml
95
+ %exclude %{gem_instdir}/.rspec
96
+ %exclude %{gem_instdir}/ext/redhat/%{name}.spec
97
+ %license %{gem_instdir}/LICENSE.txt
98
+ %{gem_instdir}/bin
99
+ %{gem_libdir}
100
+ %exclude %{gem_cache}
101
+ %{gem_spec}
102
+ %config(noreplace) %{_sysconfdir}/%{gem_name}rc.yaml
103
+ %dir %attr(-, -, -) %{_sharedstatedir}/%{gem_name}
104
+ %config(noreplace) %attr(660, root, root) %{_sharedstatedir}/%{gem_name}/%{gem_name}_data.yaml
105
+
106
+ %files doc
107
+ %doc %{gem_docdir}
108
+ %doc %{gem_instdir}/.document
109
+ %{gem_instdir}/Gemfile
110
+ %doc %{gem_instdir}/README.md
111
+ %doc %{gem_instdir}/CHANGELOG.md
112
+ %{gem_instdir}/Rakefile
113
+ %{gem_instdir}/spec
114
+ %{gem_instdir}/trocla.gemspec
115
+
116
+ %changelog
117
+ * Mon Dec 21 2015 mh - 0.2.0-1
118
+ - Release of v0.2.0
119
+ * Sun Jun 21 2015 mh - 0.1.2-1
120
+ - Initial package
data/lib/VERSION ADDED
@@ -0,0 +1,4 @@
1
+ major:0
2
+ minor:3
3
+ patch:0
4
+ build:
data/lib/trocla.rb ADDED
@@ -0,0 +1,162 @@
1
+ require 'trocla/version'
2
+ require 'trocla/util'
3
+ require 'trocla/formats'
4
+ require 'trocla/encryptions'
5
+ require 'trocla/stores'
6
+
7
+ class Trocla
8
+ def initialize(config_file=nil)
9
+ if config_file
10
+ @config_file = File.expand_path(config_file)
11
+ elsif File.exists?(def_config_file=File.expand_path('~/.troclarc.yaml')) || File.exists?(def_config_file=File.expand_path('/etc/troclarc.yaml'))
12
+ @config_file = def_config_file
13
+ end
14
+ end
15
+
16
+ def self.open(config_file=nil)
17
+ trocla = Trocla.new(config_file)
18
+
19
+ if block_given?
20
+ yield trocla
21
+ trocla.close
22
+ else
23
+ trocla
24
+ end
25
+ end
26
+
27
+ def password(key,format,options={})
28
+ # respect a default profile, but let the
29
+ # profiles win over the default options
30
+ options['profiles'] ||= config['options']['profiles']
31
+ if options['profiles']
32
+ options = merge_profiles(options['profiles']).merge(options)
33
+ end
34
+ options = config['options'].merge(options)
35
+
36
+ raise "Format #{format} is not supported! Supported formats: #{Trocla::Formats.all.join(', ')}" unless Trocla::Formats::available?(format)
37
+
38
+ unless (password=get_password(key,format,options)).nil?
39
+ return password
40
+ end
41
+
42
+ plain_pwd = get_password(key,'plain',options)
43
+ 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'
46
+ elsif !options['random'] && plain_pwd.nil?
47
+ raise "Password must be present as plaintext if you don't want a random password"
48
+ end
49
+ pwd = self.formats(format).format(plain_pwd,options)
50
+ # it's possible that meanwhile another thread/process was faster in
51
+ # formating the password. But we want todo that second lookup
52
+ # only for expensive formats
53
+ if self.formats(format).expensive?
54
+ get_password(key,format,options) || set_password(key, format, pwd, options)
55
+ else
56
+ set_password(key, format, pwd, options)
57
+ end
58
+ end
59
+
60
+ def get_password(key, format, options={})
61
+ render(format,decrypt(store.get(key,format)),options)
62
+ end
63
+
64
+ def reset_password(key,format,options={})
65
+ set_password(key,format,nil,options)
66
+ password(key,format,options)
67
+ end
68
+
69
+ def delete_password(key,format=nil,options={})
70
+ v = store.delete(key,format)
71
+ if v.is_a?(Hash)
72
+ Hash[*v.map do |f,encrypted_value|
73
+ [f,render(format,decrypt(encrypted_value),options)]
74
+ end.flatten]
75
+ else
76
+ render(format,decrypt(v),options)
77
+ end
78
+ end
79
+
80
+ def set_password(key,format,password,options={})
81
+ store.set(key,format,encrypt(password),options)
82
+ render(format,password,options)
83
+ end
84
+
85
+ def formats(format)
86
+ (@format_cache||={})[format] ||= Trocla::Formats[format].new(self)
87
+ end
88
+
89
+ def encryption
90
+ @encryption ||= Trocla::Encryptions[config['encryption']].new(config['encryption_options'],self)
91
+ end
92
+
93
+ def config
94
+ @config ||= read_config
95
+ end
96
+
97
+ def close
98
+ store.close
99
+ end
100
+
101
+ private
102
+ def store
103
+ @store ||= build_store
104
+ end
105
+
106
+ def build_store
107
+ s = config['store']
108
+ clazz = if s.is_a?(Symbol)
109
+ Trocla::Stores[s]
110
+ else
111
+ require config['store_require'] if config['store_require']
112
+ eval(s)
113
+ end
114
+ clazz.new(config['store_options'],self)
115
+ end
116
+
117
+ def read_config
118
+ if @config_file.nil?
119
+ default_config
120
+ else
121
+ raise "Configfile #{@config_file} does not exist!" unless File.exists?(@config_file)
122
+ c = default_config.merge(YAML.load(File.read(@config_file)))
123
+ c['profiles'] = default_config['profiles'].merge(c['profiles'])
124
+ # migrate all options to new store options
125
+ # TODO: remove workaround in 0.3.0
126
+ c['store_options']['adapter'] = c['adapter'] if c['adapter']
127
+ c['store_options']['adapter_options'] = c['adapter_options'] if c['adapter_options']
128
+ c['encryption_options'] = c['ssl_options'] if c['ssl_options']
129
+ c
130
+ end
131
+ end
132
+
133
+ def encrypt(value)
134
+ encryption.encrypt(value)
135
+ end
136
+
137
+ def decrypt(value)
138
+ return nil if value.nil?
139
+ encryption.decrypt(value)
140
+ end
141
+
142
+ def render(format,output,options={})
143
+ if format && output && f=self.formats(format)
144
+ f.render(output,options['render']||{})
145
+ else
146
+ output
147
+ end
148
+ end
149
+
150
+ def default_config
151
+ require 'yaml'
152
+ YAML.load(File.read(File.expand_path(File.join(File.dirname(__FILE__),'trocla','default_config.yaml'))))
153
+ end
154
+
155
+ def merge_profiles(profiles)
156
+ Array(profiles).inject({}) do |res,profile|
157
+ raise "No such profile #{profile} defined" unless profile_hash = config['profiles'][profile]
158
+ profile_hash.merge(res)
159
+ end
160
+ end
161
+
162
+ end