proxes 0.9.2 → 0.9.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23a733cd75da2400f2490d5586a3a8039e395bdede3ecdb8ac380d986a23c74e
4
- data.tar.gz: 8c316a8c6f51a14ec99c381594b1755b54064426d3885669fa0c77f40b1b2ad4
3
+ metadata.gz: a89e0871c6d36ce3b7e6ff19c4b7fa7556cedeedd0d364bd78d0aa741b06c6e9
4
+ data.tar.gz: b126f70689158d474c5f096386c13a46ffdb8e58c90c7c77181a11cef3de14d9
5
5
  SHA512:
6
- metadata.gz: 45bdf16dc25e971b2b7b0ff43f7e6cf1f49fe8d9d62a8b3ba7b28a846734a775b7d416dfbfbc07057ac79768308abb7ea114d3a35bc4e0dbe91961e044c463e3
7
- data.tar.gz: c104db7338a96b33a5a44b46d2b92d90ad531b8335fe2864669014524f0cbfc151a51a7fe4ec00efa723eba1e8925432ff036d115fceb85d9424801ebb7fba73
6
+ metadata.gz: 83df2e988cb4f9d64fae3693603dddb03912483b29e04b3c65e0ee691b341b2fe68d029e2253559534c23ee15fad129d83b7954261a297f2ab004e97dc81f2ba
7
+ data.tar.gz: 591c032478769ea14170e7dfb8ae92c7e34f0a5ad23dc4569bfcb5745ba47bcaa1e1585c1b4e2a5ec669c88fbb2417b9253f6427fbf671b181e8d8b21db3cbbe
@@ -1,7 +1,7 @@
1
1
  sudo: true
2
2
  language: ruby
3
3
  rvm:
4
- - 2.5.0
4
+ - 2.5.1
5
5
  - 2.4.3
6
6
  - 2.3.6
7
7
  - 2.2.9
@@ -9,6 +9,7 @@ gemfile: Gemfile.ci
9
9
  env:
10
10
  global:
11
11
  - CC_TEST_REPORTER_ID=1f562305f75e169f5f5eca3b738fee879550c98e50099c2b9cd6ae71478007a0
12
+ - DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL=true
12
13
  - secure: wTIBP3ZtltfLyIFS6p4XgzPff70EZLxIOpzL4tWCsB2XmEMtCBPdfm9CXLw88yxH4mCJbCVljJHRzrav9N5RimVGb3h5AX5ODpqJYO4PWRZzNYGglXM3f/3QWFXExEdJ97y0m2XytOzlVWtYjShqpCq1V9z88XwmJ2YQtiFMma4n2fjW8+WK30eGwNw60GAf7a2IAA2qMSIymEdM0OcRRVjmyWRtdAslnjANQ/m9lRYeNTfwfG5Xw26BRzv46Urmlw9MZsFGV5bG5bAKRFbp3h00UsPjKlAUL22bqEQ5XmRCuXCMzffoPhfJHDao+3iaVZlViwbCmoHRuuwHnTmUBFvvef/MRSUdLLwBsKBeDVofpGhLEInPX+D/kicNEnidYTtz+O8XZ0Hgur+VN+ayItIVZqFyaw96c9n+2H/KR7TG2lsccdFHNgATTX/fVOCc2AuBI84z3qM1LFYpJibwoTktQS1dnlmX6hwwEg+lJFhY0Spr3+aKOHf/zgqK+Q0jrEDQMo0khDTuPaOy2kznzK6ktrZVizw1tZyH80Eop/Yxpx1bIab8CF49pWlMZAkwmTP3e24qewUtgExyxUK1yBZ23L7QDnYLtfwCFfUbiEScjghJ9Aez1yerMCxNUrSOltyk1XxZw9xMrptA8VgN/0sFFmfICTXTZSqJdIGkfGY=
