fix: allow unauthenticated status health probe for container checks (#295)
- allow /api/status?action=health through proxy without session/API key - short-circuit health action in status route before role gating - add proxy regression tests for health probe allow + non-health deny Regression checks: - pnpm vitest run src/proxy.test.ts src/lib/__tests__/auth.test.ts - pnpm playwright test tests/auth-guards.spec.ts - smoke: /api/status?action=health=200, login=200, /api/auth/me=200
This commit is contained in:
parent
a18240381c
commit
55f2351bdc
|
|
@ -16,6 +16,13 @@ import { isHermesInstalled, scanHermesSessions } from '@/lib/hermes-sessions'
|
|||
import { registerMcAsDashboard } from '@/lib/gateway-runtime'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
// Docker/Kubernetes health probes must work without auth/cookies.
|
||||
const preAction = new URL(request.url).searchParams.get('action') || 'overview'
|
||||
if (preAction === 'health') {
|
||||
const health = await performHealthCheck()
|
||||
return NextResponse.json(health)
|
||||
}
|
||||
|
||||
const auth = requireRole(request, 'viewer')
|
||||
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
||||
|
||||
|
|
|
|||
|
|
@ -50,4 +50,62 @@ describe('proxy host matching', () => {
|
|||
const response = proxy(request)
|
||||
expect(response.status).toBe(403)
|
||||
})
|
||||
|
||||
it('allows unauthenticated health probe for /api/status?action=health', async () => {
|
||||
vi.resetModules()
|
||||
vi.doMock('node:os', () => ({
|
||||
default: { hostname: () => 'hetzner-jarv' },
|
||||
hostname: () => 'hetzner-jarv',
|
||||
}))
|
||||
|
||||
const { proxy } = await import('./proxy')
|
||||
const request = {
|
||||
headers: new Headers({ host: 'localhost:3000' }),
|
||||
nextUrl: {
|
||||
host: 'localhost:3000',
|
||||
hostname: 'localhost',
|
||||
pathname: '/api/status',
|
||||
searchParams: new URLSearchParams('action=health'),
|
||||
clone: () => ({ pathname: '/api/status' }),
|
||||
},
|
||||
method: 'GET',
|
||||
cookies: { get: () => undefined },
|
||||
} as any
|
||||
|
||||
setNodeEnv('production')
|
||||
process.env.MC_ALLOWED_HOSTS = 'localhost,127.0.0.1'
|
||||
delete process.env.MC_ALLOW_ANY_HOST
|
||||
|
||||
const response = proxy(request)
|
||||
expect(response.status).not.toBe(401)
|
||||
})
|
||||
|
||||
it('still blocks unauthenticated non-health status API calls', async () => {
|
||||
vi.resetModules()
|
||||
vi.doMock('node:os', () => ({
|
||||
default: { hostname: () => 'hetzner-jarv' },
|
||||
hostname: () => 'hetzner-jarv',
|
||||
}))
|
||||
|
||||
const { proxy } = await import('./proxy')
|
||||
const request = {
|
||||
headers: new Headers({ host: 'localhost:3000' }),
|
||||
nextUrl: {
|
||||
host: 'localhost:3000',
|
||||
hostname: 'localhost',
|
||||
pathname: '/api/status',
|
||||
searchParams: new URLSearchParams('action=overview'),
|
||||
clone: () => ({ pathname: '/api/status' }),
|
||||
},
|
||||
method: 'GET',
|
||||
cookies: { get: () => undefined },
|
||||
} as any
|
||||
|
||||
setNodeEnv('production')
|
||||
process.env.MC_ALLOWED_HOSTS = 'localhost,127.0.0.1'
|
||||
delete process.env.MC_ALLOW_ANY_HOST
|
||||
|
||||
const response = proxy(request)
|
||||
expect(response.status).toBe(401)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -181,8 +181,9 @@ export function proxy(request: NextRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
// Allow login page, auth API, and docs without session
|
||||
if (pathname === '/login' || pathname.startsWith('/api/auth/') || pathname === '/api/docs' || pathname === '/docs') {
|
||||
// Allow login page, auth API, docs, and container health probe without session
|
||||
const isPublicHealthProbe = pathname === '/api/status' && request.nextUrl.searchParams.get('action') === 'health'
|
||||
if (pathname === '/login' || pathname.startsWith('/api/auth/') || pathname === '/api/docs' || pathname === '/docs' || isPublicHealthProbe) {
|
||||
const { response, nonce } = nextResponseWithNonce(request)
|
||||
return addSecurityHeaders(response, request, nonce)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue