jerakia 1.2.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/jerakia.rb +4 -5
- data/lib/jerakia/answer.rb +33 -3
- data/lib/jerakia/cli.rb +3 -1
- data/lib/jerakia/cli/lookup.rb +3 -3
- data/lib/jerakia/cli/secret.rb +58 -0
- data/lib/jerakia/datasource.rb +73 -32
- data/lib/jerakia/datasource/dummy.rb +6 -10
- data/lib/jerakia/datasource/file.rb +77 -63
- data/lib/jerakia/datasource/file/json.rb +9 -11
- data/lib/jerakia/datasource/file/yaml.rb +12 -14
- data/lib/jerakia/dsl/lookup.rb +15 -17
- data/lib/jerakia/dsl/policy.rb +11 -8
- data/lib/jerakia/encryption.rb +60 -0
- data/lib/jerakia/encryption/vault.rb +168 -0
- data/lib/jerakia/error.rb +10 -0
- data/lib/jerakia/launcher.rb +18 -6
- data/lib/jerakia/log.rb +2 -18
- data/lib/jerakia/lookup.rb +0 -24
- data/lib/jerakia/policy.rb +31 -58
- data/lib/jerakia/response/filter.rb +2 -1
- data/lib/jerakia/response/filter/encryption.rb +21 -38
- data/lib/jerakia/schema.rb +3 -3
- data/lib/jerakia/util/http.rb +51 -0
- data/lib/jerakia/version.rb +1 -1
- metadata +6 -7
- data/lib/hiera/backend/jerakia_backend.rb +0 -59
- data/lib/jerakia/datasource/file_new.rb +0 -82
- data/lib/jerakia/policy/registry.rb +0 -23
- data/lib/puppet/indirector/data_binding/jerakia.rb +0 -33
- data/lib/puppet/indirector/data_binding/jerakia_rest.rb +0 -44
@@ -1,17 +1,15 @@
|
|
1
1
|
class Jerakia::Datasource
|
2
|
-
|
3
|
-
|
2
|
+
class File
|
3
|
+
module Json
|
4
4
|
EXTENSION = 'json'.freeze
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
raise Jerakia::FileParseError, "Could not parse JSON content, #{e.message}"
|
14
|
-
end
|
6
|
+
require 'json'
|
7
|
+
def convert(data)
|
8
|
+
return {} if data.empty?
|
9
|
+
begin
|
10
|
+
JSON.load(data)
|
11
|
+
rescue JSON::ParserError => e
|
12
|
+
raise Jerakia::FileParseError, "Could not parse JSON content, #{e.message}"
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
@@ -1,18 +1,16 @@
|
|
1
1
|
class Jerakia::Datasource
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
end
|
2
|
+
class File
|
3
|
+
module Yaml
|
4
|
+
EXTENSION = 'yaml'.freeze
|
5
|
+
require 'yaml'
|
6
|
+
def convert(data)
|
7
|
+
return {} if data.empty?
|
8
|
+
begin
|
9
|
+
YAML.load(data)
|
10
|
+
rescue Psych::SyntaxError => e
|
11
|
+
raise Jerakia::FileParseError, "Error parsing YAML document: #{e.message}"
|
12
|
+
end
|
13
|
+
|
16
14
|
end
|
17
15
|
end
|
18
16
|
end
|
data/lib/jerakia/dsl/lookup.rb
CHANGED
@@ -1,34 +1,28 @@
|
|
1
1
|
class Jerakia
|
2
2
|
module Dsl
|
3
3
|
class Lookup
|
4
|
-
attr_reader :policy
|
5
4
|
attr_reader :request
|
5
|
+
attr_reader :scope_object
|
6
6
|
attr_accessor :lookup
|
7
7
|
|
8
|
-
def initialize(name,
|
9
|
-
@
|
10
|
-
@
|
11
|
-
|
12
|
-
@lookup = Jerakia::Lookup.new(name, opts, @request, scope)
|
8
|
+
def initialize(name, request, scope, opts = {})
|
9
|
+
@request = request
|
10
|
+
@scope_object = scope
|
11
|
+
@lookup = Jerakia::Lookup.new(name, opts, request, scope)
|
13
12
|
end
|
14
13
|
|
15
|
-
def self.evaluate(name,
|
16
|
-
lookup_block = new(name,
|
14
|
+
def self.evaluate(name, request, scope, opts, &block)
|
15
|
+
lookup_block = new(name, request, scope, opts)
|
17
16
|
lookup_block.instance_eval &block
|
18
|
-
|
17
|
+
return lookup_block.lookup
|
19
18
|
end
|
20
19
|
|
21
20
|
# define the data source for the lookup
|
22
21
|
# @api: public
|
23
22
|
def datasource(name, opts = {})
|
24
|
-
datasource =
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# give access to the lookup scope object
|
29
|
-
# @api: public
|
30
|
-
def scope
|
31
|
-
lookup.scope
|
23
|
+
lookup.datasource = { :name => name, :opts => opts }
|
24
|
+
#datasource = Jerakia::Datasource.new(name, lookup, opts)
|
25
|
+
#lookup.datasource = datasource
|
32
26
|
end
|
33
27
|
|
34
28
|
# pass through exposed functions from the main lookup object
|
@@ -37,6 +31,10 @@ class Jerakia
|
|
37
31
|
lookup.confine(*args)
|
38
32
|
end
|
39
33
|
|
34
|
+
def scope
|
35
|
+
@scope_object.value
|
36
|
+
end
|
37
|
+
|
40
38
|
def exclude(*args)
|
41
39
|
lookup.exclude(*args)
|
42
40
|
end
|
data/lib/jerakia/dsl/policy.rb
CHANGED
@@ -3,14 +3,14 @@ require 'jerakia/cache/file'
|
|
3
3
|
class Jerakia
|
4
4
|
module Dsl
|
5
5
|
class Policy
|
6
|
-
def self.evaluate_file(filename
|
7
|
-
policy = new
|
6
|
+
def self.evaluate_file(filename)
|
7
|
+
policy = new
|
8
8
|
policy.evaluate_file(filename)
|
9
9
|
policy.instance
|
10
10
|
end
|
11
11
|
|
12
|
-
def self.evaluate(
|
13
|
-
policy = new
|
12
|
+
def self.evaluate(&block)
|
13
|
+
policy = new
|
14
14
|
policy.instance_eval &block
|
15
15
|
policy.instance
|
16
16
|
end
|
@@ -18,8 +18,7 @@ class Jerakia
|
|
18
18
|
attr_accessor :request
|
19
19
|
attr_reader :instance
|
20
20
|
|
21
|
-
def initialize(
|
22
|
-
@request = req
|
21
|
+
def initialize()
|
23
22
|
end
|
24
23
|
|
25
24
|
def evaluate_file(filename)
|
@@ -33,7 +32,7 @@ class Jerakia
|
|
33
32
|
end
|
34
33
|
|
35
34
|
def policy(name, opts = {}, &block)
|
36
|
-
@instance = Jerakia::Policy.new(name, opts
|
35
|
+
@instance = Jerakia::Policy.new(name, opts)
|
37
36
|
Jerakia::Dsl::Policyblock.evaluate(instance, &block)
|
38
37
|
end
|
39
38
|
end
|
@@ -51,7 +50,11 @@ class Jerakia
|
|
51
50
|
end
|
52
51
|
|
53
52
|
def lookup(name, opts = {}, &block)
|
54
|
-
Jerakia
|
53
|
+
Jerakia.log.debug("Adding lookup #{name} for policy #{policy}")
|
54
|
+
policy.lookups << Proc.new do |request, scope|
|
55
|
+
Jerakia.log.debug("Invoking lookup #{name}")
|
56
|
+
Jerakia::Dsl::Lookup.evaluate(name, request, scope, opts, &block)
|
57
|
+
end
|
55
58
|
end
|
56
59
|
end
|
57
60
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Jerakia
|
2
|
+
class Encryption
|
3
|
+
|
4
|
+
@handler = nil
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def handler
|
8
|
+
@handler || @handler = self.new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :loaded
|
13
|
+
|
14
|
+
def initialize(provider=nil)
|
15
|
+
if provider.nil?
|
16
|
+
provider = config["provider"]
|
17
|
+
end
|
18
|
+
|
19
|
+
return nil if provider.nil?
|
20
|
+
|
21
|
+
begin
|
22
|
+
require "jerakia/encryption/#{provider}"
|
23
|
+
rescue LoadError => e
|
24
|
+
raise Jerakia::Error, "Failed to load encryption provider #{provider}"
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
eval "extend Jerakia::Encryption::#{provider.capitalize}"
|
29
|
+
rescue NameError => e
|
30
|
+
raise Jerakia::Error, "Encryption provider #{provider} did not provide class"
|
31
|
+
end
|
32
|
+
@loaded = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def loaded?
|
36
|
+
loaded
|
37
|
+
end
|
38
|
+
|
39
|
+
def features?(feature)
|
40
|
+
case feature
|
41
|
+
when :encrypt
|
42
|
+
respond_to?('encrypt')
|
43
|
+
when :decrypt
|
44
|
+
respond_to?('decrypt')
|
45
|
+
else
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.config
|
51
|
+
Jerakia.config[:encryption] || {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def config
|
55
|
+
self.class.config
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'jerakia/util/http'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
class Jerakia
|
6
|
+
class Encryption
|
7
|
+
module Vault
|
8
|
+
class AuthenticationError < Jerakia::Error
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :approle_token
|
12
|
+
attr_reader :vault_ssl_key
|
13
|
+
attr_reader :vault_ssl_cert
|
14
|
+
|
15
|
+
def signiture
|
16
|
+
/^vault:v[0-9]:[^ ]+$/
|
17
|
+
end
|
18
|
+
|
19
|
+
def config
|
20
|
+
{
|
21
|
+
"vault_keyname" => "jerakia",
|
22
|
+
"vault_addr" => "https://127.0.0.1:8200",
|
23
|
+
"vault_use_ssl" => true,
|
24
|
+
"vault_ssl_verify" => true,
|
25
|
+
"vault_token" => ENV['VAULT_TOKEN'] || nil,
|
26
|
+
"vault_api_version" => 1
|
27
|
+
}.merge(self.class.config)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
def vault_url(endpoint)
|
33
|
+
uri = []
|
34
|
+
uri << config['vault_addr']
|
35
|
+
uri << "v#{config['vault_api_version']}"
|
36
|
+
uri << endpoint
|
37
|
+
uri.flatten.join("/")
|
38
|
+
end
|
39
|
+
|
40
|
+
def login
|
41
|
+
Jerakia.log.debug('Requesting new token from Vault server')
|
42
|
+
role_id = config['vault_role_id']
|
43
|
+
secret_id = config['vault_secret_id']
|
44
|
+
|
45
|
+
login_data = { "role_id" => role_id }
|
46
|
+
login_data['secret_id'] = secret_id unless secret_id.nil?
|
47
|
+
|
48
|
+
response = vault_post(login_data, :login, false)
|
49
|
+
@approle_token = response['auth']['client_token']
|
50
|
+
Jerakia.log.debug("Recieved authentication token from vault server, ttl: #{response['auth']['lease_duration']}")
|
51
|
+
end
|
52
|
+
|
53
|
+
def ssl?
|
54
|
+
config['vault_use_ssl']
|
55
|
+
end
|
56
|
+
|
57
|
+
def read_file(file)
|
58
|
+
raise Jerakia::EncryptionError, "Cannot read #{file}" unless File.exists?(file)
|
59
|
+
File.read(file)
|
60
|
+
end
|
61
|
+
|
62
|
+
def ssl_key
|
63
|
+
return nil if config['vault_ssl_key'].nil?
|
64
|
+
@vault_ssl_key ||= read_file(config['vault_ssl_key'])
|
65
|
+
vault_ssl_key
|
66
|
+
end
|
67
|
+
|
68
|
+
def ssl_cert
|
69
|
+
return nil if config['vault_ssl_cert'].nil?
|
70
|
+
@vault_ssl_cert ||= read_file(config['vault_ssl_cert'])
|
71
|
+
vault_ssl_cert
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def token_configured?
|
76
|
+
not config['vault_token'].nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
def token
|
80
|
+
authenticate
|
81
|
+
config['vault_token'] || approle_token
|
82
|
+
end
|
83
|
+
|
84
|
+
def authenticate
|
85
|
+
unless token_configured?
|
86
|
+
login if approle_token.nil?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def endpoint(action)
|
91
|
+
{
|
92
|
+
:decrypt => "transit/decrypt/#{config['vault_keyname']}",
|
93
|
+
:encrypt => "transit/encrypt/#{config['vault_keyname']}",
|
94
|
+
:login => "auth/approle/login"
|
95
|
+
}[action]
|
96
|
+
end
|
97
|
+
|
98
|
+
def url_path(action)
|
99
|
+
vault_url(endpoint(action))
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
def parse_response(response)
|
105
|
+
body = JSON.load(response.body)
|
106
|
+
if response.code_type == Net::HTTPOK
|
107
|
+
return body
|
108
|
+
else
|
109
|
+
if response.code == "403"
|
110
|
+
raise Jerakia::Encryption::Vault::AuthenticationError, body
|
111
|
+
end
|
112
|
+
if body['errors'].is_a?(Array)
|
113
|
+
message = body['errors'].join("\n")
|
114
|
+
else
|
115
|
+
message = "Failed to decrypt entry #{body}"
|
116
|
+
end
|
117
|
+
raise Jerakia::EncryptionError, "Error decrypting data from Vault: #{message}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def vault_post(data, action, use_token=true, headers={})
|
122
|
+
url = url_path(action)
|
123
|
+
http_options = {}
|
124
|
+
|
125
|
+
if ssl?
|
126
|
+
http_options = {
|
127
|
+
:ssl => true,
|
128
|
+
:ssl_verify => config['vault_ssl_verify'],
|
129
|
+
:ssl_cert => ssl_cert,
|
130
|
+
:ssl_key => ssl_key,
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
Jerakia.log.debug("Connecting to vault at #{url}")
|
135
|
+
tries = 0
|
136
|
+
begin
|
137
|
+
headers['X-Vault-Token'] = token if use_token
|
138
|
+
tries += 1
|
139
|
+
parse_response Jerakia::Util::Http.post(url, data, headers, http_options)
|
140
|
+
rescue Jerakia::Encryption::Vault::AuthenticationError => e
|
141
|
+
Jerakia.log.debug("Encountered Jerakia::Encryption::Vault::AuthenticationError, retrying with new token (#{tries})")
|
142
|
+
|
143
|
+
login
|
144
|
+
retry if tries < 2
|
145
|
+
raise
|
146
|
+
rescue Jerakia::HTTPError => e
|
147
|
+
raise Jerakia::EncryptionError, "Error connecting to Vault service: #{e.message}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def decrypt(string)
|
152
|
+
response = vault_post({ 'ciphertext' => string}, :decrypt)
|
153
|
+
response_data=response['data']
|
154
|
+
Base64.decode64(response_data['plaintext'])
|
155
|
+
end
|
156
|
+
|
157
|
+
def encrypt(plain)
|
158
|
+
encoded = Base64.encode64(plain)
|
159
|
+
response = vault_post({ 'plaintext' => encoded}, :encrypt)
|
160
|
+
response_data=response['data']
|
161
|
+
response_data['ciphertext']
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
data/lib/jerakia/error.rb
CHANGED
data/lib/jerakia/launcher.rb
CHANGED
@@ -8,15 +8,27 @@ class Jerakia
|
|
8
8
|
class Launcher
|
9
9
|
attr_reader :request
|
10
10
|
attr_reader :answer
|
11
|
+
attr_reader :policies
|
11
12
|
|
12
|
-
def initialize
|
13
|
-
@
|
13
|
+
def initialize
|
14
|
+
@policies = {}
|
15
|
+
policy_files.each do |policy_file|
|
16
|
+
policy = Jerakia::Dsl::Policy.evaluate_file(policy_file)
|
17
|
+
raise Jerakia::PolicyError, "Policy #{policy.name} declared twice" if @policies[policy.name]
|
18
|
+
@policies[policy.name] = policy
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
def policy_dir
|
23
|
+
Jerakia.config.policydir
|
24
|
+
end
|
25
|
+
|
26
|
+
def policy_files
|
27
|
+
Dir[File.join(policy_dir, '*.rb')]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.evaluate(&block)
|
31
|
+
Jerakia::Dsl::Policy.evaluate(&block)
|
20
32
|
end
|
21
33
|
|
22
34
|
def invoke_from_file
|