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.
Files changed (274) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +150 -0
  4. data/bin/empeira +18 -0
  5. data/config/defaults.yaml +229 -0
  6. data/docs/addons.md +331 -0
  7. data/docs/architecture.md +277 -0
  8. data/docs/configuration.md +1204 -0
  9. data/docs/development.md +472 -0
  10. data/docs/hiera-and-eyaml.md +361 -0
  11. data/docs/installation.md +280 -0
  12. data/docs/networking.md +362 -0
  13. data/docs/nodes.md +642 -0
  14. data/docs/proxy.md +136 -0
  15. data/docs/troubleshooting.md +361 -0
  16. data/lib/empeira/addons/environment.rb +82 -0
  17. data/lib/empeira/addons/error.rb +14 -0
  18. data/lib/empeira/addons/installation.rb +119 -0
  19. data/lib/empeira/addons/manifest.rb +112 -0
  20. data/lib/empeira/addons/service.rb +50 -0
  21. data/lib/empeira/addons/storage.rb +94 -0
  22. data/lib/empeira/agent/acquisition.rb +120 -0
  23. data/lib/empeira/agent/artifact.rb +41 -0
  24. data/lib/empeira/agent/authentication.rb +122 -0
  25. data/lib/empeira/agent/cache.rb +122 -0
  26. data/lib/empeira/agent/cache_storage.rb +82 -0
  27. data/lib/empeira/agent/download.rb +84 -0
  28. data/lib/empeira/agent/native_commands.rb +84 -0
  29. data/lib/empeira/agent/release_sources.rb +64 -0
  30. data/lib/empeira/agent/target.rb +66 -0
  31. data/lib/empeira/agent/versions.rb +29 -0
  32. data/lib/empeira/application.rb +86 -0
  33. data/lib/empeira/build.json +5 -0
  34. data/lib/empeira/build_info.rb +46 -0
  35. data/lib/empeira/cli/addons.rb +45 -0
  36. data/lib/empeira/cli/base.rb +73 -0
  37. data/lib/empeira/cli/completion.rb +42 -0
  38. data/lib/empeira/cli/config.rb +22 -0
  39. data/lib/empeira/cli/main.rb +114 -0
  40. data/lib/empeira/cli/node.rb +87 -0
  41. data/lib/empeira/cli/progress.rb +169 -0
  42. data/lib/empeira/cli/proxy.rb +36 -0
  43. data/lib/empeira/cli/update.rb +23 -0
  44. data/lib/empeira/completion/addons.rb +93 -0
  45. data/lib/empeira/completion/bash.rb +62 -0
  46. data/lib/empeira/completion/catalog.rb +170 -0
  47. data/lib/empeira/configuration/additional_services.rb +74 -0
  48. data/lib/empeira/configuration/addons.rb +85 -0
  49. data/lib/empeira/configuration/agent_schema.rb +163 -0
  50. data/lib/empeira/configuration/agent_values.rb +54 -0
  51. data/lib/empeira/configuration/bootstrap_guests.rb +42 -0
  52. data/lib/empeira/configuration/command_mocks.rb +65 -0
  53. data/lib/empeira/configuration/display.rb +20 -0
  54. data/lib/empeira/configuration/dns_rewrites.rb +58 -0
  55. data/lib/empeira/configuration/document.rb +67 -0
  56. data/lib/empeira/configuration/eyaml.rb +46 -0
  57. data/lib/empeira/configuration/hiera_modulepath.rb +40 -0
  58. data/lib/empeira/configuration/hiera_mount_schema.rb +62 -0
  59. data/lib/empeira/configuration/hiera_mounts.rb +83 -0
  60. data/lib/empeira/configuration/image_schema.rb +68 -0
  61. data/lib/empeira/configuration/loader.rb +70 -0
  62. data/lib/empeira/configuration/merge.rb +48 -0
  63. data/lib/empeira/configuration/network_redirects.rb +73 -0
  64. data/lib/empeira/configuration/node_images.rb +64 -0
  65. data/lib/empeira/configuration/schema.rb +215 -0
  66. data/lib/empeira/configuration/server_mounts.rb +106 -0
  67. data/lib/empeira/configuration/service_configuration.rb +40 -0
  68. data/lib/empeira/configuration/ssh_preferences.rb +83 -0
  69. data/lib/empeira/configuration/user.rb +46 -0
  70. data/lib/empeira/configuration/vm_interfaces.rb +147 -0
  71. data/lib/empeira/control_plane/additional_configurations.rb +106 -0
  72. data/lib/empeira/control_plane/browser.rb +73 -0
  73. data/lib/empeira/control_plane/controller.rb +763 -0
  74. data/lib/empeira/control_plane/discovery.rb +69 -0
  75. data/lib/empeira/control_plane/environment.rb +101 -0
  76. data/lib/empeira/control_plane/files.rb +263 -0
  77. data/lib/empeira/control_plane/health.rb +104 -0
  78. data/lib/empeira/control_plane/plan.rb +356 -0
  79. data/lib/empeira/control_plane/storage.rb +48 -0
  80. data/lib/empeira/errors.rb +9 -0
  81. data/lib/empeira/execution/console.rb +45 -0
  82. data/lib/empeira/execution/diagnostics.rb +210 -0
  83. data/lib/empeira/execution/launch_error.rb +14 -0
  84. data/lib/empeira/execution/result.rb +11 -0
  85. data/lib/empeira/execution/runner.rb +235 -0
  86. data/lib/empeira/execution/terminal.rb +35 -0
  87. data/lib/empeira/hostname_pattern.rb +14 -0
  88. data/lib/empeira/images/configuration.rb +44 -0
  89. data/lib/empeira/images/identity.rb +30 -0
  90. data/lib/empeira/images/recipe.rb +22 -0
  91. data/lib/empeira/images/reference.rb +43 -0
  92. data/lib/empeira/images/source.rb +19 -0
  93. data/lib/empeira/immutable.rb +19 -0
  94. data/lib/empeira/infrastructure/cleanup.rb +328 -0
  95. data/lib/empeira/infrastructure/definition.rb +59 -0
  96. data/lib/empeira/infrastructure/owned_files.rb +63 -0
  97. data/lib/empeira/infrastructure/recovery_inventory.rb +53 -0
  98. data/lib/empeira/infrastructure/service.rb +237 -0
  99. data/lib/empeira/infrastructure/status.rb +73 -0
  100. data/lib/empeira/infrastructure/store.rb +274 -0
  101. data/lib/empeira/modules/events.rb +67 -0
  102. data/lib/empeira/modules/git_safety.rb +60 -0
  103. data/lib/empeira/modules/host_git.rb +91 -0
  104. data/lib/empeira/modules/image.rb +27 -0
  105. data/lib/empeira/modules/installer.rb +67 -0
  106. data/lib/empeira/modules/request.rb +68 -0
  107. data/lib/empeira/modules/state.rb +87 -0
  108. data/lib/empeira/modules/storage.rb +35 -0
  109. data/lib/empeira/network/bootstrap_proxy.rb +157 -0
  110. data/lib/empeira/network/definition.rb +61 -0
  111. data/lib/empeira/network/direct_egress.rb +133 -0
  112. data/lib/empeira/network/egress.rb +27 -0
  113. data/lib/empeira/network/gateway.rb +79 -0
  114. data/lib/empeira/network/host_policy.rb +53 -0
  115. data/lib/empeira/network/naming.rb +28 -0
  116. data/lib/empeira/network/peer/adapter_image.rb +32 -0
  117. data/lib/empeira/network/peer/allocation.rb +45 -0
  118. data/lib/empeira/network/peer/attachment.rb +27 -0
  119. data/lib/empeira/network/peer/backend.rb +108 -0
  120. data/lib/empeira/network/peer/channel.rb +133 -0
  121. data/lib/empeira/network/peer/channel_server.rb +71 -0
  122. data/lib/empeira/network/peer/docker_adapter.rb +165 -0
  123. data/lib/empeira/network/peer/helper_security.rb +40 -0
  124. data/lib/empeira/network/peer/layout.rb +55 -0
  125. data/lib/empeira/network/peer/linux_podman.rb +114 -0
  126. data/lib/empeira/network/peer/podman_machine.rb +112 -0
  127. data/lib/empeira/network/policy.rb +39 -0
  128. data/lib/empeira/network/proxy_bindings.rb +52 -0
  129. data/lib/empeira/network/proxy_policy.rb +64 -0
  130. data/lib/empeira/network/redirects.rb +89 -0
  131. data/lib/empeira/network/resource.rb +50 -0
  132. data/lib/empeira/node/agent_installer.rb +31 -0
  133. data/lib/empeira/node/agent_package.rb +125 -0
  134. data/lib/empeira/node/agent_repository.rb +160 -0
  135. data/lib/empeira/node/apt_configuration.rb +85 -0
  136. data/lib/empeira/node/bootstrap.rb +17 -0
  137. data/lib/empeira/node/certificates.rb +98 -0
  138. data/lib/empeira/node/command_mock.rb +38 -0
  139. data/lib/empeira/node/command_mocks.rb +114 -0
  140. data/lib/empeira/node/container.rb +134 -0
  141. data/lib/empeira/node/container_agent_bootstrap.rb +43 -0
  142. data/lib/empeira/node/container_bootstrap.rb +52 -0
  143. data/lib/empeira/node/container_guest.rb +42 -0
  144. data/lib/empeira/node/container_lifecycle.rb +145 -0
  145. data/lib/empeira/node/container_preparation.rb +144 -0
  146. data/lib/empeira/node/container_state.rb +129 -0
  147. data/lib/empeira/node/definition.rb +20 -0
  148. data/lib/empeira/node/diagnostic_command.rb +19 -0
  149. data/lib/empeira/node/dnf_agent_repository.rb +202 -0
  150. data/lib/empeira/node/external_fact.rb +21 -0
  151. data/lib/empeira/node/image.rb +38 -0
  152. data/lib/empeira/node/interactive_tools.rb +33 -0
  153. data/lib/empeira/node/interface.rb +18 -0
  154. data/lib/empeira/node/inventory.rb +143 -0
  155. data/lib/empeira/node/package_bootstrap.rb +129 -0
  156. data/lib/empeira/node/package_proxy.rb +102 -0
  157. data/lib/empeira/node/package_sources.rb +19 -0
  158. data/lib/empeira/node/puppet_command.rb +12 -0
  159. data/lib/empeira/node/rpm_configuration.rb +62 -0
  160. data/lib/empeira/node/run_request.rb +52 -0
  161. data/lib/empeira/node/runtime_proxy.rb +86 -0
  162. data/lib/empeira/node/service.rb +72 -0
  163. data/lib/empeira/node/ssh_client.rb +25 -0
  164. data/lib/empeira/node/ssh_credentials.rb +78 -0
  165. data/lib/empeira/node/ssh_endpoint.rb +58 -0
  166. data/lib/empeira/node/ssh_identity.rb +37 -0
  167. data/lib/empeira/node/user_ssh.rb +60 -0
  168. data/lib/empeira/node/vm.rb +89 -0
  169. data/lib/empeira/node/vm_bootstrap.rb +53 -0
  170. data/lib/empeira/node/vm_lifecycle.rb +212 -0
  171. data/lib/empeira/node/vm_preflight.rb +37 -0
  172. data/lib/empeira/node/vm_preparation.rb +161 -0
  173. data/lib/empeira/node/vm_state.rb +161 -0
  174. data/lib/empeira/platform/facts.rb +66 -0
  175. data/lib/empeira/platform/kvm.rb +91 -0
  176. data/lib/empeira/platform/locations.rb +51 -0
  177. data/lib/empeira/platform/resolvers.rb +85 -0
  178. data/lib/empeira/platform/routes.rb +50 -0
  179. data/lib/empeira/progress.rb +70 -0
  180. data/lib/empeira/project_root.rb +47 -0
  181. data/lib/empeira/providers/lifecycle.rb +96 -0
  182. data/lib/empeira/providers/registry.rb +21 -0
  183. data/lib/empeira/providers/resource.rb +32 -0
  184. data/lib/empeira/providers.rb +55 -0
  185. data/lib/empeira/runtime/agent_helper.rb +34 -0
  186. data/lib/empeira/runtime/container.rb +184 -0
  187. data/lib/empeira/runtime/diagnostic_execution.rb +52 -0
  188. data/lib/empeira/runtime/docker.rb +87 -0
  189. data/lib/empeira/runtime/image_metadata.rb +83 -0
  190. data/lib/empeira/runtime/interface.rb +8 -0
  191. data/lib/empeira/runtime/mount_probe.rb +57 -0
  192. data/lib/empeira/runtime/podman.rb +122 -0
  193. data/lib/empeira/runtime/recipe_build.rb +103 -0
  194. data/lib/empeira/runtime/registry_diagnostics.rb +23 -0
  195. data/lib/empeira/runtime/service_arguments.rb +55 -0
  196. data/lib/empeira/runtime/service_images.rb +118 -0
  197. data/lib/empeira/runtime/service_inspection.rb +159 -0
  198. data/lib/empeira/runtime/services.rb +120 -0
  199. data/lib/empeira/runtime/update_helper.rb +97 -0
  200. data/lib/empeira/runtime/workspace_network.rb +69 -0
  201. data/lib/empeira/server/environment_configuration.rb +53 -0
  202. data/lib/empeira/server/http.rb +38 -0
  203. data/lib/empeira/server/puppetdb_runtime.rb +74 -0
  204. data/lib/empeira/server/relay_certificate.rb +76 -0
  205. data/lib/empeira/server/runtime.rb +90 -0
  206. data/lib/empeira/services/definition.rb +42 -0
  207. data/lib/empeira/updates/images.rb +104 -0
  208. data/lib/empeira/updates/modules.rb +45 -0
  209. data/lib/empeira/updates/service.rb +45 -0
  210. data/lib/empeira/version_requirement.rb +23 -0
  211. data/lib/empeira/vm/agent.rb +66 -0
  212. data/lib/empeira/vm/bootstrap_requirements.rb +69 -0
  213. data/lib/empeira/vm/certificate_runtime.rb +30 -0
  214. data/lib/empeira/vm/cloud_init.rb +171 -0
  215. data/lib/empeira/vm/disk.rb +136 -0
  216. data/lib/empeira/vm/guest.rb +123 -0
  217. data/lib/empeira/vm/guest_channel.rb +110 -0
  218. data/lib/empeira/vm/guest_network.rb +94 -0
  219. data/lib/empeira/vm/guest_output.rb +52 -0
  220. data/lib/empeira/vm/guest_shell.rb +79 -0
  221. data/lib/empeira/vm/guest_upload.rb +43 -0
  222. data/lib/empeira/vm/image_cache.rb +77 -0
  223. data/lib/empeira/vm/image_source.rb +91 -0
  224. data/lib/empeira/vm/interface.rb +8 -0
  225. data/lib/empeira/vm/interface_ownership.rb +124 -0
  226. data/lib/empeira/vm/interface_routes.rb +101 -0
  227. data/lib/empeira/vm/interfaces.rb +240 -0
  228. data/lib/empeira/vm/management.rb +73 -0
  229. data/lib/empeira/vm/prerequisites.rb +42 -0
  230. data/lib/empeira/vm/qemu.rb +83 -0
  231. data/lib/empeira/vm/qemu_runtime.rb +404 -0
  232. data/lib/empeira/vm/removal.rb +30 -0
  233. data/lib/empeira/vm/root_disk.rb +92 -0
  234. data/lib/empeira/vm/session_lock.rb +56 -0
  235. data/lib/empeira/vm/system_ssh.rb +29 -0
  236. data/lib/empeira/workspace.rb +42 -0
  237. data/lib/empeira.rb +209 -0
  238. data/resources/browser/relay.rb +22 -0
  239. data/resources/gateway/Containerfile +8 -0
  240. data/resources/gateway/bridge.rb +66 -0
  241. data/resources/gateway/configure.rb +323 -0
  242. data/resources/modules/Containerfile +15 -0
  243. data/resources/modules/Gemfile +5 -0
  244. data/resources/modules/Gemfile.lock +105 -0
  245. data/resources/modules/worker.rb +161 -0
  246. data/resources/network/adapter/Containerfile +13 -0
  247. data/resources/network/adapter/bridge_linux.go +51 -0
  248. data/resources/network/adapter/framing.go +45 -0
  249. data/resources/network/adapter/framing_test.go +53 -0
  250. data/resources/network/adapter/go.mod +3 -0
  251. data/resources/network/adapter/main_linux.go +273 -0
  252. data/resources/network/channel.rb +18 -0
  253. data/resources/network/ssh_connect.rb +32 -0
  254. data/resources/nodes/apt_sources.rb +66 -0
  255. data/resources/nodes/debian/Containerfile +10 -0
  256. data/resources/nodes/diagnostic_exec.rb +109 -0
  257. data/resources/nodes/interactive_tools.rb +139 -0
  258. data/resources/nodes/managed_file.rb +91 -0
  259. data/resources/nodes/management/guest_agent.py +415 -0
  260. data/resources/nodes/management/setup.sh +20 -0
  261. data/resources/nodes/management/shell_rc.sh +12 -0
  262. data/resources/nodes/management/system_account.sh +24 -0
  263. data/resources/nodes/management/system_home.sh +15 -0
  264. data/resources/nodes/rpm/Containerfile +10 -0
  265. data/resources/nodes/runtime_proxy.rb +244 -0
  266. data/resources/nodes/ssh/proxy.rb +16 -0
  267. data/resources/nodes/ssh/sshd_config +15 -0
  268. data/resources/nodes/ssh/start +6 -0
  269. data/resources/proxy/Containerfile +10 -0
  270. data/resources/relay/Containerfile +5 -0
  271. data/resources/relay/puppetdb.rb +81 -0
  272. data/resources/relay/server_environment.rb +10 -0
  273. data/resources/relay/server_start.sh +41 -0
  274. metadata +326 -0
