Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .github/actions/build-policy-wasm/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ description: >
runs:
using: composite
steps:
- name: Install OPA
shell: bash
env:
OPA_VERSION: v1.8.0
run: |
curl -fsSL -o /usr/local/bin/opa \
"https://github.com/open-policy-agent/opa/releases/download/${OPA_VERSION}/opa_linux_amd64_static"
chmod +x /usr/local/bin/opa
- name: Build policy WASM
shell: bash
run: ./script/build_policy_wasm.sh
2 changes: 2 additions & 0 deletions docs/auth/authorization/policy-engine.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The embedded PDP uses a WASM-compiled Rego policy engine built into the auth ser

### How It Works

- Rego policy files are compiled into the `policy.wasm` artifact loaded by the auth service
- Policy data (role bindings, workspace visibility, endpoint permissions) is served by the auth service
- Data is refreshed on a configurable interval (default: 30 seconds)

Expand All @@ -34,6 +35,7 @@ auth:
enabled: true
policy_decision_point_provider: "embedded"
policy_decision_point_base_url: "http://auth:8000"
embedded_pdp_auto_build_wasm: true
policy_data_refresh_interval: 30 # seconds
```

Expand Down
24 changes: 24 additions & 0 deletions docs/auth/deployment/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ NMP_AUTH_ENABLED=true
NMP_AUTH_POLICY_DECISION_POINT_BASE_URL=http://auth:8000
NMP_AUTH_POLICY_DECISION_POINT_PROVIDER=embedded
NMP_AUTH_ADMIN_EMAIL=admin@example.com
NMP_AUTH_EMBEDDED_PDP_AUTO_BUILD_WASM=true
```

Nested keys (e.g., OIDC) use double underscore: `NMP_AUTH_OIDC__ISSUER`, `NMP_AUTH_OIDC__CLIENT_ID`.
Expand All @@ -93,16 +94,39 @@ auth:
enabled: true
policy_decision_point_provider: embedded
policy_decision_point_base_url: "http://localhost:8080"
embedded_pdp_auto_build_wasm: true
admin_email: "admin@example.com"
```

When running from a source checkout, embedded PDP startup can automatically build
a missing `policy.wasm` with the pinned OPA version used by `make build-policy`.
Packaged deployments should include `policy.wasm` at image or wheel build time;
set `embedded_pdp_auto_build_wasm: false` to fail fast if that artifact is missing.
After changing policy source files, run `make build-policy` to refresh the
artifact.

In offline development environments, provide the pinned OPA binary explicitly:

```bash
OPA_BIN=/path/to/opa_linux_amd64_static uv run nemo services run --host 127.0.0.1 --port 8080
```

Or seed the local cache used by `script/build_policy_wasm.sh`:

```bash
mkdir -p .cache/opa/v1.8.0
cp /path/to/opa_linux_amd64_static .cache/opa/v1.8.0/opa_linux_amd64_static
chmod +x .cache/opa/v1.8.0/opa_linux_amd64_static
```

### Production with embedded PDP

```yaml
auth:
enabled: true
policy_decision_point_base_url: "http://auth:8000"
policy_decision_point_provider: embedded
embedded_pdp_auto_build_wasm: false
policy_data_refresh_interval: 30
admin_email: "platform-admin@company.com"
oidc:
Expand Down
2 changes: 2 additions & 0 deletions docs/set-up/config-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ auth:
propagation_poll_interval_seconds: 1.0
# Allow unsigned JWTs (`alg=none`) for local development/testing. Disabled by default and should not be enabled in production. | default: False
allow_unsigned_jwt: false
# When auth is enabled with the embedded PDP and policy.wasm is missing, build it automatically from a local NeMo Platform source checkout. Packaged deployments should include policy.wasm at build time and can disable this for fail-fast startup. | default: True
embedded_pdp_auto_build_wasm: true
# OIDC configuration for native token validation.
oidc:
# Enable native OIDC token validation. | default: False
Expand Down
9 changes: 9 additions & 0 deletions packages/nmp_common/src/nmp/common/config/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,15 @@ class AuthConfig(create_service_config_class("auth")):
),
)

embedded_pdp_auto_build_wasm: bool = Field(
default=True,
description=(
"When auth is enabled with the embedded PDP and policy.wasm is missing, "
"build it automatically from a local NeMo Platform source checkout. Packaged deployments "
"should include policy.wasm at build time and can disable this for fail-fast startup."
),
)

oidc: OIDCConfig = Field(
default_factory=OIDCConfig,
description="OIDC configuration for native token validation.",
Expand Down
18 changes: 18 additions & 0 deletions packages/nmp_platform_runner/src/nmp/platform_runner/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ async def dispatch(self, request: Request, call_next) -> Response:
return await call_next(request)


def preflight_embedded_auth_policy_wasm(auth_config) -> None:
"""Ensure local embedded auth PDP has a loadable policy.wasm before serving traffic."""
if not auth_config.enabled or auth_config.policy_decision_point_provider != "embedded":
return

try:
from nmp.core.auth.app.embedded_pdp.policy_wasm import ensure_embedded_policy_wasm
except ImportError as exc:
raise RuntimeError(
"Auth is enabled with the embedded PDP, but the nmp-auth package is not installed. "
"Install nmp-auth or set auth.policy_decision_point_provider='opa'."
) from exc
Comment thread
mckornfield marked this conversation as resolved.

ensure_embedded_policy_wasm(auto_build=getattr(auth_config, "embedded_pdp_auto_build_wasm", True))


def create_platform_openapi_app() -> FastAPI:
"""Create the platform app used for aggregate OpenAPI generation."""
services = []
Expand Down Expand Up @@ -196,13 +212,15 @@ async def root_handler() -> Response:

def run_server(services: list[Service] | None = None, host: str = "0.0.0.0", port: int = 8080) -> None:
"""Run the platform API server."""
preflight_embedded_auth_policy_wasm(get_auth_config())
app = create_app(services or [])
setup_fastapi_instrumentations(app)
uvicorn.run(app, host=host, port=port, log_config=None)


def run_server_with_reload(app_factory: str, host: str = "0.0.0.0", port: int = 8080) -> None:
"""Run the platform API server with uvicorn reload enabled."""
preflight_embedded_auth_policy_wasm(get_auth_config())
reload_dirs = [
"packages/nmp_platform/src",
"services/core",
Expand Down
55 changes: 55 additions & 0 deletions packages/nmp_platform_runner/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,61 @@ def fake_create_app(services, controller_run_funcs=None, _http_client=None):
assert captured["controller_run_funcs"] == {"agents-deployment": plugin_controller}


def test_embedded_auth_preflight_invokes_policy_wasm_helper(monkeypatch):
calls: list[bool] = []
auth_cfg = AuthConfig(
enabled=True,
policy_decision_point_provider="embedded",
embedded_pdp_auto_build_wasm=False,
)

from nmp.core.auth.app.embedded_pdp import policy_wasm

monkeypatch.setattr(policy_wasm, "ensure_embedded_policy_wasm", lambda *, auto_build: calls.append(auto_build))

server.preflight_embedded_auth_policy_wasm(auth_cfg)

assert calls == [False]


@pytest.mark.parametrize(
"auth_cfg",
[
AuthConfig(enabled=False, policy_decision_point_provider="embedded"),
AuthConfig(enabled=True, policy_decision_point_provider="opa"),
],
)
def test_embedded_auth_preflight_skips_when_not_needed(auth_cfg, monkeypatch):
calls: list[bool] = []

from nmp.core.auth.app.embedded_pdp import policy_wasm

monkeypatch.setattr(policy_wasm, "ensure_embedded_policy_wasm", lambda *, auto_build: calls.append(auto_build))

server.preflight_embedded_auth_policy_wasm(auth_cfg)

assert calls == []


def test_run_server_runs_embedded_auth_preflight():
auth_cfg = _make_auth_config(enabled=True)
calls: list[AuthConfig] = []
with (
patch("nmp.platform_runner.server.get_auth_config", return_value=auth_cfg),
patch(
"nmp.platform_runner.server.preflight_embedded_auth_policy_wasm", side_effect=lambda cfg: calls.append(cfg)
),
patch("nmp.platform_runner.server.create_app", return_value=FastAPI()) as create_app,
patch("nmp.platform_runner.server.setup_fastapi_instrumentations"),
patch("nmp.platform_runner.server.uvicorn.run") as uvicorn_run,
):
server.run_server(services=[], host="127.0.0.1", port=9999)

assert calls == [auth_cfg]
create_app.assert_called_once_with([])
uvicorn_run.assert_called_once()


def test_create_default_app_raises_for_unknown_service_from_env(monkeypatch):
monkeypatch.setattr(server, "_obs_initialized", True)
monkeypatch.setenv("NMP_SERVICES", "missing-service")
Expand Down
Loading
Loading