hiera-eyaml 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .idea
2
+ *.iml
3
+ *.gradle
4
+ keys/*.pem
5
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gem 'highline'
4
+ gem 'trollop'
5
+
data/Gemfile.lock ADDED
@@ -0,0 +1,12 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ highline (1.6.19)
5
+ trollop (2.0)
6
+
7
+ PLATFORMS
8
+ ruby
9
+
10
+ DEPENDENCIES
11
+ highline
12
+ trollop
data/README.md ADDED
@@ -0,0 +1,135 @@
1
+ Hiera eYaml
2
+ ===========
3
+
4
+ A backend for Hiera that provides per-value asymmetric encryption of sensitive data
5
+ within yaml type files to be used by Puppet.
6
+
7
+ More info can be found [in this corresponding post](http://themettlemonkey.wordpress.com/2013/07/15/hiera-eyaml-per-value-encrypted-backend-for-hiera-and-puppet/).
8
+
9
+ The Hiera eYaml backend uses yaml formatted files with the .eyaml extension. Simply wrap your
10
+ encrypted string with ENC[] and place it in an eyaml file. You can mix your plain values
11
+ in as well or separate them into different files.
12
+
13
+ <pre>
14
+ ---
15
+ plain-property: You can see me
16
+
17
+ encrypted-property: >
18
+ ENC[Y22exl+OvjDe+drmik2XEeD3VQtl1uZJXFFF2NnrMXDWx0csyqLB/2NOWefv
19
+ NBTZfOlPvMlAesyr4bUY4I5XeVbVk38XKxeriH69EFAD4CahIZlC8lkE/uDh
20
+ jJGQfh052eonkungHIcuGKY/5sEbbZl/qufjAtp/ufor15VBJtsXt17tXP4y
21
+ l5ZP119Fwq8xiREGOL0lVvFYJz2hZc1ppPCNG5lwuLnTekXN/OazNYpf4CMd
22
+ /HjZFXwcXRtTlzewJLc+/gox2IfByQRhsI/AgogRfYQKocZgFb/DOZoXR7wm
23
+ IZGeunzwhqfmEtGiqpvJJQ5wVRdzJVpTnANBA5qxeA==]
24
+ </pre>
25
+
26
+ eYaml also supports encrypted values within arrays, hashes, nested arrays and nested hashes
27
+ (see below for examples)
28
+
29
+ N.B. when using the multi-line string syntax (i.e. >) **don't wrap encrypted strings with "" or ''**
30
+
31
+ Setup
32
+ =====
33
+
34
+ ### Installing hiera-eyaml
35
+
36
+ $ gem install hiera-eyaml
37
+
38
+ ### Generate keys
39
+
40
+ The first step is to create a pair of keys on the Puppet master
41
+
42
+ $ eyaml -c
43
+
44
+ This creates a public and private key with default names in the default location. (keys/ directory)
45
+
46
+ ### Encryption
47
+
48
+ To encrypt something, you only need the public_key, so distribute that to people creating hiera properties
49
+
50
+ $ eyaml -e text # Encrypt some text
51
+ $ eyaml -e -p # Encrypt a password (prompt for it)
52
+ $ eyaml -e -f filename # Encrypt a file
53
+
54
+ ### Decryption
55
+
56
+ To decrypt something, you need the public_key and the private_key on the puppet master.
57
+
58
+ To test decryption you can also use the eyaml tool if you have both keys
59
+
60
+ $ eyaml -d SOME-ENCRYPTED-TEXT # Decrypt some text
61
+ $ eyaml -d -f filename # Decrypt a file (PEM format)
62
+
63
+ Change the permissions so that the private key is only readable by the user that hiera (puppet) is
64
+ running as.
65
+
66
+ ### Configure Hiera
67
+
68
+ Next configure hiera.yaml to use the eyaml backend
69
+
70
+ <pre>
71
+ ---
72
+ :backends:
73
+ - eyaml
74
+ - yaml
75
+
76
+ :hierarchy:
77
+ - %{environment}
78
+ - common
79
+
80
+ :yaml:
81
+ :datadir: '/etc/puppet/hieradata'
82
+ :eyaml:
83
+ :datadir: '/etc/puppet/hieradata'
84
+
85
+ # Optional. Default is /etc/hiera/keys/private_key.pem
86
+ :private_key: /new/path/to/key/private_key.pem
87
+
88
+ # Optional. Default is /etc/hiera/keys/public_key.pem
89
+ :public_key: /new/path/to/key/public_key.pem
90
+ </pre>
91
+
92
+ ### YAML files
93
+
94
+ Once the value is encrypted, wrap it with ENC[] and place it in the .eyaml file.
95
+
96
+ Usages:
97
+ <pre>
98
+ ---
99
+ plain-property: You can see me
100
+
101
+ cipher-property : >
102
+ ENC[Y22exl+OvjDe+drmik2XEeD3VQtl1uZJXFFF2NnrMXDWx0csyqLB/2NOWefv
103
+ NBTZfOlPvMlAesyr4bUY4I5XeVbVk38XKxeriH69EFAD4CahIZlC8lkE/uDh
104
+ jJGQfh052eonkungHIcuGKY/5sEbbZl/qufjAtp/ufor15VBJtsXt17tXP4y
105
+ l5ZP119Fwq8xiREGOL0lVvFYJz2hZc1ppPCNG5lwuLnTekXN/OazNYpf4CMd
106
+ /HjZFXwcXRtTlzewJLc+/gox2IfByQRhsI/AgogRfYQKocZgFb/DOZoXR7wm
107
+ IZGeunzwhqfmEtGiqpvJJQ5wVRdzJVpTnANBA5qxeA==]
108
+
109
+ environments:
110
+ development:
111
+ host: localhost
112
+ password: password
113
+ production:
114
+ host: prod.org.com
115
+ password: >
116
+ ENC[Y22exl+OvjDe+drmik2XEeD3VQtl1uZJXFFF2NnrMXDWx0csyqLB/2NOWefv
117
+ NBTZfOlPvMlAesyr4bUY4I5XeVbVk38XKxeriH69EFAD4CahIZlC8lkE/uDh
118
+ jJGQfh052eonkungHIcuGKY/5sEbbZl/qufjAtp/ufor15VBJtsXt17tXP4y
119
+ l5ZP119Fwq8xiREGOL0lVvFYJz2hZc1ppPCNG5lwuLnTekXN/OazNYpf4CMd
120
+ /HjZFXwcXRtTlzewJLc+/gox2IfByQRhsI/AgogRfYQKocZgFb/DOZoXR7wm
121
+ IZGeunzwhqfmEtGiqpvJJQ5wVRdzJVpTnANBA5qxeA==]
122
+
123
+ things:
124
+ - thing 1
125
+ - - nested thing 1.0
126
+ - >
127
+ ENC[Y22exl+OvjDe+drmik2XEeD3VQtl1uZJXFFF2NnrMXDWx0csyqLB/2NOWefv
128
+ NBTZfOlPvMlAesyr4bUY4I5XeVbVk38XKxeriH69EFAD4CahIZlC8lkE/uDh
129
+ jJGQfh052eonkungHIcuGKY/5sEbbZl/qufjAtp/ufor15VBJtsXt17tXP4y
130
+ l5ZP119Fwq8xiREGOL0lVvFYJz2hZc1ppPCNG5lwuLnTekXN/OazNYpf4CMd
131
+ /HjZFXwcXRtTlzewJLc+/gox2IfByQRhsI/AgogRfYQKocZgFb/DOZoXR7wm
132
+ IZGeunzwhqfmEtGiqpvJJQ5wVRdzJVpTnANBA5qxeA==]
133
+ - - nested thing 2.0
134
+ - nested thing 2.1
135
+ </pre>
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/eyaml ADDED
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+ require 'trollop'
6
+ require 'highline/import'
7
+ require 'hiera/backend/version'
8
+
9
+ def ensureKeyDirExists(key_file)
10
+ key_dir = File.dirname(key_file)
11
+
12
+ if !File.directory?(key_dir)
13
+ Dir.mkdir(key_dir)
14
+ puts "Created #{key_dir} dir for #{key_file}."
15
+ end
16
+ end
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] [string-to-encrypt]
26
+ EOS
27
+
28
+ opt :createkeys, "Create public and private keys for use encrypting properties", :short => 'c'
29
+ opt :password, "Encrypt a password entered on the terminal", :short => 'p'
30
+ opt :file, "Encrypt a file instead of a string", :short => 'f', :type => :string
31
+ opt :private_key, "Filename of the private_key", :type => :string
32
+ opt :public_key, "Filename of the public_key", :type => :string
33
+ opt :encrypt, "Encrypt something"
34
+ opt :decrypt, "Decrypt something"
35
+ end
36
+
37
+ Trollop::die "You cannot specify --encrypt and --decrypt" if options[:encrypt] and options[:decrypt]
38
+
39
+ # Defaults
40
+ options[:private_key_filename] ||= "/etc/hiera/keys/private_key.pem"
41
+ options[:public_key_filename] ||= "/etc/hiera/keys/public_key.pem"
42
+ options[:string] = ARGV.join(' ')
43
+
44
+ if options[:password]
45
+ password = ask("Enter password: ") {|q| q.echo = "*" }
46
+ options[:string] = password
47
+ end
48
+
49
+ if options[:createkeys]
50
+
51
+ # Try to do equivalent of:
52
+ # openssl req -x509 -nodes -days 100000 -newkey rsa:2048 -keyout privatekey.pem -out publickey.pem -subj '/'
53
+
54
+ ensureKeyDirExists(options[:private_key_filename])
55
+ ensureKeyDirExists(options[:public_key_filename])
56
+
57
+ key = OpenSSL::PKey::RSA.new(2048)
58
+ open( options[:private_key_filename], "w" ) do |io|
59
+ io.write(key.to_pem)
60
+ end
61
+
62
+ puts "#{options[:private_key_filename]} created."
63
+
64
+ name = OpenSSL::X509::Name.parse("/")
65
+ cert = OpenSSL::X509::Certificate.new()
66
+ cert.serial = 0
67
+ cert.version = 2
68
+ cert.not_before = Time.now
69
+ cert.not_after = Time.now + 50 * 365 * 24 * 60 * 60
70
+ cert.public_key = key.public_key
71
+
72
+ ef = OpenSSL::X509::ExtensionFactory.new
73
+ ef.subject_certificate = cert
74
+ ef.issuer_certificate = cert
75
+ cert.extensions = [
76
+ ef.create_extension("basicConstraints","CA:TRUE", true),
77
+ ef.create_extension("subjectKeyIdentifier", "hash"),
78
+ # ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
79
+ ]
80
+ cert.add_extension ef.create_extension("authorityKeyIdentifier",
81
+ "keyid:always,issuer:always")
82
+
83
+ cert.sign key, OpenSSL::Digest::SHA1.new
84
+
85
+ open( options[:public_key_filename], "w" ) do |io|
86
+ io.write(cert.to_pem)
87
+ end
88
+ puts "#{options[:public_key_filename]} created."
89
+ exit
90
+ end
91
+
92
+ if options[:encrypt]
93
+
94
+ plaintext = nil
95
+ plaintext = options[:string] if options[:string]
96
+ plaintext = File.read( options[:file] ) if options[:file]
97
+
98
+ if plaintext.nil?
99
+ puts "Specify a string or --file to encrypt something. See --help for more usage instructions."
100
+ exit
101
+ end
102
+
103
+ public_key_pem = File.read( options[:public_key_filename] )
104
+ public_key = OpenSSL::X509::Certificate.new( public_key_pem )
105
+
106
+ cipher = OpenSSL::Cipher::AES.new(256, :CBC)
107
+ ciphertext_binary = OpenSSL::PKCS7::encrypt([public_key], plaintext, cipher, OpenSSL::PKCS7::BINARY).to_der
108
+ ciphertext_as_block = Base64.encode64(ciphertext_binary).strip
109
+ ciphertext_as_string = ciphertext_as_block.split("\n").join('')
110
+
111
+ puts "string: ENC[#{ciphertext_as_string}]\n\nOR\n\n"
112
+ puts "block: >"
113
+ puts " ENC[" + ciphertext_as_block.gsub(/\n/, "\n ") + "]"
114
+ exit
115
+
116
+ end
117
+
118
+ if options[:decrypt]
119
+
120
+ ciphertext = nil
121
+ ciphertext = options[:string] if options[:string]
122
+ ciphertext = File.read( options[:file] ) if options[:file]
123
+
124
+ if ciphertext.start_with? "ENC["
125
+
126
+ ciphertext = ciphertext[4..-2]
127
+ ciphertext_decoded = Base64.decode64(ciphertext)
128
+
129
+ if ciphertext.nil?
130
+ puts "Specify a string or --file to decrypt something. See --help for more usage instructions."
131
+ exit
132
+ end
133
+
134
+ private_key_pem = File.read( options[:private_key_filename] )
135
+ private_key = OpenSSL::PKey::RSA.new( private_key_pem )
136
+
137
+ public_key_pem = File.read( options[:public_key_filename] )
138
+ public_key = OpenSSL::X509::Certificate.new( public_key_pem )
139
+
140
+ pkcs7 = OpenSSL::PKCS7.new( ciphertext_decoded )
141
+
142
+ plaintext = pkcs7.decrypt(private_key, public_key)
143
+ puts "#{plaintext}"
144
+ exit
145
+
146
+ else
147
+
148
+ puts "Ciphertext is not an eyaml encrypted string (Does not start with ENC[...])"
149
+
150
+ end
151
+
152
+ end
data/bin/regem.sh ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ gem uninstall hiera-eyaml --executables
4
+ rake build
5
+ gem install pkg/hiera-eyaml
6
+ eyaml -v
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hiera/backend/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "hiera-eyaml"
8
+ gem.version = Hiera::Backend::Eyaml::VERSION
9
+ gem.description = "Hiera backend for decrypting encrypted yaml properties"
10
+ gem.summary = "OpenSSL Encryption backend for Hiera"
11
+ gem.author = "Tom Poulton"
12
+
13
+ gem.homepage = "http://github.com/TomPoulton/hiera-eyaml"
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_dependency('trollop', '>=2.0')
20
+ gem.add_dependency('highline', '>=1.6.19')
21
+ end
data/keys/.keepme ADDED
File without changes
@@ -0,0 +1,129 @@
1
+ class Hiera
2
+ module Backend
3
+ class Eyaml_backend
4
+
5
+ def initialize
6
+ require 'openssl'
7
+ require 'base64'
8
+ end
9
+
10
+ def lookup(key, scope, order_override, resolution_type)
11
+
12
+ debug("Lookup called for key #{key}")
13
+ answer = nil
14
+
15
+ Backend.datasources(scope, order_override) do |source|
16
+ eyaml_file = Backend.datafile(:eyaml, scope, source, "eyaml") || next
17
+
18
+ debug("Processing datasource: #{eyaml_file}")
19
+
20
+ data = YAML.load(File.read( eyaml_file ))
21
+
22
+ next if !data
23
+ next if data.empty?
24
+ debug ("Data contains valid YAML")
25
+
26
+ next unless data.include?(key)
27
+ debug ("Key #{key} found in YAML document")
28
+
29
+ parsed_answer = parse_answer(data[key], scope)
30
+
31
+ begin
32
+ case resolution_type
33
+ when :array
34
+ debug("Appending answer array")
35
+ raise Exception, "Hiera type mismatch: expected Array and got #{parsed_answer.class}" unless parsed_answer.kind_of? Array or parsed_answer.kind_of? String
36
+ answer ||= []
37
+ answer << parsed_answer
38
+ when :hash
39
+ debug("Merging answer hash")
40
+ raise Exception, "Hiera type mismatch: expected Hash and got #{parsed_answer.class}" unless parsed_answer.kind_of? Hash
41
+ answer ||= {}
42
+ answer = parsed_answer.merge answer
43
+ else
44
+ debug("Assigning answer variable")
45
+ answer = parsed_answer
46
+ break
47
+ end
48
+ rescue NoMethodError
49
+ raise Exception, "Resolution type is #{resolution_type} but parsed_answer is a #{parsed_answer.class}"
50
+ end
51
+ end
52
+
53
+ return answer
54
+ end
55
+
56
+ def parse_answer(data, scope, extra_data={})
57
+ if data.is_a?(Numeric) or data.is_a?(TrueClass) or data.is_a?(FalseClass)
58
+ # Can't be encrypted
59
+ return data
60
+ elsif data.is_a?(String)
61
+ parsed_string = Backend.parse_string(data, scope)
62
+ return decrypt(parsed_string, scope)
63
+ elsif data.is_a?(Hash)
64
+ answer = {}
65
+ data.each_pair do |key, val|
66
+ answer[key] = parse_answer(val, scope, extra_data)
67
+ end
68
+ return answer
69
+ elsif data.is_a?(Array)
70
+ answer = []
71
+ data.each do |item|
72
+ answer << parse_answer(item, scope, extra_data)
73
+ end
74
+ return answer
75
+ end
76
+ end
77
+
78
+ def decrypt(value, scope)
79
+
80
+ if is_encrypted(value)
81
+
82
+ # remove enclosing 'ENC[]'
83
+ ciphertext = value[4..-2]
84
+ ciphertext_decoded = Base64.decode64(ciphertext)
85
+
86
+ debug("Decrypting value")
87
+
88
+ private_key_path = Backend.parse_string(Config[:eyaml][:private_key], scope) || '/etc/hiera/keys/private_key.pem'
89
+ public_key_path = Backend.parse_string(Config[:eyaml][:public_key], scope) || '/etc/hiera/keys/public_key.pem'
90
+
91
+ private_key_pem = File.read( private_key_path )
92
+ private_key = OpenSSL::PKey::RSA.new( private_key_pem )
93
+
94
+ public_key_pem = File.read( public_key_path )
95
+ public_key = OpenSSL::X509::Certificate.new( public_key_pem )
96
+
97
+ pkcs7 = OpenSSL::PKCS7.new( ciphertext_decoded )
98
+
99
+ begin
100
+ plaintext = pkcs7.decrypt(private_key, public_key)
101
+ rescue
102
+ raise Exception, "Hiera eyaml backend: Unable to decrypt hiera data. Do the keys match and are they the same as those used to encrypt?"
103
+ end
104
+
105
+ return plaintext
106
+
107
+ else
108
+ return value
109
+ end
110
+ end
111
+
112
+ def is_encrypted(value)
113
+ if value.start_with?('ENC[')
114
+ return true
115
+ else
116
+ return false
117
+ end
118
+ end
119
+
120
+ def debug(msg)
121
+ Hiera.debug("[eyaml_backend]: #{msg}")
122
+ end
123
+
124
+ def warn(msg)
125
+ Hiera.warn("[eyaml_backend]: #{msg}")
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,7 @@
1
+ module Hiera
2
+ module Backend
3
+ module Eyaml
4
+ VERSION = "1.1.0"
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hiera-eyaml
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tom Poulton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: trollop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: highline
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.6.19
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.6.19
46
+ description: Hiera backend for decrypting encrypted yaml properties
47
+ email:
48
+ executables:
49
+ - eyaml
50
+ - regem.sh
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - Gemfile.lock
57
+ - README.md
58
+ - Rakefile
59
+ - bin/eyaml
60
+ - bin/regem.sh
61
+ - hiera-eyaml.gemspec
62
+ - keys/.keepme
63
+ - lib/hiera/backend/eyaml_backend.rb
64
+ - lib/hiera/backend/version.rb
65
+ homepage: http://github.com/TomPoulton/hiera-eyaml
66
+ licenses: []
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 1.8.25
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: OpenSSL Encryption backend for Hiera
89
+ test_files: []