Skip to content

Commit 22d0dae

Browse files
authored
Require explicit flag for gateway auth enforcement (#468)
1 parent 2ab14ae commit 22d0dae

2 files changed

Lines changed: 32 additions & 8 deletions

File tree

projects/policyengine-api-simulation/src/modal/gateway/auth.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
1313
- ``GATEWAY_AUTH_ISSUER`` - Auth0 issuer URL (must end with ``/``)
1414
- ``GATEWAY_AUTH_AUDIENCE`` - Auth0 API identifier the gateway accepts
15-
- ``GATEWAY_AUTH_REQUIRED`` - if truthy, missing issuer/audience is a 503
15+
- ``GATEWAY_AUTH_REQUIRED`` - if truthy, bearer JWT auth is enforced
1616
1717
For local development and unit tests the dependency can be bypassed by
1818
setting ``GATEWAY_AUTH_DISABLED=1``. This bypass is hard-gated by
@@ -21,8 +21,8 @@
2121
missing or looks like production, and otherwise requires an explicit
2222
``GATEWAY_AUTH_DISABLED_ACK=I_UNDERSTAND_THIS_IS_DEV`` acknowledgement so
2323
the bypass cannot be activated by a single stray env var. The gateway
24-
also returns ``503`` to callers if auth is required but the issuer/audience
25-
configuration is missing, or if only one of issuer/audience is present.
24+
also returns ``503`` to callers if only one of issuer/audience is present, or
25+
if auth is required but issuer/audience are missing.
2626
"""
2727

2828
from __future__ import annotations
@@ -193,18 +193,29 @@ def require_auth(
193193
missing or invalid token produces a 403 (matching the underlying
194194
decoder's contract).
195195
196-
If issuer/audience env configuration is absent, the dependency preserves
197-
the legacy public gateway behavior unless ``GATEWAY_AUTH_REQUIRED`` is
198-
truthy. Partial auth configuration always returns 503 because it indicates
199-
an operator intended to enable auth but shipped an incomplete secret.
196+
The gateway preserves its legacy public behavior unless
197+
``GATEWAY_AUTH_REQUIRED`` is truthy. Issuer/audience may be staged in the
198+
Modal secret ahead of enforcement; setting those values alone must not
199+
silently make the gateway private. Partial auth configuration always
200+
returns 503 because it indicates an incomplete secret.
200201
"""
201202

202203
if _auth_disabled():
203204
return None
204205

205206
issuer = os.environ.get(GATEWAY_AUTH_ISSUER_ENV)
206207
audience = os.environ.get(GATEWAY_AUTH_AUDIENCE_ENV)
207-
if not issuer and not audience and not _auth_required():
208+
if bool(issuer) != bool(audience):
209+
logger.error(
210+
"Gateway auth partially configured: issuer_present=%s audience_present=%s",
211+
bool(issuer),
212+
bool(audience),
213+
)
214+
raise HTTPException(
215+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
216+
detail="Gateway authentication is not configured.",
217+
)
218+
if not _auth_required():
208219
return None
209220

210221
try:

projects/policyengine-api-simulation/tests/gateway/test_auth.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def __call__(self, token):
4848

4949
monkeypatch.setattr(auth_module, "_get_decoder", lambda: FailingDecoder())
5050
monkeypatch.delenv(auth_module.GATEWAY_AUTH_DISABLED_ENV, raising=False)
51+
monkeypatch.setenv(auth_module.GATEWAY_AUTH_REQUIRED_ENV, "1")
5152
monkeypatch.setenv(auth_module.GATEWAY_AUTH_ISSUER_ENV, "https://issuer.example/")
5253
monkeypatch.setenv(auth_module.GATEWAY_AUTH_AUDIENCE_ENV, "aud")
5354

@@ -84,6 +85,17 @@ def test__given_auth_not_configured_and_not_required__then_dependency_allows(
8485
assert auth_module.require_auth(token=None) is None
8586

8687

88+
def test__given_auth_configured_but_not_required__then_dependency_allows(
89+
monkeypatch,
90+
):
91+
monkeypatch.delenv(auth_module.GATEWAY_AUTH_DISABLED_ENV, raising=False)
92+
monkeypatch.delenv(auth_module.GATEWAY_AUTH_REQUIRED_ENV, raising=False)
93+
monkeypatch.setenv(auth_module.GATEWAY_AUTH_ISSUER_ENV, "https://issuer.example/")
94+
monkeypatch.setenv(auth_module.GATEWAY_AUTH_AUDIENCE_ENV, "aud")
95+
96+
assert auth_module.require_auth(token=None) is None
97+
98+
8799
def test__given_auth_required_and_misconfigured__then_dependency_raises_503(
88100
monkeypatch,
89101
):
@@ -169,6 +181,7 @@ def test__given_repeated_requests__then_decoder_not_reinstantiated(monkeypatch):
169181
from policyengine_fastapi.auth import jwt_decoder as jwt_decoder_module
170182

171183
monkeypatch.delenv(auth_module.GATEWAY_AUTH_DISABLED_ENV, raising=False)
184+
monkeypatch.setenv(auth_module.GATEWAY_AUTH_REQUIRED_ENV, "1")
172185
monkeypatch.setenv(auth_module.GATEWAY_AUTH_ISSUER_ENV, "https://issuer.example/")
173186
monkeypatch.setenv(auth_module.GATEWAY_AUTH_AUDIENCE_ENV, "aud-repeat")
174187

0 commit comments

Comments
 (0)