Skip to content

[Security/Deployment] Bind local Compose published ports to loopback by default #180

Description

@seonghobae

Buyer / developer-security gap

Protected develop@622e5e6c3d534f230c390f10e3832efadfc01825 correctly labels docker-compose.yml as the local stack, but every published port currently uses Docker's all-interface host binding unless the operator adds an external firewall rule:

  • PostgreSQL 5432:5432;
  • replica PostgreSQL 5433:5432;
  • ZooKeeper 2181:2181;
  • Kafka 29092:29092 with plaintext listener;
  • Zipkin 9412:9412;
  • Eureka 8761:8761;
  • ETL 8000:8000;
  • CDC 8001:8001;
  • gateway 8080:8080.

The Compose network already provides service-to-service connectivity by service name; host publication is only for developer/operator access. A local-development profile therefore does not need to make unauthenticated/plaintext infrastructure ports reachable from every host interface by default.

This is not a claim that loopback binding makes the stack production-secure. Kafka/ZooKeeper/PostgreSQL/service authentication/TLS and production deployment profiles remain separate architecture controls. The goal here is a safer local default and an explicit opt-in boundary for remote exposure.

RCA

  • Immediate cause: every Compose ports: entry omits a host IP, so Docker publishes on all host addresses by default.
  • Technical root cause: local service-network reachability and host/LAN exposure are represented by the same terse port declarations with no repository contract distinguishing them.
  • Control failure: current tests cover Zipkin port consistency ([Operability] Restore bundled Zipkin tracing transport to the standard collector port #166/fix(ops): restore bundled Zipkin tracing transport #167) but do not assert the host-exposure policy for local development.
  • Blast radius: developer laptops, shared build hosts, and incorrectly firewalled test VMs can expose databases, Kafka/ZooKeeper, service discovery, and application ports beyond localhost.

Remediation options / feasibility

  1. Preferred — loopback-bind the local profile. Change published mappings to 127.0.0.1:<host>:<container> (preserving fix(ops): restore bundled Zipkin tracing transport #167's eventual Zipkin internal/host distinction) and add a deterministic Compose contract test. Internal service-name networking remains unchanged; local browsers/CLI still work.
  2. Acceptable explicit remote-development profile: if remote-host access is genuinely required, put non-loopback publication behind an explicit opt-in override/profile with a warning and separate authentication/network requirements rather than making it default.
  3. Reject: call the current all-interface publication safe because the file says “local”; rely on undocumented host firewalls; or add TLS/secrets piecemeal solely to justify retaining broad default publication.

Current mutation classification

defer_until_trigger: active PR #167 currently owns docker-compose.yml for the Zipkin transport repair. Do not open a competing Compose source branch until #167 integrates/closes or its writer hands off a stable exact parent. This issue remains independently executable immediately after that trigger.

Acceptance

  • fail-first test proves protected local Compose exposes published ports on all interfaces today;
  • default published ports bind only to loopback or an equivalently explicit local-only host address;
  • Docker-internal service connectivity remains by service/network name and does not depend on host publication;
  • standalone ETL/CDC/gateway developer access from the same machine still works;
  • any remote-development exposure is explicit opt-in and documented as a different risk profile;
  • README/Architecture/Operability distinguish local-dev publication from production deployment/security claims;
  • no new passwords/certificates/secrets are invented by this slice;
  • [Product/Security Gap] Replace direct ETL HTTP Basic trust boundary #161 direct ETL authentication remains a separate trust-boundary requirement even when host publication is loopback-only;
  • exact current-head CI/security/non-vacuous coverage/review evidence is regenerated after implementation.

Relationship: #166/#167 own Zipkin port correctness; #161 owns ETL authentication; #165 owns release/provenance; #159 owns canonical deployment/security documentation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: apiAPI, protocol, event, or external contractarea: authAuthentication, authorization, identity, or tenant isolationarea: ci-cdCI, GitHub Actions, checks, release, or supply chainarea: securitySecurity boundary, hardening, or vulnerability preventionbugSomething isn't workingpriority: mediumNormal-priority or P2 workstatus: triagedOpen issue has an organization taxonomy assignmenttype: featureNew or expanded product capability

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions