hiera-ehttp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README.md +86 -0
- data/Rakefile +24 -0
- data/bin/hiera-ehttp +137 -0
- data/lib/hiera/backend/ehttp_backend.rb +160 -0
- metadata +67 -0
data/.gitignore
ADDED
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: []
|