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:
- Open https://motovaultpro.com in browser
- Open Developer Tools (F12)
- Go to Console tab
- 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:
-
Check file exists on host:
ls -la ./secrets/app/google-maps-api-key.txt -
Check docker-compose.yml mount:
grep -A5 "mvp-backend:" docker-compose.yml | grep google-mapsShould show:
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro -
Check file in container:
docker compose exec mvp-backend ls -la /run/secrets/ -
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:
-
Check entrypoint script exists:
docker compose exec mvp-frontend ls -la /app/load-config.sh -
Check entrypoint runs:
docker compose logs mvp-frontend | head -20Should show config generation messages.
-
Run entrypoint manually:
docker compose exec mvp-frontend sh /app/load-config.sh -
Restart frontend:
docker compose restart mvp-frontend
Browser Shows "API key undefined"
Symptom: Browser console shows window.CONFIG.googleMapsApiKey is undefined
Solutions:
-
Check config.js loads: Open browser DevTools > Network tab > Refresh page > Look for
config.js -
Check config.js content:
curl http://localhost:3000/config.js -
Check index.html loads config.js: View page source, look for:
<script src="/config.js"></script> -
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:
-
Verify key format:
cat ./secrets/app/google-maps-api-key.txt | wc -cShould be 39 characters (including newline) or 40.
-
Check for extra whitespace:
cat ./secrets/app/google-maps-api-key.txt | od -cShould only show key and newline, no spaces/tabs.
-
Verify key in Google Cloud Console:
- Go to APIs & Services > Credentials
- Confirm key exists and is not deleted
-
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