proxes 0.9.2 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
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