empeira 0.1.0.pre.alpha.1
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/LICENSE +661 -0
- data/README.md +150 -0
- data/bin/empeira +18 -0
- data/config/defaults.yaml +229 -0
- data/docs/addons.md +331 -0
- data/docs/architecture.md +277 -0
- data/docs/configuration.md +1204 -0
- data/docs/development.md +472 -0
- data/docs/hiera-and-eyaml.md +361 -0
- data/docs/installation.md +280 -0
- data/docs/networking.md +362 -0
- data/docs/nodes.md +642 -0
- data/docs/proxy.md +136 -0
- data/docs/troubleshooting.md +361 -0
- data/lib/empeira/addons/environment.rb +82 -0
- data/lib/empeira/addons/error.rb +14 -0
- data/lib/empeira/addons/installation.rb +119 -0
- data/lib/empeira/addons/manifest.rb +112 -0
- data/lib/empeira/addons/service.rb +50 -0
- data/lib/empeira/addons/storage.rb +94 -0
- data/lib/empeira/agent/acquisition.rb +120 -0
- data/lib/empeira/agent/artifact.rb +41 -0
- data/lib/empeira/agent/authentication.rb +122 -0
- data/lib/empeira/agent/cache.rb +122 -0
- data/lib/empeira/agent/cache_storage.rb +82 -0
- data/lib/empeira/agent/download.rb +84 -0
- data/lib/empeira/agent/native_commands.rb +84 -0
- data/lib/empeira/agent/release_sources.rb +64 -0
- data/lib/empeira/agent/target.rb +66 -0
- data/lib/empeira/agent/versions.rb +29 -0
- data/lib/empeira/application.rb +86 -0
- data/lib/empeira/build.json +5 -0
- data/lib/empeira/build_info.rb +46 -0
- data/lib/empeira/cli/addons.rb +45 -0
- data/lib/empeira/cli/base.rb +73 -0
- data/lib/empeira/cli/completion.rb +42 -0
- data/lib/empeira/cli/config.rb +22 -0
- data/lib/empeira/cli/main.rb +114 -0
- data/lib/empeira/cli/node.rb +87 -0
- data/lib/empeira/cli/progress.rb +169 -0
- data/lib/empeira/cli/proxy.rb +36 -0
- data/lib/empeira/cli/update.rb +23 -0
- data/lib/empeira/completion/addons.rb +93 -0
- data/lib/empeira/completion/bash.rb +62 -0
- data/lib/empeira/completion/catalog.rb +170 -0
- data/lib/empeira/configuration/additional_services.rb +74 -0
- data/lib/empeira/configuration/addons.rb +85 -0
- data/lib/empeira/configuration/agent_schema.rb +163 -0
- data/lib/empeira/configuration/agent_values.rb +54 -0
- data/lib/empeira/configuration/bootstrap_guests.rb +42 -0
- data/lib/empeira/configuration/command_mocks.rb +65 -0
- data/lib/empeira/configuration/display.rb +20 -0
- data/lib/empeira/configuration/dns_rewrites.rb +58 -0
- data/lib/empeira/configuration/document.rb +67 -0
- data/lib/empeira/configuration/eyaml.rb +46 -0
- data/lib/empeira/configuration/hiera_modulepath.rb +40 -0
- data/lib/empeira/configuration/hiera_mount_schema.rb +62 -0
- data/lib/empeira/configuration/hiera_mounts.rb +83 -0
- data/lib/empeira/configuration/image_schema.rb +68 -0
- data/lib/empeira/configuration/loader.rb +70 -0
- data/lib/empeira/configuration/merge.rb +48 -0
- data/lib/empeira/configuration/network_redirects.rb +73 -0
- data/lib/empeira/configuration/node_images.rb +64 -0
- data/lib/empeira/configuration/schema.rb +215 -0
- data/lib/empeira/configuration/server_mounts.rb +106 -0
- data/lib/empeira/configuration/service_configuration.rb +40 -0
- data/lib/empeira/configuration/ssh_preferences.rb +83 -0
- data/lib/empeira/configuration/user.rb +46 -0
- data/lib/empeira/configuration/vm_interfaces.rb +147 -0
- data/lib/empeira/control_plane/additional_configurations.rb +106 -0
- data/lib/empeira/control_plane/browser.rb +73 -0
- data/lib/empeira/control_plane/controller.rb +763 -0
- data/lib/empeira/control_plane/discovery.rb +69 -0
- data/lib/empeira/control_plane/environment.rb +101 -0
- data/lib/empeira/control_plane/files.rb +263 -0
- data/lib/empeira/control_plane/health.rb +104 -0
- data/lib/empeira/control_plane/plan.rb +356 -0
- data/lib/empeira/control_plane/storage.rb +48 -0
- data/lib/empeira/errors.rb +9 -0
- data/lib/empeira/execution/console.rb +45 -0
- data/lib/empeira/execution/diagnostics.rb +210 -0
- data/lib/empeira/execution/launch_error.rb +14 -0
- data/lib/empeira/execution/result.rb +11 -0
- data/lib/empeira/execution/runner.rb +235 -0
- data/lib/empeira/execution/terminal.rb +35 -0
- data/lib/empeira/hostname_pattern.rb +14 -0
- data/lib/empeira/images/configuration.rb +44 -0
- data/lib/empeira/images/identity.rb +30 -0
- data/lib/empeira/images/recipe.rb +22 -0
- data/lib/empeira/images/reference.rb +43 -0
- data/lib/empeira/images/source.rb +19 -0
- data/lib/empeira/immutable.rb +19 -0
- data/lib/empeira/infrastructure/cleanup.rb +328 -0
- data/lib/empeira/infrastructure/definition.rb +59 -0
- data/lib/empeira/infrastructure/owned_files.rb +63 -0
- data/lib/empeira/infrastructure/recovery_inventory.rb +53 -0
- data/lib/empeira/infrastructure/service.rb +237 -0
- data/lib/empeira/infrastructure/status.rb +73 -0
- data/lib/empeira/infrastructure/store.rb +274 -0
- data/lib/empeira/modules/events.rb +67 -0
- data/lib/empeira/modules/git_safety.rb +60 -0
- data/lib/empeira/modules/host_git.rb +91 -0
- data/lib/empeira/modules/image.rb +27 -0
- data/lib/empeira/modules/installer.rb +67 -0
- data/lib/empeira/modules/request.rb +68 -0
- data/lib/empeira/modules/state.rb +87 -0
- data/lib/empeira/modules/storage.rb +35 -0
- data/lib/empeira/network/bootstrap_proxy.rb +157 -0
- data/lib/empeira/network/definition.rb +61 -0
- data/lib/empeira/network/direct_egress.rb +133 -0
- data/lib/empeira/network/egress.rb +27 -0
- data/lib/empeira/network/gateway.rb +79 -0
- data/lib/empeira/network/host_policy.rb +53 -0
- data/lib/empeira/network/naming.rb +28 -0
- data/lib/empeira/network/peer/adapter_image.rb +32 -0
- data/lib/empeira/network/peer/allocation.rb +45 -0
- data/lib/empeira/network/peer/attachment.rb +27 -0
- data/lib/empeira/network/peer/backend.rb +108 -0
- data/lib/empeira/network/peer/channel.rb +133 -0
- data/lib/empeira/network/peer/channel_server.rb +71 -0
- data/lib/empeira/network/peer/docker_adapter.rb +165 -0
- data/lib/empeira/network/peer/helper_security.rb +40 -0
- data/lib/empeira/network/peer/layout.rb +55 -0
- data/lib/empeira/network/peer/linux_podman.rb +114 -0
- data/lib/empeira/network/peer/podman_machine.rb +112 -0
- data/lib/empeira/network/policy.rb +39 -0
- data/lib/empeira/network/proxy_bindings.rb +52 -0
- data/lib/empeira/network/proxy_policy.rb +64 -0
- data/lib/empeira/network/redirects.rb +89 -0
- data/lib/empeira/network/resource.rb +50 -0
- data/lib/empeira/node/agent_installer.rb +31 -0
- data/lib/empeira/node/agent_package.rb +125 -0
- data/lib/empeira/node/agent_repository.rb +160 -0
- data/lib/empeira/node/apt_configuration.rb +85 -0
- data/lib/empeira/node/bootstrap.rb +17 -0
- data/lib/empeira/node/certificates.rb +98 -0
- data/lib/empeira/node/command_mock.rb +38 -0
- data/lib/empeira/node/command_mocks.rb +114 -0
- data/lib/empeira/node/container.rb +134 -0
- data/lib/empeira/node/container_agent_bootstrap.rb +43 -0
- data/lib/empeira/node/container_bootstrap.rb +52 -0
- data/lib/empeira/node/container_guest.rb +42 -0
- data/lib/empeira/node/container_lifecycle.rb +145 -0
- data/lib/empeira/node/container_preparation.rb +144 -0
- data/lib/empeira/node/container_state.rb +129 -0
- data/lib/empeira/node/definition.rb +20 -0
- data/lib/empeira/node/diagnostic_command.rb +19 -0
- data/lib/empeira/node/dnf_agent_repository.rb +202 -0
- data/lib/empeira/node/external_fact.rb +21 -0
- data/lib/empeira/node/image.rb +38 -0
- data/lib/empeira/node/interactive_tools.rb +33 -0
- data/lib/empeira/node/interface.rb +18 -0
- data/lib/empeira/node/inventory.rb +143 -0
- data/lib/empeira/node/package_bootstrap.rb +129 -0
- data/lib/empeira/node/package_proxy.rb +102 -0
- data/lib/empeira/node/package_sources.rb +19 -0
- data/lib/empeira/node/puppet_command.rb +12 -0
- data/lib/empeira/node/rpm_configuration.rb +62 -0
- data/lib/empeira/node/run_request.rb +52 -0
- data/lib/empeira/node/runtime_proxy.rb +86 -0
- data/lib/empeira/node/service.rb +72 -0
- data/lib/empeira/node/ssh_client.rb +25 -0
- data/lib/empeira/node/ssh_credentials.rb +78 -0
- data/lib/empeira/node/ssh_endpoint.rb +58 -0
- data/lib/empeira/node/ssh_identity.rb +37 -0
- data/lib/empeira/node/user_ssh.rb +60 -0
- data/lib/empeira/node/vm.rb +89 -0
- data/lib/empeira/node/vm_bootstrap.rb +53 -0
- data/lib/empeira/node/vm_lifecycle.rb +212 -0
- data/lib/empeira/node/vm_preflight.rb +37 -0
- data/lib/empeira/node/vm_preparation.rb +161 -0
- data/lib/empeira/node/vm_state.rb +161 -0
- data/lib/empeira/platform/facts.rb +66 -0
- data/lib/empeira/platform/kvm.rb +91 -0
- data/lib/empeira/platform/locations.rb +51 -0
- data/lib/empeira/platform/resolvers.rb +85 -0
- data/lib/empeira/platform/routes.rb +50 -0
- data/lib/empeira/progress.rb +70 -0
- data/lib/empeira/project_root.rb +47 -0
- data/lib/empeira/providers/lifecycle.rb +96 -0
- data/lib/empeira/providers/registry.rb +21 -0
- data/lib/empeira/providers/resource.rb +32 -0
- data/lib/empeira/providers.rb +55 -0
- data/lib/empeira/runtime/agent_helper.rb +34 -0
- data/lib/empeira/runtime/container.rb +184 -0
- data/lib/empeira/runtime/diagnostic_execution.rb +52 -0
- data/lib/empeira/runtime/docker.rb +87 -0
- data/lib/empeira/runtime/image_metadata.rb +83 -0
- data/lib/empeira/runtime/interface.rb +8 -0
- data/lib/empeira/runtime/mount_probe.rb +57 -0
- data/lib/empeira/runtime/podman.rb +122 -0
- data/lib/empeira/runtime/recipe_build.rb +103 -0
- data/lib/empeira/runtime/registry_diagnostics.rb +23 -0
- data/lib/empeira/runtime/service_arguments.rb +55 -0
- data/lib/empeira/runtime/service_images.rb +118 -0
- data/lib/empeira/runtime/service_inspection.rb +159 -0
- data/lib/empeira/runtime/services.rb +120 -0
- data/lib/empeira/runtime/update_helper.rb +97 -0
- data/lib/empeira/runtime/workspace_network.rb +69 -0
- data/lib/empeira/server/environment_configuration.rb +53 -0
- data/lib/empeira/server/http.rb +38 -0
- data/lib/empeira/server/puppetdb_runtime.rb +74 -0
- data/lib/empeira/server/relay_certificate.rb +76 -0
- data/lib/empeira/server/runtime.rb +90 -0
- data/lib/empeira/services/definition.rb +42 -0
- data/lib/empeira/updates/images.rb +104 -0
- data/lib/empeira/updates/modules.rb +45 -0
- data/lib/empeira/updates/service.rb +45 -0
- data/lib/empeira/version_requirement.rb +23 -0
- data/lib/empeira/vm/agent.rb +66 -0
- data/lib/empeira/vm/bootstrap_requirements.rb +69 -0
- data/lib/empeira/vm/certificate_runtime.rb +30 -0
- data/lib/empeira/vm/cloud_init.rb +171 -0
- data/lib/empeira/vm/disk.rb +136 -0
- data/lib/empeira/vm/guest.rb +123 -0
- data/lib/empeira/vm/guest_channel.rb +110 -0
- data/lib/empeira/vm/guest_network.rb +94 -0
- data/lib/empeira/vm/guest_output.rb +52 -0
- data/lib/empeira/vm/guest_shell.rb +79 -0
- data/lib/empeira/vm/guest_upload.rb +43 -0
- data/lib/empeira/vm/image_cache.rb +77 -0
- data/lib/empeira/vm/image_source.rb +91 -0
- data/lib/empeira/vm/interface.rb +8 -0
- data/lib/empeira/vm/interface_ownership.rb +124 -0
- data/lib/empeira/vm/interface_routes.rb +101 -0
- data/lib/empeira/vm/interfaces.rb +240 -0
- data/lib/empeira/vm/management.rb +73 -0
- data/lib/empeira/vm/prerequisites.rb +42 -0
- data/lib/empeira/vm/qemu.rb +83 -0
- data/lib/empeira/vm/qemu_runtime.rb +404 -0
- data/lib/empeira/vm/removal.rb +30 -0
- data/lib/empeira/vm/root_disk.rb +92 -0
- data/lib/empeira/vm/session_lock.rb +56 -0
- data/lib/empeira/vm/system_ssh.rb +29 -0
- data/lib/empeira/workspace.rb +42 -0
- data/lib/empeira.rb +209 -0
- data/resources/browser/relay.rb +22 -0
- data/resources/gateway/Containerfile +8 -0
- data/resources/gateway/bridge.rb +66 -0
- data/resources/gateway/configure.rb +323 -0
- data/resources/modules/Containerfile +15 -0
- data/resources/modules/Gemfile +5 -0
- data/resources/modules/Gemfile.lock +105 -0
- data/resources/modules/worker.rb +161 -0
- data/resources/network/adapter/Containerfile +13 -0
- data/resources/network/adapter/bridge_linux.go +51 -0
- data/resources/network/adapter/framing.go +45 -0
- data/resources/network/adapter/framing_test.go +53 -0
- data/resources/network/adapter/go.mod +3 -0
- data/resources/network/adapter/main_linux.go +273 -0
- data/resources/network/channel.rb +18 -0
- data/resources/network/ssh_connect.rb +32 -0
- data/resources/nodes/apt_sources.rb +66 -0
- data/resources/nodes/debian/Containerfile +10 -0
- data/resources/nodes/diagnostic_exec.rb +109 -0
- data/resources/nodes/interactive_tools.rb +139 -0
- data/resources/nodes/managed_file.rb +91 -0
- data/resources/nodes/management/guest_agent.py +415 -0
- data/resources/nodes/management/setup.sh +20 -0
- data/resources/nodes/management/shell_rc.sh +12 -0
- data/resources/nodes/management/system_account.sh +24 -0
- data/resources/nodes/management/system_home.sh +15 -0
- data/resources/nodes/rpm/Containerfile +10 -0
- data/resources/nodes/runtime_proxy.rb +244 -0
- data/resources/nodes/ssh/proxy.rb +16 -0
- data/resources/nodes/ssh/sshd_config +15 -0
- data/resources/nodes/ssh/start +6 -0
- data/resources/proxy/Containerfile +10 -0
- data/resources/relay/Containerfile +5 -0
- data/resources/relay/puppetdb.rb +81 -0
- data/resources/relay/server_environment.rb +10 -0
- data/resources/relay/server_start.sh +41 -0
- metadata +326 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
require 'tempfile'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'strscan'
|
|
8
|
+
require 'shellwords'
|
|
9
|
+
|
|
10
|
+
module Empeira
|
|
11
|
+
# Executed with the installed agent Ruby in the owned guest, never on the host.
|
|
12
|
+
# rubocop:disable-next Metrics/ClassLength -- Standalone guest helper contains both native package-manager adapters.
|
|
13
|
+
class RuntimeProxyGuest
|
|
14
|
+
APT_PATH = '/etc/apt/apt.conf.d/90-empeira-proxy'
|
|
15
|
+
DNF_PATH = '/etc/dnf/dnf.conf'
|
|
16
|
+
PROFILE_PATH = '/etc/profile.d/90-empeira-proxy.sh'
|
|
17
|
+
BEGIN_MARKER = "# BEGIN EMPEIRA RUNTIME PROXY\n"
|
|
18
|
+
END_MARKER = "# END EMPEIRA RUNTIME PROXY\n"
|
|
19
|
+
|
|
20
|
+
def initialize(request, root: Pathname('/'))
|
|
21
|
+
@desired = request.fetch('desired')
|
|
22
|
+
@accepted = request.fetch('accepted')
|
|
23
|
+
@root = Pathname(root)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def reconcile
|
|
27
|
+
changed = reconcile_apt if path('/etc/apt').directory?
|
|
28
|
+
changed = reconcile_dnf || changed if path('/etc/dnf').directory?
|
|
29
|
+
reconcile_profile || !!changed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def profile_content(definition)
|
|
35
|
+
variables = %w[HTTP_PROXY HTTPS_PROXY http_proxy https_proxy].to_h { |key| [key, definition&.fetch('url') || ''] }
|
|
36
|
+
variables.merge!(%w[NO_PROXY no_proxy].to_h { |key| [key, definition&.fetch('no_proxy') || ''] })
|
|
37
|
+
variables.merge!('ALL_PROXY' => '', 'all_proxy' => '')
|
|
38
|
+
exports = variables.map do |key, value|
|
|
39
|
+
"export #{key}=#{Shellwords.escape(value)}\n"
|
|
40
|
+
end.join
|
|
41
|
+
"# Managed by Empeira: normal runtime proxy environment.\n#{exports}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# rubocop:disable-next Naming/PredicateMethod -- Clear stale container image environment when disabled.
|
|
45
|
+
def reconcile_profile
|
|
46
|
+
target = path(PROFILE_PATH)
|
|
47
|
+
current = read(target)
|
|
48
|
+
content = profile_content(@desired)
|
|
49
|
+
if current && ![@desired, *@accepted, nil].map { |entry| profile_content(entry) }.include?(current)
|
|
50
|
+
raise 'Foreign or modified /etc/profile.d/90-empeira-proxy.sh; refusing to replace it'
|
|
51
|
+
end
|
|
52
|
+
return false if content == current
|
|
53
|
+
|
|
54
|
+
write(target, content)
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def path(value)
|
|
59
|
+
@root.join(value.delete_prefix('/'))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def read(target)
|
|
63
|
+
target.parent.ascend do |parent|
|
|
64
|
+
raise "Unsafe proxy configuration parent: #{parent}" if parent.symlink?
|
|
65
|
+
end
|
|
66
|
+
stat = target.lstat
|
|
67
|
+
unless stat.file? && stat.nlink == 1 && stat.uid == Process.euid
|
|
68
|
+
raise "Unsafe or unowned proxy configuration: #{target}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
target.binread
|
|
72
|
+
rescue Errno::ENOENT
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def write(target, content, mode: 0o644)
|
|
77
|
+
FileUtils.mkdir_p(target.parent, mode: 0o755)
|
|
78
|
+
Tempfile.create('.empeira-proxy-', target.parent) do |file|
|
|
79
|
+
file.chmod(mode)
|
|
80
|
+
file.write(content)
|
|
81
|
+
file.flush
|
|
82
|
+
file.fsync
|
|
83
|
+
File.rename(file.path, target)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def apt_content(definition, foreign = {})
|
|
88
|
+
return unless definition
|
|
89
|
+
|
|
90
|
+
url = definition.fetch('url')
|
|
91
|
+
globals = "// Managed by Empeira: normal proxy, no bootstrap credentials.\n" \
|
|
92
|
+
"Acquire::http::Proxy \"#{url}\";\nAcquire::https::Proxy \"#{url}\";\n"
|
|
93
|
+
globals + direct_lines(definition).reject { |line| foreign.key?(line.split.first.downcase) }.join
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def direct_lines(definition)
|
|
97
|
+
definition.fetch('direct').flat_map do |host|
|
|
98
|
+
%w[http https].map { |protocol| "Acquire::#{protocol}::Proxy::#{host} \"DIRECT\";\n" }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def owned_apt?(current, definition)
|
|
103
|
+
globals = apt_content(definition.merge('direct' => []))
|
|
104
|
+
return false unless current.start_with?(globals)
|
|
105
|
+
|
|
106
|
+
lines = current.delete_prefix(globals).lines
|
|
107
|
+
lines.uniq == lines && (lines - direct_lines(definition)).empty?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# rubocop:disable-next Naming/PredicateMethod -- Reconciliation mutates the APT fragment.
|
|
111
|
+
def reconcile_apt
|
|
112
|
+
target = path(APT_PATH)
|
|
113
|
+
current = read(target)
|
|
114
|
+
verify_apt_file(current)
|
|
115
|
+
|
|
116
|
+
foreign = @desired ? foreign_apt_settings(target) : {}
|
|
117
|
+
verify_apt_globals(foreign) if @desired
|
|
118
|
+
content = apt_content(@desired, foreign)
|
|
119
|
+
return false if content == current
|
|
120
|
+
|
|
121
|
+
content ? write(target, content) : target.unlink
|
|
122
|
+
true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def verify_apt_file(current)
|
|
126
|
+
return unless current
|
|
127
|
+
return if @accepted.any? { |entry| owned_apt?(current, entry) }
|
|
128
|
+
|
|
129
|
+
raise 'Foreign or modified /etc/apt/apt.conf.d/90-empeira-proxy; refusing to replace or remove it'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def foreign_apt_settings(target)
|
|
133
|
+
files = path('/etc/apt/apt.conf.d').glob('*') + [path('/etc/apt/apt.conf')]
|
|
134
|
+
files.each_with_object({}) do |file, settings|
|
|
135
|
+
next if file == target || !file.exist? || file.directory?
|
|
136
|
+
|
|
137
|
+
settings.merge!(apt_settings(read(file)).transform_keys(&:downcase))
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def verify_apt_globals(settings)
|
|
142
|
+
settings.slice('acquire::http::proxy', 'acquire::https::proxy').each do |key, value|
|
|
143
|
+
next if [@desired.fetch('url'), "#{@desired.fetch('url')}/"].include?(value)
|
|
144
|
+
|
|
145
|
+
raise "Foreign APT #{key} conflicts with the normal Empeira proxy"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Parse native flat and nested scopes; host-specific proxy/DIRECT keys remain untouched.
|
|
150
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- Parse flat and nested native APT scopes without evaluating configuration.
|
|
151
|
+
def apt_settings(content)
|
|
152
|
+
scanner = StringScanner.new(content)
|
|
153
|
+
scopes = []
|
|
154
|
+
statement = []
|
|
155
|
+
values = {}
|
|
156
|
+
until scanner.eos?
|
|
157
|
+
next if scanner.scan(%r{\s+|//[^\n]*|/\*.*?\*/|#(?!(?:include|clear)\b)[^\n]*}m)
|
|
158
|
+
|
|
159
|
+
token = scanner.scan(/"(?:\\.|[^"\\])*"|[^\s{};]+|[{};]/)
|
|
160
|
+
raise 'Cannot safely parse foreign APT configuration' unless token
|
|
161
|
+
raise 'Foreign APT #include/#clear requires explicit proxy configuration review' if token.start_with?('#')
|
|
162
|
+
|
|
163
|
+
case token
|
|
164
|
+
when '{' then scopes << statement.join('::')
|
|
165
|
+
statement = []
|
|
166
|
+
when '}' then scopes.pop
|
|
167
|
+
statement = []
|
|
168
|
+
when ';'
|
|
169
|
+
values[[*scopes, statement.first].compact.join('::')] =
|
|
170
|
+
statement.last&.delete_prefix('"')&.delete_suffix('"')
|
|
171
|
+
statement = []
|
|
172
|
+
else statement << token
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
values
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def dnf_content(definition)
|
|
179
|
+
return '' unless definition
|
|
180
|
+
|
|
181
|
+
"#{BEGIN_MARKER}proxy=#{definition.fetch('url')}\n#{END_MARKER}"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# rubocop:disable-next Naming/PredicateMethod -- Reconciliation mutates the DNF main block.
|
|
185
|
+
def reconcile_dnf
|
|
186
|
+
target = path(DNF_PATH)
|
|
187
|
+
current = read(target)
|
|
188
|
+
raise 'Missing /etc/dnf/dnf.conf; refusing to initialize foreign configuration' unless current
|
|
189
|
+
|
|
190
|
+
block = current[/#{Regexp.escape(BEGIN_MARKER)}.*?#{Regexp.escape(END_MARKER)}/m]
|
|
191
|
+
verify_dnf_block(current, block)
|
|
192
|
+
foreign = block ? current.sub(block, '') : current
|
|
193
|
+
desired = dnf_content(@desired)
|
|
194
|
+
content = integrate_dnf(foreign, desired)
|
|
195
|
+
return false if current == content
|
|
196
|
+
|
|
197
|
+
write(target, content, mode: target.stat.mode & 0o777)
|
|
198
|
+
true
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def verify_dnf_block(current, block)
|
|
202
|
+
return unless current.include?(BEGIN_MARKER) || current.include?(END_MARKER)
|
|
203
|
+
return if owned_dnf_block?(current, block)
|
|
204
|
+
|
|
205
|
+
raise 'Modified Empeira DNF proxy block; refusing to replace or remove it'
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def owned_dnf_block?(current, block)
|
|
209
|
+
block && @accepted.map { |entry| dnf_content(entry) }.include?(block) &&
|
|
210
|
+
current.scan(BEGIN_MARKER).size == 1 && current.scan(END_MARKER).size == 1 &&
|
|
211
|
+
current.match?(/^\s*\[main\][^\n]*\n#{Regexp.escape(block)}/i)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def integrate_dnf(content, desired)
|
|
215
|
+
sections = content.split(/(?=^\s*\[)/)
|
|
216
|
+
main = sections.grep(/\A\s*\[main\]\s*\n/i)
|
|
217
|
+
raise 'DNF requires one unambiguous [main] section for normal proxy integration' unless main.size == 1
|
|
218
|
+
|
|
219
|
+
verify_dnf_proxy(main.first) if @desired
|
|
220
|
+
# Insert directly after the section header without changing any existing lines.
|
|
221
|
+
replacement = main.first.sub(/(\A\s*\[main\][^\n]*\n)/i) { "#{Regexp.last_match(1)}#{desired}" }
|
|
222
|
+
sections.map { |section| section.equal?(main.first) ? replacement : section }.join
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def verify_dnf_proxy(section)
|
|
226
|
+
section.each_line do |line|
|
|
227
|
+
match = /^\s*proxy\s*=\s*(.*?)\s*$/i.match(line)
|
|
228
|
+
next unless match
|
|
229
|
+
next if match[1] == @desired.fetch('url')
|
|
230
|
+
|
|
231
|
+
raise 'Foreign DNF [main] proxy conflicts with the normal Empeira proxy; repository overrides are preserved'
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
if $PROGRAM_NAME == __FILE__
|
|
238
|
+
begin
|
|
239
|
+
puts JSON.generate('changed' => Empeira::RuntimeProxyGuest.new(JSON.parse(ARGV.fetch(0))).reconcile)
|
|
240
|
+
rescue StandardError => e
|
|
241
|
+
warn e.message
|
|
242
|
+
exit 1
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OpenSSH ProxyCommand delegates only the byte transport to the central runner.
|
|
4
|
+
require_relative '../../../lib/empeira'
|
|
5
|
+
|
|
6
|
+
runtime, id, port = ARGV
|
|
7
|
+
port ||= '22'
|
|
8
|
+
unless Empeira::Runtime.registry.names.include?(runtime) && id&.match?(/\A[a-f0-9]{12,64}\z/) &&
|
|
9
|
+
port.match?(/\A[0-9]{1,5}\z/) && port.to_i.between?(1, 65_535) && ARGV.size <= 3
|
|
10
|
+
abort 'Invalid managed SSH transport'
|
|
11
|
+
end
|
|
12
|
+
result = Empeira::Execution::Runner.new.stream(runtime, arguments: [
|
|
13
|
+
'exec', '--interactive', id, '/opt/puppetlabs/puppet/bin/ruby',
|
|
14
|
+
'/usr/local/libexec/empeira-ssh-connect.rb', port
|
|
15
|
+
])
|
|
16
|
+
exit result.exit_status
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Port 22
|
|
2
|
+
AddressFamily inet
|
|
3
|
+
PermitRootLogin prohibit-password
|
|
4
|
+
PasswordAuthentication no
|
|
5
|
+
KbdInteractiveAuthentication no
|
|
6
|
+
PubkeyAuthentication yes
|
|
7
|
+
AuthenticationMethods publickey
|
|
8
|
+
AuthorizedKeysFile .ssh/authorized_keys
|
|
9
|
+
UsePAM yes
|
|
10
|
+
DisableForwarding yes
|
|
11
|
+
PermitTunnel no
|
|
12
|
+
X11Forwarding no
|
|
13
|
+
PermitUserEnvironment no
|
|
14
|
+
PrintMotd no
|
|
15
|
+
Subsystem sftp internal-sftp
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
ARG EMPEIRA_BASE_UBUNTU=library/ubuntu@sha256:da6fc2be547864451aa253836dd926da33623312df4a9a243e35dc877c378a78
|
|
2
|
+
FROM ${EMPEIRA_BASE_UBUNTU}
|
|
3
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
4
|
+
RUN ["apt-get", "-o", "APT::Update::Error-Mode=any", "update"]
|
|
5
|
+
RUN ["apt-get", "install", "--yes", "--no-install-recommends", "ca-certificates"]
|
|
6
|
+
RUN ["sed", "-i", "/Types: deb/a Snapshot: yes", "/etc/apt/sources.list.d/ubuntu.sources"]
|
|
7
|
+
RUN ["apt-get", "--snapshot", "20261001T000000Z", "-o", "APT::Update::Error-Mode=any", "update"]
|
|
8
|
+
RUN ["apt-get", "--snapshot", "20261001T000000Z", "install", "--yes", "--no-install-recommends", "squid=7.2-2ubuntu2.2"]
|
|
9
|
+
RUN ["apt-get", "clean"]
|
|
10
|
+
USER proxy
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'resolv'
|
|
6
|
+
require 'timeout'
|
|
7
|
+
|
|
8
|
+
# Two fixed listeners for one backend. The TLS endpoint satisfies the PuppetDB
|
|
9
|
+
# terminus; the isolated Empeira network is the security boundary.
|
|
10
|
+
BACKEND = 'puppetdb-backend.empeira.internal'
|
|
11
|
+
MAX_CONNECTIONS = 64
|
|
12
|
+
tokens = Queue.new
|
|
13
|
+
MAX_CONNECTIONS.times { tokens << true }
|
|
14
|
+
context = OpenSSL::SSL::SSLContext.new
|
|
15
|
+
context.cert = OpenSSL::X509::Certificate.new(File.read('/empeira-relay/cert.pem'))
|
|
16
|
+
context.key = OpenSSL::PKey.read(File.read('/empeira-relay/key.pem'))
|
|
17
|
+
context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
18
|
+
listeners = { TCPServer.new('0.0.0.0', 8080) => false, TCPServer.new('0.0.0.0', 8081) => true }
|
|
19
|
+
|
|
20
|
+
def backend_address
|
|
21
|
+
Resolv::DNS.open(nameserver: [ENV.fetch('EMPEIRA_DNS_IP')], search: [], ndots: 1) do |dns|
|
|
22
|
+
dns.timeouts = 2
|
|
23
|
+
dns.getresource(BACKEND, Resolv::DNS::Resource::IN::A).address.to_s
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def respond(connection, status)
|
|
28
|
+
connection.write("HTTP/1.1 #{status}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n")
|
|
29
|
+
rescue IOError, SystemCallError
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def pump(source, destination)
|
|
34
|
+
loop { destination.write(source.readpartial(16_384)) }
|
|
35
|
+
rescue EOFError
|
|
36
|
+
destination.close_write if destination.is_a?(TCPSocket)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# rubocop:disable-next Metrics/BlockLength -- Bounded bidirectional connection handling is kept together.
|
|
40
|
+
loop do
|
|
41
|
+
IO.select(listeners.keys).first.each do |listener|
|
|
42
|
+
socket = listener.accept
|
|
43
|
+
begin
|
|
44
|
+
tokens.pop(true)
|
|
45
|
+
rescue ThreadError
|
|
46
|
+
respond(socket, '503 Service Unavailable') unless listeners.fetch(listener)
|
|
47
|
+
socket.close
|
|
48
|
+
next
|
|
49
|
+
end
|
|
50
|
+
Thread.new(socket, listeners.fetch(listener)) do |client, tls|
|
|
51
|
+
connection = nil
|
|
52
|
+
upstream = nil
|
|
53
|
+
sender = nil
|
|
54
|
+
begin
|
|
55
|
+
connection = tls ? OpenSSL::SSL::SSLSocket.new(client, context) : client
|
|
56
|
+
Timeout.timeout(5) { connection.accept } if tls
|
|
57
|
+
request = Timeout.timeout(10) { connection.gets("\n", 8192) }
|
|
58
|
+
unless request&.match?(%r{\A(?:GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH) /\S* HTTP/1\.[01]\r?\n\z})
|
|
59
|
+
respond(connection, '400 Bad Request')
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
upstream = Socket.tcp(backend_address, 8080, connect_timeout: 5)
|
|
63
|
+
upstream.write(request)
|
|
64
|
+
Timeout.timeout(120) do
|
|
65
|
+
sender = Thread.new { pump(connection, upstream) }
|
|
66
|
+
pump(upstream, connection)
|
|
67
|
+
end
|
|
68
|
+
rescue Timeout::Error
|
|
69
|
+
respond(connection, '504 Gateway Timeout') if connection && !upstream
|
|
70
|
+
rescue IOError, SystemCallError, Resolv::ResolvError, SocketError, OpenSSL::SSL::SSLError
|
|
71
|
+
respond(connection, '502 Bad Gateway') if connection && !upstream
|
|
72
|
+
ensure
|
|
73
|
+
sender&.kill&.join
|
|
74
|
+
upstream&.close
|
|
75
|
+
connection&.close
|
|
76
|
+
client.close unless client.closed?
|
|
77
|
+
tokens << true
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
+
|
|
5
|
+
require 'puppet'
|
|
6
|
+
|
|
7
|
+
# Read native server settings without selecting an environment.conf override.
|
|
8
|
+
Puppet.initialize_settings(ARGV)
|
|
9
|
+
Puppet.settings.preferred_run_mode = :server
|
|
10
|
+
print Puppet.settings.value(:environment_timeout)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
# Keep the standard Puppet config read-only outside upstream ownership sweeps.
|
|
5
|
+
stage_eyaml() {
|
|
6
|
+
for key in private_key public_key; do
|
|
7
|
+
source="/empeira-eyaml/${key}.pkcs7.pem"
|
|
8
|
+
destination="/etc/puppetlabs/puppet/eyaml/${key}.pkcs7.pem"
|
|
9
|
+
mode=0644
|
|
10
|
+
if [ "$key" = private_key ]; then mode=0600; fi
|
|
11
|
+
install -o puppet -g puppet -m "$mode" "$source" "${destination}.pending"
|
|
12
|
+
mv -f "${destination}.pending" "$destination"
|
|
13
|
+
done
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if [ "${1:-}" = refresh-eyaml ]; then
|
|
17
|
+
stage_eyaml
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
upstream_entrypoint="$1"
|
|
22
|
+
config_destination="$2"
|
|
23
|
+
eyaml_mode="$3"
|
|
24
|
+
puppetdb_mode="$4"
|
|
25
|
+
shift 4
|
|
26
|
+
# Use Puppet's own writer: retain every other setting in the configured confdir.
|
|
27
|
+
# Environment-variable mappings are only supplementary image startup hints.
|
|
28
|
+
confdir="${config_destination%/*}"
|
|
29
|
+
/opt/puppetlabs/bin/puppet config set environment_timeout 0 --section server --confdir "$confdir"
|
|
30
|
+
native_timeout=$(/opt/puppetlabs/puppet/bin/ruby /empeira-server/environment.rb --confdir "$confdir")
|
|
31
|
+
if [ "$native_timeout" != 0 ]; then
|
|
32
|
+
echo "Native Puppetserver [server] environment_timeout is not 0 after configuration" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
if [ "$eyaml_mode" = staged ]; then
|
|
36
|
+
stage_eyaml
|
|
37
|
+
fi
|
|
38
|
+
if [ "$puppetdb_mode" = puppetdb ]; then
|
|
39
|
+
ln -sfn /empeira-server/puppetdb.conf "$config_destination"
|
|
40
|
+
fi
|
|
41
|
+
exec "$upstream_entrypoint" "$@"
|