Commit 6816ca08 authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

fix(fm): serve /internal only on its own port, never on the EWBI one

Partners reach the EWBI on 8082, and a deployment that publishes that port
also published the unauthenticated /internal/* API. FM now listens on two
ports (FM_PORT 8082, FM_INTERNAL_PORT 8083) and answers /internal/* on any
other port with the same 404 as an unknown path.
parent 8b791159
Loading
Loading
Loading
Loading
Loading
+3 −2
Changes for Dockerfile: 3 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -32,5 +32,6 @@ FROM python:3.12-slim
COPY --from=build /install /usr/local
RUN useradd --create-home --uid 10001 fm
USER fm
EXPOSE 8082
ENTRYPOINT ["uvicorn", "federation_manager.main:app", "--host", "0.0.0.0", "--port", "8082"]
# 8082 is the EWBI partners reach; 8083 serves /internal/* and must stay inside the cluster.
EXPOSE 8082 8083
ENTRYPOINT ["python", "-m", "federation_manager.server"]
+3 −1
Changes for README.md: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -57,6 +57,8 @@ docker compose up --build
```

That starts Federation Manager on **:8082**, plus PostgreSQL, NATS JetStream and Keycloak.
Partners reach the EWBI on 8082. The internal API (`/internal/*`) answers only on 8083,
so publishing 8082 never publishes it (`FM_PORT`, `FM_INTERNAL_PORT`).

```bash
curl http://localhost:8082/healthz
@@ -68,7 +70,7 @@ curl http://localhost:8082/healthz
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
docker compose -f docker-compose.dev.yaml up -d postgres nats keycloak
uvicorn federation_manager.main:app --reload --port 8082
python -m federation_manager.server   # EWBI on :8082, /internal/* on :8083
```

### Tests
+2 −0
Changes for docker-compose.yaml: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -7,6 +7,8 @@ services:
    restart: unless-stopped
    ports:
      - "8082:8082"
      # /internal/*, kept to this machine
      - "127.0.0.1:8083:8083"
    environment:
      FM_POSTGRES_URL: postgresql+asyncpg://fm:fm@postgres:5432/fm_db
      FM_NATS_URL: nats://nats:4222
+13 −1
Changes for src/federation_manager/core/config.py: 13 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@ import base64
import binascii
from functools import lru_cache

from pydantic import SecretStr, field_validator
from pydantic import SecretStr, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


@@ -15,6 +15,12 @@ class Settings(BaseSettings):
        hide_input_in_errors=True,
    )

    # Partners reach the EWBI on `port`. `/internal/*` answers only on `internal_port`,
    # so publishing `port` to the outside never publishes the internal API with it.
    host: str = "0.0.0.0"
    port: int = 8082
    internal_port: int = 8083

    postgres_url: str = "postgresql+asyncpg://fm:fm@localhost:5433/fm_db"
    postgres_echo: bool = False
    log_level: str = "INFO"
@@ -57,6 +63,12 @@ class Settings(BaseSettings):
            _decode_key(value.get_secret_value())
        return value

    @model_validator(mode="after")
    def _internal_port_is_separate(self) -> "Settings":
        if self.internal_port == self.port:
            raise ValueError("FM_INTERNAL_PORT must differ from FM_PORT")
        return self

    @property
    def partner_secret_key_bytes(self) -> bytes | None:
        if self.partner_secret_key is None:
+2 −0
Changes for src/federation_manager/main.py: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ from federation_manager.core.config import Settings, get_settings
from federation_manager.core.logging import configure_logging, get_logger
from federation_manager.dependencies import get_local_operator
from federation_manager.domain.ports import EwbiClientPort
from federation_manager.middleware.internal_port import InternalPortOnlyMiddleware
from federation_manager.middleware.topology_hiding import TopologyHidingMiddleware


@@ -220,6 +221,7 @@ def create_app(lifespan: Lifespan[FastAPI] | None = None) -> FastAPI:
        lifespan=lifespan or default_lifespan,
    )
    app.add_middleware(TopologyHidingMiddleware)
    app.add_middleware(InternalPortOnlyMiddleware, internal_port=get_settings().internal_port)
    register_exception_handlers(app)
    app.openapi = _openapi_with_problem_schemas(app)  # type: ignore[method-assign]
    app.include_router(health_router)
Loading