13
14
  - secure: z4AUz0bssd0C7rfPsh71aH6ki8Zp49WZrYNRhj50MytJympFsEOYRVdVKuuAoq7jaD0cahIioDHrTf1J9bIOqMPc3l2lk9RFWA27Qk46C5zNxspnNBin0disEeO/AENlb5Zwe/Cqamg7Sc/NVpELPqUl8gHfX3FVv4Po3dZZWQb1d/pLXBpl84HGLWgFwz6ku+74XW6aqX1Qzd8G0s9gsqsAgUszwEX3ppltBrai1VdtZyFbv0XCOds3F687Ecyht3FJBQCOaTopPQztdmA8rBTPW+ffhfoZuQWfo8aILqmn9xCGr4vhMjwaOP7cVQdXcPs1ecsFBKFS0XRm5kJKBtBPLDfEc4zl6UIIe5xZBh5m0dysxu78ZoNFVui3QszyUP+dARf3AHsy1m3d8iRXMNEqKTTglKdPIdFKZLnWGoVSf+fidR7NIyJH7MGhr4VC8esGFGazzOXBnqVAX+z1tEse6n7llFcy0dREW0FNMpKMSP6TqN4LRiKXtRdSbeSvHXP3UiH6vPjAE/lHhf7qXSNOLkZDtCWrD0us4kT1ACeL2AV2SArJf7pmGdEL9pUyp4jSuStwxtRPhYJj2kuS6op9MYoU6Q8eyXhIw8Uju4SitGtkTnb8t5L50Xw7vUQT9faEah9ewJwB2kxa2RZxCdIzylwuYssCON4xkdXI1fY=
14
15
  - secure: lUfjVwbIxUBQTGkDTOSMNvSYONBwI5yObn8fCByzGkfleddWPjLEeMPjGBZboOu2CVZ/q/FG3xbn63Hmss7CU83FmWRoV5dMP9Yc7X7cHaGmi31mZcaY2Yah+jgx7PcNIdK8LOAB3/zhxDNOcr8U4O9gqxGZWnV2NLR8SB+KiNTg5Hou3nOeVw7l1Iz+h1hXvZfxkMv7aX/3TLMT8j2JMkfgCN8moMlNRAYnvm/B68FPcVs6UFcaZIwg2mBnq3FrARo127jG1Ozms2SOPTqkPY9y8yoGyo3TYH2iQOKdRxCCECBnkSO8HYRYTL9CUoMk/D4ZTp7TGNRIKxjHtlt+9O1WtfmZB2aevZX5c7ChCZ0wNhQpzlsyPDz0GoIOkms5Bfv7nsvbloejr+vsRxe8YsNaVAVG+9RVZziKxGCWsxTopfnFMfFBK7KIoJ4N60ijhtBr3qcYQbSeoZWviwSd/GCjl9I2d2mTPVI0DsiFVt8lP92ZZitPJEhPRcxHdHu47Wu7oftndcVqzhhgdGnOxMchYKgzccR17VWlactGxffJP+ZIUkHaWCkxVR4etcrE753uaUX9x+M1fMoKKrwvycstX/YWLncgsYM1YensRUN7oW87wk216anlKfHubO2aVeDNnXn+wszK87M12o9gFu1khTBowqZpXsVDaUaa+oc=
data/Dockerfile CHANGED
@@ -18,13 +18,12 @@ RUN apk add --update \
18
18
  postgresql-dev \
19
19
  && rm -rf /var/cache/apk/* \
20
20
  && mkdir /root/.ssh \
21
- && mkdir /usr/src/app/tmp \
22
- && mkdir /usr/src/app/logs \
23
21
  && mkdir /usr/src/app/config \
24
22
  && touch /var/log/cron.log \
25
23
  && ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts \
26
24
  && gem install bundler
27
25
 
26
+ ADD views /usr/src/app/views
28
27
  COPY config.ru /usr/src/app/
29
28
  COPY config/logger.yml /usr/src/app/config/
30
29
  COPY config/puma.rb /usr/src/app/config/
@@ -44,7 +44,7 @@ GEM
44
44
  tilt
45
45
  hashie (3.5.7)
46
46
  highline (1.7.10)
47
- i18n (0.9.3)
47
+ i18n (0.9.4)
48
48
  concurrent-ruby (~> 1.0)
49
49
  logger (1.2.8)
50
50
  minitest (5.11.3)
@@ -62,7 +62,7 @@ GEM
62
62
  bcrypt-ruby (~> 3.0)
63
63
  omniauth (~> 1.0)
64
64
  pg (1.0.0)
65
- proxes (0.9.1)
65
+ proxes (0.9.2)
66
66
  activesupport (>= 3)
67
67
  bcrypt (~> 3.1)
68
68
  ditty (>= 0.2)
@@ -3,9 +3,9 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in proxes.gemspec
4
4
  gemspec
5
5
 
6
+ gem 'dotenv'
7
+ gem 'pry-byebug'
8
+ gem 'puma'
6
9
  gem 'rerun', git: 'https://github.com/alexch/rerun.git', branch: 'master'
7
- gem 'sqlite3'
8
10
  gem 'simplecov'
9
- gem 'pry-byebug'
10
- gem 'dotenv'
11
-
11
+ gem 'sqlite3'
data/config.ru CHANGED
@@ -5,6 +5,8 @@ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
5
5
 
6
6
  require 'dotenv/load'
7
7
 
8
+ require 'ditty/services/logger'
9
+ use Rack::CommonLogger, Ditty::Services::Logger.instance
8
10
  # Session
9
11
  use Rack::Session::Cookie,
10
12
  key: '_ProxES_session',
@@ -53,11 +55,15 @@ end
53
55
 
54
56
  map '/' do
55
57
  # Proxy all Elasticsearch requests
56
- require 'proxes/security'
58
+ require 'proxes/middleware/metrics'
59
+ require 'proxes/middleware/error_handling'
60
+ require 'proxes/middleware/security'
57
61
  require 'proxes/forwarder'
58
62
 
59
63
  # Security
60
- use ProxES::Security, Ditty::Services::Logger.instance
64
+ use ProxES::Middleware::Metrics
65
+ use ProxES::Middleware::ErrorHandling
66
+ use ProxES::Middleware::Security, Ditty::Services::Logger.instance unless ENV['PROXES_PASSTHROUGH']
61
67
  use Rack::ContentLength
62
68
 
63
69
  # Forward requests to ES
@@ -1,3 +1,5 @@
1
1
  ---
2
2
  - name: default
3
3
  class: Logger
4
+ level: WARN
5
+ options: $stdout
@@ -1,7 +1,11 @@
1
1
  version: '3'
2
2
  services:
3
+ db:
4
+ image: postgres
5
+ es:
6
+ image: elasticsearch
3
7
  web:
4
- build: .
8
+ image: eagerelk/proxes:latest
5
9
  command: web-proxes
6
10
  ports:
7
11
  - '9292:9292'
@@ -11,7 +15,3 @@ services:
11
15
  depends_on:
12
16
  - db
13
17
  - es
14
- db:
15
- image: postgres
16
- es:
17
- image: elasticsearch
@@ -16,7 +16,7 @@ module ProxES
16
16
  { name: :verb }
17
17
  ].freeze
18
18
 
19
- SEARCHABLE = %i[pattern]
19
+ SEARCHABLE = %i[pattern].freeze
20
20
 
21
21
  helpers do
22
22
  def user_options
@@ -28,7 +28,7 @@ module ProxES
28
28
  master_nodes = []
29
29
  data_nodes = []
30
30
  ingestion_nodes = []
31
- node_stats['nodes'].values.each do |node|
31
+ node_stats['nodes'].each_value do |node|
32
32
  if node['roles']
33
33
  master_nodes << node['name'] if node['roles'].include? 'master'
34
34
  data_nodes << node['name'] if node['roles'].include? 'data'
@@ -61,7 +61,7 @@ module ProxES
61
61
 
62
62
  jvm_values = []
63
63
  jvm_passed = true
64
- node_stats['nodes'].values.each do |node|
64
+ node_stats['nodes'].each_value do |node|
65
65
  jvm_values << "#{node['name']}: #{node['jvm']['mem']['heap_used_percent']}%"
66
66
  jvm_passed = false if node['jvm']['mem']['heap_used_percent'] > 85
67
67
  end
@@ -69,19 +69,19 @@ module ProxES
69
69
 
70
70
  fs_values = []
71
71
  fs_passed = true
72
- node_stats['nodes'].values.each do |node|
72
+ node_stats['nodes'].each_value do |node|
73
73
  next if node['attributes'] && node['attributes']['data'] == 'false'
74
74
  next if node['roles'] && node['roles'].include?('data') == false
75
75
  stats = node['fs']['total']
76
76
  left = stats['available_in_bytes'] / stats['total_in_bytes'].to_f * 100
77
- fs_values << "#{node['name']}: #{'%.02f' % left}% Free"
77
+ fs_values << "#{node['name']}: #{format('%.02f', left)}% Free"
78
78
  fs_passed = false if left < 10
79
79
  end
80
80
  checks << { text: 'Node File Systems', passed: fs_passed, value: fs_values.sort }
81
81
 
82
82
  cpu_values = []
83
83
  cpu_passed = true
84
- node_stats['nodes'].values.each do |node|
84
+ node_stats['nodes'].each_value do |node|
85
85
  value = (node['os']['cpu_percent'] || node['os']['cpu']['percent'])
86
86
  cpu_values << "#{node['name']}: #{value}"
87
87
  cpu_passed = false if value.to_i > 70
@@ -89,18 +89,27 @@ module ProxES
89
89
  checks << { text: 'Node CPU Usage', passed: cpu_passed, value: cpu_values.sort }
90
90
 
91
91
  memory_values = []
92
- memory_passed = true
93
- node_stats['nodes'].values.each do |node|
92
+ memory_sum = 0
93
+ node_stats['nodes'].each_value do |node|
94
+ memory_sum += node['os']['mem']['used_percent']
94
95
  memory_values << "#{node['name']}: #{node['os']['mem']['used_percent']}"
95
- memory_passed = false if node['os']['mem']['used_percent'].to_i >= 100
96
96
  end
97
+ memory_passed = (memory_sum / memory_values.size).to_i < 100
97
98
  checks << { text: 'Node Memory Usage', passed: memory_passed, value: memory_values.sort }
98
99
  rescue Faraday::Error => e
99
100
  checks << { text: 'Cluster Reachable', passed: false, value: e.message }
100
101
  end
101
102
 
102
103
  status checks.find { |c| c[:passed] == false } ? 500 : 200
103
- haml :'status/check', locals: { title: 'Status Check', checks: checks }
104
+
105
+ respond_to do |format|
106
+ format.html do
107
+ haml :'status/check', locals: { title: 'Status Check', checks: checks }
108
+ end
109
+ format.json do
110
+ json checks
111
+ end
112
+ end
104
113
  end
105
114
  end
106
115
  end
@@ -9,16 +9,6 @@ module ProxES
9
9
  include Singleton
10
10
  include ProxES::Services::ES
11
11
 
12
- attr_reader :streaming
13
-
14
- def backend
15
- @backend ||= URI(ENV['ELASTICSEARCH_URL'])
16
- end
17
-
18
- def backend=(var)
19
- @backend = URI(var)
20
- end
21
-
22
12
  def call(env)
23
13
  forward(env)
24
14
  rescue SocketError
@@ -28,7 +18,6 @@ module ProxES
28
18
 
29
19
  def forward(env)
30
20
  source = Rack::Request.new(env)
31
- conn.basic_auth backend.user, backend.password
32
21
  response = conn.send(source.request_method.downcase) do |req|
33
22
  source_body = body_from(source)
34
23
  req.body = source_body if source_body
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support'
4
+ require 'active_support/core_ext/object/blank'
5
+
3
6
  module ProxES
4
7
  module Helpers
5
8
  module Indices
6
9
  def filter(asked, against)
7
- return against.map { |a| a.gsub(/\.\*/, '*') } if asked == ['*'] || asked == [] || asked.nil?
10
+ return against.map { |a| a.gsub(/\.\*/, '*') } if asked == ['*'] || asked.blank?
8
11
 
