bolt 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hocon'
4
+ require 'bolt/error'
5
+
6
+ module BoltServer
7
+ class Config
8
+ CONFIG_KEYS = ['host', 'port', 'ssl-cert', 'ssl-key', 'ssl-ca-cert',
9
+ 'ssl-cipher-suites', 'loglevel', 'logfile', 'whitelist', 'concurrency',
10
+ 'cache-dir', 'file-server-conn-timeout', 'file-server-uri'].freeze
11
+
12
+ DEFAULTS = {
13
+ 'host' => '127.0.0.1',
14
+ 'port' => 62658,
15
+ 'ssl-cipher-suites' => ['ECDHE-ECDSA-AES256-GCM-SHA384',
16
+ 'ECDHE-RSA-AES256-GCM-SHA384',
17
+ 'ECDHE-ECDSA-CHACHA20-POLY1305',
18
+ 'ECDHE-RSA-CHACHA20-POLY1305',
19
+ 'ECDHE-ECDSA-AES128-GCM-SHA256',
20
+ 'ECDHE-RSA-AES128-GCM-SHA256',
21
+ 'ECDHE-ECDSA-AES256-SHA384',
22
+ 'ECDHE-RSA-AES256-SHA384',
23
+ 'ECDHE-ECDSA-AES128-SHA256',
24
+ 'ECDHE-RSA-AES128-SHA256'],
25
+ 'loglevel' => 'notice',
26
+ 'concurrency' => 100,
27
+ 'cache-dir' => "/opt/puppetlabs/server/data/bolt-server/cache",
28
+ 'file-server-conn-timeout' => 120
29
+ }.freeze
30
+
31
+ CONFIG_KEYS.each do |key|
32
+ define_method(key.tr('-', '_').to_sym) do
33
+ @data[key]
34
+ end
35
+ end
36
+
37
+ def initialize(config = nil)
38
+ @data = DEFAULTS.clone
39
+ @data = @data.merge(config.select { |key, _| CONFIG_KEYS.include?(key) }) if config
40
+ @config_path = nil
41
+ end
42
+
43
+ def load_config(path)
44
+ @config_path = path
45
+ begin
46
+ parsed_hocon = Hocon.load(path)['bolt-server']
47
+ rescue Hocon::ConfigError => e
48
+ raise "Hocon data in '#{path}' failed to load.\n Error: '#{e.message}'"
49
+ rescue Errno::EACCES
50
+ raise "Your user doesn't have permission to read #{path}"
51
+ end
52
+
53
+ raise "Could not find bolt-server config at #{path}" if parsed_hocon.nil?
54
+
55
+ parsed_hocon = parsed_hocon.select { |key, _| CONFIG_KEYS.include?(key) }
56
+ @data = @data.merge(parsed_hocon)
57
+
58
+ validate
59
+ self
60
+ end
61
+
62
+ def natural?(num)
63
+ num.is_a?(Integer) && num.positive?
64
+ end
65
+
66
+ def validate
67
+ ssl_keys = ['ssl-cert', 'ssl-key', 'ssl-ca-cert']
68
+ required_keys = ssl_keys + ['file-server-uri']
69
+
70
+ required_keys.each do |k|
71
+ next unless @data[k].nil?
72
+ raise Bolt::ValidationError, "You must configure #{k} in #{@config_path}"
73
+ end
74
+
75
+ unless natural?(port)
76
+ raise Bolt::ValidationError, "Configured 'port' must be a valid integer greater than 0"
77
+ end
78
+ ssl_keys.each do |sk|
79
+ unless File.file?(@data[sk]) && File.readable?(@data[sk])
80
+ raise Bolt::ValidationError, "Configured #{sk} must be a valid filepath"
81
+ end
82
+ end
83
+
84
+ unless ssl_cipher_suites.is_a?(Array)
85
+ raise Bolt::ValidationError, "Configured 'ssl-cipher-suites' must be an array of cipher suite names"
86
+ end
87
+
88
+ unless whitelist.nil? || whitelist.is_a?(Array)
89
+ raise Bolt::ValidationError, "Configured 'whitelist' must be an array of names"
90
+ end
91
+
92
+ unless natural?(concurrency)
93
+ raise Bolt::ValidationError, "Configured 'concurrency' must be a positive integer"
94
+ end
95
+
96
+ unless natural?(file_server_conn_timeout)
97
+ raise Bolt::ValidationError, "Configured 'file-server-conn-timeout' must be a positive integer"
98
+ end
99
+ end
100
+
101
+ def [](key)
102
+ @data[key]
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+ require 'digest'
5
+ require 'fileutils'
6
+ require 'net/http'
7
+ require 'logging'
8
+
9
+ require 'bolt/error'
10
+
11
+ module BoltServer
12
+ class FileCache
13
+ class Error < Bolt::Error
14
+ def initialize(msg)
15
+ super(msg, 'bolt-server/file-cache-error')
16
+ end
17
+ end
18
+
19
+ PURGE_TIMEOUT = 60 * 60
20
+ PURGE_INTERVAL = 24 * PURGE_TIMEOUT
21
+ PURGE_TTL = 7 * PURGE_INTERVAL
22
+
23
+ def initialize(config,
24
+ executor: Concurrent::SingleThreadExecutor.new,
25
+ purge_interval: PURGE_INTERVAL,
26
+ purge_timeout: PURGE_TIMEOUT,
27
+ purge_ttl: PURGE_TTL)
28
+ @executor = executor
29
+ @cache_dir = config.cache_dir
30
+ @config = config
31
+ @logger = Logging.logger[self]
32
+ @cache_dir_mutex = Concurrent::ReadWriteLock.new
33
+
34
+ @purge = Concurrent::TimerTask.new(execution_interval: purge_interval,
35
+ timeout_interval: purge_timeout,
36
+ run_now: true) { expire(purge_ttl) }
37
+ @purge.execute
38
+ end
39
+
40
+ def tmppath
41
+ File.join(@cache_dir, 'tmp')
42
+ end
43
+
44
+ def setup
45
+ FileUtils.mkdir_p(@cache_dir)
46
+ FileUtils.mkdir_p(tmppath)
47
+ self
48
+ end
49
+
50
+ def ssl_cert
51
+ @ssl_cert ||= File.read(@config.ssl_cert)
52
+ end
53
+
54
+ def ssl_key
55
+ @ssl_key ||= File.read(@config.ssl_key)
56
+ end
57
+
58
+ def client
59
+ @client ||= begin
60
+ uri = URI(@config.file_server_uri)
61
+ https = Net::HTTP.new(uri.host, uri.port)
62
+ https.use_ssl = true
63
+ https.ssl_version = :TLSv1_2
64
+ https.ca_file = @config.ssl_ca_cert
65
+ https.cert = OpenSSL::X509::Certificate.new(ssl_cert)
66
+ https.key = OpenSSL::PKey::RSA.new(ssl_key)
67
+ https.verify_mode = OpenSSL::SSL::VERIFY_PEER
68
+ https.open_timeout = @config.file_server_conn_timeout
69
+ https
70
+ end
71
+ end
72
+
73
+ def request_file(path, params, file)
74
+ uri = "#{@config.file_server_uri.chomp('/')}#{path}"
75
+ uri = URI(uri)
76
+ uri.query = URI.encode_www_form(params)
77
+
78
+ req = Net::HTTP::Get.new(uri)
79
+
80
+ begin
81
+ client.request(req) do |resp|
82
+ if resp.code != "200"
83
+ msg = "Failed to download task: #{resp.body}"
84
+ @logger.warn resp.body
85
+ raise Error, msg
86
+ end
87
+ resp.read_body do |chunk|
88
+ file.write(chunk)
89
+ end
90
+ end
91
+ rescue StandardError => e
92
+ if e.is_a?(Bolt::Error)
93
+ raise e
94
+ else
95
+ @logger.warn e
96
+ raise Error, "Failed to download task: #{e.message}"
97
+ end
98
+ end
99
+ ensure
100
+ file.close
101
+ end
102
+
103
+ def check_file(file_path, sha)
104
+ File.exist?(file_path) && Digest::SHA256.file(file_path) == sha
105
+ end
106
+
107
+ def serial_execute(&block)
108
+ promise = Concurrent::Promise.new(executor: @executor, &block).execute.wait
109
+ raise promise.reason if promise.state == :rejected
110
+ promise.value
111
+ end
112
+
113
+ # Create a cache dir if necessary and update it's last write time. Returns the dir.
114
+ # Acquires @cache_dir_mutex to ensure we don't try to purge the directory at the same time.
115
+ # Uses the directory mtime because it's simpler to ensure the directory exists and update
116
+ # mtime in a single place than with a file in a directory that may not exist.
117
+ def create_cache_dir(sha)
118
+ file_dir = File.join(@cache_dir, sha)
119
+ @cache_dir_mutex.with_read_lock do
120
+ # mkdir_p doesn't error if the file exists
121
+ FileUtils.mkdir_p(file_dir, mode: 0o750)
122
+ FileUtils.touch(file_dir)
123
+ end
124
+ file_dir
125
+ end
126
+
127
+ def download_file(file_path, sha, uri)
128
+ if check_file(file_path, sha)
129
+ @logger.debug("File was downloaded while queued: #{file_path}")
130
+ return file_path
131
+ end
132
+
133
+ @logger.debug("Downloading file: #{file_path}")
134
+
135
+ tmpfile = Tempfile.new(sha, tmppath)
136
+ request_file(uri['path'], uri['params'], tmpfile)
137
+
138
+ if Digest::SHA256.file(tmpfile.path) == sha
139
+ # mv doesn't error if the file exists
140
+ FileUtils.mv(tmpfile.path, file_path)
141
+ @logger.debug("Downloaded file: #{file_path}")
142
+ file_path
143
+ else
144
+ msg = "Downloaded file did not match checksum for: #{file_path}"
145
+ @logger.warn msg
146
+ raise Error, msg
147
+ end
148
+ end
149
+
150
+ # If the file doesn't exist or is invalid redownload it
151
+ # This downloads, validates and moves into place
152
+ def update_file(file_data)
153
+ sha = file_data['sha256']
154
+ file_dir = create_cache_dir(file_data['sha256'])
155
+ file_path = File.join(file_dir, File.basename(file_data['filename']))
156
+ if check_file(file_path, sha)
157
+ @logger.debug("Using prexisting task file: #{file_path}")
158
+ return file_path
159
+ end
160
+
161
+ @logger.debug("Queueing download for: #{file_path}")
162
+ serial_execute { download_file(file_path, sha, file_data['uri']) }
163
+ end
164
+
165
+ def expire(purge_ttl)
166
+ expired_time = Time.now - purge_ttl
167
+ @cache_dir_mutex.with_write_lock do
168
+ Dir.glob(File.join(@cache_dir, '*')).select { |f| File.directory?(f) }.each do |dir|
169
+ if (mtime = File.mtime(dir)) < expired_time
170
+ @logger.debug("Removing #{dir}, last used at #{mtime}")
171
+ FileUtils.remove_dir(dir)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -42,23 +42,33 @@
42
42
  }
