Skip to content

Move co-located containers onto shared foreman-core-network - #728

Open
Alleny244 wants to merge 1 commit into
theforeman:masterfrom
Alleny244:fix/postgresql-listen-localhost
Open

Move co-located containers onto shared foreman-core-network#728
Alleny244 wants to merge 1 commit into
theforeman:masterfrom
Alleny244:fix/postgresql-listen-localhost

Conversation

@Alleny244

@Alleny244 Alleny244 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Problem Statement

Embedded PostgreSQL defaults to listen_addresses = '*', exposing port 5432 on public interfaces when containers use host networking.

https://redhat.atlassian.net/browse/SAT-48503

Changes

  • Add a shared Podman bridge network foreman-core-network (10.130.0.0/24) via the foreman_core_network role, created early in deploy.
  • Move co-located containers off host networking onto that network (PostgreSQL, Valkey, Candlepin, Pulp, Foreman/Dynflow, IOP services).
  • Reach services by container name on the shared network (e.g. postgresql, valkey, candlepin, iop-core-gateway).
  • Publish host access on loopback only where needed (e.g. 127.0.0.1:5432, 127.0.0.1:6379, Candlepin/IOP gateway ports) so ports are not exposed on 0.0.0.0 / public interfaces.
  • Remove the IOP-only iop_network / iop-core-network; IOP uses foreman-core-network.
  • Update TLS SANs and Smart Proxy URL so Foreman can reach Candlepin and the IOP gateway by container name.
  • Leave foreman_proxy on host networking (edge/DHCP/DNS/TFTP/REX).
  • Add/update tests to ensure PostgreSQL (and Valkey) are not published on 0.0.0.0:5432 / [::]:5432 (and equivalent for Valkey).

Tests

./forge test --pytest-args="tests/postgresql_test.py -vv"

Checklist

  • Tests added/updated
  • Documentation updated (docs/iop.md)

@Alleny244
Alleny244 force-pushed the fix/postgresql-listen-localhost branch from c6c2b37 to de1fe85 Compare August 5, 2026 14:19
@Alleny244
Alleny244 marked this pull request as draft August 5, 2026 14:22
@Alleny244
Alleny244 force-pushed the fix/postgresql-listen-localhost branch 4 times, most recently from 6bf8eb0 to a7edb41 Compare August 6, 2026 09:16
@Alleny244
Alleny244 marked this pull request as ready for review August 6, 2026 11:47
Comment thread src/roles/iop_core/tasks/main.yaml Outdated
ansible.builtin.include_role:
name: iop_kafka

# Kafka is the first container on iop-core-network, so the gateway IP

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, as you are discovering, is tricky and the same issue I ran into when I tried to do this before. This creates a rather fragile experience.

@ehelms

ehelms commented Aug 7, 2026

Copy link
Copy Markdown
Member

Here's two other ideas:

  1. Put all the containers on the same network.

The iop network was created back in the foreman-installer for isolation, to reduce the need for TLS everywhere and because we were learning. Then we copied the idea here to start with parity. However, back when we implemented iop we always said this should get easier to manage once everything is in containers because we can normalize.

  1. Put postgresql on both networks and have it listen to both.

@Alleny244

Copy link
Copy Markdown
Contributor Author

Here's two other ideas:

  1. Put all the containers on the same network.

The iop network was created back in the foreman-installer for isolation, to reduce the need for TLS everywhere and because we were learning. Then we copied the idea here to start with parity. However, back when we implemented iop we always said this should get easier to manage once everything is in containers because we can normalize.

  1. Put postgresql on both networks and have it listen to both.

I’ll go with PostgreSQL on both networks here. Putting all containers on the same network would require broader changes, so a separate PR would be right?

@Alleny244
Alleny244 force-pushed the fix/postgresql-listen-localhost branch 5 times, most recently from 3b4d9d7 to 6f86341 Compare August 7, 2026 11:47
@ehelms

ehelms commented Aug 7, 2026

Copy link
Copy Markdown
Member

I’ll go with PostgreSQL on both networks here. Putting all containers on the same network would require broader changes, so a separate PR would be right?

Yes and no, you are ultimately trying to solve this broader problem of running only what's needed on the public interface. I'd rather us solve the architecture question than patch it and change it again right after.

I was reading on how pods accomplish the networking issue, and they create a lightweight "infra" container that basically does nothing but is the first container to always start and is present on the network to ensure it's available. I do not think we should use pods here as that will complicate things, but perhaps we can learn from this idea. The trickiest part would be getting the ordering right. But I do think if they are all on the same network, things get easier and I do not see a reason anymore to split them across networks.

@Alleny244

Copy link
Copy Markdown
Contributor Author

