Skip to content

Add project keys to OCI endpoints#830

Open
epompeii wants to merge 12 commits intodevelfrom
u/ep/oci-project-key
Open

Add project keys to OCI endpoints#830
epompeii wants to merge 12 commits intodevelfrom
u/ep/oci-project-key

Conversation

@epompeii
Copy link
Copy Markdown
Member

@epompeii epompeii commented May 7, 2026

This changeset adds accepting project keys (#823) to the OCI endpoints.
The docker login will now take a project slug or UUID and a bencher_run token for OCI authentication.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

🤖 Claude Code Review

PR: #830
Base: devel
Head: u/ep/oci-project-key
Commit: 96fd6ff32766ca721c27cf050d6cd0d858652302


Now I have a complete picture of the changes. Let me write the review.

Pull Request Review: OCI Project Key Authentication

Summary

This PR adds a new authentication path for OCI operations using project keys (prefixed bencher_run_). It introduces a new OciProject JWT audience type, project-scoped rate limiting for requests, and integration tests validating the end-to-end flow.


Positive Observations

  • Good separation of concerns — credential dispatch in extract_basic_credentials is clean and follows the existing patterns for runner/public tokens.
  • Comprehensive cross-type JWT rejection tests ensure tokens can't be confused across audiences.
  • Logging improvements (inspect_err + log_unauthorized_with_www_authenticate) replacing #[expect(clippy::map_err_ignore)] improves debuggability without leaking sensitive info to callers.
  • The verify_project_token function is simple and correct — comparing UUID from the resolved project against the JWT subject.

Issues & Concerns

Security

  1. Missing rate limiting on the token endpoint for project key auth (lib/api_auth/src/oci/mod.rs:312-378): The project_key_oci_token function validates the key and issues a JWT but does not apply any rate limit before issuing the token. An attacker with a stolen or brute-forced key prefix could hammer the token endpoint. User auth calls apply rate limits in the downstream OCI operations, but since project keys are long-lived secrets (vs short-lived JWTs), rate limiting at the token issuance point would add defense-in-depth.

  2. Utc::now() in new_oci_project (lib/bencher_token/src/key.rs:141): The project key token endpoint correctly uses context.clock.now() for key expiration checks, but new_oci_project uses Utc::now() directly. Per CLAUDE.md, time-based logic should use the injected clock for testability. The other OCI token constructors (new_oci_public, new_oci_runner) have the same issue, so this is pre-existing — but since you're adding a new function, this is an opportunity to address it.

  3. Prefix-based dispatch is fragile (lib/api_auth/src/oci/mod.rs:102): password.starts_with(PROJECT_KEY_PREFIX) routes to project key auth. If a user happens to have an API key JWT whose encoded form starts with bencher_run_, it would be misrouted. This is extremely unlikely given JWT structure (ey...), but worth a comment documenting the assumption.

Potential Bugs

  1. Token endpoint issues token with Push + Pull actions but project_key_oci_token blocks pull-only (lib/api_auth/src/oci/mod.rs:326): When Docker does a push, it requests scope push,pull. The guard only blocks Pull && !Push. This is correct for the Docker push flow, but the comment "Only bare metal runners should be pulling" is somewhat misleading — the issued token will have pull capability when push is also requested. The pull capability is then exercised in validate_pull_access which accepts OciPullIdentity::Project. This all works correctly, but the code path is non-obvious.

  2. require_push_access resolves project twice on user-auth path (plus/api_oci/src/auth.rs:228-247): When the project token path doesn't match, the function falls through to user auth. If the user auth succeeds and the repository parses as a ProjectResourceId, it calls resolve_project again (line 241). This is a second DB query for the same project that validate_push_access (the other push path) also resolves. Minor inefficiency, not a bug.

Code Quality

  1. map_err(|_err| ...) pattern (plus/api_oci/src/auth.rs:423, lib/api_auth/src/oci/mod.rs:237): Two instances still use |_err| after .inspect_err(|e| ...). These could simply use |_| since the binding is unused — or better, since you're already logging in inspect_err, consider whether these should use log_unauthorized_with_www_authenticate consistently instead of the split inspect_err + manual map_err.

  2. Missing rate limit on the token endpoint project-key path: The project_key_oci_token function resolves query_project.uuid but never calls context.rate_limiting.project_request(project.uuid). Every other code path that resolves a project applies this rate limit. The rate limit is applied later when the issued token is used, but the token issuance itself is unmetered.

  3. ProjectOciClaims has all fields pub (lib/bencher_token/src/claims.rs:311-318): Other claims types (RunnerOciClaims, AuthOciClaims) don't expose internal fields publicly. Consider making these private with accessor methods for consistency.

Style / CLAUDE.md Compliance

  1. The test file (tasks/test_api/src/task/plus/runner.rs) uses anyhow which is acceptable per CLAUDE.md for tasks/ crates — confirmed correct.

  2. The openapi.json changes look auto-generated and consistent with the code changes.


Recommendation

The core design is sound. The main actionable items are:

  1. Add project_request rate limiting inside project_key_oci_token before issuing the JWT.
  2. Consider adding a brief comment explaining why prefix-based dispatch is safe.
  3. Align new_oci_project with the clock injection pattern if practical (or leave as tech debt with a note, since it's pre-existing).

Model: claude-opus-4-6

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

🐰 Bencher Report

Branchu/ep/oci-project-key
Testbedintel-v1
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
microseconds (µs)
(Result Δ%)
Upper Boundary
microseconds (µs)
(Limit %)
Adapter::Json📈 view plot
🚷 view threshold
4.56 µs
(+0.28%)Baseline: 4.55 µs
4.73 µs
(96.35%)
Adapter::Magic (JSON)📈 view plot
🚷 view threshold
4.42 µs
(-0.07%)Baseline: 4.42 µs
4.61 µs
(95.88%)
Adapter::Magic (Rust)📈 view plot
🚷 view threshold
25.36 µs
(+0.03%)Baseline: 25.36 µs
26.29 µs
(96.48%)
Adapter::Rust📈 view plot
🚷 view threshold
3.46 µs
(+0.67%)Baseline: 3.44 µs
3.58 µs
(96.75%)
Adapter::RustBench📈 view plot
🚷 view threshold
3.46 µs
(+0.67%)Baseline: 3.43 µs
3.56 µs
(97.01%)
🐰 View full continuous benchmarking report in Bencher

@epompeii epompeii force-pushed the u/ep/oci-project-key branch from 3eb3d1c to fac6d6a Compare May 8, 2026 02:52
@epompeii epompeii force-pushed the u/ep/oci-project-key branch from 4e5c30c to 9a0aacf Compare May 8, 2026 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant