08-Project-Health / 08.07.CLE-Ad-Hoc

08.07.CLE Ad Hoc

08.07. CLE Ad Hoc

Yhteenveto

Tarkistettiin, että kaikki Google Sheetin ympäristömuuttujat löytyvät GCP Secret Managerista kaikissa ympäristöissä (inf, dev, tst, prd).

Löydökset

Puuttuvat salaisuudet inf-ympäristöstä (2 kpl)

Puuttuvat salaisuudet kaikista ympäristöistä (6 kpl)

Google Sheetissä oli 6 salaisuutta, joille ei ollut kontteja GCP:ssä: - bnc-cpt-paypal-test-user-username - bnc-cpt-paypal-test-user-password - bnc-cpt-stripte-test-account-card-number - bnc-cpt-stripte-test-account-card-validity-month - bnc-cpt-stripte-test-account-card-validity-year - bnc-cpt-stripte-test-account-card-crc-code

Huom: Stripe-salaisuuksissa on kirjoitusvirhe Google Sheetissä (stripte eikä stripe). Käytetään Google Sheetin muotoa, koska se on ainoa totuuden lähde.

Tehdyt toimenpiteet

  1. Lisätty 6 puuttuvaa salaisuutta YAML-konfiguraatioihin (inf, dev, tst, prd)
  2. Generoitu Terraform-konfiguraatiot uudelleen kaikille ympäristöille
  3. Provisiointu step 029 kaikille ympäristöille (12 resurssia lisätty per ympäristö)
  4. Synkronoitu Google Sheet → GCP Secret Manager kaikille 4 ympäristölle

Lopputulos

Ympäristö Salaisuudet YAML Salaisuudet GCP Synkronoitu Tila
inf 41 41 39 OK OK
dev 41 41 39 OK OK
tst 41 41 39 OK OK
prd 41 41 39 OK OK

Kaikki ympäristöt synkronoitu onnistuneesti, 0 virhettä.


Tesla Gateway — Local Dev & CI Proxy Architecture

1. The Problem

Local development and CI environments cannot perform real Tesla OAuth flows or Fleet API calls because they lack:

Without these, the local API container (con-bnc-cpt-api) cannot: 1. Generate a valid Tesla OAuth authorization URL 2. Exchange an authorization code for tokens 3. Query the Fleet API for vehicle data

The .env.lcl already references a mock container that does not yet exist:

TESLA_AUTH_URL=http://con-bnc-cpt-tesla-mock:9100/oauth2/v3/authorize
TESLA_TOKEN_URL=http://con-bnc-cpt-tesla-mock:9100/oauth2/v3/token
TESLA_FLEET_API_BASE=http://con-bnc-cpt-tesla-mock:9100

We need a container on port 9100 that fulfills this contract.

2. The Solution: Tesla Gateway (Dual-Mode)

A single Tesla Gateway container (con-bnc-cpt-tesla-gw) on port 9100 replaces the planned con-bnc-cpt-tesla-mock. It operates in one of two modes controlled by the TESLA_GW_MODE environment variable:

Mode Use Case External Dependencies Real Tesla Data
mock CI, fast local dev None No
proxy Manual local testing Dev API on GCP Yes

Key architectural insight: Mock mode and proxy mode use completely different routing patterns:

3. Control Flow Diagrams

3.1 Mock Mode (Default)

The gateway impersonates Tesla. The local API treats it as the real Tesla API. No external network calls are made.

┌─────────────────────────────────────────────────────────────────────┐
│                        MOCK MODE FLOW                               │
│                                                                     │
│  Browser (localhost:3333)                                           │
│    │                                                                │
│    │ 1. POST /api/v1/tesla/oauth/initiate {paymentId, plan}        │
│    ▼                                                                │
│  Vite Dev Proxy (/api -> localhost:8100)                            │
│    │                                                                │
│    ▼                                                                │
│  Local API (con-bnc-cpt-api:8100)                                  │
│    │                                                                │
│    │ 2. GET gateway:9100/oauth2/v3/authorize?...                   │
│    │    (TESLA_AUTH_URL points to gateway)                          │
│    ▼                                                                │
│  ┌──────────────────────────────────────────┐                       │
│  │  Tesla Gateway (con-bnc-cpt-tesla-gw)   │                       │
│  │  Port 9100 — MOCK MODE                  │                       │
│  │                                          │                       │
│  │  /oauth2/v3/authorize                    │                       │
│  │    → Immediate redirect with mock code   │                       │
│  │                                          │                       │
│  │  /oauth2/v3/token                        │                       │
│  │    → Returns mock access_token           │                       │
│  │                                          │                       │
│  │  /api/1/vehicles                         │                       │
│  │    → Returns fake vehicle list           │                       │
│  │      VIN: 5YJ3E1EA8NF000001             │                       │
│  │      Name: "Mock Tesla"                  │                       │
│  │      Model: Model 3                      │                       │
│  │                                          │                       │
│  │  /api/1/vehicles/{tag}/vehicle_data      │                       │
│  │    → Returns fake odometer, battery, etc │                       │
│  └──────────────────────────────────────────┘                       │
│    │                                                                │
│    │ 3. Mock token + mock vehicle data returned                     │
│    ▼                                                                │
│  Local API builds report from mock data                             │
│    │                                                                │
│    │ 4. Redirect to localhost:3333?oauth=success&session_id=xxx     │
│    ▼                                                                │
│  Browser displays mock report                                       │
│                                                                     │
│  *** No external network calls. No real credentials needed. ***     │
└─────────────────────────────────────────────────────────────────────┘

3.2 Proxy Mode

The gateway proxies application-level Tesla API calls to the dev API on GCP Cloud Run. The dev API holds real Tesla credentials and performs real OAuth + Fleet API calls.

┌─────────────────────────────────────────────────────────────────────┐
│                        PROXY MODE FLOW                              │
│                                                                     │
│  Browser (localhost:3333)                                           │
│    │                                                                │
│    │ 1. POST /api/v1/tesla/oauth/initiate {paymentId, plan}        │
│    ▼                                                                │
│  Vite Dev Proxy                                                     │
│    │                                                                │
│    │  /api/v1/tesla/* → localhost:9100  (gateway)                   │
│    │  /api/*          → localhost:8100  (local API)                 │
│    ▼                                                                │
│  ┌──────────────────────────────────────────┐                       │
│  │  Tesla Gateway (con-bnc-cpt-tesla-gw)   │                       │
│  │  Port 9100 — PROXY MODE                 │                       │
│  │                                          │                       │
│  │  Receives: POST /api/v1/tesla/oauth/     │                       │
│  │            initiate {paymentId, plan}     │                       │
│  │                                          │                       │
│  │  Injects:                                │                       │
│  │    Header: X-Gateway-Key: <secret>       │                       │
│  │    Body:   frontendOrigin:               │                       │
│  │            http://localhost:3333          │                       │
│  │                                          │                       │
│  │  Forwards to:                            │                       │
│  │    https://dev.api.carpulsetracker.com   │                       │
│  │    /api/v1/tesla/oauth/initiate          │                       │
│  └──────────────────────────────────────────┘                       │
│    │                                                                │
│    │ 2. Dev API returns {authUrl: "https://auth.tesla.com/..."}     │
│    ▼                                                                │
│  Browser redirects to Tesla auth                                    │
│    │                                                                │
│    │ 3. User authenticates on auth.tesla.com                        │
│    ▼                                                                │
│  Tesla redirects to registered TESLA_REDIRECT_URI:                  │
│    https://dev.api.carpulsetracker.com/api/v1/tesla/oauth/callback  │
│    │                                                                │
│    │ 4. Dev API exchanges code for token, fetches vehicles,         │
│    │    builds report, reads frontend_origin from OAuth state       │
│    ▼                                                                │
│  Dev API redirects browser to:                                      │
│    http://localhost:3333?oauth=success&session_id=xxx                │
│    │                                                                │
│    │ 5. GET /api/v1/tesla/report/{session_id}                       │
│    ▼                                                                │
│  Vite Proxy → Gateway → dev.api.carpulsetracker.com                 │
│    │                                                                │
│    │ 6. Returns stored report with real vehicle data                │
│    ▼                                                                │
│  Browser displays real Tesla report                                 │
│                                                                     │
│  *** Gateway does NOT sit in the Tesla redirect path. ***           │
│  *** Tesla redirects to dev API directly. ***                       │
└─────────────────────────────────────────────────────────────────────┘

4. Data Flow Diagrams

4.1 Mock Mode — Data Flow

                  Docker Network (all_lcl_docker)
    ┌──────────────────────────────────────────────────────┐
    │                                                      │
    │  ┌──────────┐       ┌──────────┐    ┌────────────┐  │
    │  │   WUI    │──API──│  API     │────│  Gateway   │  │
    │  │  :3333   │ calls │  :8100   │    │  :9100     │  │
    │  │          │       │          │    │  (mock)    │  │
    │  └──────────┘       └──────────┘    └────────────┘  │
    │                                                      │
    └──────────────────────────────────────────────────────┘

    Data flow:

    WUI                        API                     Gateway (mock)
     │                          │                          │
     │ POST /api/v1/tesla/      │                          │
     │   oauth/initiate         │                          │
     │ {paymentId, plan}        │                          │
     │─────────────────────────>│                          │
     │                          │ GET /oauth2/v3/authorize │
     │                          │─────────────────────────>│
     │                          │    redirect + mock code  │
     │                          │<─────────────────────────│
     │                          │                          │
     │                          │ POST /oauth2/v3/token    │
     │                          │ {code: "mock-code"}      │
     │                          │─────────────────────────>│
     │                          │ {access_token: "mock-*"} │
     │                          │<─────────────────────────│
     │                          │                          │
     │                          │ GET /api/1/vehicles      │
     │                          │ Authorization: Bearer *  │
     │                          │─────────────────────────>│
     │                          │ {response: [{id: 1, ..}]}│
     │                          │<─────────────────────────│
     │                          │                          │
     │  redirect to WUI         │                          │
     │  ?oauth=success          │                          │
     │  &session_id=xxx         │                          │
     │<─────────────────────────│                          │
     │                          │                          │
     │ GET /api/v1/tesla/       │                          │
     │   report/{session_id}    │                          │
     │─────────────────────────>│                          │
     │ {vin, odometer, ...}     │                          │
     │<─────────────────────────│                          │

4.2 Proxy Mode — Data Flow

    Docker Network                              GCP Cloud Run
    ┌─────────────────────────────┐    ┌───────────────────────┐
    │                             │    │                       │
    │  ┌──────┐    ┌──────────┐  │    │  ┌────────────────┐   │
    │  │ WUI  │    │ Gateway  │──│────│──│ Dev API        │   │
    │  │ :3333│────│ :9100    │  │    │  │ (Cloud Run)    │   │
    │  │      │    │ (proxy)  │  │    │  │                │   │
    │  └──────┘    └──────────┘  │    │  └────────────────┘   │
    │                             │    │        │              │
    │  ┌──────────┐              │    │        ▼              │
    │  │ API      │ (bypassed   │    │  Tesla Fleet API      │
    │  │ :8100    │  for Tesla) │    │  GCP Secret Manager   │
    │  └──────────┘              │    │                       │
    └─────────────────────────────┘    └───────────────────────┘

    Data flow:

    WUI              Gateway (proxy)        Dev API             Tesla
     │                    │                    │                   │
     │ POST /api/v1/      │                    │                   │
     │ tesla/oauth/       │                    │                   │
     │ initiate           │                    │                   │
     │───────────────────>│                    │                   │
     │                    │ POST /api/v1/      │                   │
     │                    │ tesla/oauth/       │                   │
     │                    │ initiate           │                   │
     │                    │ + X-Gateway-Key    │                   │
     │                    │ + frontendOrigin   │                   │
     │                    │───────────────────>│                   │
     │                    │ {authUrl: "https:  │                   │
     │                    │  //auth.tesla..."}  │                   │
     │                    │<───────────────────│                   │
     │ {authUrl}          │                    │                   │
     │<───────────────────│                    │                   │
     │                    │                    │                   │
     │ Browser redirect ──│────────────────────│──────────────────>│
     │ to auth.tesla.com  │                    │   User authorizes │
     │                    │                    │<──────────────────│
     │                    │                    │ callback with code│
     │                    │                    │                   │
     │                    │                    │ Exchange code ───>│
     │                    │                    │ <── tokens ───────│
     │                    │                    │ GET vehicles ────>│
     │                    │                    │ <── vehicle data ─│
     │                    │                    │                   │
     │ Redirect to        │                    │                   │
     │ localhost:3333     │                    │                   │
     │ ?oauth=success     │                    │                   │
     │<────────────────────────────────────────│                   │
     │                    │                    │                   │
     │ GET /api/v1/tesla/ │                    │                   │
     │ report/{session_id}│                    │                   │
     │───────────────────>│                    │                   │
     │                    │ GET /api/v1/tesla/ │                   │
     │                    │ report/{session_id}│                   │
     │                    │ + X-Gateway-Key    │                   │
     │                    │───────────────────>│                   │
     │                    │ {vin, odometer,..} │                   │
     │                    │<───────────────────│                   │
     │ {report data}      │                    │                   │
     │<───────────────────│                    │                   │

5. The OAuth Redirect Challenge & frontend_origin Solution

The Problem

Tesla's OAuth flow requires a registered TESLA_REDIRECT_URI. Ours is:

https://dev.api.carpulsetracker.com/api/v1/tesla/oauth/callback

This cannot be changed to http://localhost:* — Tesla only accepts pre-registered URIs. After the dev API's callback handler processes the OAuth exchange (code → token → vehicle data → report), it must redirect the browser back to the frontend. By default, it redirects to https://dev.carpulsetracker.com. For local development, we need it to redirect to http://localhost:3333.

The Solution

Pass frontend_origin through the OAuth state:

Step 1: Gateway injects frontend_origin
─────────────────────────────────────────────────
  Original request from WUI:
    POST /api/v1/tesla/oauth/initiate
    Body: {paymentId: "abc", plan: "premium"}

  Gateway adds before forwarding to dev API:
    Body: {paymentId: "abc", plan: "premium",
           frontendOrigin: "http://localhost:3333"}
    Header: X-Gateway-Key: <secret>

Step 2: Dev API stores frontend_origin in OAuth state
─────────────────────────────────────────────────
  state_data = {
    "paymentId": "abc",
    "plan": "premium",
    "frontend_origin": "http://localhost:3333"
  }
  # Stored server-side, keyed by random state token

Step 3: Tesla callback uses frontend_origin for redirect
─────────────────────────────────────────────────
  Tesla redirects to:
    dev.api.carpulsetracker.com/api/v1/tesla/oauth/callback
      ?code=REAL_AUTH_CODE&state=random_token

  Dev API callback handler:
    1. Validates state token
    2. Exchanges code for access token
    3. Fetches vehicle data from Fleet API
    4. Stores report
    5. Reads frontend_origin from state_data
    6. Redirects browser to:
       http://localhost:3333?oauth=success&session_id=xxx
       (instead of https://dev.carpulsetracker.com?...)

Why This Works

6. Security Model

6.1 Gateway API Key

┌──────────────┐    X-Gateway-Key: <secret>    ┌──────────────┐
│              │──────────────────────────────>│              │
│  Gateway     │                               │  Dev API     │
│  (local)     │   Only accepts frontendOrigin │  (Cloud Run) │
│              │   when key is valid           │              │
└──────────────┘                               └──────────────┘

6.2 CORS Validation

Even with a valid gateway key, frontendOrigin is only accepted if it appears in the dev API's CORS_ORIGINS allowlist:

CORS_ORIGINS: "https://dev.carpulsetracker.com,http://localhost:3333"

This double validation (gateway key + CORS allowlist) prevents both unauthorized gateway usage and arbitrary redirect targets.

6.3 What the Gateway Does NOT Hold

The gateway container contains no Tesla credentials:

┌─────────────────────────────────────────────────┐
│  Gateway container secrets:                     │
│                                                 │
│    TESLA_GW_API_KEY ........... gateway key only │
│    TESLA_CLIENT_ID ............ ✗ NOT present   │
│    TESLA_CLIENT_SECRET ........ ✗ NOT present   │
│    TESLA_PRIVATE_KEY .......... ✗ NOT present   │
│                                                 │
│  All sensitive operations happen on the dev API │
│  (token exchange, Fleet API calls with real     │
│  tokens, vehicle data retrieval).               │
└─────────────────────────────────────────────────┘

6.4 Rate Limiting

Existing slowapi rate limiting on the dev API's Tesla endpoints applies. Gateway requests count against these limits. No additional rate limiting is needed.

7. Environment Configuration

Switching Between Modes

Edit .env.lcl (or set in shell before docker compose up):

# ── Mock mode (default, for CI and fast local dev) ──────────
TESLA_GW_MODE=mock
# No other config needed. Gateway serves fake data.

# ── Proxy mode (for real Tesla testing) ─────────────────────
TESLA_GW_MODE=proxy
TESLA_GW_DEV_API_URL=https://dev.api.carpulsetracker.com
TESLA_GW_API_KEY=<value from GCP Secret Manager>
TESLA_GW_FRONTEND_ORIGIN=http://localhost:3333

Full Environment Variables

Variable Default Description
TESLA_GW_MODE mock mock or proxy
TESLA_GW_PORT_HOST 9100 Host port mapping
TESLA_GW_DEV_API_URL https://dev.api.carpulsetracker.com Target API (proxy mode only)
TESLA_GW_API_KEY (empty) Gateway auth key (proxy mode only)
TESLA_GW_FRONTEND_ORIGIN http://localhost:3333 Injected into frontendOrigin
TESLA_AUTH_URL http://con-bnc-cpt-tesla-gw:9100/... API's Tesla auth URL (mock mode)
TESLA_TOKEN_URL http://con-bnc-cpt-tesla-gw:9100/... API's Tesla token URL (mock mode)
TESLA_FLEET_API_BASE http://con-bnc-cpt-tesla-gw:9100 API's Fleet API base (mock mode)

WUI Vite Proxy Configuration

In proxy mode, the Vite dev server must split Tesla routes from other API routes:

vite.config.ts proxy rules:

  /api/v1/tesla/*  →  VITE_TESLA_GW_URL (localhost:9100, the gateway)
  /api/*           →  VITE_API_URL       (localhost:8100, the local API)

This split is necessary because in proxy mode, Tesla requests bypass the local API entirely and go through the gateway to the dev API on GCP.

In mock mode, this split is not needed — all requests go to the local API, which talks to the gateway internally via the TESLA_*_URL environment variables.

8. Docker Compose Integration

New Service Definition

# docker-compose-app.yaml
tesla-gw:
  image: img-tesla-gw
  container_name: con-bnc-cpt-tesla-gw
  pull_policy: never
  build:
    context: ./tesla-gw
    dockerfile: Dockerfile
  ports:
    - "${TESLA_GW_PORT_HOST:-9100}:9100"
  environment:
    TESLA_GW_MODE: "${TESLA_GW_MODE:-mock}"
    TESLA_GW_DEV_API_URL: "${TESLA_GW_DEV_API_URL:-https://dev.api.carpulsetracker.com}"
    TESLA_GW_API_KEY: "${TESLA_GW_API_KEY:-}"
    TESLA_GW_FRONTEND_ORIGIN: "${TESLA_GW_FRONTEND_ORIGIN:-http://localhost:3333}"
  networks:
    - all_lcl_docker

Where It Fits in the Stack

┌──────────────────────────────────────────────────────────────────┐
│                    docker-compose stack                           │
│                                                                  │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────────┐ │
│  │  con-bnc-cpt-  │  │  con-bnc-cpt-  │  │  con-bnc-cpt-     │ │
│  │  wui           │  │  api           │  │  tesla-gw         │ │
│  │  :3333         │  │  :8100         │  │  :9100            │ │
│  │  (Vue 3)       │  │  (FastAPI)     │  │  (FastAPI)        │ │
│  └───────┬────────┘  └───────┬────────┘  └─────┬─────────────┘ │
│          │                   │                  │               │
│          │                   │   mock mode:     │               │
│          │    all /api/*     │   API ──────────>│               │
│          │──────────────────>│   (Tesla URLs)   │               │
│          │                   │                  │               │
│          │   proxy mode:     │                  │               │
│          │   /api/v1/tesla/* │                  │               │
│          │──────────────────────────────────────>│               │
│          │                   │                  │  ────> GCP    │
│          │   /api/* (rest)   │                  │               │
│          │──────────────────>│                  │               │
│                                                                  │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────────┐ │
│  │  con-bnc-cpt-  │  │  con-bnc-cpt-  │  │  con-bnc-cpt-     │ │
│  │  db            │  │  redis         │  │  mailhog          │ │
│  │  :5432         │  │  :6379         │  │  :8025            │ │
│  │  (PostgreSQL)  │  │  (Redis)       │  │  (Email)          │ │
│  └────────────────┘  └────────────────┘  └────────────────────┘ │
│                                                                  │
│  Network: all_lcl_docker                                         │
└──────────────────────────────────────────────────────────────────┘

Health Check

The gateway exposes a /health endpoint:

GET http://localhost:9100/health
→ {"status": "healthy", "mode": "mock"}   # or "proxy"

Shell Actions to Update

Shell Action Change
check-dev-readiness.func.sh Replace con-bnc-cpt-tesla-mockcon-bnc-cpt-tesla-gw
generate-docker-env-ports.func.sh Update Tesla endpoint resolution to use tesla_gw

CNF Configuration

# lcl.env.yaml
tesla_gw:
  port: 9100
  host: "localhost"
  protocol: "http"
  url: "http://localhost:9100"
  health_path: "/health"
  container_name: "con-bnc-cpt-tesla-gw"

Design Decisions

Decision Chosen Alternative Rationale
Separate container vs built-in proxy Separate container Add proxy logic to local API Keeps API codebase clean; mock + proxy in one place
Frontend origin via OAuth state Server-side state Query param on callback URL State is secure (server-side), no URL tampering possible
Gateway API key Shared secret in header mTLS, IP allowlist Simple, sufficient for dev API; mTLS is overkill for internal tool
Mock mode as default Mock (no deps) Proxy (real data) CI needs no external deps; developer opts into proxy explicitly
Vite proxy split in proxy mode Separate /api/v1/tesla route Single API URL for all Necessary because proxy mode bypasses local API for Tesla routes