diff --git a/infra/gae-internal/.gcloudignore b/infra/gae-internal/.gcloudignore new file mode 100644 index 00000000000..603f0b6ea06 --- /dev/null +++ b/infra/gae-internal/.gcloudignore @@ -0,0 +1,19 @@ +# This file specifies files that are *not* uploaded to Google Cloud +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +# Python pycache: +__pycache__/ +# Ignored by the build system +/setup.cfg \ No newline at end of file diff --git a/infra/gae-internal/app.yaml b/infra/gae-internal/app.yaml new file mode 100644 index 00000000000..f00aff916c3 --- /dev/null +++ b/infra/gae-internal/app.yaml @@ -0,0 +1,8 @@ +runtime: python312 + +entrypoint: gunicorn -b :$PORT main:app + +instance_class: F1 + +automatic_scaling: + max_instances: 2 diff --git a/infra/gae-internal/deploy b/infra/gae-internal/deploy new file mode 100755 index 00000000000..8a7bb47b3b1 --- /dev/null +++ b/infra/gae-internal/deploy @@ -0,0 +1,3 @@ +#!/bin/bash +gcloud app deploy app.yaml --project google.com:perfetto-gae-internal + diff --git a/infra/gae-internal/main.py b/infra/gae-internal/main.py new file mode 100644 index 00000000000..96cd9f2e4c6 --- /dev/null +++ b/infra/gae-internal/main.py @@ -0,0 +1,41 @@ +import posixpath + +from flask import Flask, Response, abort +from flask_cors import CORS +from google.cloud import storage + +app = Flask(__name__) +CORS( + app, + origins=["https://ui.perfetto.dev", "http://localhost:10000"], + supports_credentials=True) + +BUCKET_NAME = "perfetto-ui-internal" +BASE_PREFIX = "extension-server-v1" + +gcs_client = storage.Client() +bucket = gcs_client.bucket(BUCKET_NAME) + + +@app.route("/", defaults={"path": ""}) +@app.route("/") +def serve(path): + # Normalize and reject any path traversal attempts. + normalized = posixpath.normpath(path) if path else "" + if normalized.startswith( + "..") or "/../" in normalized or normalized.startswith("/"): + abort(403) + + blob_path = f"{BASE_PREFIX}/{normalized}" if normalized else BASE_PREFIX + "/" + + blob = bucket.blob(blob_path) + if not blob.exists(): + abort(404) + + data = blob.download_as_bytes() + content_type = blob.content_type or "application/octet-stream" + return Response(data, content_type=content_type) + + +if __name__ == "__main__": + app.run(host="127.0.0.1", port=8080) diff --git a/infra/gae-internal/requirements.txt b/infra/gae-internal/requirements.txt new file mode 100644 index 00000000000..60dd206376e --- /dev/null +++ b/infra/gae-internal/requirements.txt @@ -0,0 +1,4 @@ +Flask==3.0.0 +Flask-Cors==4.0.0 +google-cloud-storage==2.14.0 +gunicorn==21.2.0