I’ll go with PostgreSQL on both networks here. Putting all containers on the same network would require broader changes, so a separate PR would be right?

Yes and no, you are ultimately trying to solve this broader problem of running only what's needed on the public interface. I'd rather us solve the architecture question than patch it and change it again right after.

I was reading on how pods accomplish the networking issue, and they create a lightweight "infra" container that basically does nothing but is the first container to always start and is present on the network to ensure it's available. I do not think we should use pods here as that will complicate things, but perhaps we can learn from this idea. The trickiest part would be getting the ordering right. But I do think if they are all on the same network, things get easier and I do not see a reason anymore to split them across networks.

Yeah, makes sense. I’ll look into the single network approach and see how we can handle the startup ordering.

@Alleny244

Copy link
Copy Markdown
Contributor Author

I’ll go with PostgreSQL on both networks here. Putting all containers on the same network would require broader changes, so a separate PR would be right?

Yes and no, you are ultimately trying to solve this broader problem of running only what's needed on the public interface. I'd rather us solve the architecture question than patch it and change it again right after.

I was reading on how pods accomplish the networking issue, and they create a lightweight "infra" container that basically does nothing but is the first container to always start and is present on the network to ensure it's available. I do not think we should use pods here as that will complicate things, but perhaps we can learn from this idea. The trickiest part would be getting the ordering right. But I do think if they are all on the same network, things get easier and I do not see a reason anymore to split them across networks.

Should I reuse the existing iop-core-network or create a new general purpose network ( if naming conventions are being looked into) for the containers

@ehelms

ehelms commented Aug 10, 2026

Copy link
Copy Markdown
Member

Should I reuse the existing iop-core-network or create a new general purpose network ( if naming conventions are being looked into) for the containers

New general purpose network name that is more descriptive.

@stejskalleos
stejskalleos self-requested a review August 11, 2026 07:57
foreman_development_database_host: "localhost"
# Prefer 127.0.0.1 over localhost: Postgres is published on IPv4 only, and
# "localhost" often tries ::1 first (connection refused).
foreman_development_database_host: "127.0.0.1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit title says:

Restrict PostgreSQL listen_addresses to localhost and IOP gateway

But I don't see any change in the listen_addresses. Please update the commit message & PR title/description to reflect the current state of the PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sure. I’ll update it. Also, the current implementation was based on putting PostgreSQL on both networks. Based on the discussion above, we decided to move forward with creating a new network and placing all the containers on it instead. I’ve been working on that implementation and will push the changes shortly.

# Optional parameters - can use defaults.
# Use 127.0.0.1: Postgres is published on IPv4 only (not ::1), and TCP
# requires a password now that it is no longer on host networking.
iop_fdw_database_host: "127.0.0.1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The iop.md architecture diagram (mermaid) still draws Postgres under "Host System", outside the iop-core-network subgraph, while the prose now says Postgres is reached via postgresql:5432 on that network.

Should we update that as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's updated now

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do some places use 127.0.0.1 and others use postgresql ?

name: postgres_fdw
login_db: "{{ iop_fdw_database_name }}"
login_user: postgres
login_password: "{{ iop_fdw_login_password }}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will log the password value in the logs, especially when using -vvv. Should we use no_log: true or something similar (secrets?) to mask the value of the password?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added no_log property here

Comment thread src/vars/database.yml

iop_database_host: host.containers.internal
# Co-located Postgres joins iop-core-network; IOP reaches it by container name.
iop_database_host: postgresql

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion from AI:

Pre-existing (not introduced by this PR) architectural gap: iop_database_host in src/vars/database.yml is unconditionally set to postgresql (a container name only resolvable on iop-core-network), with no branching on database_mode. Combining --database-mode external with the iop feature was already questionable before this PR (nothing in features.yaml or obsah metadata blocks that combination), and this PR doesn't change that — just flagging it's still an open gap, not something to necessarily fix in this PR.

Comment thread src/vars/database.yml Outdated
password: "{{ iop_vulnerability_database_password }}"
feature: iop


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Comment thread src/roles/postgresql/tasks/main.yml
@Alleny244 Alleny244 changed the title Restrict PostgreSQL listen_addresses to localhost and IOP gateway Move co-located containers onto shared foreman-core-network Aug 11, 2026
@Alleny244
Alleny244 force-pushed the fix/postgresql-listen-localhost branch from 6f86341 to d65b220 Compare August 11, 2026 10:53
@Alleny244
Alleny244 force-pushed the fix/postgresql-listen-localhost branch 2 times, most recently from 5c5760a to 456b6e3 Compare August 11, 2026 13:16
@stejskalleos

Copy link
Copy Markdown
Contributor

Given the amount of surface area both PRs touch, it'd be good for a maintainer to pick one direction (or explicitly merge the best of both) before either goes further, rather than have both mature in parallel and conflict at merge time.

@ehelms @ekohl we need a decision on which way (PR) we want to go.

Comment thread src/roles/certificates/tasks/issue.yml Outdated
- name: Issue server certificate
when:
- (certificates_source != 'custom_server') or (certificates_hostname == 'localhost')
# localhost/candlepin are always issued (service-name TLS on the shared network).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why these changes are necessary for this PR. Can you please explain?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the services are now on the same network, they communicate using the container names instead of localhost. So we need to add those names to the certificate SANs as well, else TLS hostname verification will fail, right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this role need to know this though? Just like localhost, can candlepin not be specified in the list of hosts in the playbook to generate certificates for?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, it's updated now

@ekohl

ekohl commented Aug 12, 2026

Copy link
Copy Markdown
Member

I'd like to avoid introducing additional networks at this point. It's a major architectural change and currently I'm much more interested in actually having feature parity with our existing installation. That will allow us to deprecate the package-based installations, which in turn unlocks dropping Ruby 3.1 support, which in turn allows upgrading Ruby on Rails to at least Rails 7.2. That is needed to get CVE fixes.

At this point in the release cycle even this change is IMHO very risky. If we're going to introduce networks, I think we should do it properly and well thought out.

IMHO neither this nor #403 do what I would expect: have 1 network per service.

When I say service, I mean:

  • Candlepin
  • Foreman
  • Foreman Proxy
  • Pulp
  • IoP

But then there are more complexities, like do you run 1 instance of Valkey for both Foreman and Pulp or isolate them from each other? PostgreSQL is in a similar boat.

@pablomh

pablomh commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

On "1 network per service" — I think the tricky part is exactly the Valkey/Postgres question you raised.

If Foreman, Candlepin and Pulp all need the same Postgres, there are really only two ways to handle it: hook Postgres up to all three networks, or give each service its own Postgres.

The multi-homing option doesn't end up buying much isolation though — Postgres is reachable from all three networks either way, so "who can reach the DB" looks basically the same as just having one foreman-db network with those three on it. Same picture, just more bridges to draw it.

Separate Postgres/Valkey per service would give real isolation, but that feels like a much bigger change than what we're actually trying to fix (containers listening on 0.0.0.0).

That's the thinking behind going with networks per workflow rather than per named service: foreman-db/foreman-cache are the DB/cache-access boundary — one shared instance, one network, and membership is controlled per consumer. foreman-app covers Foreman/Candlepin/Pulp/httpd, since those already need to talk to each other directly regardless of how many networks exist. And foreman-proxy is split off on its own, since it's the one piece here talking to arbitrary remote hosts and probably shouldn't have a path to the database.

That proxy split feels like the boundary that matters most. The rest ends up as one network because those services are already tightly coupled, and splitting them further wouldn't really stop anything from reaching anything — just add more bridges for the same result.

@ekohl

ekohl commented Aug 13, 2026

Copy link
Copy Markdown
Member

And foreman-proxy is split off on its own, since it's the one piece here talking to arbitrary remote hosts and probably shouldn't have a path to the database.

#611 would disagree with you.

@ehelms

ehelms commented Aug 13, 2026

Copy link
Copy Markdown
Member

I'll give both PRs a re-read, but the thing I am missing is why or what value individual networks per service gives us.

Networks aren't for free, they will require our users to ensure that address space is available and the more introduce the more we potentially clash with any existing configuration they might have. We should be thoughtful about this trade-off when considering what additional networks bring us in value given most of our services run locally.

The biggest reason I pushed for a single, custom podman network is to allow easy container-to-container DNS name resolution as this is not available on the default podman network.

@ekohl

ekohl commented Aug 13, 2026

Copy link
Copy Markdown
Member

I'll give both PRs a re-read, but the thing I am missing is why or what value individual networks per service gives us.

My initial thought was that you would have a good isolation. However, given all the cross-service talk it's questionable how much benefit we would actually get.

Networks aren't for free, they will require our users to ensure that address space is available and the more introduce the more we potentially clash with any existing configuration they might have. We should be thoughtful about this trade-off when considering what additional networks bring us in value given most of our services run locally.

If you make the network IPv6-only then the address space is less of a concern. Also note that with systemd socket activation for Foreman and Pulp they don't have any ingress, only egress.

But sure, it's also additional complexity that can break.

Comment thread development/roles/foreman_development/defaults/main.yaml Outdated
Comment thread src/roles/candlepin/defaults/main.yml Outdated
Comment thread src/roles/foreman/defaults/main.yaml Outdated
Comment thread src/roles/foreman/defaults/main.yaml Outdated
Comment thread src/roles/foreman/defaults/main.yaml Outdated
Comment thread src/roles/foreman/tasks/main.yaml Outdated
Comment thread src/roles/foreman/templates/katello.yaml.j2 Outdated
iop_advisor_database_user: advisor_user
iop_advisor_database_password: "{{ undef(hint='Set a secure database password') }}"
iop_advisor_database_host: host.containers.internal
iop_advisor_database_host: "{{ iop_database_host | default('postgresql') }}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
iop_advisor_database_host: "{{ iop_database_host | default('postgresql') }}"
iop_advisor_database_host: postgresql

I think you get the pattern by now. While I get the temptation to use a variable, the whole point of defaults is to default to something that is meaningful. And not to be reliant on some variable to exist somewhere external.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah agreed, have updated all to use default postgresql

postgresql_networks:
- "{{ foreman_core_network_name | default('foreman-core-network') }}"
postgresql_publish:
- "127.0.0.1:5432:5432"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be published if everything is on the network that needs it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For host side consumers, they still require port publication right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What host side consumers are you expecting to exist?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly for deploy-dev workflow which runs forman on the host itself

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a doubt here, please correct me if I’m wrong, since the Ansible task community.postgresql.postgresql_user uses login_host: 127.0.0.1, doesn't PostgreSQL need to have its port published on the host for this task to work?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, I suppose that is the best we can do right now.

@ehelms

ehelms commented Aug 18, 2026

Copy link
Copy Markdown
Member

This raises the question of:

  • Do we need TLS for Candlepin?
  • Do we need TLS for the iop-gateway?

These are both services that are only internally communicated with by other services on the network aka the foreman container. We currently do not have TLS between any of the other services (e.g. iop-*, postgresql, redis).

@Alleny244

Copy link
Copy Markdown
Contributor Author

This raises the question of:

  • Do we need TLS for Candlepin?
  • Do we need TLS for the iop-gateway?

These are both services that are only internally communicated with by other services on the network aka the foreman container. We currently do not have TLS between any of the other services (e.g. iop-*, postgresql, redis).

TLS here is mainly because Candlepin is currently configured to use HTTPS, and the IOP gateway is used as a Smart Proxy, which Foreman expects to access over HTTPS right?. It’s not required by the shared network itself

@ehelms

ehelms commented Aug 18, 2026

Copy link
Copy Markdown
Member

TLS here is mainly because Candlepin is currently configured to use HTTPS, and the IOP gateway is used as a Smart Proxy, which Foreman expects to access over HTTPS right?. It’s not required by the shared network itself

Fair point on the IOP gateway.
My point with Candlepin is -- should it continue to be? I suppose we do not have to answer that now, but it's a valid question to consider. We do not, for example, lock down postgresql over TLS.

The dedicated network does offer some security enhancements.

@Alleny244

Copy link
Copy Markdown
Contributor Author

TLS here is mainly because Candlepin is currently configured to use HTTPS, and the IOP gateway is used as a Smart Proxy, which Foreman expects to access over HTTPS right?. It’s not required by the shared network itself

Fair point on the IOP gateway. My point with Candlepin is -- should it continue to be? I suppose we do not have to answer that now, but it's a valid question to consider. We do not, for example, lock down postgresql over TLS.

The dedicated network does offer some security enhancements.

yeah maybe we could leave TLS posure as a follow up?

@Alleny244
Alleny244 force-pushed the fix/postgresql-listen-localhost branch 3 times, most recently from 0a1546e to 0e55232 Compare August 20, 2026 10:33
@ehelms

ehelms commented Aug 20, 2026

Copy link
Copy Markdown
Member

Can you introduce a new document that lays out the architecture of the network? Take a look at #771 as I'd like it to go into that folder structure.

@Alleny244

Copy link
Copy Markdown
Contributor Author

Can you introduce a new document that lays out the architecture of the network? Take a look at #771 as I'd like it to go into that folder structure.

Sure I ll take a look into that and formulate one

@Alleny244
Alleny244 force-pushed the fix/postgresql-listen-localhost branch from 0e55232 to 4c367a1 Compare August 21, 2026 11:32
@Alleny244

Copy link
Copy Markdown
Contributor Author

One thing that I noticed was , TLS issue affects both Candlepin and IOP: their certs are issued for candlepin / iop-core-gateway, but deploy-dev Foreman connects via localhost. Currently we are using those hostnames instead of localhost so hostname verification succeeds for production. For the host Foreman, I guess we’d also need to add both names to /etc/hosts pointing to 127.0.0.1 for TLS

@Alleny244
Alleny244 force-pushed the fix/postgresql-listen-localhost branch from 4c367a1 to ea97817 Compare August 21, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants