jerakia 1.2.1 → 2.0.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.
- 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
|