Skip to content

Commit b2e2338

Browse files
pablomhclaude
andcommitted
Redesign container network topology for better service isolation
Replace the three-network model (foreman-db, foreman-cache, foreman-app) with a per-service network topology derived from actual service communication requirements: foreman (not internal, isolated) Foreman owns this network. Provides internet access for outbound calls. PostgreSQL and Redis attach here for Foreman data access. Dynflow, Candlepin, Pulp, and Foreman Proxy also attach to enable direct communication with Foreman. pulp (not internal, isolated) Pulp owns this network. Provides internet access for content synchronisation. PostgreSQL and Redis attach here for Pulp data access. Foreman and Dynflow also attach to reach Pulp directly. candlepin (internal, isolated) Candlepin owns this network. PostgreSQL attaches here for Candlepin data access. Foreman attaches to reach Candlepin. Candlepin also joins the foreman network so that its hostname is resolvable from Foreman's primary DNS without cross-bridge routing. Pulp and Foreman Proxy have no route to Candlepin. foreman-proxy (not internal, not isolated) Connects Foreman and Foreman Proxy. Not isolated because Foreman reaches the proxy at quadlet.example.com:8443 via host-gateway DNAT, which crosses the foreman-to-foreman-proxy bridge boundary. Netavark isolation rules would block this cross-bridge DNAT forwarding. External managed hosts reach the proxy via the published port from the physical network, unaffected by isolation. Additionally: - postgresql_network and redis_network renamed to postgresql_networks and redis_networks (lists) to support multiple network attachments. Updated development and remote-database playbooks accordingly. - Foreman Proxy container attaches only to foreman-proxy network, not to foreman, keeping it isolated from candlepin and pulp. - Updated docs/deployment.md to reflect the new topology. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1c8eaec commit b2e2338

File tree

11 files changed

+81
-79
lines changed

11 files changed

+81
-79
lines changed

development/playbooks/deploy-dev/deploy-dev.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
- role: certificates
1818
- role: postgresql
1919
vars:
20-
postgresql_network: host
20+
postgresql_networks: host
2121
postgresql_databases:
2222
- name: "{{ candlepin_database_name }}"
2323
owner: "{{ candlepin_database_user }}"
@@ -37,7 +37,7 @@
3737
password: "{{ pulp_database_password }}"
3838
- role: redis
3939
vars:
40-
redis_network: host
40+
redis_networks: host
4141
- role: candlepin
4242
vars:
4343
candlepin_networks: host

docs/deployment.md

Lines changed: 47 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -241,90 +241,87 @@ There is a desire to allow deployments where a single `foremanctl` control node
241241

242242
All containers are connected to one or more named Podman bridge networks instead of sharing the host network namespace, limiting lateral movement: a container can only reach the services it is explicitly connected to.
243243

244+
The topology is designed top-down from service communication requirements. Each of the four application services (Foreman, Pulp, Candlepin, Foreman Proxy) owns a network that carries its private backend traffic. Services join each other's networks only where communication is required, making the allowed paths explicit and auditable.
245+
244246
### Networks
245247

246-
#### `foreman-db`
248+
#### `foreman`
247249

248-
**Properties:** `internal: true`, `isolate: true`
250+
**Properties:** `internal: false`, `isolate: true`
249251

250-
The database network. Only containers that need to read or write persistent data are attached.
252+
Foreman's primary network. Provides outbound internet access for Foreman's external API calls. PostgreSQL and Redis attach here for Foreman's data access. Candlepin, Pulp, Dynflow, and Foreman Proxy also attach so that Foreman can reach each of them directly, and they can reach Foreman.
251253

252-
- `internal: true` removes the default gateway, so no container on this network can initiate outbound internet connections. Database servers have no reason to reach the internet, and clients that need internet access (e.g. for content sync) are multi-homed and use a different network for that.
253-
- `isolate: true` prevents containers on this network from forwarding packets to containers on other bridge networks, closing off lateral movement paths between network segments.
254+
`isolate: true` prevents containers on this network from forwarding packets to containers on other bridge networks, so Candlepin cannot reach Pulp via this network even though both are attached.
254255

255256
| Container | Role |
256257
|-----------|------|
257-
| `postgresql` | Server — listens on port 5432 (internal DB only) |
258-
| `foreman` | Client |
258+
| `postgresql` | Server — Foreman database |
259+
| `redis` | Server — Foreman cache and Dynflow queue |
260+
| `candlepin` | Server — also joins to make its hostname resolvable from Foreman's primary DNS |
261+
| `foreman` | Owner |
259262
| `dynflow-sidekiq@*` | Client |
260-
| `foreman-recurring@*` | Client |
261-
| `candlepin` | Client (internal DB only) |
262-
| `pulp-api` | Client |
263-
| `pulp-content` | Client |
264-
| `pulp-worker@*` | Client |
263+
| `pulp-api` | Server — reachable from Foreman |
264+
| `pulp-content` | Server — reachable from Foreman |
265+
| `pulp-worker@*` | Worker |
266+
| `foreman-proxy` | Server — reachable from Foreman |
265267

266-
Ansible's `community.postgresql.*` modules reach the database during deployment via a Unix socket: `/var/run/postgresql` is bind-mounted from the host into the container so the socket is accessible on the host without publishing a TCP port.
268+
`foreman`, `pulp-api`, and `pulp-content` publish their respective ports to `127.0.0.1` so that the `httpd` reverse proxy running on the host can reach them.
267269

268-
#### `foreman-cache`
270+
#### `pulp`
269271

270-
**Properties:** `internal: true`, `isolate: true`
272+
**Properties:** `internal: false`, `isolate: true`
271273

272-
The cache network. Only containers that need to reach Redis are attached. The same rationale as `foreman-db` applies: cache servers have no business reaching the internet, and the `isolate` flag prevents bridge pivoting.
274+
Pulp's private network. Provides outbound internet access for content synchronisation. PostgreSQL and Redis attach here for Pulp's data access. Foreman and Dynflow also attach to reach Pulp directly.
273275

274276
| Container | Role |
275277
|-----------|------|
276-
| `redis` | Server — listens on port 6379 |
277-
| `foreman` | Clientapp cache and Dynflow queue |
278-
| `dynflow-sidekiq@*` | Client — job queue |
279-
| `foreman-recurring@*` | Client — job queue |
280-
| `pulp-api` | Client |
281-
| `pulp-content` | Client |
282-
| `pulp-worker@*` | Client |
278+
| `postgresql` | Server — Pulp database |
279+
| `redis` | ServerPulp cache and task queue |
280+
| `pulp-api` | Owner |
281+
| `pulp-content` | Owner |
282+
| `pulp-worker@*` | Owner |
283+
| `foreman` | Client |
284+
| `dynflow-sidekiq@*` | Client |
283285

284-
Redis does not publish any port to the host: it is a purely internal service with no legitimate consumers outside the container network.
286+
#### `candlepin`
285287

286-
#### `foreman-app`
288+
**Properties:** `internal: true`, `isolate: true`
287289

288-
**Properties:** none (`internal: false`, `isolate: false`)
290+
Candlepin's private network. `internal: true` removes the default gateway: Candlepin has no reason to initiate outbound internet connections. PostgreSQL attaches here for Candlepin's data access. Foreman attaches to reach Candlepin directly. Pulp and Foreman Proxy have no route to Candlepin.
289291

290-
The application network. Containers that need to communicate with each other at the application layer, or that need outbound internet access (e.g. for content synchronisation), are attached here.
292+
Candlepin also joins the `foreman` network so that its hostname is registered in Foreman's primary DNS zone, avoiding cross-bridge DNAT for the Foreman-to-Candlepin connection.
291293

292294
| Container | Role |
293295
|-----------|------|
294-
| `candlepin` | Server — Tomcat (23443) and Artemis STOMP broker (61613) |
295-
| `foreman` | Client to Candlepin; serves Foreman Proxy requests |
296-
| `dynflow-sidekiq@*` | Client |
297-
| `foreman-recurring@*` | Client |
298-
| `pulp-api` | Server — API (24817); needs internet for content sync |
299-
| `pulp-content` | Server — content (24816); needs internet for content sync |
300-
| `pulp-worker@*` | Worker — needs internet for content sync |
296+
| `postgresql` | Server — Candlepin database |
297+
| `candlepin` | Owner — Tomcat (23443) and Artemis STOMP broker (61613) |
298+
| `foreman` | Client |
301299

302-
Candlepin does not publish any ports to the host: `foreman` reaches it directly over the bridge using its DNS name. `foreman`, `pulp-api`, and `pulp-content` publish their respective ports to `127.0.0.1` so that the `httpd` reverse proxy running on the host can reach them.
300+
Candlepin does not publish any ports to the host: Foreman reaches it directly over the bridge using its DNS name.
303301

304-
#### `foreman-proxy-net`
302+
#### `foreman-proxy`
305303

306-
**Properties:** none (`internal: false`, `isolate: false`)
304+
**Properties:** `internal: false`, `isolate: false`
307305

308-
The proxy network, used exclusively for communication between Foreman and Foreman Proxy. Keeping this traffic on a dedicated network makes it straightforward to apply stricter controls in future without affecting the rest of the application.
306+
The proxy network, used for communication between Foreman and Foreman Proxy. Not isolated because Foreman reaches the proxy at `quadlet.example.com:8443` via host-gateway DNAT, which crosses the `foreman`-to-`foreman-proxy` bridge boundary; netavark isolation rules would block this cross-bridge DNAT forwarding.
309307

310308
| Container | Role |
311309
|-----------|------|
312-
| `foreman-proxy` | Server — listens on 0.0.0.0:8443 (external) |
310+
| `foreman-proxy` | Owner — listens on `0.0.0.0:8443` |
313311
| `foreman` | Client |
314312

315-
`foreman-proxy` publishes port `0.0.0.0:8443` so that remote Foreman Proxies and clients can register and communicate with it from outside the host.
313+
`foreman-proxy` publishes port `0.0.0.0:8443` so that external managed hosts can communicate with it, and so that Foreman on the `foreman` network can call back to it via host-gateway.
316314

317315
### Network membership summary
318316

319-
| Container | foreman-db | foreman-cache | foreman-app | foreman-proxy-net |
320-
|-----------|:----------:|:-------------:|:-----------:|:-----------------:|
321-
| `postgresql` || | | |
322-
| `redis` | || | |
323-
| `candlepin` |(internal DB) | || |
317+
| Container | `foreman` | `pulp` | `candlepin` | `foreman-proxy` |
318+
|-----------|:---------:|:------:|:-----------:|:---------------:|
319+
| `postgresql` || | | |
320+
| `redis` | || | |
321+
| `candlepin` || || |
324322
| `foreman` |||||
325-
| `dynflow-sidekiq@*` |||| |
326-
| `foreman-recurring@*` |||| |
323+
| `dynflow-sidekiq@*` ||| | |
327324
| `foreman-proxy` | | | ||
328-
| `pulp-api` ||| | |
329-
| `pulp-content` ||| | |
330-
| `pulp-worker@*` ||| | |
325+
| `pulp-api` ||| | |
326+
| `pulp-content` ||| | |
327+
| `pulp-worker@*` ||| | |

src/playbooks/deploy/deploy.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,20 @@
2424
certificate_checks_ca: "{{ ca_certificate }}"
2525
- role: deploy_network
2626
vars:
27-
deploy_network_name: foreman-db
28-
deploy_network_internal: true
27+
deploy_network_name: foreman
2928
deploy_network_isolate: true
3029
- role: deploy_network
3130
vars:
32-
deploy_network_name: foreman-cache
33-
deploy_network_internal: true
31+
deploy_network_name: pulp
3432
deploy_network_isolate: true
3533
- role: deploy_network
3634
vars:
37-
deploy_network_name: foreman-app
35+
deploy_network_name: candlepin
36+
deploy_network_internal: true
37+
deploy_network_isolate: true
3838
- role: deploy_network
3939
vars:
40-
deploy_network_name: foreman-proxy-net
40+
deploy_network_name: foreman-proxy
4141
- role: postgresql
4242
when:
4343
- database_mode == 'internal'

src/roles/candlepin/defaults/main.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ candlepin_ciphers:
1515
candlepin_container_image: quay.io/foreman/candlepin
1616
candlepin_container_tag: "4.4.14"
1717
candlepin_registry_auth_file: /etc/foreman/registry-auth.json
18-
candlepin_networks: "{{ (['foreman-db'] if database_mode == 'internal' else []) + ['foreman-app'] }}"
18+
candlepin_networks:
19+
- candlepin
20+
- foreman
1921

