2026-03-11 | Resolving Swagger 403, ArgoCD Dashboard, and CORS Issues


1. Swagger 403 → OAuth Login Redirect#

Problem#

  • Accessing swagger.dev.goormgb.space shows a 403 Forbidden page
  • No redirect to the login page

Root Cause#

  • Browser has a stale cookie (expired _oauth2_proxy cookie)
  • OAuth2 Proxy validates the cookie, finds it invalid → returns 403
  • Normal flow: no cookie → 302 redirect → login
[Normal flow - no cookie]
User → OAuth2 Proxy → no cookie → 302 → Google login

[Problem - expired cookie]
User → OAuth2 Proxy → cookie present (expired) → 403 Forbidden
                                                ↑ stops here!

Fix: EnvoyFilter to convert 403→302 redirect#

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: swagger-403-redirect
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: GATEWAY
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
              subFilter:
                name: envoy.filters.http.router
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.lua
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
            inlineCode: |
              function envoy_on_response(response_handle)
                local host = response_handle:headers():get(":authority") or ""
                local status = response_handle:headers():get(":status")

                -- only handle 403 responses on the swagger domain
                if string.find(host, "swagger") and status == "403" then
                  local path = response_handle:headers():get("x-original-uri") or "/"
                  local redirect_url = "/oauth2/start?rd=" .. path

                  -- convert to 302 redirect
                  response_handle:headers():replace(":status", "302")
                  response_handle:headers():add("location", redirect_url)
                  response_handle:headers():add("cache-control", "no-cache, no-store")
                end
              end

Key Points#

  • envoy_on_response: intercepts at the response phase
  • swagger domain only: no impact on other services
  • 403 → 302 conversion + /oauth2/start redirect
  • OAuth2 Proxy restarts the login process

2. CORS Errors Causing Frontend Blank Page#

Problem#

  • CORS errors when the frontend makes API calls
  • Only errors visible in the browser console; the page goes blank

Root Cause#

  • The backend does not include CORS headers in 400/500 error responses
[Successful 200 response]
Access-Control-Allow-Origin: https://dev.goormgb.space  ✓
→ Frontend can read the response body

[Error 500 response]
Access-Control-Allow-Origin: (absent)  ✗
→ Browser blocks the response → frontend cannot determine the error cause
→ Error handling fails → blank page

Why CORS headers are needed even on error responses#

  1. Browser security policy: All responses require CORS headers when origins differ
  2. Error handling: Without CORS headers, response.json() itself fails
  3. UX improvement: Error messages can be surfaced to the user

Fix (forwarded to backend team)#

// Spring Boot - include CORS headers in error responses too
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("https://dev.goormgb.space")
            .allowedMethods("*")
            .allowCredentials(true);
    }
}

// Or add headers to all exception responses via @ControllerAdvice

3. /auth/token/refresh 404 Error#

Problem#

  • Frontend calls /auth/token/refresh and receives 404

Root Cause: Path Mismatch#

