Files
motovaultpro/backend/src/features/stations/docs/deployment/SECRETS-VERIFICATION.md
2025-11-04 18:46:46 -06:00

12 KiB

Gas Stations Feature - Secrets Verification Guide

Overview

Complete guide for verifying Google Maps API key secrets are correctly configured in both backend and frontend containers. This document covers verification commands, troubleshooting steps, and security validation.

Secrets Architecture

Backend Secret Flow

Host filesystem: ./secrets/app/google-maps-api-key.txt
    ↓ (Docker volume mount)
Container: /run/secrets/google-maps-api-key
    ↓ (Config loader reads at startup)
Backend service: process.env or config object
    ↓ (Used in Google Maps client)
Google Places API calls

Frontend Secret Flow

Host filesystem: ./secrets/app/google-maps-api-key.txt
    ↓ (Docker volume mount)
Container: /run/secrets/google-maps-api-key
    ↓ (Entrypoint script reads at startup)
Generated file: /usr/share/nginx/html/config.js
    ↓ (Loaded in index.html)
Browser: window.CONFIG.googleMapsApiKey
    ↓ (Used in maps-loader.ts)
Google Maps JavaScript API

Verification Commands

1. Host Filesystem Verification

Check secret file exists:

ls -la ./secrets/app/google-maps-api-key.txt

Expected Output:

-rw-r--r-- 1 user user 39 Jan 15 10:00 ./secrets/app/google-maps-api-key.txt

View secret content (only in development):

cat ./secrets/app/google-maps-api-key.txt

Expected: API key starting with AIzaSy... (39 characters)

Check file permissions:

stat -c "%a %n" ./secrets/app/google-maps-api-key.txt

Expected: 644 (readable by owner and group)

2. Backend Container Verification

Check secret mounted in container:

docker compose exec mvp-backend cat /run/secrets/google-maps-api-key

Expected: Same API key as host file

Check secret file permissions in container:

docker compose exec mvp-backend ls -la /run/secrets/google-maps-api-key

Expected: Read-only file (:ro mount flag)

Check backend can access secret:

docker compose exec mvp-backend node -e "
const fs = require('fs');
try {
  const key = fs.readFileSync('/run/secrets/google-maps-api-key', 'utf8').trim();
  console.log('Secret loaded, length:', key.length);
  console.log('Starts with AIzaSy:', key.startsWith('AIzaSy'));
} catch (error) {
  console.error('Failed to read secret:', error.message);
}
"

Expected Output:

Secret loaded, length: 39
Starts with AIzaSy: true

Check backend logs for secret loading:

docker compose logs mvp-backend | grep -i "google.*api.*key"

Expected (log message should NOT show actual key, only status):

[Config] Google Maps API key loaded successfully

3. Frontend Container Verification

Check secret mounted in container:

docker compose exec mvp-frontend cat /run/secrets/google-maps-api-key

Expected: Same API key as host file

Check config.js generated:

docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js

Expected Output:

window.CONFIG = {
  googleMapsApiKey: 'AIzaSyYourActualKeyHere'
};

Check entrypoint script ran:

docker compose logs mvp-frontend | grep -i config

Expected Output:

[Config] Loaded Google Maps API key from /run/secrets/google-maps-api-key
[Config] Generated /usr/share/nginx/html/config.js

Check config.js is served:

curl -s http://localhost:3000/config.js

Expected: JavaScript file with window.CONFIG = {...}

4. Browser Verification

Check config loaded in browser console:

  1. Open https://motovaultpro.com in browser
  2. Open Developer Tools (F12)
  3. Go to Console tab
  4. Run:
console.log(window.CONFIG);

Expected Output:

{
  googleMapsApiKey: "AIzaSyYourActualKeyHere"
}

Check Google Maps script loads:

console.log(typeof google !== 'undefined' ? 'Google Maps loaded' : 'Not loaded yet');

Check for config errors:

// Should not show any errors related to config
console.log(document.querySelectorAll('script[src*="config.js"]').length);
// Should return: 1

5. API Integration Verification

Test backend can call Google Maps API:

docker compose exec mvp-backend node -e "
const https = require('https');
const fs = require('fs');

