jerakia 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hiera/backend/jerakia_backend.rb +13 -14
  3. data/lib/jerakia/answer.rb +28 -27
  4. data/lib/jerakia/cache/entry.rb +2 -6
  5. data/lib/jerakia/cache/file.rb +53 -23
  6. data/lib/jerakia/cache.rb +44 -11
  7. data/lib/jerakia/cli/lookup.rb +124 -0
  8. data/lib/jerakia/cli/server.rb +50 -0
  9. data/lib/jerakia/cli/token.rb +64 -0
  10. data/lib/jerakia/cli.rb +7 -117
  11. data/lib/jerakia/config.rb +5 -5
  12. data/lib/jerakia/datasource/dummy.rb +1 -6
  13. data/lib/jerakia/datasource/file/json.rb +1 -3
  14. data/lib/jerakia/datasource/file/yaml.rb +1 -3
  15. data/lib/jerakia/datasource/file.rb +21 -44
  16. data/lib/jerakia/datasource/http.rb +17 -23
  17. data/lib/jerakia/datasource.rb +37 -36
  18. data/lib/jerakia/dsl/lookup.rb +4 -6
  19. data/lib/jerakia/dsl/policy.rb +11 -12
  20. data/lib/jerakia/error.rb +0 -5
  21. data/lib/jerakia/launcher.rb +26 -30
  22. data/lib/jerakia/log.rb +21 -22
  23. data/lib/jerakia/lookup/plugin/hiera.rb +3 -4
  24. data/lib/jerakia/lookup/plugin.rb +5 -6
  25. data/lib/jerakia/lookup/plugin_config.rb +31 -0
  26. data/lib/jerakia/lookup/pluginfactory.rb +30 -36
  27. data/lib/jerakia/lookup.rb +31 -32
  28. data/lib/jerakia/policy.rb +60 -45
  29. data/lib/jerakia/request.rb +3 -2
  30. data/lib/jerakia/response/filter/encryption.rb +7 -12
  31. data/lib/jerakia/response/filter/strsub.rb +4 -9
  32. data/lib/jerakia/response/filter.rb +5 -5
  33. data/lib/jerakia/response.rb +7 -13
  34. data/lib/jerakia/schema.rb +23 -35
  35. data/lib/jerakia/scope/metadata.rb +0 -1
  36. data/lib/jerakia/scope/puppetdb.rb +38 -0
  37. data/lib/jerakia/scope/server.rb +60 -0
  38. data/lib/jerakia/scope/yaml.rb +3 -4
  39. data/lib/jerakia/scope.rb +0 -2
  40. data/lib/jerakia/server/auth/token.rb +35 -0
  41. data/lib/jerakia/server/auth.rb +72 -0
  42. data/lib/jerakia/server/rest.rb +140 -0
  43. data/lib/jerakia/server.rb +41 -0
  44. data/lib/jerakia/util.rb +6 -7
  45. data/lib/jerakia/version.rb +1 -3
  46. data/lib/jerakia.rb +58 -40
  47. data/lib/puppet/indirector/data_binding/jerakia.rb +9 -11
  48. data/lib/puppet/indirector/data_binding/jerakia_rest.rb +11 -13
  49. metadata +78 -11
@@ -1,19 +1,17 @@
1
1
  class Jerakia::Schema
2
-
3
2
  # Arguments: request(Jerakia::Request), opts(Hash)
4
3
  #