SidePath
Frontend call/auth/token/refresh
API Gateway routingPath=/auth/** → Auth-Guard
Auth-Guard actual path/token/refresh (no class-level @RequestMapping)
Frontend: /auth/token/refresh
               ↓
API Gateway: matches /auth/** → forwards to Auth-Guard
               ↓
Auth-Guard: receives /auth/token/refresh
               ↓
404! (only /token/refresh actually exists)

Fix (forwarded to backend team)#

Option 1: Add StripPrefix filter (recommended)

routes:
  - id: auth-guard
    uri: ${AUTH_GUARD_URL}
    predicates:
      - Path=/auth/**
    filters:
      - StripPrefix=1  # /auth/token/refresh → /token/refresh

Option 2: Add @RequestMapping to the Controller

@RestController
@RequestMapping("/auth")  // add this
public class AuthController {

4. ArgoCD Dashboard Improvements#

Changes#

  1. Dashboard split

    • argocd-app-deployments.json - service apps (dev-*)
    • argocd-infra-deployments.json - infra (Istio, Monitoring, etc.)
  2. Unhealthy query fix

    • Before: health_status=~"Degraded|Progressing|Unknown" (counted Progressing as Unhealthy)
    • After: health_status="Degraded" + OR on() vector(0) (shows 0 when no results)
  3. Log section added (same in both dashboards)

    • Reconciliation count (Loki timeseries)
    • Error logs (Loki logs)
    • Webhook received logs
    • Sync completed logs
  4. Default time range changed

    • now-24hnow-30m
    • Fixes Loki query timeout issues
  5. Header condensed to one line (removes scrollbar)


File Changes#

Created#

  • dev/root/infra/security/swagger-403-redirect.yaml

Modified#

  • common-charts/.../argocd-app-deployments.json
  • common-charts/.../argocd-infra-deployments.json
  • dev/root/kustomization.yaml (registers swagger-403-redirect)

5. Helm Chart Dependency Management#

Problem#

Error: An error occurred while checking for chart dependencies.
You may need to run `helm dependency build` to fetch missing dependencies:
found in Chart.yaml, but missing in charts/ directory: base

ArgoCD cannot find dependencies when running helm template.

Root Cause#

  • Dependencies are defined in the Helm chart’s Chart.yaml
  • Running helm dependency build downloads them to the charts/ directory
  • If charts/ is in .gitignore or not committed, ArgoCD cannot use them

Fix: Periodic dependency updates#

# 1. Add Helm repos (one-time)
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo add calico https://docs.tigera.io/calico/charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

# 2. Build dependencies
helm dependency build common-charts/infra/istio/base
helm dependency build common-charts/infra/calico
helm dependency build common-charts/infra/monitoring/loki

# 3. Commit the charts/ directory
git add common-charts/**/charts/
git commit -m "chore(helm): update chart dependencies"
git push

Why this needs to be done periodically#

  1. Version upgrades: when upstream chart versions change, Chart.yaml must be updated and rebuilt
  2. Chart.lock mismatch: rebuild when lock files differ between local and remote
  3. Cache issues: when the ArgoCD repo-server cache is stale

Automation (TODO)#

  • Run helm dependency build automatically in CI/CD
  • Or use helm.skipCrds: true + pre-sync hook in the ArgoCD Application

6. EnvoyFilter Dynamic Metadata Issue#

Problem#

  • Accessing the :authority header in envoy_on_response returns nil

Root Cause#

  • :authority is a request header and cannot be accessed during the response phase
  • To pass request information into the response phase in an Envoy Lua filter, dynamic metadata must be used

Fix#

-- Request phase: save to metadata
function envoy_on_request(request_handle)
  local host = request_handle:headers():get(":authority") or ""
  local path = request_handle:headers():get(":path") or "/"

  if string.find(host, "swagger") then
    request_handle:streamInfo():dynamicMetadata():set("swagger_filter", "is_swagger", "true")
    request_handle:streamInfo():dynamicMetadata():set("swagger_filter", "original_path", path)
  end
end

-- Response phase: read from metadata
function envoy_on_response(response_handle)
  local metadata = response_handle:streamInfo():dynamicMetadata():get("swagger_filter")

  if metadata and metadata["is_swagger"] == "true" then
    -- processing logic
  end
end

7. Istio IngressGateway Routing Sync Issue#

Problem#

  • Routing changes to VirtualService/EnvoyFilter are not reflected immediately
  • 404 errors occur even when istioctl proxy-status shows SYNCED

Root Cause#

  • Config push delay between Istiod and IngressGateway
  • Potential conflicts when multiple VirtualServices reference the same host

Fix#

# Restart IngressGateway (most reliable)
kubectl rollout restart deployment istio-ingressgateway -n istio-system

# Or restart Istiod (re-sends config to all proxies)
kubectl rollout restart deployment istiod -n istio-system

Prevention#

  • Clean up unnecessary VirtualServices (e.g. faro-receiver)
  • Remove VirtualServices referencing deleted services

Commits#

fix(grafana): standardize ArgoCD dashboard structure and fix Unhealthy query
feat(istio): add swagger 403→login redirect EnvoyFilter
fix(istio): use dynamic metadata for EnvoyFilter cross-phase header access
chore(helm): add chart dependencies for istio, calico, loki