hiera-ehttp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ /pkg
2
+ *.pem
3
+ *.yaml
4
+ *.json
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ hiera-ehttp
2
+ ==============
3
+
4
+ Description
5
+ -----------
6
+
7
+ This is a back end plugin for Hiera that allows lookup to be sourced from HTTP queries. The intent is to make this backend adaptable to allow you to query any data stored in systems with a RESTful API such as CouchDB or even a custom store with a web front-end
8
+
9
+ Example Configuration
10
+ ---------------------
11
+
12
+ You can generate default keys with
13
+
14
+ hiera-ehttp keys -n "CN=hiera-http/DC=neverland"
15
+
16
+ Grab the hiera-ehttp gem and then add this to your hiera config file
17
+
18
+ :backends:
19
+ - ehttp
20
+
21
+ :ehttp:
22
+ :host: 127.0.0.1
23
+ :port: 5984
24
+ :output: json
25
+ :failure: graceful
26
+ :keyfile: /etc/puppet/keys/key.pem
27
+ :certfile: /etc/puppet/keys/cert.pem
28
+ :paths:
29
+ - /hiera/%{fqdn}
30
+ - /hiera/defaults
31
+
32
+
33
+ Using the command line utility you can encrypt a value
34
+
35
+ hiera-ehttp encrypt -c cert.pem -s "secret value"
36
+
37
+ Configuration Parameters
38
+ ------------------------
39
+
40
+ The following are optional configuration parameters
41
+
42
+ `:output:` Specify what handler to use for the output of the request. Currently supported outputs are plain, which will just return the whole document, or YAML and JSON which parse the data and try to look up the key
43
+
44
+ `:http_connect_timeout:` Timeout in seconds for the HTTP connect (default 10)
45
+
46
+ `:http_read_timeout:` Timeout in seconds for waiting for a HTTP response (default 10)
47
+
48
+ `:failure:` When set to `graceful` will stop hiera-http from throwing an exception in the event of a connection error, timeout or invalid HTTP response and move on. Without this option set hiera-http will throw an exception in such circumstances
49
+
50
+ The `:paths:` parameter can also parse the lookup key, eg:
51
+
52
+ :paths:
53
+ /configuraiton.php?lookup=%{key}
54
+
55
+ `:use_ssl:` When set to true, enable SSL (default: false)
56
+
57
+ `:ssl_ca_cert` Specify a CA cert for use with SSL
58
+
59
+ `:ssl_cert` Specify location of SSL certificate
60
+
61
+ `:ssl_key` Specify location of SSL key
62
+
63
+ `:keyfile:` The private key used when storing encrypted data
64
+
65
+ `:certfile:` The certificate used when storing encrypted data
66
+
67
+ If and only if both `:keyfile:` and `:certfile:` are specified then encryption will be enabled
68
+
69
+ Notes
70
+ -----
71
+
72
+ If you want/need features added and don't hesitate to send a pull request or ask me to add them
73
+ for you.
74
+
75
+ This backend loosely follows the scheme that hiera-eyaml use so there may be some compatibility
76
+ between these two projects, but I make no promises.
77
+
78
+ The encryption support is not very fetured yet, and some things that came from the original
79
+ hiera-http backend have not been tested in this backend yet. The moral is if you find a bug,
80
+ make an issue so I know, or create a fix and create a pull request.
81
+
82
+ Credits
83
+ -------
84
+
85
+ * Much of this code comes from the original hiera-http created by Craig Dunn <craig@craigdunn.org>
86
+ * SSL components contributed from Ben Ford <ben.ford@puppetlabs.com>
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package_task'
3
+
4
+ spec = Gem::Specification.new do |gem|
5
+ gem.name = "hiera-ehttp"
6
+ gem.version = "0.0.1"
7
+
8
+ gem.author = "Josh Hoover"
9
+ gem.email = "floomby@nmt.edu"
10
+ gem.homepage = "http://github.com/floomby/hiera-ehttp"
11
+ gem.summary = "HTTP backend for Hiera supporting encrypted entries"
12
+ gem.description = "Hiera backend for looking up data over HTTP APIs with support for encrypted values"
13
+
14
+ gem.require_path = "lib"
15
+ gem.files = `git ls-files`.split($\)
16
+
17
+ gem.executables = ["hiera-ehttp"]
18
+
19
+ gem.add_dependency('json')
20
+ end
21
+
22
+ Gem::PackageTask.new(spec) do |pkg|
23
+ pkg.gem_spec = spec
24
+ end
data/bin/hiera-ehttp ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'base64'
4
+ require 'openssl'
5
+ require 'optparse'
6
+ require 'ostruct'
7
+ require 'pp'
8
+ require 'time'
9
+ require 'date'
10
+
11
+ class HieraEhttpKeys
12
+ @banner = "Usage: hiera-ehttp keys [options]"
13
+ def self.parse(args)
14
+ options = OpenStruct.new
15
+
16
+ # set the default cert name
17
+ options.name = "CN=hiera-http/DC=neverland"
18
+
19
+ opt_parser = OptionParser.new do |opts|
20
+ opts.banner = @banner
21
+
22
+ opts.on("-n", "--name <cert name>", "The name to use on the certificate") do |name| options.name = name end
23
+
24
+ end
25
+
26
+ opt_parser.parse!(args)
27
+ options
28
+ end
29
+
30
+ def self.create(options)
31
+
32
+ # create a key
33
+ key = OpenSSL::PKey::RSA.new 2048
34
+
35
+ # write the private key out in pem format
36
+ open 'key.pem', 'w' do |io| io.write key.to_pem end
37
+
38
+ # create a certificate to encrypt with
39
+ name = OpenSSL::X509::Name.parse options.name
40
+
41
+ cert = OpenSSL::X509::Certificate.new
42
+
43
+ cert.version = 2
44
+ cert.serial = 0
45
+ # make the certificate good for a year
46
+ cert.not_after = Time.now
47
+ cert.not_before = Time.now + (60*60*24*365)
48
+
49
+ cert.public_key = key.public_key
50
+ cert.subject = name
51
+ cert.issuer = name
52
+
53
+ # write the cert in pem format
54
+ open 'cert.pem', 'w' do |io| io.write cert.to_pem end
55
+
56
+ end
57
+
58
+ def self.usage()
59
+ puts @banner
60
+ end
61
+ end
62
+
63
+ class HieraEhttpEncrypt
64
+ @banner = "Usage: hiera-ehttp encrypt [options]"
65
+
66
+ def self.parse(args)
67
+ options = OpenStruct.new
68
+
69
+ options.strings = []
70
+
71
+ opt_parser = OptionParser.new do |opts|
72
+ opts.banner = @banner
73
+
74
+ opts.on("-c", "--certfile <certficate>", "Certificate to encrypt with") do |cert| options.certificate = cert end
75
+ opts.on("-s", "--string <value>", "String to encrypt") do |str| options.strings << str end
76
+ #opts.on("-j", "--json <file>", "Encrypt all of the values in a json file") do |file| options.jsons << file end
77
+ #opts.on("-y", "--yaml <file>", "Encrypt all of the values in a yaml file") do |file| options.yamls << file end
78
+
79
+ end
80
+
81
+ opt_parser.parse!(args)
82
+ options
83
+ end
84
+
85
+ def self.encrypt(options)
86
+ # read in the certificate and create the cipher object
87
+ cert = OpenSSL::X509::Certificate.new (File.read options.certificate)
88
+ cipher = OpenSSL::Cipher.new 'AES-128-CBC'
89
+
90
+ # encrypt strings, jsons, and yamls
91
+ strings options.strings, cert, cipher
92
+ #jsons options.jsons, cert, cipher
93
+ #yamls options.yamls, cert, cipher
94
+ end
95
+
96
+ def self.strings(strings, cert, cipher)
97
+ encs = []
98
+ strings.each do |str|
99
+ puts ("\"#{str}\":\"ENC[PKCS7," + (Base64.encode64 (OpenSSL::PKCS7::encrypt [cert], str, cipher, OpenSSL::PKCS7::BINARY).to_der).delete("\n\r") + "]\"\n")
100
+ end
101
+ end
102
+
103
+ def self.jsons(jsons, cert, cipher)
104
+ puts "Encryption on json files unimplimented... Skiping"
105
+ end
106
+
107
+ def self.yamls(yamls, cert, cipher)
108
+ puts "Encryption on yaml files unimplimented... Skiping"
109
+ end
110
+
111
+ def self.usage()
112
+ puts @banner
113
+ end
114
+ end
115
+
116
+ usage = "Usage: hiera-ehttp <action>"
117
+
118
+ action, *args = ARGV
119
+
120
+ if !action or action == 'help'
121
+ puts usage
122
+ exit
123
+ end
124
+
125
+ case action
126
+ when 'keys'
127
+ options = HieraEhttpKeys.parse args
128
+ HieraEhttpKeys.create options
129
+
130
+ when 'encrypt'
131
+ options = HieraEhttpEncrypt.parse args
132
+ HieraEhttpEncrypt.encrypt options
133
+
134
+
135
+ else
136
+ puts 'invalid action (valid actions: keys, encrypt)'
137
+ end
@@ -0,0 +1,160 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'openssl'
4
+
5
+ class Hiera
6
+ module Backend
7
+ class Ehttp_backend
8
+
9
+ def initialize
10
+
11
+ Hiera.debug "test"
12
+
13
+ @config = Config[:ehttp]
14
+
15
+ @http = Net::HTTP.new @config[:host], @config[:port]
16
+ @http.read_timeout = @config[:http_read_timeout] || 10
17
+ @http.open_timeout = @config[:http_connect_timeout] || 10
18
+
19
+ if @config[:use_ssl]
20
+ @http.use_ssl = true
21
+ if @config[:ssl_cert]
22
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
23
+ store = OpenSSL::X509::Store.new
24
+ store.add_cert OpenSSL::X509::Certificate.new File.read @config[:ssl_ca_cert]
25
+ @http.cert_store = store
26
+
27
+ @http.key = OpenSSL::PKey::RSA.new File.read @config[:ssl_cert]
28
+ @http.cert = OpenSSL::X509::Certificate.new File.read @config[:ssl_key]
29
+ end
30
+ else
31
+ @http.use_ssl = false
32
+ end
33
+
34
+ @keyfile = @config[:keyfile]
35
+ @certfile = @config[:certfile]
36
+
37
+ # we will read in the key and the cert
38
+ if @keyfile && @certfile
39
+ @key = OpenSSL::PKey::RSA.new File.read @keyfile
40
+ @cert = OpenSSL::X509::Certificate.new File.read @certfile
41
+ end
42
+ end
43
+
44
+ def lookup(key, scope, order_override, resolution_type)
45
+
46
+ Hiera.debug "test"
47
+
48
+ answer = nil
49
+
50
+ paths = @config[:paths].map { |p| Backend.parse_string p, scope, { 'key' => key } }
51
+ if order_override
52
+ paths.insert 0, order_override
53
+ end
54
+
55
+
56
+ paths.each do |path|
57
+
58
+ Hiera.debug "[hiera-ehttp]: Lookup #{key} from #{@config[:host]}:#{@config[:port]}#{path}"
59
+ httpreq = Net::HTTP::Get.new path
60
+
61
+ begin
62
+ httpres = @http.request httpreq
63
+ rescue Exception => e
64
+ Hiera.warn "[hiera-ehttp]: Net::HTTP threw exception #{e.message}"
65
+ raise Exception, e.message unless @config[:failure] == 'graceful'
66
+ next
67
+ end
68
+
69
+ unless httpres.kind_of? Net::HTTPSuccess
70
+ Hiera.debug "[hiera-ehttp]: bad http response from #{@config[:host]}:#{@config[:port]}#{path}"
71
+ Hiera.debug "HTTP response code was #{httpres.code}"
72
+ raise Exception, 'Bad HTTP response' unless @config[:failure] == 'graceful'
73
+ next
74
+ end
75
+
76
+ result = self.parse_response key, httpres.body
77
+ next unless result
78
+
79
+ parsed_result = Backend.parse_answer result, scope
80
+
81
+ case resolution_type
82
+ when :array
83
+ answer ||= []
84
+ answer << parsed_result
85
+ when :hash
86
+ answer ||= {}
87
+ answer = parsed_result.merge answer
88
+ else
89
+ answer = parsed_result
90
+ break
91
+ end
92
+ end
93
+ answer
94
+ end
95
+
96
+
97
+ def parse_response(key,answer)
98
+
99
+ return nil unless answer
100
+
101
+ Hiera.debug "[hiera-ehttp]: Query returned data, parsing response as #{@config[:output] || 'plain'}"
102
+
103
+ case @config[:output]
104
+
105
+ when 'json'
106
+ # If JSON is specified as the output format, assume the output of the
107
+ # endpoint URL is a JSON document and return keypart that matched our
108
+ # lookup key
109
+ self.json_handler key, answer
110
+ when 'yaml'
111
+ # If YAML is specified as the output format, assume the output of the
112
+ # endpoint URL is a YAML document and return keypart that matched our
113
+ # lookup key
114
+ self.yaml_handler key, answer
115
+ else
116
+ # When the output format is configured as plain we assume that if the
117
+ # endpoint URL returns an HTTP success then the contents of the response
118
+ # body is the value itself, or nil.
119
+ #
120
+ decrypt answer
121
+ end
122
+ end
123
+
124
+ # Handlers
125
+ # Here we define specific handlers to parse the output of the http request
126
+ # and return a value. Currently we support YAML and JSON
127
+ #
128
+ def json_handler(key, answer)
129
+ require 'json'
130
+ self.decrypt (JSON.parse answer)[key]
131
+ end
132
+
133
+ def yaml_handler(key, answer)
134
+ require 'yaml'
135
+ self.decrypt (YAML.load answer)[key]
136
+ end
137
+
138
+ def decrypt(answer)
139
+ if @keyfile && @certfile
140
+ require 'base64'
141
+ if a = /ENC\[([^,]+),([^\]]+)\]/.match(answer)
142
+ # right now we only support PKCS7
143
+ if a[1] != 'PKCS7'
144
+ Hiera.debug "[hiera-ehttp] #{a[1]} is an unsupported algorithm (supported: PKCS7)"
145
+ raise Exception, 'Unsupported Algorithm' unless @config[:failure] == 'graceful'
146
+ end
147
+
148
+ # we are good to decrypt
149
+ (OpenSSL::PKCS7.new Base64.decode64 a[2]).decrypt @key, @cert
150
+ else
151
+ answer
152
+ end
153
+ else
154
+ answer
155
+ end
156
+ end
157
+
158
+ end
159
+ end
160
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hiera-ehttp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Josh Hoover
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-04-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '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: '0'
30
+ description: Hiera backend for looking up data over HTTP APIs with support for encrypted
31
+ values
32
+ email: floomby@nmt.edu
33
+ executables:
34
+ - hiera-ehttp
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - README.md
40
+ - Rakefile
41
+ - bin/hiera-ehttp
42
+ - lib/hiera/backend/ehttp_backend.rb
43
+ homepage: http://github.com/floomby/hiera-ehttp
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 1.8.23
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: HTTP backend for Hiera supporting encrypted entries
67
+ test_files: []