5
- def initialize(request,opts)
6
- schema_datasource=datasource(opts)
7
- schema_request=Jerakia::Request.new(
4
+ def initialize(request, opts)
5
+ schema_datasource = datasource(opts)
6
+ schema_request = Jerakia::Request.new(
8
7
  :metadata => request.metadata,
9
8
  :key => request.key,
10
9
  :namespace => request.namespace,
11
- :use_schema => false,
10
+ :use_schema => false
12
11
  )
13
12
 
14
13
  Jerakia.log.debug("Schema lookup invoked for #{request.key} namespace: #{request.namespace}")
15
- schema_lookup = Jerakia::Launcher.new(schema_request)
16
-
14
+ schema_lookup = Jerakia::Launcher.new(schema_request)
17
15
 
18
16
  begin
19
17
  schema_lookup.evaluate do
@@ -27,58 +25,48 @@ class Jerakia::Schema
27
25
  raise Jerakia::SchemaError, "Schema lookup for #{request.key} failed: #{e.message}"
28
26
  end
29
27
 
30
-
31
28
  @schema_data = schema_lookup.answer.payload || {}
32
29
 
33
30
  # Validate the returned data from the schema
34
31
  raise Jerakia::SchemaError, "Schema must return a hash for key #{request.key}" unless @schema_data.is_a?(Hash)
35
32
 
36
- valid_opts = [ "alias", "cascade", "merge" ]
33
+ valid_opts = %w(alias cascade merge)
37
34
  @schema_data.keys.each do |key|
38
35
  unless valid_opts.include?(key)
39
36
  raise Jerakia::SchemaError, "Unknown schema option #{key} for key #{request.key}"
40
37
  end
41
38
  end
42
39
 
43
-
44
-
45
-
46
40
  Jerakia.log.debug("Schema returned #{@schema_data}")
47
41
 
48
- if salias = @schema_data["alias"]
49
- Jerakia.log.debug("Schema alias found to #{@schema_data["alias"]}")
50
- request.namespace=Array(salias["namespace"]) if salias["namespace"]
51
- request.key = salias["key"] if salias["key"]
42
+ if salias = @schema_data['alias']
43
+ Jerakia.log.debug("Schema alias found to #{@schema_data['alias']}")
44
+ request.namespace = Array(salias['namespace']) if salias['namespace']
45
+ request.key = salias['key'] if salias['key']
52
46
  end
53
47
 
54
-
55
- if @schema_data["cascade"]
48
+ if @schema_data['cascade']
56
49
  Jerakia.log.debug("Overriding lookup_type from #{request.lookup_type} to :cascade")
57
- request.lookup_type= :cascade
50
+ request.lookup_type = :cascade
58
51
  end
59
52
 
60
- if @schema_data["merge"]
61
- if ["array", "hash", "deep_hash"].include?(@schema_data["merge"])
62
- request.merge = @schema_data["merge"].to_sym
53
+ if @schema_data['merge']
54
+ if %w(array hash deep_hash).include?(@schema_data['merge'])
55
+ request.merge = @schema_data['merge'].to_sym
63
56
  else
64
- raise Jerakia::SchemaError, "Invalid merge type #{@schema_data['merge']} found in schema for key #{request.key}"
57
+ raise Jerakia::SchemaError, "Invalid merge type #{@schema_data['merge']} found in schema for key #{request.key}"
65
58
  end
66
59
  end
67
-
68
60
  end
69
61
 
70
- def datasource(opts={})
71
- [
62
+ def datasource(opts = {})
63
+ [
72
64
  :file, {
73
- :docroot => opts["docroot"] || "/var/lib/jerakia/schema",
74
- :format => opts["format"] || :json,
75
- :enable_caching => opts["enable_caching"] || true,
76
- :searchpath => [ '' ],
65
+ :docroot => opts['docroot'] || '/var/lib/jerakia/schema',
66
+ :format => opts['format'] || :json,
67
+ :enable_caching => opts['enable_caching'] || true,
68
+ :searchpath => ['']
77
69
  }
78
- ]
70
+ ]
79
71
  end
80
-
81
-
82
72
  end
83
-
84
-
@@ -15,4 +15,3 @@ class Jerakia::Scope
15
15
  end
16
16
  end
17
17
  end
18
-
@@ -0,0 +1,38 @@
1
+ require 'lookup_http'
2
+ class Jerakia::Scope
3
+ module Puppetdb
4
+ def create
5
+ yaml_file = request.scope_options['file'] || './jerakia_scope.yaml'
6
+ puppetdb_host = request.scope_options['puppetdb_host'] || 'localhost'
7
+ puppetdb_port = request.scope_options['puppetdb_port'] || 8080
8
+ puppetdb_api = request.scope_options['puppetdb_api'] || 4
9
+ node = request.scope_options['node']
10
+
11
+ raise Jerakia::Error, "Must pass the option node to the puppetdb scope handler" unless node
12
+
13
+ connection_opts = {
14
+ :host => puppetdb_host,
15
+ :port => puppetdb_port,
16
+ :output => 'json',
17
+ :ignore_404 => true
18
+ }.merge(request.scope_options['puppetdb_http_opts'] || {})
19
+
20
+ puppetdb_con = LookupHttp.new(connection_opts)
21
+
22
+ case puppetdb_api
23
+ when 4
24
+ path = "/pdb/query/v4/nodes/#{node}/facts"
25
+ else
26
+ raise Jerakia::Error, "Unsupported PuppetDB API version, #{puppetdb_api}"
27
+ end
28
+
29
+ Jerakia.log.debug("Sending HTTP query to PuppetDB #{puppetdb_host}:#{puppetdb_port} at path #{path}")
30
+
31
+ response = puppetdb_con.get_parsed(path)
32
+
33
+ raise Jerakia::Error, "PuppetDB returned no data for node #{node}" unless response.is_a?(Array)
34
+
35
+ response.each { |r| value[r['name'].to_sym] = r['value'] }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ require 'securerandom'
2
+ require 'data_mapper'
3
+ # The server scope handler can store and retrieve scope data server side
4
+ #
5
+ class Jerakia::Scope
6
+ module Server
7
+ class Database
8
+ DataMapper.setup(:scope, "sqlite://#{Jerakia.config[:databasedir]}/scope.db")
9
+
10
+ class Resource
11
+ include DataMapper::Resource
12
+
13
+
14
+ def self.default_repository_name
15
+ :scope
16
+ end
17
+
18
+ property :id, Serial, :key => true
19
+ property :identifier, String, :index => true
20
+ property :realm, String, :index => true
21
+ property :uuid, String
22
+ property :scope, Object
23
+ end
24
+
25
+ DataMapper.repository(:scope).auto_upgrade!
26
+ DataMapper.repository(:scope).auto_migrate!
27
+ end
28
+
29
+ def create
30
+ realm = request.scope_options['realm']
31
+ identifier = request.scope_options['identifier']
32
+
33
+ raise Jerakia::Error, "Must supply realm and identifier for server scope handler" unless realm and identifier
34
+ resource = Jerakia::Scope::Server.find(realm, identifier)
35
+ raise Jerakia::Error, "No scope data found for realm:#{realm} identifier:#{identifier}" if resource.nil?
36
+ scope = resource.scope
37
+ raise Jerakia::Error, "Scope did not return a hash for realm:#{realm} identifier:#{identifier}" unless scope.is_a?(Hash)
38
+ @value = Hash[ scope.map { |k,v| [ k.to_sym, v ] } ]
39
+ end
40
+
41
+ class << self
42
+ def find(realm, identifier)
43
+ Database::Resource.first(:identifier => identifier, :realm => realm)
44
+ end
45
+
46
+
47
+ def store(realm, identifier, scope)
48
+ uuid = SecureRandom.uuid
49
+ entry = find(realm, identifier)
50
+ if entry.nil?
51
+ Database::Resource.create(:identifier => identifier, :realm => realm, :scope => scope, :uuid => uuid)
52
+ else
53
+ entry.update({:scope => scope, :uuid => uuid})
54
+ entry.save
55
+ end
56
+ uuid
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,13 +2,12 @@
2
2
  class Jerakia::Scope
3
3
  module Yaml
4
4
  def create
5
- yaml_file = request.scope_options["file"] || "./jerakia_scope.yaml"
6
- raise Jerakia::Error, "Scope file #{yaml_file} not found" unless File.exists?(yaml_file)
5
+ yaml_file = request.scope_options['file'] || './jerakia_scope.yaml'
6
+ raise Jerakia::Error, "Scope file #{yaml_file} not found" unless File.exist?(yaml_file)
7
7
  data = YAML.load(File.read(yaml_file))
8
- data.each do |key,val|
8
+ data.each do |key, val|
9
9
  value[key.to_sym] = val
10
10
  end
11
11
  end
12
12
  end
13
13
  end
14
-
data/lib/jerakia/scope.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  class Jerakia::Scope
2
-
3
2
  attr_reader :value
4
3
  attr_reader :handler
5
4
  attr_reader :request
@@ -12,5 +11,4 @@ class Jerakia::Scope
12
11
  instance_eval "extend Jerakia::Scope::#{@handler.to_s.capitalize}"
13
12
  create
14
13
  end
15
-
16
14
  end
@@ -0,0 +1,35 @@
1
+ require 'data_mapper'
2
+ require 'dm-sqlite-adapter'
3
+ require 'bcrypt'
4
+ require 'jerakia'
5
+ require 'jerakia/config'
6
+ class Jerakia
7
+ class Server
8
+ class Auth
9
+
10
+ Jerakia.log.debug("Authentication database sqlite://#{Jerakia.config[:vardir]}/tokens.db")
11
+
12
+ DataMapper.setup(:tokens, "sqlite://#{Jerakia.config[:databasedir]}/tokens.db")
13
+
14
+ class Token
15
+
16
+ include DataMapper::Resource
17
+ include BCrypt
18
+
19
+ def self.default_repository_name
20
+ :tokens
21
+ end
22
+
23
+ property :api_id, String, :key => true
24
+ property :token, BCryptHash
25
+ property :active, Boolean, :default => true
26
+ property :last_seen, DateTime, :default => DateTime.now
27
+ end
28
+
29
+ DataMapper.finalize
30
+ DataMapper.auto_upgrade!
31
+ end
32
+ end
33
+ end
34
+
35
+
@@ -0,0 +1,72 @@
1
+ require 'jerakia/server/auth/token'
2
+ require 'securerandom'
3
+
4
+ class Jerakia
5
+ class Server
6
+ class Auth
7
+
8
+ class << self
9
+
10
+ def generate_token
11
+ SecureRandom.hex(40)
12
+ end
13
+
14
+
15
+ def get_entry(api_id)
16
+ Jerakia::Server::Auth::Token.get(api_id)
17
+ end
18
+
19
+ def update(api_id, fields)
20
+ entry = get_entry(api_id)
21
+ entry.update(fields)
22
+ entry.save
23
+ end
24
+
25
+ def seen!(api_id)
26
+ update(api_id, { :last_seen => DateTime.now })
27
+ end
28
+
29
+ def disable(api_id)
30
+ update(api_id, { :active => false })
31
+ end
32
+
33
+ def enable(api_id)
34
+ update(api_id, { :active => true })
35
+ end
36
+
37
+ def destroy(api_id)
38
+ entry = get_entry(api_id)
39
+ entry.destroy
40
+ end
41
+
42
+ def exists?(api_id)
43
+ get_entry(api_id)
44
+ end
45
+
46
+ def create(api_id)
47
+ raise Jerakia::Error, "API ID #{api_id} already exists" if exists?(api_id)
48
+ token = generate_token
49
+ entry = Jerakia::Server::Auth::Token.new(:api_id => api_id, :token => token)
50
+ entry.save
51
+ api_id + ":" + token
52
+ end
53
+
54
+ def get_tokens
55
+ Jerakia::Server::Auth::Token.find
56
+ end
57
+
58
+ def authenticate(token_string)
59
+ api_id, token = token_string.split(/:/)
60
+ entry = get_entry(api_id)
61
+ return false if entry.nil?
62
+ if entry.token == token and entry.active
63
+ seen!(api_id)
64
+ true
65
+ else
66
+ false
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,140 @@
1
+ require 'sinatra'
2
+ require 'jerakia'
3
+ require 'jerakia/server/auth'
4
+ require 'json'
5
+ require 'jerakia/scope/server'
6
+
7
+ class Jerakia
8
+ class Server
9
+ class Rest < Sinatra::Base
10
+
11
+ def self.jerakia
12
+ Jerakia::Server.jerakia
13
+ end
14
+
15
+ def initialize
16
+ @authorized_tokens={}
17
+ super
18
+ end
19
+
20
+ def jerakia
21
+ self.class.jerakia
22
+ end
23
+
24
+ def auth_denied
25
+ request_failed('unauthorized', 401)
26
+ end
27
+
28
+ def token_ttl
29
+ Jerakia::Server.config["token_ttl"]
30
+ end
31
+
32
+ def token_valid?(token)
33
+ return false unless @authorized_tokens[token].is_a?(Time)
34
+ (Time.now - @authorized_tokens[token]) < token_ttl.to_i
35
+ end
36
+
37
+ def authenticate!
38
+ token = env['HTTP_X_AUTHENTICATION']
39
+ auth_denied if token.nil?
40
+ return true if token_valid?(token)
41
+ unless Jerakia::Server::Auth.authenticate(token)
42
+ auth_denied
43
+ end
44
+ @authorized_tokens[token] = Time.now
45
+ end
46
+
47
+ before do
48
+ content_type 'application/json'
49
+ end
50
+
51
+ get '/' do
52
+ auth_denied
53
+ end
54
+
55
+ def request_failed(message, status_code=501)
56
+ halt(status_code, {
57
+ :status => 'failed',
58
+ :message => message,
59
+ }.to_json)
60
+ end
61
+
62
+ def mandatory_params(mandatory, params)
63
+ mandatory.each do |m|
64
+ unless params.include?(m)
65
+ request_failed("Must include parameter #{m} in request", 400)
66
+ end
67
+ end
68
+ end
69
+
70
+ get '/v1/lookup' do
71
+ request_failed("Keyless lookups not supported in this version of Jerakia")
72
+ end
73
+
74
+ get '/v1/lookup/:key' do
75
+ mandatory_params(['namespace'], params)
76
+ request_opts = {
77
+ :key => params['key'],
78
+ :namespace => params['namespace'].split(/\//),
79
+ }
80
+
81
+ metadata = params.select { |k,v| k =~ /^metadata_.*/ }
82
+ scope_opts = params.select { |k,v| k =~ /^scope_.*/ }
83
+
84
+ request_opts[:metadata] = Hash[metadata.map { |k,v| [k.gsub(/^metadata_/, ""), v] }]
85
+ request_opts[:scope_options] = Hash[scope_opts.map { |k,v| [k.gsub(/^scope_/, ""), v] }]
86
+
87
+
88
+ request_opts[:policy] = params['policy'].to_sym if params['policy']
89
+ request_opts[:lookup_type] = params['lookup_type'].to_sym if params['lookup_type']
90
+ request_opts[:merge] = params['merge'].to_sym if params['merge']
91
+ request_opts[:scope] = params['scope'].to_sym if params['scope']
92
+ request_opts[:use_schema] = false if params['use_schema'] == 'false'
93
+
94
+ begin
95
+ request = Jerakia::Request.new(request_opts)
96
+ answer = jerakia.lookup(request)
97
+ rescue Jerakia::Error => e
98
+ request_failed(e.message, 501)
99
+ end
100
+ {
101
+ :status => 'ok',
102
+ :payload => answer.payload
103
+ }.to_json
104
+ end
105
+
106
+ get '/v1/scope/:realm/:identifier' do
107
+ resource = Jerakia::Scope::Server.find(params['realm'], params['identifier'])
108
+ if resource.nil?
109
+ halt(404, { :status => 'failed', :message => "No scope data found" }.to_json)
110
+ else
111
+ {
112
+ :status => 'ok',
113
+ :payload => resource.scope
114
+ }.to_json
115
+ end
116
+ end
117
+
118
+ put '/v1/scope/:realm/:identifier' do
119
+ scope = JSON.parse(request.body.read)
120
+ uuid = Jerakia::Scope::Server.store(params['realm'], params['identifier'], scope)
121
+ {
122
+ :status => 'ok',
123
+ :uuid => uuid
124
+ }.to_json
125
+ end
126
+
127
+ get '/v1/scope/:realm/:identifier/uuid' do
128
+ resource = Jerakia::Scope::Server.find(params['realm'], params['identifier'])
129
+ if resource.nil?
130
+ request_failed('No scope data found', 404)
131
+ else
132
+ {
133
+ :status => 'ok',
134
+ :uuid => resource.uuid
135
+ }.to_json
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,41 @@
1
+ require 'sinatra'
2
+ require 'jerakia'
3
+ require 'thin'
4
+ class Jerakia
5
+ class Server
6
+
7
+ def jerakia
8
+ self.class.jerakia
9
+ end
10
+
11
+ class << self
12
+
13
+ @jerakia = nil
14
+ @config = {}
15
+
16
+ attr_reader :config
17
+
18
+ def default_config
19
+ {
20
+ 'bind' => '127.0.0.1',
21
+ 'port' => '9843',
22
+ 'token_ttl' => 300,
23
+ }
24
+ end
25
+
26
+ def jerakia
27
+ @jerakia
28
+ end
29
+
30
+ def start(opts)
31
+ @jerakia = Jerakia.new(opts)
32
+ require 'jerakia/server/rest'
33
+ @config = default_config.merge(Jerakia.config[:server] || {})
34
+ Thin::Logging.logger=Jerakia.log.logger
35
+ Jerakia::Server::Rest.set :bind, @config['bind']
36
+ Jerakia::Server::Rest.set :port, @config['port']
37
+ Jerakia::Server::Rest.run!
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/jerakia/util.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  class Jerakia
2
2
  module Util
3
3
  class << self
4
- def autoload(path,mod)
4
+ def autoload(path, mod)
5
5
  Jerakia.log.debug "autoloading #{path} #{mod}"
6
- require "jerakia/#{path}/#{mod.to_s}"
6
+ require "jerakia/#{path}/#{mod}"
7
7
  end
8
8
 
9
9
  def walk(data)
@@ -21,7 +21,7 @@ class Jerakia
21
21
  end
22
22
 
23
23
  def walk_hash(data)
24
- data.inject({}) do |h,(k,v)|
24
+ data.each_with_object({}) do |(_k, v), h|
25
25
  if v.is_a?(Hash)
26
26
  walk_hash(v) { |x| yield x }
27
27
  elsif v.is_a?(Array)
@@ -29,9 +29,9 @@ class Jerakia
29
29
  else
30
30
  yield v
31
31
  end
32
- h
32
+ h
33
33
  end
34
- return data
34
+ data
35
35
  end
36
36
 
37
37
  def walk_array(data)
@@ -44,9 +44,8 @@ class Jerakia
44
44
  yield element
45
45
  end
46
46
  element
47
- end
47
+ end
48
48
  end
49
49
  end
50
50
  end
51
51
  end
52
-
@@ -1,9 +1,7 @@
1
1
  class Jerakia
2
-
3
2
  # Public API to retrieve the latest released version of the code
4
3
  #
5
4
  # This should be updated when a new gem is released and it is read from the gemspec file
6
5
  #
7
- VERSION = "1.1.2"
8
-
6
+ VERSION = '1.2.0'.freeze
9
7
  end