hiera-eyaml 1.1.0

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