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.
Files changed (258) hide show
  1. checksums.yaml +7 -0
  2. data/Puppetfile +52 -0
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +60 -0
  4. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
  5. data/bolt-modules/boltlib/lib/puppet/datatypes/future.rb +25 -0
  6. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +71 -0
  7. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +55 -0
  8. data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +65 -0
  9. data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +93 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +33 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +38 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +208 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/background.rb +62 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +57 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +130 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +31 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +52 -0
  18. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +87 -0
  19. data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +34 -0
  20. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +35 -0
  21. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +74 -0
  22. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +97 -0
  23. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +47 -0
  24. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +52 -0
  25. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +40 -0
  26. data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +42 -0
  27. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
  28. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +106 -0
  29. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  30. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +291 -0
  31. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +145 -0
  32. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +164 -0
  33. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +211 -0
  34. data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +48 -0
  35. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +43 -0
  36. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +145 -0
  37. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +38 -0
  38. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +101 -0
  39. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +29 -0
  40. data/bolt-modules/boltlib/lib/puppet/functions/wait.rb +131 -0
  41. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +59 -0
  42. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +39 -0
  43. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +50 -0
  44. data/bolt-modules/boltlib/types/planresult.pp +18 -0
  45. data/bolt-modules/boltlib/types/targetspec.pp +7 -0
  46. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +42 -0
  47. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +20 -0
  48. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
  49. data/bolt-modules/file/lib/puppet/functions/file/delete.rb +21 -0
  50. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +28 -0
  51. data/bolt-modules/file/lib/puppet/functions/file/join.rb +20 -0
  52. data/bolt-modules/file/lib/puppet/functions/file/read.rb +33 -0
  53. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +28 -0
  54. data/bolt-modules/file/lib/puppet/functions/file/write.rb +24 -0
  55. data/bolt-modules/log/lib/puppet/functions/log/debug.rb +39 -0
  56. data/bolt-modules/log/lib/puppet/functions/log/error.rb +40 -0
  57. data/bolt-modules/log/lib/puppet/functions/log/fatal.rb +40 -0
  58. data/bolt-modules/log/lib/puppet/functions/log/info.rb +39 -0
  59. data/bolt-modules/log/lib/puppet/functions/log/trace.rb +39 -0
  60. data/bolt-modules/log/lib/puppet/functions/log/warn.rb +41 -0
  61. data/bolt-modules/out/lib/puppet/functions/out/message.rb +36 -0
  62. data/bolt-modules/out/lib/puppet/functions/out/verbose.rb +35 -0
  63. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  64. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +65 -0
  65. data/bolt-modules/system/lib/puppet/functions/system/env.rb +20 -0
  66. data/exe/bolt +17 -0
  67. data/guides/debugging.yaml +27 -0
  68. data/guides/inventory.yaml +23 -0
  69. data/guides/links.yaml +12 -0
  70. data/guides/logging.yaml +17 -0
  71. data/guides/module.yaml +18 -0
  72. data/guides/modulepath.yaml +24 -0
  73. data/guides/project.yaml +21 -0
  74. data/guides/targets.yaml +28 -0
  75. data/guides/transports.yaml +22 -0
  76. data/lib/bolt/analytics.rb +233 -0
  77. data/lib/bolt/application.rb +806 -0
  78. data/lib/bolt/applicator.rb +368 -0
  79. data/lib/bolt/apply_inventory.rb +93 -0
  80. data/lib/bolt/apply_result.rb +154 -0
  81. data/lib/bolt/apply_target.rb +90 -0
  82. data/lib/bolt/bolt_option_parser.rb +1226 -0
  83. data/lib/bolt/catalog/logging.rb +15 -0
  84. data/lib/bolt/catalog.rb +144 -0
  85. data/lib/bolt/cli.rb +949 -0
  86. data/lib/bolt/config/modulepath.rb +30 -0
  87. data/lib/bolt/config/options.rb +673 -0
  88. data/lib/bolt/config/transport/base.rb +133 -0
  89. data/lib/bolt/config/transport/docker.rb +34 -0
  90. data/lib/bolt/config/transport/jail.rb +33 -0
  91. data/lib/bolt/config/transport/local.rb +39 -0
  92. data/lib/bolt/config/transport/lxd.rb +34 -0
  93. data/lib/bolt/config/transport/options.rb +431 -0
  94. data/lib/bolt/config/transport/orch.rb +41 -0
  95. data/lib/bolt/config/transport/podman.rb +33 -0
  96. data/lib/bolt/config/transport/remote.rb +24 -0
  97. data/lib/bolt/config/transport/ssh.rb +138 -0
  98. data/lib/bolt/config/transport/winrm.rb +63 -0
  99. data/lib/bolt/config.rb +515 -0
  100. data/lib/bolt/container_result.rb +105 -0
  101. data/lib/bolt/error.rb +194 -0
  102. data/lib/bolt/executor.rb +539 -0
  103. data/lib/bolt/fiber_executor.rb +190 -0
  104. data/lib/bolt/inventory/group.rb +446 -0
  105. data/lib/bolt/inventory/inventory.rb +391 -0
  106. data/lib/bolt/inventory/options.rb +139 -0
  107. data/lib/bolt/inventory/target.rb +293 -0
  108. data/lib/bolt/inventory.rb +120 -0
  109. data/lib/bolt/logger.rb +252 -0
  110. data/lib/bolt/module.rb +54 -0
  111. data/lib/bolt/module_installer/installer.rb +44 -0
  112. data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
  113. data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
  114. data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
  115. data/lib/bolt/module_installer/puppetfile.rb +131 -0
  116. data/lib/bolt/module_installer/resolver.rb +129 -0
  117. data/lib/bolt/module_installer/specs/forge_spec.rb +91 -0
  118. data/lib/bolt/module_installer/specs/git_spec.rb +150 -0
  119. data/lib/bolt/module_installer/specs/id/base.rb +116 -0
  120. data/lib/bolt/module_installer/specs/id/gitclone.rb +120 -0
  121. data/lib/bolt/module_installer/specs/id/github.rb +90 -0
  122. data/lib/bolt/module_installer/specs/id/gitlab.rb +92 -0
  123. data/lib/bolt/module_installer/specs.rb +95 -0
  124. data/lib/bolt/module_installer.rb +208 -0
  125. data/lib/bolt/node/errors.rb +55 -0
  126. data/lib/bolt/node/output.rb +29 -0
  127. data/lib/bolt/outputter/human.rb +958 -0
  128. data/lib/bolt/outputter/json.rb +205 -0
  129. data/lib/bolt/outputter/logger.rb +76 -0
  130. data/lib/bolt/outputter/rainbow.rb +118 -0
  131. data/lib/bolt/outputter.rb +57 -0
  132. data/lib/bolt/pal/issues.rb +19 -0
  133. data/lib/bolt/pal/logging.rb +17 -0
  134. data/lib/bolt/pal/yaml_plan/evaluator.rb +83 -0
  135. data/lib/bolt/pal/yaml_plan/loader.rb +94 -0
  136. data/lib/bolt/pal/yaml_plan/parameter.rb +63 -0
  137. data/lib/bolt/pal/yaml_plan/step/command.rb +45 -0
  138. data/lib/bolt/pal/yaml_plan/step/download.rb +37 -0
  139. data/lib/bolt/pal/yaml_plan/step/eval.rb +42 -0
  140. data/lib/bolt/pal/yaml_plan/step/message.rb +31 -0
  141. data/lib/bolt/pal/yaml_plan/step/plan.rb +42 -0
  142. data/lib/bolt/pal/yaml_plan/step/resources.rb +170 -0
  143. data/lib/bolt/pal/yaml_plan/step/script.rb +62 -0
  144. data/lib/bolt/pal/yaml_plan/step/task.rb +42 -0
  145. data/lib/bolt/pal/yaml_plan/step/upload.rb +37 -0
  146. data/lib/bolt/pal/yaml_plan/step/verbose.rb +31 -0
  147. data/lib/bolt/pal/yaml_plan/step.rb +223 -0
  148. data/lib/bolt/pal/yaml_plan/transpiler.rb +90 -0
  149. data/lib/bolt/pal/yaml_plan.rb +172 -0
  150. data/lib/bolt/pal.rb +847 -0
  151. data/lib/bolt/plan_creator.rb +219 -0
  152. data/lib/bolt/plan_future.rb +86 -0
  153. data/lib/bolt/plan_result.rb +44 -0
  154. data/lib/bolt/plugin/cache.rb +76 -0
  155. data/lib/bolt/plugin/env_var.rb +54 -0
  156. data/lib/bolt/plugin/module.rb +276 -0
  157. data/lib/bolt/plugin/prompt.rb +36 -0
  158. data/lib/bolt/plugin/puppet_connect_data.rb +84 -0
  159. data/lib/bolt/plugin/puppetdb.rb +124 -0
  160. data/lib/bolt/plugin/task.rb +72 -0
  161. data/lib/bolt/plugin.rb +380 -0
  162. data/lib/bolt/project.rb +219 -0
  163. data/lib/bolt/project_manager/config_migrator.rb +113 -0
  164. data/lib/bolt/project_manager/inventory_migrator.rb +67 -0
  165. data/lib/bolt/project_manager/migrator.rb +39 -0
  166. data/lib/bolt/project_manager/module_migrator.rb +203 -0
  167. data/lib/bolt/project_manager.rb +221 -0
  168. data/lib/bolt/puppetdb/client.rb +153 -0
  169. data/lib/bolt/puppetdb/config.rb +176 -0
  170. data/lib/bolt/puppetdb/instance.rb +146 -0
  171. data/lib/bolt/puppetdb.rb +15 -0
  172. data/lib/bolt/r10k_log_proxy.rb +30 -0
  173. data/lib/bolt/rerun.rb +55 -0
  174. data/lib/bolt/resource_instance.rb +133 -0
  175. data/lib/bolt/result.rb +247 -0
  176. data/lib/bolt/result_set.rb +128 -0
  177. data/lib/bolt/shell/bash/tmpdir.rb +62 -0
  178. data/lib/bolt/shell/bash.rb +516 -0
  179. data/lib/bolt/shell/powershell/snippets.rb +181 -0
  180. data/lib/bolt/shell/powershell.rb +365 -0
  181. data/lib/bolt/shell.rb +105 -0
  182. data/lib/bolt/target.rb +174 -0
  183. data/lib/bolt/task/puppet_server.rb +27 -0
  184. data/lib/bolt/task/run.rb +55 -0
  185. data/lib/bolt/task.rb +163 -0
  186. data/lib/bolt/transport/base.rb +252 -0
  187. data/lib/bolt/transport/docker/connection.rb +150 -0
  188. data/lib/bolt/transport/docker.rb +23 -0
  189. data/lib/bolt/transport/jail/connection.rb +81 -0
  190. data/lib/bolt/transport/jail.rb +21 -0
  191. data/lib/bolt/transport/local/connection.rb +106 -0
  192. data/lib/bolt/transport/local.rb +20 -0
  193. data/lib/bolt/transport/lxd/connection.rb +115 -0
  194. data/lib/bolt/transport/lxd.rb +26 -0
  195. data/lib/bolt/transport/orch/connection.rb +111 -0
  196. data/lib/bolt/transport/orch.rb +271 -0
  197. data/lib/bolt/transport/podman/connection.rb +102 -0
  198. data/lib/bolt/transport/podman.rb +19 -0
  199. data/lib/bolt/transport/remote.rb +41 -0
  200. data/lib/bolt/transport/simple.rb +54 -0
  201. data/lib/bolt/transport/ssh/connection.rb +321 -0
  202. data/lib/bolt/transport/ssh/exec_connection.rb +140 -0
  203. data/lib/bolt/transport/ssh.rb +48 -0
  204. data/lib/bolt/transport/winrm/connection.rb +378 -0
  205. data/lib/bolt/transport/winrm.rb +33 -0
  206. data/lib/bolt/util/format.rb +68 -0
  207. data/lib/bolt/util/puppet_log_level.rb +21 -0
  208. data/lib/bolt/util.rb +465 -0
  209. data/lib/bolt/validator.rb +227 -0
  210. data/lib/bolt/version.rb +5 -0
  211. data/lib/bolt.rb +8 -0
  212. data/lib/bolt_server/acl.rb +39 -0
  213. data/lib/bolt_server/base_config.rb +112 -0
  214. data/lib/bolt_server/config.rb +64 -0
  215. data/lib/bolt_server/file_cache.rb +200 -0
  216. data/lib/bolt_server/request_error.rb +11 -0
  217. data/lib/bolt_server/schemas/action-check_node_connections.json +14 -0
  218. data/lib/bolt_server/schemas/action-run_command.json +12 -0
  219. data/lib/bolt_server/schemas/action-run_script.json +47 -0
  220. data/lib/bolt_server/schemas/action-run_task.json +20 -0
  221. data/lib/bolt_server/schemas/action-upload_file.json +47 -0
  222. data/lib/bolt_server/schemas/partials/target-any.json +10 -0
  223. data/lib/bolt_server/schemas/partials/target-ssh.json +88 -0
  224. data/lib/bolt_server/schemas/partials/target-winrm.json +67 -0
  225. data/lib/bolt_server/schemas/partials/task.json +94 -0
  226. data/lib/bolt_server/schemas/transport-ssh.json +25 -0
  227. data/lib/bolt_server/schemas/transport-winrm.json +19 -0
  228. data/lib/bolt_server/transport_app.rb +554 -0
  229. data/lib/bolt_spec/bolt_context.rb +226 -0
  230. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +51 -0
  231. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
  232. data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +55 -0
  233. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +59 -0
  234. data/lib/bolt_spec/plans/action_stubs/task_stub.rb +57 -0
  235. data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +65 -0
  236. data/lib/bolt_spec/plans/action_stubs.rb +196 -0
  237. data/lib/bolt_spec/plans/mock_executor.rb +361 -0
  238. data/lib/bolt_spec/plans/publish_stub.rb +49 -0
  239. data/lib/bolt_spec/plans.rb +190 -0
  240. data/lib/bolt_spec/run.rb +246 -0
  241. data/lib/logging_extensions/logging.rb +13 -0
  242. data/libexec/apply_catalog.rb +130 -0
  243. data/libexec/bolt_catalog +68 -0
  244. data/libexec/custom_facts.rb +63 -0
  245. data/libexec/query_resources.rb +75 -0
  246. data/modules/aggregate/lib/puppet/functions/aggregate/count.rb +21 -0
  247. data/modules/aggregate/lib/puppet/functions/aggregate/nodes.rb +22 -0
  248. data/modules/aggregate/lib/puppet/functions/aggregate/targets.rb +21 -0
  249. data/modules/aggregate/plans/count.pp +56 -0
  250. data/modules/aggregate/plans/targets.pp +56 -0
  251. data/modules/canary/lib/puppet/functions/canary/merge.rb +13 -0
  252. data/modules/canary/lib/puppet/functions/canary/random_split.rb +22 -0
  253. data/modules/canary/lib/puppet/functions/canary/skip.rb +25 -0
  254. data/modules/canary/plans/init.pp +100 -0
  255. data/modules/puppet_connect/plans/test_input_data.pp +94 -0
  256. data/modules/puppetdb_fact/plans/init.pp +20 -0
  257. data/resources/bolt_bash_completion.sh +214 -0
  258. 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ module BoltServer
6
+ class RequestError < Bolt::Error
7
+ def initialize(msg, details = {})
8
+ super(msg, 'bolt-server/request-error', details)
9
+ end
10
+ end
11
+ 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
+ }