proxes 0.9.13 → 0.10.1

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ditty/components/proxes.rb +61 -16
  3. data/lib/proxes/controllers/permissions.rb +1 -6
  4. data/lib/proxes/controllers/search.rb +12 -10
  5. data/lib/proxes/controllers/status.rb +23 -86
  6. data/lib/proxes/controllers/status_checks.rb +18 -0
  7. data/lib/proxes/forwarder.rb +44 -20
  8. data/lib/proxes/middleware/error_handling.rb +5 -3
  9. data/lib/proxes/middleware/security.rb +4 -3
  10. data/lib/proxes/models/permission.rb +47 -3
  11. data/lib/proxes/models/status_check.rb +76 -0
  12. data/lib/proxes/models/status_checks/cluster_health_status_check.rb +19 -0
  13. data/lib/proxes/models/status_checks/cpu_status_check.rb +29 -0
  14. data/lib/proxes/models/status_checks/file_system_status_check.rb +32 -0
  15. data/lib/proxes/models/status_checks/jvm_heap_status_check.rb +28 -0
  16. data/lib/proxes/models/status_checks/memory_status_check.rb +29 -0
  17. data/lib/proxes/models/status_checks/nodes_status_check.rb +53 -0
  18. data/lib/proxes/policies/permission_policy.rb +3 -3
  19. data/lib/proxes/policies/request/bulk_policy.rb +0 -12
  20. data/lib/proxes/policies/request/create_policy.rb +0 -3
  21. data/lib/proxes/policies/request/index_policy.rb +0 -5
  22. data/lib/proxes/policies/request/mget_policy.rb +12 -0
  23. data/lib/proxes/policies/request/msearch_policy.rb +12 -0
  24. data/lib/proxes/policies/request/root_policy.rb +0 -3
  25. data/lib/proxes/policies/request/snapshot_policy.rb +0 -3
  26. data/lib/proxes/policies/request_policy.rb +24 -25
  27. data/lib/proxes/policies/search_policy.rb +6 -2
  28. data/lib/proxes/policies/status_check_policy.rb +37 -0
  29. data/lib/proxes/request.rb +19 -5
  30. data/lib/proxes/request/bulk.rb +8 -24
  31. data/lib/proxes/request/cat.rb +6 -0
  32. data/lib/proxes/request/create.rb +4 -0
  33. data/lib/proxes/request/index.rb +4 -0
  34. data/lib/proxes/request/mget.rb +24 -0
  35. data/lib/proxes/request/msearch.rb +24 -0
  36. data/lib/proxes/request/multi.rb +40 -0
  37. data/lib/proxes/request/search.rb +4 -0
  38. data/lib/proxes/request/stats.rb +4 -0
  39. data/lib/proxes/services/es.rb +5 -1
  40. data/lib/proxes/services/listener.rb +29 -6
  41. data/lib/proxes/services/search.rb +4 -1
  42. data/lib/proxes/version.rb +1 -1
  43. data/migrate/20180908_index_specific_permissions.rb +9 -0
  44. data/migrate/20190109_add_status_checks_table.rb +17 -0
  45. data/views/layout.haml +17 -6
  46. metadata +81 -25
  47. data/lib/proxes/helpers/indices.rb +0 -35
@@ -13,7 +13,7 @@ module ProxES
13
13
 
14
14
  def initialize(app, logger = nil)
15
15
  @app = app
16
- @logger = logger || ::Ditty::Services::Logger.instance
16
+ @logger = logger || ::Ditty::Services::Logger
17
17
  end
18
18
 
19
19
  def call(env)
@@ -22,8 +22,8 @@ module ProxES
22
22
 
23
23
  check_basic request
24
24
  authorize request
25
-
26
25
  request.index = policy_scope(request) if request.indices?
26
+
27
27
  log(request, 'AFTER')
28
28
 
29
29
  @app.call env
@@ -35,7 +35,8 @@ module ProxES
35
35
 
36
36
  identity = ::Ditty::Identity.find(username: auth.credentials[0])
37
37
  identity ||= ::Ditty::Identity.find(username: CGI.unescape(auth.credentials[0]))
38
- return false unless identity && identity.authenticate(auth.credentials[1])
38
+ return false unless identity&.authenticate(auth.credentials[1])
39
+
39
40
  request.env['rack.session'] ||= {}
40
41
  request.env['rack.session']['user_id'] = identity.user_id
41
42
  end
@@ -3,6 +3,7 @@
3
3
  require 'ditty/models/base'
4
4
  require 'ditty/models/user'
5
5
  require 'ditty/models/role'
6
+ require 'active_support/core_ext/object/blank'
6
7
 
7
8
  module ProxES
8
9
  class Permission < ::Sequel::Model
@@ -12,27 +13,57 @@ module ProxES
12
13
  many_to_one :user, class: ::Ditty::User
13
14
 
14
15
  dataset_module do
15
- def for_user(a_user, action)
16
- where(verb: action).where { Sequel.|({ role: a_user.roles }, { user_id: a_user.id }) }
16
+ def for_user(usr)
17
+ return where(id: -1) if usr.nil?
18
+
19
+ # TODO: Injection of user fields into regex
20
+ # permission.pattern.gsub(/\{user.(.*)\}/) { |_match| user.send(Regexp.last_match[1].to_sym) }
21
+ where { Sequel.|({ role: usr.roles }, { user_id: usr.id }) }
22
+ end
23
+
24
+ def for_request(request)
25
+ where(verb: request.request_method).all.select { |perm| perm.pattern_regex.match request.path }
17
26
  end
18
27
  end
19
28
 
20
29
  def validate
30
+ super
21
31
  validates_presence %i[verb pattern]
22
32
  validates_presence :role_id unless user_id
23
33
  validates_presence :user_id unless role_id
24
34
  validates_includes self.class.verbs, :verb
25
35
  end
26
36
 
37
+ def pattern_regex
38
+ regex pattern
39
+ end
40
+
41
+ def index_regex
42
+ regex index
43
+ end
44
+
45
+ private
46
+
47
+ def regex(str)
48
+ str ||= ''
49
+ return Regexp.new(str) if str.blank? || (str[0] == '|' && str[-1] == '|')
50
+
51
+ str = str.gsub(/([^.])\*/, '\1.*')
52
+ str = '.*' if str == '*' # My regex foo is not strong enough to combine the previous line and this one
53
+ Regexp.new '^' + str
54
+ end
55
+
27
56
  class << self
28
57
  def verbs
29
- %w[GET POST PUT DELETE HEAD OPTIONS TRACE INDEX]
58
+ %w[GET POST PUT DELETE HEAD OPTIONS TRACE]
30
59
  end
31
60
 
32
61
  def from_audit_log(audit_log)
33
62
  return {} if audit_log.details.nil?
63
+
34
64
  match = audit_log.details.match(/^(\w)+ (\S+)/)
35
65
  return {} if match.nil?
