CORS Configuration Guide#
Overview#
The backend requires CORS (Cross-Origin Resource Sharing) to be configured with the domains that will access it. This is critical for:
- Bootstrap/admin creation flow
- Frontend-to-backend API calls
- Security (prevents unauthorized domains from accessing the API)
Configuration#
Via Helm Values#
Set backend.corsOrigins in your values.yaml or via --set:
backend:
corsOrigins: "https://kleidia.example.com"Or via command line:
helm install kleidia-services ./kleidia-services \
--set backend.corsOrigins="https://kleidia.example.com"External Load Balancer Scenarios#
Scenario 1: Known DNS Before Deployment#
If you know your DNS/domain before deployment:
helm install kleidia-services ./kleidia-services \
--set backend.corsOrigins="https://my-kleidia.example.com"Scenario 2: External LB with Unknown DNS#
If using an external load balancer where DNS isn’t known until after deployment:
Initial deployment with localhost (bootstrap will fail):
helm install kleidia-services ./kleidia-servicesGet your Load Balancer URL/IP (from cloud provider/VIP)
Upgrade with actual domain:
helm upgrade kleidia-services ./kleidia-services \ --set backend.corsOrigins="https://your-actual-domain.com"Restart backend pods to pick up new configuration:
kubectl rollout restart deployment/backend -n kleidia
Scenario 3: Multiple Environments#
For development + production:
backend:
corsOrigins: "https://kleidia.prod.example.com,https://kleidia.dev.example.com,http://localhost:3000"VIP/HAProxy Scenario#
For deployments using VIP with HAProxy:
helm upgrade kleidia-services ./kleidia-services \
--set backend.corsOrigins="https://kleidia.example.com" \
-n kleidiaSecurity Considerations#
⚠️ DO NOT Use Wildcards in Production#
# ❌ NEVER DO THIS IN PRODUCTION
backend:
corsOrigins: "*"The backend will reject wildcard (*) origins in production mode.
✅ Best Practices#
- Explicit domains only: List each allowed domain explicitly
- HTTPS in production: Use
https://nothttp://for production domains - Include all variations: If accessible via multiple domains/IPs, include all
- Separate by commas: No spaces in the comma-separated list
Examples#
Good:
backend:
corsOrigins: "https://kleidia.example.com,https://kleidia-backup.example.com"Bad:
backend:
corsOrigins: "*" # ❌ Security risk
corsOrigins: "https://kleidia.example.com, https://other.com" # ❌ Spaces cause issuesTroubleshooting#
Error: 403 Forbidden on Bootstrap#
Symptom: Can’t create admin, getting 403 errors
Cause: Frontend domain not in corsOrigins
Solution:
# Check current CORS config
kubectl exec -n kleidia deployment/backend -- printenv CORS_ORIGINS
# Update with correct domain
helm upgrade kleidia-services ./kleidia-services \
--set backend.corsOrigins="https://your-actual-domain.com" \
-n kleidia
# Restart backend
kubectl rollout restart deployment/backend -n kleidiaError: CORS blocks localhost#
Symptom: Local development failing
Solution: Add localhost to the list:
backend:
corsOrigins: "https://kleidia.example.com,http://localhost:3000,http://localhost:5173"Technical Details#
Backend Behavior#
The backend (main.go) validates CORS origins:
- Reads from
CORS_ORIGINSenvironment variable - Splits on comma
- Applies to Gin CORS middleware
- Bootstrap endpoints (
/claim,/complete) explicitly check origin headers - Rejects requests from unlisted origins with 403 Forbidden
Bootstrap Flow#
The admin creation flow requires correct CORS:
- Frontend loads at
https://kleidia.example.com - User navigates to
/adminSetup - Frontend calls
POST /api/bootstrap/claimwithOrigin: https://kleidia.example.com - Backend checks if origin is in
CORS_ORIGINS - If not found → 403 Forbidden
- If found → Returns claim token
- User submits admin credentials
- Frontend calls
POST /api/bootstrap/complete(also checks origin)
Deployment Examples#
Air-Gapped Deployment#
backend:
corsOrigins: "https://kleidia.internal.company.com"Cloud Deployment with External LB#
# After LB is created and DNS is set up
helm upgrade kleidia-services ./kleidia-services \
--set backend.corsOrigins="https://$(kubectl get vip kleidia-vip -n kleidia -o jsonpath='{.spec.fqdn}')" \
-n kleidiaDevelopment + Staging + Production#
backend:
corsOrigins: "https://kleidia.prod.example.com,https://kleidia-staging.example.com,http://localhost:3000"References#
- Backend CORS implementation:
backend-go/main.go - Bootstrap handlers:
backend-go/internal/handlers/handlers.go(BootstrapClaim, BootstrapComplete) - Helm values:
helm/kleidia-services/values.yaml