Skip to content

Ollama Cloud: keyless Ed25519 auth (challenge-response → Bearer) — follow-up to #13 #14

Description

@jmlago

Goal

Let a caller reach Ollama Cloud without provisioning an API key, by using the
Ed25519 identity already on disk at ~/.ollama/id_ed25519 (the one ollama signin
establishes) — the keyless, zero-config path, analogous to how the Codex provider
rides a ChatGPT subscription instead of an API key.

Today (after #13) the Ollama provider supports local discovery (no auth) and
Ollama Cloud via OLLAMA_API_KEYAuthorization: Bearer <key>. This issue
tracks the keyless Ed25519 path, to be done correctly and verified.

Why the first attempt was removed in #13

#13 originally shipped a hand-rolled sources/ollama_auth.py that did not
implement Ollama's real auth. It pre-signed a self-timestamped string
({method},{url}?ts={ts}) and sent Authorization: <pubkey_b64>:<sig_b64>
directly to /api/tags. That is a different, invented protocol; its tests
were self-referential (they asserted the module's own output format, never hit
ollama.com); and failures were swallowed silently (except Exception: return []),
so "no error" looked like "it worked". It was never verified against the live
endpoint. Commit 77f8608 removed it (and the unpinned cryptography>=42.0.0
dep) and shipped the API-key path; #13 merged as that.

The original code is not lost — recover it from the pre-repair commit:
git show 9eedac2:sources/ollama_auth.py (branch history of #13).

The real protocol to implement

Ollama uses a challenge-response that ends in a Bearer token, not a direct
signed header:

  1. Hit the endpoint → 401 with a WWW-Authenticate header carrying
    realm, service, scope, and a server nonce.
  2. Sign method + URL + server-nonce with the Ed25519 key (auth.Sign).
  3. POST the signature to the realm (auth server) → receive a Bearer token
    (api.TokenResponse) (getAuthorizationToken).
  4. Use Authorization: Bearer <token> on the actual API request.

(Reference: ollama source auth.Sign / getAuthorizationToken / registryChallenge.)

Acceptance criteria (the merge bar)

  • Implements the real 401 → server-nonce → sign → realm token-exchange → Bearer
    flow (no client-timestamp scheme, no direct pubkey:sig header).
  • A captured real-endpoint test: a recorded 401/WWW-Authenticate + token
    exchange proving it authenticates against ollama.com (since there is no CI,
    the test is the load-bearing evidence — self-referential tests don't count).
  • Falsification passes: with local Ollama off, OLLAMA_CLOUD=1, no
    OLLAMA_API_KEY, only ~/.ollama/id_ed25519 → cloud models actually appear.
  • Auth failures surface (no silent return [] masking a broken handshake).
  • One crypto library, pinned == (the repo pins all deps); justify it (Axis 4).
  • API-key path stays as fallback.

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions