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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions