hiera-eyaml 1.1.4 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +28 -0
  4. data/PLUGINS.md +4 -0
  5. data/README.md +51 -41
  6. data/bin/eyaml +9 -357
  7. data/features/decrypts.feature +44 -0
  8. data/features/edit.feature +54 -0
  9. data/features/encrypts.feature +26 -0
  10. data/features/keys.feature +13 -0
  11. data/features/outputs.feature +30 -0
  12. data/features/plugin.feature +35 -0
  13. data/features/plugin_api.feature +16 -0
  14. data/features/puppet.feature +15 -0
  15. data/features/sandbox/convert_decrypted_values_to_uppercase.sh +2 -0
  16. data/features/sandbox/keys/private_key.pkcs7.pem +27 -0
  17. data/features/sandbox/keys/public_key.pkcs7.pem +18 -0
  18. data/features/sandbox/pipe_string.sh +5 -0
  19. data/features/sandbox/puppet/environments/local/test.eyaml +3 -0
  20. data/features/sandbox/puppet/hiera.yaml +17 -0
  21. data/features/sandbox/puppet/manifests/init.pp +3 -0
  22. data/features/sandbox/puppet/modules/test/manifests/init.pp +18 -0
  23. data/features/sandbox/puppet/puppet.conf +6 -0
  24. data/features/sandbox/supply_password.sh +7 -0
  25. data/features/sandbox/test_input.bin +0 -0
  26. data/features/sandbox/test_input.encrypted.txt +1 -0
  27. data/features/sandbox/test_input.txt +3 -0
  28. data/features/sandbox/test_input.yaml +114 -0
  29. data/features/step_definitions/environment_overrides.rb +3 -0
  30. data/features/support/env.rb +26 -0
  31. data/features/support/setup_sandbox.rb +21 -0
  32. data/features/valid_encryption.feature +12 -0
  33. data/hiera-eyaml.gemspec +1 -1
  34. data/lib/hiera/backend/eyaml.rb +19 -0
  35. data/lib/hiera/backend/eyaml/CLI.rb +110 -0
  36. data/lib/hiera/backend/eyaml/actions/createkeys_action.rb +24 -0
  37. data/lib/hiera/backend/eyaml/actions/decrypt_action.rb +67 -0
  38. data/lib/hiera/backend/eyaml/actions/edit_action.rb +47 -0
  39. data/lib/hiera/backend/eyaml/actions/encrypt_action.rb +80 -0
  40. data/lib/hiera/backend/eyaml/encryptor.rb +71 -0
  41. data/lib/hiera/backend/eyaml/encryptors/pkcs7.rb +99 -0
  42. data/lib/hiera/backend/eyaml/options.rb +32 -0
  43. data/lib/hiera/backend/eyaml/plugins.rb +65 -0
  44. data/lib/hiera/backend/eyaml/utils.rb +83 -0
  45. data/lib/hiera/backend/eyaml_backend.rb +108 -113
  46. data/sublime_text/README.md +16 -0
  47. data/sublime_text/eyaml.sublime-package +0 -0
  48. data/sublime_text/eyaml.syntax_definition.json +288 -0
  49. data/{bin → tools}/regem.sh +1 -1
  50. metadata +71 -7
  51. data/keys/.keepme +0 -0
  52. data/lib/hiera/backend/version.rb +0 -7
@@ -0,0 +1,3 @@
1
+ Given /^my EDITOR is set to \"(.*?)\"$/ do |editor_command|
2
+ ENV['EDITOR'] = editor_command
3
+ end
@@ -0,0 +1,26 @@
1
+ ENV['RUBYLIB'] = File.dirname(__FILE__) + '/../../lib'
2
+ require 'rubygems'
3
+ require 'aruba/config'
4
+ require 'aruba/cucumber'
5
+ require 'fileutils'
6
+ require 'rspec/expectations'
7
+
8
+ test_files = {}
9
+ Dir["features/sandbox/**/*"].each do |file_name|
10
+ next unless File.file? file_name
11
+ read_mode = "r"
12
+ read_mode = "rb" if file_name =~ /\.bin$/
13
+ file = File.open(file_name, "r")
14
+ file_contents = file.read
15
+ file.close
16
+ file_name = file_name.slice(17, file_name.length)
17
+ test_files[file_name] = file_contents
18
+ end
19
+
20
+ # ENV['EDITOR']="/bin/cat"
21
+
22
+ Aruba.configure do |config|
23
+ config.before_cmd do |cmd|
24
+ SetupSandbox.create_files test_files
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ require 'fileutils'
2
+
3
+ class SetupSandbox
4
+
5
+ def self.create_files test_files
6
+
7
+ test_files.each do |test_file, contents|
8
+ extension = test_file.split('.').last
9
+ target_dir = File.dirname(test_file)
10
+ FileUtils.mkdir_p( target_dir ) unless Dir.exists?( target_dir )
11
+ write_mode = "w"
12
+ write_mode = "wb" if extension == "bin"
13
+ File.open(test_file, write_mode) {|input_file|
14
+ input_file.puts contents
15
+ } unless File.exists?( test_file )
16
+ File.chmod(0755, test_file) if extension == "sh"
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,12 @@
1
+ Feature: eyaml encrypting is valid
2
+
3
+ Scenario: encrypt and decrypt a binary file
4
+ When I run `bash -c "eyaml -e -o string -f test_input.bin > test_output.txt"`
5
+ When I run `bash -c "eyaml -d -f test_output.txt > test_output.bin"`
6
+ When I run `file test_output.bin`
7
+ Then the output should match /PNG image data/
8
+
9
+ Scenario: encrypt and decrypt a simple file
10
+ When I run `bash -c "eyaml -e -o string -f test_input.txt > test_output.txt"`
11
+ When I run `eyaml -d -f test_output.txt`
12
+ Then the output should match /fox jumped over/
data/hiera-eyaml.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'hiera/backend/version'
4
+ require 'hiera/backend/eyaml'
5
5
 
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = "hiera-eyaml"
@@ -0,0 +1,19 @@
1
+ class Hiera
2
+ module Backend
3
+ module Eyaml
4
+
5
+ VERSION = "1.3.1"
6
+
7
+ def self.default_encryption_scheme= new_encryption
8
+ @@default_encryption_scheme = new_encryption
9
+ end
10
+
11
+ def self.default_encryption_scheme
12
+ @@default_encryption_scheme ||= "PKCS7"
13
+ @@default_encryption_scheme
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,110 @@
1
+ require 'trollop'
2
+ require 'hiera/backend/eyaml'
3
+ require 'hiera/backend/eyaml/utils'
4
+ require 'hiera/backend/eyaml/actions/createkeys_action'
5
+ require 'hiera/backend/eyaml/actions/decrypt_action'
6
+ require 'hiera/backend/eyaml/actions/encrypt_action'
7
+ require 'hiera/backend/eyaml/actions/edit_action'
8
+ require 'hiera/backend/eyaml/plugins'
9
+ require 'hiera/backend/eyaml/options'
10
+
11
+ class Hiera
12
+ module Backend
13
+ module Eyaml
14
+ class CLI
15
+
16
+ def self.parse
17
+
18
+ options = Trollop::options do
19
+
20
+ version "Hiera-eyaml version " + Hiera::Backend::Eyaml::VERSION.to_s
21
+ banner <<-EOS
22
+ Hiera-eyaml is a backend for Hiera which provides OpenSSL encryption/decryption for Hiera properties
23
+
24
+ Usage:
25
+ eyaml [options]
26
+ eyaml -i file.eyaml # edit a file
27
+ eyaml -e -s some-string # encrypt a string
28
+ eyaml -e -p # encrypt a password
29
+ eyaml -e -f file.txt # encrypt a file
30
+ cat file.txt | eyaml -e # encrypt a file on a pipe
31
+
32
+ Options:
33
+ EOS
34
+
35
+ opt :createkeys, "Create public and private keys for use encrypting properties", :short => 'c'
36
+ opt :decrypt, "Decrypt something"
37
+ opt :encrypt, "Encrypt something"
38
+ opt :edit, "Decrypt, Edit, and Reencrypt", :type => :string
39
+ opt :eyaml, "Source input is an eyaml file", :type => :string
40
+ opt :password, "Source input is a password entered on the terminal", :short => 'p'
41
+ opt :string, "Source input is a string provided as an argument", :short => 's', :type => :string
42
+ opt :file, "Source input is a file", :short => 'f', :type => :string
43
+ opt :stdin, "Source input it taken from stdin", :short => 'z'
44
+ opt :encrypt_method, "Override default encryption and decryption method (default is PKCS7)", :short => 'n', :default => "pkcs7"
45
+ opt :output, "Output format of final result (examples, block, string)", :type => :string, :default => "examples"
46
+
47
+ Hiera::Backend::Eyaml::Plugins.options.each do |name, option|
48
+ opt name, option[:desc], :type => option[:type], :short => option[:short], :default => option[:default]
49
+ end
50
+
51
+ end
52
+
53
+ actions = [:createkeys, :decrypt, :encrypt, :edit].collect {|x| x if options[x]}.compact
54
+ sources = [:edit, :eyaml, :password, :string, :file, :stdin].collect {|x| x if options[x]}.compact
55
+ # sources << :stdin if STDIN
56
+
57
+ Trollop::die "You can only specify one of (#{actions.join(', ')})" if actions.count > 1
58
+ Trollop::die "You can only specify one of (#{sources.join(', ')})" if sources.count > 1
59
+ Trollop::die "Creating keys does not require a source to encrypt/decrypt" if actions.first == :createkeys and sources.count > 0
60
+
61
+ options[:source] = sources.first
62
+ options[:action] = actions.first
63
+ options[:source] = :not_applicable if options[:action] == :createkeys
64
+
65
+ Trollop::die "Nothing to do" if options[:source].nil? or options[:action].nil?
66
+
67
+ options[:input_data] = case options[:source]
68
+ when :stdin
69
+ STDIN.read
70
+ when :password
71
+ Utils.read_password
72
+ when :string
73
+ options[:string]
74
+ when :file
75
+ File.read options[:file]
76
+ when :eyaml
77
+ File.read options[:eyaml]
78
+ when :stdin
79
+ STDIN.read
80
+ else
81
+ if options[:edit]
82
+ options[:eyaml] = options[:edit]
83
+ options[:source] = :eyaml
84
+ File.read options[:edit]
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ Eyaml.default_encryption_scheme = options[:encrypt_method].upcase if options[:encrypt_method]
91
+ Eyaml::Options.set options
92
+
93
+ end
94
+
95
+ def self.execute
96
+
97
+ action = Eyaml::Options[:action]
98
+ action_class = Module.const_get('Hiera').const_get('Backend').const_get('Eyaml').const_get('Actions').const_get("#{Utils.camelcase action.to_s}Action")
99
+
100
+ puts action_class.execute
101
+
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+
110
+ end
@@ -0,0 +1,24 @@
1
+ require 'hiera/backend/eyaml/utils'
2
+ require 'hiera/backend/eyaml/options'
3
+
4
+ class Hiera
5
+ module Backend
6
+ module Eyaml
7
+ module Actions
8
+
9
+ class CreatekeysAction
10
+
11
+ def self.execute
12
+
13
+ encryptor = Encryptor.find Eyaml.default_encryption_scheme
14
+ encryptor.create_keys
15
+ nil
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,67 @@
1
+ require 'hiera/backend/eyaml/utils'
2
+ require 'hiera/backend/eyaml/options'
3
+
4
+ class Hiera
5
+ module Backend
6
+ module Eyaml
7
+ module Actions
8
+
9
+ class DecryptAction
10
+
11
+ REGEX_ENCRYPTED_BLOCK = />\n(\s*)ENC\[(\w+,)?([a-zA-Z0-9\+\/ =\n]+)\]/
12
+ REGEX_ENCRYPTED_STRING = /ENC\[(\w+,)?([a-zA-Z0-9\+\/=]+)\]/
13
+
14
+ def self.execute
15
+
16
+ output_data = case Eyaml::Options[:source]
17
+ when :eyaml
18
+ encryptions = []
19
+
20
+ # blocks
21
+ output = Eyaml::Options[:input_data].gsub( REGEX_ENCRYPTED_BLOCK ) { |match|
22
+ indentation = $1
23
+ encryption_scheme = parse_encryption_scheme( $2 )
24
+ decryptor = Encryptor.find encryption_scheme
25
+ ciphertext = $3.gsub(/[ \n]/, '')
26
+ plaintext = decryptor.decrypt( decryptor.decode ciphertext )
27
+ ">\n" + indentation + "DEC::#{decryptor.tag}[" + plaintext + "]!"
28
+ }
29
+
30
+ # strings
31
+ output.gsub!( REGEX_ENCRYPTED_STRING ) { |match|
32
+ encryption_scheme = parse_encryption_scheme( $1 )
33
+ decryptor = Encryptor.find encryption_scheme
34
+
35
+ plaintext = decryptor.decrypt( decryptor.decode $2 )
36
+ "DEC::#{decryptor.tag}[" + plaintext + "]!"
37
+ }
38
+
39
+ output
40
+ else
41
+
42
+ output = Eyaml::Options[:input_data].gsub( REGEX_ENCRYPTED_STRING ) { |match|
43
+ encryption_scheme = parse_encryption_scheme( $1 )
44
+ decryptor = Encryptor.find encryption_scheme
45
+ decryptor.decrypt( decryptor.decode $2 )
46
+ }
47
+
48
+ output
49
+ end
50
+
51
+ output_data
52
+
53
+ end
54
+
55
+ protected
56
+
57
+ def self.parse_encryption_scheme regex_result
58
+ regex_result = Eyaml.default_encryption_scheme + "," if regex_result.nil?
59
+ regex_result.split(",").first
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,47 @@
1
+ require 'hiera/backend/eyaml/utils'
2
+ require 'hiera/backend/eyaml/actions/decrypt_action'
3
+ require 'hiera/backend/eyaml/actions/encrypt_action'
4
+ require 'hiera/backend/eyaml/options'
5
+
6
+ class Hiera
7
+ module Backend
8
+ module Eyaml
9
+ module Actions
10
+
11
+ class EditAction
12
+
13
+ def self.execute
14
+
15
+ decrypted_input = DecryptAction.execute
16
+ decrypted_file = Utils.write_tempfile decrypted_input
17
+ editor = Utils.find_editor
18
+ system editor, decrypted_file
19
+ status = $?
20
+ raise StandardError, "Editor #{editor} has not exited?" unless status.exited?
21
+ raise StandardError, "Editor did not exit successfully (exit code #{status.exitstatus}), aborting" unless status.exitstatus #TODO: The file is left on the disk
22
+ raise StandardError, "File was moved by editor" unless File.file? decrypted_file
23
+
24
+ edited_file = File.read decrypted_file
25
+ Utils.secure_file_delete :file => decrypted_file, :num_bytes => [edited_file.length, decrypted_input.length].max
26
+ raise StandardError, "Edited file is blank" if edited_file.empty?
27
+ raise StandardError, "No changes" if edited_file == decrypted_input
28
+
29
+ Eyaml::Options[:input_data] = edited_file
30
+ Eyaml::Options[:output] = "raw"
31
+
32
+ encrypted_output = EncryptAction.execute
33
+
34
+ filename = Eyaml::Options[:eyaml]
35
+ File.open("#{filename}", 'w') { |file|
36
+ file.write encrypted_output
37
+ }
38
+
39
+ true
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,80 @@
1
+ require 'hiera/backend/eyaml/options'
2
+
3
+ class Hiera
4
+ module Backend
5
+ module Eyaml
6
+ module Actions
7
+
8
+ class EncryptAction
9
+
10
+ REGEX_DECRYPTED_BLOCK = />\n(\s*)DEC(::\w+)?\[(.+)\]\!/
11
+ REGEX_DECRYPTED_STRING = /DEC(::\w+)?\[(.+)\]\!/
12
+
13
+ def self.execute
14
+
15
+ output_data = case Eyaml::Options[:source]
16
+ when :eyaml
17
+ encryptions = []
18
+
19
+ # blocks
20
+ output = Eyaml::Options[:input_data].gsub( REGEX_DECRYPTED_BLOCK ) { |match|
21
+ indentation = $1
22
+ encryption_scheme = parse_encryption_scheme( $2 )
23
+ encryptor = Encryptor.find encryption_scheme
24
+ ciphertext = encryptor.encode( encryptor.encrypt($3) ).gsub(/\n/, "\n" + indentation)
25
+ ">\n" + indentation + "ENC[#{encryptor.tag},#{ciphertext}]"
26
+ }
27
+
28
+ # strings
29
+ output.gsub!( REGEX_DECRYPTED_STRING ) { |match|
30
+ encryption_scheme = parse_encryption_scheme( $1 )
31
+ encryptor = Encryptor.find encryption_scheme
32
+ ciphertext = encryptor.encode( encryptor.encrypt($2) ).gsub(/\n/, "")
33
+ "ENC[#{encryptor.tag},#{ciphertext}]"
34
+ }
35
+
36
+ else
37
+ encryptor = Encryptor.find
38
+ ciphertext = encryptor.encode( encryptor.encrypt(Eyaml::Options[:input_data]) )
39
+ "ENC[#{encryptor.tag},#{ciphertext}]"
40
+ end
41
+
42
+ self.format :data => output_data, :structure => Eyaml::Options[:output]
43
+
44
+ end
45
+
46
+ protected
47
+
48
+ def self.parse_encryption_scheme regex_result
49
+ regex_result = "::" + Eyaml.default_encryption_scheme if regex_result.nil?
50
+ regex_result.split("::").last
51
+ end
52
+
53
+ def self.format args
54
+ data = args[:data]
55
+ data_as_block = data.split("\n").join("\n ")
56
+ data_as_string = data.split("\n").join("")
57
+ structure = args[:structure]
58
+
59
+ case structure
60
+ when "examples"
61
+ "string: #{data_as_string}\n\n" +
62
+ "OR\n\n" +
63
+ "block: >\n" +
64
+ " #{data_as_block}"
65
+ when "block"
66
+ " #{data_as_block}"
67
+ when "string"
68
+ "#{data_as_string}"
69
+ else
70
+ data.to_s
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,71 @@
1
+ require 'base64'
2
+
3
+ class Hiera
4
+ module Backend
5
+ module Eyaml
6
+
7
+ class Encryptor
8
+
9
+ class << self
10
+ attr_accessor :options
11
+ attr_accessor :tag
12
+ end
13
+
14
+ def self.find encryption_scheme = nil
15
+ encryption_scheme = Eyaml.default_encryption_scheme if encryption_scheme.nil?
16
+ require "hiera/backend/eyaml/encryptors/#{encryption_scheme.downcase}"
17
+ encryptor_module = Module.const_get('Hiera').const_get('Backend').const_get('Eyaml').const_get('Encryptors')
18
+ encryptor_class = self.find_closest_class :parent_class => encryptor_module, :class_name => encryption_scheme
19
+ raise StandardError, "Could not find hiera-eyaml encryptor: #{encryption_scheme}. Try gem install hiera-eyaml-#{encryption_scheme.downcase} ?" if encryptor_class.nil?
20
+ encryptor_class
21
+ end
22
+
23
+ def self.encode binary_string
24
+ Base64.encode64(binary_string).strip
25
+ end
26
+
27
+ def self.decode string
28
+ Base64.decode64(string)
29
+ end
30
+
31
+ def self.encrypt *args
32
+ raise StandardError, "encrypt() not defined for encryptor plugin: #{self}"
33
+ end
34
+
35
+ def self.decrypt *args
36
+ raise StandardError, "decrypt() not defined for decryptor plugin: #{self}"
37
+ end
38
+
39
+ protected
40
+
41
+ def self.register
42
+ plugin_classname = self.to_s.split("::").last.downcase
43
+ Hiera::Backend::Eyaml::Plugins.register_options :options => self.options, :plugin => plugin_classname
44
+ end
45
+
46
+ def self.option name
47
+ plugin_classname = self.to_s.split("::").last.downcase
48
+ Eyaml::Options[ "#{plugin_classname}_#{name}" ] || self.options[ "#{plugin_classname}_#{name}" ]
49
+ end
50
+
51
+ def self.find_closest_class args
52
+ parent_class = args[ :parent_class ]
53
+ class_name = args[ :class_name ]
54
+ constants = parent_class.constants
55
+ candidates = []
56
+ constants.each do | candidate |
57
+ candidates << candidate.to_s if candidate.to_s.downcase == class_name.downcase
58
+ end
59
+ if candidates.count > 0
60
+ parent_class.const_get candidates.first
61
+ else
62
+ nil
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+