trocla-0.2.3/ 0000755 0000041 0000041 00000000000 12665377071 013046 5 ustar www-data www-data trocla-0.2.3/Rakefile 0000644 0000041 0000041 00000002773 12665377071 014524 0 ustar www-data www-data # encoding: utf-8
require 'rubygems'
require 'bundler'
begin
Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
$stderr.puts e.message
$stderr.puts "Run `bundle install` to install missing gems"
exit e.status_code
end
require 'rake'
require 'jeweler'
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
require 'trocla'
Jeweler::Tasks.new do |gem|
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
gem.name = "trocla"
gem.homepage = "https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/"
gem.license = "GPLv3"
gem.summary = "Trocla a simple password generator and storage"
gem.description = "Trocla helps you to generate random passwords and to store them in various formats (plain, MD5, bcrypt) for later retrival."
gem.email = "mh+trocla@immerda.ch"
gem.authors = ["mh"]
gem.version = Trocla::VERSION::STRING
# dependencies defined in Gemfile
end
Jeweler::RubygemsDotOrgTasks.new
require 'rspec/core'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = FileList['spec/**/*_spec.rb']
end
RSpec::Core::RakeTask.new(:rcov) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
spec.rcov = true
end
task :default => :spec
gem 'rdoc'
require 'rdoc/task'
RDoc::Task.new do |rdoc|
version = Trocla::VERSION::STRING
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "trocla #{version}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
trocla-0.2.3/bin/ 0000755 0000041 0000041 00000000000 12665377071 013616 5 ustar www-data www-data trocla-0.2.3/bin/trocla 0000755 0000041 0000041 00000007516 12665377071 015041 0 ustar www-data www-data #!/usr/bin/env ruby
# CLI client for Trocla.
#
require 'rubygems'
require 'trocla'
require 'optparse'
require 'yaml'
options = { :config_file => nil, :ask_password => true, :trace => false }
OptionParser.new do |opts|
opts.on('--version', '-V', 'Version information') do
puts Trocla::VERSION::STRING
exit
end
opts.on('--config CONFIG', '-c', 'Configuration file') do |v|
if File.exist?(v)
options[:config_file] = v
else
STDERR.puts "Cannot find config file: #{v}"
exit 1
end
end
opts.on('--trace', 'Show stack trace on failure') do
options[:trace] = true
end
opts.on('--no-random', 'Do not generate a random password if there is no plain text password available') do
options['random'] = false
end
opts.on('--no-format', 'Do not format a password when setting it using `set`') do
options['no_format'] = true
end
opts.on('--length LENGTH', 'Length for a randomly created password') do |v|
options['length'] = v.to_i
end
opts.on('--password [PASSWORD]', '-p', 'Provide password at command line or STDIN') do |pass|
options[:ask_password] = false
options[:password] = pass
end
end.parse!
def create(options)
Trocla.new(options.delete(:config_file)).password(
options.delete(:trocla_key),
options.delete(:trocla_format),
options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{})
)
end
def get(options)
Trocla.new(options.delete(:config_file)).get_password(
options.delete(:trocla_key),
options.delete(:trocla_format)
)
end
def set(options)
if options.delete(:ask_password)
require 'highline/import'
password = ask('Enter your password: ') { |q| q.echo = 'x' }.to_s
pwd2 = ask('Repeat password: ') { |q| q.echo = 'x' }.to_s
unless password == pwd2
STDERR.puts 'Passwords did not match, exiting!'
exit 1
end
else
password = options.delete(:password) || STDIN.read.chomp
end
format = options.delete(:trocla_format)
no_format = options.delete('no_format')
trocla = Trocla.new(options.delete(:config_file))
value = if no_format
password
else
trocla.formats(format).format(password, options.delete(:other_options).shift.to_s)
end
trocla.set_password(
options.delete(:trocla_key),
format,
value
)
''
end
def reset(options)
Trocla.new(options.delete(:config_file)).reset_password(
options.delete(:trocla_key),
options.delete(:trocla_format),
options.merge(YAML.load(options.delete(:other_options).shift.to_s)||{})
)
end
def delete(options)
Trocla.new(options.delete(:config_file)).delete_password(
options.delete(:trocla_key),
options.delete(:trocla_format)
)
end
def formats(options)
"Available formats: #{Trocla::Formats.all.join(', ')}"
end
def check_format(format_name)
if format_name.nil?
STDERR.puts 'Missing format, exiting...'
exit 1
elsif !Trocla::Formats.available?(format_name)
STDERR.puts "Error: The format #{format_name} is not available"
exit 1
end
end
actions=['create','get','set','reset','delete', 'formats' ]
if (action=ARGV.shift) && actions.include?(action)
options[:trocla_key] = ARGV.shift
options[:trocla_format] = ARGV.shift
options[:other_options] = ARGV
check_format(options[:trocla_format]) unless ['delete','formats'].include?(action)
begin
if result = send(action,options)
puts result.is_a?(String) ? result : result.inspect
end
rescue Exception => e
unless e.message == 'exit'
STDERR.puts "Action failed with the following message: #{e.message}"
STDERR.puts '(See full trace by running task with --trace)'
end
raise e if options[:trace]
exit 1
end
else
STDERR.puts "Please supply one of the following actions: #{actions.join(', ')}"
STDERR.puts "Use #{$0} --help to get a list of options for these actions"
exit 1
end
trocla-0.2.3/Gemfile 0000644 0000041 0000041 00000001306 12665377071 014341 0 ustar www-data www-data source "http://rubygems.org"
# Add dependencies required to use your gem here.
# Example:
# gem "activesupport", ">= 2.3.5"
if RUBY_VERSION.to_f > 1.8
gem "moneta"
gem "highline"
else
gem "moneta", "~> 0.7.20"
gem "highline", "~> 1.6.2"
end
if defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'jruby')
gem 'jruby-openssl'
end
gem "bcrypt"
# Add dependencies to develop your gem here.
# Include everything needed to run rake, tests, features, etc.
group :development do
if RUBY_VERSION.to_f > 1.8
gem "rspec"
gem "rdoc"
gem "jeweler"
else
gem "rspec", "~> 2.4"
gem "rdoc", "~> 3.8"
gem "jeweler", "~> 1.6"
gem "addressable", "~> 2.3.8"
end
gem 'rspec-pending_for'
end
trocla-0.2.3/.rspec 0000644 0000041 0000041 00000000010 12665377071 014152 0 ustar www-data www-data --color
trocla-0.2.3/LICENSE.txt 0000644 0000041 0000041 00000001253 12665377071 014672 0 ustar www-data www-data Trocla - a simple password generator and storage
Copyright (C) 2011-2015 Marcel Haerry
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
trocla-0.2.3/spec/ 0000755 0000041 0000041 00000000000 12665377071 014000 5 ustar www-data www-data trocla-0.2.3/spec/data/ 0000755 0000041 0000041 00000000000 12665377071 014711 5 ustar www-data www-data trocla-0.2.3/spec/data/.keep 0000644 0000041 0000041 00000000000 12665377071 015624 0 ustar www-data www-data trocla-0.2.3/spec/trocla_spec.rb 0000644 0000041 0000041 00000014276 12665377071 016635 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
describe "Trocla" do
before(:each) do
expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config)
@trocla = Trocla.new
end
describe "password" do
it "generates random passwords by default" do
expect(@trocla.password('random1','plain')).not_to eq(@trocla.password('random2','plain'))
end
it "generates passwords of length #{default_config['options']['length']}" do
expect(@trocla.password('random1','plain').length).to eq(default_config['options']['length'])
end
Trocla::Formats.all.each do |format|
describe "#{format} password format" do
it "retursn a password hashed in the #{format} format" do
expect(@trocla.password('some_test',format,format_options[format])).not_to be_empty
end
it "returns the same hashed for the #{format} format on multiple invocations" do
expect(round1=@trocla.password('some_test',format,format_options[format])).not_to be_empty
expect(@trocla.password('some_test',format,format_options[format])).to eq(round1)
end
it "also stores the plain password by default" do
pwd = @trocla.password('some_test','plain')
expect(pwd).not_to be_empty
expect(pwd.length).to eq(16)
end
end
end
Trocla::Formats.all.reject{|f| f == 'plain' }.each do |format|
it "raises an exception if not a random password is asked but plain password is not present for format #{format}" do
expect{@trocla.password('not_random',format, 'random' => false)}.to raise_error(/Password must be present as plaintext/)
end
end
describe 'with profiles' do
it 'raises an exception on unknown profile' do
expect{@trocla.password('no profile known','plain',
'profiles' => 'unknown_profile') }.to raise_error(/No such profile unknown_profile defined/)
end
it 'takes a profile and merge its options' do
pwd = @trocla.password('some_test','plain', 'profiles' => 'rootpw')
expect(pwd).not_to be_empty
expect(pwd.length).to eq(32)
expect(pwd).to_not match(/[={}\[\]\?%\*()&!]+/)
end
it 'is possible to combine profiles but first profile wins' do
pwd = @trocla.password('some_test','plain', 'profiles' => ['rootpw','login'])
expect(pwd).not_to be_empty
expect(pwd.length).to eq(32)
expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
end
it 'is possible to combine profiles but first profile wins 2' do
pwd = @trocla.password('some_test','plain', 'profiles' => ['login','mysql'])
expect(pwd).not_to be_empty
expect(pwd.length).to eq(16)
expect(pwd).not_to match(/[={}\[\]\?%\*()&!]+/)
end
it 'is possible to combine profiles but first profile wins 3' do
pwd = @trocla.password('some_test','plain', 'profiles' => ['mysql','login'])
expect(pwd).not_to be_empty
expect(pwd.length).to eq(32)
expect(pwd).to match(/[+%\/@=\?_.,:]+/)
end
end
end
describe "set_password" do
it "resets hashed passwords on a new plain password" do
expect(@trocla.password('set_test','mysql')).not_to be_empty
expect(@trocla.get_password('set_test','mysql')).not_to be_nil
expect(old_plain=@trocla.password('set_test','mysql')).not_to be_empty
expect(@trocla.set_password('set_test','plain','foobar')).not_to eq(old_plain)
expect(@trocla.get_password('set_test','mysql')).to be_nil
end
it "otherwise updates only the hash" do
expect(mysql = @trocla.password('set_test2','mysql')).not_to be_empty
expect(md5crypt = @trocla.password('set_test2','md5crypt')).not_to be_empty
expect(plain = @trocla.get_password('set_test2','plain')).not_to be_empty
expect(new_mysql = @trocla.set_password('set_test2','mysql','foo')).not_to eql(mysql)
expect(@trocla.get_password('set_test2','mysql')).to eq(new_mysql)
expect(@trocla.get_password('set_test2','md5crypt')).to eq(md5crypt)
expect(@trocla.get_password('set_test2','plain')).to eq(plain)
end
end
describe "reset_password" do
it "resets a password" do
plain1 = @trocla.password('reset_pwd','plain')
plain2 = @trocla.reset_password('reset_pwd','plain')
expect(plain1).not_to eq(plain2)
end
it "does not reset other formats" do
expect(mysql = @trocla.password('reset_pwd2','mysql')).not_to be_empty
expect(md5crypt1 = @trocla.password('reset_pwd2','md5crypt')).not_to be_empty
expect(md5crypt2 = @trocla.reset_password('reset_pwd2','md5crypt')).not_to be_empty
expect(md5crypt2).not_to eq(md5crypt1)
expect(@trocla.get_password('reset_pwd2','mysql')).to eq(mysql)
end
end
describe "delete_password" do
it "deletes all passwords if no format is given" do
expect(@trocla.password('delete_test1','mysql')).not_to be_nil
expect(@trocla.get_password('delete_test1','plain')).not_to be_nil
@trocla.delete_password('delete_test1')
expect(@trocla.get_password('delete_test1','plain')).to be_nil
expect(@trocla.get_password('delete_test1','mysql')).to be_nil
end
it "deletes only a given format" do
expect(@trocla.password('delete_test2','mysql')).not_to be_nil
expect(@trocla.get_password('delete_test2','plain')).not_to be_nil
@trocla.delete_password('delete_test2','plain')
expect(@trocla.get_password('delete_test2','plain')).to be_nil
expect(@trocla.get_password('delete_test2','mysql')).not_to be_nil
end
it "deletes only a given non-plain format" do
expect(@trocla.password('delete_test3','mysql')).not_to be_nil
expect(@trocla.get_password('delete_test3','plain')).not_to be_nil
@trocla.delete_password('delete_test3','mysql')
expect(@trocla.get_password('delete_test3','mysql')).to be_nil
expect(@trocla.get_password('delete_test3','plain')).not_to be_nil
end
end
def format_options
@format_options ||= Hash.new({}).merge({
'pgsql' => { 'username' => 'test' },
'x509' => { 'CN' => 'test' },
})
end
end
describe "VERSION" do
it "returns a version" do
expect(Trocla::VERSION::STRING).not_to be_empty
end
end
trocla-0.2.3/spec/spec_helper.rb 0000644 0000041 0000041 00000025774 12665377071 016635 0 ustar www-data www-data $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'rspec'
require 'rspec/pending_for'
require 'yaml'
require 'trocla'
# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
RSpec.shared_examples "encryption_basics" do
describe 'storing' do
it "random passwords" do
expect(@trocla.password('random1', 'plain').length).to eql(16)
end
it "long random passwords" do
expect(@trocla.set_password('random1_long','plain',4096.times.collect{|s| 'x' }.join('')).length).to eql(4096)
end
end
describe 'retrieve' do
it "random passwords" do
stored = @trocla.password('random1', 'plain')
retrieved = @trocla.password('random1', 'plain')
retrieved_again = @trocla.password('random1', 'plain')
expect(retrieved).to eql(stored)
expect(retrieved_again).to eql(stored)
expect(retrieved_again).to eql(retrieved)
end
it "encrypted passwords" do
@trocla.set_password('some_pass', 'plain', 'super secret')
expect(@trocla.get_password('some_pass', 'plain')).to eql('super secret')
end
end
describe 'deleting' do
it "plain" do
@trocla.set_password('some_pass', 'plain', 'super secret')
expect(@trocla.delete_password('some_pass', 'plain')).to eql('super secret')
end
it "delete formats" do
plain = @trocla.password('some_mysqlpass', 'plain')
mysql = @trocla.password('some_mysqlpass', 'mysql')
expect(@trocla.delete_password('some_mysqlpass', 'mysql')).to eql(mysql)
expect(@trocla.delete_password('some_mysqlpass', 'plain')).to eql(plain)
expect(@trocla.get_password('some_mysqlpass','plain')).to be_nil
expect(@trocla.get_password('some_mysqlpass','mysql')).to be_nil
end
it "all passwords" do
plain = @trocla.password('some_mysqlpass', 'plain')
mysql = @trocla.password('some_mysqlpass', 'mysql')
deleted = @trocla.delete_password('some_mysqlpass')
expect(deleted).to be_a_kind_of(Hash)
expect(deleted['plain']).to eql(plain)
expect(deleted['mysql']).to eql(mysql)
end
end
end
RSpec.shared_examples "verify_encryption" do
it "does not store plaintext passwords" do
@trocla.set_password('noplain', 'plain', 'plaintext_password')
expect(File.readlines(trocla_yaml_file).grep(/plaintext_password/)).to be_empty
end
it "makes sure identical passwords do not match when stored" do
@trocla.set_password('one_key', 'plain', 'super secret')
@trocla.set_password('another_key', 'plain', 'super secret')
yaml = YAML.load_file(trocla_yaml_file)
expect(yaml['one_key']['plain']).not_to eq(yaml['another_key']['plain'])
end
end
RSpec.shared_examples 'store_validation' do |store|
describe '.get' do
it { expect(store.get('some_key','plain')).to be_nil }
end
describe '.set' do
it 'stores nil values' do
store.set('some_nil_value','plain',nil)
expect(store.get('some_nil_value','plain')).to be_nil
end
it 'stores plain format' do
store.set('some_value','plain','value')
expect(store.get('some_value','plain')).to eql('value')
end
it 'stores other formats' do
store.set('some_value','foo','bla')
expect(store.get('some_value','foo')).to eql('bla')
end
it 'resets other formats on setting plain' do
store.set('some_value','foo','bla')
store.set('some_value','plain','value')
expect(store.get('some_value','plain')).to eql('value')
expect(store.get('some_value','foo')).to be_nil
end
end
describe '.delete' do
it { expect(store.delete('something','foo')).to be_nil }
it { expect(store.delete('something')).to be_empty }
it 'deletes the value of a format' do
store.set('some_value','foo','bla')
expect(store.delete('some_value','foo')).to eql('bla')
expect(store.get('some_value','foo')).to be_nil
end
it 'deletes only the value of a format' do
store.set('some_value','plain','value')
store.set('some_value','foo','bla')
expect(store.delete('some_value','plain')).to eql('value')
expect(store.get('some_value','plain')).to be_nil
expect(store.get('some_value','foo')).to eql('bla')
end
it 'deletes all values without a format' do
store.set('some_value','plain','value')
store.set('some_value','foo','bla')
hash = store.delete('some_value')
expect(hash).to be_a_kind_of(Hash)
expect(hash['plain']).to eql('value')
expect(hash['foo']).to eql('bla')
expect(store.get('some_value','plain')).to be_nil
expect(store.get('some_value','foo')).to be_nil
end
end
describe 'expiration' do
it 'will not return an expired key' do
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 2 })
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
sleep 3
expect(store.get('some_expiring_value','plain')).to be_nil
end
it 'increases expiration when setting anything for that key' do
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 2 })
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
sleep 1
store.set('some_expiring_value','bla','bla_to_be_expired',{ 'expires' => 3 })
sleep 2
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
sleep 2
expect(store.get('some_expiring_value','plain')).to be_nil
end
it 'keeps expiration when setting another value' do
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 2 })
store.set('some_expiring_value','foo','to_be_expired_foo')
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
sleep 3
expect(store.get('some_expiring_value','plain')).to be_nil
expect(store.get('some_expiring_value','foo')).to be_nil
end
it 'setting plain clears everything including expiration' do
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 2 })
sleep 1
store.set('some_expiring_value','plain','to_be_expired2')
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
sleep 3
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
end
it 'extends expiration when setting another value' do
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 4 })
sleep 2
store.set('some_expiring_value','foo','to_be_expired_foo')
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
sleep 3
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
sleep 2
expect(store.get('some_expiring_value','plain')).to be_nil
end
it 'extends expiration when deleting a format' do
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 4 })
store.set('some_expiring_value','foo','to_be_expired2')
sleep 2
expect(store.delete('some_expiring_value','foo')).to eql('to_be_expired2')
sleep 3
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
sleep 2
expect(store.get('some_expiring_value','plain')).to be_nil
end
it 'keeps expiration although we\'re fetching a value' do
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 3 })
sleep 2
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
sleep 2
expect(store.get('some_expiring_value','plain')).to be_nil
end
it 'readding a value with an expiration makes it expiring in the future' do
store.set('some_expiring_value','plain','to_be_expired')
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
sleep 3
expect(store.get('some_expiring_value','plain')).to be_nil
end
it 'setting an expires of false removes expiration' do
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => false })
sleep 3
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
end
it 'setting an expires of 0 removes expiration' do
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
store.set('some_expiring_value','plain','to_be_expired',{ 'expires' => 0 })
sleep 3
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired')
end
it 'setting an expires of false removes expiration even if it\'s for a different format' do
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
store.set('some_expiring_value','foo','to_be_expired_foo',{ 'expires' => false })
sleep 3
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
expect(store.get('some_expiring_value','foo')).to eql('to_be_expired_foo')
end
it 'setting an expires of 0 removes expiration even if it\'s for a different format' do
store.set('some_expiring_value','plain','to_be_expired2',{ 'expires' => 2 })
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
store.set('some_expiring_value','foo','to_be_expired_foo',{ 'expires' => 0 })
sleep 3
expect(store.get('some_expiring_value','plain')).to eql('to_be_expired2')
expect(store.get('some_expiring_value','foo')).to eql('to_be_expired_foo')
end
end
end
def default_config
@default_config ||= YAML.load(File.read(File.expand_path(base_dir+'/lib/trocla/default_config.yaml')))
end
def test_config
@config ||= default_config.merge({
'store' => :memory,
})
end
def test_config_persistent
@config ||= default_config.merge({
'store_options' => {
'adapter' => :YAML,
'adapter_options' => {
:file => trocla_yaml_file
},
},
})
end
def ssl_test_config
@ssl_config ||= test_config_persistent.merge({
'encryption' => :ssl,
'encryption_options' => {
:private_key => data_dir('trocla.key'),
:public_key => data_dir('trocla.pub'),
},
})
end
def base_dir
File.dirname(__FILE__)+'/../'
end
def data_dir(file = nil)
File.expand_path(File.join(base_dir, 'spec/data', file))
end
def trocla_yaml_file
data_dir('trocla_store.yaml')
end
def generate_ssl_keys
require 'openssl'
rsa_key = OpenSSL::PKey::RSA.new(4096)
File.open(data_dir('trocla.key'), 'w') { |f| f.write(rsa_key.to_pem) }
File.open(data_dir('trocla.pub'), 'w') { |f| f.write(rsa_key.public_key.to_pem) }
end
def remove_ssl_keys
File.unlink(data_dir('trocla.key'))
File.unlink(data_dir('trocla.pub'))
end
def remove_yaml_store
File.unlink(trocla_yaml_file)
end
trocla-0.2.3/spec/trocla/ 0000755 0000041 0000041 00000000000 12665377071 015264 5 ustar www-data www-data trocla-0.2.3/spec/trocla/formats/ 0000755 0000041 0000041 00000000000 12665377071 016737 5 ustar www-data www-data trocla-0.2.3/spec/trocla/formats/x509_spec.rb 0000644 0000041 0000041 00000041733 12665377071 021013 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
require 'date'
describe "Trocla::Format::X509" do
before(:each) do
expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config)
@trocla = Trocla.new
end
let(:ca_options) do
{
'CN' => 'This is my self-signed certificate which doubles as CA',
'become_ca' => true,
}
end
let(:cert_options) do
{
'ca' => 'my_shiny_selfsigned_ca',
'subject' => '/C=ZZ/O=Trocla Inc./CN=test/emailAddress=example@example.com',
}
end
def verify(ca,cert)
store = OpenSSL::X509::Store.new
store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
Array(ca).each do |c|
store.add_cert(c)
end
store.verify(cert)
end
describe "x509 selfsigned" do
it "is able to create self signed cert without being a ca by default" do
cert_str = @trocla.password('my_shiny_selfsigned_ca', 'x509', {
'CN' => 'This is my self-signed certificate',
'become_ca' => false,
})
cert = OpenSSL::X509::Certificate.new(cert_str)
# selfsigned?
expect(cert.issuer.to_s).to eq(cert.subject.to_s)
# default size
# https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
expect(cert.public_key.n.num_bytes * 8).to eq(4096)
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
# it's a self signed cert and NOT a CA
expect(verify(cert,cert)).to be false
v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
expect(v).to eq('CA:FALSE')
# we want to include only CNs that look like a DNS name
expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }).to be_nil
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
expect(ku).not_to match(/Certificate Sign/)
expect(ku).not_to match(/CRL Sign/)
end
it "is able to create a self signed cert that is a CA" do
ca_str = @trocla.password('my_shiny_selfsigned_ca', 'x509', ca_options)
ca = OpenSSL::X509::Certificate.new(ca_str)
# selfsigned?
expect(ca.issuer.to_s).to eq(ca.subject.to_s)
expect((Date.parse(ca.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
expect(verify(ca,ca)).to be true
v = ca.extensions.find{|e| e.oid == 'basicConstraints' }.value
expect(v).to eq('CA:TRUE')
ku = ca.extensions.find{|e| e.oid == 'keyUsage' }.value
expect(ku).to match(/Certificate Sign/)
expect(ku).to match(/CRL Sign/)
end
it "is able to create a self signed cert without any keyUsage restrictions" do
cert_str = @trocla.password('my_shiny_selfsigned_without restrictions', 'x509', {
'CN' => 'This is my self-signed certificate',
'key_usages' => [],
})
cert = OpenSSL::X509::Certificate.new(cert_str)
# selfsigned?
expect(cert.issuer.to_s).to eq(cert.subject.to_s)
# default size
# https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
expect(cert.public_key.n.num_bytes * 8).to eq(4096)
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
# it's a self signed cert and NOT a CA, but has no keyUsage limitation
expect(verify(cert,cert)).to be true
v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
expect(v).to_not eq('CA:TRUE')
expect(cert.extensions.find{|e| e.oid == 'keyUsage' }).to be_nil
end
it "is able to create a self signed cert with custom keyUsage restrictions" do
cert_str = @trocla.password('my_shiny_selfsigned_without restrictions', 'x509', {
'CN' => 'This is my self-signed certificate',
'key_usages' => [ 'cRLSign', ],
})
cert = OpenSSL::X509::Certificate.new(cert_str)
# selfsigned?
expect(cert.issuer.to_s).to eq(cert.subject.to_s)
# default size
# https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
expect(cert.public_key.n.num_bytes * 8).to eq(4096)
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
# it's a self signed cert and NOT a CA, as it's key is restricted to only CRL Sign
expect(verify(cert,cert)).to be false
v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
expect(v).to_not eq('CA:TRUE')
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
expect(ku).to match(/CRL Sign/)
expect(ku).not_to match(/Certificate Sign/)
end
end
describe "x509 signed by a ca" do
before(:each) do
ca_str = @trocla.password('my_shiny_selfsigned_ca', 'x509', ca_options)
@ca = OpenSSL::X509::Certificate.new(ca_str)
end
it 'is able to get a cert signed by the ca' do
cert_str = @trocla.password('mycert', 'x509', cert_options)
cert = OpenSSL::X509::Certificate.new(cert_str)
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
expect(verify(@ca,cert)).to be true
v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
expect(v).to eq('CA:FALSE')
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
expect(ku).not_to match(/Certificate Sign/)
expect(ku).not_to match(/CRL Sign/)
end
it 'supports fetching only the key' do
cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'keyonly' => true }))
expect(cert_str).not_to match(/-----BEGIN CERTIFICATE-----/)
expect(cert_str).to match(/-----BEGIN RSA PRIVATE KEY-----/)
end
it 'supports fetching only the cert' do
cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'certonly' => true }))
expect(cert_str).to match(/-----BEGIN CERTIFICATE-----/)
expect(cert_str).not_to match(/-----BEGIN RSA PRIVATE KEY-----/)
end
it 'supports fetching only the cert even a second time' do
cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'certonly' => true }))
expect(cert_str).to match(/-----BEGIN CERTIFICATE-----/)
expect(cert_str).not_to match(/-----BEGIN RSA PRIVATE KEY-----/)
cert_str = @trocla.password('mycert', 'x509', cert_options.merge('render' => {'certonly' => true }))
expect(cert_str).to match(/-----BEGIN CERTIFICATE-----/)
expect(cert_str).not_to match(/-----BEGIN RSA PRIVATE KEY-----/)
end
it 'does not simply increment the serial' do
cert_str = @trocla.password('mycert', 'x509', cert_options)
cert1 = OpenSSL::X509::Certificate.new(cert_str)
cert_str = @trocla.password('mycert2', 'x509', cert_options)
cert2 = OpenSSL::X509::Certificate.new(cert_str)
expect(cert1.serial.to_i).not_to eq(1)
expect(cert2.serial.to_i).not_to eq(2)
expect((cert2.serial - cert1.serial).to_i).not_to eq(1)
end
it 'is able to get a cert signed by the ca that is again a ca' do
cert_str = @trocla.password('mycert', 'x509', cert_options.merge({
'become_ca' => true,
}))
cert = OpenSSL::X509::Certificate.new(cert_str)
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
expect(verify(@ca,cert)).to be true
expect(cert.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
expect(ku).to match(/Certificate Sign/)
expect(ku).to match(/CRL Sign/)
end
it 'supports simple name constraints for CAs' do
ca2_str = @trocla.password('mycert_with_nc', 'x509', cert_options.merge({
'name_constraints' => ['example.com','bla.example.net'],
'become_ca' => true,
}))
ca2 = OpenSSL::X509::Certificate.new(ca2_str)
expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
expect((Date.parse(ca2.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
expect(verify(@ca,ca2)).to be true
end
expect(ca2.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
ku = ca2.extensions.find{|e| e.oid == 'keyUsage' }.value
expect(ku).to match(/Certificate Sign/)
expect(ku).to match(/CRL Sign/)
nc = ca2.extensions.find{|e| e.oid == 'nameConstraints' }.value
pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
expect(nc).to match(/Permitted:\n DNS:example.com\n DNS:bla.example.net/)
end
valid_cert_str = @trocla.password('myvalidexamplecert','x509', {
'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.com/emailAddress=example@example.com',
'ca' => 'mycert_with_nc'
})
valid_cert = OpenSSL::X509::Certificate.new(valid_cert_str)
expect(valid_cert.issuer.to_s).to eq(ca2.subject.to_s)
expect(verify([@ca,ca2],valid_cert)).to be true
expect((Date.parse(valid_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
false_cert_str = @trocla.password('myfalseexamplecert','x509', {
'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.net/emailAddress=example@example.com',
'ca' => 'mycert_with_nc'
})
false_cert = OpenSSL::X509::Certificate.new(false_cert_str)
expect(false_cert.issuer.to_s).to eq(ca2.subject.to_s)
expect(verify([@ca,ca2],false_cert)).to be false
expect((Date.parse(false_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
end
it 'supports simple name constraints for CAs with leading dots' do
ca2_str = @trocla.password('mycert_with_nc', 'x509', cert_options.merge({
'name_constraints' => ['.example.com','.bla.example.net'],
'become_ca' => true,
}))
ca2 = OpenSSL::X509::Certificate.new(ca2_str)
expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
expect((Date.parse(ca2.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
pending_for(:engine => 'jruby',:reason => 'NameConstraints verification seem to be broken in jRuby: https://github.com/jruby/jruby/issues/3502') do
expect(verify(@ca,ca2)).to be true
end
expect(ca2.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:TRUE')
ku = ca2.extensions.find{|e| e.oid == 'keyUsage' }.value
expect(ku).to match(/Certificate Sign/)
expect(ku).to match(/CRL Sign/)
nc = ca2.extensions.find{|e| e.oid == 'nameConstraints' }.value
expect(nc).to match(/Permitted:\n DNS:.example.com\n DNS:.bla.example.net/)
valid_cert_str = @trocla.password('myvalidexamplecert','x509', {
'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.com/emailAddress=example@example.com',
'ca' => 'mycert_with_nc'
})
valid_cert = OpenSSL::X509::Certificate.new(valid_cert_str)
expect(valid_cert.issuer.to_s).to eq(ca2.subject.to_s)
expect((Date.parse(valid_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
# workaround broken openssl
if %x{openssl version} =~ /1\.0\.[2-9]/
expect(verify([@ca,ca2],valid_cert)).to be true
else
skip_for(:engine => 'ruby',:reason => 'NameConstraints verification is broken on older openssl versions https://rt.openssl.org/Ticket/Display.html?id=3562') do
expect(verify([@ca,ca2],valid_cert)).to be true
end
end
false_cert_str = @trocla.password('myfalseexamplecert','x509', {
'subject' => '/C=ZZ/O=Trocla Inc./CN=foo.example.net/emailAddress=example@example.com',
'ca' => 'mycert_with_nc'
})
false_cert = OpenSSL::X509::Certificate.new(false_cert_str)
expect(false_cert.issuer.to_s).to eq(ca2.subject.to_s)
expect((Date.parse(false_cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
expect(verify([@ca,ca2],false_cert)).to be false
end
it 'is able to get a cert signed by the ca that is again a ca that is able to sign certs' do
ca2_str = @trocla.password('mycert_and_ca', 'x509', cert_options.merge({
'become_ca' => true,
}))
ca2 = OpenSSL::X509::Certificate.new(ca2_str)
expect(ca2.issuer.to_s).to eq(@ca.subject.to_s)
expect((Date.parse(ca2.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
expect(verify(@ca,ca2)).to be true
cert2_str = @trocla.password('mycert', 'x509', {
'ca' => 'mycert_and_ca',
'subject' => '/C=ZZ/O=Trocla Inc./CN=test2/emailAddress=example@example.com',
'become_ca' => true,
})
cert2 = OpenSSL::X509::Certificate.new(cert2_str)
expect(cert2.issuer.to_s).to eq(ca2.subject.to_s)
expect((Date.parse(cert2.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
skip_for(:engine => 'jruby',:reason => 'Chained CA validation seems to be broken on jruby atm.') do
expect(verify([@ca,ca2],cert2)).to be true
end
end
it 'respects all options' do
co = cert_options.merge({
'hash' => 'sha1',
'keysize' => 2048,
'days' => 3650,
'subject' => nil,
'C' => 'AA',
'ST' => 'Earth',
'L' => 'Here',
'O' => 'SSLTrocla',
'OU' => 'root',
'CN' => 'www.test',
'emailAddress' => 'test@example.com',
'altnames' => [ 'test', 'test1', 'test2', 'test3' ],
})
cert_str = @trocla.password('mycert', 'x509', co)
cert = OpenSSL::X509::Certificate.new(cert_str)
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
['C','ST','L','O','OU','CN'].each do |field|
expect(cert.subject.to_s).to match(/#{field}=#{co[field]}/)
end
expect(cert.subject.to_s).to match(/(Email|emailAddress)=#{co['emailAddress']}/)
hash_match = (defined?(RUBY_ENGINE) &&RUBY_ENGINE == 'jruby') ? 'RSA-SHA1' : 'sha1WithRSAEncryption'
expect(cert.signature_algorithm).to eq(hash_match)
expect(cert.not_before).to be < Time.now
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(3650)
# https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
expect(cert.public_key.n.num_bytes * 8).to eq(2048)
expect(verify(@ca,cert)).to be true
expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }.value).to eq('DNS:www.test, DNS:test, DNS:test1, DNS:test2, DNS:test3')
expect(cert.extensions.find{|e| e.oid == 'basicConstraints' }.value).to eq('CA:FALSE')
ku = cert.extensions.find{|e| e.oid == 'keyUsage' }.value
expect(ku).not_to match(/Certificate Sign/)
expect(ku).not_to match(/CRL Sign/)
end
it 'shold not add subject alt name on empty array' do
co = cert_options.merge({
'CN' => 'www.test',
'altnames' => []
})
cert_str = @trocla.password('mycert', 'x509', co)
cert = OpenSSL::X509::Certificate.new(cert_str)
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
expect(verify(@ca,cert)).to be true
expect(cert.extensions.find{|e| e.oid == 'subjectAltName' }).to be_nil
end
it 'prefers full subject of single subject parts' do
co = cert_options.merge({
'C' => 'AA',
'ST' => 'Earth',
'L' => 'Here',
'O' => 'SSLTrocla',
'OU' => 'root',
'CN' => 'www.test',
'emailAddress' => 'test@example.net',
})
cert_str = @trocla.password('mycert', 'x509', co)
cert = OpenSSL::X509::Certificate.new(cert_str)
['C','ST','L','O','OU','CN'].each do |field|
expect(cert.subject.to_s).not_to match(/#{field}=#{co[field]}/)
end
expect(cert.subject.to_s).not_to match(/(Email|emailAddress)=#{co['emailAddress']}/)
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
expect(verify(@ca,cert)).to be true
end
it "is able to create a signed cert with custom keyUsage restrictions" do
cert_str = @trocla.password('mycert_without_restrictions', 'x509', cert_options.merge({
'CN' => 'sign only test',
'key_usages' => [ ],
}))
cert = OpenSSL::X509::Certificate.new(cert_str)
# default size
# https://stackoverflow.com/questions/13747212/determine-key-size-from-public-key-pem-format
expect(cert.public_key.n.num_bytes * 8).to eq(4096)
expect((Date.parse(cert.not_after.localtime.to_s) - Date.today).to_i).to eq(365)
expect(cert.issuer.to_s).to eq(@ca.subject.to_s)
expect(verify(@ca,cert)).to be true
v = cert.extensions.find{|e| e.oid == 'basicConstraints' }.value
expect(v).to_not eq('CA:TRUE')
expect(cert.extensions.find{|e| e.oid == 'keyUsage' }).to be_nil
end
end
end
trocla-0.2.3/spec/trocla/encryptions/ 0000755 0000041 0000041 00000000000 12665377071 017641 5 ustar www-data www-data trocla-0.2.3/spec/trocla/encryptions/ssl_spec.rb 0000644 0000041 0000041 00000000772 12665377071 022007 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
describe "Trocla::Encryptions::Ssl" do
before(:all) do
generate_ssl_keys
end
after(:all) do
remove_ssl_keys
end
before(:each) do
expect_any_instance_of(Trocla).to receive(:read_config).and_return(ssl_test_config)
@trocla = Trocla.new
end
after(:each) do
remove_yaml_store
end
describe "encrypt" do
include_examples 'encryption_basics'
include_examples 'verify_encryption'
end
end
trocla-0.2.3/spec/trocla/encryptions/none_spec.rb 0000644 0000041 0000041 00000001143 12665377071 022136 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
describe "Trocla::Encryptions::None" do
before(:each) do
expect_any_instance_of(Trocla).to receive(:read_config).and_return(test_config_persistent)
@trocla = Trocla.new
end
after(:each) do
remove_yaml_store
end
describe "none" do
include_examples 'encryption_basics'
it "stores plaintext passwords" do
@trocla.set_password('noplain', 'plain', 'plaintext_password')
expect(File.readlines(trocla_yaml_file).grep(/plaintext_password/)).to eq([" plain: plaintext_password\n"])
end
end
end
trocla-0.2.3/spec/trocla/store/ 0000755 0000041 0000041 00000000000 12665377071 016420 5 ustar www-data www-data trocla-0.2.3/spec/trocla/store/memory_spec.rb 0000644 0000041 0000041 00000000331 12665377071 021264 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
require 'trocla/stores/memory'
describe Trocla::Stores::Memory do
include_examples 'store_validation', Trocla::Stores::Memory.new({},nil)
end
trocla-0.2.3/spec/trocla/store/moneta_spec.rb 0000644 0000041 0000041 00000000374 12665377071 021246 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
require 'trocla/stores/moneta'
describe Trocla::Stores::Moneta do
include_examples 'store_validation', Trocla::Stores::Moneta.new({'adapter' => :Memory},{:expires => true})
end
trocla-0.2.3/spec/trocla/util_spec.rb 0000644 0000041 0000041 00000002343 12665377071 017602 0 ustar www-data www-data require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe "Trocla::Util" do
{ :random_str => 12, :salt => 8 }.each do |m,length|
describe m do
it "is random" do
expect(Trocla::Util.send(m)).not_to eq(Trocla::Util.send(m))
end
it "defaults to length #{length}" do
expect(Trocla::Util.send(m).length).to eq(length)
end
it "is possible to change length" do
expect(Trocla::Util.send(m,8).length).to eq(8)
expect(Trocla::Util.send(m,32).length).to eq(32)
expect(Trocla::Util.send(m,1).length).to eq(1)
end
end
end
describe :numeric_generator do
10.times.each do |i|
it "creates random numeric password #{i}" do
expect(Trocla::Util.random_str(12, 'numeric')).to match(/^[0-9]{12}$/)
end
end
end
describe :hexadecimal_generator do
10.times.each do |i|
it "creates random hexadecimal password #{i}" do
expect(Trocla::Util.random_str(12, 'hexadecimal')).to match(/^[0-9a-f]{12}$/)
end
end
end
describe :salt do
10.times.each do |i|
it "contains only characters and numbers #{i}" do
expect(Trocla::Util.salt).to match(/^[a-z0-9]+$/i)
end
end
end
end
trocla-0.2.3/.travis.yml 0000644 0000041 0000041 00000000164 12665377071 015160 0 ustar www-data www-data language: ruby
sudo: false
rvm:
- jruby-18mode
- jruby-19mode
- 2.2.0
- 2.1.0
- 2.0.0
- 1.9.3
- 1.8.7
trocla-0.2.3/lib/ 0000755 0000041 0000041 00000000000 12665377071 013614 5 ustar www-data www-data trocla-0.2.3/lib/trocla/ 0000755 0000041 0000041 00000000000 12665377071 015100 5 ustar www-data www-data trocla-0.2.3/lib/trocla/stores/ 0000755 0000041 0000041 00000000000 12665377071 016417 5 ustar www-data www-data trocla-0.2.3/lib/trocla/stores/memory.rb 0000644 0000041 0000041 00000002442 12665377071 020256 0 ustar www-data www-data # a simple in memory store just as an example
class Trocla::Stores::Memory < Trocla::Store
attr_reader :memory
def initialize(config,trocla)
super(config,trocla)
@memory = Hash.new({})
end
def get(key,format)
unless expired?(key)
memory[key][format]
else
delete_all(key)
nil
end
end
def set(key,format,value,options={})
super(key,format,value,options)
set_expires(key,options['expires'])
end
private
def set_plain(key,value,options)
memory[key] = { 'plain' => value }
end
def set_format(key,format,value,options)
memory[key].merge!({ format => value })
end
def delete_all(key)
memory.delete(key)
end
def delete_format(key,format)
old_val = (h = memory[key]).delete(format)
h.empty? ? memory.delete(key) : memory[key] = h
set_expires(key,nil)
old_val
end
private
def set_expires(key,expires)
expires = memory[key]['_expires'] if expires.nil?
if expires && expires > 0
memory[key]['_expires'] = expires
memory[key]['_expires_at'] = Time.now + expires
else
memory[key].delete('_expires')
memory[key].delete('_expires_at')
end
end
def expired?(key)
memory.key?(key) &&
(a = memory[key]['_expires_at']).is_a?(Time) && \
(a < Time.now)
end
end
trocla-0.2.3/lib/trocla/stores/moneta.rb 0000644 0000041 0000041 00000002667 12665377071 020242 0 ustar www-data www-data # the default moneta based store
class Trocla::Stores::Moneta < Trocla::Store
attr_reader :moneta
def initialize(config,trocla)
super(config,trocla)
require 'moneta'
# load expire support by default
adapter_options = { :expires => true }.merge(
store_config['adapter_options']||{})
@moneta = Moneta.new(store_config['adapter'],adapter_options)
end
def get(key,format)
moneta.fetch(key, {})[format]
end
private
def set_plain(key,value,options)
h = { 'plain' => value }
mo = moneta_options(key,options)
if options['expires'] && options['expires'] > 0
h['_expires'] = options['expires']
else
# be sure that we disable the existing
# expires if nothing is set.
mo[:expires] = false
end
moneta.store(key,h,mo)
end
def set_format(key,format,value,options)
moneta.store(key,
moneta.fetch(key,{}).merge({ format => value }),
moneta_options(key,options))
end
def delete_all(key)
moneta.delete(key)
end
def delete_format(key,format)
old_val = (h = moneta.fetch(key,{})).delete(format)
h.empty? ? moneta.delete(key) : moneta.store(key,h,moneta_options(key,{}))
old_val
end
def moneta_options(key,options)
res = {}
if options.key?('expires')
res[:expires] = options['expires']
elsif e = moneta.fetch(key, {})['_expires']
res[:expires] = e
end
res
end
end
trocla-0.2.3/lib/trocla/formats/ 0000755 0000041 0000041 00000000000 12665377071 016553 5 ustar www-data www-data trocla-0.2.3/lib/trocla/formats/x509.rb 0000644 0000041 0000041 00000014361 12665377071 017612 0 ustar www-data www-data require 'openssl'
class Trocla::Formats::X509 < Trocla::Formats::Base
def format(plain_password,options={})
if plain_password.match(/-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/m)
# just an import, don't generate any new keys
return plain_password
end
cn = nil
if options['subject']
subject = options['subject']
if cna = OpenSSL::X509::Name.parse(subject).to_a.find{|e| e[0] == 'CN' }
cn = cna[1]
end
elsif options['CN']
subject = ''
cn = options['CN']
['C','ST','L','O','OU','CN','emailAddress'].each do |field|
subject << "/#{field}=#{options[field]}" if options[field]
end
else
raise "You need to pass \"subject\" or \"CN\" as an option to use this format"
end
hash = options['hash'] || 'sha2'
sign_with = options['ca']
become_ca = options['become_ca'] || false
keysize = options['keysize'] || 4096
days = options['days'].nil? ? 365 : options['days'].to_i
name_constraints = Array(options['name_constraints'])
key_usages = options['key_usages']
key_usages = Array(key_usages) if key_usages
altnames = if become_ca || (an = options['altnames']) && Array(an).empty?
[]
else
# ensure that we have the CN with us, but only if it
# it's like a hostname.
# This might have to be improved.
if cn.include?(' ')
Array(an).collect { |v| "DNS:#{v}" }.join(', ')
else
(["DNS:#{cn}"] + Array(an).collect { |v| "DNS:#{v}" }).uniq.join(', ')
end
end
begin
key = mkkey(keysize)
rescue Exception => e
puts e.backtrace
raise "Private key for #{subject} creation failed: #{e.message}"
end
cert = nil
if sign_with # certificate signed with CA
begin
ca_str = trocla.get_password(sign_with,'x509')
ca = OpenSSL::X509::Certificate.new(ca_str)
cakey = OpenSSL::PKey::RSA.new(ca_str)
caserial = getserial(sign_with)
rescue Exception => e
raise "Value of #{sign_with} can't be loaded as CA: #{e.message}"
end
begin
subj = OpenSSL::X509::Name.parse(subject)
request = mkreq(subj, key.public_key)
request.sign(key, signature(hash))
rescue Exception => e
raise "Certificate request #{subject} creation failed: #{e.message}"
end
begin
cert = mkcert(caserial, request.subject, ca, request.public_key, days,
altnames, key_usages, name_constraints, become_ca)
cert.sign(cakey, signature(hash))
addserial(sign_with, caserial)
rescue Exception => e
raise "Certificate #{subject} signing failed: #{e.message}"
end
else # self-signed certificate
begin
subj = OpenSSL::X509::Name.parse(subject)
cert = mkcert(getserial(subj), subj, nil, key.public_key, days,
altnames, key_usages, name_constraints, become_ca)
cert.sign(key, signature(hash))
rescue Exception => e
raise "Self-signed certificate #{subject} creation failed: #{e.message}"
end
end
key.to_pem + cert.to_pem
end
def render(output,render_options={})
if render_options['keyonly']
OpenSSL::PKey::RSA.new(output).to_pem
elsif render_options['certonly']
OpenSSL::X509::Certificate.new(output).to_pem
else
super(output,render_options)
end
end
private
# nice help: https://gist.github.com/mitfik/1922961
def signature(hash = 'sha2')
if hash == 'sha1'
OpenSSL::Digest::SHA1.new
elsif hash == 'sha224'
OpenSSL::Digest::SHA224.new
elsif hash == 'sha2' || hash == 'sha256'
OpenSSL::Digest::SHA256.new
elsif hash == 'sha384'
OpenSSL::Digest::SHA384.new
elsif hash == 'sha512'
OpenSSL::Digest::SHA512.new
else
raise "Unrecognized hash: #{hash}"
end
end
def mkkey(len)
OpenSSL::PKey::RSA.generate(len)
end
def mkreq(subject,public_key)
request = OpenSSL::X509::Request.new
request.subject = subject
request.public_key = public_key
request
end
def mkcert(serial,subject,issuer,public_key,days,altnames, key_usages = nil, name_constraints = [], become_ca = false)
cert = OpenSSL::X509::Certificate.new
issuer = cert if issuer == nil
cert.subject = subject
cert.issuer = issuer.subject
cert.not_before = Time.now
cert.not_after = Time.now + days * 24 * 60 * 60
cert.public_key = public_key
cert.serial = serial
cert.version = 2
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = issuer
cert.extensions = [ ef.create_extension("subjectKeyIdentifier", "hash") ]
if become_ca
cert.add_extension ef.create_extension("basicConstraints","CA:TRUE", true)
unless (ku = key_usages || ca_key_usages).empty?
cert.add_extension ef.create_extension("keyUsage", ku.join(', '), true)
end
if name_constraints && !name_constraints.empty?
cert.add_extension ef.create_extension("nameConstraints","permitted;DNS:#{name_constraints.join(',permitted;DNS:')}",true)
end
else
cert.add_extension ef.create_extension("subjectAltName", altnames, true) unless altnames.empty?
cert.add_extension ef.create_extension("basicConstraints","CA:FALSE", true)
unless (ku = key_usages || cert_key_usages).empty?
cert.add_extension ef.create_extension("keyUsage", ku.join(', '), true)
end
end
cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
cert
end
def getserial(ca)
newser = Trocla::Util.random_str(20,'hexadecimal').to_i(16)
all_serials(ca).include?(newser) ? getserial(ca) : newser
end
def all_serials(ca)
if allser = trocla.get_password("#{ca}_all_serials",'plain')
YAML.load(allser)
else
[]
end
end
def addserial(ca,serial)
serials = all_serials(ca) << serial
trocla.set_password("#{ca}_all_serials",'plain',YAML.dump(serials))
end
def cert_key_usages
['nonRepudiation', 'digitalSignature', 'keyEncipherment']
end
def ca_key_usages
['keyCertSign', 'cRLSign', 'nonRepudiation',
'digitalSignature', 'keyEncipherment' ]
end
end
trocla-0.2.3/lib/trocla/formats/mysql.rb 0000644 0000041 0000041 00000000314 12665377071 020243 0 ustar www-data www-data class Trocla::Formats::Mysql < Trocla::Formats::Base
require 'digest/sha1'
def format(plain_password,options={})
"*" + Digest::SHA1.hexdigest(Digest::SHA1.digest(plain_password)).upcase
end
end
trocla-0.2.3/lib/trocla/formats/sha1.rb 0000644 0000041 0000041 00000000324 12665377071 017733 0 ustar www-data www-data class Trocla::Formats::Sha1 < Trocla::Formats::Base
require 'digest/sha1'
require 'base64'
def format(plain_password,options={})
'{SHA}' + Base64.encode64(Digest::SHA1.digest(plain_password))
end
end
trocla-0.2.3/lib/trocla/formats/sha512crypt.rb 0000644 0000041 0000041 00000000271 12665377071 021165 0 ustar www-data www-data # salted crypt
class Trocla::Formats::Sha512crypt < Trocla::Formats::Base
def format(plain_password,options={})
plain_password.crypt('$6$' << Trocla::Util.salt << '$')
end
end
trocla-0.2.3/lib/trocla/formats/plain.rb 0000644 0000041 0000041 00000000200 12665377071 020173 0 ustar www-data www-data class Trocla::Formats::Plain < Trocla::Formats::Base
def format(plain_password,options={})
plain_password
end
end
trocla-0.2.3/lib/trocla/formats/sha256crypt.rb 0000644 0000041 0000041 00000000271 12665377071 021172 0 ustar www-data www-data # salted crypt
class Trocla::Formats::Sha256crypt < Trocla::Formats::Base
def format(plain_password,options={})
plain_password.crypt('$5$' << Trocla::Util.salt << '$')
end
end
trocla-0.2.3/lib/trocla/formats/ssha.rb 0000644 0000041 0000041 00000000455 12665377071 020042 0 ustar www-data www-data # salted crypt
require 'base64'
require 'digest'
class Trocla::Formats::Ssha < Trocla::Formats::Base
def format(plain_password,options={})
salt = options['salt'] || Trocla::Util.salt(16)
"{SSHA}"+Base64.encode64("#{Digest::SHA1.digest("#{plain_password}#{salt}")}#{salt}").chomp
end
end
trocla-0.2.3/lib/trocla/formats/bcrypt.rb 0000644 0000041 0000041 00000000254 12665377071 020404 0 ustar www-data www-data class Trocla::Formats::Bcrypt < Trocla::Formats::Base
require 'bcrypt'
def format(plain_password,options={})
BCrypt::Password.create(plain_password).to_s
end
end
trocla-0.2.3/lib/trocla/formats/md5crypt.rb 0000644 0000041 0000041 00000000266 12665377071 020653 0 ustar www-data www-data # salted crypt
class Trocla::Formats::Md5crypt < Trocla::Formats::Base
def format(plain_password,options={})
plain_password.crypt('$1$' << Trocla::Util.salt << '$')
end
end
trocla-0.2.3/lib/trocla/formats/pgsql.rb 0000644 0000041 0000041 00000000451 12665377071 020226 0 ustar www-data www-data class Trocla::Formats::Pgsql < Trocla::Formats::Base
require 'digest/md5'
def format(plain_password,options={})
raise "You need pass the username as an option to use this format" unless options['username']
"md5" + Digest::MD5.hexdigest(plain_password + options['username'])
end
end
trocla-0.2.3/lib/trocla/encryptions/ 0000755 0000041 0000041 00000000000 12665377071 017455 5 ustar www-data www-data trocla-0.2.3/lib/trocla/encryptions/ssl.rb 0000644 0000041 0000041 00000002062 12665377071 020603 0 ustar www-data www-data require 'openssl'
require 'base64'
class Trocla::Encryptions::Ssl < Trocla::Encryptions::Base
def encrypt(value)
ciphertext = ''
value.scan(/.{0,#{chunksize}}/m).each do |chunk|
ciphertext += Base64.encode64(public_key.public_encrypt(chunk)).gsub("\n",'')+"\n" if chunk
end
ciphertext
end
def decrypt(value)
plaintext = ''
value.split(/\n/).each do |line|
plaintext += private_key.private_decrypt(Base64.decode64(line)) if line
end
plaintext
end
private
def chunksize
public_key.n.num_bytes - 11
end
def private_key
@private_key ||= begin
file = require_option(:private_key)
OpenSSL::PKey::RSA.new(File.read(file), nil)
end
end
def public_key
@public_key ||= begin
file = require_option(:public_key)
OpenSSL::PKey::RSA.new(File.read(file), nil)
end
end
def option(key)
config[key]
end
def require_option(key)
val = option(key)
raise "Config error: 'ssl_options' => :#{key} is not defined" if val.nil?
val
end
end
trocla-0.2.3/lib/trocla/encryptions/none.rb 0000644 0000041 0000041 00000000214 12665377071 020736 0 ustar www-data www-data class Trocla::Encryptions::None < Trocla::Encryptions::Base
def encrypt(value)
value
end
def decrypt(value)
value
end
end
trocla-0.2.3/lib/trocla/default_config.yaml 0000644 0000041 0000041 00000001305 12665377071 020734 0 ustar www-data www-data ---
store: :moneta
store_options:
adapter: :YAML
adapter_options:
:file: '/tmp/trocla.yaml'
encryption: :none
options:
random: true
length: 16
charset: default
profiles:
rootpw:
charset: consolesafe
length: 32
mysql:
charset: shellsafe
length: 32
login:
charset: consolesafe
length: 16
x509veryverylong:
# 15 years
days: 5475
# 5475 days
expires: 466560000
x509verylong:
# 10 years
days: 3650
# 3600 days
expires: 311040000
x509long:
# 5 years
days: 1825
# 1800 days
expires: 155520000
x509auto:
days: 40
# 30 days
expires: 2592000
x509short:
days: 2
# 1 day
expires: 86400
trocla-0.2.3/lib/trocla/version.rb 0000644 0000041 0000041 00000001012 12665377071 017104 0 ustar www-data www-data # encoding: utf-8
class Trocla
class VERSION
version = {}
File.read(File.join(File.dirname(__FILE__), '../', 'VERSION')).each_line do |line|
type, value = line.chomp.split(":")
next if type =~ /^\s+$/ || value =~ /^\s+$/
version[type] = value
end
MAJOR = version['major']
MINOR = version['minor']
PATCH = version['patch']
BUILD = version['build']
STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
def self.version
STRING
end
end
end
trocla-0.2.3/lib/trocla/encryptions.rb 0000644 0000041 0000041 00000002377 12665377071 020013 0 ustar www-data www-data class Trocla::Encryptions
class Base
attr_reader :trocla, :config
def initialize(config, trocla)
@trocla = trocla
@config = config
end
def encrypt(value)
raise NoMethodError.new("#{self.class.name} needs to implement 'encrypt()'")
end
def decrypt(value)
raise NoMethodError.new("#{self.class.name} needs to implement 'decrypt()'")
end
end
class << self
def [](enc)
encryptions[enc.to_s.downcase]
end
def all
Dir[ path '*' ].collect do |enc|
File.basename(enc, '.rb').downcase
end
end
def available?(encryption)
all.include?(encryption.to_s.downcase)
end
private
def encryptions
@@encryptions ||= Hash.new do |hash, encryption|
encryption = encryption.to_s.downcase
if File.exists?( path encryption )
require "trocla/encryptions/#{encryption}"
class_name = "Trocla::Encryptions::#{encryption.capitalize}"
hash[encryption] = (eval class_name)
else
raise "Encryption #{encryption} is not supported!"
end
end
end
def path(encryption)
File.expand_path(
File.join(File.dirname(__FILE__), 'encryptions', "#{encryption}.rb")
)
end
end
end
trocla-0.2.3/lib/trocla/store.rb 0000644 0000041 0000041 00000003622 12665377071 016564 0 ustar www-data www-data # implements the default store behavior
class Trocla::Store
attr_reader :store_config, :trocla
def initialize(config,trocla)
@store_config = config
@trocla = trocla
end
# should return value for key & format
# returns nil if nothing or a nil value
# was found.
# If a key is expired it must return nil.
def get(key,format)
raise 'not implemented'
end
# sets value for key & format
# setting the plain format must invalidate
# all other formats as they should either
# be derived from plain or set directly.
# options is a hash containing further
# information for the store. e.g. expiration
# of a key. Keys can have an expiration /
# timeout by setting `expires` within
# the options hashs. Value of `expires`
# must be an integer indicating the
# amount of seconds a key can live with.
# This mechanism is expected to be
# be implemented by the backend.
def set(key,format,value,options={})
if format == 'plain'
set_plain(key,value,options)
else
set_format(key,format,value,options)
end
end
# deletes the value for format
# if format is nil everything is deleted
# returns value of format or hash of
# format => value # if everything is
# deleted.
def delete(key,format=nil)
format.nil? ? (delete_all(key)||{}) : delete_format(key,format)
end
private
# sets a new plain value
# *must* invalidate all
# other formats
def set_plain(key,value,options)
raise 'not implemented'
end
# sets a value of a format
def set_format(key,format,value,options)
raise 'not implemented'
end
# deletes all entries of this key
# and returns a hash with all
# formats and values
# or nil if nothing is found
def delete_all(key)
raise 'not implemented'
end
# deletes the value of the passed
# key & format and returns the
# value.
def delete_format(key,format)
raise 'not implemented'
end
end
trocla-0.2.3/lib/trocla/stores.rb 0000644 0000041 0000041 00000001540 12665377071 016744 0 ustar www-data www-data require 'trocla/store'
# store management
class Trocla::Stores
class << self
def [](store)
stores[store.to_s.downcase]
end
def all
@all ||= Dir[ path '*' ].collect do |store|
File.basename(store, '.rb').downcase
end
end
def available?(store)
all.include?(store.to_s.downcase)
end
private
def stores
@@stores ||= Hash.new do |hash, store|
store = store.to_s.downcase
if File.exists?(path(store))
require "trocla/stores/#{store}"
class_name = "Trocla::Stores::#{store.capitalize}"
hash[store] = (eval class_name)
else
raise "Store #{store} is not supported!"
end
end
end
def path(store)
File.expand_path(
File.join(File.dirname(__FILE__), 'stores', "#{store}.rb")
)
end
end
end
trocla-0.2.3/lib/trocla/util.rb 0000644 0000041 0000041 00000003521 12665377071 016403 0 ustar www-data www-data require 'securerandom'
class Trocla
class Util
class << self
def random_str(length=12, charset='default')
_charsets = charsets[charset] || charsets['default']
(1..length).collect{|a| _charsets[SecureRandom.random_number(_charsets.size)] }.join.to_s
end
def salt(length=8)
alphanumeric_size = alphanumeric.size
(1..length).collect{|a| alphanumeric[SecureRandom.random_number(alphanumeric_size)] }.join.to_s
end
private
def charsets
@charsets ||= begin
h = {
'default' => chars,
'alphanumeric' => alphanumeric,
'shellsafe' => shellsafe,
'windowssafe' => windowssafe,
'numeric' => numeric,
'hexadecimal' => hexadecimal,
'consolesafe' => consolesafe,
}
h.each { |k, v| h[k] = v.uniq }
end
end
def chars
@chars ||= shellsafe + special_chars
end
def shellsafe
@shellsafe ||= alphanumeric + shellsafe_chars
end
def windowssafe
@windowssafe ||= alphanumeric + windowssafe_chars
end
def consolesafe
@consolesafe ||= alphanumeric + consolesafe_chars
end
def hexadecimal
@hexadecimal ||= numeric + ('a'..'f').to_a
end
def alphanumeric
@alphanumeric ||= ('a'..'z').to_a + ('A'..'Z').to_a + numeric
end
def numeric
@numeric ||= ('0'..'9').to_a
end
def special_chars
@special_chars ||= "*()&![]{}-".split(//)
end
def shellsafe_chars
@shellsafe_chars ||= "+%/@=?_.,:".split(//)
end
def windowssafe_chars
@windowssafe_chars ||= "+%/@=?_.,".split(//)
end
def consolesafe_chars
@consolesafe_chars ||= '+.-,_'.split(//)
end
end
end
end
trocla-0.2.3/lib/trocla/formats.rb 0000644 0000041 0000041 00000001703 12665377071 017101 0 ustar www-data www-data class Trocla::Formats
class Base
attr_reader :trocla
def initialize(trocla)
@trocla = trocla
end
def render(output,render_options={})
output
end
end
class << self
def [](format)
formats[format.downcase]
end
def all
Dir[File.expand_path(File.join(File.dirname(__FILE__),'formats','*.rb'))].collect{|f| File.basename(f,'.rb').downcase }
end
def available?(format)
all.include?(format.downcase)
end
private
def formats
@@formats ||= Hash.new do |hash, format|
format = format.downcase
if File.exists?(path(format))
require "trocla/formats/#{format}"
hash[format] = (eval "Trocla::Formats::#{format.capitalize}")
else
raise "Format #{format} is not supported!"
end
end
end
def path(format)
File.expand_path(File.join(File.dirname(__FILE__),'formats',"#{format}.rb"))
end
end
end
trocla-0.2.3/lib/trocla.rb 0000644 0000041 0000041 00000010006 12665377071 015422 0 ustar www-data www-data require 'trocla/version'
require 'trocla/util'
require 'trocla/formats'
require 'trocla/encryptions'
require 'trocla/stores'
class Trocla
def initialize(config_file=nil)
if config_file
@config_file = File.expand_path(config_file)
elsif File.exists?(def_config_file=File.expand_path('~/.troclarc.yaml')) || File.exists?(def_config_file=File.expand_path('/etc/troclarc.yaml'))
@config_file = def_config_file
end
end
def password(key,format,options={})
# respect a default profile, but let the
# profiles win over the default options
options['profiles'] ||= config['options']['profiles']
if options['profiles']
options = merge_profiles(options['profiles']).merge(options)
end
options = config['options'].merge(options)
raise "Format #{format} is not supported! Supported formats: #{Trocla::Formats.all.join(', ')}" unless Trocla::Formats::available?(format)
unless (password=get_password(key,format,options)).nil?
return password
end
plain_pwd = get_password(key,'plain',options)
if options['random'] && plain_pwd.nil?
plain_pwd = Trocla::Util.random_str(options['length'].to_i,options['charset'])
set_password(key,'plain',plain_pwd,options) unless format == 'plain'
elsif !options['random'] && plain_pwd.nil?
raise "Password must be present as plaintext if you don't want a random password"
end
set_password(key,
format,
self.formats(format).format(plain_pwd,options),
options)
end
def get_password(key, format, options={})
render(format,decrypt(store.get(key,format)),options)
end
def reset_password(key,format,options={})
set_password(key,format,nil,options)
password(key,format,options)
end
def delete_password(key,format=nil,options={})
v = store.delete(key,format)
if v.is_a?(Hash)
Hash[*v.map do |f,encrypted_value|
[f,render(format,decrypt(encrypted_value),options)]
end.flatten]
else
render(format,decrypt(v),options)
end
end
def set_password(key,format,password,options={})
store.set(key,format,encrypt(password),options)
render(format,password,options)
end
def formats(format)
(@format_cache||={})[format] ||= Trocla::Formats[format].new(self)
end
def encryption
@encryption ||= Trocla::Encryptions[config['encryption']].new(config['encryption_options'],self)
end
def config
@config ||= read_config
end
private
def store
@store ||= build_store
end
def build_store
s = config['store']
clazz = if s.is_a?(Symbol)
Trocla::Stores[s]
else
require config['store_require'] if config['store_require']
eval(s)
end
clazz.new(config['store_options'],self)
end
def read_config
if @config_file.nil?
default_config
else
raise "Configfile #{@config_file} does not exist!" unless File.exists?(@config_file)
c = default_config.merge(YAML.load(File.read(@config_file)))
c['profiles'] = default_config['profiles'].merge(c['profiles'])
# migrate all options to new store options
# TODO: remove workaround in 0.3.0
c['store_options']['adapter'] = c['adapter'] if c['adapter']
c['store_options']['adapter_options'] = c['adapter_options'] if c['adapter_options']
c['encryption_options'] = c['ssl_options'] if c['ssl_options']
c
end
end
def encrypt(value)
encryption.encrypt(value)
end
def decrypt(value)
return nil if value.nil?
encryption.decrypt(value)
end
def render(format,output,options={})
if format && output && f=self.formats(format)
f.render(output,options['render']||{})
else
output
end
end
def default_config
require 'yaml'
YAML.load(File.read(File.expand_path(File.join(File.dirname(__FILE__),'trocla','default_config.yaml'))))
end
def merge_profiles(profiles)
Array(profiles).inject({}) do |res,profile|
raise "No such profile #{profile} defined" unless profile_hash = config['profiles'][profile]
profile_hash.merge(res)
end
end
end
trocla-0.2.3/lib/VERSION 0000644 0000041 0000041 00000000037 12665377071 014664 0 ustar www-data www-data major:0
minor:2
patch:3
build:
trocla-0.2.3/metadata.yml 0000644 0000041 0000041 00000011117 12665377071 015352 0 ustar www-data www-data --- !ruby/object:Gem::Specification
name: trocla
version: !ruby/object:Gem::Version
version: 0.2.3
platform: ruby
authors:
- mh
autorequire:
bindir: bin
cert_chain: []
date: 2016-02-15 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: moneta
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: highline
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: bcrypt
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rspec
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rdoc
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: jeweler
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
- !ruby/object:Gem::Dependency
name: rspec-pending_for
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
description: Trocla helps you to generate random passwords and to store them in various
formats (plain, MD5, bcrypt) for later retrival.
email: mh+trocla@immerda.ch
executables:
- trocla
extensions: []
extra_rdoc_files:
- LICENSE.txt
- README.md
files:
- ".document"
- ".rspec"
- ".travis.yml"
- CHANGELOG.md
- Gemfile
- LICENSE.txt
- README.md
- Rakefile
- bin/trocla
- ext/redhat/rubygem-trocla.spec
- lib/VERSION
- lib/trocla.rb
- lib/trocla/default_config.yaml
- lib/trocla/encryptions.rb
- lib/trocla/encryptions/none.rb
- lib/trocla/encryptions/ssl.rb
- lib/trocla/formats.rb
- lib/trocla/formats/bcrypt.rb
- lib/trocla/formats/md5crypt.rb
- lib/trocla/formats/mysql.rb
- lib/trocla/formats/pgsql.rb
- lib/trocla/formats/plain.rb
- lib/trocla/formats/sha1.rb
- lib/trocla/formats/sha256crypt.rb
- lib/trocla/formats/sha512crypt.rb
- lib/trocla/formats/ssha.rb
- lib/trocla/formats/x509.rb
- lib/trocla/store.rb
- lib/trocla/stores.rb
- lib/trocla/stores/memory.rb
- lib/trocla/stores/moneta.rb
- lib/trocla/util.rb
- lib/trocla/version.rb
- spec/data/.keep
- spec/spec_helper.rb
- spec/trocla/encryptions/none_spec.rb
- spec/trocla/encryptions/ssl_spec.rb
- spec/trocla/formats/x509_spec.rb
- spec/trocla/store/memory_spec.rb
- spec/trocla/store/moneta_spec.rb
- spec/trocla/util_spec.rb
- spec/trocla_spec.rb
- trocla.gemspec
homepage: https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/
licenses:
- GPLv3
metadata: {}
post_install_message:
rdoc_options: []
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project:
rubygems_version: 2.2.2
signing_key:
specification_version: 4
summary: Trocla a simple password generator and storage
test_files: []
trocla-0.2.3/trocla.gemspec 0000644 0000041 0000041 00000006577 12665377071 015716 0 ustar www-data www-data # Generated by jeweler
# DO NOT EDIT THIS FILE DIRECTLY
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
# -*- encoding: utf-8 -*-
# stub: trocla 0.2.3 ruby lib
Gem::Specification.new do |s|
s.name = "trocla"
s.version = "0.2.3"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib"]
s.authors = ["mh"]
s.date = "2016-02-15"
s.description = "Trocla helps you to generate random passwords and to store them in various formats (plain, MD5, bcrypt) for later retrival."
s.email = "mh+trocla@immerda.ch"
s.executables = ["trocla"]
s.extra_rdoc_files = [
"LICENSE.txt",
"README.md"
]
s.files = [
".document",
".rspec",
".travis.yml",
"CHANGELOG.md",
"Gemfile",
"LICENSE.txt",
"README.md",
"Rakefile",
"bin/trocla",
"ext/redhat/rubygem-trocla.spec",
"lib/VERSION",
"lib/trocla.rb",
"lib/trocla/default_config.yaml",
"lib/trocla/encryptions.rb",
"lib/trocla/encryptions/none.rb",
"lib/trocla/encryptions/ssl.rb",
"lib/trocla/formats.rb",
"lib/trocla/formats/bcrypt.rb",
"lib/trocla/formats/md5crypt.rb",
"lib/trocla/formats/mysql.rb",
"lib/trocla/formats/pgsql.rb",
"lib/trocla/formats/plain.rb",
"lib/trocla/formats/sha1.rb",
"lib/trocla/formats/sha256crypt.rb",
"lib/trocla/formats/sha512crypt.rb",
"lib/trocla/formats/ssha.rb",
"lib/trocla/formats/x509.rb",
"lib/trocla/store.rb",
"lib/trocla/stores.rb",
"lib/trocla/stores/memory.rb",
"lib/trocla/stores/moneta.rb",
"lib/trocla/util.rb",
"lib/trocla/version.rb",
"spec/data/.keep",
"spec/spec_helper.rb",
"spec/trocla/encryptions/none_spec.rb",
"spec/trocla/encryptions/ssl_spec.rb",
"spec/trocla/formats/x509_spec.rb",
"spec/trocla/store/memory_spec.rb",
"spec/trocla/store/moneta_spec.rb",
"spec/trocla/util_spec.rb",
"spec/trocla_spec.rb",
"trocla.gemspec"
]
s.homepage = "https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/"
s.licenses = ["GPLv3"]
s.rubygems_version = "2.2.2"
s.summary = "Trocla a simple password generator and storage"
if s.respond_to? :specification_version then
s.specification_version = 4
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q, [">= 0"])
s.add_runtime_dependency(%q, [">= 0"])
s.add_runtime_dependency(%q, [">= 0"])
s.add_development_dependency(%q, [">= 0"])
s.add_development_dependency(%q, [">= 0"])
s.add_development_dependency(%q, [">= 0"])
s.add_development_dependency(%q, [">= 0"])
else
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
end
else
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
s.add_dependency(%q, [">= 0"])
end
end
trocla-0.2.3/CHANGELOG.md 0000644 0000041 0000041 00000010006 12665377071 014654 0 ustar www-data www-data # Changelog
## to 0.2.3
1. Add extended CA validity profiles
1. Make it possible to define keyUsage
## to 0.2.2
1. Bugfix to render output correctly also on an already existing set
1. Fix tests not working around midnight, due to timezone differences
## to 0.2.1
1. New Feature: Introduce a way to render specific formats, mainly this allows you to control the output of a specific format. See the x509 format for more information.
## to 0.2.0
1. New feature profiles: Introduce profiles to make it easy to have a default set of properties. See the profiles section for more information.
1. New feature expiration: Make it possible that keys can have an expiration. See the expiration section for more information.
1. Increase default password length to 16.
1. Add a console safe password charset. It should provide a subset of chars that are easier to type on a physical keyboard.
1. Fix a bug with encryptions while deleting all formats.
1. Introduce pluggable stores, so in the future we are able to talk to different backends and not only moneta. For testing and inspiration a simple in memory storage backend was added.
1. CHANGE: moneta's configuration for `adapter` & `adapter_options` now live under store_options in the configuration file. Till 0.3.0 old configuration entries will still be accepted.
1. CHANGE: ssl_options is now known as encryption_options. Till 0.3.0 old configuration entries will still be accepted.
1. Improve randomness when creating a serial number.
1. Add a new charset: hexadecimal
1. Add support for name constraints within the x509 format
1. Clarify documentation of the set action, as well as introduce `--no-format` for the set action.
## to 0.1.3
1. CHANGE: Self signed certificates are no longer CAs by default, actually they have never been due to a bug. If you want that a certificate is also a CA, you *must* pass `become_ca: true` to the options hash. But this makes it actually possible, that you can even have certificate chains. Thanks for initial hint to [Adrien Bréfort](https://github.com/abrefort)
1. Default keysize is now 4096
1. SECURITY: Do not increment serial, rather choose a random one.
1. Fixing setting of altnames, was not possible due to bug, till now.
1. Add extended tests for the x509 format, that describe all the internal specialities and should give an idea how it can be used.
1. Add cli option to list all formats
## to 0.1.1
1. fix storing data longer that public Keysize -11. Thanks [Timo Goebel](https://github.com/timogoebel)
1. add a numeric only charset. Thanks [Jonas Genannt](https://github.com/hggh)
1. fix reading key expire time. Thanks [asquelt](https://github.com/asquelt)
## to 0.1.0
1. Supporting encryption of the backends. Many thanks to Thomas Gelf
1. Adding a windows safe password charset
## to 0.0.12
1. change from sha1 signature for the x509 format to sha2
1. Fix an issue where shellsafe characters might have already been initialized with shell-unsafe characters. Plz review any shell-safe character passwords regarding this problem. See the [fix](https://github.com/duritong/trocla/pull/19) for more information. Thanks [asquelt](https://github.com/asquelt) for the fix.
## to 0.0.8
1. be sure to update as well the moneta gem, trocla now uses the official moneta releases and supports current avaiable versions.
1. Options for moneta's backends have changed. For example, if you are using the yaml-backend you will likely need to change the adapter option `:path:` to `:file:` to match moneta's new API.
1. **IMPORTANT:** If you are using the yaml backend you need to migrate the current data *before* using the new trocla version! You can migrate the datastore by using the following two sed commands: `sed -i 's/^\s\{3\}/ /' /PATH/TO/trocla_data.yaml` && `sed -i '/^\s\{2\}value\:/d' /PATH/TO/trocla_data.yaml`.
1. **SECURITY:** Previous versions of trocla used quite a simple random generator. Especially in combination with the puppet `fqdn_rand` function, you likely have very predictable random passwords and I recommend you to regenerate all randomly generated passwords! Now!
trocla-0.2.3/ext/ 0000755 0000041 0000041 00000000000 12665377071 013646 5 ustar www-data www-data trocla-0.2.3/ext/redhat/ 0000755 0000041 0000041 00000000000 12665377071 015115 5 ustar www-data www-data trocla-0.2.3/ext/redhat/rubygem-trocla.spec 0000644 0000041 0000041 00000005724 12665377071 020735 0 ustar www-data www-data # Generated from trocla-0.1.2.gem by gem2rpm -*- rpm-spec -*-
%global gem_name trocla
Name: rubygem-%{gem_name}
Version: 0.2.2
Release: 1%{?dist}
Summary: Trocla a simple password generator and storage
Group: Development/Languages
License: GPLv3
URL: https://tech.immerda.ch/2011/12/trocla-get-hashed-passwords-out-of-puppet-manifests/
Source0: https://rubygems.org/gems/%{gem_name}-%{version}.gem
Requires: rubygem-moneta
Requires: rubygem-bcrypt
Requires: rubygem-highline
BuildRequires: rubygem-moneta = 0.7.20
BuildRequires: rubygem-bcrypt
BuildRequires: rubygem-highline
%if 0%{?rhel} >= 7
BuildRequires: ruby(release)
%endif
BuildRequires: rubygems-devel
BuildRequires: ruby
# BuildRequires: rubygem(mocha)
# BuildRequires: rubygem(rspec) => 2.4
# BuildRequires: rubygem(rspec) < 3
# BuildRequires: rubygem(jeweler) => 1.6
# BuildRequires: rubygem(jeweler) < 2
BuildArch: noarch
%description
Trocla helps you to generate random passwords and to store them in various
formats (plain, MD5, bcrypt) for later retrival.
%package doc
Summary: Documentation for %{name}
Group: Documentation
Requires: %{name} = %{version}-%{release}
BuildArch: noarch
%description doc
Documentation for %{name}.
%prep
gem unpack %{SOURCE0}
%setup -q -D -T -n %{gem_name}-%{version}
gem spec %{SOURCE0} -l --ruby > %{gem_name}.gemspec
%build
# Create the gem as gem install only works on a gem file
gem build %{gem_name}.gemspec
# %%gem_install compiles any C extensions and installs the gem into ./%%gem_dir
# by default, so that we can move it into the buildroot in %%install
%gem_install
%install
mkdir -p %{buildroot}%{gem_dir}
cp -a .%{gem_dir}/* \
%{buildroot}%{gem_dir}/
mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_sysconfdir}
mkdir -p %{buildroot}/%{_sharedstatedir}/%{gem_name}
touch %{buildroot}/%{_sharedstatedir}/%{gem_name}/%{gem_name}_data.yaml
cp -pa .%{_bindir}/* \
%{buildroot}%{_bindir}/
chmod a+x %{buildroot}%{gem_instdir}/bin/%{gem_name}
cat < %{buildroot}/%{_sysconfdir}/%{gem_name}rc.yaml
---
adapter: :YAML
adapter_options:
:file: '%{_sharedstatedir}/%{gem_name}/%{gem_name}_data.yaml'
EOF
# Run the test suite
%check
pushd .%{gem_instdir}
popd
%files
%dir %{gem_instdir}
%{_bindir}/trocla
%{gem_instdir}/.rspec
%exclude %{gem_instdir}/.travis.yml
%exclude %{gem_instdir}/.rspec
%exclude %{gem_instdir}/ext/redhat/%{name}.spec
%license %{gem_instdir}/LICENSE.txt
%{gem_instdir}/bin
%{gem_libdir}
%exclude %{gem_cache}
%{gem_spec}
%config(noreplace) %{_sysconfdir}/%{gem_name}rc.yaml
%dir %attr(-, -, -) %{_sharedstatedir}/%{gem_name}
%config(noreplace) %attr(660, root, root) %{_sharedstatedir}/%{gem_name}/%{gem_name}_data.yaml
%files doc
%doc %{gem_docdir}
%doc %{gem_instdir}/.document
%{gem_instdir}/Gemfile
%doc %{gem_instdir}/README.md
%doc %{gem_instdir}/CHANGELOG.md
%{gem_instdir}/Rakefile
%{gem_instdir}/spec
%{gem_instdir}/trocla.gemspec
%changelog
* Mon Dec 21 2015 mh - 0.2.0-1
- Release of v0.2.0
* Sun Jun 21 2015 mh - 0.1.2-1
- Initial package
trocla-0.2.3/README.md 0000644 0000041 0000041 00000036226 12665377071 014336 0 ustar www-data www-data # trocla
[](https://travis-ci.org/duritong/trocla)
Trocla provides you a simple way to create and store (random) passwords on a
central server, which can be retrieved by other applications. An example for
such an application is puppet and trocla can help you to not store any
plaintext or hashed passwords in your manifests by keeping these passwords only
on your puppetmaster.
Furthermore it provides you a simple cli that helps you to modify the password
storage from the cli.
Trocla does not only create and/or store a plain password, it is also able to
generate (and store) any kind of hashed passwords based on the plain password.
As long as the plain password is preset, trocla is able to generate any kind
of hashed passwords through an easy extendible plugin system.
It is not necessary to store the plain password on the server, you can also
just feed trocla with the hashed password and use that in your other tools.
A common example for that is that you let puppet retrieve (and hence create)
a salted sha512 password for a user. This will then store the salted sha512 of
a random password AND the plain text password in trocla. Later you can
retrieve (by deleting) the plain password and send it to the user. Puppet
will still simply retrieve the hashed password that is stored in trocla,
while the plain password is not anymore stored on the server.
Be default trocla uses moneta to store the passwords and can use any kind of
key/value based storage supported by moneta for trocla. By default it uses a
simple yaml file.
However, since version 0.2.0 trocla also supports a pluggable storage backend
which allows you to write your custom backend. See more about stores below.
Trocla can also be integrated into [Hiera](https://docs.puppetlabs.com/hiera/) by using ZeroPointEnergy's [hiera-backend](https://github.com/ZeroPointEnergy/hiera-backend-trocla).
## Usage
### create
Assuming that we have an empty trocla storage.
trocla create user1 plain
This will create (if not already stored in trocla) a random password and
store its plain text under key user1. The password will also be returned
by trocla.
trocla create user2 mysql
This will create a random password and store its plain and mysql-style hashed
sha1 password in trocla. The hashed password is returned.
trocla create user1 mysql
This will take the already stored plain text password of key user1 and generate
and store the mysql-style hashed sha1 password.
It is possible that certain hash formats require additional options. For example
the pgsql hash requires also the user to create the md5 hash for the password.
You can pass these additional requirements as yaml-based strings to the format:
trocla create user1 pgsql 'username: user1'
This will create a pgsql password hash using the username user1.
Valid global options are:
* length: int - Define any lenght that a newly created password should have. Default: 16 - or whatever you define in your global settings.
* charset: (default|alphanumeric|shellsafe) - Which set of chars should be used for a random password? Default: default - or whatever you define in your global settings.
* profiles: a profile name or an array of profiles matching a profile_name in your configuration. Learn more about profiles below.
* random: boolean - Whether we allow creation of random passwords or we expect a password to be preset. Default: true - or whatever you define in your global settings.
* expires: An integer indicating the amount of seconds a value (e.g. password) is available. After expiration a value will not be available anymore and trying to `get` this key will return no value (nil). Meaning that calling create after expiration, would create a new password automatically. There is more about expiration in the storage backends section.
* render: A hash providing flags for formats to render the output specifially. This is a global option, but support depends on a per format basis.
Example:
trocla create some_shellsafe_password plain 'charset: shellsafe'
trocla create another_alphanumeric_20_char_password plain "charset: alphanumeric
length: 20"
### get
Get simply returns a stored password. It will not create a new password.
Assuming that we are still working with the same storage
trocla get user2 plain
will return the plain text password of the key user2.
trocla get user3 plain
This will return nothing, as no password with this format have been stored so
far.
### set
trocla set user3 plain
This will ask you for a password and set it under the appropriate key/format.
We expect a plain password to be entered and will format the password with
the selected format before storing it.
trocla set --password mysupersecretpassword user4 plain
This will take the password from the cli without asking you.
trocla set user5 mysql -p mysuperdbpassword
This will store a mysql sha1 hash for the key user5, without storing any kind
of plain text password.
If you like trocla not to format a password, as you are passing in an already
formatted password (like the sha512 hash), then you must use `--no-format` to
skip formatting. Like:
trocla set user5 sha512crypt --no-format -p '$6$1234$xxxx....'
You can also pipe in a password:
echo -n foo | trocla set user6 plain -p
or a file
cat some_file | trocla set user6 plain -p
trocla set user6 plain -p < some_file
### reset
trocla reset user1 md5crypt
This will recreate the salted md5 shadow-style hash. However, it will not create
a new plain text passwords. Hence, this is mainly usefull to create new hashed
passwords based on new salts.
If the plain password of a key is resetted, every already hashed password is
deleted as well, as the hashes wouldn't match anymore the plain text password.
### delete
trocla delete user1 plain
This will delete the plain password of the key user1 and return it.
### formats
trocla formats
This will list all available and supported formats.
## Attention
If you don't feed trocla initially with a hash and/or delete the generated
plain text passwords trocla will likely create a lot of plain text passwords
and store them on your machine/server. This is by intend and is all about which
problems (mainly passwords in configuration management manifests) trocla tries
to address. It is possible to store all passwords encrypted in the specific
backend.
See backend encryption for more information, however be aware that the key must
always also reside on the trocla node. So it mainly makes sense if you store
them on a remote backend like a central database server.
## Formats
Most formats are straight forward to use. Some formats require some additional
options to work properly. These are documented here:
### pgsql
Password hashes for PostgreSQL servers. Requires the option `username` to be set
to the username to which the password will be assigned.
### x509
This format takes a set of additional options. Required are:
subject: A subject for the target certificate. E.g. /C=ZZ/O=Trocla Inc./CN=test/emailAddress=example@example.com
OR
CN: The CN of the the target certificate. E.g. 'This is my self-signed certificate which doubles as CA'
Additional options are:
ca The trocla key of CA (imported into or generated within trocla) that
will be used to sign that certificate.
become_ca Whether the certificate should become a CA or not. Default: false,
to enable set it to true.
hash Hash to be used. Default sha2
keysize Keysize for the new key. Default is: 4096
serial Serial to be used, default is selecting a random one.
days How many days should the certificate be valid. Default 365
C instead within the subject string
ST instead within the subject string
L instead within the subject string
O instead within the subject string
OU instead within the subject string
emailAddress instead within the subject string
key_usages Any specific key_usages different than the default ones. If you specify
any, you must specify all that you want. If you don't want to have any,
you must specify an empty array.
altnames An array of subjectAltNames. By default for non CA certificates we
ensure that the CN ends up here as well. If you don't want that.
You need to pass an empty array.
name_constraints An array of domains that are added as permitted x509 NameConstraint.
By default, we do not add any contraint, meaning all domains are
signable by the CA, as soon as we have one item in the list, only
DNS entries matching this list are allowed. Be aware, that older
openssl versions have a bug with [leading dots](https://rt.openssl.org/Ticket/Display.html?id=3562) for name
constraints. So using them might not work everywhere as expected.
Output render options are:
certonly If set to true the x509 format will return only the certificate
keyonly If set to true the x509 format will return only the private key
## Installation
* Debian has trocla within its sid-release: `apt-get install trocla`
* For RHEL/CentOS 7 there is a [copr reporisotry](https://copr.fedoraproject.org/coprs/duritong/trocla/). Follow the help there to integrate the repository and install trocla.
* Trocla is also distributed as gem: `gem install trocla`
## Configuration
Trocla can be configured in /etc/troclarc.yaml and in ~/.troclarc.yaml. A sample configuration file can be found in `lib/trocla/default_config.yaml`.
By default trocla configures moneta to store all data in /tmp/trocla.yaml
### Profiles
It is possible to define profiles within the configuration file. The idea behind profiles are to make it easy to group together certain options for
automatic password generation.
Trocla ships with a default set of profiles, which are part of the `lib/trocla/default_config.yaml` configuration file. It is possible to override
the existing profiles within your own configuration file, as well as adding more. Note that the profiles part of the configuration file is merged
together and your configuration file has precedence.
The profiles part in the config is a hash where each entry consist of a name (key) and a hash of options (value).
Profiles make it especially easy to define a preset of options for SSL certificates as you will only need to set the certificate specific options,
while global options such as C, O or OU can be preset within the profile.
Profiles are used by setting the profiles option to a name of the pre-configured profiles, when passing options to the password option. On the cli
this looks like:
trocla create foo plain 'profiles: rootpw'
It is possible to pass mutliple profiles as an array, while the order will also reflect the precedence of the options.
Also it is possible to set a default profiles option in the options part of the configuration file.
### Storage backends
Trocla has a pluggable storage backend, which allows you to choose the way that values are stored (persistently).
Such a store is a simple class that implements Trocla::Store and at the moment there are the following store implementations:
* Moneta - the default store using [moneta](https://rubygems.org/gems/moneta) to delegate storing the values
* Memory - simple inmemory backend. Mainly used for testing.
The backend is chosen based on the `store` configuration option. If it is a symbol, we expect it to be a store that we ship with trocla. Otherwise, we assume it to be a fully qualified ruby class name, that inherits from Trocla::Store. If trocla should load an additional library to be able to find your custom store class, you can set `store_require` to whatever should be passed to a ruby require statement.
Store backends can be configured through the `store_options` configuration.
#### Expiration
We expect storage backends to implement support for the `expires` option, so that keys expire after the passed amount of seconds. Furthermore a storage backend needs to implement the behaviour described by the rspec shared_example 'store_validation' section 'expiration'. Mainly:
* Expiration is always for all formats per key.
* Adding, deleting or updating a format will keep the existing expiration, but reset the planned expiration.
* While setting a new plain format will not only erase all other formats, but also erase/reset any expires.
* Setting a value with an expires option of 0 or false, will remove any existent expiration.
New backends should be tested using the provided shared example.
#### Moneta backends
Trocla uses moneta as its default storage backend and hence can store your passwords in any of moneta's supported backends. By default it uses the yaml backend, which is configured as followed:
```YAML
store_options:
adapter: :YAML
adapter_options:
:file: '/tmp/trocla.yaml'
```
In environments with multiple Puppet masters using an existing DB cluster might make sense. The configured user needs to be granted at least SELECT, INSERT, UPDATE, DELETE and CREATE permissions on your database:
```YAML
store_options:
adapter: :Sequel
adapter_options:
:db: 'mysql://db.server.name'
:user: 'trocla'
:password: '***'
:database: 'trocladb'
:table: 'trocla'
```
These examples are by no way complete, moneta has much more to offer. Please have a look at [moneta's documentation](https://github.com/minad/moneta/blob/master/README.md) for further information.
### Backend encryption
By default trocla does not encrypt anything it stores. You might want to let Trocla encrypt all your passwords, at the moment the only supported way is SSL.
Given that often trocla's store is on the same system at it's being used, there might be little sense to encrypt everything while the encryption keys are on the same system. However, if you are for example using an existing DB cluster using backend encryption you won't store any plaintext passwords within the database system.
### Backend SSL encryption
To enable SSL encryption (e.g. by using your puppet masters SSL keys), you need to set the following configuration options:
```YAML
encryption: :ssl
encryption_options:
:private_key: '/var/lib/puppet/ssl/private_keys/trocla.pem'
:public_key: '/var/lib/puppet/ssl/public_keys/trocla.pem'
```
## Update & Changes
See [Changelog](CHANGELOG.md)
## Contributing to trocla
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
* Fork the project
* Start a feature/bugfix branch
* Commit and push until you are happy with your contribution
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
## Copyright
Copyright (c) 2011-2015 mh. See LICENSE.txt for
further details.
trocla-0.2.3/.document 0000644 0000041 0000041 00000000041 12665377071 014660 0 ustar www-data www-data lib/**/*.rb
bin/*
-
LICENSE.txt