proxes 0.9.8 → 0.9.9

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ditty/components/proxes.rb +89 -0
  3. data/lib/proxes/controllers/permissions.rb +41 -0
  4. data/lib/proxes/controllers/search.rb +55 -0
  5. data/lib/proxes/controllers/status.rb +115 -0
  6. data/lib/proxes/forwarder.rb +49 -0
  7. data/lib/proxes/helpers/indices.rb +33 -0
  8. data/lib/proxes/loggers/elasticsearch.rb +10 -0
  9. data/lib/proxes/middleware/error_handling.rb +64 -0
  10. data/lib/proxes/middleware/metrics.rb +25 -0
  11. data/lib/proxes/middleware/security.rb +59 -0
  12. data/lib/proxes/models/permission.rb +55 -0
  13. data/lib/proxes/policies/permission_policy.rb +37 -0
  14. data/lib/proxes/policies/request/bulk_policy.rb +24 -0
  15. data/lib/proxes/policies/request/cat_policy.rb +12 -0
  16. data/lib/proxes/policies/request/create_policy.rb +15 -0
  17. data/lib/proxes/policies/request/index_policy.rb +19 -0
  18. data/lib/proxes/policies/request/root_policy.rb +13 -0
  19. data/lib/proxes/policies/request/search_policy.rb +14 -0
  20. data/lib/proxes/policies/request/snapshot_policy.rb +15 -0
  21. data/lib/proxes/policies/request/stats_policy.rb +12 -0
  22. data/lib/proxes/policies/request_policy.rb +62 -0
  23. data/lib/proxes/policies/search_policy.rb +29 -0
  24. data/lib/proxes/policies/status_policy.rb +21 -0
  25. data/lib/proxes/request.rb +84 -0
  26. data/lib/proxes/request/bulk.rb +40 -0
  27. data/lib/proxes/request/cat.rb +32 -0
  28. data/lib/proxes/request/create.rb +33 -0
  29. data/lib/proxes/request/index.rb +33 -0
  30. data/lib/proxes/request/root.rb +11 -0
  31. data/lib/proxes/request/search.rb +37 -0
  32. data/lib/proxes/request/snapshot.rb +17 -0
  33. data/lib/proxes/request/stats.rb +35 -0
  34. data/lib/proxes/services/es.rb +34 -0
  35. data/lib/proxes/services/listener.rb +29 -0
  36. data/lib/proxes/services/search.rb +45 -0
  37. data/lib/proxes/version.rb +5 -0
  38. data/migrate/20170209_permissions.rb +13 -0
  39. data/migrate/20170416_user_specific_permissions.rb +9 -0
  40. data/public/browserconfig.xml +9 -0
  41. data/public/manifest.json +25 -0
  42. data/views/index.haml +1 -0
  43. data/views/layout.haml +60 -0
  44. metadata +44 -2
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request/bulk_policy'
5
+
6
+ module ProxES
7
+ class Request
8
+ class Bulk < Request
9
+ attr_reader :index, :type
10
+
11
+ REGEX = /"(index|delete|create|update)".*"_index"\s*:\s*"(.*?)"/
12
+
13
+ def bulk_indices
14
+ @bulk_indices ||= begin
15
+ body.read.scan(REGEX).tap { |_r| body.rewind }
16
+ end.map { |e| e[1] }.uniq
17
+ end
18
+
19
+ def index=(idx)
20
+ @index = idx
21
+ self.path_info = '/' + [index, type, endpoint].compact
22
+ .map { |v| v.is_a?(Array) ? v.join(',') : v }
23
+ .select { |v| !v.nil? && v != '' }.join('/')
24
+ end
25
+
26
+ def endpoint
27
+ '_bulk'
28
+ end
29
+
30
+ def parse
31
+ @index ||= check_part(path_parts[0]) unless path_parts[0] == endpoint
32
+ @type ||= check_part(path_parts[1]) unless path_parts[1] == endpoint
33
+ end
34
+
35
+ def indices?
36
+ !@index.nil?
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request/cat_policy'
5
+
6
+ module ProxES
7
+ class Request
8
+ class Cat < Request
9
+ attr_reader :index, :type
10
+
11
+ def index=(idx)
12
+ @index = idx
13
+ self.path_info = '/' + [endpoint, type, index].compact
14
+ .map { |v| v.is_a?(Array) ? v.join(',') : v }
15
+ .select { |v| !v.nil? && v != '' }.join('/')
16
+ end
17
+
18
+ def endpoint
19
+ '_cat'
20
+ end
21
+
22
+ def parse
23
+ @type ||= check_part(path_parts[1])
24
+ @index ||= check_part(path_parts[2])
25
+ end
26
+
27
+ def indices?
28
+ %w[shards indices segments count recovery].include? type.first
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request/create_policy'
5
+
6
+ module ProxES
7
+ class Request
8
+ class Create < Request
9
+ attr_reader :index, :type, :id
10
+
11
+ def index=(idx)
12
+ @index = idx
13
+ self.path_info = '/' + [index, type, id, endpoint].compact
14
+ .map { |v| v.is_a?(Array) ? v.join(',') : v }
15
+ .select { |v| !v.nil? && v != '' }.join('/')
16
+ end
17
+
18
+ def endpoint
19
+ '_create'
20
+ end
21
+
22
+ def parse
23
+ @index ||= check_part(path_parts[0])
24
+ @type ||= check_part(path_parts[1])
25
+ @id ||= check_part(path_parts[2])
26
+ end
27
+
28
+ def indices?
29
+ true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request/index_policy'
5
+
6
+ module ProxES
7
+ class Request
8
+ class Index < Request
9
+ attr_reader :index, :type, :id
10
+
11
+ def index=(idx)
12
+ @index = idx
13
+ self.path_info = '/' + [index, type, id].compact
14
+ .map { |v| v.is_a?(Array) ? v.join(',') : v }
15
+ .select { |v| !v.nil? && v != '' }.join('/')
16
+ end
17
+
18
+ def endpoint
19
+ nil
20
+ end
21
+
22
+ def parse
23
+ @index ||= check_part(path_parts[0])
24
+ @type ||= check_part(path_parts[1])
25
+ @id ||= check_part(path_parts[2])
26
+ end
27
+
28
+ def indices?
29
+ true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request/root_policy'
5
+
6
+ module ProxES
7
+ class Request
8
+ class Root < Request
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request/search_policy'
5
+
6
+ module ProxES
7
+ class Request
8
+ class Search < Request
9
+ attr_reader :index, :type
10
+
11
+ def index=(idx)
12
+ @index = idx
13
+ self.path_info = '/' + [index, type, id, endpoint].compact
14
+ .map { |v| v.is_a?(Array) ? v.join(',') : v }
15
+ .select { |v| !v.nil? && v != '' }.join('/')
16
+ end
17
+
18
+ def endpoint
19
+ '_search'
20
+ end
21
+
22
+ def parse
23
+ @index ||= check_part(path_parts[0]) unless path_parts[0] == endpoint
24
+ @type ||= check_part(path_parts[1]) unless path_parts[1] == endpoint
25
+ @id ||= check_part(path_parts[2]) unless path_parts[2] == endpoint
26
+ end
27
+
28
+ def id
29
+ @id == [] ? nil : @id
30
+ end
31
+
32
+ def indices?
33
+ type != ['scroll']
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request/snapshot_policy'
5
+
6
+ module ProxES
7
+ class Request
8
+ class Snapshot < Request
9
+ attr_reader :repository
10
+
11
+ def parse
12
+ @repository ||= check_part(path_parts[1])
13
+ @repository = [] if repository.nil?
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request/stats_policy'
5
+
6
+ module ProxES
7
+ class Request
8
+ class Stats < Request
9
+ attr_reader :index
10
+
11
+ def index=(idx)
12
+ @index = idx
13
+ self.path_info = '/' + [index, endpoint].compact
14
+ .map { |v| v.is_a?(Array) ? v.join(',') : v }
15
+ .select { |v| !v.nil? && v != '' }.join('/')
16
+ end
17
+
18
+ def endpoint
19
+ '_stats'
20
+ end
21
+
22
+ def parse
23
+ @index ||= check_part(path_parts[0])
24
+ end
25
+
26
+ def stats
27
+ @stats ||= check_part(path_parts[2])
28
+ end
29
+
30
+ def indices?
31
+ true
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'elasticsearch'
5
+ require 'ditty/services/logger'
6
+
7
+ module ProxES
8
+ module Services
9
+ module ES
10
+ def client
11
+ @client ||= Elasticsearch::Client.new(
12
+ url: ENV['ELASTICSEARCH_URL'],
13
+ transport_options: {
14
+ ssl: {
15
+ verify: ENV['SSL_VERIFY_NONE'].to_i != 1,
16
+ cert_store: ssl_store
17
+ }
18
+ },
19
+ logger: Ditty::Services::Logger.instance
20
+ )
21
+ end
22
+
23
+ def ssl_store
24
+ store = OpenSSL::X509::Store.new
25
+ store.set_default_paths
26
+ store
27
+ end
28
+
29
+ def conn
30
+ client.transport.connections.get_connection.connection
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wisper'
4
+ require 'ditty/models/audit_log'
5
+ require 'ditty/services/logger'
6
+
7
+ module ProxES
8
+ class Listener
9
+ def es_request_failed(request, response)
10
+ Ditty::AuditLog.create(
11
+ action: :es_request_failed,
12
+ user: request.user,
13
+ details: "#{request.detail} > #{response[0]}"
14
+ )
15
+ end
16
+
17
+ def es_request_denied(request, exception = nil)
18
+ detail = request.detail
19
+ detail = "#{detail} - #{exception.class}" if exception
20
+ Ditty::AuditLog.create(
21
+ action: :es_request_denied,
22
+ user: request.user,
23
+ details: detail
24
+ )
25
+ end
26
+ end
27
+ end
28
+
29
+ Wisper.subscribe(ProxES::Listener.new) unless ENV['RACK_ENV'] == 'test'
@@ -0,0 +1,45 @@
1
+ require 'proxes/services/es'
2
+
3
+ # TODO: This needs to be filtered.
4
+
5
+ module ProxES
6
+ module Services
7
+ module Search
8
+ class << self
9
+ include ES
10
+
11
+ def indices
12
+ client.indices.get_mapping(index: '_all').keys
13
+ end
14
+
15
+ def fields(index: '_all', names_only: false)
16
+ fields = {}
17
+ client.indices.get_mapping(index: index).each do |_idx, index_map|
18
+ index_map['mappings'].each do |_type, type_map|
19
+ next if type_map['properties'].nil?
20
+ type_map['properties'].each do |name, details|
21
+ if details['type'] != 'keyword' && details['fields'] && (names_only == false)
22
+ keyword = details['fields'].find do |v|
23
+ v[1]['type'] == 'keyword'
24
+ end
25
+ fields["#{name}.#{keyword[0]}"] ||= keyword[1]['type'] if keyword
26
+ end
27
+ fields[name] ||= details['type'] unless details['type'].nil?
28
+ end
29
+ end.to_h
30
+ end
31
+ fields
32
+ end
33
+
34
+ def values(field, index = '_all', size = 25)
35
+ result = client.search index: index, body: { size: 0, aggs: { values: { terms: { field: field, size: size } } } }
36
+ result['aggregations']['values']['buckets'].map { |e| e['key'] }
37
+ end
38
+
39
+ def search(qs, options = {})
40
+ client.search options.merge(q: qs) # , explain: true
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProxES
4
+ VERSION = '0.9.9'.freeze
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table :permissions do
6
+ primary_key :id
7
+ String :verb
8
+ String :pattern
9
+ DateTime :created_at
10
+ foreign_key :role_id, :roles
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ alter_table :permissions do
6
+ add_foreign_key :user_id, :users
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <browserconfig>
3
+ <msapplication>
4
+ <tile>
5
+ <square150x150logo src="/_proxes/images/mstile-150x150.png"/>
6
+ <TileColor>#da532c</TileColor>
7
+ </tile>
8
+ </msapplication>
9
+ </browserconfig>
@@ -0,0 +1,25 @@
1
+ {
2
+ "short_name": "ProxES",
3
+ "name": "ProxES",
4
+ "icons": [
5
+ {
6
+ "src": "_proxes/images/launcher-icon-1x.png",
7
+ "type": "image/png",
8
+ "sizes": "48x48"
9
+ },
10
+ {
11
+ "src": "_proxes/images/launcher-icon-2x.png",
12
+ "type": "image/png",
13
+ "sizes": "96x96"
14
+ },
15
+ {
16
+ "src": "_proxes/images/launcher-icon-4x.png",
17
+ "type": "image/png",
18
+ "sizes": "192x192"
19
+ }
20
+ ],
21
+ "start_url": "_proxes/auth/login",
22
+ "theme_color": "#bebebe",
23
+ "background_color": "#bebebe",
24
+ "display": "fullscreen"
25
+ }
@@ -0,0 +1 @@
1
+ #react-dashboard{ 'data-elasticsearch-url' => '..'}
@@ -0,0 +1,60 @@
1
+ !!! 5
2
+ %html{ lang: 'en' }
3
+ %head
4
+ %meta{ charset: 'utf-8' }
5
+ %meta{ 'http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1' }
6
+ %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
7
+ %meta{ name: 'theme-color', content: '#ffffff' }
8
+ %link{ rel: 'manifest', href: '/_proxes/manifest.json' }
9
+ %link{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/_proxes/images/favicon-32x32.png' }
10
+ %link{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/_proxes/images/favicon-16x16.png' }
11
+ %link{ rel: 'apple-touch-icon', sizes: '76x76', href: '/_proxes/images/apple-icon.png' }
12
+ %link{ rel: 'mask-icon', href: '/_proxes/images/safari-pinned-tab.svg', color: '#5bbad5' }
13
+
14
+ %title
15
+ ProxES
16
+ - if defined? title
17
+ = "- #{title}"
18
+
19
+ %meta{ name: 'description', content: '' }
20
+ %meta{ name: 'author', content: '' }
21
+
22
+ / Le styles
23
+ %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', media: 'screen' }
24
+ %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/3.3.7+1/css/sb-admin-2.min.css', media: 'screen' }
25
+ %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/metisMenu/2.5.2/metisMenu.min.css', media: 'screen' }
26
+ %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css', media: 'screen' }
27
+ /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
28
+ /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
29
+
30
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js' }
31
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/3.3.7+1/js/sb-admin-2.min.js' }
32
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/metisMenu/2.5.2/metisMenu.min.js' }
33
+ %body
34
+ #wrapper
35
+ = haml :'partials/navbar', locals: { title: (defined?(title) ? title : 'ProxES') }
36
+ #page-wrapper{ style: 'opacity: 0.8' }
37
+ .row
38
+ .col-md-12
39
+ -if defined? title
40
+ %h1.page-header= title
41
+ = haml :'partials/notifications'
42
+
43
+ = yield
44
+ %footer.footer.text-muted.text-center
45
+ %hr
46
+ .copyright
47
+ :plain
48
+ &copy; <script>document.write(new Date().getFullYear())</script>, DataTools.io
49
+
50
+
51
+ / Placed at the end of the document so the pages load faster
52
+ %script{ type: 'text/javascript', src: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' }
53
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js' }
54
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.min.js' }
55
+ %script{ type: 'text/javascript', src: '/_proxes/js/bundle.js' }
56
+ :javascript
57
+ $(function() {
58
+ $('.sidebar-nav').metisMenu();
59
+ });
60
+