Repository navigation
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Contributor Report for @jeffspahr
|
There was a problem hiding this comment.
🔵 Needs a closer look
The unresolved cross-namespace proxy-retargeting issue and changed authorization boundary require human security review.
1 open finding
What changed in this PR
Adds opt-in storage-based artifact authorization so namespace-scoped credentials can serve existing artifact roots without migration.
Changes:
- Adds authorization-mode validation and namespace-bound proxy routing.
- Generates artifact-proxy ingress policies.
- Adds storage-isolation tests, CI coverage, and rollout documentation.
| File | Description |
|---|---|
| test/seaweedfs/artifact_authorization_test.sh | Runs disposable storage verification. |
| test/seaweedfs/artifact_authorization_fixture.py | Provisions tenant identities and historical roots. |
| manifests/kustomize/third-party/metacontroller/base/cluster-role.yaml | Grants NetworkPolicy management permissions. |
| manifests/kustomize/README.md | Documents storage-mode prerequisites. |
| manifests/kustomize/base/installs/multi-user/pipelines-profile-controller/test_sync.py | Tests ingress policies and readiness. |
| manifests/kustomize/base/installs/multi-user/pipelines-profile-controller/sync.py | Generates proxy ingress policies. |
| manifests/kustomize/base/installs/multi-user/pipelines-profile-controller/deployment.yaml | Supplies the installation namespace. |
| manifests/kustomize/base/installs/multi-user/pipelines-profile-controller/decorator-controller.yaml | Registers NetworkPolicy attachments. |
| frontend/server/integration-tests/artifact-storage-authorization.test.ts | Tests authorization and routing safeguards. |
| frontend/server/integration-tests/artifact-storage-authorization-live.test.ts | Tests real storage read/list isolation. |
| frontend/server/handlers/artifacts.ts | Implements storage authorization and routing guards. |
| frontend/server/configs.ts | Adds validated authorization-mode configuration. |
| frontend/server/configs.test.ts | Tests mode defaults and prerequisites. |
| frontend/server/app.ts | Wires authorization mode into middleware. |
| frontend/README.md | Explains artifact authorization choices. |
| docs/operator-guides/server-config.md | Documents the new setting. |
| docs/operator-guides/index.md | Links the rollout guide. |
| docs/operator-guides/artifact-authorization.md | Explains isolation, adoption, and rollback. |
| docs/agents/ci.md | Documents fixture scope and limitations. |
| CHANGELOG.md | Adds artifact compatibility guidance. |
| .github/workflows/frontend.yml | Runs the real-storage fixture. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
aa6cbc1 to
02c2cfa
Compare
|
Reran the generated artifact-proxy ingress policy on stock Kind networking: all 32 assertions passed. Environment: Kind v0.33.0, Kubernetes v1.36.1, one Linux arm64 node, IPv4. The cluster used its default kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-planeTested the actual policy produced by the profile-controller Each row was checked through both the destination Pod IP on port 3000 and its Service IP on port 80:
8 successful baseline controls + 16 allow/deny assertions across two passes + 8 successful policy-removal controls = 32 passing assertions. Denials were actual request timeouts; generic This validates the generated standard Kubernetes NetworkPolicy's ingress boundary on default Kind networking. It uses an HTTP responder with the proxy's labels and ports; it is not a full KFP deployment, real-user SAR/ingress authentication, Istio, or multi-node/dual-stack acceptance test. The separately reported real SeaweedFS storage authorization tests remain separate evidence. Installations must still verify their own CNI, mesh, and additive policies; final release-image acceptance remains outstanding. |
02c2cfa to
5999101
Compare
f220d8b to
75ae895
Compare
14ee8b1 to
7908758
Compare
0164060 to
698fd83
Compare
2863c85 to
7d4b095
Compare
Authorize artifact access through controller-certified namespace storage or the existing key-prefix policy. Support administrator-owned adoption policies for external storage, stable identity bindings, and rotating broker credentials. Preserve receiver ownership and fail-closed readiness across enrollment, revocation and recovery. Cover historical roots and cross-tenant isolation with component and full-cluster acceptance fixtures; document operator trust. Signed-off-by: Jeff Spahr <spahrj@gmail.com>
7d4b095 to
0d89120
Compare

