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.
@@ -1,17 +1,15 @@
1
1
  class Jerakia::Datasource
2
- module File
3
- class Json
2
+ class File
3
+ module Json
4
4
  EXTENSION = 'json'.freeze
5
5
 
6
- class << self
7
- require 'json'
8
- def convert(data)
9
- return {} if data.empty?
10
- begin
11
- JSON.load(data)
12
- rescue JSON::ParserError => e
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
- module File
3
- class Yaml
4
- EXTENSION = 'yaml'.freeze
5
-
6
- class << self
7
- require 'yaml'
8
- def convert(data)
9
- return {} if data.empty?
10
- begin
11
- YAML.load(data)
12
- rescue Psych::SyntaxError => e
13
- raise Jerakia::FileParseError, "Error parsing YAML document: #{e.message}"
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
@@ -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, policy, opts = {})
9
- @policy = policy
10
- @request = policy.clone_request
11
- scope = policy.scope
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, policy, opts, &block)
16
- lookup_block = new(name, policy, opts)
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
- policy.submit_lookup(lookup_block.lookup)
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 = Jerakia::Datasource.new(name, lookup, opts)
25
- lookup.datasource = datasource
26
- end
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
@@ -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, request)
7
- policy = new(request)
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(request, &block)
13
- policy = new(request)
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(req)
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, request)
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::Dsl::Lookup.evaluate(name, policy, opts, &block)
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
@@ -13,4 +13,14 @@ class Jerakia
13
13
 
14
14
  class FileParseError < Jerakia::Error
15
15
  end
16
+
17
+ class DatasourceArgumentError < Jerakia::Error
18
+ end
19
+
20
+ class HTTPError < Jerakia::Error
21
+ end
22
+
23
+ class EncryptionError < Jerakia::Error
24
+ end
25
+
16
26
  end
@@ -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(req)
13
- @request = req
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 evaluate(&block)
17
- policy = Jerakia::Dsl::Policy.evaluate(request, &block)
18
- policy.execute
19
- @answer = policy.answer
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