GoogleCloudPlatform / GoogleCloudPlatform/gcsfuse
Feature request: --auth-token-file for short-lived OAuth2 access tokens
- Dominant language
- Go
- Stars
- 2.3k
- Forks
- 510
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 35
Description
## Use case
FUSE is being used in short-lived microvm sandbox environments like [e2b](https://e2b.dev), Modal, Daytona, and similar Firecracker-microVM-based sandboxes. Many of these providers offer fast spin up and exist in an api platform (not always self hosted where workload identity is an option).
in these environments:
- The orchestrator already has authenticated GCP credentials and can mint a short-lived oauth access token like with sts Credential Access Boundary downscoping, scoped to a specific bucket prefix on the orchestrator side.
- The orchestrator wants to hand that bearer token to `gcsfuse` inside the sandbox, mount, and move on.
- The sandbox lifetime is much shorter than the token TTL (sandbox idle timeout often ~120s, token TTL typically 1h), so refresh is structurally unnecessary.
## Current options and why they don't fit
- `--key-file `: shipping a long-lived service account private key into a sandbox where AI-generated code is going to execute is a non-starter from a security perspective. We want short-lived, prefix-scoped credentials. A typical pattern is a single gcs bucket with per user prefixes, and 1 sandbox per user, so a token scoped to a single users prefix and mounted with --only-dir is perfect.
- `--token-url `: works, but forces us to run an HTTP server inside the sandbox just to serve a static token. Concretely, our current setup needs:
1. A small HTTP daemon baked into the sandbox template, listening on `127.0.0.1:8001`, that reads a token file and returns it on `GET /token`:
```python
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
with open("/tmp/gcs-token", "rb") as f:
body = f.read()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 8001), Handler).serve_forever()
2. Orchestrator-side glue to write the token file and start that daemon before gcsfuse runs:
echo '{"access_token":"ya29...","expires_in":3600,"token_type":"Bearer"}' > /tmp/gcs-token
nohup /opt/token-server.py > /dev/null 2>&1 &
sudo gcsfuse --token-url=http://127.0.0.1:8001/token --only-dir=user-prefix bucket /home/user
This works but it's ~50 lines of glue (HTTP daemon + lifecycle + readiness check) per integration just to satisfy the "fetch-via-URL" constraint. It also adds a localhost listening port for the lifetime of the
sandbox, which is one more thing to harden against accidentally exposing.
Proposal: --auth-token-file
Add a flag that points at a file containing an OAuth2 token response, matching the existing --key-file API shape:
echo '{"access_token":"ya29...","expires_in":3600,"token_type":"Bearer"}' > /tmp/gcs-token
sudo gcsfuse --auth-token-file=/tmp/gcs-token --only-dir=user-prefix bucket /home/user
The file would contain the same JSON shape that --token-url endpoints already return today, so the credential-loader could reuse most of the existing code path:
{
"access_token": "ya29.",
"expires_in": 3600,
"token_type": "Bearer"
}
Behaviorally:
- On mount, read the file once and construct an oauth2.Token directly. No JWT signing, no metadata server call, no HTTP.
- When the cached token nears expiry, re-read the file (so callers who care can refresh by rewriting the file from outside) — or fail if expires_in has elapsed and the file hasn't been touched. Either is fine;
the latter is simpler.
For our use case the sandbox always dies before the token expires, so even a "read once, fail on expiry" implementation would be sufficient.
Alternatives considered
- --auth-token on argv: would leak via ps, /proc/*/cmdline, shell history, audit logs. File-based is strictly better for the same convenience.
- GOOGLE_OAUTH_ACCESS_TOKEN env var: same ps//proc exposure as argv. File-based avoids it.
- Workload Identity Federation via external_account JSON: works in some environments but requires a token-source URL or executable, which has the same "you need a callback" problem we're trying to avoid.
## Willing to contribute
Happy to send a PR if there's interest. Would appreciate guidance on:
- Preferred flag name (--auth-token-file? --access-token-file? something else)
- Behavior when expires_in elapses with no file rewrite (fail vs. continue and let GCS reject)
- Whether the file should be the OAuth2 JSON shape above, or a simpler bearer-only file (one line: the access token string)
Contributor guide
Assessment
This issue has not been assessed yet.