Problem
The artifact security fix can reject an existing root even when the namespace's storage identity already isolates its objects. A layout such as
s3://external-artifacts/model/customer-17-training/should not require moving objects merely because it does not use KFP's namespace-prefix convention. Automatically selecting storage authorization previously recognized only controller-managed static credentials forminio://mlpipeline.Behavior
KFP_SECURITY_ARTIFACT_AUTHORIZATION_MODE=autoselects a ready namespace receiver for either the verified managed store or an administrator-approved external S3 credential-file broker. The shared UI still authorizes the user for the namespace; the namespace storage identity decides which objects may be read. Storage denial never retries with shared UI credentials. No MLMD authority or audit exception is introduced. Managedautoretains ownership/prefix checks for the legacyartifacts/root: its broad stock grant also covers private pre-SeaweedFS V1 outputs, so readiness cannot make that history shared.For external storage, an installation administrator can configure one
adoption-policies.jsonpolicy inkfp-artifact-storage-config. It selects candidate namespaces and fixes an exact storage destination and receiver template. A trusted GitOps provisioner, controller or external-service adapter publishes current namespace UID/principal bindings in a protected ConfigMap. Binding and template sources must be explicitly referenced, labelled for discovery, and located in the KFP installation namespace. Matching labels or tenant-local ConfigMaps cannot approve access. Existing and newly provisioned namespaces enroll automatically;approvals.jsonremains available for non-overlapping exceptions.The policy derives the existing approval object and reuses receiver reconciliation and readiness checks. It preserves approved broker sidecars/volumes/ServiceAccount; credential values are excluded from the binding, while the approved workload revision is included. Each request reads the current complete broker-written session profile, so normal STS renewal does not restart the receiver. Missing/stale bindings, missing sources/templates, conflicting authorities and revoked policy remain pending and block
autowith retryable HTTP 503 after reconciliation. Enrollment flags can distinguish existing/new namespaces using an explicit UTC cutoff. Invalid shared JSON, document structure or selectors temporarily fail closed across enabled namespaces. A valid nonmatching selector excludes unrelated invalid policy payloads. New controller-owned provisional records recover automatically after configuration correction or deletion only if no external approval was ever resolved; adopted, revoked and ambiguous earlier records stay fail-closed. Restoring valid external approval recovers adopted namespaces. Missing referenced sources block the matching integration.This adapter supports brokers writing a named AWS shared-credentials file with access key, secret key and session token. The administrator trusts the provisioner’s namespace-to-principal mapping and storage grants; KFP does not infer isolation from an IRSA annotation or independently prove arbitrary external IAM policy. Enrollment configures artifact reads and receiver rollout; the provisioner still supplies identities, broker/ServiceAccount wiring and pipeline root/write permissions. Deployments owned by another controller are not taken over automatically. Existing roots with valid grants remain in place within the approved store. Switching a namespace from the bundled MinIO receiver to external S3 is a destination migration: old
minio://mlpipeline/...run artifacts become unreadable through that receiver. Inventory and migrate both objects and persisted references before cutover, or postpone adoption; KFP does not copy objects, rewrite history or serve both destinations automatically. Other custom integrations retain prefix enforcement or reviewed explicitstorage.Managed rollout remains automatic: ingress policy first, receiver rollout/readiness, bound protocol requests and retryable pending states. No manual UI/controller ordering is required. Updated receivers also pin legacy requests to the approved external destination and credential source; native URI query overrides are rejected instead of silently ignored. Removing automatic approval blocks
auto; revoking the underlying identity also requires action in the broker/store for old-UI or explicit-storage consumers. HTTP artifacts, archived logs and volume policies remain separate.Operator guide includes the reusable adoption policy and explicit approval examples, file contract, readiness inspection, capacity and network-isolation requirements. The standard receiver adds one Pod per enabled namespace (10m CPU/70Mi requests; 100m CPU/500Mi limits), plus broker/mesh sidecars and rollout surge. Actual NetworkPolicy enforcement and additive-policy review remain necessary.
Review recovery
A missing readiness record preserves prefix/metadata authorization and the namespace proxy route; it never selects central UI credentials. Failed record lookup/validation remains HTTP 503 with actionable permission/configuration guidance. External reconciliation reports
kubeflow-pipelines-ready. Managed recovery preserves existing, verified Namespace ownership for receiver, configuration, credential and execution-bootstrap children. It never claims orphaned or foreign resources; central record ownership stays outside the preservation path. Malformed launcher configuration keeps the prior prefix-authorized namespace-proxy behavior instead of an endless reconciliation retry; the receiver can still return an actionable configuration error. Driver-agent CA rotation is reconciled even during pending/revoked approval while populated service-account token Secrets and resource ownership are preserved.The live fixture now exercises malformed policy correction and approval-config deletion against real artifact reads, requiring recovery with the same authorization Secret UID. It also checks intentional rejection of old bundled-store history after external cutover, recovery of adopted namespaces, and renewed cross-tenant object/list/SAR checks. Revocation and source/identity binding are unchanged.
Legacy roots and custom stores
The managed version-1 path now runs existing ownership checks for exact
artifactsand its descendants after key normalization, including encoded paths, previews, raw downloads and directory routes. It retains the existing IAM grant for direct-workload compatibility; approved external version-2 storage and reviewed explicitstoragestill use their identity grants. Namespace-less legacy history can require object/reference migration, even for the owning user; untracked V1 objects cannot use the narrow 2.18 MLMD-backed audit exception.Approved external buckets with dots now use the same validation predicate in configuration and serving. Namespace and volume resource-name validation remains strict. Live external acceptance uses a dotted bucket throughout historical access, rotation and isolation checks.
Stores without the controller's IAM verification API can remain pending despite pre-created Secrets. Unsupported-operation, transport and permission errors remain fail-closed; repeated-sync regressions prove credentials are preserved and no users/keys or policies are created. The guides require selecting
key-prefixbefore rollout or reviewed explicitstoragewith genuinely restricted identities and a compatible receiver. Existing endpoint configuration and custom namespace provisioning remain necessary. This is controller error-path coverage, not live MinIO qualification.Rollout errors and existing storage grants
An incompatible namespace receiver now returns a fresh plain-text
503withX-Kfp-Artifact-Proxy-Error: receiver-upgrade-required. Its HTML, content encoding and other upstream headers are discarded; valid artifacts still stream. The message explains automatic rollout and where to investigate a persistent failure. Pending/transient states and receiver upgrades retainRetry-After: 5. Invalid or empty readiness records (including a missing data key) and Kubernetes 401/403 instead give corrective configuration/authentication/RBAC guidance withoutRetry-After; no Secret contents are exposed.shared/remains intentionally tenant-readable and tenant-writable. Retained administrator-added object-read grants can also make additional objects readable to namespace viewers, including users unable to launch Pods or read Secrets.readyverifies required grants and receiver wiring, not least privilege of every effective policy. Before upgrading, inventory actual viewers and effective inline/attached/group/bucket permissions; narrow unintended grants or selectkey-prefixbefore rollout if that audience expansion is not intended. Listing still depends on effective bucket policy. The guide and agent assessment require these checks; no grant is silently removed. Legacy managedartifacts/remains ownership-checked.flowchart LR subgraph Before A["Old receiver returns HTML"] --> B["503 still contains HTML"] end subgraph After C["Old receiver response"] --> D["Discard body and upstream headers"] --> E["503 with plain upgrade guidance"] endMinimal reproducer, from
frontend/serverafter installing its declared dependencies:With the new regression tests applied to the previous implementation, six assertions failed on each branch: fixed-length/chunked/gzip HTML leaked and empty/whitespace/missing-key records were misclassified. They pass with this fix. Additional tests check hostile upstream headers, incremental valid streaming and sanitized permanent/transient errors. The live fixture checks the real old receiver's exact plain error and a distinct read-only namespace-A viewer reading tenant-B-written shared data while being denied Pod creation, B's private data and access to B's namespace. A separate real namespace-A ListObjectsV2 must return exactly the seeded shared object. SeaweedFS 4.34 returns an empty directory marker for GET, so that response is not counted as UI listing evidence.
IPv6 endpoints and IAM recovery
Approved external IPv6 endpoints now pass the bare IPv6 host to Minio while retaining the canonical approved URL and authorization binding. Previously,
http://[fd00::10]:8333passed controller validation but failed Minio client construction, producing a misleading credential error. Real-client regressions cover HTTP/HTTPS IPv6, default/explicit ports, IPv4 and DNS destinations. Malformed JSON, invalid schema and whitespace inKFP_ARTIFACT_EXTERNAL_STORAGE_CONFIGnow fail startup with a sanitized error naming that setting, without printing its contents.The managed IAM failure policy remains fail-closed. The controller already requests reconciliation after 10 seconds when verification is pending; this is not a recovery-time guarantee. Expanded regressions start ready, inject failures in each of
ListAccessKeys,GetUserPolicyandGetBucketPolicy, persist pending, then verify automatic readiness recovery with unchanged credentials and no new keys or policy writes. An old ready record does not establish current authorization; a pending record does not silently activate prefix/audit fallback. Per-request readiness reads and validation remain unchanged.Minimal endpoint/config reproducer, from
frontend/serverwith declared dependencies installed:The new tests produced five failures per branch before the fix (two real Minio IPv6 constructor failures and three startup-diagnostic failures), then passed after it.
flowchart LR subgraph Before A["Approved bracketed IPv6 URL"] --> B["Bracketed host sent to Minio"] --> C["InvalidEndpointError / artifact 503"] end subgraph After D["Same approved URL and binding"] --> E["Bare host passed only to Minio"] --> F["Client constructs successfully"] endFull-cluster execution also exposed an overly broad fixture leak sentinel: a legitimate 403 message names the namespace followed by a colon. The assertion now checks the exact seeded artifact payload; a regression proves a 403 containing those bytes is still rejected. This changes the test, not runtime authorization.
Validation
Single DCO-signed commit
0d891201erebased ona6bc4e783. Exact-head full-cluster acceptance passed 44 managed checks and 1 expected old-2.17.2-UI/V1-API skip, plus 25 external-storage checks. Source and runtime image identities agree; both suites have empty cleanup-error lists. Read-only viewer, exact shared-object listing and legacy-root checks pass before and after rotation. The real old receiver also produces the required plain-text upgrade error.External acceptance also verifies approved dotted-bucket historical access, three real STS renewals, expired-session recovery, foreign-role and cross-tenant object/list/SAR denial, approval/source revocation and recovery, and malformed-policy correction/deletion without replacing the authorization Secret or losing managed child ownership. The old failed revisions remain failed evidence: their fixture confused namespace punctuation in a correct 403 with artifact bytes; the corrected exact-payload regression now passes in the real cluster.
Current-head hosted frontend CI passed 1,855 UI tests, 1,371 server tests, 25 managed and 15 external live tests, 57 fixture regressions, 5 production-bundle startup checks and 38 real STS sessions, with clean cleanup. Local server builds/lint, controller tests (268 master / 248 release), all 49 runtime-path hooks and current workflow-policy checks pass. Standalone master/release/guide and exact combined guide tree
f850b5d591a425e004df220dfa0a193efb841d65pass Sphinx with warnings treated as errors and zero warnings.Stock Kind networking is used. The IPv6 regression uses the real client constructor; it does not claim live IPv6 transport qualification. IAM error recovery is controller error-path coverage, not live MinIO qualification. Candidate artifact acceptance is complete; required CI, maintainer approval and merge remain separate, as do final merged-release, populated upgrade, ingress login, external IAM and mesh/additive-policy qualification.
Merge #14883 on master, then #14884 on 2.18, then #14703. Tracking: #14421.