@@ -0,0 +1,362 @@
1
+ # Networking
2
+
3
+ Container and VM nodes share one isolated IPv4 peer network with logical DNS names
4
+ and arbitrary internal TCP/UDP. They have no unrestricted host-network or Internet
5
+ attachment. External traffic requires an explicit TCP grant or allowlisted proxy
6
+ policy. IPv6 is disabled; external UDP grants are unsupported.
7
+
8
+ VM-only [additional dummy/VLAN interfaces](configuration.md#additional-vm-interfaces)
9
+ exist entirely inside the guest. They provide ordinary Facter bindings without an
10
+ external VLAN attachment or changes to the peer NIC. Their connected
11
+ routes must avoid existing guest routes, the workspace subnet,
12
+ and transparent redirect source addresses. `/32` is preferred for isolated fact
13
+ tests. Empeira verifies and preserves default routes, and refuses foreign device
14
+ ownership or dependencies instead of removing them.
15
+
16
+ ```mermaid
17
+ flowchart LR
18
+ subgraph Lab[Isolated Empeira workspace]
19
+ C[Container nodes] <--> V[VM nodes]
20
+ C --> D[CoreDNS]
21
+ V --> D
22
+ C --> S[OpenVox Server]
23
+ V --> S
24
+ S --> DB[OpenVoxDB relay and backend] --> PG[PostgreSQL]
25
+ B[Chromium / additional services] <--> C
26
+ B <--> V
27
+ C --> P[Normal / bootstrap proxy]
28
+ V --> P
29
+ end
30
+ D --> G[Workspace gateway: default DROP]
31
+ P --> G
32
+ C -. explicit TCP grant .-> G
33
+ V -. explicit TCP grant .-> G
34
+ G --> External[Permitted DNS / LAN / Internet destinations]
35
+ ```
36
+
37
+ The workspace is a cooperative lab, not an adversarial tenant boundary. Privileged
38
+ peers can spoof shared-network identities. Engine administrators can inspect
39
+ containers and credentials. Use disposable test data and trusted guests.
40
+
41
+ ## Logical network and backend resources
42
+
43
+ Docker uses an internal bridge with
44
+ [isolated gateway mode](https://docs.docker.com/engine/network/port-publishing/#gateway-modes).
45
+ Podman uses an internal Netavark bridge with isolation and runtime DNS disabled.
46
+ Empeira verifies ownership, driver and isolation before reuse; unsupported or
47
+ unverifiable capabilities fail closed.
48
+
49
+ | CLI host/runtime | VM peer attachment |
50
+ | --- | --- |
51
+ | Linux/WSL2, rootless Podman | Nonpersistent TAP in the rootless Netavark namespace; capless peer TCP client |
52
+ | Linux/WSL2, Docker | Private QEMU Ethernet channel and owned bridge adapter |
53
+ | macOS, Podman Machine | QEMU channel through machine SSH and rootless TAP |
54
+ | macOS, Docker Desktop | QEMU channel through runtime exec and owned bridge adapter |
55
+
56
+ One backend factory owns this selection. No host route to container IPs is assumed
57
+ on macOS. KVM/HVF and platform-specific wiring require the corresponding
58
+ [manual gates](development.md#vm-and-shared-network-prerequisites).
59
+ Podman Machine needs one unambiguous active rootless connection and shared canonical
60
+ state paths; Empeira never resets the machine or falls back to rootful execution.
61
+
62
+ The static Go adapter is built from packaged source in an isolated, content-hashed
63
+ context by the selected runtime. Go is not a host prerequisite. Packet channels
64
+ use private directories/sockets outside the control repository. Adapter helpers
65
+ have no control-code mount or credentials; identity, definition and checksum are
66
+ verified. VM management uses a separate private VirtIO-Serial channel and no
67
+ network NIC, SLIRP forward or TCP listener. Its Unix socket is private to the host
68
+ user and bound to the owned QEMU instance. It adds no route or egress exception.
69
+
70
+ System SSH connects directly through the owned peer attachment to the VM's
71
+ verified peer IPv4 and selected guest port (22 by default). Native Podman uses a
72
+ capless owned peer client on the existing isolated network;
73
+ Docker keeps its existing adapter IP on its local bridge with forwarding disabled.
74
+ Podman machine uses its capless staging adapter. TCP binds the observed internal
75
+ client IP, with no host route or additional VM NIC. Ownership, attachment and subnet
76
+ are verified
77
+ before connecting. User SSH needs no management check or authentication and adds
78
+ no host publication or additional VM NIC. OpenSSH uses private per-node known-host
79
+ state and preserves native connection/authentication errors. See
80
+ [SSH and recovery](nodes.md#shell-and-ssh).
81
+
82
+ The CLI host and engine may have different filesystems. Control/Hiera directories,
83
+ server mounts and state must be visible at their canonical paths. Mount probes
84
+ inspect visibility without writing markers. Remote-engine path translation is not
85
+ implemented. On WSL2, keep source checkouts on the Linux filesystem.
86
+
87
+ ### Address lifetime and recovery
88
+
89
+ Under the workspace lock, Empeira selects an unused private /24 after checking
90
+ host routes and runtime networks. Gateway/DNS/proxy addresses, VM/container leases
91
+ and runtime infrastructure allocation use separate pools. There is no fixed global
92
+ subnet or user IP/MAC setting. Unknown overlap or ownership fails closed.
93
+
94
+ A node's IP, MAC and DNS record survive stop/start. Destroy releases its lease and
95
+ removes its record, transport and credentials; recreating a name need not reuse its
96
+ address. A later VPN route can require workspace recreation. Unsupported inventory
97
+ or definition revisions fail closed without migration or automatic adoption.
98
+
99
+ ## One gateway, one direct-egress policy
100
+
101
+ Every peer uses the owned gateway as its IPv4 default route. Only the gateway has
102
+ an external uplink; DNS, proxies, server, database and nodes stay isolated.
103
+ The fixed browser UI relay has a separate loopback access path with forwarding off.
104
+
105
+ `network.egress` grants destination IPv4/TCP-port pairs:
106
+
107
+ ```yaml
108
+ network:
109
+ egress:
110
+ - host: repository.example.net
111
+ ports: [443]
112
+ - ip: 203.0.113.10
113
+ ports: [443, 8443]
114
+ ```
115
+
116
+ Entries have exactly one `host` or `ip`. Hostnames match exactly, without wildcards;
117
+ `up` resolves every current A record and replaces the complete policy. IP selectors
118
+ bypass DNS and may intentionally permit private LAN/VPN destinations. A different
119
+ name sharing an allowed IP is reachable on that port: the firewall cannot distinguish
120
+ HTTP Host or TLS SNI. DNS changes require another `up`.
121
+
122
+ Reconciliation installs DROP before applying complete rules. Removed grants also
123
+ revoke existing connections. DNS or firewall failure leaves egress blocked/stopped,
124
+ never on unrestricted provider NAT. Policy edits preserve node/gateway identities;
125
+ `status` checks actual rules and forwarding, and `up` repairs drift.
126
+
127
+ CoreDNS alone receives TCP/UDP 53 to its selected resolvers. Proxies alone receive
128
+ TCP 80/443 and enforce their destination allowlists. Normal proxy allow rules apply
129
+ regardless of target IP range; the temporary authenticated bootstrap proxy retains
130
+ its separate private/reserved-address restrictions.
131
+ `proxy.global` does not grant direct egress. Applications choose DIRECT/proxy using
132
+ their own settings and `NO_PROXY`; direct/proxy selection has no automatic fallback.
133
+ Both explicit hostnames and IPv4 selectors from `network.egress` are included in
134
+ normal proxy bypass settings. The gateway still permits only their configured TCP ports.
135
+ See [proxy policy](proxy.md) for hostname rules and bootstrap destinations.
136
+
137
+ During bootstrap, a node is excluded from direct egress and normal proxy bindings.
138
+ A separate authenticated /32-source-bound proxy installs packages. Cleanup restores
139
+ package configuration, removes temporary access/credentials and activates final
140
+ runtime policy before enrollment and Puppet. Failed bootstrap stays incomplete.
141
+
142
+ Normal proxy settings are distinct from that temporary bootstrap transaction.
143
+ Managed Puppet processes receive matching uppercase/lowercase HTTP/HTTPS and
144
+ NO_PROXY variables inside the privileged guest command, including direct-root VM
145
+ management.
146
+ APT uses `/etc/apt/apt.conf.d/90-empeira-proxy`; DNF receives an owned block in
147
+ the existing `/etc/dnf/dnf.conf` `[main]` section. Direct-egress hosts get native
148
+ APT DIRECT entries unless an explicit host setting already exists. Foreign
149
+ host-specific APT settings and DNF repository overrides remain unchanged.
150
+ Conflicting global settings, modified owned files or unsafe paths fail with a
151
+ diagnosis. APT configurations using `#include`/`#clear` require explicit review.
152
+ DNF repository `proxy=` disables the inherited proxy; `_none_` retains DNF's
153
+ native curl environment semantics (see the [DNF reference](https://dnf.readthedocs.io/en/stable/conf_ref.html#proxy)).
154
+ These settings change client routing only; destination grants and isolation stay
155
+ under the existing gateway and proxy policies.
156
+
157
+ ## Transparent TCP redirects
158
+
159
+ `network.redirects` maps exact original IPv4/protocol/port destination tuples to
160
+ the current address/port of an owned internal application service. Multiple pairs may share a
161
+ service. This supports fixed production endpoints such as PowerDNS APIs without
162
+ changing Puppet/Hiera or implementing application logic in Empeira. Configuration
163
+ and an example are in [configuration](configuration.md#transparent-tcp-redirects).
164
+
165
+ Container nodes, Puppet/OpenVox Server and internal services already route external
166
+ destinations through the workspace gateway. VM cloud-init installs the same default
167
+ route, and peer adapters transport Ethernet without introducing another IP router.
168
+ The browser UI relay and the gateway itself are infrastructure endpoints, outside
169
+ this source/target contract. Source addresses inside the workspace subnet or in
170
+ loopback, link-local, unspecified or multicast/reserved ranges are rejected because
171
+ those paths would bypass normal gateway routing.
172
+
173
+ The optional source protocol defaults to TCP. UDP DNS uses the same translation
174
+ path on port 53 to the built-in `coredns`/`dns` service. A fixed virtual endpoint such
175
+ as `198.18.0.53` remains independent of the dynamic workspace subnet, without an
176
+ interface alias or resolver-file rewrite. Benchmark addresses are restricted to
177
+ validated redirect sources; overlapping host/VPN/engine routes fail before mutation.
178
+
179
+ The gateway applies exact DNAT in its private namespace. Since the destination and
180
+ client share a subnet, it also SNATs redirected flows to its internal address so
181
+ replies return through the same conntrack translation. The test service sees the
182
+ gateway as its client; the original client sees the configured external endpoint.
183
+ TCP bytes (including HTTP headers/bodies) and UDP DNS messages are unchanged.
184
+ TLS remains end to end
185
+ and the application must validate the original endpoint's certificate as usual.
186
+ This follows Netfilter's [same-network NAT guidance](https://www.netfilter.org/documentation/HOWTO/NAT-HOWTO-6.html).
187
+
188
+ Filtering accepts only the configured original pair and the observed internal
189
+ target pair in the appropriate direction. It drops other packets for that original
190
+ pair before DNS/proxy/direct-egress grants, so a missing target never falls back to
191
+ the external IP. Bootstrap nodes remain excluded. Target addresses come from the
192
+ existing ownership-checked runtime discovery, with no additional inventory mapping.
193
+ CoreDNS retains the peer layout reservation at offset 3 within each dynamic workspace
194
+ subnet, keeping existing guest resolver addresses stable. A same-workspace recreation
195
+ normally keeps that address. A new workspace
196
+ with a different subnet has a different actual CoreDNS IP; the virtual endpoint
197
+ remains unchanged. Reconciliation never stores that actual IP in redirect inventory.
198
+ Before target recreation, the redirect is blocked; after services start, `up`
199
+ refreshes the policy using their current addresses. A target that exits or refuses
200
+ connections causes a connection failure. Removing a service still referenced by
201
+ configuration fails validation; remove its redirect as part of the same edit.
202
+
203
+ Reconciliation first installs DROP, installs changed NAT while forwarding is still
204
+ blocked, then clears conntrack only inside the owned gateway namespace and activates
205
+ the complete filter. Mapping changes therefore revoke old connections, including
206
+ previous connections to the original external destination. This also interrupts
207
+ other connections passing through that gateway; clients must reconnect. Identical
208
+ NAT on repeated `up` preserves connection state. Any apply/capability failure leaves
209
+ the gateway blocked/stopped. `status` checks complete filter/NAT rules and forwarding.
210
+ No host conntrack, foreign firewall or target container configuration is changed.
211
+
212
+ The same helper runs in the Linux engine for Docker, rootless Podman, Docker Desktop
213
+ and Podman Machine; QEMU peers use the existing adapters. Runtime isolation and
214
+ attachment requirements still apply. Custom `images.direct_egress` images must
215
+ implement `redirects-capability` (`tcp-udp-redirects-v2` for UDP DNS; legacy
216
+ `tcp-redirects-v1` remains valid for TCP-only configuration), the redirect plan and
217
+ conntrack invalidation; incompatible images fail explicitly. Native macOS requires
218
+ execution of the platform gates before claiming validation.
219
+
220
+ Redirects do not change DNS, proxy allowlists/environment, `NO_PROXY`, or bootstrap
221
+ grants. An application explicitly using a proxy initially connects to that proxy;
222
+ it still needs a proxy allowlist entry and receives no automatic direct exception.
223
+ Use application-specific DIRECT settings or `curl --noproxy '*'` for direct tests.
224
+
225
+ ## Runtime attachment and privilege boundary
226
+
227
+ Normal Empeira operation requires no interactive sudo/root. One-time runtime,
228
+ TUN and accelerator setup can need administrator privileges.
229
+
230
+ Containers receive managed routes through short-lived helpers in their owned
231
+ network namespace and do not retain `NET_ADMIN`. Docker peer helpers have narrowly
232
+ scoped `NET_ADMIN` and `/dev/net/tun`, read-only filesystems and no new privileges.
233
+
234
+ Docker also requires a short-lived administrative helper in the engine Linux host
235
+ network namespace. It has `NET_ADMIN`/`NET_RAW` and installs only two exact owned
236
+ bridge/subnet rules: raw acceptance and same-bridge forwarding. It verifies network
237
+ labels/ID, bridge name and subnet first, changes no host routes/default policies,
238
+ never flushes firewall tables, and removes its exact rules before network deletion.
239
+ It mounts no host files, engine socket or PID namespace. This is a real privilege
240
+ boundary relying on Docker daemon authorization. Rootless Podman does not use it.
241
+ Docker iptables-nft/legacy interfaces are supported; unknown/native nftables-only
242
+ engines fail closed. Docker Desktop host-network helper restrictions require
243
+ actual platform validation.
244
+
245
+ Do not attach extra interfaces or restart gateways outside the managed lifecycle.
246
+ Cross-workspace subnets, namespaces and bridge rules are distinct, but this shared
247
+ network does not protect against a malicious privileged peer.
248
+
249
+ The gateway recipe uses official Ruby/Alpine and distribution iproute2/iptables
250
+ and [conntrack-tools](https://www.netfilter.org/projects/conntrack-tools/)
251
+ (Netfilter, GPL-2.0-or-later) packages. The connection tool is a separate executable
252
+ in the owned helper, with no host installation or new Ruby dependency. Go/Ruby,
253
+ image and package notices are preserved in built images; only
254
+ project-owned source/recipes are shipped in Empeira. See
255
+ [provenance](configuration.md#control-plane-images-and-provenance).
256
+
257
+ ## DNS and service communication
258
+
259
+ CoreDNS owns `empeira.internal` service names and node hostnames. Unknown names in
260
+ that internal zone never forward externally. Other names use host-derived resolvers:
261
+
262
+ ```yaml
263
+ dns:
264
+ upstream:
265
+ mode: host
266
+ servers: []
267
+ ```
268
+
269
+ Linux/WSL2 reads the host resolver configuration, including systemd-resolved's
270
+ upstream file when needed. macOS reads `scutil --dns` and keeps per-domain routes.
271
+ Unusable loopback/link-local resolvers fail with instructions for an explicit
272
+ reachable IPv4 override. There is no public fallback resolver.
273
+
274
+ ```yaml
275
+ dns:
276
+ upstream:
277
+ mode: explicit
278
+ servers: [10.20.30.53]
279
+ additional_resolver: resolver.empeira.internal
280
+ ```
281
+
282
+ The optional additional resolver is queried first for non-Empeira names; NXDOMAIN
283
+ or NODATA falls through to the existing upstream. It may be a DNS name or address
284
+ and need not be an Empeira service. `up` activates it after additional services
285
+ start. VPN/engine routing remains the host's responsibility; flat Linux resolver
286
+ files cannot represent every split-DNS route. AAAA queries return NODATA.
287
+
288
+ ### Exact DNS rewrites
289
+
290
+ `dns.rewrites` redirects an exact external hostname to an enabled internal service:
291
+
292
+ ```yaml
293
+ dns:
294
+ rewrites:
295
+ - from: ipam.example.net
296
+ to: api-layer.empeira.internal
297
+ - from: inventory.example.net
298
+ to: api-layer.empeira.internal
299
+ ```
300
+
301
+ Both container and VM nodes use the same CoreDNS policy. Sources are normalized
302
+ case-insensitively, with an optional trailing root dot. A rule matches only its
303
+ source: `child.ipam.example.net` still follows the normal resolver policy, including
304
+ host split-DNS routes and the optional additional resolver. Unknown internal names
305
+ and configured rewrite lookups never fall back to an external resolver.
306
+
307
+ CoreDNS uses an exact `rewrite name` rule and the existing authoritative hosts-file
308
+ discovery for `empeira.internal`. An A query returns the target service's current
309
+ IPv4 address under the original source name, with a one-second TTL. No CNAME record
310
+ is created: CNAME queries return NOERROR with no answers for a discovered target.
311
+ AAAA queries retain IPv4-only behavior and return NOERROR with no answers. If a
312
+ target temporarily has no discovery record, A/CNAME queries return SERVFAIL locally;
313
+ AAAA remains empty. `up` rejects targets absent from the enabled service plan.
314
+
315
+ Adding, changing or removing rules takes effect through `empeira up`, using the
316
+ existing Corefile reload and SIGUSR1. CoreDNS and existing nodes keep their
317
+ identities and resolver bindings. Hosts-file updates are polled every second, so
318
+ service-address changes do not require a CoreDNS restart. Client DNS caches may
319
+ delay observations until their cached records expire. Unchanged `up` is idempotent.
320
+ Changes to the DNS image or container definition still retain the existing
321
+ protection against DNS replacement while nodes exist.
322
+
323
+ An API Compatibility Layer can therefore simulate several production API names
324
+ through one additional service; see the [configuration example](configuration.md#dns-rewrites-for-internal-services).
325
+ Only DNS changes. The original HTTP Host, TLS SNI, protocol, port and certificate
326
+ checks remain the application's responsibility. Gateway grants and proxy allowlists
327
+ are unchanged; a client using an HTTP proxy remains subject to that proxy's policy.
328
+
329
+ The server is `server.empeira.internal:8140`. PostgreSQL and OpenVoxDB are internal,
330
+ with retained volumes and random private database credentials. Readiness verifies
331
+ real SQL and database-backed PuppetDB queries.
332
+
333
+ Puppet/OpenVox Server uses `https://puppetdb.empeira.internal:8081`; internal tools
334
+ use `http://puppetdb.empeira.internal:8080`. Both fixed relay listeners forward to
335
+ `puppetdb-backend.empeira.internal:8080`. The HTTPS relay has a workspace CA-issued
336
+ certificate for terminus compatibility; it does not authenticate clients.
337
+ **PuppetDB HTTP is deliberately unauthenticated within the isolated lab.** It is
338
+ never host-published; this is not a production deployment recommendation.
339
+ PostgreSQL transport TLS is not configured. Normal agent/server/CA TLS and disabled
340
+ global autosigning remain intact.
341
+
342
+ `down` retains CA/database volumes and generated credentials; `destroy` removes
343
+ owned persistent data. Missing recorded storage or credentials fails closed.
344
+ Preserve state and volumes together for recovery.
345
+
346
+ ## Internal browser and additional services
347
+
348
+ After `up`, `browser` starts/reuses only Chromium and its fixed UI relay and prints
349
+ `https://127.0.0.1:<dynamic-port>/`. It does not start stopped infrastructure.
350
+ The relay carries TLS only to the observed browser's port 3001, accepts no arbitrary
351
+ forwarding target and disables IP forwarding. Chromium stays isolated and has no
352
+ Internet grant from enabling the node proxy; its profile is disposable.
353
+
354
+ Additional services receive `<name>.empeira.internal` and publish no host ports.
355
+ Use internal URLs in Chromium, for example
356
+ `http://openvoxview.empeira.internal:5000` or `http://host1:8080`. VM/container peers
357
+ are reachable directly at their logical names, with no application exposure matrix.
358
+ The UI is accessible to local users who can reach its loopback endpoint.
359
+
360
+ Image/module acquisition uses the separate [update plane](architecture.md#runtime-and-update-planes),
361
+ with native runtime networking and user proxy/registry settings. Those helpers
362
+ never join the workspace or start its DNS/proxy/services.