const apiKey = fs.readFileSync('/run/secrets/google-maps-api-key', 'utf8').trim();
const url = \`https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=37.7749,-122.4194&radius=5000&type=gas_station&key=\${apiKey}\`;

https.get(url, (res) => {
  let data = '';
  res.on('data', (chunk) => data += chunk);
  res.on('end', () => {
    const json = JSON.parse(data);
    console.log('API Status:', json.status);
    console.log('Results:', json.results?.length || 0, 'stations');
  });
}).on('error', (err) => {
  console.error('API Error:', err.message);
});
"

Expected Output:

API Status: OK
Results: 20 stations

Test full API workflow with JWT:

# Get JWT token (from Auth0 or test token)
export TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

# Search for stations
curl -X POST http://localhost:3001/api/stations/search \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "latitude": 37.7749,
    "longitude": -122.4194,
    "radius": 5000
  }' | jq '.stations | length'

Expected: Number greater than 0 (indicating stations found)

Security Validation

1. Secret Not in Environment Variables

Check backend environment:

docker compose exec mvp-backend env | grep -i google

Expected: No GOOGLE_MAPS_API_KEY in environment (should use file mount)

Check frontend environment:

docker compose exec mvp-frontend env | grep -i google

Expected: No GOOGLE_MAPS_API_KEY in environment

2. Secret Not in Docker Images

Check backend image:

docker history motovaultpro-backend | grep -i google

Expected: No API key in image layers

Check frontend image:

docker history motovaultpro-frontend | grep -i google

Expected: No API key in image layers

3. Secret Not in Logs

Check all logs for API key exposure:

docker compose logs | grep -i "AIzaSy"

Expected: No matches (API key should never be logged)

Check for "secret" or "key" logging:

docker compose logs | grep -i "api.*key" | grep -v "loaded successfully"

Expected: Only status messages, no actual key values

4. File Permissions Secure

Check host file not world-readable:

ls -l ./secrets/app/google-maps-api-key.txt | awk '{print $1}'

Expected: -rw-r--r-- or stricter (not -rw-rw-rw-)

Check container mount is read-only:

docker compose config | grep -A5 "google-maps-api-key"

Expected: Contains :ro flag

5. Secret Rotation Test

Update secret and verify change propagates:

# Backup current secret
cp ./secrets/app/google-maps-api-key.txt ./secrets/app/google-maps-api-key.txt.bak

# Update with new key (test key for rotation testing)
echo "AIzaSyTestKeyForRotation" > ./secrets/app/google-maps-api-key.txt

# Restart containers
docker compose restart mvp-backend mvp-frontend

# Wait for startup
sleep 5

# Verify new key loaded in backend
docker compose exec mvp-backend cat /run/secrets/google-maps-api-key

# Verify new key in frontend config.js
docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js

# Restore original key
mv ./secrets/app/google-maps-api-key.txt.bak ./secrets/app/google-maps-api-key.txt

# Restart again
docker compose restart mvp-backend mvp-frontend

Troubleshooting

Backend Can't Read Secret

Symptom:

docker compose logs mvp-backend | grep -i "google"
# Shows: Failed to load Google Maps API key

Solutions:

  1. Check file exists on host:

    ls -la ./secrets/app/google-maps-api-key.txt
    
  2. Check docker-compose.yml mount:

    grep -A5 "mvp-backend:" docker-compose.yml | grep google-maps
    

    Should show:

    - ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro
    
  3. Check file in container:

    docker compose exec mvp-backend ls -la /run/secrets/
    
  4. Restart backend:

    docker compose restart mvp-backend
    

Frontend config.js Not Generated

Symptom:

docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
# Shows: No such file or directory

Solutions:

  1. Check entrypoint script exists:

    docker compose exec mvp-frontend ls -la /app/load-config.sh
    
  2. Check entrypoint runs:

    docker compose logs mvp-frontend | head -20
    

    Should show config generation messages.

  3. Run entrypoint manually:

    docker compose exec mvp-frontend sh /app/load-config.sh
    
  4. Restart frontend:

    docker compose restart mvp-frontend
    

Browser Shows "API key undefined"

Symptom: Browser console shows window.CONFIG.googleMapsApiKey is undefined

Solutions:

  1. Check config.js loads: Open browser DevTools > Network tab > Refresh page > Look for config.js

  2. Check config.js content:

    curl http://localhost:3000/config.js
    
  3. Check index.html loads config.js: View page source, look for:

    <script src="/config.js"></script>
    
  4. Clear browser cache: Hard refresh: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)

Google API Returns "Invalid API Key"

Symptom: API calls return 400 error with "The provided API key is invalid"

Solutions:

  1. Verify key format:

    cat ./secrets/app/google-maps-api-key.txt | wc -c
    

    Should be 39 characters (including newline) or 40.

  2. Check for extra whitespace:

    cat ./secrets/app/google-maps-api-key.txt | od -c
    

    Should only show key and newline, no spaces/tabs.

  3. Verify key in Google Cloud Console:

    • Go to APIs & Services > Credentials
    • Confirm key exists and is not deleted
  4. Check API restrictions:

    • Verify IP/domain restrictions don't block your server
    • Verify API restrictions include Places API

Secret File Permissions Wrong

Symptom: Container can't read secret file

Solutions:

# Fix file permissions
chmod 644 ./secrets/app/google-maps-api-key.txt

# Verify ownership
ls -l ./secrets/app/google-maps-api-key.txt

# Restart containers
docker compose restart mvp-backend mvp-frontend

Automated Verification Script

Create a verification script for convenience:

#!/bin/bash
# verify-secrets.sh

echo "=== Gas Stations Secrets Verification ==="
echo ""

echo "1. Host file exists:"
[ -f ./secrets/app/google-maps-api-key.txt ] && echo "✓ PASS" || echo "✗ FAIL"

echo ""
echo "2. Backend can read secret:"
docker compose exec -T mvp-backend cat /run/secrets/google-maps-api-key >/dev/null 2>&1 && echo "✓ PASS" || echo "✗ FAIL"

echo ""
echo "3. Frontend can read secret:"
docker compose exec -T mvp-frontend cat /run/secrets/google-maps-api-key >/dev/null 2>&1 && echo "✓ PASS" || echo "✗ FAIL"

echo ""
echo "4. Frontend config.js generated:"
docker compose exec -T mvp-frontend cat /usr/share/nginx/html/config.js >/dev/null 2>&1 && echo "✓ PASS" || echo "✗ FAIL"

echo ""
echo "5. API key format valid:"
KEY=$(cat ./secrets/app/google-maps-api-key.txt)
[[ $KEY =~ ^AIzaSy ]] && echo "✓ PASS" || echo "✗ FAIL"

echo ""
echo "=== Verification Complete ==="

Usage:

chmod +x verify-secrets.sh
./verify-secrets.sh

References

  • Architecture: /backend/src/features/stations/docs/ARCHITECTURE.md
  • Google Maps Setup: /backend/src/features/stations/docs/GOOGLE-MAPS-SETUP.md
  • Deployment Checklist: /backend/src/features/stations/docs/deployment/DEPLOYMENT-CHECKLIST.md
  • Runtime Config: /frontend/docs/RUNTIME-CONFIG.md