Skip to content

Commit 8d6fdee

Browse files
committed
feat(auth): add idp reference contract scaffolding
Signed-off-by: Ryan S <267728323+ironcommit@users.noreply.github.com>
1 parent 3f25b9a commit 8d6fdee

67 files changed

Lines changed: 2603 additions & 7 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/actions/changes/action.yaml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ outputs:
4343
cpu-smoke:
4444
description: "'true' if CPU smoke image or Kubernetes smoke test inputs changed"
4545
value: ${{ steps.filter.outputs.deps == 'true' || steps.filter.outputs.docker == 'true' || steps.filter.outputs.docker-scripts == 'true' || steps.filter.outputs.helm == 'true' || steps.filter.outputs.openapi == 'true' || steps.filter.outputs.python-runtime == 'true' || steps.filter.outputs.web-studio == 'true' || steps.filter.outputs.k8s-smoke == 'true' }}
46+
auth-idp:
47+
description: "'true' if auth-idp tests or their containerized E2E harness inputs changed"
48+
value: ${{ steps.filter.outputs.auth-idp == 'true' }}
4649

4750
runs:
4851
using: "composite"
@@ -97,3 +100,7 @@ runs:
97100
- 'e2e/k8s/values/**'
98101
- 'e2e/test_jobs.py'
99102
- '.github/actions/free-disk-space/action.yaml'
103+
auth-idp:
104+
- 'tests/auth_idp/**'
105+
- 'e2e/**'
106+
- 'contrib/auth/authentik/**'

‎.github/workflows/ci.yaml‎

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ jobs:
4444
docker: ${{ steps.changes.outputs.docker }}
4545
helm: ${{ steps.changes.outputs.helm }}
4646
cpu-smoke: ${{ steps.changes.outputs.cpu-smoke }}
47+
auth-idp: ${{ steps.changes.outputs.auth-idp }}
4748
steps:
4849
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
4950
- uses: ./.github/actions/changes
@@ -101,7 +102,8 @@ jobs:
101102
if: >
102103
!cancelled() && (
103104
github.event_name == 'workflow_dispatch' ||
104-
needs.changes.outputs.cpu-smoke == 'true'
105+
needs.changes.outputs.cpu-smoke == 'true' ||
106+
needs.changes.outputs.auth-idp == 'true'
105107
)
106108
runs-on: ubuntu-latest
107109
timeout-minutes: 90
@@ -908,6 +910,59 @@ jobs:
908910
coverage.xml
909911
coverage.json
910912
913+
python-auth-idp-test:
914+
name: Python auth-idp tests
915+
needs: [changes, policy-wasm, build-cpu-smoke-images]
916+
if: >
917+
!cancelled() && (
918+
github.event_name == 'workflow_dispatch' ||
919+
needs.changes.outputs.cpu-smoke == 'true' ||
920+
needs.changes.outputs.auth-idp == 'true'
921+
) &&
922+
needs.build-cpu-smoke-images.result == 'success'
923+
runs-on: ubuntu-latest
924+
permissions:
925+
contents: read
926+
packages: read
927+
steps:
928+
- name: Checkout code
929+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
930+
- name: Free disk space
931+
uses: ./.github/actions/free-disk-space
932+
- name: Download policy WASM
933+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
934+
with:
935+
name: policy-wasm
936+
path: services/core/auth/src/nmp/core/auth/assets
937+
- name: Install uv
938+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
939+
with:
940+
python-version: "3.11"
941+
enable-cache: true
942+
cache-dependency-glob: uv.lock
943+
- name: Log in to GHCR
944+
if: needs.build-cpu-smoke-images.outputs.publish_images == 'true'
945+
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
946+
with:
947+
registry: ghcr.io
948+
username: ${{ github.actor }}
949+
password: ${{ github.token }}
950+
- name: Run auth-idp tests
951+
run: make test-auth-idp
952+
env:
953+
_TYPER_FORCE_DISABLE_TERMINAL: "1"
954+
E2E_SERVICES_LOG_DIR: ${{ runner.temp }}/e2e-services-logs
955+
IMAGE_REGISTRY: ${{ needs.build-cpu-smoke-images.outputs.image_registry }}
956+
BAKE_TAG: ${{ needs.build-cpu-smoke-images.outputs.image_tag }}
957+
- name: Upload test artifacts
958+
if: always()
959+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
960+
with:
961+
name: python-auth-idp-test-results
962+
retention-days: 30
963+
path: |
964+
${{ runner.temp }}/e2e-services-logs/
965+
911966
# Build wheels for all packages × python versions. Downstream jobs
912967
# (wheel-test, python-e2e-test) download these artifacts instead
913968
# of rebuilding.

‎Makefile‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ docs-watch: ## Start Fern docs dev plus a repo-level watcher for docs/** changes
122122
docs-check: ## Validate the Fern docs (fern check + validate-mdx + gated-link check)
123123
cd docs/fern && npm run check
124124

125+
.PHONY: test-auth-idp
126+
test-auth-idp: ## Run the auth-idp test suite
127+
uv run --frozen pytest tests/auth_idp -v
128+
129+
.PHONY: test-auth-idp-matrix
130+
test-auth-idp-matrix: test-auth-idp ## Backward-compatible alias for auth-idp suite
131+
125132
.PHONY: docs-check-python-snippets
126133
docs-check-python-snippets: ## Syntax-check and type-check Python snippets in one doc (DOCS_PATH=...)
127134
@if [ -z "$(strip $(DOCS_PATH))" ]; then echo "Usage: make docs-check-python-snippets DOCS_PATH=docs/customizer/tutorials/import-hf-model.mdx" >&2; exit 2; fi

‎conftest.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import pytest
1919
from nmp.testing.pytest_outcomes import pytest_skip as skip_test
2020

21+
from tests.auth_idp.xdist import append_xdist_group_suffix
2122
from tests.discovery_exclusions import TEST_DISCOVERY_EXCLUSIONS
2223

2324
# Set test environment variables BEFORE any imports
@@ -211,6 +212,7 @@ def pytest_collection_modifyitems(config, items):
211212
category_markers = {
212213
"unit",
213214
"e2e",
215+
"auth_idp",
214216
"smoke_gpu_tasks",
215217
"smoke_nmp_automodel_tasks",
216218
"smoke_nmp_automodel_training",
@@ -247,6 +249,13 @@ def pytest_collection_modifyitems(config, items):
247249
if not marker_names.intersection(category_markers):
248250
item.add_marker(pytest.mark.unit)
249251

252+
if getattr(config.option, "numprocesses", None) or getattr(config, "workerinput", None) is not None:
253+
group_names = set()
254+
for mark in item.iter_markers("xdist_group"):
255+
name = mark.args[0] if mark.args else mark.kwargs.get("name", "default")
256+
group_names.add(str(name))
257+
item._nodeid = append_xdist_group_suffix(item.nodeid, group_names)
258+
250259

251260
# ============================================================================
252261
# Pytest command-line options
@@ -295,6 +304,7 @@ def pytest_runtest_setup(item):
295304
skip_test("Skipping container-only test (requires NMP_BASE_URL)")
296305

297306

307+
from xdist.scheduler.loadgroup import LoadGroupScheduling # noqa: E402
298308
from xdist.scheduler.loadscope import LoadScopeScheduling # noqa: E402
299309

300310
# Temporary workaround for https://github.com/pytest-dev/pytest-xdist/issues/1189
@@ -310,3 +320,7 @@ def _patched_reschedule(self, node):
310320

311321

312322
LoadScopeScheduling._reschedule = _patched_reschedule # type: ignore[invalid-assignment]
323+
324+
325+
def pytest_xdist_make_scheduler(config, log):
326+
return LoadGroupScheduling(config, log)

‎contrib/auth/README.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Identity Provider References
2+
3+
This directory contains NeMo Platform identity-provider reference bundles.
4+
5+
Each provider bundle defines one contract for local validation and production
6+
adaptation:
7+
8+
- expose OIDC discovery metadata
9+
- include a gateway layer that strips inbound `X-NMP-Principal-*` headers
10+
- define one human identity and one machine identity for shared auth testing
11+
- treat external machine identities as ordinary OIDC principals authorized by
12+
group binding, not as internal `service:*` principals
13+
- document provider-specific setup in a local `README.md`
14+
15+
Open-source providers with `mode: compose-ci` are intended for the shared auth
16+
matrix. Reference-only providers stay documented and manifest-driven but are
17+
excluded from the local Compose-backed matrix.

‎contrib/auth/authelia/README.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Authelia Reference
2+
3+
## Local reference
4+
5+
1. Start the local stack with `docker compose up -d`.
6+
2. Wait for OIDC discovery to return healthy.
7+
3. Run the shared auth-idp matrix with `-k authelia`.
8+
9+
## Production adaptation
10+
11+
- configure NeMo `auth.oidc` with the Authelia issuer
12+
- enforce gateway-level stripping for trusted principal headers
13+
- map NeMo authorization to Authelia groups
14+
- keep machine callers on standard OIDC subject identifiers
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
auth:
2+
enabled: true
3+
oidc:
4+
enabled: true
5+
issuer: http://127.0.0.1:48081
6+
client_id: nemo-local
7+
subject_claim: sub
8+
groups_claim: groups
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
gateway:
3+
image: envoyproxy/envoy:v1.33-latest
4+
command: ["envoy", "-c", "/etc/envoy/envoy.yaml"]
5+
volumes:
6+
- ./gateway/envoy.yaml:/etc/envoy/envoy.yaml:ro
7+
ports:
8+
- "48080:8080"
9+
10+
authelia:
11+
image: authelia/authelia:4.39
12+
command: ["authelia", "serve", "--config", "/config/configuration.yml"]
13+
ports:
14+
- "48081:9091"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
static_resources:
2+
listeners:
3+
- name: listener_0
4+
address:
5+
socket_address:
6+
address: 0.0.0.0
7+
port_value: 8080
8+
filter_chains:
9+
- filters:
10+
- name: envoy.filters.network.http_connection_manager
11+
typed_config:
12+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
13+
stat_prefix: ingress_http
14+
route_config:
15+
name: local_route
16+
virtual_hosts:
17+
- name: nemo
18+
domains: ["*"]
19+
request_headers_to_remove:
20+
- x-nmp-principal-id
21+
- x-nmp-principal-email
22+
- x-nmp-principal-groups
23+
- x-nmp-principal-on-behalf-of
24+
- x-nmp-principal-on-behalf-of-email
25+
- x-nmp-principal-on-behalf-of-groups
26+
routes:
27+
- match:
28+
prefix: "/"
29+
route:
30+
cluster: nemo
31+
http_filters:
32+
- name: envoy.filters.http.router
33+
clusters:
34+
- name: nemo
35+
connect_timeout: 5s
36+
type: LOGICAL_DNS
37+
load_assignment:
38+
cluster_name: nemo
39+
endpoints:
40+
- lb_endpoints:
41+
- endpoint:
42+
address:
43+
socket_address:
44+
address: nemo
45+
port_value: 8080
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
provider: authelia
2+
mode: compose-ci
3+
compose_file: docker-compose.yml
4+
gateway_base_url: http://127.0.0.1:48080
5+
issuer_url: http://127.0.0.1:48081
6+
discovery_url: http://127.0.0.1:48081/.well-known/openid-configuration
7+
nemo_config: config/nemo-auth.yaml
8+
principal_contract:
9+
subject_claim: sub
10+
groups_claim: groups
11+
external_machine_principals_use_service_prefix: false
12+
internal_service_prefix_reserved: "service:"
13+
human_identity:
14+
username: demo-user
15+
expected_email: demo-user@example.com
16+
machine_identity:
17+
principal_id: authelia-machine-subject
18+
expected_groups:
19+
- nemo-editors
20+
healthchecks:
21+
- kind: http
22+
url: http://127.0.0.1:48081/.well-known/openid-configuration
23+
startup_timeouts:
24+
healthchecks_seconds: 120
25+
gateway_seconds: 30
26+
token_endpoint_seconds: 60

0 commit comments

Comments
 (0)