Commit a298ccc4 authored by Stig Mathisen's avatar Stig Mathisen

Imported Upstream version 2.1.0

parent 28fd3bc2
language: ruby
rvm:
- "1.8.7-p374"
- "1.9.2"
- "1.9.3"
- "2.0.0"
- "2.1.5"
before_install:
- sudo apt-get update
- sudo apt-get install expect
- "2.2.3"
env:
- PUPPET_VERSION=3.7.5
- PUPPET_VERSION=3.8.4
- PUPPET_VERSION=4.2.2
sudo: false
addons:
apt:
packages:
- expect
script:
bundle exec cucumber -f progress
notifications:
email: false
matrix:
exclude:
- rvm: 1.8.7-p374
env: PUPPET_VERSION=4.2.2
- rvm: 2.2.3
env: PUPPET_VERSION=3.7.5
- rvm: 2.2.3
env: PUPPET_VERSION=3.8.4
source 'https://rubygems.org/'
gem 'highline', '~> 1.6.19'
gem 'trollop', '~> 2.0'
gemspec
group :development do
gem "aruba", '~> 0.6.2'
gem "cucumber", '~> 1.1'
gem "rspec-expectations", '~> 3.1.0'
gem "hiera-eyaml-plaintext"
gem "puppet"
gem "puppet", ENV['PUPPET_VERSION'] || '~> 3.8'
end
group :test do
......
......@@ -281,6 +281,7 @@ This is a list of available plugins:
- [hiera-eyaml-twofac](https://github.com/gtmtechltd/hiera-eyaml-twofac) - PKCS7 keypair + AES256 symmetric password for two-factor encryption
Note that this plugin mandates the user enter a password. It is useful for non-automated scenarios, and is not advised to be used
in conjunction with puppet, as it requires entry of a password over a terminal.
- [hiera-eyaml-kms](https://github.com/adenot/hiera-eyaml-kms) - Encryption using AWS Key Management Service (KMS)
Notes
......
......@@ -12,15 +12,15 @@ Hiera::Backend::Eyaml::Plugins.find
begin
Hiera::Backend::Eyaml::CLI.parse
rescue StandardError => e
Hiera::Backend::Eyaml::Utils.warn e.message
Hiera::Backend::Eyaml::Utils.debug e.backtrace.join("\n")
Hiera::Backend::Eyaml::LoggingHelper.warn e.message
Hiera::Backend::Eyaml::LoggingHelper.debug e.backtrace.join("\n")
exit 1
end
begin
Hiera::Backend::Eyaml::CLI.execute
rescue StandardError => e
Hiera::Backend::Eyaml::Utils.warn e.message
Hiera::Backend::Eyaml::Utils.debug e.backtrace.join("\n")
Hiera::Backend::Eyaml::LoggingHelper.warn e.message
Hiera::Backend::Eyaml::LoggingHelper.debug e.backtrace.join("\n")
exit 1
end
......@@ -2,7 +2,7 @@ class Hiera
module Backend
module Eyaml
VERSION = "2.0.8"
VERSION = "2.1.0"
DESCRIPTION = "Hiera-eyaml is a backend for Hiera which provides OpenSSL encryption/decryption for Hiera properties"
class RecoverableError < StandardError
......
require 'trollop'
require 'hiera/backend/eyaml'
require 'hiera/backend/eyaml/logginghelper'
require 'hiera/backend/eyaml/utils'
require 'hiera/backend/eyaml/plugins'
require 'hiera/backend/eyaml/options'
......@@ -45,8 +46,8 @@ class Hiera
result = executor.execute
puts result unless result.nil?
rescue Exception => e
Utils.warn e.message
Utils.debug e.backtrace.join("\n")
LoggingHelper.warn e.message
LoggingHelper.debug e.backtrace.join("\n")
end
end
......
require 'hiera/backend/eyaml/logginghelper'
class Hiera
module Backend
module Eyaml
class EditHelper
def self.find_editor
editor = ENV['EDITOR']
editor ||= %w{ /usr/bin/sensible-editor /usr/bin/editor /usr/bin/vim /usr/bin/vi }.collect {|e| e if FileTest.executable? e}.compact.first
raise StandardError, "Editor not found. Please set your EDITOR env variable" if editor.nil?
if editor.index(' ')
editor = editor.dup if editor.frozen? # values from ENV are frozen
editor.gsub!(/([^\\]|^)~/, '\1' + ENV['HOME']) # replace ~ with home unless escaped
editor.gsub!(/(^|[^\\])"/, '\1') # remove unescaped quotes during processing
editor.gsub!(/\\ /, ' ') # unescape spaces since we quote paths
pieces = editor.split(' ')
paths = pieces.each_with_index.map {|_,x| pieces[0..x].join(' ')}.reverse # get possible paths, starting with longest
extensions = (ENV['PATHEXT'] || '').split(';') # handle Windows executables
pathdirs = ENV['PATH'].split(File::PATH_SEPARATOR)
paths += pathdirs.collect { |dir| paths.collect { |path| File.expand_path(path, dir) } }.flatten
editorfile = paths.select { |path|
FileTest.file?(path) || ! extensions.select {|ext| FileTest.file?(path + ext) }.empty?
}.first
raise StandardError, "Editor not found. Please set your EDITOR env variable" if editorfile.nil?
raw_command = paths[(paths.index editorfile) % pieces.size]
editor = "\"#{editorfile}\"#{editor[raw_command.size()..-1]}"
end
editor
end
def self.secure_file_delete args
file = File.open(args[:file], 'r+')
num_bytes = args[:num_bytes]
[0xff, 0x55, 0xaa, 0x00].each do |byte|
file.seek(0, IO::SEEK_SET)
num_bytes.times { file.print(byte.chr) }
file.fsync
end
file.close
File.delete args[:file]
end
def self.write_tempfile data_to_write
file = Tempfile.open(['eyaml_edit', '.yaml'])
path = file.path
file.close!
file = File.open(path, "w")
file.chmod(0600)
if ENV['OS'] == 'Windows_NT'
# Windows doesn't support chmod
icacls = 'C:\Windows\system32\icacls.exe'
if File.executable? icacls
current_user = `C:\\Windows\\system32\\whoami.exe`.chomp
# Use ACLs to restrict access to the current user only
command = %Q{#{icacls} "#{file.path}" /grant:r "#{current_user}":f /inheritance:r}
system "#{command} >NUL 2>&1"
end
end
file.puts data_to_write
file.close
LoggingHelper::debug "Wrote temporary file: #{path}"
path
end
end
end
end
end
require 'tempfile'
require 'fileutils'
class Hiera
module Backend
module Eyaml
class EncryptHelper
def self.write_important_file args
require 'hiera/backend/eyaml/highlinehelper'
filename = args[ :filename ]
content = args[ :content ]
mode = args[ :mode ]
if File.file? "#{filename}"
raise StandardError, "User aborted" unless HighlineHelper::confirm? "Are you sure you want to overwrite \"#{filename}\"?"
end
open( "#{filename}", "w" ) do |io|
io.write(content)
end
File.chmod( mode, filename ) unless mode.nil?
end
def self.ensure_key_dir_exists key_file
key_dir = File.dirname key_file
unless File.directory? key_dir
begin
FileUtils.mkdir_p key_dir
LoggingHelper::info "Created key directory: #{key_dir}"
rescue
raise StandardError, "Cannot create key directory: #{key_dir}"
end
end
end
end
end
end
end
require 'base64'
require 'hiera/backend/eyaml/utils'
require 'hiera/backend/eyaml/encrypthelper'
class Hiera
module Backend
......@@ -60,19 +60,19 @@ class Hiera
end
def self.trace msg
Utils::trace :from => plugin_classname, :msg => msg
LoggingHelper::trace :from => plugin_classname, :msg => msg
end
def self.debug msg
Utils::debug :from => plugin_classname, :msg => msg
LoggingHelper::debug :from => plugin_classname, :msg => msg
end
def self.info msg
Utils::info :from => plugin_classname, :msg => msg
LoggingHelper::info :from => plugin_classname, :msg => msg
end
def self.warn msg
Utils::warn :from => plugin_classname, :msg => msg
LoggingHelper::warn :from => plugin_classname, :msg => msg
end
end
......
require 'openssl'
require 'hiera/backend/eyaml/encryptor'
require 'hiera/backend/eyaml/utils'
require 'hiera/backend/eyaml/encrypthelper'
require 'hiera/backend/eyaml/logginghelper'
require 'hiera/backend/eyaml/options'
class Hiera
......@@ -65,8 +66,8 @@ class Hiera
subject = self.option :subject
key = OpenSSL::PKey::RSA.new(2048)
Utils.ensure_key_dir_exists private_key
Utils.write_important_file :filename => private_key, :content => key.to_pem, :mode => 0600
EncryptHelper.ensure_key_dir_exists private_key
EncryptHelper.write_important_file :filename => private_key, :content => key.to_pem, :mode => 0600
cert = OpenSSL::X509::Certificate.new()
cert.subject = OpenSSL::X509::Name.parse(subject)
......@@ -92,9 +93,9 @@ class Hiera
cert.sign key, OpenSSL::Digest::SHA1.new
Utils.ensure_key_dir_exists public_key
Utils.write_important_file :filename => public_key, :content => cert.to_pem
Utils.info "Keys created OK"
EncryptHelper.ensure_key_dir_exists public_key
EncryptHelper.write_important_file :filename => public_key, :content => cert.to_pem
LoggingHelper.info "Keys created OK"
end
......
require 'highline/import'
class Hiera
module Backend
module Eyaml
class HighlineHelper
def self.read_password
ask("Enter password: ") {|q| q.echo = "*" }
end
def self.confirm? message
result = ask("#{message} (y/N): ")
if result.downcase == "y" or result.downcase == "yes"
true
else
false
end
end
end
end
end
end
require 'tempfile'
require 'fileutils'
class Hiera
module Backend
module Eyaml
class LoggingHelper
def self.structure_message messageinfo
message = {:from => "hiera-eyaml-core"}
case messageinfo.class.to_s
when 'Hash'
message.merge!(messageinfo)
else
message.merge!({:msg => messageinfo.to_s})
end
message[:prefix] = "[#{message[:from]}]"
message[:spacer] = " #{' ' * message[:from].length} "
formatted_output = message[:msg].split("\n").each_with_index.map do |line, index|
if index == 0
"#{message[:prefix]} #{line}"
else
"#{message[:spacer]} #{line}"
end
end
formatted_output.join "\n"
end
def self.warn messageinfo
self.print_message({ :message => self.structure_message( messageinfo ), :hiera_loglevel => :warn, :cli_color => :red })
end
def self.info messageinfo
self.print_message({ :message => self.structure_message( messageinfo ), :hiera_loglevel => :debug, :cli_color => :white, :threshold => 0 })
end
def self.debug messageinfo
self.print_message({ :message => self.structure_message( messageinfo ), :hiera_loglevel => :debug, :cli_color => :green, :threshold => 1 })
end
def self.trace messageinfo
self.print_message({ :message => self.structure_message( messageinfo ), :hiera_loglevel => :debug, :cli_color => :blue, :threshold => 2 })
end
def self.print_message( args )
message = args[:message] ||= ""
hiera_loglevel = args[:hiera_loglevel] ||= :debug
cli_color = args[:cli_color] ||= :blue
threshold = args[:threshold]
if self.hiera?
Hiera.send(hiera_loglevel, message) if threshold.nil? or Eyaml.verbosity_level > threshold
else
STDERR.puts self.colorize( message, cli_color ) if threshold.nil? or Eyaml.verbosity_level > threshold
end
end
def self.colorize message, color
suffix = "\e[0m"
prefix = case color
when :red
"\e[31m"
when :green
"\e[32m"
when :blue
"\e[34m"
else #:white
"\e[0m"
end
"#{prefix}#{message}#{suffix}"
end
def self.hiera?
"hiera".eql? Eyaml::Options[:source]
end
end
end
end
end
......@@ -21,16 +21,16 @@ class Hiera
end
def self.trace
Utils::trace "Dump of eyaml tool options dict:"
Utils::trace "--------------------------------"
LoggingHelper::trace "Dump of eyaml tool options dict:"
LoggingHelper::trace "--------------------------------"
@@options.each do |k, v|
begin
Utils::trace sprintf "%18s %-18s = %18s %-18s", "(#{k.class.name})", k.to_s, "(#{v.class.name})", v.to_s
LoggingHelper::trace sprintf "%18s %-18s = %18s %-18s", "(#{k.class.name})", k.to_s, "(#{v.class.name})", v.to_s
rescue
Utils::trace sprintf "%18s %-18s = %18s %-18s", "(#{k.class.name})", k.to_s, "(#{v.class.name})", "<unprintable>" # case where v is binary
LoggingHelper::trace sprintf "%18s %-18s = %18s %-18s", "(#{k.class.name})", k.to_s, "(#{v.class.name})", "<unprintable>" # case where v is binary
end
end
Utils::trace "--------------------------------"
LoggingHelper::trace "--------------------------------"
end
end
......
......@@ -38,7 +38,7 @@ class Hiera
[ "/etc/eyaml/config.yaml", "#{ENV['HOME']}/.eyaml/config.yaml", "#{ENV['EYAML_CONFIG']}" ].each do |config_file|
begin
yaml_contents = YAML.load_file(config_file)
Utils::info "Loaded config from #{config_file}"
LoggingHelper::info "Loaded config from #{config_file}"
config.merge! yaml_contents
rescue
raise StandardError, "Could not open config file \"#{config_file}\" for reading"
......
require 'hiera/backend/eyaml/utils'
require 'hiera/backend/eyaml/edithelper'
require 'hiera/backend/eyaml/highlinehelper'
require 'hiera/backend/eyaml/options'
require 'hiera/backend/eyaml/parser/parser'
require 'hiera/backend/eyaml/subcommand'
require 'highline/import'
class Hiera
module Backend
......@@ -61,14 +61,14 @@ eos
raise StandardError, "Could not open file for reading: #{options[:eyaml]}"
end
else
Utils.info "#{options[:eyaml]} doesn't exist, editing new file"
LoggingHelper.info "#{options[:eyaml]} doesn't exist, editing new file"
options[:input_data] = "---"
end
options
end
def self.execute
editor = Utils.find_editor
editor = EditHelper.find_editor
encrypted_parser = Parser::ParserFactory.encrypted_parser
tokens = encrypted_parser.parse Eyaml::Options[:input_data]
......@@ -76,7 +76,7 @@ eos
decrypted_file_content = Eyaml::Options[:no_preamble] ? decrypted_input : (self.preamble + decrypted_input)
begin
decrypted_file = Utils.write_tempfile decrypted_file_content unless decrypted_file
decrypted_file = EditHelper.write_tempfile decrypted_file_content unless decrypted_file
system "#{editor} \"#{decrypted_file}\""
status = $?
......@@ -90,7 +90,7 @@ eos
raise StandardError, "Edited file is blank" if edited_file.empty?
if edited_file == decrypted_input
Utils.info "No changes detected, exiting"
LoggingHelper.info "No changes detected, exiting"
else
decrypted_parser = Parser::ParserFactory.decrypted_parser
edited_tokens = decrypted_parser.parse(edited_file)
......@@ -123,14 +123,14 @@ eos
}
end
rescue RecoverableError => e
Utils.info e
LoggingHelper.info e
if agree "Return to the editor to try again?"
retry
else
raise e
end
ensure
Utils.secure_file_delete :file => decrypted_file, :num_bytes => [edited_file.length, decrypted_input.length].max
EditHelper.secure_file_delete :file => decrypted_file, :num_bytes => [edited_file.length, decrypted_input.length].max
end
nil
......
......@@ -53,7 +53,8 @@ class Hiera
options[:input_data] = case options[:source]
when :password
Utils.read_password
require 'hiera/backend/eyaml/highlinehelper'
HighlineHelper.read_password
when :string
options[:string]
when :file
......
......@@ -19,7 +19,7 @@ class Hiera
def self.execute
plugin_versions = {}
Eyaml::Utils.info "hiera-eyaml (core): #{Eyaml::VERSION}"
Eyaml::LoggingHelper.info "hiera-eyaml (core): #{Eyaml::VERSION}"
Plugins.plugins.each do |plugin|
plugin_shortname = plugin.name.split("hiera-eyaml-").last
......@@ -28,7 +28,7 @@ class Hiera
rescue
"unknown (is plugin compatible with eyaml 2.0+ ?)"
end
Eyaml::Utils.info "hiera-eyaml-#{plugin_shortname} (gem): #{plugin_version}"
Eyaml::LoggingHelper.info "hiera-eyaml-#{plugin_shortname} (gem): #{plugin_version}"
end
nil
......
require 'highline/import'
require 'tempfile'
require 'fileutils'
require 'hiera/backend/eyaml/logginghelper'
class Hiera
module Backend
module Eyaml
class Utils
def self.read_password
ask("Enter password: ") {|q| q.echo = "*" }
end
def self.confirm? message
result = ask("#{message} (y/N): ")
if result.downcase == "y" or result.downcase == "yes"
true
else
false
end
end
def self.camelcase string
return string if string !~ /_/ && string =~ /[A-Z]+.*/
string.split('_').map{|e| e.capitalize}.join
......@@ -30,94 +17,6 @@ class Hiera
string.split(/(?=[A-Z])/).collect {|x| x.downcase}.join("_")
end
def self.find_editor
editor = ENV['EDITOR']
editor ||= %w{ /usr/bin/sensible-editor /usr/bin/editor /usr/bin/vim /usr/bin/vi }.collect {|e| e if FileTest.executable? e}.compact.first
raise StandardError, "Editor not found. Please set your EDITOR env variable" if editor.nil?
if editor.index(' ')
editor = editor.dup if editor.frozen? # values from ENV are frozen
editor.gsub!(/([^\\]|^)~/, '\1' + ENV['HOME']) # replace ~ with home unless escaped
editor.gsub!(/(^|[^\\])"/, '\1') # remove unescaped quotes during processing
editor.gsub!(/\\ /, ' ') # unescape spaces since we quote paths
pieces = editor.split(' ')
paths = pieces.each_with_index.map {|_,x| pieces[0..x].join(' ')}.reverse # get possible paths, starting with longest
extensions = (ENV['PATHEXT'] || '').split(';') # handle Windows executables
pathdirs = ENV['PATH'].split(File::PATH_SEPARATOR)
paths += pathdirs.collect { |dir| paths.collect { |path| File.expand_path(path, dir) } }.flatten
editorfile = paths.select { |path|
FileTest.file?(path) || ! extensions.select {|ext| FileTest.file?(path + ext) }.empty?
}.first
raise StandardError, "Editor not found. Please set your EDITOR env variable" if editorfile.nil?
raw_command = paths[(paths.index editorfile) % pieces.size]
editor = "\"#{editorfile}\"#{editor[raw_command.size()..-1]}"
end
editor
end
def self.secure_file_delete args
file = File.open(args[:file], 'r+')
num_bytes = args[:num_bytes]
[0xff, 0x55, 0xaa, 0x00].each do |byte|
file.seek(0, IO::SEEK_SET)
num_bytes.times { file.print(byte.chr) }
file.fsync
end
file.close
File.delete args[:file]
end
def self.write_tempfile data_to_write
file = Tempfile.open(['eyaml_edit', '.yaml'])
path = file.path
file.close!
file = File.open(path, "w")
file.chmod(0600)
if ENV['OS'] == 'Windows_NT'
# Windows doesn't support chmod
icacls = 'C:\Windows\system32\icacls.exe'
if File.executable? icacls
current_user = `C:\\Windows\\system32\\whoami.exe`.chomp
# Use ACLs to restrict access to the current user only
command = %Q{#{icacls} "#{file.path}" /grant:r "#{current_user}":f /inheritance:r}
system "#{command} >NUL 2>&1"
end
end
file.puts data_to_write
file.close
Utils::debug "Wrote temporary file: #{path}"
path
end
def self.write_important_file args
filename = args[ :filename ]
content = args[ :content ]
mode = args[ :mode ]
if File.file? "#{filename}"
raise StandardError, "User aborted" unless Utils::confirm? "Are you sure you want to overwrite \"#{filename}\"?"
end
open( "#{filename}", "w" ) do |io|
io.write(content)
end
File.chmod( mode, filename ) unless mode.nil?
end
def self.ensure_key_dir_exists key_file
key_dir = File.dirname key_file
unless File.directory? key_dir
begin
FileUtils.mkdir_p key_dir
Utils::info "Created key directory: #{key_dir}"
rescue
raise StandardError, "Cannot create key directory: #{key_dir}"
end
end
end
def self.find_closest_class args
parent_class = args[ :parent_class ]
class_name = args[ :class_name ]
......@@ -138,7 +37,7 @@ class Hiera
root_folder = File.dirname(__FILE__) + "/" + Array.new(num_class_hierarchy_levels).fill("..").join("/")
class_folder = root_folder + "/" + classdir
Dir[File.expand_path("#{class_folder}/*.rb")].uniq.each do |file|
self.trace "Requiring file: #{file}"
LoggingHelper.trace "Requiring file: #{file}"
require file
end
end
......@@ -157,70 +56,6 @@ class Hiera
"hiera".eql? Eyaml::Options[:source]
end
def self.structure_message messageinfo
message = {:from => "hiera-eyaml-core"}
case messageinfo.class.to_s
when 'Hash'
message.merge!(messageinfo)
else
message.merge!({:msg => messageinfo.to_s})
end
message[:prefix] = "[#{message[:from]}]"
message[:spacer] = " #{' ' * message[:from].length} "
formatted_output = message[:msg].split("\n").each_with_index.map do |line, index|
if index == 0
"#{message[:prefix]} #{line}"
else
"#{message[:spacer]} #{line}"
end
end
formatted_output.join "\n"
end
def self.warn messageinfo
self.print_message({ :message => self.structure_message( messageinfo ), :hiera_loglevel => :warn, :cli_color => :red })
end
def self.info messageinfo
self.print_message({ :message => self.structure_message( messageinfo ), :hiera_loglevel => :debug, :cli_color => :white, :threshold => 0 })
end
def self.debug messageinfo
self.print_message({ :message => self.structure_message( messageinfo ), :hiera_loglevel => :debug, :cli_color => :green, :threshold => 1 })
end
def self.trace messageinfo
self.print_message({ :message => self.structure_message( messageinfo ), :hiera_loglevel => :debug, :cli_color => :blue, :threshold => 2 })
end
def self.print_message( args )
message = args[:message] ||= ""
hiera_loglevel = args[:hiera_loglevel] ||= :debug
cli_color = args[:cli_color] ||= :blue
threshold = args[:threshold]
if self.hiera?
Hiera.send(hiera_loglevel, message) if threshold.nil? or Eyaml.verbosity_level > threshold
else
STDERR.puts self.colorize( message, cli_color ) if threshold.nil? or Eyaml.verbosity_level > threshold
end
end
def self.colorize message, color
suffix = "\e[0m"
prefix = case color
when :red
"\e[31m"
when :green
"\e[32m"
when :blue
"\e[34m"
else #:white
"\e[0m"
end
"#{prefix}#{message}#{suffix}"
end