Conversation
<!--- Provide a general summary of your changes in the Title above --> ## Description <!--- Describe your changes in detail --> This PR fixes two related bugs in the `/v1/models` endpoint: ### Bug 1: Empty Results ([RHOAIENG-52646](https://issues.redhat.com/browse/RHOAIENG-52646)) **Problem:** `/v1/models` returned empty list `{"data": []}` even when MaaSModelRefs existed and users had proper authorization. **Root Cause:** The maas-api searched for MaaSModelRefs only in its own namespace (`opendatahub`), but after PR #483, all MaaSModelRefs are deployed in the `llm` namespace (where models live). The informer watches all namespaces, but the code filtered results to only include models from a single namespace. **Solution:** Remove namespace filtering entirely. Since models can be deployed in any namespace, the `/v1/models` endpoint now returns models from all namespaces that are accessible to the user. **Changes:** - Remove `maasModelNamespace` parameter from `ModelsHandler` - Remove `namespace` parameter from `MaaSModelRefLister` interface - Remove namespace filtering logic in `cluster_config.go` - Update unit tests to match new interface ### Bug 2: Null Data ([RHOAIENG-52505](https://issues.redhat.com/browse/RHOAIENG-52505)) **Problem:** `/v1/models` returned `{"data": null}` instead of `{"data": []}` when users had no accessible models. **Root Cause:** Two locations initialized model slices using `var modelList []Model` (nil slice) instead of `modelList := []Model{}` (empty slice). In Go, nil slices marshal to JSON `null` while empty slices marshal to `[]`. **Solution:** Initialize model slices to empty slices in both locations: - `handlers/models.go`: Initialize `modelList` at declaration - `models/discovery.go`: Initialize `out` in `FilterModelsByAccess` **Changes:** - `handlers/models.go:159`: `modelList := []models.Model{}` - `models/discovery.go:75`: `out := []Model{}` ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> **Unit Tests:** ```bash cd maas-api go test ./internal/handlers/... -v go test ./internal/models/... -v All existing tests pass with updated interfaces. E2E Tests: Using new tests from #509 cd test/e2e ./scripts/prow_run_smoke_test.sh Results: - test_single_subscription_auto_select: ✅ XPASS (now returns models from all namespaces) - test_explicit_subscription_header: ✅ XPASS (now returns models from all namespaces) - test_empty_model_list: ✅ XPASS (now returns [] instead of null) - All other /v1/models tests: ✅ PASS Manual Testing: 1. Deployed fix to test cluster 2. Created test service account with no model access 3. Verified /v1/models returns {"object": "list", "data": []} (not null) 4. Created service account with model access in llm namespace 5. Verified /v1/models returns models from llm namespace (not empty) Environment: - OpenShift cluster with MaaS components deployed - maas-controller watching models-as-a-service namespace - Models deployed in llm namespace (post PR #483) - Kuadrant/Authorino for authorization - PostgreSQL backend for API key storage ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Bug Fixes** * Fixed JSON responses returning null for empty model lists; now correctly returns empty arrays. * **Refactor** * Simplified model listing to retrieve models across all namespaces instead of single namespace filtering. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…controller (#454) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.39.0 to 0.45.0. <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/golang/crypto/commit/4e0068c0098be10d7025c99ab7c50ce454c1f0f9"><code>4e0068c</code></a> go.mod: update golang.org/x dependencies</li> <li><a href="https://github.com/golang/crypto/commit/e79546e28b85ea53dd37afe1c4102746ef553b9c"><code>e79546e</code></a> ssh: curb GSSAPI DoS risk by limiting number of specified OIDs</li> <li><a href="https://github.com/golang/crypto/commit/f91f7a7c31bf90b39c1de895ad116a2bacc88748"><code>f91f7a7</code></a> ssh/agent: prevent panic on malformed constraint</li> <li><a href="https://github.com/golang/crypto/commit/2df4153a0311bdfea44376e0eb6ef2faefb0275b"><code>2df4153</code></a> acme/autocert: let automatic renewal work with short lifetime certs</li> <li><a href="https://github.com/golang/crypto/commit/bcf6a849efcf4702fa5172cb0998b46c3da1e989"><code>bcf6a84</code></a> acme: pass context to request</li> <li><a href="https://github.com/golang/crypto/commit/b4f2b62076abeee4e43fb59544dac565715fbf1e"><code>b4f2b62</code></a> ssh: fix error message on unsupported cipher</li> <li><a href="https://github.com/golang/crypto/commit/79ec3a51fcc7fbd2691d56155d578225ccc542e2"><code>79ec3a5</code></a> ssh: allow to bind to a hostname in remote forwarding</li> <li><a href="https://github.com/golang/crypto/commit/122a78f140d9d3303ed3261bc374bbbca149140f"><code>122a78f</code></a> go.mod: update golang.org/x dependencies</li> <li><a href="https://github.com/golang/crypto/commit/c0531f9c34514ad5c5551e2d6ce569ca673a8afd"><code>c0531f9</code></a> all: eliminate vet diagnostics</li> <li><a href="https://github.com/golang/crypto/commit/0997000b45e3a40598272081bcad03ffd21b8adb"><code>0997000</code></a> all: fix some comments</li> <li>Additional commits viewable in <a href="https://github.com/golang/crypto/compare/v0.39.0...v0.45.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/opendatahub-io/models-as-a-service/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…in /maas-api (#461) Bumps [go.opentelemetry.io/otel/sdk](https://github.com/open-telemetry/opentelemetry-go) from 1.37.0 to 1.40.0. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md">go.opentelemetry.io/otel/sdk's changelog</a>.</em></p> <blockquote> <h2>[1.40.0/0.62.0/0.16.0] 2026-02-02</h2> <h3>Added</h3> <ul> <li>Add <code>AlwaysRecord</code> sampler in <code>go.opentelemetry.io/otel/sdk/trace</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7724">#7724</a>)</li> <li>Add <code>Enabled</code> method to all synchronous instrument interfaces (<code>Float64Counter</code>, <code>Float64UpDownCounter</code>, <code>Float64Histogram</code>, <code>Float64Gauge</code>, <code>Int64Counter</code>, <code>Int64UpDownCounter</code>, <code>Int64Histogram</code>, <code>Int64Gauge</code>,) in <code>go.opentelemetry.io/otel/metric</code>. This stabilizes the synchronous instrument enabled feature, allowing users to check if an instrument will process measurements before performing computationally expensive operations. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7763">#7763</a>)</li> <li>Add <code>go.opentelemetry.io/otel/semconv/v1.39.0</code> package. The package contains semantic conventions from the <code>v1.39.0</code> version of the OpenTelemetry Semantic Conventions. See the <a href="https://github.com/open-telemetry/opentelemetry-go/blob/main/semconv/v1.39.0/MIGRATION.md">migration documentation</a> for information on how to upgrade from <code>go.opentelemetry.io/otel/semconv/v1.38.0.</code> (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7783">#7783</a>, <a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7789">#7789</a>)</li> </ul> <h3>Changed</h3> <ul> <li>Improve the concurrent performance of <code>HistogramReservoir</code> in <code>go.opentelemetry.io/otel/sdk/metric/exemplar</code> by 4x. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7443">#7443</a>)</li> <li>Improve the concurrent performance of <code>FixedSizeReservoir</code> in <code>go.opentelemetry.io/otel/sdk/metric/exemplar</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7447">#7447</a>)</li> <li>Improve performance of concurrent histogram measurements in <code>go.opentelemetry.io/otel/sdk/metric</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7474">#7474</a>)</li> <li>Improve performance of concurrent synchronous gauge measurements in <code>go.opentelemetry.io/otel/sdk/metric</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7478">#7478</a>)</li> <li>Add experimental observability metrics in <code>go.opentelemetry.io/otel/exporters/stdout/stdoutmetric</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7492">#7492</a>)</li> <li><code>Exporter</code> in <code>go.opentelemetry.io/otel/exporters/prometheus</code> ignores metrics with the scope <code>go.opentelemetry.io/contrib/bridges/prometheus</code>. This prevents scrape failures when the Prometheus exporter is misconfigured to get data from the Prometheus bridge. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7688">#7688</a>)</li> <li>Improve performance of concurrent exponential histogram measurements in <code>go.opentelemetry.io/otel/sdk/metric</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7702">#7702</a>)</li> <li>The <code>rpc.grpc.status_code</code> attribute in the experimental metrics emitted from <code>go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc</code> is replaced with the <code>rpc.response.status_code</code> attribute to align with the semantic conventions. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7854">#7854</a>)</li> <li>The <code>rpc.grpc.status_code</code> attribute in the experimental metrics emitted from <code>go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc</code> is replaced with the <code>rpc.response.status_code</code> attribute to align with the semantic conventions. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7854">#7854</a>)</li> </ul> <h3>Fixed</h3> <ul> <li>Fix bad log message when key-value pairs are dropped because of key duplication in <code>go.opentelemetry.io/otel/sdk/log</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7662">#7662</a>)</li> <li>Fix <code>DroppedAttributes</code> on <code>Record</code> in <code>go.opentelemetry.io/otel/sdk/log</code> to not count the non-attribute key-value pairs dropped because of key duplication. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7662">#7662</a>)</li> <li>Fix <code>SetAttributes</code> on <code>Record</code> in <code>go.opentelemetry.io/otel/sdk/log</code> to not log that attributes are dropped when they are actually not dropped. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7662">#7662</a>)</li> <li>Fix missing <code>request.GetBody</code> in <code>go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp</code> to correctly handle HTTP/2 <code>GOAWAY</code> frame. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7794">#7794</a>)</li> <li><code>WithHostID</code> detector in <code>go.opentelemetry.io/otel/sdk/resource</code> to use full path for <code>ioreg</code> command on Darwin (macOS). (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7818">#7818</a>)</li> </ul> <h3>Deprecated</h3> <ul> <li>Deprecate <code>go.opentelemetry.io/otel/exporters/zipkin</code>. For more information, see the <a href="https://opentelemetry.io/blog/2025/deprecating-zipkin-exporters/">OTel blog post deprecating the Zipkin exporter</a>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7670">#7670</a>)</li> </ul> <h2>[1.39.0/0.61.0/0.15.0/0.0.14] 2025-12-05</h2> <h3>Added</h3> <ul> <li>Greatly reduce the cost of recording metrics in <code>go.opentelemetry.io/otel/sdk/metric</code> using hashing for map keys. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7175">#7175</a>)</li> <li>Add <code>WithInstrumentationAttributeSet</code> option to <code>go.opentelemetry.io/otel/log</code>, <code>go.opentelemetry.io/otel/metric</code>, and <code>go.opentelemetry.io/otel/trace</code> packages. This provides a concurrent-safe and performant alternative to <code>WithInstrumentationAttributes</code> by accepting a pre-constructed <code>attribute.Set</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7287">#7287</a>)</li> <li>Add experimental observability for the Prometheus exporter in <code>go.opentelemetry.io/otel/exporters/prometheus</code>. Check the <code>go.opentelemetry.io/otel/exporters/prometheus/internal/x</code> package documentation for more information. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7345">#7345</a>)</li> <li>Add experimental observability metrics in <code>go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7353">#7353</a>)</li> <li>Add temporality selector functions <code>DeltaTemporalitySelector</code>, <code>CumulativeTemporalitySelector</code>, <code>LowMemoryTemporalitySelector</code> to <code>go.opentelemetry.io/otel/sdk/metric</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7434">#7434</a>)</li> <li>Add experimental observability metrics for simple log processor in <code>go.opentelemetry.io/otel/sdk/log</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7548">#7548</a>)</li> <li>Add experimental observability metrics in <code>go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc</code>. (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7459">#7459</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/a3a5317c5caed1656fb5b301b66dfeb3c4c944e0"><code>a3a5317</code></a> Release v1.40.0 (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7859">#7859</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/77785da545d67b38774891cbdd334368bfacdfd8"><code>77785da</code></a> chore(deps): update github/codeql-action action to v4.32.1 (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7858">#7858</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/56fa1c297bf71f0ada3dbf4574a45d0607812cc0"><code>56fa1c2</code></a> chore(deps): update module github.com/clipperhouse/uax29/v2 to v2.5.0 (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7857">#7857</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/298cbedf256b7a9ab3c21e41fc5e3e6d6e4e94aa"><code>298cbed</code></a> Upgrade semconv use to v1.39.0 (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7854">#7854</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/3264bf171b1e6cd70f6be4a483f2bcb84eda6ccf"><code>3264bf1</code></a> refactor: modernize code (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7850">#7850</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/fd5d030c0aa8b5bfe786299047bc914b5714d642"><code>fd5d030</code></a> chore(deps): update module github.com/grpc-ecosystem/grpc-gateway/v2 to v2.27...</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/8d3b4cb2501dec9f1c5373123e425f109c43b8d2"><code>8d3b4cb</code></a> chore(deps): update actions/cache action to v5.0.3 (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7847">#7847</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/91f7cadfcac363d67030f6913687c6dbbe086823"><code>91f7cad</code></a> chore(deps): update github.com/timakin/bodyclose digest to 73d1f95 (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7845">#7845</a>)</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/fdad1eb7f350ee1f5fdb3d9a0c6855cc88ee9d75"><code>fdad1eb</code></a> chore(deps): update module github.com/grpc-ecosystem/grpc-gateway/v2 to v2.27...</li> <li><a href="https://github.com/open-telemetry/opentelemetry-go/commit/c46d3bac181ddaaa83286e9ccf2cd9f7705fd3d9"><code>c46d3ba</code></a> chore(deps): update golang.org/x/telemetry digest to fcf36f6 (<a href="https://redirect.github.com/open-telemetry/opentelemetry-go/issues/7843">#7843</a>)</li> <li>Additional commits viewable in <a href="https://github.com/open-telemetry/opentelemetry-go/compare/v1.37.0...v1.40.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/opendatahub-io/models-as-a-service/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
<!--- Provide a general summary of your changes in the Title above --> ## Description <!--- Describe your changes in detail --> Related to - https://issues.redhat.com/browse/RHOAIENG-49790 **User story** As an operator currently using the tier-based setup (ConfigMap, gateway-auth-policy), I want a clear migration guide to move to the subscription model (MaaSModel, MaaSAuthPolicy, MaaSSubscription), So that I can adopt the new model without breaking production. **Acceptance criteria** Given the existing old-vs-new-flow doc, When an operator follows the migration guide, Then the guide provides steps to move from tier ConfigMap + gateway-auth-policy to MaaSModel + MaaSAuthPolicy + MaaSSubscription. Given ODH Model Controller cleanup (AuthPolicy removal) is done or planned, When the guide is read, Then it covers that ODH no longer relies on gateway-auth-policy and maas-controller install. Given the guide does not assume API key minting or list-subscriptions endpoints, When an operator migrates, Then auth for inference can remain OpenShift token (or existing mechanism). **Testability**: Doc review; optionally follow the guide in a test env and verify subscription flow works. **Documentation**: Migration guide (new or supplement to old-vs-new-flow). ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> Tested on the cluster - https://console-openshift-console.apps.ci-ln-ir3wk52-76ef8.aws-4.ci.openshift.org ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ./scripts/migrate-tier-to-subscription.sh --tier premium \ --models model-a,model-b,model-c \ --groups premium-users \ --rate-limit 50000 \ --output migration-crs/premium/ ✅ Created output directory: migration-crs/premium/ ℹ️ Migration Configuration: ℹ️ Generating MaaSModelRef CRs... ✅ Generated: migration-crs/premium//maasmodelref-model-a.yaml ✅ Generated: migration-crs/premium//maasmodelref-model-b.yaml ✅ Generated: migration-crs/premium//maasmodelref-model-c.yaml ℹ️ Generating MaaSAuthPolicy CR... ✅ Generated: migration-crs/premium//maasauthpolicy-premium.yaml ℹ️ Generating MaaSSubscription CR... ✅ Generated: migration-crs/premium//maassubscription-premium.yaml ✅ Migration CRs generated successfully! ℹ️ Summary: ℹ️ Output directory: migration-crs/premium/ maasauthpolicy-premium.yaml maasmodelref-model-a.yaml maasmodelref-model-b.yaml maasmodelref-model-c.yaml maassubscription-premium.yaml ℹ️ Next steps: ✅ Migration script completed! somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ``` ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ./scripts/migrate-tier-to-subscription.sh --tier free \ --models simulator,qwen3 \ --groups system:authenticated \ --rate-limit 100 \ --apply ✅ Created output directory: migration-crs ℹ️ Migration Configuration: ℹ️ Generating MaaSModelRef CRs... ✅ Generated: migration-crs/maasmodelref-simulator.yaml ✅ Generated: migration-crs/maasmodelref-qwen3.yaml ℹ️ Generating MaaSAuthPolicy CR... ✅ Generated: migration-crs/maasauthpolicy-free.yaml ℹ️ Generating MaaSSubscription CR... ✅ Generated: migration-crs/maassubscription-free.yaml ✅ Migration CRs generated successfully! ℹ️ Summary: ℹ️ Output directory: migration-crs maasauthpolicy-enterprise.yaml maasauthpolicy-free.yaml maasmodelref-e2e-unconfigured-facebook-opt-125m-simulated.yaml maasmodelref-facebook-opt-125m-simulated.yaml maasmodelref-premium-simulated-simulated-premium.yaml maasmodelref-qwen3.yaml maasmodelref-simulator.yaml maassubscription-enterprise.yaml maassubscription-free.yaml premium ℹ️ Applying CRs to cluster... maasauthpolicy.maas.opendatahub.io/enterprise-models-access unchanged maasauthpolicy.maas.opendatahub.io/free-models-access configured maasmodelref.maas.opendatahub.io/e2e-unconfigured-facebook-opt-125m-simulated unchanged maasmodelref.maas.opendatahub.io/facebook-opt-125m-simulated unchanged maasmodelref.maas.opendatahub.io/premium-simulated-simulated-premium unchanged maasmodelref.maas.opendatahub.io/qwen3 configured maasmodelref.maas.opendatahub.io/simulator configured maassubscription.maas.opendatahub.io/enterprise-models-subscription unchanged maassubscription.maas.opendatahub.io/free-models-subscription configured ✅ CRs applied successfully! ℹ️ Validating deployment... ℹ️ Checking MaaSModelRef status...⚠️ simulator: Failed⚠️ qwen3: Failed ℹ️ Checking generated Kuadrant policies...⚠️ Not all policies created yet. Controller may still be reconciling. ℹ️ Check maas-controller logs: kubectl logs -n opendatahub -l app.kubernetes.io/name=maas-controller ℹ️ Next steps: ✅ Migration script completed! somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ``` ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ./scripts/migrate-tier-to-subscription.sh --tier enterprise \ --models $(kubectl get llminferenceservice -n llm -o name | cut -d/ -f2 | tr '\n' ',') \ --groups enterprise-users \ --rate-limit 100000 \ --apply \ --verbose ✅ Created output directory: migration-crs ℹ️ Migration Configuration: Tier: enterprise Models: 3 (e2e-unconfigured-facebook-opt-125m-simulated,facebook-opt-125m-simulated,premium-simulated-simulated-premium,) Groups: 1 (enterprise-users) Rate Limit: 100000 tokens per 1m Output: migration-crs Namespaces: MaaS=opendatahub, Subscription=models-as-a-service, Model=llm Mode: GENERATE + APPLY ℹ️ Generating MaaSModelRef CRs... ✅ Generated: migration-crs/maasmodelref-e2e-unconfigured-facebook-opt-125m-simulated.yaml ✅ Generated: migration-crs/maasmodelref-facebook-opt-125m-simulated.yaml ✅ Generated: migration-crs/maasmodelref-premium-simulated-simulated-premium.yaml ℹ️ Generating MaaSAuthPolicy CR... ✅ Generated: migration-crs/maasauthpolicy-enterprise.yaml ℹ️ Generating MaaSSubscription CR... ✅ Generated: migration-crs/maassubscription-enterprise.yaml ✅ Migration CRs generated successfully! ℹ️ Summary: Tier: enterprise Models: 3 Groups: 1 Files generated: - 3 MaaSModelRef CRs - 1 MaaSAuthPolicy CR - 1 MaaSSubscription CR ℹ️ Output directory: migration-crs Files: maasauthpolicy-enterprise.yaml maasauthpolicy-free.yaml maasmodelref-e2e-unconfigured-facebook-opt-125m-simulated.yaml maasmodelref-facebook-opt-125m-simulated.yaml maasmodelref-premium-simulated-simulated-premium.yaml maasmodelref-qwen3.yaml maasmodelref-simulator.yaml maassubscription-enterprise.yaml maassubscription-free.yaml premium ℹ️ Applying CRs to cluster... maasauthpolicy.maas.opendatahub.io/enterprise-models-access configured maasauthpolicy.maas.opendatahub.io/free-models-access unchanged maasmodelref.maas.opendatahub.io/e2e-unconfigured-facebook-opt-125m-simulated configured maasmodelref.maas.opendatahub.io/facebook-opt-125m-simulated configured maasmodelref.maas.opendatahub.io/premium-simulated-simulated-premium configured maasmodelref.maas.opendatahub.io/qwen3 unchanged maasmodelref.maas.opendatahub.io/simulator unchanged maassubscription.maas.opendatahub.io/enterprise-models-subscription configured maassubscription.maas.opendatahub.io/free-models-subscription unchanged ✅ CRs applied successfully! ℹ️ Validating deployment... ℹ️ Checking MaaSModelRef status... ✅ e2e-unconfigured-facebook-opt-125m-simulated: Ready ✅ facebook-opt-125m-simulated: Ready ✅ premium-simulated-simulated-premium: Ready ℹ️ Checking generated Kuadrant policies... AuthPolicies created: 0 (expected: 3) TokenRateLimitPolicies created: 0 (expected: 3)⚠️ Not all policies created yet. Controller may still be reconciling. ℹ️ Check maas-controller logs: kubectl logs -n opendatahub -l app.kubernetes.io/name=maas-controller ℹ️ Next steps: 1. Test model access with users in tier 'enterprise' groups 2. Validate rate limiting is working as expected 3. Once validated, remove tier annotations from models 4. Remove old gateway-auth-policy and tier-based TokenRateLimitPolicy ✅ Migration script completed! ``` ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a comprehensive migration guide covering prerequisites, zero-downtime and full-cutover strategies, phased workflows, per-model transition examples, verification, rollback, troubleshooting, a conversion worksheet, and updated navigation and cross-references. * **New Features** * Added a migration CLI to convert tier configurations into subscription resources, with dry-run/apply modes, parameter options, and basic post-apply validation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
<!--- Provide a general summary of your changes in the Title above --> ## Description <!--- Describe your changes in detail --> Currently, if `deploy.sh --deployment-mode kustomize` is run multiple times, it won't work because `kubectl apply --server-side=true` conflicts with previous `configure_cluster_audience`. By adding env var `KUSTOMIZE_FORCE_CONFLICTS` and setting it to true, `deploy.sh --deployment-mode kustomize` can be idempotent. ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> - `./scripts/deploy.sh --deployment-mode kustomize` -> Failure - `KUSTOMIZE_FORCE_CONFLICTS=true ./scripts/deploy.sh --deployment-mode kustomize` -> Success ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added `KUSTOMIZE_FORCE_CONFLICTS` environment variable for deployment configurations (defaults to `false`). * **Documentation** * Updated environment variable documentation to include the new configuration option. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!--- Provide a general summary of your changes in the Title above --> ## Description Related to https://issues.redhat.com/browse/RHOAIENG-47840 In maas-api/cmd/main.go:59–70, debug mode enables an unrestricted CORS policy. While this was mainly intended to support local development (e.g., self-serving the OpenAPI schema), leaving these settings in place creates a risk if debug mode is ever enabled in production. If misconfigured, this could allow unintended cross-origin access, increasing the potential for CSRF-style abuse or data exfiltration. Debug mode is currently off by default and the API uses Bearer tokens (not cookies), which lowers the immediate risk. However, to prevent accidental exposure to dangerous behavior, the first step should be to remove these permissive CORS settings (or strictly scope them to a safe development-only mode). ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> - Unit tests are added. - Tested on the cluster - https://console-openshift-console.apps.ci-ln-idrxfgk-76ef8.aws-2.ci.openshift.org/dashboards ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % oc port-forward deployment/maas-api 8080:8080 -n maas-api 2>&1 Forwarding from 127.0.0.1:8080 -> 8080 Forwarding from [::1]:8080 -> 8080 Handling connection for 8080 Handling connection for 8080 Handling connection for 8080 Handling connection for 8080 Handling connection for 8080 ``` ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % echo "=== TEST 1: Localhost origin (should be ALLOWED) ===" && curl -si http://localhost:8080/health -H "Origin: http://localhost:3000" | grep -iE "HTTP/|Access-Control|^$" && echo "" && echo "=== TEST 2: External origin (should be REJECTED with 403) ===" && curl -si http://localhost:8080/health -H "Origin: https://evil.com" | grep -iE "HTTP/|Access-Control|^$" && echo "" && echo "=== TEST 3: Preflight from localhost (should be 204 with CORS headers) ===" && curl -si -X OPTIONS http://localhost:8080/health -H "Origin: http://localhost:3000" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Authorization" | grep -iE "HTTP/|Access-Control|^$" && echo "" && echo "=== TEST 4: Preflight from external (should have NO CORS headers) ===" && curl -si -X OPTIONS http://localhost:8080/health -H "Origin: https://evil.com" -H "Access-Control-Request-Method: POST" | grep -iE "HTTP/|Access-Control|^$" && echo "" && echo "=== TEST 5: Credentials NOT allowed ===" && curl -si http://localhost:8080/health -H "Origin: http://localhost:3000" | grep -i "Access-Control-Allow-Credentials" && echo "(empty = PASS: no credentials header)" && echo "" === TEST 1: Localhost origin (should be ALLOWED) === HTTP/1.1 200 OK Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Expose-Headers: Content-Type === TEST 2: External origin (should be REJECTED with 403) === HTTP/1.1 403 Forbidden === TEST 3: Preflight from localhost (should be 204 with CORS headers) === HTTP/1.1 204 No Content Access-Control-Allow-Headers: Authorization,Content-Type,Accept Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Max-Age: 43200 Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers === TEST 4: Preflight from external (should have NO CORS headers) === HTTP/1.1 403 Forbidden === TEST 5: Credentials NOT allowed === ``` ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Debug-mode CORS now only allows localhost origins, blocking external cross-origin requests while preserving local development access. * **Tests** * Added comprehensive tests covering localhost vs external origins, preflight behavior, credential handling, same-origin requests, and debug-mode toggles. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
Hi @github-actions[bot]. Thanks for your PR. I'm waiting for a opendatahub-io member to verify that this patch is reasonable to test. If it is, they should reply with Regular contributors should join the org to skip this step. Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/retest |
…516) <!--- Provide a general summary of your changes in the Title above --> https://issues.redhat.com/browse/RHOAIENG-48332 ## Description <!--- Describe your changes in detail --> This PR: - updates the way MaaSAuthPolicy generates Kuadrant AuthPolicy to enforce "fail-close" logic for inference endpoint when subscription endpoint is down. - updates `cleanup-odh.sh` in accordance with new namespace structure. - clean up some loose ends on comments from previous PRs ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> Before the change: - Inference requests with subscription header get `200`, otherwise get `403`. - With subscription URL in AuthPolicy tampered, inference requests with or without subscription header get `200` ("fail-open"). After the change: - Inference requests with subscription header get `200`, otherwise get `403`. - With subscription URL in AuthPolicy tampered, inference requests with or without subscription header get `403` ("fail-close"). ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Stricter subscription validation in authentication policies: requests now fail when subscription name is missing. * **Tests** * Updated end-to-end test expectations to reflect rebuilt policy behavior after deletion operations. * **Chores** * Improved namespace cleanup: added an extra deletion step and enhanced handling to remove finalizers from additional resource types before namespace removal. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…#528) ## Description <!--- Describe your changes in detail --> Related to https://redhat.atlassian.net/browse/RHOAIENG-52941 The test test_multiple_subscriptions_different_namespaces_same_model intermittently fails in Prow with a 429 (Too Many Requests) when calling the model listing endpoint. The test expects 200 but receives 429. **Observed failure:** ``` r = _list_models(api_key, MODEL_REF, MODEL_NAMESPACE, subscription=sub1_name) assert r.status_code == 200, f"Model listing failed: {r.status_code}" AssertionError: Model listing failed: 429 assert 429 == 200 Test location: test/e2e/tests/test_namespace_scoping.py — TestCrossNamespaceSubscription.test_multiple_subscriptions_different_namespaces_same_model ``` **Test flow:** - Creates two MaaSSubscription}}s in different namespaces for the same model (sub1: {{system:authenticated, 100/min; sub2: test_group, 200/min) - Waits RECONCILE_WAIT (default 10s) for reconciliation - Waits for AuthPolicy to be enforced - Verifies TokenRateLimitPolicy exists - Calls _list_models with subscription=sub1_name — fails here with 429 **Suspected Causes** Rate limit exhaustion: Previous tests in the suite may have consumed the rate limit for the same model/API key before this test runs TRLP reconciliation timing: TokenRateLimitPolicy or Limitador may not be fully synced when the test runs; RECONCILE_WAIT (10s) may be insufficient under Prow load Shared rate limit state: Multiple subscriptions aggregating into one TRLP may have unexpected interaction with Limitador counters Gateway default TRLP: Default deny or low default limits may apply before subscription-specific limits are enforced **Acceptance Criteria** ``` Given the Prow E2E suite runs test_multiple_subscriptions_different_namespaces_same_model, When the test creates MaaSSubscriptions and calls the model listing endpoint with a valid subscription, Then the response is 200, not 429. ``` ``` Given the test runs in isolation (e.g. pytest test_namespace_scoping.py::TestCrossNamespaceSubscription::test_multiple_subscriptions_different_namespaces_same_model), When run multiple times, Then the test passes consistently (or root cause is identified if 429 persists). ``` **Verification** Reproduce: Run full e2e suite in Prow or locally; observe intermittent 429 Isolate: Run test_multiple_subscriptions_different_namespaces_same_model in isolation; check if 429 still occurs Increase E2E_RECONCILE_WAIT (e.g. 15–20s) and re-run; check if failure rate decreases Inspect Limitador/TRLP state and gateway logs when 429 occurs **Root cause:** MaaSSubscription resources were being created in random test namespaces, but the maas-controller only watches the models-as-a-service namespace (configured via MAAS_SUBSCRIPTION_NAMESPACE). This meant subscriptions were never reconciled, TokenRateLimitPolicy was never updated, and requests hit the default-deny policy resulting in 429 errors. **Changes:** - Fixed test_subscription_in_different_namespace to use MAAS_SUBSCRIPTION_NAMESPACE - Fixed test_multiple_subscriptions_different_namespaces_same_model to use MAAS_SUBSCRIPTION_NAMESPACE - Added _wait_for_trlp_enforced() to check TokenRateLimitPolicy enforcement status - Added _wait_for_trlp_enforced_with_retry() with 3 retry attempts and 5s backoff - Added _get_trlp_generation() to track TRLP metadata.generation changes - Added _get_trlp_metadata() to track uid/resourceVersion/generation for verification - Increased RECONCILE_WAIT from 10s to 15s for better Prow tolerance - Added TRLP enforcement waits before making requests - Added TRLP re-enforcement wait after subscription deletion - Updated test docstrings to clarify namespace requirements CodeRabbit fixes: - Fixed race condition: moved TRLP enforcement wait before existence checks - Added comprehensive metadata verification to ensure TRLP was actually updated - Fixed type annotation: expected_generation parameter now uses Optional[int] **Test Results:** - Before: ~0% success rate (intermittent 429 errors) - After: 100% success rate (5/5 consecutive test runs passed) The test is now production-ready and will pass reliably in Prow CI. <!--- Provide a general summary of your changes in the Title above --> ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> Tested on live cluster : ``` GATEWAY_HOST=maas.apps.ci-ln-3rxf0wb-76ef8.aws-4.ci.openshift.org E2E_SKIP_TLS_VERIFY=true MAAS_API_BASE_URL=https://maas.apps.ci-ln-3rxf0wb-76ef8.aws-4.ci.openshift.org/maas-api E2E_RECONCILE_WAIT=15 (default) ``` ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Improved end-to-end coverage for cross-namespace subscription and rate-limit policy enforcement. * Increased reconciliation timeout and added stronger wait/retry logic to reduce flakiness. * Added metadata and generation checks to verify policy presence, updates, and recreation after subscription changes. * Enhanced multi-subscription scenarios, cleanup, and logging to better validate cross-namespace interactions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
## Description <!--- Describe your changes in detail --> This PR adds comprehensive E2E test coverage for the `/v1/models` endpoint that validates subscription-aware model filtering and access control. **Test Coverage:** - **Single subscription auto-selection**: Users with one subscription can list models without providing `x-maas-subscription` header - **Explicit subscription header**: Users with multiple subscriptions can select which subscription to use - **Multiple subscriptions requiring header**: Validates proper 403 error when header is missing - **Subscription filtering and validation**: Ensures models are filtered by subscription access - **Model deduplication scenarios**: Tests handling of multiple modelRefs serving the same model - **Empty model lists**: Validates response when user has no accessible models - **Response schema validation**: Ensures API responses match OpenAI-compatible schema - **Error cases**: Tests 401 (unauthenticated) and 403 (forbidden) scenarios **Sample Resources:** - Added two distinct test models (`e2e-distinct-simulated`, `e2e-distinct-2-simulated`) to enable multi-model subscription testing - Added corresponding MaaSModelRef kustomize configurations for test deployment **Known Issues (marked as xfail):** Three tests expose a known bug where `/v1/models` returns all accessible models instead of filtering by the selected subscription. These are marked with `@pytest.mark.xfail` and will pass once the filtering bug is fixed: - `test_single_subscription_auto_select` - `test_explicit_subscription_header` - `test_multiple_distinct_models_in_subscription` ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> **Test Execution:** ```bash cd test/e2e ./scripts/prow_run_smoke_test.sh Results: - 12 tests passed - 3 tests xfailed (expected failures due to known bug) - 0 tests failed - All tests run in isolated namespaces with proper cleanup Environment: - OpenShift cluster with MaaS components deployed - maas-controller watching models-as-a-service namespace - Models deployed in llm namespace - Kuadrant/Authorino for authorization - PostgreSQL backend for API key storage Validation: - Verified subscription-based access control works correctly - Verified API key authentication flow - Verified OpenAI-compatible response format - Verified proper error handling for unauthorized access ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added sample Kustomize and model manifests (including MaaSModelRef) to demonstrate distinct multi-model deployments and updated top-level samples to include distinct and distinct-2 entries. * **Tests** * Added comprehensive end-to-end tests for the /v1/models endpoint covering subscription-aware filtering, multi-model scenarios, auth/error paths and deduplication checks; updated test runner to include them and added env vars to support multi-model testing. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…le instead of being hardcoded (#527) During testing on the lates RHOAI operator build noticed that the namespace for our auth policy is still opendatahub. I updated it so it should be pulling from the `params.env` now. Also changes the url to try to make it easier to identify this mistake earlier (fail fast). How did I test: Can change the app_namespace in the `params.env` to whatever `my_totally_correct_namespace` and validate the url looks correct ``` jland@fedora:~/Documents/RedHat/poc/models-as-a-service$ kustomize build deployment/overlays/odh | grep -E "url:|api-keys/validate" url: https://maas-api.my_totally_correct_namespace.svc.cluster.local:8443/internal/v1/api-keys/validate ``` And I checked the actual prams being installed with the operator and that looks right. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated internal authentication policy configuration to use overlay-based URL replacement pattern, improving deployment flexibility across environments. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!--- Provide a general summary of your changes in the Title above --> ## Description Related to - https://redhat.atlassian.net/browse/RHOAIENG-53772 Background PR #527 updated the maas-api auth policy to use a placeholder URL (maas-api.placehold.svc) instead of hardcoded namespace (opendatahub). The placeholder gets replaced via kustomize replacements in the ODH overlay using the app-namespace parameter from params.env. This change was intended to support flexible namespace deployment for different operators (ODH vs RHOAI). Problem The TLS backend (tls-backend) and HTTP backend (http-backend) overlays used in kustomize deployment mode do not have: - params.env file with namespace configuration - ConfigMap generator for parameters - Kustomize replacements to patch the placeholder URL This caused the auth policy to be deployed with the literal placeholder URL https://maas-api.placehold.svc.cluster.local:8443/internal/v1/api-keys/validate, breaking API key authentication. Steps to Reproduce 1. Deploy MaaS using kustomize mode: ./scripts/deploy.sh --deployment-mode kustomize --enable-tls-backend 2. Check the deployed AuthPolicy: kubectl get authpolicy maas-api-auth-policy -n opendatahub -o yaml | grep "api-keys/validate" Expected Behavior The auth policy URL should be patched to use the actual deployment namespace: url: https://maas-api.opendatahub.svc.cluster.local:8443/internal/v1/api-keys/validate Actual Behavior The auth policy URL contains the unpatched placeholder: url: https://maas-api.placehold.svc.cluster.local:8443/internal/v1/api-keys/validate This causes API key validation requests to fail with DNS resolution errors, breaking all API key-based authentication. Root Cause The kustomize overlays (tls-backend, http-backend) lack the replacement configuration that exists in the odh overlay: ODH overlay has: - params.env with app-namespace=opendatahub - ConfigMapGenerator creating maas-parameters - Kustomize replacement targeting spec.rules.metadata.apiKeyValidation.http.url TLS/HTTP backend overlays missing: - All of the above components Impact - Severity: High - Breaks API key authentication completely in kustomize deployment mode - Scope: Affects all deployments using --deployment-mode kustomize (both TLS and HTTP backends) - Workaround: None - deployment is broken for kustomize mode - Operator mode: Not affected (ODH/RHOAI overlays have proper replacement logic) Solution Added sed-based patching in scripts/deploy.sh to replace the placeholder URL with the actual namespace during kustomize build: kustomize build "$overlay" | sed "s/maas-api\.placehold\.svc/maas-api.$NAMESPACE.svc/g" Design Decision: Chose script-based patching over duplicating params.env files across three overlays to maintain DRY principles and avoid configuration drift. Files Changed 1. scripts/deploy.sh:577 - Added sed patching to replace placeholder with $NAMESPACE 2. deployment/base/maas-api/policies/auth-policy.yaml:32-34 - Updated comment to document both patching methods (ODH overlay replacement vs deploy.sh sed) ``` Testing/Verification ✅ Tested on cluster: ./scripts/deploy.sh --deployment-mode kustomize --enable-tls-backend ✅ Verified URL patching: kubectl get authpolicy maas-api-auth-policy -n opendatahub -o yaml | grep "api-keys/validate" # Output: url: https://maas-api.opendatahub.svc.cluster.local:8443/internal/v1/api-keys/validate ✅ Verified both overlays work: - tls-backend overlay: ✅ URL correctly patched - http-backend overlay: ✅ URL correctly patched - odh overlay: ✅ Still works with kustomize replacements ✅ CodeRabbit review: No findings ``` ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> Tested on live cluster ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % oc login https://api.ci-ln-hlkdrfk-76ef8.aws-4.ci.openshift.org:6443 --web The server uses a certificate signed by an unknown authority. You can bypass the certificate check, but any data you send to the server could be intercepted by others. Use insecure connections? (y/n): y WARNING: Using insecure TLS client config. Setting this option is not supported! Opening login URL in the default browser: https://oauth-openshift.apps.ci-ln-hlkdrfk-76ef8.aws-4.ci.openshift.org/oauth/authorize?client_id=openshift-cli-client&code_challenge=fvLt3bBVCbvYdSatndrZg_2MK5869FIZ7abjlUxLDsA&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A52210%2Fcallback&response_type=code Login successful. You have access to 74 projects, the list has been suppressed. You can list all projects with 'oc projects' Using project "default". somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ./scripts/deploy.sh --deployment-mode kustomize --enable-tls-backend [INFO] =================================================== [INFO] Models-as-a-Service Deployment [INFO] =================================================== [INFO] Validating configuration... [INFO] Configuration validated successfully [INFO] Deployment configuration: [INFO] Mode: kustomize [INFO] Policy Engine: kuadrant [INFO] Namespace: opendatahub [INFO] TLS Backend: true [INFO] Starting kustomize-based deployment... [INFO] Installing policy engine: kuadrant [INFO] Installing Kuadrant v1.3.1 (upstream community) [INFO] Creating Kuadrant v1.3.1 catalog source... namespace/kuadrant-system created catalogsource.operators.coreos.com/kuadrant-operator-catalog created [INFO] Waiting for Kuadrant catalog to be ready... operatorgroup.operators.coreos.com/kuadrant-operator-group created [INFO] Installing operator: kuadrant-operator in namespace: kuadrant-system namespace/kuadrant-system condition met [INFO] Creating Subscription for kuadrant-operator from kuadrant-operator-catalog (channel: stable) subscription.operators.coreos.com/kuadrant-operator created [INFO] Waiting for subscription to install... * Waiting for Subscription kuadrant-system/kuadrant-operator to start setup... subscription.operators.coreos.com/kuadrant-operator condition met * Waiting for Subscription setup to finish setup. CSV = kuadrant-operator.v1.3.1 ... clusterserviceversion.operators.coreos.com/kuadrant-operator.v1.3.1 condition met [INFO] Operator kuadrant-operator installed successfully [INFO] Patching kuadrant-operator CSV for OpenShift Gateway controller... clusterserviceversion.operators.coreos.com/kuadrant-operator.v1.3.1 patched [INFO] CSV patched for OpenShift Gateway controller [INFO] Forcing operator restart to apply new Gateway controller configuration... pod "kuadrant-operator-controller-manager-54b54c8744-smx2q" force deleted from kuadrant-system namespace pod "kuadrant-operator-controller-manager-68d7ff44d6-srsgr" force deleted from kuadrant-system namespace pod "limitador-operator-controller-manager-84d8fbb794-zp7mt" force deleted from kuadrant-system namespace [INFO] Waiting for operator pod to restart... Waiting for deployment "kuadrant-operator-controller-manager" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "kuadrant-operator-controller-manager" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "kuadrant-operator-controller-manager" rollout to finish: 1 old replicas are pending termination... deployment "kuadrant-operator-controller-manager" successfully rolled out [WARN] Operator pod may not have correct env yet: [INFO] Waiting 15s for operator to fully initialize with Gateway controller configuration... [INFO] Initializing Gateway API and ModelsAsService gateway... [INFO] Setting up Gateway API infrastructure... gatewayclass.gateway.networking.k8s.io/openshift-default created [INFO] Setting up ModelsAsService gateway... [INFO] Cluster domain: apps.ci-ln-hlkdrfk-76ef8.aws-4.ci.openshift.org [INFO] Detecting TLS certificate secret... [INFO] * Found certificate from router deployment: router-certs-default [INFO] TLS certificate secret: router-certs-default [INFO] Creating maas-default-gateway resource (allowing routes from all namespaces)... gateway.gateway.networking.k8s.io/maas-default-gateway serverside-applied [INFO] Waiting for Gateway to be Programmed (Service Mesh initialization)... gateway.gateway.networking.k8s.io/maas-default-gateway condition met [INFO] Applying Kuadrant custom resource in kuadrant-system... kuadrant.kuadrant.io/kuadrant created [INFO] Waiting for Kuadrant to become ready (initial check)... [INFO] Waiting for: Kuadrant ready in kuadrant-system (timeout: 60s) [WARN] Kuadrant ready in kuadrant-system - Timeout after 60s [INFO] Kuadrant shows MissingDependency - restarting operator to re-register Gateway controller... pod "authorino-76d7b84c9-8d5dj" force deleted from kuadrant-system namespace pod "kuadrant-operator-controller-manager-68d7ff44d6-lqn8z" force deleted from kuadrant-system namespace pod "limitador-operator-controller-manager-84d8fbb794-95tvc" force deleted from kuadrant-system namespace [INFO] Retrying Kuadrant readiness check after operator restart... [INFO] Waiting for: Kuadrant ready in kuadrant-system (timeout: 120s) [INFO] Kuadrant ready in kuadrant-system - Ready [INFO] Kuadrant setup complete [INFO] Using TLS backend overlay [INFO] Creating namespace: opendatahub namespace/opendatahub created [INFO] Deploying PostgreSQL for API key storage... [INFO] Generated random PostgreSQL password (stored in secret postgres-creds) [INFO] Creating PostgreSQL deployment... [INFO]⚠️ Using POC configuration (ephemeral storage) secret/postgres-creds created deployment.apps/postgres created service/postgres created secret/maas-db-config created [INFO] Waiting for PostgreSQL to be ready... deployment.apps/postgres condition met [INFO] PostgreSQL deployed successfully [INFO] Database: maas [INFO] User: maas [INFO] Secret: maas-db-config (contains DB_CONNECTION_URL) [INFO] [INFO]⚠️ For production, use AWS RDS, Crunchy Operator, or Azure Database [INFO] Note: Schema migrations run automatically when maas-api starts [INFO] Applying kustomize manifests... customresourcedefinition.apiextensions.k8s.io/maasauthpolicies.maas.opendatahub.io serverside-applied customresourcedefinition.apiextensions.k8s.io/maasmodelrefs.maas.opendatahub.io serverside-applied customresourcedefinition.apiextensions.k8s.io/maassubscriptions.maas.opendatahub.io serverside-applied serviceaccount/maas-api serverside-applied serviceaccount/maas-controller serverside-applied role.rbac.authorization.k8s.io/maas-controller-leader-election-role serverside-applied clusterrole.rbac.authorization.k8s.io/maas-api serverside-applied clusterrole.rbac.authorization.k8s.io/maas-controller-role serverside-applied rolebinding.rbac.authorization.k8s.io/maas-controller-leader-election-rolebinding serverside-applied clusterrolebinding.rbac.authorization.k8s.io/maas-api serverside-applied clusterrolebinding.rbac.authorization.k8s.io/maas-controller-rolebinding serverside-applied configmap/tier-to-group-mapping serverside-applied service/maas-api serverside-applied deployment.apps/maas-api serverside-applied deployment.apps/maas-controller serverside-applied httproute.gateway.networking.k8s.io/maas-api-route serverside-applied authpolicy.kuadrant.io/maas-api-auth-policy serverside-applied destinationrule.networking.istio.io/maas-api-backend-tls serverside-applied networkpolicy.networking.k8s.io/maas-authorino-allow serverside-applied [INFO] Applying gateway policies (openshift-ingress)... authpolicy.kuadrant.io/gateway-default-auth serverside-applied tokenratelimitpolicy.kuadrant.io/gateway-default-deny serverside-applied [INFO] Configuring TLS backend for Authorino and MaaS API... * Waiting for deployment/authorino in kuadrant-system... * Found deployment/authorino [INFO] Running TLS configuration script... [INFO] TLS configuration script completed successfully [INFO] Restarting deployments to pick up TLS configuration... deployment.apps/maas-api restarted deployment.apps/authorino restarted [INFO] Waiting for Authorino deployment to be ready... Waiting for deployment "authorino" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "authorino" rollout to finish: 1 old replicas are pending termination... deployment "authorino" successfully rolled out [INFO] TLS backend configuration complete [INFO] Checking cluster OIDC audience... [INFO] Standard Kubernetes audience detected, no patching needed [INFO] Kustomize deployment completed [INFO] [INFO] MaaS Subscription Controller... [INFO] Controller deployed via kustomize overlay (deployment/base/maas-controller/default) [INFO] Waiting for maas-controller to be ready... deployment "maas-controller" successfully rolled out [INFO] Subscription controller ready. [INFO] Create MaaSModelRef, MaaSAuthPolicy, and MaaSSubscription to enable per-model auth and rate limiting. [INFO] =================================================== [INFO] Deployment completed successfully! [INFO] =================================================== somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % kubectl get authpolicy maas-api-auth-policy -n opendatahub -o yaml | grep -A2 "api-keys/validate" url: https://maas-api.opendatahub.svc.cluster.local:8443/internal/v1/api-keys/validate metrics: false priority: 0 ``` ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved deployment configuration to automatically replace placeholder API URLs with actual namespace-specific addresses, ensuring correct API connectivity across different deployment environments and reducing manual configuration errors. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Description For https://redhat.atlassian.net/browse/RHOAIENG-52971 This PR moves the subscription selection endpoint from `/v1/subscriptions/select` to `/internal/v1/subscriptions/select`, aligning it with the existing internal endpoint pattern used by `/internal/v1/api-keys/validate`. **Background:** The subscription selection endpoint is exclusively used as an HTTP callback by Authorino during the authentication/authorization flow. It is not intended for direct user consumption and should not be part of the public `/v1` API. **Changes:** - **maas-api/cmd/main.go**: Moved endpoint registration from `v1Routes` to `internalRoutes` group - **maas-controller/.../maasauthpolicy_controller.go**: Updated AuthPolicy controller to call the new `/internal/v1/subscriptions/select` URL - **test/e2e/scripts/auth_utils.sh**: Updated test scripts to use new endpoint path - **test/e2e/tests/test_subscription.py**: Updated test documentation comments - **maas-api/architecture.md**: Added endpoint to API reference table for consistency with other internal endpoints **Impact:** - ✅ No user-facing breaking changes (endpoint was never part of public API) - ✅ Only caller is Authorino via AuthPolicy (internal) - ✅ Improves API architecture clarity by properly distinguishing public vs internal endpoints - ✅ Consistent with existing `/internal/v1/api-keys/validate` pattern ## How Has This Been Tested? ### Unit Tests Ran the subscription handler unit test suite: ```bash cd maas-api && go test -v ./internal/subscription/... Result: ✅ All 7 tests passed E2E Subscription Tests Ran the complete subscription E2E test suite: =============================================================== test session starts ================================================================ platform linux -- Python 3.13.12, pytest-8.4.2, pluggy-1.6.0 -- /home/jrhyness/src/models-as-a-service/test/e2e/.venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.13.12', 'Platform': 'Linux-6.19.6-100.fc42.x86_64-x86_64-with-glibc2.41', 'Packages': {'pytest': '8.4.2', 'pluggy': '1.6.0'}, 'Plugins': {'metadata': '3.1.1', 'html': '4.2.0'}} rootdir: /home/jrhyness/src/models-as-a-service/test/e2e plugins: metadata-3.1.1, html-4.2.0 collected 31 items tests/test_subscription.py::TestAuthEnforcement::test_authorized_user_gets_200 PASSED tests/test_subscription.py::TestAuthEnforcement::test_no_auth_gets_401 PASSED tests/test_subscription.py::TestAuthEnforcement::test_invalid_token_gets_403 PASSED tests/test_subscription.py::TestAuthEnforcement::test_wrong_group_gets_403 PASSED tests/test_subscription.py::TestSubscriptionEnforcement::test_subscribed_user_gets_200 PASSED tests/test_subscription.py::TestSubscriptionEnforcement::test_auth_pass_no_subscription_gets_403 PASSED tests/test_subscription.py::TestSubscriptionEnforcement::test_invalid_subscription_header_gets_429 PASSED tests/test_subscription.py::TestSubscriptionEnforcement::test_explicit_subscription_header_works PASSED tests/test_subscription.py::TestSubscriptionEnforcement::test_rate_limit_exhaustion_gets_429 PASSED tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_user_in_one_of_two_subscriptions_gets_200 PASSED tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_multi_tier_auto_select_highest PASSED tests/test_subscription.py::TestMultipleAuthPoliciesPerModel::test_two_auth_policies_or_logic PASSED tests/test_subscription.py::TestMultipleAuthPoliciesPerModel::test_delete_one_auth_policy_other_still_works PASSED tests/test_subscription.py::TestCascadeDeletion::test_delete_subscription_rebuilds_trlp PASSED tests/test_subscription.py::TestCascadeDeletion::test_delete_last_subscription_denies_access PASSED tests/test_subscription.py::TestOrderingEdgeCases::test_subscription_before_auth_policy PASSED tests/test_subscription.py::TestManagedAnnotation::test_authpolicy_managed_false_prevents_update PASSED tests/test_subscription.py::TestManagedAnnotation::test_trlp_managed_false_prevents_update PASSED tests/test_subscription.py::TestMaasSubscriptionNamespace::test_authpolicy_and_subscription_in_maas_subscription_namespace PASSED tests/test_subscription.py::TestMaasSubscriptionNamespace::test_authpolicy_and_subscription_in_another_namespace PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_with_both_access_and_subscription_gets_200 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_with_access_but_no_subscription_gets_403 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_with_subscription_but_no_access_gets_403 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_single_subscription_auto_selects PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_multiple_subscriptions_without_header_gets_403 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_multiple_subscriptions_with_valid_header_gets_200 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_multiple_subscriptions_with_invalid_header_gets_403 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_multiple_subscriptions_with_inaccessible_header_gets_403 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_group_based_access_gets_200 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_group_based_auth_but_no_subscription_gets_403 PASSED tests/test_subscription.py::TestE2ESubscriptionFlow::test_e2e_group_based_subscription_but_no_auth_gets_403 PASSED ================================================================= warnings summary ================================================================= tests/test_subscription.py: 29 warnings /home/jrhyness/src/models-as-a-service/test/e2e/.venv/lib64/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host 'maas.apps.rosa.u22hm-zn2m5-w2g.1u73.p3.openshiftapps.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings warnings.warn( -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html =================================================== 31 passed, 29 warnings in 492.93s (0:08:12) ==================================================== Result: ✅ 31/31 tests passed - All subscription authentication, authorization, multi-subscription, cascade deletion, and E2E flow tests passed successfully. Manual Verification - ✅ Verified AuthPolicy resources are correctly generated with new internal URL - ✅ Confirmed subscription selection callback works correctly during Authorino flow - ✅ Validated that old /v1/subscriptions/select endpoint no longer exists Merge criteria: - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Chores** * Reorganized internal API endpoint structure for subscription selection to align with authentication workflow architecture. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
## Summary - Add `git clone` and `cd` instructions before the first `deploy.sh` command Users need to clone the repository before running `./scripts/deploy.sh`. The quickstart assumed users already had the repo cloned without stating this. --- *Created by document-review workflow `/create-prs` phase.* Signed-off-by: Mynhardt Burger <mynhardt@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ore (#487) <!--- Provide a general summary of your changes in the Title above --> Related to https://issues.redhat.com/browse/RHOAIENG-51673 ## Description Summary Remove dead or unused testing code and scripts (e.g. redundant deploy/run wrappers, unused test helpers) and consolidate or remove redundant .gitignore files under test/ so that the test layout is clear and maintainable. Story As a maintainer, I want dead testing code and unused test scripts (e.g. duplicate deploy/run scripts) removed and redundant {{.gitignore}}s cleaned up, so that contributors and CI use a single clear test entry point and we avoid duplicate or obsolete test paths. Acceptance Criteria Audit test/ (e.g. test/e2e/run-model-and-smoke.sh, test/e2e/bootstrap.sh, test/maas_billing_tests_independent/, and other test scripts): remove or consolidate scripts that are unused, duplicated by prow_run_smoke_test.sh or smoke.sh, or obsolete. Keep only the scripts that are part of the intended flow (e.g. smoke.sh, prow script, auth_utils). Remove redundant .gitignore files (e.g. under test/e2e/, test/maas_billing_tests_independent/) where patterns are already covered by the root .gitignore or can be merged into a single test .gitignore without losing needed exclusions. No broken references to removed scripts; e2e and any documented test flows still run. Testability / Verification Run e2e (prow script or smoke.sh) and confirm tests still pass. Confirm no references to deleted scripts remain (grep or docs). Verify .gitignore behavior: ignored paths are still ignored and nothing necessary is accidentally tracked. ## How Has This Been Tested? Tested on cluster https://console-openshift-console.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/ ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % cd /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service && SKIP_DEPLOYMENT=true SKIP_VALIDATION=true E2E_SKIP_TLS_VERIFY=true ./test/e2e/scripts/prow_run_smoke_test.sh ---------------------------------------- Deploying Maas on OpenShift ---------------------------------------- Checking prerequisites... ✅ Prerequisites met - logged in as: kube:admin on OpenShift Skipping deployment (SKIP_DEPLOYMENT=true) Assuming MaaS platform and models are already deployed ---------------------------------------- Setting up variables for tests ---------------------------------------- -- Setting up variables for tests -- K8S_CLUSTER_URL: https://api.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org:6443 HOST: maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org MAAS_API_BASE_URL: https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/maas-api CLUSTER_DOMAIN: apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org ✅ Variables for tests setup completed ---------------------------------------- Admin Setup (Premium Test Resources) ---------------------------------------- Setting up premium test token (SA-based, works when oc whoami -t is unavailable)... Creating namespace: premium-users-namespace namespace/premium-users-namespace created Creating service account: premium-service-account serviceaccount/premium-service-account created Patching MaaSAuthPolicy premium-simulator-access to include system:serviceaccount:premium-users-namespace:premium-service-account... maasauthpolicy.maas.opendatahub.io/premium-simulator-access patched Patching MaaSSubscription premium-simulator-subscription to include system:serviceaccount:premium-users-namespace:premium-service-account... maassubscription.maas.opendatahub.io/premium-simulator-subscription patched ✅ Premium test token setup complete (E2E_TEST_TOKEN_SA_* exported) ---------------------------------------- Setting up test tokens ---------------------------------------- Setting up test tokens (admin + regular user)... Current admin session: kube:admin (will be preserved) ✅ Admin token for kube:admin (added to odh-admins) ✅ Regular user token for kube:admin (same as admin for local testing) Token setup complete (main session unchanged: kube:admin) ---------------------------------------- Running E2E Tests ---------------------------------------- -- E2E Tests (API Keys + Subscription) -- Running e2e tests with: - TOKEN: sha256~viySFSVlUVxuR... - ADMIN_OC_TOKEN: sha256~viySFSVlUVxuR... - GATEWAY_HOST: maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org ============================= test session starts ============================== platform darwin -- Python 3.13.3, pytest-8.4.2, pluggy-1.6.0 -- /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/.venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.13.3', 'Platform': 'macOS-15.7.4-arm64-arm-64bit-Mach-O', 'Packages': {'pytest': '8.4.2', 'pluggy': '1.6.0'}, 'Plugins': {'metadata': '3.1.1', 'html': '4.2.0'}} rootdir: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service plugins: metadata-3.1.1, html-4.2.0 collected 30 items test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_create_api_key [token] using env TOKEN (masked): 50 [create] Created key id=811be98a-21ed-4d11-943a-8be772581549, key prefix=sk-oai-dwfx7AGZ... PASSED [ 3%] test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_list_api_keys FAILED [ 6%] test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_revoke_api_key [revoke] Key eeffd5f5-f7e5-44f8-b0f2-a0c6550fd0e2 successfully revoked PASSED [ 10%] test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_admin_manage_other_users_keys [admin_token] using env ADMIN_OC_TOKEN (masked): 50 [admin] User 'kube:admin' created key ccedb884-8137-4c55-a17f-98679a78e541 [admin] Admin listed 8 keys for 'kube:admin' [admin] Admin successfully revoked user's key ccedb884-8137-4c55-a17f-98679a78e541 PASSED [ 13%] test/e2e/tests/test_api_keys.py::T estAPIKeyAuthorization::test_non_a dmin_cannot_access_other_users_key s FAILED [ 16%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_own_keys [bulk-revoke] Successfully revoked 11 keys for user kube:admin PASSED [ 20%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_other_user_forbidden [bulk-revoke] Non-admin correctly got 403 when trying to bulk revoke other user's keys PASSED [ 23%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_admin_can_revoke_any_user [bulk-revoke] Admin successfully revoked 1 keys for user kube:admin PASSED [ 26%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_model_access_success [api_key] Creating API key for inference tests... [api_key] Created API key id=ec224f1b-2e8f-424a-8559-1b174926df6c, key prefix=sk-oai-wqwZduWS... [inference] Model access succeeded: facebook/opt-125m, tokens=12 PASSED [ 30%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_invalid_api_key_rejected [inference] Invalid API key correctly rejected with 403 PASSED [ 33%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_no_auth_header_rejected [inference] Missing auth header correctly rejected with 401 PASSED [ 36%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_revoked_api_key_rejected [inference] Key before revoke: HTTP 200 [inference] Revoked key correctly rejected with 403 PASSED [ 40%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_chat_completions [inference] Chat completions succeeded: facebook/opt-125m PASSED [ 43%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_with_explicit_subscription_header [inference] API key with x-maas-subscription header succeeded PASSED [ 46%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_with_invalid_subscription_header [inference] API key with invalid subscription correctly rejected with 403 PASSED [ 50%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_authorized_user_gets_200 PASSED [ 53%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_no_auth_gets_401 PASSED [ 56%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_invalid_token_gets_403 PASSED [ 60%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_wrong_group_gets_403 PASSED [ 63%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_subscribed_user_gets_200 PASSED [ 66%] test/e2e/tests/test_subscription.py::TestSubscriptionEn t_auth_pass_no_subscription_gets_403 PASSED [ 70%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_invalid_subscription_header_gets_429 PASSED [ 73%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_explicit_subscription_header_works PASSED [ 76%] test/e2e/tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_user_in_one_of_two_subscriptions_gets_200 PASSED [ 80%] test/e2e/tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_multi_tier_auto_select_highest PASSED [ 83%] test/e2e/tests/test_subscription.py:odel::test_two_auth_policies_or_logPASSED [ 86%] test/e2e/tests/test_subscription.py: :TestMultipleAuthPoliciesPerModel::t est_delete_one_auth_policy_other_sti ll_works PASSED [ 90%] test/e2e/tests/test_subscription.py::TestCascadeDeletion::test_delete_subscription_rebuilds_trlp PASSED [ 93%] test/e2e/tests/test_subscription.py::TestCascadeDeletion::test_delete_last_subscription_denies_access PASSED [ 96%] test/e2e/tests/test_subscription.py::TestOrderingEdgeCases::test_subscription_before_auth_policy PASSED [100%] =================================== FAILURES =================================== ______________________ TestAPIKeyCRUD.test_list_api_keys _______________________ self = <test_api_keys.TestAPIKeyCRUD object at 0x1044fb4d0> api_keys_base_url = 'https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/maas-api/v1/api-keys' headers = {'Authorization': 'Bearer sha256~viySFSVlUVxuRXmhBeLaoOIbus4VoJtEVowPviu6z24', 'Content-Type': 'application/json'} def test_list_api_keys(self, api_keys_base_url: str, headers: dict): """Test 2: List own keys - verify basic functionality.""" # Create two keys r1 = requests.post(api_keys_base_url, headers=headers, json={"name": "test-key-list-1"}, timeout=30, verify=TLS_VERIFY) assert r1.status_code in (200, 201) key1_id = r1.json()["id"] r2 = requests.post(api_keys_base_url, headers=headers, json={"name": "test-key-list-2"}, timeout=30, verify=TLS_VERIFY) assert r2.status_code in (200, 201) key2_id = r2.json()["id"] # List keys using search endpoint r = requests.post( f"{api_keys_base_url}/search", headers=headers, json={ "filters": {"status": ["active"]}, "sort": {"by": "created_at", "order": "desc"}, "pagination": {"limit": 50, "offset": 0} }, timeout=30, verify=TLS_VERIFY ) > assert r.status_code == 200 E assert 500 == 200 E + where 500 = <Response [500]>.status_code test/e2e/tests/test_api_keys.py:94: AssertionError ____ TestAPIKeyAuthorization.test_non_admin_cannot_access_other_users_keys _____ self = <test_api_keys.TestAPIKeyAuthorization object at 0x1044fb610> api_keys_base_url = 'https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/maas-api/v1/api-keys' headers = {'Authorization': 'Bearer sha256~viySFSVlUVxuRXmhBeLaoOIbus4VoJtEVowPviu6z24', 'Content-Type': 'application/json'} admin_headers = {'Authorization': 'Bearer sha256~viySFSVlUVxuRXmhBeLaoOIbus4VoJtEVowPviu6z24', 'Content-Type': 'application/json'} def test_non_admin_cannot_access_other_users_keys(self, api_keys_base_url: str, headers: dict, admin_headers: dict): """Test 5: Non-admin cannot access other user's keys - verify denial. Note: API returns 404 instead of 403 for IDOR protection (prevents key enumeration). This is a security best practice - returning 403 would reveal the key exists. """ if not admin_headers: pytest.skip("ADMIN_OC_TOKEN not set") # Admin creates a key r_admin = requests.post(api_keys_base_url, headers=admin_headers, json={"name": "admin-only-key"}, timeout=30, verify=TLS_VERIFY) assert r_admin.status_code in (200, 201) admin_key_id = r_admin.json()["id"] # Regular user tries to GET admin's key - returns 404 for IDOR protection r_get = requests.get(f"{api_keys_base_url}/{admin_key_id}", headers=headers, timeout=30, verify=TLS_VERIFY) > assert r_get.status_code == 404, f"Expected 404 (IDOR protection), got {r_get.status_code}" E AssertionError: Expected 404 (IDOR protection), got 200 E assert 200 == 404 E + where 200 = <Response [200]>.status_code test/e2e/tests/test_api_keys.py:205: AssertionError - generated xml file: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/e2e-kube:admin.xml - - Generated html report: file:///Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/e2e-kube%3Aadmin.html - =========================== short test summary info ============================ FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_list_api_keys - assert 500 == 200 FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_non_admin_cannot_access_other_users_keys - AssertionError: Expected 404 (IDOR protection), got 200 ============ 2 failed, 28 passed, 32 warnings in 211.15s (0:03:31) ============= ❌ ERROR: E2E tests failed ========== E2E Artifact Collection ========== Artifact dir: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports Collecting Authorino logs (token-redacted) to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/authorino-debug.log Saved to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/authorino-debug.log Collecting cluster state to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports Saved to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/cluster-state.log Collecting pod logs from namespace opendatahub to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/pod-logs Saved 16 pod log file(s) to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/pod-logs ============================================== ========== Auth Debug Report ========== ======================================== Cluster / Namespace Info ======================================== --- Current context --- default/api-ci-ln-wxzb7yb-76ef8-aws-4-ci-openshift-org:6443/kube:admin --- Logged-in user --- kube:admin --- Cluster domain --- apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org MAAS_NAMESPACE: opendatahub AUTHORINO_NAMESPACE: kuadrant-system ======================================== MaaS API Deployment ======================================== --- maas-api pods --- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES maas-api-86b6d6f987-mcktb 1/1 Running 0 8m32s 10.128.2.44 ip-10-0-26-55.us-west-1.compute.internal <none> <none> --- maas-api service --- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR maas-api ClusterIP 172.30.7.186 <none> 8443/TCP 20m app.kubernetes.io/component=api,app.kubernetes.io/name=maas-api,app.kubernetes.io/part-of=models-as-a-service ======================================== maas-controller ======================================== --- maas-controller pods --- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES maas-controller-854bf8d4f8-cbrmc 1/1 Running 0 18m 10.129.2.25 ip-10-0-39-245.us-west-1.compute.internal <none> <none> --- maas-controller MAAS_API_NAMESPACE --- MAAS_API_NAMESPACE=metadata.namespace ======================================== Kuadrant AuthPolicies ======================================== --- AuthPolicies (all namespaces) --- NAMESPACE NAME ACCEPTED ENFORCED TARGETKIND TARGETNAME TARGETSECTION AGE llm maas-auth-facebook-opt-125m-simulated True True HTTPRoute facebook-opt-125m-simulated-kserve-route 2m2s llm maas-auth-premium-simulated-simulated-premium True True HTTPRoute premium-simulated-simulated-premium-kserve-route 57s opendatahub maas-api-auth-policy True True HTTPRoute maas-api-route 20m openshift-ingress gateway-auth-policy True True Gateway maas-default-gateway 20m ======================================== MaaS CRs ======================================== --- MaaSAuthPolicies --- NAME PHASE AGE AUTHPOLICIES premium-simulator-access Active 16m maas-auth-premium-simulated-simulated-premium simulator-access Active 16m maas-auth-facebook-opt-125m-simulated --- MaaSSubscriptions --- NAME PHASE PRIORITY AGE premium-simulator-subscription Active 0 16m simulator-subscription Active 0 86s --- MaaSModelRefs --- NAME PHASE ENDPOINT HTTPROUTE GATEWAY AGE e2e-unconfigured-facebook-opt-125m-simulated Ready https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/llm/e2e-unconfigured-facebook-opt-125m-simulated e2e-unconfigured-facebook-opt-125m-simulated-kserve-route maas-default-gateway 16m facebook-opt-125m-simulated Ready https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/llm/facebook-opt-125m-simulated facebook-opt-125m-simulated-kserve-route maas-default-gateway 16m premium-simulated-simulated-premium Ready https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/llm/premium-simulated-simulated-premium premium-simulated-simulated-premium-kserve-route maas-default-gateway 16m ======================================== Gateway / HTTPRoutes ======================================== --- Gateway --- NAME CLASS ADDRESS PROGRAMMED AGE maas-default-gateway openshift-default a400e45cbfc0749d79007e636fa9fa9f-439495217.us-west-1.elb.amazonaws.com True 23m --- HTTPRoutes (maas-api) --- NAME HOSTNAMES AGE maas-api-route 20m ======================================== Authorino ======================================== --- Authorino pods --- --- ======================================== Subscription Selector Endpoint Validation ======================================== Expected URL (from maas-controller config): https://maas-api.opendatahub.svc.cluster.local:8443/v1/subscriptions/select --- Connectivity test (from kuadrant-system, simulates Authorino) --- curl -vsk -m 10 -X POST 'https://maas-api.opendatahub.svc.cluster.local:8443/v1/subscriptions/select' -H 'Content-Type: application/json' -d '{}' kubectl run failed or timed out ======================================== DNS Resolution Check ======================================== Resolving: maas-api.opendatahub.svc.cluster.local nslookup failed ====================================== ``` ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % cd /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service && E2E_SKIP_TLS_VERIFY=true MODEL_NAME=facebook-opt-125m-simulated ./test/e2e/smoke.sh [smoke] Setting up Python virtual environment... [smoke] Activating virtual environment [smoke] Installing Python dependencies [smoke] Virtual environment setup complete [smoke] MAAS_API_BASE_URL=https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/maas-api [smoke] Using MODEL_NAME=facebook-opt-125m-simulated [smoke] Performing smoke test for user: kube:admin [smoke] Setting up admin token for admin tests... [smoke] Added kube:admin to odh-admins group [smoke] ADMIN_OC_TOKEN configured - admin tests will run [smoke] models catalog empty (attempt 1/10), retrying in 3s... [smoke] models catalog empty (attempt 2/10), retrying in 3s... [smoke] models catalog empty (attempt 3/10), retrying in 3s... [smoke] models catalog empty (attempt 4/10), retrying in 3s... [smoke] models catalog empty (attempt 5/10), retrying in 3s... [smoke] models catalog empty (attempt 6/10), retrying in 3s... [smoke] models catalog empty (attempt 7/10), retrying in 3s... [smoke] models catalog empty (attempt 8/10), retrying in 3s... [smoke] models catalog empty (attempt 9/10), retrying in 3s... [smoke] models catalog empty (attempt 10/10), retrying in 3s... [smoke] Using MODEL_URL=https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/llm/facebook-opt-125m-simulated/v1 [token] using env TOKEN (masked): 50 [create] Created key id=8631bd6e-3168-4d98-b220-d3be1043ce01, key prefix=sk-oai-tTvemsxv... .[list] Found 3 keys [list] Pagination works: limit=1 returned 1 items .[revoke] Key 8ae795e9-cfe2-461a-bd15-52d97c9ffb18 successfully revoked .[admin_token] using env ADMIN_OC_TOKEN (masked): 50 [admin] User 'kube:admin' created key 114afcd2-a1e3-48f7-b4a1-6047d094a3d1 [admin] Admin listed 4 keys for 'kube:admin' [admin] Admin successfully revoked user's key 114afcd2-a1e3-48f7-b4a1-6047d094a3d1 .F ================================= FAILURES ================================= __ TestAPIKeyAuthorization.test_non_admin_cannot_access_other_users_keys ___ self = <test_api_keys.TestAPIKeyAuthorization object at 0x10318f4d0> api_keys_base_url = 'https://maas.apps.ci-ln-wxzb7yb-76ef8.aws-4.ci.openshift.org/maas-api/v1/api-keys' headers = {'Authorization': 'Bearer sha256~viySFSVlUVxuRXmhBeLaoOIbus4VoJtEVowPviu6z24', 'Content-Type': 'application/json'} admin_headers = {'Authorization': 'Bearer sha256~viySFSVlUVxuRXmhBeLaoOIbus4VoJtEVowPviu6z24', 'Content-Type': 'application/json'} def test_non_admin_cannot_access_other_users_keys(self, api_keys_base_url: str, headers: dict, admin_headers: dict): """Test 5: Non-admin cannot access other user's keys - verify denial. Note: API returns 404 instead of 403 for IDOR protection (prevents key enumeration). This is a security best practice - returning 403 would reveal the key exists. """ if not admin_headers: pytest.skip("ADMIN_OC_TOKEN not set") # Admin creates a key r_admin = requests.post(api_keys_base_url, headers=admin_headers, json={"name": "admin-only-key"}, timeout=30, verify=TLS_VERIFY) assert r_admin.status_code in (200, 201) admin_key_id = r_admin.json()["id"] # Regular user tries to GET admin's key - returns 404 for IDOR protection r_get = requests.get(f"{api_keys_base_url}/{admin_key_id}", headers=headers, timeout=30, verify=TLS_VERIFY) > assert r_get.status_code == 404, f"Expected 404 (IDOR protection), got {r_get.status_code}" E AssertionError: Expected 404 (IDOR protection), got 200 E assert 200 == 404 E + where 200 = <Response [200]>.status_code test/e2e/tests/test_api_keys.py:205: AssertionError - generated xml file: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/smoke-kube:admin.xml - - Generated html report: file:///Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/smoke-kube%3Aadmin.html - ========================= short test summary info ========================== FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_non_admin_cannot_access_other_users_keys - AssertionError: Expected 404 (IDOR protection), got 200 !!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!! somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ``` Both smoke.sh and prow_run_smoke_test.sh are working on the live cluster. ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % git check-ignore -v \ test/e2e/__pycache__/ \ test/e2e/.pytest_cache/ \ test/e2e/.venv/ \ test/e2e/.env \ test/e2e/reports/ \ test/e2e/something.log \ test/e2e/.vscode/ \ test/e2e/.DS_Store .gitignore:27:__pycache__/ test/e2e/__pycache__/ .gitignore:42:.pytest_cache/ test/e2e/.pytest_cache/ .gitignore:32:.venv/ test/e2e/.venv/ .gitignore:3:.env test/e2e/.env test/.gitignore:2:reports/ test/e2e/reports/ .gitignore:2:*.log test/e2e/something.log somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % git check-ignore -v \ test/maas_billing_tests_independent/.venv/ \ test/maas_billing_tests_independent/__pycache__/ \ test/maas_billing_tests_independent/.pytest_cache/ \ test/maas_billing_tests_independent/artifacts/somefile \ test/maas_billing_tests_independent/reports/somefile \ test/maas_billing_tests_independent/ingress-ca.crt .gitignore:32:.venv/ test/maas_billing_tests_independent/.venv/ .gitignore:27:__pycache__/ test/maas_billing_tests_independent/__pycache__/ .gitignore:42:.pytest_cache/ test/maas_billing_tests_independent/.pytest_cache/ test/.gitignore:3:**/artifacts/* test/maas_billing_tests_independent/artifacts/somefile test/.gitignore:2:reports/ test/maas_billing_tests_independent/reports/somefile test/.gitignore:7:ingress-ca.crt test/maas_billing_tests_independent/ingress-ca.crt somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % git check-ignore test/maas_billing_tests_independent/artifacts/.gitkeep somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ``` As per discussion with @SB159 , `maas_billing_tests_independent ` has also being cleaned ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % find /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent -type f | head -30 /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/pytest.ini /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/requirements.txt /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_token_ratelimit.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/conftest.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_tool_calling.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_streaming_chat.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_limits_interplay.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_models_user.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_rbac.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/run-billing-tests.sh /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/README.MD /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_quota_global.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_maas_api_mint_and_models.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_gateway_endpoints.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_quota_per_user.py /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent/tests/test_tokens.py somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ls -la /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/ total 8 drwxr-xr-x 5 somyabhatnagar staff 160 Mar 6 23:55 . drwxr-xr-x@ 17 somyabhatnagar staff 544 Mar 6 04:50 .. -rw-r--r--@ 1 somyabhatnagar staff 152 Mar 7 01:02 .gitignore drwxr-xr-x 9 somyabhatnagar staff 288 Mar 6 11:54 e2e drwxr-xr-x@ 5 somyabhatnagar staff 160 Mar 6 23:55 maas_billing_tests_independent somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % rm -rf /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/maas_billing_tests_independent ``` After the cleanup, ran tests on cluster again ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % cd /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service && SKIP_DEPLOYMENT=true SKIP_VALIDATION=true E2E_SKIP_TLS_VERIFY=true ./test/e2e/scripts/prow_run_smoke_test.sh ---------------------------------------- Deploying Maas on OpenShift ---------------------------------------- Checking prerequisites... ✅ Prerequisites met - logged in as: kube:admin on OpenShift Skipping deployment (SKIP_DEPLOYMENT=true) Assuming MaaS platform and models are already deployed ---------------------------------------- Setting up variables for tests ---------------------------------------- -- Setting up variables for tests -- K8S_CLUSTER_URL: https://api.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org:6443 HOST: maas.apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org MAAS_API_BASE_URL: https://maas.apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org/maas-api CLUSTER_DOMAIN: apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org ✅ Variables for tests setup completed ---------------------------------------- Admin Setup (Premium Test Resources) ---------------------------------------- Setting up premium test token (SA-based, works when oc whoami -t is unavailable)... Creating namespace: premium-users-namespace namespace/premium-users-namespace created Creating service account: premium-service-account serviceaccount/premium-service-account created Patching MaaSAuthPolicy premium-simulator-access to include system:serviceaccount:premium-users-namespace:premium-service-account... maasauthpolicy.maas.opendatahub.io/premium-simulator-access patched Patching MaaSSubscription premium-simulator-subscription to include system:serviceaccount:premium-users-namespace:premium-service-account... maassubscription.maas.opendatahub.io/premium-simulator-subscription patched ✅ Premium test token setup complete (E2E_TEST_TOKEN_SA_* exported) ---------------------------------------- Setting up test tokens ---------------------------------------- Setting up test tokens (admin + regular user)... Current admin session: kube:admin (will be preserved) ✅ Admin token for kube:admin (added to odh-admins) ✅ Regular user token for kube:admin (same as admin for local testing) Token setup complete (main session unchanged: kube:admin) ---------------------------------------- Running E2E Tests ---------------------------------------- -- E2E Tests (API Keys + Subscription) -- Running e2e tests with: - TOKEN: sha256~K8ztKJZ_tkMXs... - ADMIN_OC_TOKEN: sha256~K8ztKJZ_tkMXs... - GATEWAY_HOST: maas.apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org ================================== test session starts ================================== platform darwin -- Python 3.13.3, pytest-8.4.2, pluggy-1.6.0 -- /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/.venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.13.3', 'Platform': 'macOS-15.7.4-arm64-arm-64bit-Mach-O', 'Packages': {'pytest': '8.4.2', 'pluggy': '1.6.0'}, 'Plugins': {'metadata': '3.1.1', 'html': '4.2.0'}} rootdir: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service plugins: metadata-3.1.1, html-4.2.0 collected 30 items test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_create_api_key [token] using env TOKEN (masked): 50 [create] Created key id=157847b9-ad26-4999-8cbc-7bf6302c0d85, key prefix=sk-oai-1IWDUUqa... PASSED [ 3%] test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_list_api_keys [list] Found 7 keys [list] Pagination works: limit=1 returned 1 items PASSED [ 6%] test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_revoke_api_key [revoke] Key e3eaede5-c230-45ae-99f9-0d9f1e62caf5 successfully revoked PASSED [ 10%] test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_admin_manage_other_users_keys [admin_token] using env ADMIN_OC_TOKEN (masked): 50 [admin] User 'kube:admin' created key 10c9e1eb-633f-4d9f-89be-2e495c04125f [admin] Admin listed 8 keys for 'kube:admin' [admin] Admin successfully revoked user's key 10c9e1eb-633f-4d9f-89be-2e495c04125f PASSED [ 13%] test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_non_admin_cannot_access_other_users_keys FAILED [ 16%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_own_keys [bulk-revoke] Successfully revoked 11 keys for user kube:admin PASSED [ 20%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_other_user_forbidden [bulk-revoke] Non-admin correctly got 403 when trying to bulk revoke other user's keys PASSED [ 23%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_admin_can_revoke_any_user [bulk-revoke] Admin successfully revoked 1 keys for user kube:admin PASSED [ 26%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_model_access_success [api_key] Creating API key for inference tests... [api_key] Created API key id=ef238728-0091-4036-b55d-bb30778d3887, key prefix=sk-oai-Yc7THMz7... [inference] Model access succeeded: facebook/opt-125m, tokens=7 PASSED [ 30%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_invalid_api_key_rejected [inference] Invalid API key correctly rejected with 403 PASSED [ 33%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_no_auth_header_rejected [inference] Missing auth header correctly rejected with 401 PASSED [ 36%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_revoked_api_key_rejected [inference] Key before revoke: HTTP 200 [inference] Revoked key correctly rejected with 403 PASSED [ 40%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_chat_completions [inference] Chat completions succeeded: facebook/opt-125m PASSED [ 43%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_with_explicit_subscription_header [inference] API key with x-maas-subscription header succeeded PASSED [ 46%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_with_invalid_subscription_header [inference] API key with invalid subscription correctly rejected with 403 PASSED [ 50%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_authorized_user_gets_200 PASSED [ 53%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_no_auth_gets_401 PASSED [ 56%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_invalid_token_gets_403 PASSED [ 60%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_wrong_group_gets_403 PASSED [ 63%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_subscribed_user_gets_200 PASSED [ 66%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_auth_pass_no_subscription_gets_403 PASSED [ 70%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_invalid_subscription_header_gets_429 PASSED [ 73%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_explicit_subscription_header_works PASSED [ 76%] test/e2e/tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_user_in_one_of_two_subscriptions_gets_200 PASSED [ 80%] test/e2e/tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_multi_tier_auto_select_highest FAILED [ 83%] test/e2e/tests/test_subscription.py::TestMultipleAuthPoliciesPerModel::test_two_auth_policies_or_logic FAILED [ 86%] test/e2e/tests/test_subscription.py::TestMultipleAuthPoliciesPerModel::test_delete_one_auth_policy_other_still_works PASSED [ 90%] test/e2e/tests/test_subscription.py::TestCascadeDeletion::test_delete_subscription_rebuilds_trlp PASSED [ 93%] test/e2e/tests/test_subscription.py::TestCascadeDeletion::test_delete_last_subscription_denies_access FAILED [ 96%] test/e2e/tests/test_subscription.py::TestOrderingEdgeCases::test_subscription_before_auth_policy FAILED [100%] ======================================= FAILURES ======================================== _________ TestAPIKeyAuthorization.test_non_admin_cannot_access_other_users_keys _________ self = <test_api_keys.TestAPIKeyAuthorization object at 0x102313610> api_keys_base_url = 'https://maas.apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org/maas-api/v1/api-keys' headers = {'Authorization': 'Bearer sha256~K8ztKJZ_tkMXsjy2_W454fwmp4yRkif0fsMDOFphpUg', 'Content-Type': 'application/json'} admin_headers = {'Authorization': 'Bearer sha256~K8ztKJZ_tkMXsjy2_W454fwmp4yRkif0fsMDOFphpUg', 'Content-Type': 'application/json'} def test_non_admin_cannot_access_other_users_keys(self, api_keys_base_url: str, headers: dict, admin_headers: dict): """Test 5: Non-admin cannot access other user's keys - verify denial. Note: API returns 404 instead of 403 for IDOR protection (prevents key enumeration). This is a security best practice - returning 403 would reveal the key exists. """ if not admin_headers: pytest.skip("ADMIN_OC_TOKEN not set") # Admin creates a key r_admin = requests.post(api_keys_base_url, headers=admin_headers, json={"name": "admin-only-key"}, timeout=30, verify=TLS_VERIFY) assert r_admin.status_code in (200, 201) admin_key_id = r_admin.json()["id"] # Regular user tries to GET admin's key - returns 404 for IDOR protection r_get = requests.get(f"{api_keys_base_url}/{admin_key_id}", headers=headers, timeout=30, verify=TLS_VERIFY) > assert r_get.status_code == 404, f"Expected 404 (IDOR protection), got {r_get.status_code}" E AssertionError: Expected 404 (IDOR protection), got 200 E assert 200 == 404 E + where 200 = <Response [200]>.status_code test/e2e/tests/test_api_keys.py:205: AssertionError _________ TestMultipleSubscriptionsPerModel.test_multi_tier_auto_select_highest _________ self = <test_subscription.TestMultipleSubscriptionsPerModel object at 0x102400690> def test_multi_tier_auto_select_highest(self): """With 2 tiers for the same model, API key in both should still get access. (Verifies multiple overlapping subscriptions don't break routing.)""" ns = _ns() try: _apply_cr({ "apiVersion": "maas.opendatahub.io/v1alpha1", "kind": "MaaSSubscription", "metadata": {"name": "e2e-high-tier", "namespace": ns}, "spec": { "owner": {"groups": [{"name": "system:authenticated"}]}, "modelRefs": [{"name": MODEL_REF, "tokenRateLimits": [{"limit": 9999, "window": "1m"}]}], }, }) api_key = _get_default_api_key() > _poll_status(api_key, 200, extra_headers={"x-maas-subscription": "e2e-high-tier"}) test/e2e/tests/test_subscription.py:521: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ token = 'sk-oai-1lXBtNeec4lILk7NKmL1VyvkSMdDGdt6YExSvzHKKyq', expected = 200, path = None extra_headers = {'x-maas-subscription': 'e2e-high-tier'}, subscription = None timeout = 60, poll_interval = 2 def _poll_status(token, expected, path=None, extra_headers=None, subscription=None, timeout=None, poll_interval=2): """Poll inference endpoint until expected HTTP status or timeout.""" timeout = timeout or max(RECONCILE_WAIT * 3, 60) deadline = time.time() + timeout last = None last_err = None while time.time() < deadline: try: r = _inference(token, path=path, extra_headers=extra_headers, subscription=subscription) last_err = None ok = r.status_code == expected if isinstance(expected, int) else r.status_code in expected if ok: return r last = r except requests.RequestException as exc: last_err = exc log.debug(f"Transient request error while polling: {exc}") except Exception as exc: # Catch-all to surface non-RequestException (e.g. JSON decode, timeout config) last_err = exc log.warning(f"Unexpected error while polling: {exc}") time.sleep(poll_interval) # Build failure message with all available context exp_str = expected if isinstance(expected, int) else " or ".join(str(e) for e in expected) err_msg = f"Expected {exp_str} within {timeout}s" if last is not None: err_msg += f", last status: {last.status_code}" if last_err is not None: err_msg += f", last error: {last_err}" if last is None and last_err is None: err_msg += ", no response (all requests may have raised non-RequestException)" > raise AssertionError(err_msg) E AssertionError: Expected 200 within 60s, last status: 403 test/e2e/tests/test_subscription.py:338: AssertionError ___________ TestMultipleAuthPoliciesPerModel.test_two_auth_policies_or_logic ____________ self = <test_subscription.TestMultipleAuthPoliciesPerModel object at 0x1024007d0> def test_two_auth_policies_or_logic(self): """Two auth policies for the premium model with OR logic: user matching either gets access.""" ns = _ns() try: # Create a 2nd auth policy that allows system:authenticated (user's actual group) _apply_cr({ "apiVersion": "maas.opendatahub.io/v1alpha1", "kind": "MaaSAuthPolicy", "metadata": {"name": "e2e-premium-sa-auth", "namespace": ns}, "spec": { "modelRefs": [PREMIUM_MODEL_REF], "subjects": {"groups": [{"name": "system:authenticated"}]}, }, }) # Create a subscription for system:authenticated on premium model _apply_cr({ "apiVersion": "maas.opendatahub.io/v1alpha1", "kind": "MaaSSubscription", "metadata": {"name": "e2e-premium-sa-sub", "namespace": ns}, "spec": { "owner": {"groups": [{"name": "system:authenticated"}]}, "modelRefs": [{"name": PREMIUM_MODEL_REF, "tokenRateLimits": [{"limit": 100, "window": "1m"}]}], }, }) _wait_reconcile() # Default API key (inherits user's system:authenticated group) should now work api_key = _get_default_api_key() > r = _poll_status(api_key, 200, path=PREMIUM_MODEL_PATH, subscription="e2e-premium-sa-sub") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ test/e2e/tests/test_subscription.py:561: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ token = 'sk-oai-1lXBtNeec4lILk7NKmL1VyvkSMdDGdt6YExSvzHKKyq', expected = 200 path = '/llm/premium-simulated-simulated-premium', extra_headers = None subscription = 'e2e-premium-sa-sub', timeout = 60, poll_interval = 2 def _poll_status(token, expected, path=None, extra_headers=None, subscription=None, timeout=None, poll_interval=2): """Poll inference endpoint until expected HTTP status or timeout.""" timeout = timeout or max(RECONCILE_WAIT * 3, 60) deadline = time.time() + timeout last = None last_err = None while time.time() < deadline: try: r = _inference(token, path=path, extra_headers=extra_headers, subscription=subscription) last_err = None ok = r.status_code == expected if isinstance(expected, int) else r.status_code in expected if ok: return r last = r except requests.RequestException as exc: last_err = exc log.debug(f"Transient request error while polling: {exc}") except Exception as exc: # Catch-all to surface non-RequestException (e.g. JSON decode, timeout config) last_err = exc log.warning(f"Unexpected error while polling: {exc}") time.sleep(poll_interval) # Build failure message with all available context exp_str = expected if isinstance(expected, int) else " or ".join(str(e) for e in expected) err_msg = f"Expected {exp_str} within {timeout}s" if last is not None: err_msg += f", last status: {last.status_code}" if last_err is not None: err_msg += f", last error: {last_err}" if last is None and last_err is None: err_msg += ", no response (all requests may have raised non-RequestException)" > raise AssertionError(err_msg) E AssertionError: Expected 200 within 60s, last status: 403 test/e2e/tests/test_subscription.py:338: AssertionError ____________ TestCascadeDeletion.test_delete_last_subscription_denies_access ____________ self = <test_subscription.TestCascadeDeletion object at 0x102400b90> def test_delete_last_subscription_denies_access(self): """Delete all subscriptions for a model -> access denied (403 or 429). When the last subscription is deleted, access is denied. The exact code depends on which policy evaluates first: - 403: AuthPolicy's subscription-error-check denies (no subscription found) - 429: Default-deny TRLP with 0 tokens kicks in Both indicate the intended behavior: no subscription = no access. """ api_key = _get_default_api_key() original = _snapshot_cr("maassubscription", SIMULATOR_SUBSCRIPTION) assert original, f"Pre-existing {SIMULATOR_SUBSCRIPTION} not found" try: _delete_cr("maassubscription", SIMULATOR_SUBSCRIPTION) # With no subscription, expect either 403 or 429 (both = access denied) > r = _poll_status(api_key, [403, 429], subscription=False, timeout=30) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ test/e2e/tests/test_subscription.py:638: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ token = 'sk-oai-1lXBtNeec4lILk7NKmL1VyvkSMdDGdt6YExSvzHKKyq', expected = [403, 429] path = None, extra_headers = None, subscription = False, timeout = 30, poll_interval = 2 def _poll_status(token, expected, path=None, extra_headers=None, subscription=None, timeout=None, poll_interval=2): """Poll inference endpoint until expected HTTP status or timeout.""" timeout = timeout or max(RECONCILE_WAIT * 3, 60) deadline = time.time() + timeout last = None last_err = None while time.time() < deadline: try: r = _inference(token, path=path, extra_headers=extra_headers, subscription=subscription) last_err = None ok = r.status_code == expected if isinstance(expected, int) else r.status_code in expected if ok: return r last = r except requests.RequestException as exc: last_err = exc log.debug(f"Transient request error while polling: {exc}") except Exception as exc: # Catch-all to surface non-RequestException (e.g. JSON decode, timeout config) last_err = exc log.warning(f"Unexpected error while polling: {exc}") time.sleep(poll_interval) # Build failure message with all available context exp_str = expected if isinstance(expected, int) else " or ".join(str(e) for e in expected) err_msg = f"Expected {exp_str} within {timeout}s" if last is not None: err_msg += f", last status: {last.status_code}" if last_err is not None: err_msg += f", last error: {last_err}" if last is None and last_err is None: err_msg += ", no response (all requests may have raised non-RequestException)" > raise AssertionError(err_msg) E AssertionError: Expected 403 or 429 within 30s, last status: 200 test/e2e/tests/test_subscription.py:338: AssertionError ______________ TestOrderingEdgeCases.test_subscription_before_auth_policy _______________ self = <test_subscription.TestOrderingEdgeCases object at 0x102400cd0> def test_subscription_before_auth_policy(self): """Create subscription first, then auth policy -> should work once both exist.""" ns = _ns() try: # Get the default API key (inherits user's groups including system:authenticated) api_key = _get_default_api_key() # Create subscription first (for system:authenticated group) _apply_cr({ "apiVersion": "maas.opendatahub.io/v1alpha1", "kind": "MaaSSubscription", "metadata": {"name": "e2e-ordering-sub", "namespace": ns}, "spec": { "owner": {"groups": [{"name": "system:authenticated"}]}, "modelRefs": [{"name": PREMIUM_MODEL_REF, "tokenRateLimits": [{"limit": 100, "window": "1m"}]}], }, }) _wait_reconcile() # Without auth policy for system:authenticated on premium model, request should fail with 403 r1 = _inference(api_key, path=PREMIUM_MODEL_PATH, subscription="e2e-ordering-sub") log.info(f"Sub only (no auth policy) -> {r1.status_code}") assert r1.status_code == 403, f"Expected 403 (no auth policy yet), got {r1.status_code}" # Now add the auth policy _apply_cr({ "apiVersion": "maas.opendatahub.io/v1alpha1", "kind": "MaaSAuthPolicy", "metadata": {"name": "e2e-ordering-auth", "namespace": ns}, "spec": { "modelRefs": [PREMIUM_MODEL_REF], "subjects": {"groups": [{"name": "system:authenticated"}]}, }, }) # Now it should work > r2 = _poll_status(api_key, 200, path=PREMIUM_MODEL_PATH, subscription="e2e-ordering-sub") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ test/e2e/tests/test_subscription.py:692: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ token = 'sk-oai-1lXBtNeec4lILk7NKmL1VyvkSMdDGdt6YExSvzHKKyq', expected = 200 path = '/llm/premium-simulated-simulated-premium', extra_headers = None subscription = 'e2e-ordering-sub', timeout = 60, poll_interval = 2 def _poll_status(token, expected, path=None, extra_headers=None, subscription=None, timeout=None, poll_interval=2): """Poll inference endpoint until expected HTTP status or timeout.""" timeout = timeout or max(RECONCILE_WAIT * 3, 60) deadline = time.time() + timeout last = None last_err = None while time.time() < deadline: try: r = _inference(token, path=path, extra_headers=extra_headers, subscription=subscription) last_err = None ok = r.status_code == expected if isinstance(expected, int) else r.status_code in expected if ok: return r last = r except requests.RequestException as exc: last_err = exc log.debug(f"Transient request error while polling: {exc}") except Exception as exc: # Catch-all to surface non-RequestException (e.g. JSON decode, timeout config) last_err = exc log.warning(f"Unexpected error while polling: {exc}") time.sleep(poll_interval) # Build failure message with all available context exp_str = expected if isinstance(expected, int) else " or ".join(str(e) for e in expected) err_msg = f"Expected {exp_str} within {timeout}s" if last is not None: err_msg += f", last status: {last.status_code}" if last_err is not None: err_msg += f", last error: {last_err}" if last is None and last_err is None: err_msg += ", no response (all requests may have raised non-RequestException)" > raise AssertionError(err_msg) E AssertionError: Expected 200 within 60s, last status: 403 test/e2e/tests/test_subscription.py:338: AssertionError ----------------------------------- Captured log call ----------------------------------- INFO test_subscription:test_subscription.py:677 Sub only (no auth policy) -> 403 - generated xml file: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/e2e-kube:admin.xml - - Generated html report: file:///Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/e2e-kube%3Aadmin.html - ================================ short test summary info ================================ FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_non_admin_cannot_access_other_users_keys - AssertionError: Expected 404 (IDOR protection), got 200 FAILED test/e2e/tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_multi_tier_auto_select_highest - AssertionError: Expected 200 within 60s, last status: 403 FAILED test/e2e/tests/test_subscription.py::TestMultipleAuthPoliciesPerModel::test_two_auth_policies_or_logic - AssertionError: Expected 200 within 60s, last status: 403 FAILED test/e2e/tests/test_subscription.py::TestCascadeDeletion::test_delete_last_subscription_denies_access - AssertionError: Expected 403 or 429 within 30s, last status: 200 FAILED test/e2e/tests/test_subscription.py::TestOrderingEdgeCases::test_subscription_before_auth_policy - AssertionError: Expected 200 within 60s, last status: 403 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 5 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ================= 5 failed, 25 passed, 32 warnings in 439.20s (0:07:19) ================= ❌ ERROR: E2E tests failed ========== E2E Artifact Collection ========== Artifact dir: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports Collecting Authorino logs (token-redacted) to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/authorino-debug.log Saved to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/authorino-debug.log Collecting cluster state to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports Saved to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/cluster-state.log Collecting pod logs from namespace opendatahub to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/pod-logs Saved 24 pod log file(s) to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/pod-logs ============================================== ========== Auth Debug Report ========== ======================================== Cluster / Namespace Info ======================================== --- Current context --- default/api-ci-ln-2xgfz8b-76ef8-aws-4-ci-openshift-org:6443/kube:admin --- Logged-in user --- kube:admin --- Cluster domain --- apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org MAAS_NAMESPACE: opendatahub AUTHORINO_NAMESPACE: kuadrant-system ======================================== MaaS API Deployment ======================================== --- maas-api pods --- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES maas-api-86644774cc-2j7jf 1/1 Running 0 77m 10.129.2.46 ip-10-0-112-143.ec2.internal <none> <none> --- maas-api service --- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR maas-api ClusterIP 172.30.51.20 <none> 8443/TCP 105m app.kubernetes.io/component=api,app.kubernetes.io/name=maas-api,app.kubernetes.io/part-of=models-as-a-service ======================================== maas-controller ======================================== --- maas-controller pods --- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES maas-controller-854bf8d4f8-9ngfr 1/1 Running 0 103m 10.129.2.37 ip-10-0-112-143.ec2.internal <none> <none> --- maas-controller MAAS_API_NAMESPACE --- MAAS_API_NAMESPACE=metadata.namespace ======================================== Kuadrant AuthPolicies ======================================== --- AuthPolicies (all namespaces) --- NAMESPACE NAME ACCEPTED ENFORCED TARGETKIND TARGETNAME TARGETSECTION AGE llm maas-auth-facebook-opt-125m-simulated True True HTTPRoute facebook-opt-125m-simulated-kserve-route 3m57s llm maas-auth-premium-simulated-simulated-premium True True HTTPRoute premium-simulated-simulated-premium-kserve-route 76s opendatahub maas-api-auth-policy True True HTTPRoute maas-api-route 105m openshift-ingress gateway-auth-policy True True Gateway maas-default-gateway 105m ======================================== MaaS CRs ======================================== --- MaaSAuthPolicies --- NAME PHASE AGE AUTHPOLICIES premium-simulator-access Active 90m maas-auth-premium-simulated-simulated-premium simulator-access Active 90m maas-auth-facebook-opt-125m-simulated --- MaaSSubscriptions --- NAME PHASE PRIORITY AGE premium-simulator-subscription Active 0 90m simulator-subscription Active 0 2m46s --- MaaSModelRefs --- NAME PHASE ENDPOINT HTTPROUTE GATEWAY AGE e2e-unconfigured-facebook-opt-125m-simulated Ready https://maas.apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org/llm/e2e-unconfigured-facebook-opt-125m-simulated e2e-unconfigured-facebook-opt-125m-simulated-kserve-route maas-default-gateway 90m facebook-opt-125m-simulated Ready https://maas.apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org/llm/facebook-opt-125m-simulated facebook-opt-125m-simulated-kserve-route maas-default-gateway 90m premium-simulated-simulated-premium Ready https://maas.apps.ci-ln-2xgfz8b-76ef8.aws-4.ci.openshift.org/llm/premium-simulated-simulated-premium premium-simulated-simulated-premium-kserve-route maas-default-gateway 90m ======================================== Gateway / HTTPRoutes ======================================== --- Gateway --- NAME CLASS ADDRESS PROGRAMMED AGE maas-default-gateway openshift-default a2d5d6bbaf9eb47d2b70203bc5863c5e-864156544.us-east-1.elb.amazonaws.com True 109m --- HTTPRoutes (maas-api) --- NAME HOSTNAMES AGE maas-api-route 106m ======================================== Authorino ======================================== --- Authorino pods --- --- ======================================== Subscription Selector Endpoint Validation ======================================== Expected URL (from maas-controller config): https://maas-api.opendatahub.svc.cluster.local:8443/v1/subscriptions/select --- Connectivity test (from kuadrant-system, simulates Authorino) --- curl -vsk -m 10 -X POST 'https://maas-api.opendatahub.svc.cluster.local:8443/v1/subscriptions/select' -H 'Content-Type: application/json' -d '{}' kubectl run failed or timed out ======================================== DNS Resolution Check ======================================== Resolving: maas-api.opendatahub.svc.cluster.local nslookup failed ====================================== ``` ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated ignore rules across repo and test areas to exclude common Python/tooling/editor artifacts. * Removed numerous legacy end-to-end test scripts and supporting test harness files. * **Documentation** * Simplified end-to-end test instructions to run pytest directly within a virtual environment; bootstrap orchestration docs removed. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Mynhardt Burger <mynhardt@gmail.com>
) # Extract Authorino TLS and database setup into standalone scripts ## Description Support work for the manual install documentation. - **`scripts/setup-authorino-tls.sh`** — Moved from `deployment/overlays/tls-backend/configure-authorino-tls.sh`; configures Authorino for TLS with maas-api. - **`scripts/setup-database.sh`** — New script for PostgreSQL deployment for API key storage. - **`scripts/deploy.sh`** — Calls these scripts instead of inlining their logic. ## How Has This Been Tested? - Ran `./scripts/deploy.sh` on OpenShift; deployment and validation succeeded. - Ran `setup-database.sh` and `setup-authorino-tls.sh` manually and confirmed they work. ## Merge criteria - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Expanded scripts documentation with detailed usage examples, prerequisites, and configuration options for TLS and database setup. * **New Features** * Added automated PostgreSQL database provisioning with built-in credential generation and readiness validation. * Enhanced deployment workflow with modular scripts and improved user guidance for TLS configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary - Replace stale `deploy-rhoai-stable.sh` reference with `deploy.sh` in quickstart The `deploy-rhoai-stable.sh` script does not exist. The unified `deploy.sh` handles TLS configuration with TLS backend enabled by default. --- *Created by document-review workflow `/create-prs` phase.* <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated quickstart documentation with corrected script references for automated TLS configuration guidance. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Mynhardt Burger <mynhardt@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - Remove `envsubst` from required tools list in maas-setup.md The tool is listed as required but never used in any command in the document. --- *Created by document-review workflow `/create-prs` phase.* Signed-off-by: Mynhardt Burger <mynhardt@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - Add links for installation guide, TLS configuration, token management, Limitador persistence, and migration guide to the documentation landing page The index page only linked to 5 of 20+ documentation sections. Users could not discover installation guides, TLS configuration, or the migration guide from the landing page. ## Human Input Needed - [ ] Confirm which access-control model is current vs legacy (tier-based vs subscription-based) and add appropriate labels --- *Created by document-review workflow `/create-prs` phase.* <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Expanded documentation with new advanced administration guides covering Limitador Persistence, TLS Configuration, and Token Management features. * Added comprehensive Installation Guide with sections on Prerequisites, Platform Setup, MaaS Setup, and Validation procedures. * Introduced Migration guide specifically for Tier to Subscription Migration scenarios. * Updated navigation structure to reflect all new documentation additions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Mynhardt Burger <mynhardt@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary Add `tools.mk` to maas-controller for golangci-lint installation similar to `maas-api`. ## Description - Introduce `tools.mk` that installs golangci-lint v2.6.2 into a local `bin/tools/` directory with version-based caching. - Add a `lint` make target (`golangci-lint fmt` + `run`) with optional `LINT_FIX=true` for auto-fixing. ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> - Ran `make lint` — confirmed golangci-lint is downloaded, cached, and runs against the codebase. - Verified `bin/tools/` is covered by the root `.gitignore`. ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Added automated linting with an option to auto-fix formatting and lint issues, and surfaced a new lint command in the project tooling. * Introduced local tool installation and versioned binaries with symlink management to ensure reproducible, project-local developer tools. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Chetan Kulkarni <ckulkarn@redhat.com> Signed-off-by: Chaitanya Kulkarni <chkulkar@redhat.com>
…not provided (#544) <!--- Provide a general summary of your changes in the Title above --> ## Description This PR removes support for permanent API keys and introduces default expiration for all API keys. ### Changes **Behavior Change:** * API keys now default to `API_KEY_MAX_EXPIRATION_DAYS (90 days)` when expiresIn is not provided * Permanent keys (keys without expiration) are no longer supported * This improves security by ensuring all API keys have a bounded lifetime **Configuration:** * Removed `API_KEY_EXPIRATION_POLICY` environment variable (no longer needed since all keys must expire) * Changed DefaultAPIKeyMaxExpirationDays from 30 to 90 days ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> * Unit tests * Manual testing ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * API keys now expire by default (90 days) and the expiration is configurable; creation responses always include an expiresAt timestamp and the plaintext key on creation. * **Documentation** * Updated API docs and examples to reflect default expiration, new example key names, and short-lived key example (30 days). * **Chores** * Removed legacy API key expiration policy configuration field. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
) The verify-models-and-limits.sh script was broken because it called the removed /maas-api/v1/tokens endpoint. This migrates it to the current /v1/api-keys flow. Model discovery stays on the /v1/models API (no kubectl calls). Changes: Use POST /maas-api/v1/api-keys with named keys instead of removed /v1/tokens Parse .key and .id from API key response Clean up temporary API key on exit via DELETE /v1/api-keys/{id} Remove unused USER_NAME and stale JWT decoding logic Prerequisite: The maas-api-auth-policy must support API key authentication (see deployment/base/maas-api/policies/auth-policy.yaml). The ODH operator currently deploys a stale version without API key support. How Has This Been Tested? Ran the script end-to-end on a live cluster (maas.apps.giteltal.dev.datahub.redhat.com) API key creation, model discovery, inference, rate limiting, and cleanup all passed Merge criteria: The commits are squashed in a cohesive manner and have meaningful messages. Testing instructions have been added in the PR body. The developer has manually tested the changes and verified that the changes work. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Switched verification scripts from token-based to API key-based authentication and updated related messaging. * Added creation and guaranteed cleanup (revocation) of temporary API keys, with improved success/error reporting. * Removed JWT decoding and related user-extraction steps; updated model discovery and inference calls to use API key authorization. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary - Correct database reference from SQLite to PostgreSQL in maas-api README The code exclusively uses PostgreSQL (`NewPostgresStoreFromURL` in cmd/main.go:138). The SQLite reference is incorrect and contradicts the prerequisites doc. --- *Created by document-review workflow `/create-prs` phase.* Signed-off-by: Mynhardt Burger <mynhardt@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Description Major rewrite of the MaaS documentation to align with the subscription-based architecture and API key flow. Changes include: - **Authentication**: Replaced token workflow with API key flow and `X-MaaS-Subscription` header guidance - **Install**: Split into Operator (production) and Kustomize (development) paths; added troubleshooting, validation, DB and TLS setup - **Subscription model**: New docs for overview, cardinality, known issues, and quota/access configuration - **Terminology**: Updated from "tier" to "subscription" across docs and dashboards - **API reference**: Interactive Swagger UI, OpenAPI spec, and CRD reference pages - **Samples**: New sample model deployments and release notes page This is a large doc update. If you find issues, please open JIRAs so we can track and fix them incrementally. ## How Has This Been Tested? - Ran through the full install process on a local cluster - Verified Quota and Access Configuration flow end-to-end - Built docs with `mkdocs build` and confirmed navigation and links - (Note I know some of the other docs around Token Rate Limit and Model Listing probably need some updates) **Preview**: [https://jland-redhat.github.io/models-as-a-service/dev/](https://jland-redhat.github.io/models-as-a-service/dev/) ## Merge criteria: - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified the changes work. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Replaced token-based with API key-based authentication for improved security and lifecycle management. * Introduced subscription-based access control replacing tier-based system for flexible quota and access management. * Added support for multiple sample model deployments (Facebook OPT-125M, Qwen3). * Integrated interactive Swagger UI for API documentation. * **Documentation** * Significantly restructured guides to reflect subscription-based model and API key workflows. * Updated platform setup, configuration, troubleshooting, and migration documentation. * Added comprehensive quota, access control, and model setup guides. * Removed legacy tier-based documentation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…564) ## Summary Enable FIPS compliance in `maas-controller/Dockerfile.konflux` by aligning it with `maas-api/Dockerfile.konflux` (introduced in PR #255). ## Description - Set `CGO_ENABLED=1` — required to link against system crypto libraries (OpenSSL) instead of Go's built-in crypto - Add `GOEXPERIMENT=strictfipsruntime` — enables startup validation that a FIPS-compatible crypto backend is active at runtime No behavioral changes to the controller logic or deployment manifests. ## How it was tested - Built `Dockerfile.konflux` locally using `podman build` targeting `linux/amd64`, image built successfully through both builder and runtime stages ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated build configuration to enforce stricter compliance standards and optimize native library integration. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Chaitanya Kulkarni <chkulkar@redhat.com>
#493) <!--- Provide a general summary of your changes in the Title above --> ## Description Related to https://issues.redhat.com/browse/RHOAIENG-51672 Summary Audit and remove unused or dead code and scripts from .github/hack/, scripts/, scripts/installers/, and any other redundant paths. Consolidate or delete scripts that are duplicated, obsolete, or no longer referenced by CI or docs, so the codebase has a single clear path for deployment and no leftover cruft. Story As a maintainer, I want unused and dead code and scripts removed from the hack dir, scripts directory, installers, and elsewhere, so that we reduce confusion, avoid duplicate code paths, and simplify the e2e refactor (deploy in deploy.sh, tests in smoke.sh). Acceptance Criteria Audit .github/hack/ (e.g. install-cert-manager-and-lws.sh, install-odh.sh, cleanup-odh.sh, uninstall-leader-worker-set.sh): remove or consolidate into scripts/ if they are to be the single source of truth; remove if truly dead and unreferenced. Audit scripts/ and scripts/installers/: remove or merge scripts that are unused, duplicated by deploy.sh, or superseded by a single deploy/test flow. Document any removals and update references in prow script and docs. Remove any other redundant or dead code identified during the audit. CI and documented flows (e.g. prow_run_smoke_test.sh, deploy.sh) continue to work after cleanup. Testability / Verification After cleanup, run the full e2e flow (or current prow script) and confirm it still passes. Grep/docs confirm no broken references to removed scripts. List of removed files and rationale is documented (e.g. in PR or ticket). ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> Tested on cluster https://console-openshift-console.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org ``` omyabhatnagar@Somyas-MacBook-Pro models-as-a-service % SKIP_DEPLOYMENT=true ./test/e2e/scripts/prow_run_smoke_test.sh ---------------------------------------- Deploying Maas on OpenShift ---------------------------------------- Checking prerequisites... ✅ Prerequisites met - logged in as: kube:admin on OpenShift Skipping deployment (SKIP_DEPLOYMENT=true) Assuming MaaS platform and models are already deployed ---------------------------------------- Setting up variables for tests ---------------------------------------- -- Setting up variables for tests -- K8S_CLUSTER_URL: https://api.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org:6443 HOST: maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org MAAS_API_BASE_URL: https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/maas-api CLUSTER_DOMAIN: apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org ✅ Variables for tests setup completed ---------------------------------------- Admin Setup (Premium Test Resources) ---------------------------------------- Setting up premium test token (SA-based, works when oc whoami -t is unavailable)... Patching MaaSAuthPolicy premium-simulator-access to include system:serviceaccount:premium-users-namespace:premium-service-account... maasauthpolicy.maas.opendatahub.io/premium-simulator-access patched (no change) Patching MaaSSubscription premium-simulator-subscription to include system:serviceaccount:premium-users-namespace:premium-service-account... maassubscription.maas.opendatahub.io/premium-simulator-subscription patched (no change) ✅ Premium test token setup complete (E2E_TEST_TOKEN_SA_* exported) ---------------------------------------- Setting up test tokens ---------------------------------------- Setting up test tokens (admin + regular user)... Current admin session: kube:admin (will be preserved) ✅ Admin token for kube:admin (added to odh-admins) ✅ Regular user token for kube:admin (same as admin for local testing) Token setup complete (main session unchanged: kube:admin) ---------------------------------------- Running E2E Tests ---------------------------------------- -- E2E Tests (API Keys + Subscription) -- Running e2e tests with: - TOKEN: sha256~BqgcE8DH7Gjmk... - ADMIN_OC_TOKEN: sha256~BqgcE8DH7Gjmk... - GATEWAY_HOST: maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org ========================== test session starts ========================== platform darwin -- Python 3.13.3, pytest-8.4.2, pluggy-1.6.0 -- /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/.venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.13.3', 'Platform': 'macOS-15.7.4-arm64-arm-64bit-Mach-O', 'Packages': {'pytest': '8.4.2', 'pluggy': '1.6.0'}, 'Plugins': {'metadata': '3.1.1', 'html': '4.2.0'}} rootdir: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service plugins: metadata-3.1.1, html-4.2.0 collected 30 items test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_create_api_key [token] using env TOKEN (masked): 50 [create] Created key id=3f2124fa-9001-4425-b9bc-bcfad2c4bacd, key prefix=sk-oai-dtEwcImF... PASSED [ 3%] test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_list_api_keys [list] Found 6 keys [list] Pagination works: limit=1 returned 1 items PASSED [ 6%] test/e2e/tests/test_api_keys.py::TestAPIKeyCRUD::test_revoke_api_key [revoke] Key bceb72fe-1742-423f-bb21-5924c6abf18f successfully revoked PASSED [ 10%] test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_admin_manage_other_users_keys [admin_token] using env ADMIN_OC_TOKEN (masked): 50 [admin] User 'kube:admin' created key 531a1190-87a8-4606-84a6-1aea8abbf9c8 [admin] Admin listed 8 keys for 'kube:admin' [admin] Admin successfully revoked user's key 531a1190-87a8-4606-84a6-1aea8abbf9c8 PASSED [ 13%] test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_non_admin_cannot_access_other_users_keys FAILED [ 16%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_own_keys FAILED [ 20%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_other_user_forbidden [bulk-revoke] Non-admin correctly got 403 when trying to bulk revoke other user's keys PASSED [ 23%] test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_admin_can_revoke_any_user [bulk-revoke] Admin successfully revoked 1 keys for user kube:admin PASSED [ 26%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_model_access_success [api_key] Creating API key for inference tests... [api_key] Created API key id=5831d46d-f448-4fc3-b995-656558dda908, key prefix=sk-oai-fUHvXwro... FAILED [ 30%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_invalid_api_key_rejected [inference] Invalid API key correctly rejected with 403 PASSED [ 33%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_no_auth_header_rejected [inference] Missing auth header correctly rejected with 401 PASSED [ 36%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_revoked_api_key_rejected [inference] Key before revoke: HTTP 200 [inference] Revoked key correctly rejected with 403 PASSED [ 40%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_chat_completions [inference] Chat completions returned 403: SKIPPED [ 43%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_with_explicit_subscription_header FAILED [ 46%] test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_with_invalid_subscription_header [inference] API key with invalid subscription correctly rejected with 403 PASSED [ 50%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_authorized_user_gets_200 PASSED [ 53%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_no_auth_gets_401 PASSED [ 56%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_invalid_token_gets_403 PASSED [ 60%] test/e2e/tests/test_subscription.py::TestAuthEnforcement::test_wrong_group_gets_403 PASSED [ 63%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_subscribed_user_gets_200 PASSED [ 66%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_auth_pass_no_subscription_gets_403 PASSED [ 70%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_invalid_subscription_header_gets_429 PASSED [ 73%] test/e2e/tests/test_subscription.py::TestSubscriptionEnforcement::test_explicit_subscription_header_works PASSED [ 76%] test/e2e/tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_user_in_one_of_two_subscriptions_gets_200 PASSED [ 80%] test/e2e/tests/test_subscription.py::TestMultipleSubscriptionsPerModel::test_multi_tier_auto_select_highest PASSED [ 83%] test/e2e/tests/test_subscription.py::TestMultipleAuthPoliciesPerModel::test_two_auth_policies_or_logic PASSED [ 86%] test/e2e/tests/test_subscription.py::TestMultipleAuthPoliciesPerModel::test_delete_one_auth_policy_other_still_works PASSED [ 90%] test/e2e/tests/test_subscription.py::TestCascadeDeletion::test_delete_subscription_rebuilds_trlp PASSED [ 93%] test/e2e/tests/test_subscription.py::TestCascadeDeletion::test_delete_last_subscription_denies_access PASSED [ 96%] test/e2e/tests/test_subscription.py::TestOrderingEdgeCases::test_subscription_before_auth_policy PASSED [100%] =============================== FAILURES ================================ _ TestAPIKeyAuthorization.test_non_admin_cannot_access_other_users_keys _ self = <test_api_keys.TestAPIKeyAuthorization object at 0x106fff610> api_keys_base_url = 'https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/maas-api/v1/api-keys' headers = {'Authorization': 'Bearer sha256~BqgcE8DH7GjmkR-pmt7TfMy1Q1f-DoN3ciJbRVnSPd8', 'Content-Type': 'application/json'} admin_headers = {'Authorization': 'Bearer sha256~BqgcE8DH7GjmkR-pmt7TfMy1Q1f-DoN3ciJbRVnSPd8', 'Content-Type': 'application/json'} def test_non_admin_cannot_access_other_users_keys(self, api_keys_base_url: str, headers: dict, admin_headers: dict): """Test 5: Non-admin cannot access other user's keys - verify denial. Note: API returns 404 instead of 403 for IDOR protection (prevents key enumeration). This is a security best practice - returning 403 would reveal the key exists. """ if not admin_headers: pytest.skip("ADMIN_OC_TOKEN not set") # Admin creates a key r_admin = requests.post(api_keys_base_url, headers=admin_headers, json={"name": "admin-only-key"}, timeout=30, verify=TLS_VERIFY) assert r_admin.status_code in (200, 201) admin_key_id = r_admin.json()["id"] # Regular user tries to GET admin's key - returns 404 for IDOR protection r_get = requests.get(f"{api_keys_base_url}/{admin_key_id}", headers=headers, timeout=30, verify=TLS_VERIFY) > assert r_get.status_code == 404, f"Expected 404 (IDOR protection), got {r_get.status_code}" E AssertionError: Expected 404 (IDOR protection), got 200 E assert 200 == 404 E + where 200 = <Response [200]>.status_code test/e2e/tests/test_api_keys.py:205: AssertionError __________ TestAPIKeyBulkOperations.test_bulk_revoke_own_keys ___________ self = <test_api_keys.TestAPIKeyBulkOperations object at 0x106fff750> api_keys_base_url = 'https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/maas-api/v1/api-keys' headers = {'Authorization': 'Bearer sha256~BqgcE8DH7GjmkR-pmt7TfMy1Q1f-DoN3ciJbRVnSPd8', 'Content-Type': 'application/json'} def test_bulk_revoke_own_keys(self, api_keys_base_url: str, headers: dict): """Test 8: Bulk revoke - user can bulk revoke their own keys.""" # Create multiple keys key_ids = [] for i in range(3): r = requests.post(api_keys_base_url, headers=headers, json={"name": f"bulk-test-{i}"}, timeout=30, verify=TLS_VERIFY) assert r.status_code in (200, 201) key_ids.append(r.json()["id"]) # Get username from one of the keys r_get = requests.get(f"{api_keys_base_url}/{key_ids[0]}", headers=headers, timeout=30, verify=TLS_VERIFY) username = r_get.json().get("username") or r_get.json().get("owner") assert username # Bulk revoke all keys for this user r_bulk = requests.post( f"{api_keys_base_url}/bulk-revoke", headers=headers, json={"username": username}, timeout=30, verify=TLS_VERIFY ) assert r_bulk.status_code == 200 data = r_bulk.json() > assert data.get("revokedCount") >= 3, f"Expected at least 3 revoked, got {data.get('revokedCount')}" E AssertionError: Expected at least 3 revoked, got 0 E assert 0 >= 3 E + where 0 = <built-in method get of dict object at 0x110833800>('revokedCount') E + where <built-in method get of dict object at 0x110833800> = {'message': 'Successfully revoked 0 active API key(s) for user kube:admin', 'revokedCount': 0}.get test/e2e/tests/test_api_keys.py:240: AssertionError ______ TestAPIKeyModelInference.test_api_key_model_access_success _______ self = <test_api_keys.TestAPIKeyModelInference object at 0x106fff9d0> model_completions_url = 'https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/llm/facebook-opt-125m-simulated/v1/completions' api_key_headers = {'Authorization': 'Bearer sk-oai-fUHvXwroiHvPfUQSFIwRlHrmhRuRWkJuRGru4e2U7Vz', 'Content-Type': 'application/json'} inference_model_name = 'facebook/opt-125m' def test_api_key_model_access_success( self, model_completions_url: str, api_key_headers: dict, inference_model_name: str, ): """Test 11: Valid API key can access model endpoint - verify 200 response. Note: Users with access to multiple subscriptions must specify which one to use via X-MaaS-Subscription header. """ # Add subscription header - required when user matches multiple subscriptions subscription_name = os.environ.get("E2E_SIMULATOR_SUBSCRIPTION", "simulator-subscription") headers = api_key_headers.copy() headers["X-MaaS-Subscription"] = subscription_name r = requests.post( model_completions_url, headers=headers, json={ "model": inference_model_name, "prompt": "Hello world", "max_tokens": 10, }, timeout=60, verify=TLS_VERIFY, ) > assert r.status_code == 200, f"Expected 200, got {r.status_code}: {r.text}" E AssertionError: Expected 200, got 403: E assert 403 == 200 E + where 403 = <Response [403]>.status_code test/e2e/tests/test_api_keys.py:332: AssertionError ------------------------- Captured stdout setup ------------------------- [api_key] Creating API key for inference tests... [api_key] Created API key id=5831d46d-f448-4fc3-b995-656558dda908, key prefix=sk-oai-fUHvXwro... _ TestAPIKeyModelInference.test_api_key_with_explicit_subscription_header _ self = <test_api_keys.TestAPIKeyModelInference object at 0x107066360> model_completions_url = 'https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/llm/facebook-opt-125m-simulated/v1/completions' api_key_headers = {'Authorization': 'Bearer sk-oai-fUHvXwroiHvPfUQSFIwRlHrmhRuRWkJuRGru4e2U7Vz', 'Content-Type': 'application/json'} inference_model_name = 'facebook/opt-125m' def test_api_key_with_explicit_subscription_header( self, model_completions_url: str, api_key_headers: dict, inference_model_name: str, ): """Test 16: API key with explicit x-maas-subscription header. When multiple subscriptions exist for the same model, the user can specify which subscription to use via the x-maas-subscription header. This works the same way for API keys as it does for OC tokens. """ # Default subscription for free model subscription_name = os.environ.get("E2E_SIMULATOR_SUBSCRIPTION", "simulator-subscription") # Add x-maas-subscription header to API key headers headers_with_sub = api_key_headers.copy() headers_with_sub["x-maas-subscription"] = subscription_name r = requests.post( model_completions_url, headers=headers_with_sub, json={ "model": inference_model_name, "prompt": "Test subscription header", "max_tokens": 5, }, timeout=60, verify=TLS_VERIFY, ) > assert r.status_code == 200, f"Expected 200 with explicit subscription, got {r.status_code}: {r.text}" E AssertionError: Expected 200 with explicit subscription, got 403: E assert 403 == 200 E + where 403 = <Response [403]>.status_code test/e2e/tests/test_api_keys.py:516: AssertionError - generated xml file: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/e2e-kube:admin.xml - - Generated html report: file:///Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/e2e-kube%3Aadmin.html - ======================== short test summary info ======================== FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_non_admin_cannot_access_other_users_keys - AssertionError: Expected 404 (IDOR protection), got 200 FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyBulkOperations::test_bulk_revoke_own_keys - AssertionError: Expected at least 3 revoked, got 0 FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_model_access_success - AssertionError: Expected 200, got 403: FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyModelInference::test_api_key_with_explicit_subscription_header - AssertionError: Expected 200 with explicit subscription, got 403: === 4 failed, 25 passed, 1 skipped, 32 warnings in 241.98s (0:04:01) ==== ❌ ERROR: E2E tests failed ========== E2E Artifact Collection ========== Artifact dir: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports Collecting Authorino logs (token-redacted) to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/authorino-debug.log Saved to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/authorino-debug.log Collecting cluster state to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports Saved to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/cluster-state.log Collecting pod logs from namespace opendatahub to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/pod-logs Saved 44 pod log file(s) to /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/pod-logs ============================================== ========== Auth Debug Report ========== ======================================== Cluster / Namespace Info ======================================== --- Current context --- default/api-ci-ln-c5br29k-76ef8-aws-4-ci-openshift-org:6443/kube:admin --- Logged-in user --- kube:admin --- Cluster domain --- apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org MAAS_NAMESPACE: opendatahub AUTHORINO_NAMESPACE: kuadrant-system ======================================== MaaS API Deployment ======================================== --- maas-api pods --- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES maas-api-5ddfd8d5f4-8rl7c 1/1 Running 0 5m42s 10.129.2.39 ip-10-0-75-222.us-west-1.compute.internal <none> <none> --- maas-api service --- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR maas-api ClusterIP 172.30.34.232 <none> 8443/TCP 33m app.kubernetes.io/component=api,app.kubernetes.io/name=maas-api,app.kubernetes.io/part-of=models-as-a-service ======================================== maas-controller ======================================== --- maas-controller pods --- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES maas-controller-854bf8d4f8-pmpwt 1/1 Running 0 31m 10.131.0.36 ip-10-0-105-207.us-west-1.compute.internal <none> <none> --- maas-controller MAAS_API_NAMESPACE --- MAAS_API_NAMESPACE=metadata.namespace ======================================== Kuadrant AuthPolicies ======================================== --- AuthPolicies (all namespaces) --- NAMESPACE NAME ACCEPTED ENFORCED TARGETKIND TARGETNAME TARGETSECTION AGE llm maas-auth-facebook-opt-125m-simulated True True HTTPRoute facebook-opt-125m-simulated-kserve-route 81s llm maas-auth-premium-simulated-simulated-premium True True HTTPRoute premium-simulated-simulated-premium-kserve-route 14s opendatahub maas-api-auth-policy True True HTTPRoute maas-api-route 33m openshift-ingress gateway-auth-policy True True Gateway maas-default-gateway 33m ======================================== MaaS CRs ======================================== --- MaaSAuthPolicies --- NAME PHASE AGE AUTHPOLICIES premium-simulator-access Active 21m maas-auth-premium-simulated-simulated-premium simulator-access Active 21m maas-auth-facebook-opt-125m-simulated --- MaaSSubscriptions --- NAME PHASE PRIORITY AGE premium-simulator-subscription Active 0 21m simulator-subscription Active 0 46s --- MaaSModelRefs --- NAME PHASE ENDPOINT HTTPROUTE GATEWAY AGE e2e-unconfigured-facebook-opt-125m-simulated Ready https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/llm/e2e-unconfigured-facebook-opt-125m-simulated e2e-unconfigured-facebook-opt-125m-simulated-kserve-route maas-default-gateway 21m facebook-opt-125m-simulated Ready https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/llm/facebook-opt-125m-simulated facebook-opt-125m-simulated-kserve-route maas-default-gateway 21m premium-simulated-simulated-premium Ready https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/llm/premium-simulated-simulated-premium premium-simulated-simulated-premium-kserve-route maas-default-gateway 21m ======================================== Gateway / HTTPRoutes ======================================== --- Gateway --- NAME CLASS ADDRESS PROGRAMMED AGE maas-default-gateway openshift-default ac071c6e226d143ebad34b287ccc26a9-1378294721.us-west-1.elb.amazonaws.com True 37m --- HTTPRoutes (maas-api) --- NAME HOSTNAMES AGE maas-api-route 33m ======================================== Authorino ======================================== --- Authorino pods --- --- ======================================== Subscription Selector Endpoint Validation ======================================== Expected URL (from maas-controller config): https://maas-api.opendatahub.svc.cluster.local:8443/v1/subscriptions/select --- Connectivity test (from kuadrant-system, simulates Authorino) --- curl -vsk -m 10 -X POST 'https://maas-api.opendatahub.svc.cluster.local:8443/v1/subscriptions/select' -H 'Content-Type: application/json' -d '{}' kubectl run failed or timed out ======================================== DNS Resolution Check ======================================== Resolving: maas-api.opendatahub.svc.cluster.local nslookup failed ====================================== ``` ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % cd /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service && bash scripts/ci/validate-manifests.sh 2>&1 ✅ Validating maas-api/deploy/overlays/odh/kustomization.yaml ✅ Validating maas-api/deploy/overlays/dev/kustomization.yaml ✅ Validating docs/samples/models/simulator-premium/kustomization.yaml ✅ Validating docs/samples/models/qwen3/kustomization.yaml ✅ Validating docs/samples/models/simulator/kustomization.yaml ✅ Validating docs/samples/models/facebook-opt-125m-cpu/kustomization.yaml ✅ Validating docs/samples/models/ibm-granite-2b-gpu/kustomization.yaml ✅ Validating docs/samples/maas-system/free/llm/kustomization.yaml ✅ Validating docs/samples/maas-system/free/maas/kustomization.yaml ✅ Validating docs/samples/maas-system/free/kustomization.yaml ✅ Validating docs/samples/maas-system/kustomization.yaml ✅ Validating docs/samples/maas-system/unconfigured/llm/kustomization.yaml ✅ Validating docs/samples/maas-system/unconfigured/maas/kustomization.yaml ✅ Validating docs/samples/maas-system/unconfigured/kustomization.yaml ✅ Validating docs/samples/maas-system/premium/llm/kustomization.yaml ✅ Validating docs/samples/maas-system/premium/maas/kustomization.yaml ✅ Validating docs/samples/maas-system/premium/kustomization.yaml ✅ Validating deployment/overlays/odh/kustomization.yaml ✅ Validating deployment/overlays/tls-backend-disk/kustomization.yaml ✅ Validating deployment/overlays/http-backend/kustomization.yaml ✅ Validating deployment/overlays/tls-backend/kustomization.yaml ✅ Validating deployment/overlays/openshift/kustomization.yaml ✅ Validating deployment/components/odh/kserve/kustomization.yaml ✅ Validating deployment/components/odh/operator/kustomization.yaml ✅ Validating deployment/components/observability/monitors/kustomization.yaml ✅ Validating deployment/components/observability/observability/kustomization.yaml ✅ Validating deployment/components/observability/dashboards/kustomization.yaml ✅ Validating deployment/components/observability/prometheus/kustomization.yaml ✅ Validating deployment/base/maas-controller/rbac/kustomization.yaml ✅ Validating deployment/base/maas-controller/crd/kustomization.yaml ✅ Validating deployment/base/maas-controller/default/kustomization.yaml ✅ Validating deployment/base/maas-controller/policies/kustomization.yaml ✅ Validating deployment/base/maas-controller/manager/kustomization.yaml ✅ Validating deployment/base/maas-api/rbac/kustomization.yaml ✅ Validating deployment/base/maas-api/core/kustomization.yaml ✅ Validating deployment/base/maas-api/overlays/tls/kustomization.yaml ✅ Validating deployment/base/maas-api/networking/kustomization.yaml ✅ Validating deployment/base/maas-api/kustomization.yaml ✅ Validating deployment/base/maas-api/resources/kustomization.yaml ✅ Validating deployment/base/maas-api/default/kustomization.yaml ✅ Validating deployment/base/maas-api/policies/kustomization.yaml ✅ Validating deployment/base/networking/odh/kustomization.yaml ✅ Validating deployment/base/networking/maas/kustomization.yaml ✅ Validating deployment/base/networking/kustomization.yaml ✅ Validating deployment/base/observability/kustomization.yaml somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ``` ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % cd /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service && E2E_SKIP_TLS_VERIFY=true MODEL_NAME=facebook-opt-125m-simulated ./test/e2e/smoke.sh [smoke] Setting up Python virtual environment... [smoke] Activating virtual environment [smoke] Installing Python dependencies [smoke] Virtual environment setup complete [smoke] MAAS_API_BASE_URL=https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/maas-api [smoke] Using MODEL_NAME=facebook-opt-125m-simulated [smoke] Performing smoke test for user: kube:admin [smoke] Setting up admin token for admin tests... [smoke] Added kube:admin to odh-admins group [smoke] ADMIN_OC_TOKEN configured - admin tests will run [smoke] models catalog empty (attempt 1/10), retrying in 3s... [smoke] models catalog empty (attempt 2/10), retrying in 3s... [smoke] models catalog empty (attempt 3/10), retrying in 3s... [smoke] models catalog empty (attempt 4/10), retrying in 3s... [smoke] models catalog empty (attempt 5/10), retrying in 3s... [smoke] models catalog empty (attempt 6/10), retrying in 3s... [smoke] models catalog empty (attempt 7/10), retrying in 3s... [smoke] models catalog empty (attempt 8/10), retrying in 3s... [smoke] models catalog empty (attempt 9/10), retrying in 3s... [smoke] models catalog empty (attempt 10/10), retrying in 3s... [smoke] Using MODEL_URL=https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/llm/facebook-opt-125m-simulated/v1 [token] using env TOKEN (masked): 50 [create] Created key id=4e57d078-e3d5-4a0d-af6c-91dc86e9ca2f, key prefix=sk-oai-aZdMBOUV... .[list] Found 6 keys [list] Pagination works: limit=1 returned 1 items .[revoke] Key 17f2e9b3-21ba-496f-aff9-73f8c59f061d successfully revoked .[admin_token] using env ADMIN_OC_TOKEN (masked): 50 [admin] User 'kube:admin' created key d5607437-b827-4b03-975e-4b010b018c28 [admin] Admin listed 7 keys for 'kube:admin' [admin] Admin successfully revoked user's key d5607437-b827-4b03-975e-4b010b018c28 .F ====================================== FAILURES ======================================= ________ TestAPIKeyAuthorization.test_non_admin_cannot_access_other_users_keys ________ self = <test_api_keys.TestAPIKeyAuthorization object at 0x10663b4d0> api_keys_base_url = 'https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/maas-api/v1/api-keys' headers = {'Authorization': 'Bearer sha256~BqgcE8DH7GjmkR-pmt7TfMy1Q1f-DoN3ciJbRVnSPd8', 'Content-Type': 'application/json'} admin_headers = {'Authorization': 'Bearer sha256~BqgcE8DH7GjmkR-pmt7TfMy1Q1f-DoN3ciJbRVnSPd8', 'Content-Type': 'application/json'} def test_non_admin_cannot_access_other_users_keys(self, api_keys_base_url: str, headers: dict, admin_headers: dict): """Test 5: Non-admin cannot access other user's keys - verify denial. Note: API returns 404 instead of 403 for IDOR protection (prevents key enumeration). This is a security best practice - returning 403 would reveal the key exists. """ if not admin_headers: pytest.skip("ADMIN_OC_TOKEN not set") # Admin creates a key r_admin = requests.post(api_keys_base_url, headers=admin_headers, json={"name": "admin-only-key"}, timeout=30, verify=TLS_VERIFY) assert r_admin.status_code in (200, 201) admin_key_id = r_admin.json()["id"] # Regular user tries to GET admin's key - returns 404 for IDOR protection r_get = requests.get(f"{api_keys_base_url}/{admin_key_id}", headers=headers, timeout=30, verify=TLS_VERIFY) > assert r_get.status_code == 404, f"Expected 404 (IDOR protection), got {r_get.status_code}" E AssertionError: Expected 404 (IDOR protection), got 200 E assert 200 == 404 E + where 200 = <Response [200]>.status_code test/e2e/tests/test_api_keys.py:205: AssertionError - generated xml file: /Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/smoke-kube:admin.xml - - Generated html report: file:///Users/somyabhatnagar/Documents/Projects/redhat/models-as-a-service/test/e2e/reports/smoke-kube%3Aadmin.html - =============================== short test summary info =============================== FAILED test/e2e/tests/test_api_keys.py::TestAPIKeyAuthorization::test_non_admin_cannot_access_other_users_keys - AssertionError: Expected 404 (IDOR protection), got 200 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1 failed, 4 passed, 5 warnings in 17.03s ``` ``` somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ./scripts/validate-deployment.sh ========================================= 🚀 MaaS Platform Deployment Validation ========================================= ========================================= 1️⃣ Component Status Checks ========================================= 🔍 Checking: MaaS API pods ✅ PASS: MaaS API has 1 running pod(s) 🔍 Checking: Kuadrant system pods ✅ PASS: Kuadrant has 8 running pod(s) 🔍 Checking: OpenDataHub/KServe pods ℹ️ opendatahub namespace: 8 running pod(s) ✅ PASS: OpenDataHub/RHOAI has 8 total running pod(s) 🔍 Checking: LLM namespace and models ✅ PASS: Found 3 LLMInferenceService(s) with 3 running pod(s) ========================================= 2️⃣ Gateway Status ========================================= 🔍 Checking: Gateway resource ✅ PASS: Gateway is Accepted and Programmed 🔍 Checking: HTTPRoute for maas-api ✅ PASS: HTTPRoute maas-api-route is configured and accepted 🔍 Checking: Gateway hostname ✅ PASS: Gateway hostname: https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org ========================================= 3️⃣ Policy Status ========================================= 🔍 Checking: AuthPolicy ✅ PASS: AuthPolicy is configured and accepted 🔍 Checking: TokenRateLimitPolicy⚠️ WARNING: TokenRateLimitPolicy found but status: NotFound Note: Policy may still be reconciling. Try deleting the kuadrant operator pod: kubectl delete pod -n kuadrant-system -l control-plane=controller-manager ========================================= 4️⃣ API Endpoint Tests ========================================= ℹ️ Using gateway endpoint: https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org 🔍 Checking: Authentication token ✅ PASS: Authentication token available 🔍 Checking: Models endpoint ℹ️ Testing: curl -sSk https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org/maas-api/v1/models -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN"⚠️ WARNING: Models endpoint accessible but no models found Note: You may need to deploy a model a simulated model can be deployed with the following command: kustomize build docs/samples/models/simulator | kubectl apply --server-side=true --force-conflicts -f - 🔍 Checking: Authorization enforcement (401 without token) ========================================= 📊 Validation Summary ========================================= Results: ✅ Passed: 9 ❌ Failed: 0⚠️ Warnings: 2 ✅ PASS: All critical checks passed! 🎉 Next steps: 1. Deploy a model: kustomize build docs/samples/models/simulator | kubectl apply -f - 2. Access the API at: https://maas.apps.ci-ln-c5br29k-76ef8.aws-4.ci.openshift.org} 3. Check documentation: docs/README.md 4. Re-run validation with specific model: ./scripts/validate-deployment.sh MODEL_NAME somyabhatnagar@Somyas-MacBook-Pro models-as-a-service % ``` ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Chores** * Removed several internal utility scripts including cleanup and management tools for OpenDataHub and related components, deployment helpers, and validation utilities. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Description Add the `apikey-injection` ExtProc plugin to `payload-processing/pkg/plugins/`. This plugin injects API keys into outbound requests based on the resolved model provider. Includes the plugin implementation, a credential store, a reconciler for syncing Kubernetes secrets, and full unit tests. ## How Has This Been Tested? Unit tests included for all components: - `plugin_test.go` — request/response handling and header injection - `store_test.go` — credential store operations - `reconciler_test.go` — Kubernetes secret reconciliation ## Merge criteria: - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added API key injection plugin that automatically injects authentication headers into AI provider requests. * Supports OpenAI, Anthropic, Vertex AI, and Azure OpenAI providers with proper header formatting for each. * **Tests** * Added comprehensive test coverage for plugin functionality, error handling, and concurrent operations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Yehudit Kerido <ykerido@ykerido-thinkpadp1gen7.raanaii.csb> Co-authored-by: Yehudit Kerido <ykerido@ykerido-thinkpadp1gen7.raanaii.csb>
…tion plugins (#595) ## Description Move the `model-provider-resolver` and `api-translation` plugins to `payload-processing/pkg/plugins/`. **model-provider-resolver** watches MaaSModelRef CRDs and resolves model names to providers, writing the result (provider, credentialRef) to CycleState for downstream plugins. **api-translation** translates inference API requests and responses between OpenAI Chat Completions format and provider-native formats. Currently includes the Anthropic Messages API translator. Vertex AI and Azure OpenAI translators are commented out pending their own move PRs by their respective contributors. Also uncomments the plugin registrations in `cmd/main.go` for these two plugins. ## How Has This Been Tested? Unit tests included for all components: - `model-provider-resolver/plugin_test.go` — request processing and CycleState population - `model-provider-resolver/model_store_test.go` — thread-safe model store operations - `api-translation/plugin_test.go` — request/response translation with CycleState - `api-translation/translator/anthropic/anthropic_test.go` — Anthropic Messages API translation ## Merge criteria: - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Activated API translation plugin enabling automatic format conversion between OpenAI and Anthropic APIs. * Activated model provider resolver plugin for dynamic model-to-provider mapping resolution from resource definitions. * Added Anthropic API request and response translation support. * **Tests** * Added comprehensive test coverage for API translation and model provider resolver functionality. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!--- Provide a general summary of your changes in the Title above --> ## Description move azureai api translator to the right place in the plugins dir. ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved error message clarity for AzureOpenAI model validation failures – now displays the specific invalid model value instead of a generic message, making troubleshooting easier. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Nir Rozenbaum <nrozenba@redhat.com>
…as-controller (#565) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.71.1 to 1.79.3. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/grpc/grpc-go/releases">google.golang.org/grpc's releases</a>.</em></p> <blockquote> <h2>Release 1.79.3</h2> <h1>Security</h1> <ul> <li>server: fix an authorization bypass where malformed :path headers (missing the leading slash) could bypass path-based restricted "deny" rules in interceptors like <code>grpc/authz</code>. Any request with a non-canonical path is now immediately rejected with an <code>Unimplemented</code> error. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8981">#8981</a>)</li> </ul> <h2>Release 1.79.2</h2> <h1>Bug Fixes</h1> <ul> <li>stats: Prevent redundant error logging in health/ORCA producers by skipping stats/tracing processing when no stats handler is configured. (<a href="https://redirect.github.com/grpc/grpc-go/pull/8874">grpc/grpc-go#8874</a>)</li> </ul> <h2>Release 1.79.1</h2> <h1>Bug Fixes</h1> <ul> <li>grpc: Remove the <code>-dev</code> suffix from the User-Agent header. (<a href="https://redirect.github.com/grpc/grpc-go/pull/8902">grpc/grpc-go#8902</a>)</li> </ul> <h2>Release 1.79.0</h2> <h1>API Changes</h1> <ul> <li>mem: Add experimental API <code>SetDefaultBufferPool</code> to change the default buffer pool. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8806">#8806</a>) <ul> <li>Special Thanks: <a href="https://github.com/vanja-p"><code>@vanja-p</code></a></li> </ul> </li> <li>experimental/stats: Update <code>MetricsRecorder</code> to require embedding the new <code>UnimplementedMetricsRecorder</code> (a no-op struct) in all implementations for forward compatibility. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8780">#8780</a>)</li> </ul> <h1>Behavior Changes</h1> <ul> <li>balancer/weightedtarget: Remove handling of <code>Addresses</code> and only handle <code>Endpoints</code> in resolver updates. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8841">#8841</a>)</li> </ul> <h1>New Features</h1> <ul> <li>experimental/stats: Add support for asynchronous gauge metrics through the new <code>AsyncMetricReporter</code> and <code>RegisterAsyncReporter</code> APIs. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8780">#8780</a>)</li> <li>pickfirst: Add support for weighted random shuffling of endpoints, as described in <a href="https://redirect.github.com/grpc/proposal/pull/535">gRFC A113</a>. <ul> <li>This is enabled by default, and can be turned off using the environment variable <code>GRPC_EXPERIMENTAL_PF_WEIGHTED_SHUFFLING</code>. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8864">#8864</a>)</li> </ul> </li> <li>xds: Implement <code>:authority</code> rewriting, as specified in <a href="https://github.com/grpc/proposal/blob/master/A81-xds-authority-rewriting.md">gRFC A81</a>. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8779">#8779</a>)</li> <li>balancer/randomsubsetting: Implement the <code>random_subsetting</code> LB policy, as specified in <a href="https://github.com/grpc/proposal/blob/master/A68-random-subsetting.md">gRFC A68</a>. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8650">#8650</a>) <ul> <li>Special Thanks: <a href="https://github.com/marek-szews"><code>@marek-szews</code></a></li> </ul> </li> </ul> <h1>Bug Fixes</h1> <ul> <li>credentials/tls: Fix a bug where the port was not stripped from the authority override before validation. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8726">#8726</a>) <ul> <li>Special Thanks: <a href="https://github.com/Atul1710"><code>@Atul1710</code></a></li> </ul> </li> <li>xds/priority: Fix a bug causing delayed failover to lower-priority clusters when a higher-priority cluster is stuck in <code>CONNECTING</code> state. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8813">#8813</a>)</li> <li>health: Fix a bug where health checks failed for clients using legacy compression options (<code>WithDecompressor</code> or <code>RPCDecompressor</code>). (<a href="https://redirect.github.com/grpc/grpc-go/issues/8765">#8765</a>) <ul> <li>Special Thanks: <a href="https://github.com/sanki92"><code>@sanki92</code></a></li> </ul> </li> <li>transport: Fix an issue where the HTTP/2 server could skip header size checks when terminating a stream early. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8769">#8769</a>) <ul> <li>Special Thanks: <a href="https://github.com/joybestourous"><code>@joybestourous</code></a></li> </ul> </li> <li>server: Propagate status detail headers, if available, when terminating a stream during request header processing. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8754">#8754</a>) <ul> <li>Special Thanks: <a href="https://github.com/joybestourous"><code>@joybestourous</code></a></li> </ul> </li> </ul> <h1>Performance Improvements</h1> <ul> <li>credentials/alts: Optimize read buffer alignment to reduce copies. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8791">#8791</a>)</li> <li>mem: Optimize pooling and creation of <code>buffer</code> objects. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8784">#8784</a>)</li> <li>transport: Reduce slice re-allocations by reserving slice capacity. (<a href="https://redirect.github.com/grpc/grpc-go/issues/8797">#8797</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/grpc/grpc-go/commit/dda86dbd9cecb8b35b58c73d507d81d67761205f"><code>dda86db</code></a> Change version to 1.79.3 (<a href="https://redirect.github.com/grpc/grpc-go/issues/8983">#8983</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/72186f163e75a065c39e6f7df9b6dea07fbdeff5"><code>72186f1</code></a> grpc: enforce strict path checking for incoming requests on the server (<a href="https://redirect.github.com/grpc/grpc-go/issues/8981">#8981</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/97ca3522b239edf6813e2b1106924e9d55e89d43"><code>97ca352</code></a> Changing version to 1.79.3-dev (<a href="https://redirect.github.com/grpc/grpc-go/issues/8954">#8954</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/8902ab6efea590f5b3861126559eaa26fa9783b2"><code>8902ab6</code></a> Change the version to release 1.79.2 (<a href="https://redirect.github.com/grpc/grpc-go/issues/8947">#8947</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/a9286705aa689bee321ec674323b6896284f3e02"><code>a928670</code></a> Cherry-pick <a href="https://redirect.github.com/grpc/grpc-go/issues/8874">#8874</a> to v1.79.x (<a href="https://redirect.github.com/grpc/grpc-go/issues/8904">#8904</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/06df3638c0bcee88197b1033b3ba83e1eb8bc010"><code>06df363</code></a> Change version to 1.79.2-dev (<a href="https://redirect.github.com/grpc/grpc-go/issues/8903">#8903</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/782f2de44f597af18a120527e7682a6670d84289"><code>782f2de</code></a> Change version to 1.79.1 (<a href="https://redirect.github.com/grpc/grpc-go/issues/8902">#8902</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/850eccbb2257bd2de6ac28ee88a7172ab6175629"><code>850eccb</code></a> Change version to 1.79.1-dev (<a href="https://redirect.github.com/grpc/grpc-go/issues/8851">#8851</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/765ff056b6890f6c8341894df4e9668e9bfc18ef"><code>765ff05</code></a> Change version to 1.79.0 (<a href="https://redirect.github.com/grpc/grpc-go/issues/8850">#8850</a>)</li> <li><a href="https://github.com/grpc/grpc-go/commit/68804be0e78ed0365bb5a576dedc12e2168ed63e"><code>68804be</code></a> Cherry pick <a href="https://redirect.github.com/grpc/grpc-go/issues/8864">#8864</a> to v1.79.x (<a href="https://redirect.github.com/grpc/grpc-go/issues/8896">#8896</a>)</li> <li>Additional commits viewable in <a href="https://github.com/grpc/grpc-go/compare/v1.71.1...v1.79.3">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/opendatahub-io/models-as-a-service/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
<!--- Provide a general summary of your changes in the Title above --> ## Description This PR completes the payload processing move from its own repo to MaaS repo. ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Activated API key injection plugin for enhanced request security * Enabled support for Azure OpenAI translator * Enabled support for Vertex AI translator * **Tests** * Added comprehensive test coverage for Azure OpenAI request and response handling * Added comprehensive test coverage for Vertex request processing, response transformation, error handling, and tool-call translation <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Nir Rozenbaum <nrozenba@redhat.com>
…alRef fields (#571) ## Summary Adds external model provider support to the `MaaSModelRef` CRD, per the [Egress Inference design doc](https://docs.google.com/document/d/1poHd9g5XTa0-OwRQ7AxufB3_eq4LXlo1ycxhbdkNYUg/edit). This enables users to register external AI providers (OpenAI, Anthropic) alongside internal KServe models using a single CRD. ### CRD changes Added to `ModelReference`: - `provider` — identifies API format and auth type (e.g., `openai`, `anthropic`) - `endpoint` — external provider FQDN (e.g., `api.openai.com`) Added to `MaaSModelSpec`: - `credentialRef` — references K8s Secret with provider API key (`name` + `namespace`) All new fields are optional and only used when `kind: ExternalModel`. Internal models (`kind: LLMInferenceService`) are unaffected. ### Controller changes Implemented the `ExternalModel` provider handler (previously a stub returning `ErrKindNotImplemented`): - **ReconcileRoute**: validates user-supplied HTTPRoute (`maas-model-<name>`) references the configured gateway - **Status**: returns Ready once HTTPRoute is validated, derives endpoint from gateway hostname - **Provider validation**: ExternalModel without `provider` field → phase=Failed with clear error message - **CleanupOnDelete**: no-op (user owns the HTTPRoute) ### Example ```yaml apiVersion: maas.opendatahub.io/v1alpha1 kind: MaaSModelRef metadata: name: gpt-4o namespace: llm spec: modelRef: kind: ExternalModel name: gpt-4o provider: openai endpoint: "api.openai.com" credentialRef: name: openai-api-key namespace: llm ``` ### Why this matters The BBR `provider-resolver` plugin ([ai-gateway-payload-processing#14](opendatahub-io/ai-gateway-payload-processing#14)) will read these fields from `MaaSModelRef` to resolve model→provider mappings and populate CycleState for downstream plugins (api-translation, api-key-injection). This makes `MaaSModelRef` the single source of truth for both internal and external models — no separate ConfigMaps or Secret annotations needed. ## Test plan - [x] 11 unit tests (ReconcileRoute, Status, GetModelEndpoint, validation, cleanup) - [x] `make generate && make manifests && go build ./... && go test ./pkg/controller/maas/...` — all pass - [x] Deployed on RHOAI cluster (sandbox258): - ExternalModel with provider + HTTPRoute → phase=Ready, endpoint populated - ExternalModel without provider → phase=Failed, "missing provider" - ExternalModel without HTTPRoute → phase=Failed, "create it to enable routing" - MaaSAuthPolicy → generates backing AuthPolicy (accepted + enforced) - MaaSSubscription → generates backing TokenRateLimitPolicy - Internal models unaffected (regression pass) - External models route to simulator at 3.150.113.9:8000 — inference works e2e ⎿ Tool use rejected with user message: before the commit in maas, please validate that all local changes are production grade efficient and also follow the repo guildlines nad ond optimize <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Reference credentials (Secret) for external models * Provider and endpoint fields for external model configuration * Automatic HTTPRoute discovery and HTTPS endpoint derivation with hostname resolution; status fields populated when route is accepted * **Bug Fixes / Validation** * Require provider for ExternalModel * Fail validation if credential namespace differs from resource namespace * **Tests** * Added unit tests covering routing, endpoint resolution, readiness, and credential ref handling <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This defaults our hack script to deploy ODH to EA1 through a manual deploy to prevent auto upgrade to that latest version <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added environment variables to control operator installation version and install-plan approval behavior. * **Chores** * Default operator channel changed to fast-3. * Operator subscription and install flow improved to support explicit marketplace, starting version, and manual approval handling. * Minor test/deployment messaging hygiene updated. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Yuriy Teodorovych <Yuriy@ibm.com>
<!--- Provide a general summary of your changes in the Title above --> ## Description Replaces Auth CR-based admin detection with Kubernetes SubjectAccessReview (SAR) for RBAC-aligned authorization. **Problem**: The existing admin checker enumerated groups from the `services.opendatahub.io/v1alpha1/Auth` CR, which required maintaining a separate admin group list and didn't align with Kubernetes RBAC. **Solution**: Use SAR to check if a user can `create maasauthpolicies.maas.opendatahub.io` in the MaaS namespace. This leverages existing RBAC rules - anyone with this permission is considered an admin. https://redhat.atlassian.net/browse/RHOAIENG-54600 ## How Has This Been Tested? * Unit tests * E2E tests * Local testing ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Admin authorization now uses Kubernetes RBAC (SubjectAccessReview) for platform-aligned checks. * Authorization for API key actions (view, revoke, search, bulk revoke) made context-aware and simplified. * **Chores** * Cluster RBAC updated to permit subject access reviews required for the new admin checks. * **Tests** * Unit tests updated and added to validate the new RBAC-based admin checks and behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…endpoint (#584) <!--- Provide a general summary of your changes in the Title above --> https://redhat.atlassian.net/browse/RHOAIENG-54596 ## Description <!--- Describe your changes in detail --> ### API key minting - A column to API key storage and an optional field to API key creation request for subscription are added. - If the field is omitted, the subscription with the highest priority is selected (with limit and name being the tie breaker). ### AuthPolicy flow - Subscription name is now grabbed from `apiKeyValidation` result instead of the header `X-MaaS-Subscription`. - `subscription-info` is still needed for validation check, as well as the header. It is injected after auth success. ### Controller feedback - MaaSSubscriptions have a status condition `SpecPriorityDuplicate` to indicate if they share the same priority with other peers. ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> - Created API keys with different subscriptions, and see them in the database. - Verified the auto-select rule. - Verified that each API key can only access the model ref in associated subscription. - Verified the status condition. ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * API keys are now bound to a specific subscription at creation time. Optionally specify a `subscription` parameter when creating keys; if omitted, the highest-priority accessible subscription is automatically selected. * Subscriptions can now include a `priority` field to control default selection when multiple are available. * The `X-MaaS-Subscription` header is no longer required for API key-based inference requests; subscription is determined by the key's binding. * **Documentation** * Updated guides to reflect subscription binding at key creation and removed header-requirement documentation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Jim Rhyness <jrhyness@redhat.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
<!--- Provide a general summary of your changes in the Title above --> ## Description Implements typed reference pattern for ModelReference to replace inlining variant-specific config, enabling extensible model kinds without schema changes to ModelReference. ## How Has This Been Tested? - Unit tests: All existing unit tests pass, new ExternalModel handler tests added - E2E tests: Enhanced test_namespace_scoping.py to create ExternalModel CRs inline using existing _apply_cr pattern - Manual testing: ExternalModel CRD generation and controller reconciliation verified ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced external model provider support, enabling configuration of external models as an alternative backend alongside inference services. * **Documentation** * Added comprehensive guide for external model configuration, including credential and endpoint management. * Updated model reference documentation to clarify support for both inference services and external models. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
https://issues.redhat.com/browse/RHOAIENG-41301 ## Description Approach: - For default values and env var parsing: Call Load() directly (it only registers flags, doesn't parse them, so it's safe to test values before flag.Parse()). Use t.Setenv() for env var isolation. - For flag binding: Test bindFlags() directly with a fresh flag.FlagSet per test case, which avoids the global state issue entirely. Load() calls loadTLSConfig() internally, which reads TLS_CERT, TLS_KEY, TLS_SELF_SIGNED. ## How Has This Been Tested? Tested locally on my machine before creating the PR ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Expanded configuration test suite with end-to-end coverage for default values, environment-variable overrides, flag reset handling, validation rules (TLS consistency, certificate/key pairing, API key expiration policy and bounds), secure/insecure address defaults, and deprecated-flag behavior; added helper to reinitialize command-line flags so Load() can be repeatedly registered in tests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Yuriy Teodorovych <Yuriy@ibm.com>
This PR addresses the review comments from @nirrozenbaum on #586 that were not resolved before merge. ##Changes 1. Remove namespace field from credentialRef Comment: "isn't it always the same namespace? is there a use case for other namespace? I would argue that it's the same as MaaSModelRef expecting to reference ExternalModel from the same namespace.. IMO we should be consistent." Remove optional namespace field from ExternalModel.spec.credentialRef Credential Secret must now reside in the same namespace as the ExternalModel (consistent with MaaSModelRef → ExternalModel reference pattern) 2. Clarify rate-limiting model identity Comment: "which field represents the model that MaaS is rate limiting on? is it MaaSModelRef metadata.name? model.ref.Name?" Update documentation to clarify that rate limiting is keyed on MaaSModelRef.metadata.name Add explanation of the relationship between MaaSModelRef and ExternalModel 3. Document single credential limitation Comment: "this works as long as there is a single api key per external model. I'm assuming this won't be the case for long time. just as a fyi" Add documentation note about current single-credential-per-ExternalModel limitation (Optional) Create tracking issue for multi-credential support ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Breaking Changes** * Credential references no longer support namespace override; credentials must reside in the same namespace as the `ExternalModel`. * **Validation** * Name fields now require a minimum length of 1 character. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…endpoint (#614) <!--- Provide a general summary of your changes in the Title above --> ## Description <!--- Describe your changes in detail --> ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] The commits are squashed in a cohesive manner and have meaningful messages. - [ ] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [ ] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified API key authentication flow: keys now bind to a specific subscription at creation time * Updated guidance on when to use `X-MaaS-Subscription` header—required only for user tokens with multiple subscriptions, not for API key-based inference * Revised model listing and inference examples to reflect the new subscription binding behavior for API keys * Updated authentication flow diagrams and troubleshooting guidance to reflect current behavior <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Jim Rhyness <jrhyness@redhat.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…ition (#600) ## Summary Related to - https://redhat.atlassian.net/browse/RHOAIENG-51551 Fixes the issue where MaaSModelRefs get stuck in non-Ready state when created before KServe creates the corresponding HTTPRoute. This eliminates the need for the controller bounce workaround in E2E tests. ## Problem MaaSModelRefs could get stuck indefinitely when: 1. MaaSModelRef is created before HTTPRoute exists (common during startup) 2. Controller treats "HTTPRoute not found" as permanent failure 3. Sets status to `Failed` and returns error 4. Triggers exponential backoff that can reach minutes 5. Requires controller restart to recover **Current workaround:** E2E tests bounce the controller and retry (lines 262-283 in `prow_run_smoke_test.sh`) ## Solution Changed HTTPRoute missing from permanent failure to temporary condition: - **Added** `ErrHTTPRouteNotFound` sentinel error to distinguish temporary from permanent failures - **Changed** log level from ERROR → Warn with message `HTTPRoute not found for LLMInferenceService, will retry when created` - **Set** status to `Pending` (not Failed) when HTTPRoute doesn't exist yet - **Leverages** existing HTTPRoute watch for defense-in-depth when route is created ## Changes - `providers.go`: Added `ErrHTTPRouteNotFound` error - `providers_llmisvc.go`: Return `ErrHTTPRouteNotFound` with INFO log when HTTPRoute missing - `maasmodelref_controller.go`: Handle `ErrHTTPRouteNotFound` with Pending + RequeueAfter - `maasmodelref_controller_test.go`: Added `TestMaaSModelRefReconciler_HTTPRouteRaceCondition` - `prow_run_smoke_test.sh`: Removed controller bounce workaround ## Testing ### Unit Tests ```bash ✅ make test - all tests pass ✅ TestMaaSModelRefReconciler_HTTPRouteRaceCondition - validates Pending → Ready transition ``` ### Manual Cluster Testing Deployed fix to live cluster and verified: (https://console-openshift-console.apps.ci-ln-4jf5smt-76ef8.aws-4.ci.openshift.org/) - ✅ MaaSModelRef enters Pending when HTTPRoute doesn't exist - ✅ Controller logs INFO "will retry" (not ERROR) - ✅ Transitions to Ready when HTTPRoute appears - ✅ No controller bounce needed ### Expected E2E Impact E2E tests will now: - ✅ Wait for MaaSModelRefs to reach Ready naturally (no bounce) - ✅ Fail fast if they don't (with diagnostics) - ✅ Complete faster (no retry loop) ## Validation **Before:** ``` HTTPRoute missing → ERROR → Failed status → Exponential backoff → Stuck ``` **After:** ``` HTTPRoute missing → INFO "will retry" → Pending status → RequeueAfter 5s → Ready ``` ## Fixes - Resolves issue described in test/e2e/scripts/prow_run_smoke_test.sh:262-278 - Eliminates need for controller bounce workaround - Makes MaaSModelRef reconciliation reliable during startup 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Missing HTTP routes are now treated as transient: models move to Pending and reconciliation continues instead of failing; logging for expected route-not-found races is reduced and controllers skip route-dependent work until routes appear. * **Tests** * Added a unit test covering out-of-order resource creation (HTTPRoute appearing after model) and successful recovery. * **Improvements** * Deployment script uses a bounded timeout poll, clearer diagnostics on failure, and a configurable model namespace for tests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
…#591) ## Summary Related to https://redhat.atlassian.net/browse/RHOAIENG-53771 Automatically create the `models-as-a-service` namespace when the MaaS controller starts, eliminating the need for manual `kubectl create namespace` before deploying MaaSAuthPolicy and MaaSSubscription CRs. ## Changes ### Controller (`maas-controller/cmd/manager/main.go`) - Added `ensureNamespace()` function that creates the subscription namespace on startup - Creates namespace with label `opendatahub.io/generated-namespace: "true"` - Handles existing namespace gracefully (idempotent) - Exits with error only on actual failures (not AlreadyExists) - Supports configurable namespace via `--maas-subscription-namespace` flag ### RBAC (`deployment/base/maas-controller/rbac/clusterrole.yaml`) - Added namespace permissions: `create`, `get`, `list`, `watch` - Follows least-privilege principle (no delete/update) ### Deploy Script (`scripts/deploy.sh`) - Creates subscription namespace in kustomize mode - Respects `MAAS_SUBSCRIPTION_NAMESPACE` environment variable - Idempotent - checks if namespace exists before creating ### Documentation - Removed manual namespace creation steps from: - `docs/content/migration/tier-to-subscription.md` - `maas-controller/README.md` - `docs/samples/maas-system/README.md` - Added notes explaining auto-creation behavior ## Testing ✅ **Verified on live OpenShift cluster:** - Namespace auto-created with correct label on controller startup - MaaS CRs created successfully without manual namespace creation - Idempotent behavior confirmed - controller restarts without errors when namespace exists - Generated Kuadrant policies working correctly (AuthPolicy + TokenRateLimitPolicy) **Test evidence:** ```bash # Namespace auto-created: kubectl get namespace models-as-a-service -o jsonpath='{.metadata.labels.opendatahub\.io/generated-namespace}' # Output: true # Controller logs: # {"msg":"subscription namespace ready","namespace":"models-as-a-service"} # MaaS CRs created without manual namespace step: kubectl get maasauthpolicy,maassubscription -n models-as-a-service # All CRs in Active phase ``` ## Acceptance Criteria - [x] **AC1:** Namespace auto-created when operator runs, users can create MaaS CRs without manual `kubectl create namespace` - [x] **AC2:** Supports configurable subscription namespace via `--maas-subscription-namespace` flag - [x] **AC3:** Idempotent - no errors when namespace already exists ## Benefits - 🎯 **Reduces installation friction** - one less manual step for users - ✅ **Improves UX** - namespace creation happens automatically - 🔧 **Flexible** - supports custom namespace names via configuration - 🛡️ **Safe** - idempotent, minimal RBAC permissions 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Controller now auto-creates exactly one subscription namespace on startup (default: models-as-a-service) and supports creating a single custom subscription namespace via configuration; deployment tooling ensures the subscription namespace is applied. * Controller granted permissions to create and read namespaces so it can create and monitor them. * **Documentation** * Deployment guides, samples, and migration docs updated to remove manual namespace creation and document automatic/custom-namespace behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
<!--- Provide a general summary of your changes in the Title above --> https://redhat.atlassian.net/browse/RHOAIENG-54149 ## Description <!--- Describe your changes in detail --> Fixing namespace inconsistency in documentation in accordance to the following rules: - MaaS API and controller are in `opendatahub` by default; - MaaSAuthPolicy and MaaSSubscription are in `models-as-a-service` by default; - MaaSModelRef is in the same namespace as the service it refers to (e.g., `llm`). ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> N/A ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified that model references are discovered cluster-wide by the MaaS API, with access filtering applied per referenced model. * Specified that model-reference resources should be created in the same namespace as their backend model; default namespaces for controller/API and for auth/subscription resources are documented. * Updated examples and verification commands to reflect the corrected namespace guidance. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
https://issues.redhat.com/browse/RHOAIENG-49788 ## Description **Context** MaaS CRDs (MaaSModelRef, MaaSAuthPolicy, MaaSSubscription) have no human-readable display metadata. The `GET /v1/models` API already has a Details struct with `displayName`, `description`, and `genaiUseCase` fields, and annotation constants already exist (openshift.io/display-name, openshift.io/description, opendatahub.io/genai-use-case), but `maasModelRefToModel()` never reads annotations — so modelDetails is always `nil`. **Approach**: mainly using Annotations - [x] Use existing OpenShift-standard annotations for display name/description on all 3 CRDs - [x] Use opendatahub.io/* annotations for model-specific metadata (contextWindow) - [x] No CRD spec field changes — no regeneration needed - [x] Enrich GET /v1/models API response with annotation data ## How Has This Been Tested? Successfully ran the full test suite locally. ## Merge criteria: <!--- This PR will be merged by any repository approver when it meets all the points in the checklist --> <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] The commits are squashed in a cohesive manner and have meaningful messages. - [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious). - [x] The developer has manually tested the changes and verified that the changes work <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a CRD Annotations Reference and updated configuration/flow guides to document supported annotations (display name, description, GenAI use case, context window) and their appearance in model listings; added tier-based access guidance. * **New Features** * Model listing/response now surfaces annotation-driven metadata (modelDetails: displayName, description, genaiUseCase, contextWindow). * Sample manifests and examples updated with annotation usage. * **Tests** * Tests updated to validate annotation-driven model details. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Yuriy Teodorovych <Yuriy@ibm.com>
<!--- Provide a general summary of your changes in the Title above -->
## Description
Implements per-key unique salt for API key hashing to meet FIPS 180-4
compliance requirements. The key ID embedded in each API key serves as
the salt, enabling deterministic O(1) hash lookups while providing
cryptographic uniqueness per key.
**Motivation**
Plain SHA-256 hashing of API keys doesn't meet FIPS compliance
requirements for production deployments. This change adds per-key
salting without requiring database schema changes or breaking the
existing O(1) lookup performance.
### Changes
**New API Key Format:**
```
Before: sk-oai-{secret}
After: sk-oai-{key_id}_{secret}
```
**Hash Computation:**
```
Before: SHA-256(full_key)
After: SHA-256(key_id + secret)
```
The `key_id` is a 96-bit random identifier (~16 base62 chars) that
serves as a unique per-key salt.
### Performance
Operation | Before | After | Overhead
-- | -- | -- | --
GenerateAPIKey | 2,322 ns | 3,312 ns | +43%
HashAPIKey | 101 ns | 157 ns | +55%
ValidateAPIKeyHash | N/A | 174 ns | New
### Security
- FIPS 180-4 compliant (salted SHA-256)
- Per-key unique salt prevents rainbow table attacks
- Constant-time hash comparison prevents timing attacks
- 256-bit entropy for secret portion
- 96-bit entropy for key_id (salt)
### Breaking Changes
⚠️ API keys created with the new format are not compatible with older
maas-api versions. Existing keys will fail validation after upgrade.
This is expected for a POC/feature branch.
## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->
- [x] Unit tests pass (`go test ./internal/api_keys/...`)
- [x] Linters pass (`make build`)
- [x] Manual testing
- [x] Benchmarks run
- [x] E2E tests
## Merge criteria:
<!--- This PR will be merged by any repository approver when it meets
all the points in the checklist -->
<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] The commits are squashed in a cohesive manner and have meaningful
messages.
- [x] Testing instructions have been added in the PR body (for PRs
involving changes that are not immediately obvious).
- [x] The developer has manually tested the changes and verified that
the changes work
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* API key format redesigned with separate key ID and secret components
* New methods for parsing API keys and validating key hashes
* **Bug Fixes**
* Enhanced key hashing with improved salt-based computation
* **Tests**
* Added comprehensive test coverage for key parsing, validation, and
format verification
* Extended uniqueness tests for all key components
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
/override ci/prow/images |
|
@jland-redhat: Overrode contexts on behalf of jland-redhat: ci/prow/images DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/override branch-ci-opendatahub-io-models-as-a-service-main-images |
|
@jland-redhat: Overrode contexts on behalf of jland-redhat: branch-ci-opendatahub-io-models-as-a-service-main-images DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: github-actions[bot], jland-redhat The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
@github-actions[bot]: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
Automated promotion of 36 commit(s) from
maintostable.