Skip to content

Add project keys to OCI endpoints - #830

Merged
epompeii merged 13 commits into
develfrom
u/ep/oci-project-key
May 9, 2026
Merged

Add project keys to OCI endpoints#830
epompeii merged 13 commits into
develfrom
u/ep/oci-project-key

Conversation

@epompeii

@epompeii epompeii commented May 7, 2026

Copy link
Copy Markdown
Member

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

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

PR: #830
Base: devel
Head: u/ep/oci-project-key
Commit: 1da772ed4c364cad87ea9c7c41e593c00de61550


Good — new_oci_project follows the exact same pattern as new_oci_runner. This is consistent.

Here is my review:


PR Review: OCI Project Key Authentication

Summary

This PR adds project-scoped key authentication to the OCI registry, enabling CI runners to authenticate with a project-slug:bencher_run_xxx credential pair instead of user email/JWT. It introduces a new OciProject JWT audience, project request rate limiting, and end-to-end integration tests.

Strengths

  • Good security design: Project keys are hashed with SHA-256 before storage; raw keys are only returned once at creation. The PROJECT_KEY_PREFIX (bencher_run_) provides reliable disambiguation from JWTs (which start with ey).
  • Comprehensive cross-type token rejection tests: All combinations of OCI token types (public, auth, project, runner) are tested to ensure they can't be used interchangeably.
  • Improved error observability: Removed #[expect(clippy::map_err_ignore)] suppressions throughout and replaced with inspect_err + logging before discarding errors. This is a clear improvement for debuggability.
  • Rate limiting: New per-project request rate limiter (2048/min, 8192/hr, 16384/day) is applied consistently across pull, push, and base endpoints.
  • extract_basic_credentials refactor: Cleaner API — returns Option instead of using errors for control flow.

Issues

1. Pull-only restriction may be too restrictive (Medium)

project_key_oci_token (lib/api_auth/src/oci/mod.rs line ~222) rejects pull-only requests for project keys with the message "Use a runner token to pull." However, validate_pull_access in plus/api_oci/src/auth.rs accepts project tokens for pull and calls verify_project_token. This means a project key can obtain a token with push scope and then use that token to pull, but it cannot obtain a pull-only token. The logic is internally consistent, but the error message could mislead callers who just want to pull — they'll get FORBIDDEN at the token endpoint even though the resulting push-scoped token will work for pulls too.

2. Double rate-limit application on some paths (Low)

In validate_push_access (plus/api_oci/src/auth.rs), when a user token is used, the flow calls apply_push_rate_limit (which applies user rate limits) and then also calls context.rate_limiting.project_request(push_access.project.uuid)?. For project tokens, only the project request limit is applied. This is asymmetric but probably intentional — user pushes are rate-limited by both user and project dimensions. Worth a comment to clarify intent.

3. require_push_access error on nonexistent projects (Low)

In require_push_access for user tokens (plus/api_oci/src/auth.rs line ~251):

if let Ok(repo_rid) = repository.parse::<ProjectResourceId>() {
    let project = resolve_project(context, &repo_rid).await?;
    context.rate_limiting.project_request(project.uuid)?;
}

If the project doesn't exist, resolve_project returns an error that propagates via ?. Since this function is documented for "simple write operations that don't need the full project creation flow," verify this doesn't break operations like delete on repositories that have already been removed from the project table.

4. matches_resource_id duplicates macro logic (Nit)

QueryProject::matches_resource_id (lib/bencher_schema/src/model/project/mod.rs line ~82) duplicates the slug/uuid matching logic already in the fn_eq_resource_id! macro. The in-memory version is useful when you already have a loaded QueryProject and want to avoid a DB query, but it's a second place to maintain if ProjectResourceId gains variants. Consider adding a comment or using a shared trait.

5. Utc::now() in token creation (Nit)

new_oci_project uses Utc::now() directly (lib/bencher_token/src/key.rs line 141). CLAUDE.md prefers Clock::Custom for testability. However, this is consistent with new_oci_runner and new_oci_public, so it's a pre-existing pattern, not a regression.

Security

  • Project keys provide scoped, revocable credentials — a clear improvement over sharing user API tokens with CI.
  • Token audience isolation is enforced and tested.
  • Error details are logged server-side but not leaked to clients.
  • The verify_project_token check ensures a project token can only operate on its own project, preventing cross-project access.
  • No secrets are logged — inspect_err only logs error types, not credential values.

Missing

  • No tests for extract_basic_credentials directly (e.g., malformed base64, missing colon, non-Basic scheme). The function is exercised indirectly through integration tests but unit tests would catch edge cases.
  • The OpenAPI spec change was generated (cargo gen-types), but the JsonProjectRateLimiter schema now has requests as a new optional field — downstream consumers should be aware.

Model: claude-opus-4-6

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

🐰 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.57 µs
(+0.59%)Baseline: 4.55 µs
4.73 µs
(96.65%)
Adapter::Magic (JSON)📈 view plot
🚷 view threshold
4.48 µs
(+1.35%)Baseline: 4.42 µs
4.61 µs
(97.24%)
Adapter::Magic (Rust)📈 view plot
🚷 view threshold
26.04 µs
(+2.71%)Baseline: 25.36 µs
26.29 µs
(99.07%)
Adapter::Rust📈 view plot
🚷 view threshold
3.46 µs
(+0.53%)Baseline: 3.44 µs
3.58 µs
(96.61%)
Adapter::RustBench📈 view plot
🚷 view threshold
3.46 µs
(+0.73%)Baseline: 3.43 µs
3.56 µs
(97.07%)
🐰 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
@epompeii
epompeii merged commit 044781a into devel May 9, 2026
62 checks passed
@epompeii
epompeii deleted the u/ep/oci-project-key branch May 9, 2026 03:01
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