9
12
  answer = []
10
13
  against.each do |pattern|
@@ -12,6 +15,19 @@ module ProxES
12
15
  end
13
16
  answer
14
17
  end
18
+
19
+ def patterns
20
+ return [] if user.nil?
21
+ patterns_for('INDEX').map do |permission|
22
+ return nil if permission.pattern.blank?
23
+ permission.pattern.gsub(/\{user.(.*)\}/) { |_match| user.send(Regexp.last_match[1].to_sym) }
24
+ end.compact
25
+ end
26
+
27
+ def patterns_for(action)
28
+ return [] if user.nil?
29
+ Permission.for_user(user, action)
30
+ end
15
31
  end
16
32
  end
17
33
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ditty/helpers/wisper'
4
+
5
+ module ProxES
6
+ module Middleware
7
+ class ErrorHandling
8
+ attr_reader :logger
9
+
10
+ include Wisper::Publisher
11
+ include Ditty::Helpers::Wisper
12
+
13
+ def initialize(app, logger = nil)
14
+ @app = app
15
+ @logger = logger || ::Ditty::Services::Logger.instance
16
+ end
17
+
18
+ def call(env)
19
+ request = Request.from_env(env)
20
+ code, headers, body = @app.call env
21
+ unless (200..299).cover? code
22
+ log_action(
23
+ :es_request_failed,
24
+ user: request.user,
25
+ details: "#{request.request_method.upcase} #{request.fullpath} (#{request.class.name})"
26
+ )
27
+ end
28
+ [code, headers, body]
29
+ rescue Errno::EHOSTUNREACH
30
+ error 'Could not reach Elasticsearch at ' + ENV['ELASTICSEARCH_URL']
31
+ rescue Errno::ECONNREFUSED, Faraday::ConnectionFailed
32
+ error 'Elasticsearch not listening at ' + ENV['ELASTICSEARCH_URL']
33
+ rescue Pundit::NotAuthorizedError, Ditty::Helpers::NotAuthenticated
34
+ if request.html? && request.user.nil?
35
+ env['rack.session']['omniauth.origin'] = request.url
36
+ return redirect '/_proxes/auth/identity'
37
+ end
38
+
39
+ user = request.user ? request.user.email : 'unauthenticated request'
40
+ logger.error "Access denied for #{user} by security layer: #{request.detail}"
41
+
42
+ failed request, 'Not Authorized', :es_request_denied, 401
43
+ rescue StandardError => e
44
+ raise e if env['RACK_ENV'] != 'production'
45
+
46
+ user = request.user ? request.user.email : 'unauthenticated request'
47
+ logger.error "Access denied for #{user} by security exception: #{request.detail}"
48
+ logger.error e
49
+
50
+ failed request, 'Forbidden', :es_request_denied, 403
51
+ end
52
+
53
+ def failed(request, message, action, code)
54
+ log_action(
55
+ action,
56
+ user: request.user,
57
+ details: "#{request.request_method.upcase} #{request.fullpath} (#{request.class.name})"
58
+ )
59
+ error message, code
60
+ end
61
+
62
+ # Response Helpers
63
+ def error(message, code = 500)
64
+ headers = { 'Content-Type' => 'application/json' }
65
+ headers['WWW-Authenticate'] = 'Basic realm="security"' if code == 401
66
+ [code, headers, ['{"error":"' + message + '"}']]
67
+ end
68
+
69
+ def redirect(destination, code = 302)
70
+ [code, { 'Location' => destination }, []]
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wisper'
4
+
5
+ module ProxES
6
+ module Middleware
7
+ class Metrics
8
+ include Wisper::Publisher
9
+
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ request = Request.from_env(env)
16
+ broadcast(:call_started, request)
17
+
18
+ result = @app.call request.env
19
+
20
+ broadcast(:call_completed, request) if result[0].to_i >= 200 && result[0].to_i < 300
21
+ result
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'proxes/request'
4
+ require 'proxes/policies/request_policy'
5
+ require 'ditty/services/logger'
6
+ require 'ditty/helpers/pundit'
7
+ require 'ditty/helpers/authentication'
8
+
9
+ module ProxES
10
+ module Middleware
11
+ class Security
12
+ attr_reader :env, :logger
13
+
14
+ include Ditty::Helpers::Authentication
15
+ include Ditty::Helpers::Pundit
16
+ include Ditty::Helpers::Wisper
17
+
18
+ def initialize(app, logger = nil)
19
+ @app = app
20
+ @logger = logger || ::Ditty::Services::Logger.instance
21
+ end
22
+
23
+ def call(env)
24
+ @env = env
25
+ request = ProxES::Request.from_env(env)
26
+ log(request, 'BEFORE')
27
+
28
+ check_basic request
29
+ authorize request
30
+
31
+ request.index = policy_scope(request) if request.indices?
32
+ log(request, 'AFTER')
33
+
34
+ @app.call env
35
+ end
36
+
37
+ def check_basic(request)
38
+ auth = Rack::Auth::Basic::Request.new(request.env)
39
+ return false unless auth.provided? && auth.basic?
40
+
41
+ identity = ::Ditty::Identity.find(username: auth.credentials[0])
42
+ identity ||= ::Ditty::Identity.find(username: CGI.unescape(auth.credentials[0]))
43
+ return false unless identity && identity.authenticate(auth.credentials[1])
44
+ request.env['rack.session'] ||= {}
45
+ request.env['rack.session']['user_id'] = identity.user_id
46
+ end
47
+
48
+ def authorize(request)
49
+ Pundit.authorize(request.user, request, request.request_method.downcase + '?')
50
+ end
51
+
52
+ def log(request, stage)
53
+ logger.debug '============' + stage.ljust(56) + '============'
54
+ logger.debug '= ' + "Request: #{request.detail}".ljust(76) + ' ='
55
+ logger.debug '= ' + "Endpoint: #{request.endpoint}".ljust(76) + ' ='
56
+ logger.debug '================================================================================'
57
+ end
58
+ end
59
+ end
60
+ end
@@ -29,3 +29,9 @@ module ProxES
29
29
  end
30
30
  end
31
31
  end
32
+
33
+ module Ditty
34
+ class User < ::Sequel::Model
35
+ one_to_many :permissions, class: ::ProxES::Permission
36
+ end
37
+ end