66
+
36
67
  {
37
68
  verb: match[1],
38
69
  path: match[2]
@@ -53,3 +84,16 @@ module Ditty
53
84
  one_to_many :permissions, class: ::ProxES::Permission
54
85
  end
55
86
  end
87
+
88
+ # Table: permissions
89
+ # Columns:
90
+ # id | integer | PRIMARY KEY AUTOINCREMENT
91
+ # verb | varchar(255) |
92
+ # pattern | varchar(255) |
93
+ # created_at | timestamp |
94
+ # role_id | integer |
95
+ # user_id | integer |
96
+ # index | varchar(255) | NOT NULL DEFAULT '*'
97
+ # Foreign key constraints:
98
+ # (role_id) REFERENCES roles
99
+ # (user_id) REFERENCES users
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/services/es'
4
+
5
+ module ProxES
6
+ class StatusCheck < Sequel::Model
7
+ plugin :single_table_inheritance, :type
8
+
9
+ extend ProxES::Services::ES
10
+
11
+ SOURCE_CALLS = {
12
+ health: %i[cluster health],
13
+ node_stats: %i[nodes stats]
14
+ }.freeze
15
+
16
+ def validate
17
+ super
18
+ validates_presence %i[name source]
19
+ end
20
+
21
+ def check
22
+ raise 'Unimplemented'
23
+ end
24
+
25
+ def value
26
+ raise 'Unimplemented'
27
+ end
28
+
29
+ def passed?
30
+ return @result if defined? @result
31
+
32
+ check
33
+ end
34
+
35
+ def source_result
36
+ self.class.source_result(source)
37
+ end
38
+
39
+ def children; end
40
+
41
+ def formatted(val = nil)
42
+ val || value
43
+ end
44
+
45
+ def policy_class
46
+ StatusCheckPolicy
47
+ end
48
+
49
+ class << self
50
+ def source_result(source)
51
+ @source_result ||= Hash.new do |h, k|
52
+ h[k] = client
53
+ SOURCE_CALLS[source.to_sym].each do |call|
54
+ h[k] = h[k].send(call)
55
+ end
56
+ h[k]
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ Dir.glob(File.expand_path('./status_checks', __dir__) + '/*.rb').each { |f| require f }
63
+ end
64
+
65
+ # Table: status_checks
66
+ # Columns:
67
+ # id | integer | PRIMARY KEY AUTOINCREMENT
68
+ # type | varchar(255) |
69
+ # name | varchar(255) |
70
+ # source | varchar(255) |
71
+ # required_value | varchar(255) |
72
+ # order | integer | DEFAULT 1
73
+ # created_at | timestamp | DEFAULT datetime(CURRENT_TIMESTAMP, 'localtime')
74
+ # updated_at | timestamp | DEFAULT datetime(CURRENT_TIMESTAMP, 'localtime')
75
+ # Indexes:
76
+ # sqlite_autoindex_status_checks_1 | UNIQUE (name)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProxES
4
+ class ClusterHealthStatusCheck < StatusCheck
5
+ def value
6
+ source_result['status']['status']
7
+ end
8
+
9
+ def check
10
+ return true if required_value.blank?
11
+
12
+ value == required_value
13
+ end
14
+
15
+ def formatted(val = nil)
16
+ (val || value).titlecase
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProxES
4
+ class CPUStatusCheck < StatusCheck
5
+ def value
6
+ children.values.inject(0.0) { |sum, el| sum + el } / children.count
7
+ end
8
+
9
+ def children
10
+ @children ||= source_result['nodes']['nodes'].values.map do |node|
11
+ value = node['os']['cpu_percent'] || node['os']['cpu']['percent']
12
+ [
13
+ node['name'],
14
+ value.to_f
15
+ ]
16
+ end.to_h
17
+ end
18
+
19
+ def check
20
+ return true if required_value.blank?
21
+
22
+ value < required_value.to_f
23
+ end
24
+
25
+ def formatted(val = nil)
26
+ format('%.4f%% Average Usage', val || value)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProxES
4
+ class FileSystemStatusCheck < StatusCheck
5
+ def value
6
+ children.values.min
7
+ end
8
+
9
+ def children
10
+ @children ||= source_result['nodes']['nodes'].values.map do |node|
11
+ next if node['attributes'] && node['attributes']['data'] == 'false'
12
+ next if node['roles'] && node['roles'].include?('data') == false
13
+
14
+ stats = node['fs']['total']
15
+ [
16
+ node['name'],
17
+ stats['available_in_bytes'] / stats['total_in_bytes'].to_f * 100
18
+ ]
19
+ end.compact.to_h
20
+ end
21
+
22
+ def check
23
+ return true if required_value.blank?
24
+
25
+ value > required_value.to_f
26
+ end
27
+
28
+ def formatted(val = nil)
29
+ format('%.4f%% Minimum Free', val || value)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProxES
4
+ class JVMHeapStatusCheck < StatusCheck
5
+ def value
6
+ children.values.inject(0.0) { |sum, el| sum + el } / children.count
7
+ end
8
+
9
+ def children
10
+ @children ||= source_result['nodes']['nodes'].values.map do |node|
11
+ [
12
+ node['name'],
13
+ node['jvm']['mem']['heap_used_percent'].to_f
14
+ ]
15
+ end.to_h
16
+ end
17
+
18
+ def check
19
+ return true if required_value.blank?
20
+
21
+ value < required_value.to_f
22
+ end
23
+
24
+ def formatted(val = nil)
25
+ format('%.4f%% Average Usage', val || value)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProxES
4
+ class MemoryStatusCheck < StatusCheck
5
+ def value
6
+ # Currently checks the average. Can change it to check per node too
7
+ children.values.inject(0.0) { |sum, el| sum + el } / children.count
8
+ end
9
+
10
+ def children
11
+ @children ||= source_result['nodes']['nodes'].values.map do |node|
12
+ [
13
+ node['name'],
14
+ node['os']['mem']['used_percent'].to_f
15
+ ]
16
+ end.to_h
17
+ end
18
+
19
+ def check
20
+ return true if required_value.blank?
21
+
22
+ value < required_value.to_f
23
+ end
24
+
25
+ def formatted(val = nil)
26
+ format('%.4f%% Average Usage', val || value)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProxES
4
+ class NodesStatusCheck < StatusCheck
5
+ def node_type
6
+ raise 'Unimplemented'
7
+ end
8
+
9
+ def value
10
+ children.count
11
+ end
12
+
13
+ def check_node(node)
14
+ node['roles']&.include?(node_type) ||
15
+ node['attributes'] && node.dig('attributes', node_type) != 'false' ||
16
+ node.dig('settings', 'node') && node.dig('settings', 'node', node_type) != 'false'
17
+ end
18
+
19
+ def children
20
+ @children ||= source_result['nodes']['nodes'].map do |_id, node|
21
+ [node['name'], 1] if check_node(node)
22
+ end.compact.to_h
23
+ end
24
+
25
+ def check
26
+ return true if required_value.blank?
27
+
28
+ required_value.to_i == value
29
+ end
30
+
31
+ def formatted(val = nil)
32
+ (val || value).to_i == 1 ? '1 Node' : "#{val || value} Nodes"
33
+ end
34
+ end
35
+
36
+ class MasterNodesStatusCheck < NodesStatusCheck
37
+ def node_type
38
+ 'master'
39
+ end
40
+ end
41
+
42
+ class DataNodesStatusCheck < NodesStatusCheck
43
+ def node_type
44
+ 'data'
45
+ end
46
+ end
47
+
48
+ class IngestNodesStatusCheck < NodesStatusCheck
49
+ def node_type
50
+ 'ingest'
51
+ end
52
+ end
53
+ end
@@ -5,7 +5,7 @@ require 'ditty/policies/application_policy'
5
5
  module ProxES
6
6
  class PermissionPolicy < Ditty::ApplicationPolicy
7
7
  def create?
8
- user && user.super_admin?
8
+ user&.super_admin?
9
9
  end
10
10
 
11
11
  def list?
@@ -25,12 +25,12 @@ module ProxES
25
25
  end
26
26
 
27
27
  def permitted_attributes
28
- %i[verb pattern role_id user_id]
28
+ %i[verb pattern index role_id user_id]
29
29
  end
30
30
 
31
31
  class Scope < Ditty::ApplicationPolicy::Scope
32
32
  def resolve
33
- user && user.super_admin? ? scope : scope.where(id: -1)
33
+ user&.super_admin? ? scope : scope.where(id: -1)
34
34
  end
35
35
  end
36
36
  end
@@ -1,22 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support'
4
- require 'active_support/core_ext/object/blank'
5
3
  require 'proxes/policies/request_policy'
6
4
 
7
5
  module ProxES
8
6
  class Request
9
7
  class BulkPolicy < RequestPolicy
10
- def post?
11
- return false if user.nil? ||
12
- (request.index && !index_allowed?) ||
13
- (request.bulk_indices == '' || patterns.blank?)
14
-
15
- patterns.find do |pattern|
16
- request.bulk_indices.find { |idx| idx !~ /#{pattern}/ }
17
- end.nil?
18
- end
19
-
20
8
  class Scope < RequestPolicy::Scope
21
9
  end
22
10
  end
@@ -6,9 +6,6 @@ module ProxES
6
6
  class Request
7
7
  class CreatePolicy < RequestPolicy
8
8
  class Scope < RequestPolicy::Scope
9
- def resolve
10
- super.count > 0 ? request.index : []
11
- end
12
9
  end
13
10
  end
14
11
  end