trocla-ruby2 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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