2022
candlepin_database_host: postgresql
2123
candlepin_database_port: 5432

src/roles/foreman/tasks/main.yaml

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@
102102
state: quadlet
103103
sdnotify: true
104104
network:
105-
- foreman-db
106-
- foreman-cache
107-
- foreman-app
108-
- foreman-proxy-net
105+
- foreman
106+
- pulp
107+
- candlepin
108+
- foreman-proxy
109109
ports:
110110
- "127.0.0.1:3000:3000"
111111
hostname: "{{ ansible_facts['fqdn'] }}"
@@ -144,9 +144,8 @@
144144
state: quadlet
145145
sdnotify: true
146146
network:
147-
- foreman-db
148-
- foreman-cache
149-
- foreman-app
147+
- foreman
148+
- pulp
150149
hostname: "{{ ansible_facts['fqdn'] }}"
151150
volume:
152151
- 'foreman-data-run:/var/run/foreman:z'
@@ -199,9 +198,8 @@
199198
image: "{{ foreman_container_image }}:{{ foreman_container_tag }}"
200199
sdnotify: false
201200
network:
202-
- foreman-db
203-
- foreman-cache
204-
- foreman-app
201+
- foreman
202+
- pulp
205203
hostname: "{{ ansible_facts['fqdn'] }}"
206204
command: "foreman-rake {{ item.rake }}"
207205
volume:
@@ -252,7 +250,7 @@
252250
- bin/rails db:migrate && bin/rails db:seed
253251
detach: false
254252
rm: true
255-
network: "{{ ['foreman-db'] if database_mode == 'internal' else ['foreman-app'] }}"
253+
network: foreman
256254
env:
257255
FOREMAN_ENABLED_PLUGINS: "{{ foreman_plugins | join(' ') }}"
258256
secrets:

src/roles/foreman_proxy/tasks/main.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
image: "{{ foreman_proxy_container_image }}:{{ foreman_proxy_container_tag }}"
1919
state: quadlet
2020
sdnotify: true
21-
network: foreman-proxy-net
21+
network:
22+
- foreman-proxy
2223
ports:
2324
- "0.0.0.0:8443:8443"
2425
hostname: "{{ ansible_facts['fqdn'] }}"

src/roles/postgresql/defaults/main.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ postgresql_container_image: quay.io/sclorg/postgresql-13-c9s
33
postgresql_container_tag: "latest"
44
postgresql_registry_auth_file: /etc/foreman/registry-auth.json
55
postgresql_container_name: postgresql
6-
postgresql_network: foreman-db
6+
postgresql_networks:
7+
- foreman
8+
- pulp
9+
- candlepin
710
postgresql_socket_dir: /var/run/postgresql
811
postgresql_restart_policy: always
912

src/roles/postgresql/tasks/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
state: quadlet
3737
healthcheck: pg_isready
3838
sdnotify: healthy
39-
network: "{{ postgresql_network }}"
39+
network: "{{ postgresql_networks }}"
4040
volumes:
4141
- "{{ postgresql_data_dir }}:/var/lib/pgsql/data:Z"
4242
- "{{ postgresql_socket_dir }}:{{ postgresql_socket_dir }}:Z"

src/roles/pulp/defaults/main.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,13 @@ pulp_database_user: pulp
3636
pulp_database_host: postgresql
3737
pulp_redis_url: "redis://redis:6379/8"
3838
pulp_networks:
39-
- foreman-db
40-
- foreman-cache
41-
- foreman-app
39+
- pulp
4240
pulp_api_ports:
4341
- "127.0.0.1:24817:24817"
4442
pulp_content_ports:
4543
- "127.0.0.1:24816:24816"
46-
pulp_migration_networks: "{{ ['foreman-db'] if database_mode == 'internal' else ['foreman-app'] }}"
44+
pulp_migration_networks:
45+
- pulp
4746
pulp_database_port: 5432
4847
pulp_database_ssl_mode: disabled
4948
pulp_database_ssl_ca:

src/roles/redis/defaults/main.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
redis_container_image: quay.io/sclorg/redis-6-c9s
33
redis_container_tag: "latest"
44
redis_registry_auth_file: /etc/foreman/registry-auth.json
5-
redis_network: foreman-cache
5+
redis_networks:
6+
- foreman
7+
- pulp

0 commit comments

Comments
 (0)