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
data/lib/empeira.rb
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'empeira/errors'
|
|
4
|
+
require_relative 'empeira/progress'
|
|
5
|
+
require_relative 'empeira/build_info'
|
|
6
|
+
require_relative 'empeira/immutable'
|
|
7
|
+
require_relative 'empeira/hostname_pattern'
|
|
8
|
+
require_relative 'empeira/project_root'
|
|
9
|
+
require_relative 'empeira/providers/registry'
|
|
10
|
+
require_relative 'empeira/providers/resource'
|
|
11
|
+
require_relative 'empeira/providers/lifecycle'
|
|
12
|
+
require_relative 'empeira/runtime/interface'
|
|
13
|
+
require_relative 'empeira/vm/interface'
|
|
14
|
+
require_relative 'empeira/platform/kvm'
|
|
15
|
+
require_relative 'empeira/vm/prerequisites'
|
|
16
|
+
require_relative 'empeira/vm/qemu'
|
|
17
|
+
require_relative 'empeira/node/interface'
|
|
18
|
+
require_relative 'empeira/node/diagnostic_command'
|
|
19
|
+
require_relative 'empeira/images/source'
|
|
20
|
+
require_relative 'empeira/runtime/container'
|
|
21
|
+
require_relative 'empeira/runtime/service_inspection'
|
|
22
|
+
require_relative 'empeira/runtime/service_arguments'
|
|
23
|
+
require_relative 'empeira/runtime/service_images'
|
|
24
|
+
require_relative 'empeira/runtime/image_metadata'
|
|
25
|
+
require_relative 'empeira/runtime/registry_diagnostics'
|
|
26
|
+
require_relative 'empeira/runtime/recipe_build'
|
|
27
|
+
require_relative 'empeira/runtime/services'
|
|
28
|
+
require_relative 'empeira/runtime/diagnostic_execution'
|
|
29
|
+
require_relative 'empeira/runtime/workspace_network'
|
|
30
|
+
require_relative 'empeira/runtime/update_helper'
|
|
31
|
+
require_relative 'empeira/runtime/agent_helper'
|
|
32
|
+
require_relative 'empeira/runtime/mount_probe'
|
|
33
|
+
require_relative 'empeira/runtime/docker'
|
|
34
|
+
require_relative 'empeira/runtime/podman'
|
|
35
|
+
require_relative 'empeira/providers'
|
|
36
|
+
require_relative 'empeira/network/policy'
|
|
37
|
+
require_relative 'empeira/network/resource'
|
|
38
|
+
require_relative 'empeira/network/definition'
|
|
39
|
+
require_relative 'empeira/network/egress'
|
|
40
|
+
require_relative 'empeira/network/peer/layout'
|
|
41
|
+
require_relative 'empeira/network/direct_egress'
|
|
42
|
+
require_relative 'empeira/network/gateway'
|
|
43
|
+
require_relative 'empeira/network/redirects'
|
|
44
|
+
require_relative 'empeira/images/configuration'
|
|
45
|
+
require_relative 'empeira/images/reference'
|
|
46
|
+
require_relative 'empeira/images/recipe'
|
|
47
|
+
require_relative 'empeira/server/runtime'
|
|
48
|
+
require_relative 'empeira/server/environment_configuration'
|
|
49
|
+
require_relative 'empeira/server/puppetdb_runtime'
|
|
50
|
+
require_relative 'empeira/server/http'
|
|
51
|
+
require_relative 'empeira/server/relay_certificate'
|
|
52
|
+
require_relative 'empeira/node/external_fact'
|
|
53
|
+
require_relative 'empeira/node/bootstrap'
|
|
54
|
+
require_relative 'empeira/node/package_proxy'
|
|
55
|
+
require_relative 'empeira/node/package_bootstrap'
|
|
56
|
+
require_relative 'empeira/network/naming'
|
|
57
|
+
require_relative 'empeira/network/proxy_policy'
|
|
58
|
+
require_relative 'empeira/network/host_policy'
|
|
59
|
+
require_relative 'empeira/network/proxy_bindings'
|
|
60
|
+
require_relative 'empeira/network/bootstrap_proxy'
|
|
61
|
+
require_relative 'empeira/services/definition'
|
|
62
|
+
require_relative 'empeira/configuration/document'
|
|
63
|
+
require_relative 'empeira/configuration/merge'
|
|
64
|
+
require_relative 'empeira/configuration/image_schema'
|
|
65
|
+
require_relative 'empeira/configuration/node_images'
|
|
66
|
+
require_relative 'empeira/configuration/agent_values'
|
|
67
|
+
require_relative 'empeira/configuration/agent_schema'
|
|
68
|
+
require_relative 'empeira/configuration/hiera_mount_schema'
|
|
69
|
+
require_relative 'empeira/configuration/hiera_modulepath'
|
|
70
|
+
require_relative 'empeira/configuration/hiera_mounts'
|
|
71
|
+
require_relative 'empeira/configuration/server_mounts'
|
|
72
|
+
require_relative 'empeira/configuration/eyaml'
|
|
73
|
+
require_relative 'empeira/configuration/bootstrap_guests'
|
|
74
|
+
require_relative 'empeira/configuration/additional_services'
|
|
75
|
+
require_relative 'empeira/configuration/service_configuration'
|
|
76
|
+
require_relative 'empeira/configuration/command_mocks'
|
|
77
|
+
require_relative 'empeira/configuration/dns_rewrites'
|
|
78
|
+
require_relative 'empeira/configuration/network_redirects'
|
|
79
|
+
require_relative 'empeira/configuration/vm_interfaces'
|
|
80
|
+
require_relative 'empeira/configuration/addons'
|
|
81
|
+
require_relative 'empeira/configuration/schema'
|
|
82
|
+
require_relative 'empeira/configuration/ssh_preferences'
|
|
83
|
+
require_relative 'empeira/configuration/user'
|
|
84
|
+
require_relative 'empeira/configuration/loader'
|
|
85
|
+
require_relative 'empeira/configuration/display'
|
|
86
|
+
require_relative 'empeira/platform/facts'
|
|
87
|
+
require_relative 'empeira/platform/resolvers'
|
|
88
|
+
require_relative 'empeira/platform/locations'
|
|
89
|
+
require_relative 'empeira/workspace'
|
|
90
|
+
require_relative 'empeira/images/identity'
|
|
91
|
+
require_relative 'empeira/vm/image_source'
|
|
92
|
+
require_relative 'empeira/vm/image_cache'
|
|
93
|
+
require_relative 'empeira/vm/disk'
|
|
94
|
+
require_relative 'empeira/vm/root_disk'
|
|
95
|
+
require_relative 'empeira/vm/qemu_runtime'
|
|
96
|
+
require_relative 'empeira/vm/session_lock'
|
|
97
|
+
require_relative 'empeira/vm/removal'
|
|
98
|
+
require_relative 'empeira/node/ssh_credentials'
|
|
99
|
+
require_relative 'empeira/node/ssh_client'
|
|
100
|
+
require_relative 'empeira/node/user_ssh'
|
|
101
|
+
require_relative 'empeira/node/ssh_identity'
|
|
102
|
+
require_relative 'empeira/node/ssh_endpoint'
|
|
103
|
+
require_relative 'empeira/vm/cloud_init'
|
|
104
|
+
require_relative 'empeira/vm/guest_channel'
|
|
105
|
+
require_relative 'empeira/vm/guest_output'
|
|
106
|
+
require_relative 'empeira/vm/guest_shell'
|
|
107
|
+
require_relative 'empeira/vm/guest_upload'
|
|
108
|
+
require_relative 'empeira/vm/guest'
|
|
109
|
+
require_relative 'empeira/vm/guest_network'
|
|
110
|
+
require_relative 'empeira/vm/interface_ownership'
|
|
111
|
+
require_relative 'empeira/vm/interface_routes'
|
|
112
|
+
require_relative 'empeira/vm/interfaces'
|
|
113
|
+
require_relative 'empeira/vm/certificate_runtime'
|
|
114
|
+
require_relative 'empeira/execution/result'
|
|
115
|
+
require_relative 'empeira/execution/console'
|
|
116
|
+
require_relative 'empeira/execution/runner'
|
|
117
|
+
require_relative 'empeira/execution/terminal'
|
|
118
|
+
require_relative 'empeira/node/run_request'
|
|
119
|
+
require_relative 'empeira/node/image'
|
|
120
|
+
require_relative 'empeira/node/definition'
|
|
121
|
+
require_relative 'empeira/node/certificates'
|
|
122
|
+
require_relative 'empeira/node/puppet_command'
|
|
123
|
+
require_relative 'empeira/node/apt_configuration'
|
|
124
|
+
require_relative 'empeira/agent/target'
|
|
125
|
+
require_relative 'empeira/agent/versions'
|
|
126
|
+
require_relative 'empeira/agent/authentication'
|
|
127
|
+
require_relative 'empeira/agent/download'
|
|
128
|
+
require_relative 'empeira/agent/artifact'
|
|
129
|
+
require_relative 'empeira/agent/cache_storage'
|
|
130
|
+
require_relative 'empeira/agent/cache'
|
|
131
|
+
require_relative 'empeira/agent/acquisition'
|
|
132
|
+
require_relative 'empeira/agent/release_sources'
|
|
133
|
+
require_relative 'empeira/agent/native_commands'
|
|
134
|
+
require_relative 'empeira/node/agent_repository'
|
|
135
|
+
require_relative 'empeira/node/dnf_agent_repository'
|
|
136
|
+
require_relative 'empeira/node/agent_package'
|
|
137
|
+
require_relative 'empeira/node/agent_installer'
|
|
138
|
+
require_relative 'empeira/node/rpm_configuration'
|
|
139
|
+
require_relative 'empeira/node/package_sources'
|
|
140
|
+
require_relative 'empeira/node/runtime_proxy'
|
|
141
|
+
require_relative 'empeira/node/interactive_tools'
|
|
142
|
+
require_relative 'empeira/node/command_mock'
|
|
143
|
+
require_relative 'empeira/node/command_mocks'
|
|
144
|
+
require_relative 'empeira/node/container_bootstrap'
|
|
145
|
+
require_relative 'empeira/node/vm_bootstrap'
|
|
146
|
+
require_relative 'empeira/vm/bootstrap_requirements'
|
|
147
|
+
require_relative 'empeira/vm/agent'
|
|
148
|
+
require_relative 'empeira/node/inventory'
|
|
149
|
+
require_relative 'empeira/node/container_lifecycle'
|
|
150
|
+
require_relative 'empeira/node/container_preparation'
|
|
151
|
+
require_relative 'empeira/node/container_agent_bootstrap'
|
|
152
|
+
require_relative 'empeira/node/container_state'
|
|
153
|
+
require_relative 'empeira/node/container_guest'
|
|
154
|
+
require_relative 'empeira/node/container'
|
|
155
|
+
require_relative 'empeira/node/vm_state'
|
|
156
|
+
require_relative 'empeira/vm/management'
|
|
157
|
+
require_relative 'empeira/vm/system_ssh'
|
|
158
|
+
require_relative 'empeira/node/vm_preflight'
|
|
159
|
+
require_relative 'empeira/node/vm_preparation'
|
|
160
|
+
require_relative 'empeira/node/vm_lifecycle'
|
|
161
|
+
require_relative 'empeira/node/vm'
|
|
162
|
+
require_relative 'empeira/node/service'
|
|
163
|
+
require_relative 'empeira/control_plane/files'
|
|
164
|
+
require_relative 'empeira/control_plane/additional_configurations'
|
|
165
|
+
require_relative 'empeira/modules/image'
|
|
166
|
+
require_relative 'empeira/execution/diagnostics'
|
|
167
|
+
require_relative 'empeira/modules/state'
|
|
168
|
+
require_relative 'empeira/modules/events'
|
|
169
|
+
require_relative 'empeira/modules/git_safety'
|
|
170
|
+
require_relative 'empeira/modules/storage'
|
|
171
|
+
require_relative 'empeira/modules/request'
|
|
172
|
+
require_relative 'empeira/modules/installer'
|
|
173
|
+
require_relative 'empeira/modules/host_git'
|
|
174
|
+
require_relative 'empeira/addons/error'
|
|
175
|
+
require_relative 'empeira/addons/manifest'
|
|
176
|
+
require_relative 'empeira/addons/storage'
|
|
177
|
+
require_relative 'empeira/addons/installation'
|
|
178
|
+
require_relative 'empeira/addons/environment'
|
|
179
|
+
require_relative 'empeira/addons/service'
|
|
180
|
+
require_relative 'empeira/control_plane/environment'
|
|
181
|
+
require_relative 'empeira/control_plane/browser'
|
|
182
|
+
require_relative 'empeira/control_plane/plan'
|
|
183
|
+
require_relative 'empeira/infrastructure/definition'
|
|
184
|
+
require_relative 'empeira/infrastructure/store'
|
|
185
|
+
require_relative 'empeira/infrastructure/recovery_inventory'
|
|
186
|
+
require_relative 'empeira/infrastructure/owned_files'
|
|
187
|
+
require_relative 'empeira/infrastructure/cleanup'
|
|
188
|
+
require_relative 'empeira/infrastructure/status'
|
|
189
|
+
require_relative 'empeira/control_plane/health'
|
|
190
|
+
require_relative 'empeira/control_plane/storage'
|
|
191
|
+
require_relative 'empeira/control_plane/discovery'
|
|
192
|
+
require_relative 'empeira/control_plane/controller'
|
|
193
|
+
require_relative 'empeira/infrastructure/service'
|
|
194
|
+
require_relative 'empeira/updates/images'
|
|
195
|
+
require_relative 'empeira/updates/modules'
|
|
196
|
+
require_relative 'empeira/updates/service'
|
|
197
|
+
require_relative 'empeira/application'
|
|
198
|
+
|
|
199
|
+
require_relative 'empeira/platform/routes'
|
|
200
|
+
require_relative 'empeira/network/peer/allocation'
|
|
201
|
+
require_relative 'empeira/network/peer/backend'
|
|
202
|
+
require_relative 'empeira/network/peer/channel'
|
|
203
|
+
require_relative 'empeira/network/peer/channel_server'
|
|
204
|
+
require_relative 'empeira/network/peer/adapter_image'
|
|
205
|
+
require_relative 'empeira/network/peer/helper_security'
|
|
206
|
+
require_relative 'empeira/network/peer/docker_adapter'
|
|
207
|
+
require_relative 'empeira/network/peer/linux_podman'
|
|
208
|
+
require_relative 'empeira/network/peer/podman_machine'
|
|
209
|
+
require_relative 'empeira/network/peer/attachment'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
|
|
5
|
+
# Fixed destination, opaque TLS bytes: no HTTP proxy, CONNECT or routing interface.
|
|
6
|
+
listener = TCPServer.new('0.0.0.0', 3001)
|
|
7
|
+
loop do
|
|
8
|
+
client = listener.accept
|
|
9
|
+
Thread.new(client) do |connection|
|
|
10
|
+
address = File.read('/empeira-browser/address').strip
|
|
11
|
+
Socket.tcp(address, 3001, connect_timeout: 5) do |upstream|
|
|
12
|
+
sender = Thread.new { IO.copy_stream(connection, upstream) }
|
|
13
|
+
IO.copy_stream(upstream, connection)
|
|
14
|
+
ensure
|
|
15
|
+
sender&.kill&.join
|
|
16
|
+
end
|
|
17
|
+
rescue IOError, SystemCallError, SocketError
|
|
18
|
+
nil
|
|
19
|
+
ensure
|
|
20
|
+
connection.close
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
ARG EMPEIRA_BASE_RUBY=library/ruby:3.4.11-alpine3.23
|
|
2
|
+
FROM ${EMPEIRA_BASE_RUBY}
|
|
3
|
+
RUN apk upgrade --no-cache && apk add --no-cache iproute2 iptables iptables-legacy conntrack-tools
|
|
4
|
+
COPY configure.rb /usr/local/libexec/empeira-gateway
|
|
5
|
+
COPY bridge.rb /usr/local/libexec/empeira-bridge
|
|
6
|
+
RUN chmod 0755 /usr/local/libexec/empeira-gateway /usr/local/libexec/empeira-bridge
|
|
7
|
+
ENTRYPOINT ["/usr/local/libexec/empeira-gateway"]
|
|
8
|
+
CMD ["hold"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
load File.join(__dir__, 'empeira-gateway') unless defined?(EmpeiraGateway)
|
|
5
|
+
|
|
6
|
+
# Docker's internal bridge filtering otherwise discards off-subnet IP packets
|
|
7
|
+
# even when their Ethernet destination is another peer (the workspace gateway).
|
|
8
|
+
# This helper changes only two owned bridge rules, never engine chains/policies.
|
|
9
|
+
module EmpeiraBridge
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def backend
|
|
13
|
+
candidates = %w[iptables iptables-legacy].select do |binary|
|
|
14
|
+
_output, _error, status = Open3.capture3(binary, '-t', 'filter', '-S', 'DOCKER-USER')
|
|
15
|
+
status.success?
|
|
16
|
+
rescue Errno::ENOENT
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
raise 'Cannot identify Docker iptables backend; workspace remains isolated' if candidates.empty?
|
|
20
|
+
|
|
21
|
+
candidates.last
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def rules(bridge, subnet, identity)
|
|
25
|
+
comment = ['-m', 'comment', '--comment', "empeira:#{identity}", '-j', 'ACCEPT']
|
|
26
|
+
[['raw', 'PREROUTING', ['-i', bridge, '-s', subnet, *comment]],
|
|
27
|
+
['filter', 'FORWARD', ['-i', bridge, '-o', bridge, *comment]]]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def validate(action, bridge, subnet, identity)
|
|
31
|
+
raise 'Invalid workspace bridge operation' unless %w[apply remove check].include?(action)
|
|
32
|
+
unless bridge.match?(/\Aep[a-f0-9]{10}\z/) && identity.match?(/\A[a-f0-9]{64}\z/)
|
|
33
|
+
raise 'Invalid workspace bridge identity'
|
|
34
|
+
end
|
|
35
|
+
raise 'Invalid workspace subnet' unless IPAddr.new(subnet).ipv4? && IPAddr.new(subnet).prefix == 24
|
|
36
|
+
raise 'Workspace bridge is unavailable' unless File.directory?("/sys/class/net/#{bridge}/bridge")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def run(action, bridge, subnet, identity)
|
|
40
|
+
validate(action, bridge, subnet, identity)
|
|
41
|
+
binary = backend
|
|
42
|
+
rules(bridge, subnet, identity).each do |table, chain, arguments|
|
|
43
|
+
reconcile(binary, action, table, chain, arguments)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def reconcile(binary, action, table, chain, arguments)
|
|
48
|
+
_output, _error, status = Open3.capture3(binary, '--wait', '5', '-t', table, '-C', chain, *arguments)
|
|
49
|
+
raise 'Workspace bridge attachment rules missing; run empeira up' if action == 'check' && !status.success?
|
|
50
|
+
|
|
51
|
+
if action == 'apply' && !status.success?
|
|
52
|
+
EmpeiraGateway.command(binary, '--wait', '5', '-t', table, '-I', chain, '1', *arguments)
|
|
53
|
+
elsif action == 'remove' && status.success?
|
|
54
|
+
EmpeiraGateway.command(binary, '--wait', '5', '-t', table, '-D', chain, *arguments)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if $PROGRAM_NAME == __FILE__
|
|
60
|
+
begin
|
|
61
|
+
EmpeiraBridge.run(*ARGV)
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
warn e.message
|
|
64
|
+
exit 1
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'ipaddr'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'open3'
|
|
7
|
+
require 'socket'
|
|
8
|
+
|
|
9
|
+
# Runs only inside an owned gateway or an ephemeral helper sharing one owned
|
|
10
|
+
# container's network namespace. Never modifies host firewall tables or routes.
|
|
11
|
+
# rubocop:disable-next Metrics/ModuleLength -- Self-contained, reviewed helper shipped in the gateway image.
|
|
12
|
+
module EmpeiraGateway
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def command(*arguments, input: nil)
|
|
16
|
+
output, error, status = Open3.capture3(*arguments, **(input ? { stdin_data: input } : {}))
|
|
17
|
+
raise "Gateway operation failed: #{arguments.first}: #{error.lines.last.to_s.strip}" unless status.success?
|
|
18
|
+
|
|
19
|
+
output
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def lockdown
|
|
23
|
+
"*filter\n:INPUT DROP [0:0]\n:FORWARD DROP [0:0]\n:OUTPUT DROP [0:0]\n:BOOTSTRAP - [0:0]\nCOMMIT\n"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def restore(content)
|
|
27
|
+
command('iptables-restore', '--wait', '5', input: content)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# rubocop:disable-next Metrics/AbcSize -- Compose only explicit DNS, proxy, egress and redirect grants.
|
|
31
|
+
def rules(plan, internal:, external:)
|
|
32
|
+
filter = [':INPUT DROP [0:0]', ':FORWARD DROP [0:0]', ':OUTPUT DROP [0:0]', ':BOOTSTRAP - [0:0]',
|
|
33
|
+
"-A FORWARD -i #{internal} -o #{external} -j BOOTSTRAP"]
|
|
34
|
+
unless plan.fetch('redirects', []).empty?
|
|
35
|
+
filter << "-A FORWARD -i #{internal} -o #{internal} -m conntrack --ctstate DNAT -j BOOTSTRAP"
|
|
36
|
+
end
|
|
37
|
+
plan.fetch('blocked', []).sort.each { |ip| filter << "-A BOOTSTRAP -s #{ip}/32 -j DROP" }
|
|
38
|
+
filter.concat(redirect_filter(plan, internal))
|
|
39
|
+
filter.concat(dns_rules(plan, internal, external))
|
|
40
|
+
filter.concat(proxy_rules(plan, internal, external))
|
|
41
|
+
filter.concat(direct_rules(plan, internal, external))
|
|
42
|
+
nat = [':PREROUTING ACCEPT [0:0]', ':INPUT ACCEPT [0:0]', ':OUTPUT ACCEPT [0:0]',
|
|
43
|
+
':POSTROUTING ACCEPT [0:0]',
|
|
44
|
+
"-A POSTROUTING -s #{plan.fetch('subnet')} -o #{external} -j MASQUERADE"]
|
|
45
|
+
nat.concat(redirect_nat(plan, internal))
|
|
46
|
+
"*filter\n#{filter.join("\n")}\nCOMMIT\n*nat\n#{nat.join("\n")}\nCOMMIT\n"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def redirect_selector(entry)
|
|
50
|
+
source = entry.fetch('from')
|
|
51
|
+
"-m conntrack --ctorigdst #{source.fetch('ip')} --ctorigdstport #{source.fetch('port')} " \
|
|
52
|
+
"--ctproto #{source.fetch('protocol', 'tcp')}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# rubocop:disable-next Metrics/MethodLength -- Keep both conntrack directions and the final original-tuple drop together.
|
|
56
|
+
def redirect_filter(plan, internal)
|
|
57
|
+
plan.fetch('redirects', []).flat_map do |entry|
|
|
58
|
+
target = entry['to']
|
|
59
|
+
protocol = entry.fetch('from').fetch('protocol', 'tcp')
|
|
60
|
+
selector = redirect_selector(entry)
|
|
61
|
+
allowed = if target
|
|
62
|
+
["-A FORWARD -i #{internal} -o #{internal} -s #{plan.fetch('subnet')} " \
|
|
63
|
+
"-d #{target.fetch('ip')}/32 -p #{protocol} --dport #{target.fetch('port')} #{selector} " \
|
|
64
|
+
'--ctstate DNAT --ctdir ORIGINAL -j ACCEPT',
|
|
65
|
+
"-A FORWARD -i #{internal} -o #{internal} -s #{target.fetch('ip')}/32 " \
|
|
66
|
+
"-d #{plan.fetch('subnet')} -p #{protocol} --sport #{target.fetch('port')} #{selector} " \
|
|
67
|
+
'--ctstate ESTABLISHED --ctdir REPLY -j ACCEPT']
|
|
68
|
+
else
|
|
69
|
+
[]
|
|
70
|
+
end
|
|
71
|
+
[*allowed, "-A FORWARD -p #{protocol} #{selector} -j DROP"]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def redirect_nat(plan, internal)
|
|
76
|
+
plan.fetch('redirects', []).filter_map do |entry|
|
|
77
|
+
target = entry['to']
|
|
78
|
+
next unless target
|
|
79
|
+
|
|
80
|
+
source = entry.fetch('from')
|
|
81
|
+
protocol = source.fetch('protocol', 'tcp')
|
|
82
|
+
["-A PREROUTING -i #{internal} -s #{plan.fetch('subnet')} -d #{source.fetch('ip')}/32 " \
|
|
83
|
+
"-p #{protocol} --dport #{source.fetch('port')} -j DNAT " \
|
|
84
|
+
"--to-destination #{target.fetch('ip')}:#{target.fetch('port')}",
|
|
85
|
+
"-A POSTROUTING -o #{internal} -s #{plan.fetch('subnet')} -d #{target.fetch('ip')}/32 " \
|
|
86
|
+
"-p #{protocol} --dport #{target.fetch('port')} #{redirect_selector(entry)} --ctstate DNAT --ctdir ORIGINAL " \
|
|
87
|
+
"-j SNAT --to-source #{plan.fetch('gateway')}"]
|
|
88
|
+
end.flatten
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def dns_rules(plan, internal, external)
|
|
92
|
+
filter = []
|
|
93
|
+
plan.fetch('resolvers').each do |ip|
|
|
94
|
+
%w[tcp udp].each do |protocol|
|
|
95
|
+
filter << "-A FORWARD -i #{internal} -o #{external} -s #{plan.fetch('dns')}/32 " \
|
|
96
|
+
"-d #{ip}/32 -p #{protocol} --dport 53 -j ACCEPT"
|
|
97
|
+
filter << reply(internal, external, source: plan.fetch('dns'), destination: ip, port: 53, protocol: protocol)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
filter
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def proxy_rules(plan, internal, external)
|
|
104
|
+
filter = []
|
|
105
|
+
plan.fetch('proxies').each do |ip|
|
|
106
|
+
filter << "-A FORWARD -i #{internal} -o #{external} -s #{ip}/32 -p tcp " \
|
|
107
|
+
'-m multiport --dports 80,443 -j ACCEPT'
|
|
108
|
+
[80, 443].each { |port| filter << reply(internal, external, source: ip, port: port) }
|
|
109
|
+
end
|
|
110
|
+
filter
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def direct_rules(plan, internal, external)
|
|
114
|
+
filter = []
|
|
115
|
+
plan.fetch('entries').each do |entry|
|
|
116
|
+
entry.fetch('addresses').each do |ip|
|
|
117
|
+
entry.fetch('ports').each do |port|
|
|
118
|
+
filter << "-A FORWARD -i #{internal} -o #{external} -s #{plan.fetch('subnet')} " \
|
|
119
|
+
"-d #{ip}/32 -p tcp --dport #{port} -j ACCEPT"
|
|
120
|
+
filter << reply(internal, external, destination: ip, port: port)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
filter
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def reply(internal, external, port:, source: nil, destination: nil, protocol: 'tcp')
|
|
128
|
+
selector = "--ctproto #{protocol} --ctorigdstport #{port}"
|
|
129
|
+
selector += " --ctorigsrc #{source}" if source
|
|
130
|
+
selector += " --ctorigdst #{destination}" if destination
|
|
131
|
+
"-A FORWARD -i #{external} -o #{internal} -m conntrack --ctstate ESTABLISHED,RELATED #{selector} -j ACCEPT"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def ipv4(value)
|
|
135
|
+
ip = IPAddr.new(value)
|
|
136
|
+
raise 'Gateway requires explicit IPv4 addresses' unless ip.ipv4? && ip.to_s == value
|
|
137
|
+
|
|
138
|
+
value
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def validate(plan)
|
|
142
|
+
raise 'Invalid gateway plan version' unless plan.fetch('version') == 1
|
|
143
|
+
|
|
144
|
+
subnet = IPAddr.new(plan.fetch('subnet'))
|
|
145
|
+
raise 'Invalid workspace subnet' unless subnet.ipv4? && subnet.private? && subnet.prefix == 24
|
|
146
|
+
|
|
147
|
+
validate_addresses(plan, subnet)
|
|
148
|
+
validate_entries(plan.fetch('entries'))
|
|
149
|
+
validate_redirects(plan.fetch('redirects', []), subnet)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def validate_redirects(entries, subnet)
|
|
153
|
+
sources = entries.map { |entry| validate_redirect(entry, subnet) }
|
|
154
|
+
raise 'Duplicate gateway redirect sources' unless sources.uniq == sources
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def validate_redirect(entry, subnet)
|
|
158
|
+
raise 'Invalid gateway redirect' unless entry.is_a?(Hash) && entry.keys.sort == %w[from to]
|
|
159
|
+
|
|
160
|
+
source = entry.fetch('from')
|
|
161
|
+
validate_endpoint(source, source: true)
|
|
162
|
+
raise 'Redirect source collides with workspace subnet' if subnet.include?(source.fetch('ip'))
|
|
163
|
+
|
|
164
|
+
target = entry['to']
|
|
165
|
+
validate_redirect_target(target, source, subnet)
|
|
166
|
+
source.values_at('ip', 'port') + [source.fetch('protocol', 'tcp')]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def validate_redirect_target(target, source, subnet)
|
|
170
|
+
if target
|
|
171
|
+
validate_endpoint(target)
|
|
172
|
+
raise 'Redirect target is outside workspace subnet' unless subnet.include?(target.fetch('ip'))
|
|
173
|
+
end
|
|
174
|
+
if source.fetch('protocol', 'tcp') == 'udp' &&
|
|
175
|
+
(source.fetch('port') != 53 || (target && target.fetch('port') != 53))
|
|
176
|
+
raise 'UDP redirects require DNS port 53'
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def validate_endpoint(value, source: false)
|
|
181
|
+
raise 'Invalid redirect endpoint' unless value.is_a?(Hash)
|
|
182
|
+
|
|
183
|
+
keys = source ? value.keys - ['protocol'] : value.keys
|
|
184
|
+
raise 'Invalid redirect endpoint' unless keys.sort == %w[ip port]
|
|
185
|
+
raise 'Invalid redirect protocol' if source && !%w[tcp udp].include?(value.fetch('protocol', 'tcp'))
|
|
186
|
+
|
|
187
|
+
ipv4(value.fetch('ip'))
|
|
188
|
+
validate_redirect_port(value.fetch('port'))
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def validate_redirect_port(port)
|
|
192
|
+
raise 'Invalid redirect port' unless port.is_a?(Integer) && port.between?(1, 65_535)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def validate_addresses(plan, subnet)
|
|
196
|
+
%w[gateway dns].each { |key| raise 'Invalid gateway address' unless subnet.include?(ipv4(plan.fetch(key))) }
|
|
197
|
+
%w[resolvers proxies blocked].each { |key| plan.fetch(key, []).each { |ip| ipv4(ip) } }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def validate_entries(entries)
|
|
201
|
+
entries.each do |entry|
|
|
202
|
+
entry.fetch('addresses').each { |ip| ipv4(ip) }
|
|
203
|
+
valid = entry.fetch('ports').all? { |port| port.is_a?(Integer) && port.between?(1, 65_535) }
|
|
204
|
+
raise 'Invalid gateway ports' unless valid
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def ipv4_interfaces
|
|
209
|
+
Socket.getifaddrs.select { |entry| entry.addr&.ipv4? && !entry.addr.ipv4_loopback? }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def interfaces(plan)
|
|
213
|
+
addresses = ipv4_interfaces
|
|
214
|
+
internal = addresses.find { |entry| entry.addr.ip_address == plan.fetch('gateway') }&.name
|
|
215
|
+
external = addresses.map(&:name).uniq.reject { |name| name == internal }
|
|
216
|
+
raise 'Gateway NAT/routing interfaces are unavailable' unless internal && external.size == 1
|
|
217
|
+
|
|
218
|
+
[internal, external.first]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def apply(path)
|
|
222
|
+
restore(lockdown)
|
|
223
|
+
plan = JSON.parse(File.binread(path))
|
|
224
|
+
validate(plan)
|
|
225
|
+
internal, external = interfaces(plan)
|
|
226
|
+
expected = rules(plan, internal: internal, external: external)
|
|
227
|
+
nat = expected.split('*nat', 2).last
|
|
228
|
+
actual = command('iptables-save')
|
|
229
|
+
if nat_configuration(actual) != nat_configuration(expected)
|
|
230
|
+
# Keep forwarding blocked until new NAT and namespace-local connection state agree.
|
|
231
|
+
restore("*nat#{nat}")
|
|
232
|
+
command('conntrack', '--flush')
|
|
233
|
+
end
|
|
234
|
+
restore(expected)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def nat_configuration(content)
|
|
238
|
+
normalized(content).select { |(table, _chain), _value| table == '*nat' }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def route(gateway)
|
|
242
|
+
ipv4(gateway)
|
|
243
|
+
interface = ipv4_interfaces.find do |entry|
|
|
244
|
+
IPAddr.new("#{entry.addr.ip_address}/24").include?(gateway)
|
|
245
|
+
end&.name
|
|
246
|
+
raise 'Workspace gateway is not on the attached subnet' unless interface
|
|
247
|
+
|
|
248
|
+
command('ip', '-4', 'route', 'flush', 'default')
|
|
249
|
+
command('ip', '-4', 'route', 'replace', 'default', 'via', gateway, 'dev', interface)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def phase(action, ip)
|
|
253
|
+
ipv4(ip)
|
|
254
|
+
arguments = ['BOOTSTRAP', '-s', "#{ip}/32", '-j', 'DROP']
|
|
255
|
+
_output, _error, exists = Open3.capture3('iptables', '--wait', '5', '-C', *arguments)
|
|
256
|
+
command('iptables', '--wait', '5', action == 'block' ? '-A' : '-D', *arguments) if
|
|
257
|
+
(action == 'block') != exists.success?
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
261
|
+
def normalized(content)
|
|
262
|
+
table = nil
|
|
263
|
+
content.lines.each_with_object({}) do |line, result|
|
|
264
|
+
line = line.strip.gsub(/\[\d+:\d+\]/, '[0:0]')
|
|
265
|
+
next if line.empty? || line.start_with?('#') || line == 'COMMIT'
|
|
266
|
+
|
|
267
|
+
if line.start_with?('*')
|
|
268
|
+
table = line
|
|
269
|
+
elsif line.start_with?(':')
|
|
270
|
+
result[[table, line.split.first]] = line
|
|
271
|
+
else
|
|
272
|
+
(result[[table, line.split[1]]] ||= []) << normalized_rule(line)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def normalized_rule(line)
|
|
278
|
+
line.split.each_slice(2).filter_map do |flag, value|
|
|
279
|
+
next if flag == '-m' && %w[tcp udp].include?(value)
|
|
280
|
+
|
|
281
|
+
value = value.split(',').sort.join(',') if flag == '--ctstate'
|
|
282
|
+
value = { 'tcp' => '6', 'udp' => '17' }.fetch(value, value) if flag == '--ctproto'
|
|
283
|
+
[flag, value]
|
|
284
|
+
end.sort
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def check(path)
|
|
288
|
+
plan = JSON.parse(File.binread(path))
|
|
289
|
+
validate(plan)
|
|
290
|
+
internal, external = interfaces(plan)
|
|
291
|
+
expected = rules(plan, internal: internal, external: external)
|
|
292
|
+
actual = command('iptables-save')
|
|
293
|
+
raise 'Gateway firewall drift; run empeira up' unless normalized(actual) == normalized(expected)
|
|
294
|
+
raise 'Gateway routing disabled; run empeira up' unless File.read('/proc/sys/net/ipv4/ip_forward').strip == '1'
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity -- Explicit operation dispatch for the owned helper contract.
|
|
298
|
+
def run(arguments)
|
|
299
|
+
case arguments.first
|
|
300
|
+
when 'hold' then sleep
|
|
301
|
+
when 'redirects-capability'
|
|
302
|
+
command('conntrack', '--version')
|
|
303
|
+
puts 'tcp-udp-redirects-v2'
|
|
304
|
+
when 'lockdown'
|
|
305
|
+
restore(lockdown)
|
|
306
|
+
when 'apply' then apply(arguments.fetch(1))
|
|
307
|
+
when 'route' then route(arguments.fetch(1))
|
|
308
|
+
when 'block', 'unblock' then phase(*arguments)
|
|
309
|
+
when 'check'
|
|
310
|
+
check(arguments.fetch(1))
|
|
311
|
+
else raise 'Unknown gateway operation'
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
if $PROGRAM_NAME == __FILE__
|
|
317
|
+
begin
|
|
318
|
+
EmpeiraGateway.run(ARGV)
|
|
319
|
+
rescue StandardError => e
|
|
320
|
+
warn e.message
|
|
321
|
+
exit 1
|
|
322
|
+
end
|
|
323
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
ARG EMPEIRA_BASE_RUBY=library/ruby:3.4.11-alpine3.23
|
|
3
|
+
FROM ${EMPEIRA_BASE_RUBY}
|
|
4
|
+
RUN apk add --no-cache git ca-certificates
|
|
5
|
+
WORKDIR /opt/empeira-modules
|
|
6
|
+
COPY Gemfile Gemfile.lock ./
|
|
7
|
+
RUN apk add --no-cache --virtual .build-deps build-base libffi-dev \
|
|
8
|
+
&& gem install bundler -v 4.0.22 --no-document \
|
|
9
|
+
&& bundle config set deployment true && bundle install --jobs 2 \
|
|
10
|
+
&& apk del .build-deps
|
|
11
|
+
COPY worker.rb ./
|
|
12
|
+
RUN mkdir /work && chmod 1777 /work
|
|
13
|
+
USER 65534:65534
|
|
14
|
+
ENTRYPOINT ["ruby"]
|
|
15
|
+
CMD ["-e", "sleep"]
|