openbolt 5.0.0.rc1
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 +7 -0
- data/Puppetfile +52 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +60 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +71 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +55 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +65 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +93 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +33 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +38 -0
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +208 -0
- data/bolt-modules/boltlib/lib/puppet/functions/background.rb +62 -0
- data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +57 -0
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +130 -0
- data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +31 -0
- data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +52 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +87 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +34 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +35 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +74 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +97 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +47 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +52 -0
- data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +40 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +42 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +106 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +291 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +145 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +164 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +211 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +48 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +43 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +145 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +38 -0
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +101 -0
- data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +29 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +131 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +59 -0
- data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +39 -0
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +50 -0
- data/bolt-modules/boltlib/types/planresult.pp +18 -0
- data/bolt-modules/boltlib/types/targetspec.pp +7 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +42 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +20 -0
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
- data/bolt-modules/file/lib/puppet/functions/file/delete.rb +21 -0
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +28 -0
- data/bolt-modules/file/lib/puppet/functions/file/join.rb +20 -0
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +33 -0
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +28 -0
- data/bolt-modules/file/lib/puppet/functions/file/write.rb +24 -0
- data/bolt-modules/log/lib/puppet/functions/log/debug.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/error.rb +40 -0
- data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +40 -0
- data/bolt-modules/log/lib/puppet/functions/log/info.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/trace.rb +39 -0
- data/bolt-modules/log/lib/puppet/functions/log/warn.rb +41 -0
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +36 -0
- data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +35 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +65 -0
- data/bolt-modules/system/lib/puppet/functions/system/env.rb +20 -0
- data/exe/bolt +17 -0
- data/guides/debugging.yaml +27 -0
- data/guides/inventory.yaml +23 -0
- data/guides/links.yaml +12 -0
- data/guides/logging.yaml +17 -0
- data/guides/module.yaml +18 -0
- data/guides/modulepath.yaml +24 -0
- data/guides/project.yaml +21 -0
- data/guides/targets.yaml +28 -0
- data/guides/transports.yaml +22 -0
- data/lib/bolt/analytics.rb +233 -0
- data/lib/bolt/application.rb +806 -0
- data/lib/bolt/applicator.rb +368 -0
- data/lib/bolt/apply_inventory.rb +93 -0
- data/lib/bolt/apply_result.rb +154 -0
- data/lib/bolt/apply_target.rb +90 -0
- data/lib/bolt/bolt_option_parser.rb +1226 -0
- data/lib/bolt/catalog/logging.rb +15 -0
- data/lib/bolt/catalog.rb +144 -0
- data/lib/bolt/cli.rb +949 -0
- data/lib/bolt/config/modulepath.rb +30 -0
- data/lib/bolt/config/options.rb +673 -0
- data/lib/bolt/config/transport/base.rb +133 -0
- data/lib/bolt/config/transport/docker.rb +34 -0
- data/lib/bolt/config/transport/jail.rb +33 -0
- data/lib/bolt/config/transport/local.rb +39 -0
- data/lib/bolt/config/transport/lxd.rb +34 -0
- data/lib/bolt/config/transport/options.rb +431 -0
- data/lib/bolt/config/transport/orch.rb +41 -0
- data/lib/bolt/config/transport/podman.rb +33 -0
- data/lib/bolt/config/transport/remote.rb +24 -0
- data/lib/bolt/config/transport/ssh.rb +138 -0
- data/lib/bolt/config/transport/winrm.rb +63 -0
- data/lib/bolt/config.rb +515 -0
- data/lib/bolt/container_result.rb +105 -0
- data/lib/bolt/error.rb +194 -0
- data/lib/bolt/executor.rb +539 -0
- data/lib/bolt/fiber_executor.rb +190 -0
- data/lib/bolt/inventory/group.rb +446 -0
- data/lib/bolt/inventory/inventory.rb +391 -0
- data/lib/bolt/inventory/options.rb +139 -0
- data/lib/bolt/inventory/target.rb +293 -0
- data/lib/bolt/inventory.rb +120 -0
- data/lib/bolt/logger.rb +252 -0
- data/lib/bolt/module.rb +54 -0
- data/lib/bolt/module_installer/installer.rb +44 -0
- data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
- data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
- data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
- data/lib/bolt/module_installer/puppetfile.rb +131 -0
- data/lib/bolt/module_installer/resolver.rb +129 -0
- data/lib/bolt/module_installer/specs/forge_spec.rb +91 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +150 -0
- data/lib/bolt/module_installer/specs/id/base.rb +116 -0
- data/lib/bolt/module_installer/specs/id/gitclone.rb +120 -0
- data/lib/bolt/module_installer/specs/id/github.rb +90 -0
- data/lib/bolt/module_installer/specs/id/gitlab.rb +92 -0
- data/lib/bolt/module_installer/specs.rb +95 -0
- data/lib/bolt/module_installer.rb +208 -0
- data/lib/bolt/node/errors.rb +55 -0
- data/lib/bolt/node/output.rb +29 -0
- data/lib/bolt/outputter/human.rb +958 -0
- data/lib/bolt/outputter/json.rb +205 -0
- data/lib/bolt/outputter/logger.rb +76 -0
- data/lib/bolt/outputter/rainbow.rb +118 -0
- data/lib/bolt/outputter.rb +57 -0
- data/lib/bolt/pal/issues.rb +19 -0
- data/lib/bolt/pal/logging.rb +17 -0
- data/lib/bolt/pal/yaml_plan/evaluator.rb +83 -0
- data/lib/bolt/pal/yaml_plan/loader.rb +94 -0
- data/lib/bolt/pal/yaml_plan/parameter.rb +63 -0
- data/lib/bolt/pal/yaml_plan/step/command.rb +45 -0
- data/lib/bolt/pal/yaml_plan/step/download.rb +37 -0
- data/lib/bolt/pal/yaml_plan/step/eval.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/message.rb +31 -0
- data/lib/bolt/pal/yaml_plan/step/plan.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/resources.rb +170 -0
- data/lib/bolt/pal/yaml_plan/step/script.rb +62 -0
- data/lib/bolt/pal/yaml_plan/step/task.rb +42 -0
- data/lib/bolt/pal/yaml_plan/step/upload.rb +37 -0
- data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
- data/lib/bolt/pal/yaml_plan/step.rb +223 -0
- data/lib/bolt/pal/yaml_plan/transpiler.rb +90 -0
- data/lib/bolt/pal/yaml_plan.rb +172 -0
- data/lib/bolt/pal.rb +847 -0
- data/lib/bolt/plan_creator.rb +219 -0
- data/lib/bolt/plan_future.rb +86 -0
- data/lib/bolt/plan_result.rb +44 -0
- data/lib/bolt/plugin/cache.rb +76 -0
- data/lib/bolt/plugin/env_var.rb +54 -0
- data/lib/bolt/plugin/module.rb +276 -0
- data/lib/bolt/plugin/prompt.rb +36 -0
- data/lib/bolt/plugin/puppet_connect_data.rb +84 -0
- data/lib/bolt/plugin/puppetdb.rb +124 -0
- data/lib/bolt/plugin/task.rb +72 -0
- data/lib/bolt/plugin.rb +380 -0
- data/lib/bolt/project.rb +219 -0
- data/lib/bolt/project_manager/config_migrator.rb +113 -0
- data/lib/bolt/project_manager/inventory_migrator.rb +67 -0
- data/lib/bolt/project_manager/migrator.rb +39 -0
- data/lib/bolt/project_manager/module_migrator.rb +203 -0
- data/lib/bolt/project_manager.rb +221 -0
- data/lib/bolt/puppetdb/client.rb +153 -0
- data/lib/bolt/puppetdb/config.rb +176 -0
- data/lib/bolt/puppetdb/instance.rb +146 -0
- data/lib/bolt/puppetdb.rb +15 -0
- data/lib/bolt/r10k_log_proxy.rb +30 -0
- data/lib/bolt/rerun.rb +55 -0
- data/lib/bolt/resource_instance.rb +133 -0
- data/lib/bolt/result.rb +247 -0
- data/lib/bolt/result_set.rb +128 -0
- data/lib/bolt/shell/bash/tmpdir.rb +62 -0
- data/lib/bolt/shell/bash.rb +516 -0
- data/lib/bolt/shell/powershell/snippets.rb +181 -0
- data/lib/bolt/shell/powershell.rb +365 -0
- data/lib/bolt/shell.rb +105 -0
- data/lib/bolt/target.rb +174 -0
- data/lib/bolt/task/puppet_server.rb +27 -0
- data/lib/bolt/task/run.rb +55 -0
- data/lib/bolt/task.rb +163 -0
- data/lib/bolt/transport/base.rb +252 -0
- data/lib/bolt/transport/docker/connection.rb +150 -0
- data/lib/bolt/transport/docker.rb +23 -0
- data/lib/bolt/transport/jail/connection.rb +81 -0
- data/lib/bolt/transport/jail.rb +21 -0
- data/lib/bolt/transport/local/connection.rb +106 -0
- data/lib/bolt/transport/local.rb +20 -0
- data/lib/bolt/transport/lxd/connection.rb +115 -0
- data/lib/bolt/transport/lxd.rb +26 -0
- data/lib/bolt/transport/orch/connection.rb +111 -0
- data/lib/bolt/transport/orch.rb +271 -0
- data/lib/bolt/transport/podman/connection.rb +102 -0
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/remote.rb +41 -0
- data/lib/bolt/transport/simple.rb +54 -0
- data/lib/bolt/transport/ssh/connection.rb +321 -0
- data/lib/bolt/transport/ssh/exec_connection.rb +140 -0
- data/lib/bolt/transport/ssh.rb +48 -0
- data/lib/bolt/transport/winrm/connection.rb +378 -0
- data/lib/bolt/transport/winrm.rb +33 -0
- data/lib/bolt/util/format.rb +68 -0
- data/lib/bolt/util/puppet_log_level.rb +21 -0
- data/lib/bolt/util.rb +465 -0
- data/lib/bolt/validator.rb +227 -0
- data/lib/bolt/version.rb +5 -0
- data/lib/bolt.rb +8 -0
- data/lib/bolt_server/acl.rb +39 -0
- data/lib/bolt_server/base_config.rb +112 -0
- data/lib/bolt_server/config.rb +64 -0
- data/lib/bolt_server/file_cache.rb +200 -0
- data/lib/bolt_server/request_error.rb +11 -0
- data/lib/bolt_server/schemas/action-check_node_connections.json +14 -0
- data/lib/bolt_server/schemas/action-run_command.json +12 -0
- data/lib/bolt_server/schemas/action-run_script.json +47 -0
- data/lib/bolt_server/schemas/action-run_task.json +20 -0
- data/lib/bolt_server/schemas/action-upload_file.json +47 -0
- data/lib/bolt_server/schemas/partials/target-any.json +10 -0
- data/lib/bolt_server/schemas/partials/target-ssh.json +88 -0
- data/lib/bolt_server/schemas/partials/target-winrm.json +67 -0
- data/lib/bolt_server/schemas/partials/task.json +94 -0
- data/lib/bolt_server/schemas/transport-ssh.json +25 -0
- data/lib/bolt_server/schemas/transport-winrm.json +19 -0
- data/lib/bolt_server/transport_app.rb +554 -0
- data/lib/bolt_spec/bolt_context.rb +226 -0
- data/lib/bolt_spec/plans/action_stubs/command_stub.rb +51 -0
- data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
- data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +55 -0
- data/lib/bolt_spec/plans/action_stubs/script_stub.rb +59 -0
- data/lib/bolt_spec/plans/action_stubs/task_stub.rb +57 -0
- data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +65 -0
- data/lib/bolt_spec/plans/action_stubs.rb +196 -0
- data/lib/bolt_spec/plans/mock_executor.rb +361 -0
- data/lib/bolt_spec/plans/publish_stub.rb +49 -0
- data/lib/bolt_spec/plans.rb +190 -0
- data/lib/bolt_spec/run.rb +246 -0
- data/lib/logging_extensions/logging.rb +13 -0
- data/libexec/apply_catalog.rb +130 -0
- data/libexec/bolt_catalog +68 -0
- data/libexec/custom_facts.rb +63 -0
- data/libexec/query_resources.rb +75 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/count.rb +21 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/nodes.rb +22 -0
- data/modules/aggregate/lib/puppet/functions/aggregate/targets.rb +21 -0
- data/modules/aggregate/plans/count.pp +56 -0
- data/modules/aggregate/plans/targets.pp +56 -0
- data/modules/canary/lib/puppet/functions/canary/merge.rb +13 -0
- data/modules/canary/lib/puppet/functions/canary/random_split.rb +22 -0
- data/modules/canary/lib/puppet/functions/canary/skip.rb +25 -0
- data/modules/canary/plans/init.pp +100 -0
- data/modules/puppet_connect/plans/test_input_data.pp +94 -0
- data/modules/puppetdb_fact/plans/init.pp +20 -0
- data/resources/bolt_bash_completion.sh +214 -0
- metadata +735 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/auth/rack'
|
4
|
+
|
5
|
+
module BoltServer
|
6
|
+
class ACL < Rails::Auth::ErrorPage::Middleware
|
7
|
+
class X509Matcher
|
8
|
+
def initialize(options)
|
9
|
+
@options = options.freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def match(env)
|
13
|
+
certificate = Rails::Auth::X509::Certificate.new(env['puma.peercert'])
|
14
|
+
# This can be extended fairly easily to search OpenSSL::X509::Certificate#extensions for subjectAltNames.
|
15
|
+
@options.all? { |name, value| certificate[name] == value }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(app, allowlist)
|
20
|
+
acls = []
|
21
|
+
allowlist.each do |entry|
|
22
|
+
acls << {
|
23
|
+
'resources' => [
|
24
|
+
{
|
25
|
+
'method' => 'ALL',
|
26
|
+
'path' => '/.*'
|
27
|
+
}
|
28
|
+
],
|
29
|
+
'allow_x509_subject' => {
|
30
|
+
'cn' => entry
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
acl = Rails::Auth::ACL.new(acls, matchers: { allow_x509_subject: X509Matcher })
|
35
|
+
mid = Rails::Auth::ACL::Middleware.new(app, acl: acl)
|
36
|
+
super(mid, page_body: 'Access denied')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hocon'
|
4
|
+
require 'bolt/error'
|
5
|
+
|
6
|
+
module BoltServer
|
7
|
+
class BaseConfig
|
8
|
+
def config_keys
|
9
|
+
%w[host port ssl-cert ssl-key ssl-ca-cert
|
10
|
+
ssl-cipher-suites loglevel logfile allowlist
|
11
|
+
environments-codedir
|
12
|
+
environmentpath basemodulepath]
|
13
|
+
end
|
14
|
+
|
15
|
+
def env_keys
|
16
|
+
%w[ssl-cert ssl-key ssl-ca-cert loglevel]
|
17
|
+
end
|
18
|
+
|
19
|
+
def defaults
|
20
|
+
{ 'host' => '127.0.0.1',
|
21
|
+
'loglevel' => 'warn',
|
22
|
+
'ssl-cipher-suites' => %w[ECDHE-ECDSA-AES256-GCM-SHA384
|
23
|
+
ECDHE-RSA-AES256-GCM-SHA384
|
24
|
+
ECDHE-ECDSA-CHACHA20-POLY1305
|
25
|
+
ECDHE-RSA-CHACHA20-POLY1305
|
26
|
+
ECDHE-ECDSA-AES128-GCM-SHA256
|
27
|
+
ECDHE-RSA-AES128-GCM-SHA256
|
28
|
+
ECDHE-ECDSA-AES256-SHA384
|
29
|
+
ECDHE-RSA-AES256-SHA384
|
30
|
+
ECDHE-ECDSA-AES128-SHA256
|
31
|
+
ECDHE-RSA-AES128-SHA256] }
|
32
|
+
end
|
33
|
+
|
34
|
+
def ssl_keys
|
35
|
+
%w[ssl-cert ssl-key ssl-ca-cert]
|
36
|
+
end
|
37
|
+
|
38
|
+
def required_keys
|
39
|
+
ssl_keys
|
40
|
+
end
|
41
|
+
|
42
|
+
def service_name
|
43
|
+
raise "Method service_name must be defined in the service class"
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(config = nil)
|
47
|
+
@data = defaults
|
48
|
+
@data = @data.merge(config.select { |key, _| config_keys.include?(key) }) if config
|
49
|
+
@config_path = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def load_file_config(path)
|
53
|
+
@config_path = path
|
54
|
+
begin
|
55
|
+
# This lets us get the actual config values without needing to
|
56
|
+
# know the service name
|
57
|
+
parsed_hocon = Hocon.load(path)[service_name]
|
58
|
+
rescue Hocon::ConfigError => e
|
59
|
+
raise "Hocon data in '#{path}' failed to load.\n Error: '#{e.message}'"
|
60
|
+
rescue Errno::EACCES
|
61
|
+
raise "Your user doesn't have permission to read #{path}"
|
62
|
+
end
|
63
|
+
|
64
|
+
raise "Could not find service config at #{path}" if parsed_hocon.nil?
|
65
|
+
|
66
|
+
parsed_hocon = parsed_hocon.select { |key, _| config_keys.include?(key) }
|
67
|
+
|
68
|
+
@data = @data.merge(parsed_hocon)
|
69
|
+
end
|
70
|
+
|
71
|
+
def load_env_config
|
72
|
+
raise "load_env_config should be defined in the service class"
|
73
|
+
end
|
74
|
+
|
75
|
+
def natural?(num)
|
76
|
+
num.is_a?(Integer) && num.positive?
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate
|
80
|
+
required_keys.each do |k|
|
81
|
+
# Handled nested config
|
82
|
+
if k.is_a?(Array)
|
83
|
+
next unless @data.dig(*k).nil?
|
84
|
+
else
|
85
|
+
next unless @data[k].nil?
|
86
|
+
end
|
87
|
+
raise Bolt::ValidationError, "You must configure #{k} in #{@config_path}"
|
88
|
+
end
|
89
|
+
|
90
|
+
unless natural?(@data['port'])
|
91
|
+
raise Bolt::ValidationError, "Configured 'port' must be a valid integer greater than 0"
|
92
|
+
end
|
93
|
+
ssl_keys.each do |sk|
|
94
|
+
unless File.file?(@data[sk]) && File.readable?(@data[sk])
|
95
|
+
raise Bolt::ValidationError, "Configured #{sk} must be a valid filepath"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
unless @data['ssl-cipher-suites'].is_a?(Array)
|
100
|
+
raise Bolt::ValidationError, "Configured 'ssl-cipher-suites' must be an array of cipher suite names"
|
101
|
+
end
|
102
|
+
|
103
|
+
unless @data['allowlist'].nil? || @data['allowlist'].is_a?(Array)
|
104
|
+
raise Bolt::ValidationError, "Configured 'allowlist' must be an array of names"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def [](key)
|
109
|
+
@data[key]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hocon'
|
4
|
+
require 'bolt_server/base_config'
|
5
|
+
require 'bolt/error'
|
6
|
+
|
7
|
+
module BoltServer
|
8
|
+
class Config < BoltServer::BaseConfig
|
9
|
+
def config_keys
|
10
|
+
super + %w[concurrency cache-dir file-server-conn-timeout
|
11
|
+
file-server-uri environments-codedir
|
12
|
+
environmentpath basemodulepath builtin-content-dir]
|
13
|
+
end
|
14
|
+
|
15
|
+
def env_keys
|
16
|
+
super + %w[concurrency file-server-conn-timeout file-server-uri]
|
17
|
+
end
|
18
|
+
|
19
|
+
def int_keys
|
20
|
+
%w[concurrency file-server-conn-timeout]
|
21
|
+
end
|
22
|
+
|
23
|
+
def defaults
|
24
|
+
super.merge(
|
25
|
+
'port' => 62658,
|
26
|
+
'concurrency' => 100,
|
27
|
+
'cache-dir' => "/opt/puppetlabs/server/data/bolt-server/cache",
|
28
|
+
'file-server-conn-timeout' => 120
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def required_keys
|
33
|
+
super + %w[file-server-uri]
|
34
|
+
end
|
35
|
+
|
36
|
+
def service_name
|
37
|
+
'bolt-server'
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_env_config
|
41
|
+
env_keys.each do |key|
|
42
|
+
transformed_key = "BOLT_#{key.tr('-', '_').upcase}"
|
43
|
+
next unless ENV.key?(transformed_key)
|
44
|
+
@data[key] = if int_keys.include?(key)
|
45
|
+
ENV[transformed_key].to_i
|
46
|
+
else
|
47
|
+
ENV[transformed_key]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate
|
53
|
+
super
|
54
|
+
|
55
|
+
unless natural?(@data['concurrency'])
|
56
|
+
raise Bolt::ValidationError, "Configured 'concurrency' must be a positive integer"
|
57
|
+
end
|
58
|
+
|
59
|
+
unless natural?(@data['file-server-conn-timeout'])
|
60
|
+
raise Bolt::ValidationError, "Configured 'file-server-conn-timeout' must be a positive integer"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent/atomic/read_write_lock'
|
4
|
+
require 'concurrent/executor/single_thread_executor'
|
5
|
+
require 'concurrent/promise'
|
6
|
+
require 'concurrent/timer_task'
|
7
|
+
require 'digest'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'net/http'
|
10
|
+
require 'logging'
|
11
|
+
require 'timeout'
|
12
|
+
|
13
|
+
require 'bolt/error'
|
14
|
+
|
15
|
+
module BoltServer
|
16
|
+
class FileCache
|
17
|
+
class Error < Bolt::Error
|
18
|
+
def initialize(msg)
|
19
|
+
super(msg, 'bolt-server/file-cache-error')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
PURGE_TIMEOUT = 60 * 60
|
24
|
+
PURGE_INTERVAL = 24 * PURGE_TIMEOUT
|
25
|
+
PURGE_TTL = 7 * PURGE_INTERVAL
|
26
|
+
|
27
|
+
def initialize(config,
|
28
|
+
executor: Concurrent::SingleThreadExecutor.new,
|
29
|
+
purge_interval: PURGE_INTERVAL,
|
30
|
+
purge_timeout: PURGE_TIMEOUT,
|
31
|
+
purge_ttl: PURGE_TTL,
|
32
|
+
cache_dir_mutex: Concurrent::ReadWriteLock.new,
|
33
|
+
do_purge: true)
|
34
|
+
@executor = executor
|
35
|
+
@cache_dir = config['cache-dir']
|
36
|
+
@config = config
|
37
|
+
@logger = Bolt::Logger.logger(self)
|
38
|
+
@cache_dir_mutex = cache_dir_mutex
|
39
|
+
|
40
|
+
if do_purge
|
41
|
+
@purge = Concurrent::TimerTask.new(execution_interval: purge_interval,
|
42
|
+
run_now: true) { expire(purge_ttl, purge_timeout) }
|
43
|
+
@purge.execute
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def tmppath
|
48
|
+
File.join(@cache_dir, 'tmp')
|
49
|
+
end
|
50
|
+
|
51
|
+
def setup
|
52
|
+
FileUtils.mkdir_p(@cache_dir)
|
53
|
+
FileUtils.mkdir_p(tmppath)
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def ssl_cert
|
58
|
+
@ssl_cert ||= File.read(@config['ssl-cert'])
|
59
|
+
end
|
60
|
+
|
61
|
+
def ssl_key
|
62
|
+
@ssl_key ||= File.read(@config['ssl-key'])
|
63
|
+
end
|
64
|
+
|
65
|
+
def client
|
66
|
+
# rubocop:disable Naming/VariableNumber
|
67
|
+
@client ||= begin
|
68
|
+
uri = URI(@config['file-server-uri'])
|
69
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
70
|
+
https.use_ssl = true
|
71
|
+
https.ssl_version = :TLSv1_2
|
72
|
+
https.ca_file = @config['ssl-ca-cert']
|
73
|
+
https.cert = OpenSSL::X509::Certificate.new(ssl_cert)
|
74
|
+
https.key = OpenSSL::PKey::RSA.new(ssl_key)
|
75
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
76
|
+
https.open_timeout = @config['file-server-conn-timeout']
|
77
|
+
https
|
78
|
+
end
|
79
|
+
# rubocop:enable Naming/VariableNumber
|
80
|
+
end
|
81
|
+
|
82
|
+
def request_file(path, params, file)
|
83
|
+
uri = "#{@config['file-server-uri'].chomp('/')}#{path}"
|
84
|
+
uri = URI(uri)
|
85
|
+
uri.query = URI.encode_www_form(params)
|
86
|
+
|
87
|
+
req = Net::HTTP::Get.new(uri)
|
88
|
+
|
89
|
+
begin
|
90
|
+
client.request(req) do |resp|
|
91
|
+
if resp.code != "200"
|
92
|
+
msg = "Failed to download file: #{resp.body}"
|
93
|
+
@logger.warn resp.body
|
94
|
+
raise Error, msg
|
95
|
+
end
|
96
|
+
resp.read_body do |chunk|
|
97
|
+
file.write(chunk)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
rescue StandardError => e
|
101
|
+
if e.is_a?(Bolt::Error)
|
102
|
+
raise e
|
103
|
+
else
|
104
|
+
@logger.warn e
|
105
|
+
raise Error, "Failed to download file: #{e.message}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
ensure
|
109
|
+
file.close
|
110
|
+
end
|
111
|
+
|
112
|
+
def check_file(file_path, sha)
|
113
|
+
File.exist?(file_path) && Digest::SHA256.file(file_path) == sha
|
114
|
+
end
|
115
|
+
|
116
|
+
def serial_execute(&block)
|
117
|
+
promise = Concurrent::Promise.new(executor: @executor, &block).execute.wait
|
118
|
+
raise promise.reason if promise.rejected?
|
119
|
+
promise.value
|
120
|
+
end
|
121
|
+
|
122
|
+
# Create a cache dir if necessary and update it's last write time. Returns the dir.
|
123
|
+
# Acquires @cache_dir_mutex to ensure we don't try to purge the directory at the same time.
|
124
|
+
# Uses the directory mtime because it's simpler to ensure the directory exists and update
|
125
|
+
# mtime in a single place than with a file in a directory that may not exist.
|
126
|
+
def create_cache_dir(sha)
|
127
|
+
file_dir = File.join(@cache_dir, sha)
|
128
|
+
@cache_dir_mutex.with_read_lock do
|
129
|
+
# mkdir_p doesn't error if the file exists
|
130
|
+
FileUtils.mkdir_p(file_dir, mode: 0o750)
|
131
|
+
FileUtils.touch(file_dir)
|
132
|
+
end
|
133
|
+
file_dir
|
134
|
+
end
|
135
|
+
|
136
|
+
def download_file(file_path, sha, uri)
|
137
|
+
if check_file(file_path, sha)
|
138
|
+
@logger.debug("File was downloaded while queued: #{file_path}")
|
139
|
+
return file_path
|
140
|
+
end
|
141
|
+
|
142
|
+
@logger.debug("Downloading file: #{file_path}")
|
143
|
+
|
144
|
+
tmpfile = Tempfile.new(sha, tmppath)
|
145
|
+
request_file(uri['path'], uri['params'], tmpfile)
|
146
|
+
|
147
|
+
if Digest::SHA256.file(tmpfile.path) == sha
|
148
|
+
# mv doesn't error if the file exists
|
149
|
+
FileUtils.mv(tmpfile.path, file_path)
|
150
|
+
@logger.debug("Downloaded file: #{file_path}")
|
151
|
+
file_path
|
152
|
+
else
|
153
|
+
msg = "Downloaded file did not match checksum for: #{file_path}"
|
154
|
+
@logger.warn msg
|
155
|
+
raise Error, msg
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# If the file doesn't exist or is invalid redownload it
|
160
|
+
# This downloads, validates and moves into place
|
161
|
+
def update_file(file_data)
|
162
|
+
sha = file_data['sha256']
|
163
|
+
file_dir = create_cache_dir(file_data['sha256'])
|
164
|
+
file_path = File.join(file_dir, File.basename(file_data['filename']))
|
165
|
+
if check_file(file_path, sha)
|
166
|
+
@logger.debug("Using prexisting file: #{file_path}")
|
167
|
+
return file_path
|
168
|
+
end
|
169
|
+
|
170
|
+
@logger.debug("Queueing download for: #{file_path}")
|
171
|
+
serial_execute { download_file(file_path, sha, file_data['uri']) }
|
172
|
+
end
|
173
|
+
|
174
|
+
def expire(purge_ttl, purge_timeout)
|
175
|
+
expired_time = Time.now - purge_ttl
|
176
|
+
Timeout.timeout(purge_timeout) do
|
177
|
+
@cache_dir_mutex.with_write_lock do
|
178
|
+
Dir.glob(File.join(@cache_dir, '*')).select { |f| File.directory?(f) }.each do |dir|
|
179
|
+
if (mtime = File.mtime(dir)) < expired_time && dir != tmppath
|
180
|
+
@logger.debug("Removing #{dir}, last used at #{mtime}")
|
181
|
+
FileUtils.remove_dir(dir)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def get_cached_project_file(versioned_project, file_name)
|
189
|
+
file_dir = create_cache_dir(versioned_project)
|
190
|
+
file_path = File.join(file_dir, file_name)
|
191
|
+
serial_execute { File.read(file_path) if File.exist?(file_path) }
|
192
|
+
end
|
193
|
+
|
194
|
+
def cache_project_file(versioned_project, file_name, data)
|
195
|
+
file_dir = create_cache_dir(versioned_project)
|
196
|
+
file_path = File.join(file_dir, file_name)
|
197
|
+
serial_execute { File.open(file_path, 'w') { |f| f.write(data) } }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"title": "check_node_connections request",
|
4
|
+
"description": "POST <transport>/check_node_connections request schema",
|
5
|
+
"type": "object",
|
6
|
+
"properties": {
|
7
|
+
"targets": {
|
8
|
+
"type": "array",
|
9
|
+
"items": { "$ref": "partial:target-any" }
|
10
|
+
}
|
11
|
+
},
|
12
|
+
"required": ["targets"],
|
13
|
+
"additionalProperties": false
|
14
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"title": "run_command request",
|
4
|
+
"description": "POST <transport>/run_command request schema",
|
5
|
+
"type": "object",
|
6
|
+
"properties": {
|
7
|
+
"command": { "type": "string" },
|
8
|
+
"target": { "$ref": "partial:target-any" }
|
9
|
+
},
|
10
|
+
"required": ["command", "target"],
|
11
|
+
"additionalProperties": false
|
12
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"title": "run_script request",
|
4
|
+
"description": "POST <transport>/run_script request schema",
|
5
|
+
"type": "object",
|
6
|
+
"properties": {
|
7
|
+
"script": {
|
8
|
+
"type": "object",
|
9
|
+
"properties": {
|
10
|
+
"filename": {
|
11
|
+
"type": "string"
|
12
|
+
},
|
13
|
+
"uri": {
|
14
|
+
"type": "object",
|
15
|
+
"properties": {
|
16
|
+
"path": {
|
17
|
+
"type": "string"
|
18
|
+
},
|
19
|
+
"params": {
|
20
|
+
"type": "object"
|
21
|
+
}
|
22
|
+
},
|
23
|
+
"required": [
|
24
|
+
"path",
|
25
|
+
"params"
|
26
|
+
]
|
27
|
+
},
|
28
|
+
"sha256": {
|
29
|
+
"type": "string"
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"required": [
|
33
|
+
"filename",
|
34
|
+
"uri",
|
35
|
+
"sha256"
|
36
|
+
]
|
37
|
+
},
|
38
|
+
"arguments": {
|
39
|
+
"type": "array",
|
40
|
+
"items": {
|
41
|
+
"type": "string"
|
42
|
+
}
|
43
|
+
},
|
44
|
+
"target": { "$ref": "partial:target-any" }
|
45
|
+
},
|
46
|
+
"required": ["script", "target"]
|
47
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"title": "run_task request",
|
4
|
+
"description": "POST <transport>/run_task request schema",
|
5
|
+
"type": "object",
|
6
|
+
"properties": {
|
7
|
+
"task": { "$ref": "partial:task" },
|
8
|
+
"parameters": {
|
9
|
+
"type": "object",
|
10
|
+
"description": "JSON formatted parameters to be provided to task"
|
11
|
+
},
|
12
|
+
"target": { "$ref": "partial:target-any" },
|
13
|
+
"timeout": {
|
14
|
+
"type": "integer",
|
15
|
+
"description": "Number of seconds to wait before abandoning the task execution on the tartet."
|
16
|
+
}
|
17
|
+
},
|
18
|
+
"required": ["target", "task"],
|
19
|
+
"additionalProperties": false
|
20
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
3
|
+
"title": "upload_file request",
|
4
|
+
"description": "POST <transport>/upload_file request schema",
|
5
|
+
"type": "object",
|
6
|
+
"properties": {
|
7
|
+
"files": {
|
8
|
+
"type": "array",
|
9
|
+
"items": {
|
10
|
+
"type": "object",
|
11
|
+
"properties": {
|
12
|
+
"relative_path": {
|
13
|
+
"type": "string"
|
14
|
+
},
|
15
|
+
"uri": {
|
16
|
+
"type": "object",
|
17
|
+
"properties": {
|
18
|
+
"path": {
|
19
|
+
"type": "string"
|
20
|
+
},
|
21
|
+
"params": {
|
22
|
+
"type": "object"
|
23
|
+
}
|
24
|
+
},
|
25
|
+
"required": ["path", "params"]
|
26
|
+
},
|
27
|
+
"sha256": {
|
28
|
+
"type": "string"
|
29
|
+
},
|
30
|
+
"kind": {
|
31
|
+
"type": "string"
|
32
|
+
}
|
33
|
+
},
|
34
|
+
"required": ["relative_path", "uri", "sha256", "kind"]
|
35
|
+
}
|
36
|
+
},
|
37
|
+
"job_id": {
|
38
|
+
"type": "integer"
|
39
|
+
},
|
40
|
+
"destination": {
|
41
|
+
"type": "string"
|
42
|
+
},
|
43
|
+
"target": { "$ref": "partial:target-any" }
|
44
|
+
},
|
45
|
+
"required": ["files", "job_id", "destination", "target"],
|
46
|
+
"additionalProperties": false
|
47
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{
|
2
|
+
"id": "partial:target-any",
|
3
|
+
"$schema": "http://json-schema.org/draft-04/schema",
|
4
|
+
"title": "Target information about where to run a bolt action, either over SSH or WinRM",
|
5
|
+
"type": "object",
|
6
|
+
"anyOf": [
|
7
|
+
{ "$ref": "partial:target-ssh" },
|
8
|
+
{ "$ref": "partial:target-winrm" }
|
9
|
+
]
|
10
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
{
|
2
|
+
"id": "partial:target-ssh",
|
3
|
+
"$schema": "http://json-schema.org/draft-04/schema",
|
4
|
+
"title": "Target information about where to run a bolt action over SSH",
|
5
|
+
"type": "object",
|
6
|
+
"properties": {
|
7
|
+
"hostname": {
|
8
|
+
"type": "string",
|
9
|
+
"description": "Target identifier"
|
10
|
+
},
|
11
|
+
"user": {
|
12
|
+
"type": "string",
|
13
|
+
"description": "Login user"
|
14
|
+
},
|
15
|
+
"password": {
|
16
|
+
"type": "string",
|
17
|
+
"description": "Password for SSH transport authentication"
|
18
|
+
},
|
19
|
+
"private-key-content": {
|
20
|
+
"type": "string",
|
21
|
+
"description": "Contents of private key for SSH"
|
22
|
+
},
|
23
|
+
"port": {
|
24
|
+
"type": "integer",
|
25
|
+
"description": "Connection port"
|
26
|
+
},
|
27
|
+
"connect-timeout": {
|
28
|
+
"type": "integer",
|
29
|
+
"description": "How long Bolt should wait when establishing connections"
|
30
|
+
},
|
31
|
+
"disconnect-timeout": {
|
32
|
+
"type": "integer",
|
33
|
+
"description": "How long Bolt should wait before forcing a disconnect"
|
34
|
+
},
|
35
|
+
"run-as-command": {
|
36
|
+
"type": "array",
|
37
|
+
"description": "Command elevate permissions",
|
38
|
+
"items": {
|
39
|
+
"type": "string"
|
40
|
+
}
|
41
|
+
},
|
42
|
+
"run-as": {
|
43
|
+
"type": "string",
|
44
|
+
"description": "A different user to run commands as after login"
|
45
|
+
},
|
46
|
+
"tmpdir": {
|
47
|
+
"type": "string",
|
48
|
+
"description": "The directory to upload and execute temporary files on the target"
|
49
|
+
},
|
50
|
+
"tty": {
|
51
|
+
"type": "boolean",
|
52
|
+
"description": "Should bolt use pseudo tty to meet sudoer restrictions"
|
53
|
+
},
|
54
|
+
"host-key-check": {
|
55
|
+
"type": "boolean",
|
56
|
+
"description": "Whether to perform host key validation when connecting over SSH"
|
57
|
+
},
|
58
|
+
"sudo-password": {
|
59
|
+
"type": "string",
|
60
|
+
"description": "Password to use when changing users via run-as"
|
61
|
+
},
|
62
|
+
"interpreters": {
|
63
|
+
"type": "object",
|
64
|
+
"description": "Map of file extensions to remote executable"
|
65
|
+
},
|
66
|
+
"plugin_hooks": {
|
67
|
+
"type": "object",
|
68
|
+
"description": "Configuration for plugins to use"
|
69
|
+
}
|
70
|
+
},
|
71
|
+
"oneOf": [
|
72
|
+
{
|
73
|
+
"required": [
|
74
|
+
"password"
|
75
|
+
]
|
76
|
+
},
|
77
|
+
{
|
78
|
+
"required": [
|
79
|
+
"private-key-content"
|
80
|
+
]
|
81
|
+
}
|
82
|
+
],
|
83
|
+
"required": [
|
84
|
+
"hostname",
|
85
|
+
"user"
|
86
|
+
],
|
87
|
+
"additionalProperties": false
|
88
|
+
}
|