43
43
  }
44
44
  },
45
- "file": {
46
- "type": "object",
47
- "description": "File name and content",
48
- "properties": {
49
- "filename": {
50
- "type": "string",
51
- "description": "Name of the task file"
52
- },
53
- "file_content": {
54
- "type": "string",
55
- "description": "Task's base64 encoded file content"
45
+ "files": {
46
+ "type": "array",
47
+ "description": "Description of task files",
48
+ "items": {
49
+ "type": "object",
50
+ "properties": {
51
+ "uri": {
52
+ "type": "object",
53
+ "description": "Where is the file"
54
+ },
55
+ "sha256": {
56
+ "type": "string",
57
+ "description": "checksum of file"
58
+ },
59
+ "filename": {
60
+ "type": "string",
61
+ "description": "Name of file"
62
+ },
63
+ "size": {
64
+ "type": "number",
65
+ "description": "Size of file"
66
+ }
56
67
  }
57
68
  },
58
- "required": ["filename", "file_content"],
59
- "additionalProperties": false
69
+ "required": ["filename", "uri", "sha256"]
60
70
  }
61
71
  },
62
- "required": ["name", "file"],
72
+ "required": ["name", "files"],
63
73
  "additionalProperties": false
64
74
  }
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra'
4
+ require 'bolt'
5
+ require 'bolt/target'
6
+ require 'bolt/task/puppet_server'
7
+ require 'bolt_server/file_cache'
8
+ require 'json'
9
+ require 'json-schema'
10
+
11
+ module BoltServer
12
+ class TransportApp < Sinatra::Base
13
+ # This disables Sinatra's error page generation
14
+ set :show_exceptions, false
15
+
16
+ def initialize(config)
17
+ @config = config
18
+ @schemas = {
19
+ "ssh-run_task" => JSON.parse(File.read(File.join(__dir__, 'schemas', 'ssh-run_task.json'))),
20
+ "winrm-run_task" => JSON.parse(File.read(File.join(__dir__, 'schemas', 'winrm-run_task.json')))
21
+ }
22
+ shared_schema = JSON::Schema.new(JSON.parse(File.read(File.join(__dir__, 'schemas', 'task.json'))),
23
+ Addressable::URI.parse("file:task"))
24
+ JSON::Validator.add_schema(shared_schema)
25
+
26
+ @executor = Bolt::Executor.new(0, load_config: false)
27
+
28
+ @file_cache = BoltServer::FileCache.new(@config).setup
29
+
30
+ super(nil)
31
+ end
32
+
33
+ get '/' do
34
+ 200
35
+ end
36
+
37
+ if ENV['RACK_ENV'] == 'dev'
38
+ get '/admin/gc' do
39
+ GC.start
40
+ 200
41
+ end
42
+ end
43
+
44
+ get '/admin/gc_stat' do
45
+ [200, GC.stat.to_json]
46
+ end
47
+
48
+ get '/500_error' do
49
+ raise 'Unexpected error'
50
+ end
51
+
52
+ post '/ssh/run_task' do
53
+ content_type :json
54
+
55
+ body = JSON.parse(request.body.read)
56
+ schema_error = JSON::Validator.fully_validate(@schemas["ssh-run_task"], body)
57
+ return [400, schema_error.join] if schema_error.any?
58
+
59
+ opts = body['target']
60
+ if opts['private-key-content']
61
+ opts['private-key'] = { 'key-data' => opts['private-key-content'] }
62
+ opts.delete('private-key-content')
63
+ end
64
+
65
+ target = [Bolt::Target.new(body['target']['hostname'], opts)]
66
+
67
+ task = Bolt::Task::PuppetServer.new(body['task'], @file_cache)
68
+
69
+ parameters = body['parameters'] || {}
70
+
71
+ # Since this will only be on one node we can just return the first result
72
+ results = @executor.run_task(target, task, parameters)
73
+ [200, results.first.to_json]
74
+ end
75
+
76
+ post '/winrm/run_task' do
77
+ content_type :json
78
+
79
+ body = JSON.parse(request.body.read)
80
+ schema_error = JSON::Validator.fully_validate(@schemas["winrm-run_task"], body)
81
+ return [400, schema_error.join] if schema_error.any?
82
+
83
+ opts = body['target'].merge('protocol' => 'winrm')
84
+
85
+ target = [Bolt::Target.new(body['target']['hostname'], opts)]
86
+
87
+ task = Bolt::Task::PuppetServer.new(body['task'], @file_cache)
88
+
89
+ parameters = body['parameters'] || {}
90
+
91
+ # Since this will only be on one node we can just return the first result
92
+ results = @executor.run_task(target, task, parameters)
93
+ [200, results.first.to_json]
94
+ end
95
+
96
+ error 404 do
97
+ [404, "Could not find route #{request.path}"]
98
+ end
99
+
100
+ error 500 do
101
+ e = env['sinatra.error']
102
+ [500, "500: Unknown error: #{e.message}"]
103
+ end
104
+ end
105
+ end
data/lib/bolt_spec/run.rb CHANGED
@@ -9,7 +9,7 @@ require 'bolt/puppetdb'
9
9
  require 'bolt/util'
10
10
 
11
11
  # This is intended to provide a relatively stable method of executing bolt in process from tests.
12
- # Currently it provides run_task, run_plan and run_command helpers.
12
+ # Currently it provides run_task, run_plan, run_script and run_command helpers.
13
13
  module BoltSpec
14
14
  module Run
15
15
  def run_task(task_name, targets, params = nil, config: nil, inventory: nil)
@@ -41,6 +41,14 @@ module BoltSpec
41
41
  Bolt::Util.walk_keys(result, &:to_s)
42
42
  end
43
43
 
44
+ def run_script(script, targets, arguments = nil, options = {}, config: nil, inventory: nil)
45
+ result = BoltRunner.with_runner(config, inventory) do |runner|
46
+ runner.run_script(script, targets, arguments, options)
47
+ end
48
+ result = result.to_a
49
+ Bolt::Util.walk_keys(result, &:to_s)
50
+ end
51
+
44
52
  class BoltRunner
45
53
  # Creates a temporary boltdir so no settings are picked up
46
54
  # WARNING: puppetdb config and orch config which do not use the boltdir may
@@ -93,6 +101,12 @@ module BoltSpec
93
101
  targets = inventory.get_targets(targets)
94
102
  executor.run_command(targets, command, params || {})
95
103
  end
104
+
105
+ def run_script(script, targets, arguments = nil, options = {})
106
+ executor = Bolt::Executor.new(config.concurrency, @analytics)
107
+ targets = inventory.get_targets(targets)
108
+ executor.run_script(targets, script, arguments, options)
109
+ end
96
110
  end
97
111
  end
98
112
  end
data/libexec/bolt_catalog CHANGED
@@ -34,7 +34,7 @@ require 'json'
34
34
  command = ARGV[0]
35
35
  if command == "parse"
36
36
  code = File.open(ARGV[1], &:read)
37
- puts JSON.pretty_generate(Bolt::Catalog.new.generate_ast(code))
37
+ puts JSON.pretty_generate(Bolt::Catalog.new.generate_ast(code, ARGV[1]))
38
38
  elsif command == "compile"
39
39
  request = if ARGV[1]
40
40
  File.open(ARGV[1]) { |fh| JSON.parse(fh.read) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-16 00:00:00.000000000 Z
11
+ date: 2018-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: CFPropertyList
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: concurrent-ruby
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -307,6 +321,7 @@ files:
307
321
  - lib/bolt/result_set.rb
308
322
  - lib/bolt/target.rb
309
323
  - lib/bolt/task.rb
324
+ - lib/bolt/task/puppet_server.rb
310
325
  - lib/bolt/transport/base.rb
311
326
  - lib/bolt/transport/local.rb
312
327
  - lib/bolt/transport/local/shell.rb
@@ -320,12 +335,13 @@ files:
320
335
  - lib/bolt/util/puppet_log_level.rb
321
336
  - lib/bolt/version.rb
322
337
  - lib/bolt_ext/puppetdb_inventory.rb
323
- - lib/bolt_ext/schemas/ssh-run_task.json
324
- - lib/bolt_ext/schemas/task.json
325
- - lib/bolt_ext/schemas/winrm-run_task.json
326
- - lib/bolt_ext/server.rb
327
- - lib/bolt_ext/server_acl.rb
328
- - lib/bolt_ext/server_config.rb
338
+ - lib/bolt_server/acl.rb
339
+ - lib/bolt_server/config.rb
340
+ - lib/bolt_server/file_cache.rb
341
+ - lib/bolt_server/schemas/ssh-run_task.json
342
+ - lib/bolt_server/schemas/task.json
343
+ - lib/bolt_server/schemas/winrm-run_task.json
344
+ - lib/bolt_server/transport_app.rb
329
345
  - lib/bolt_spec/plans.rb
330
346
  - lib/bolt_spec/plans/mock_executor.rb
331
347
  - lib/bolt_spec/run.rb
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sinatra'
4
- require 'bolt'
5
- require 'bolt/target'
6
- require 'bolt/task'
7
- require 'json'
8
- require 'json-schema'
9
-
10
- class TransportAPI < Sinatra::Base
11
- # This disables Sinatra's error page generation
12
- set :show_exceptions, false
13
-
14
- def initialize(app = nil)
15
- @schemas = {
16
- "ssh-run_task" => JSON.parse(File.read(File.join(__dir__, 'schemas', 'ssh-run_task.json'))),
17
- "winrm-run_task" => JSON.parse(File.read(File.join(__dir__, 'schemas', 'winrm-run_task.json')))
18
- }
19
- shared_schema = JSON::Schema.new(JSON.parse(File.read(File.join(__dir__, 'schemas', 'task.json'))),
20
- Addressable::URI.parse("file:task"))
21
- JSON::Validator.add_schema(shared_schema)
22
-
23
- @executor = Bolt::Executor.new(0, load_config: false)
24
-
25
- super(app)
26
- end
27
-
28
- get '/' do
29
- 200
30
- end
31
-
32
- if ENV['RACK_ENV'] == 'dev'
33
- get '/admin/gc' do
34
- GC.start
35
- 200
36
- end
37
- end
38
-
39
- get '/admin/gc_stat' do
40
- [200, GC.stat.to_json]
41
- end
42
-
43
- get '/500_error' do
44
- raise 'Unexpected error'
45
- end
46
-
47
- post '/ssh/run_task' do
48
- content_type :json
49
-
50
- body = JSON.parse(request.body.read)
51
- schema_error = JSON::Validator.fully_validate(@schemas["ssh-run_task"], body)
52
- return [400, schema_error.join] if schema_error.any?
53
-
54
- # CODEREVIEW: the schema is additionalProperties false do we need this?
55
- keys = %w[user password port connect-timeout run-as-command run-as
56
- tmpdir host-key-check known-hosts-content private-key-content sudo-password
57
- tty]
58
- opts = body['target'].select { |k, _| keys.include? k }
59
-
60
- if opts['private-key-content']
61
- opts['private-key'] = { 'key-data' => opts['private-key-content'] }
62
- opts.delete('private-key-content')
63
- end
64
-
65
- target = [Bolt::Target.new(body['target']['hostname'], opts)]
66
- task = Bolt::Task.new(body['task'])
67
- parameters = body['parameters'] || {}
68
-
69
- # Since this will only be on one node we can just return the first result
70
- results = @executor.run_task(target, task, parameters)
71
- [200, results.first.to_json]
72
- end
73
-
74
- post '/winrm/run_task' do
75
- content_type :json
76
-
77
- body = JSON.parse(request.body.read)
78
- schema_error = JSON::Validator.fully_validate(@schemas["winrm-run_task"], body)
79
- return [400, schema_error.join] if schema_error.any?
80
-
81
- keys = %w[user password port connect-timeout ssl ssl-verify tmpdir cacert extensions]
82
- opts = body['target'].select { |k, _| keys.include? k }
83
- opts['protocol'] = 'winrm'
84
- target = [Bolt::Target.new(body['target']['hostname'], opts)]
85
- task = Bolt::Task.new(body['task'])
86
- parameters = body['parameters'] || {}
87
-
88
- # Since this will only be on one node we can just return the first result
89
- results = @executor.run_task(target, task, parameters)
90
- [200, results.first.to_json]
91
- end
92
-
93
- error 404 do
94
- [404, "Could not find route #{request.path}"]
95
- end
96
-
97
- error 500 do
98
- e = env['sinatra.error']
99
- [500, "500: Unknown error: #{e.message}"]
100
- end
101
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails/auth/rack'
4
-
5
- class TransportACL < Rails::Auth::ErrorPage::Middleware
6
- class X509Matcher
7
- def initialize(options)
8
- @options = options.freeze
9
- end
10
-
11
- def match(env)
12
- certificate = Rails::Auth::X509::Certificate.new(env['puma.peercert'])
13
- # This can be extended fairly easily to search OpenSSL::X509::Certificate#extensions for subjectAltNames.
14
- @options.all? { |name, value| certificate[name] == value }
15
- end
16
- end
17
-
18
- def initialize(app, whitelist)
19
- acls = []
20
- whitelist.each do |entry|
21
- acls << {
22
- 'resources' => [
23
- {
24
- 'method' => 'ALL',
25
- 'path' => '/.*'
26
- }
27
- ],
28
- 'allow_x509_subject' => {
29
- 'cn' => entry
30
- }
31
- }
32
- end
33
- acl = Rails::Auth::ACL.new(acls, matchers: { allow_x509_subject: X509Matcher })
34
- mid = Rails::Auth::ACL::Middleware.new(app, acl: acl)
35
- super(mid, page_body: 'Access denied')
36
- end
37
- end