Files
riotpiao.com/app/api/kubectl/route.ts
T
2026-09-03 23:19:56 -07:00

74 lines
1.8 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { exec } from 'child_process'
import { promisify } from 'util'
const execAsync = promisify(exec)
// Blocked resources (no read access from terminal)
const BLOCKED_RESOURCES = [
'secret',
'secrets',
'configmap',
'configmaps',
'certificate',
'certificates',
'key',
'keys',
'token',
'tokens',
'serviceaccount',
'serviceaccounts',
]
export async function POST(request: NextRequest) {
try {
const { command } = await request.json()
if (!command || typeof command !== 'string') {
return NextResponse.json({ error: 'Invalid command' }, { status: 400 })
}
// Must start with kubectl (enforced by terminal, but double-check)
if (!command.trim().startsWith('kubectl ')) {
return NextResponse.json({ error: 'Only kubectl commands allowed' }, { status: 403 })
}
// Check for blocked resources
const cmdLower = command.toLowerCase()
for (const blocked of BLOCKED_RESOURCES) {
if (cmdLower.includes(blocked)) {
return NextResponse.json(
{ error: `Access denied: cannot read '${blocked}'` },
{ status: 403 }
)
}
}
// Execute kubectl (uses homelab-agent read-only context)
const { stdout, stderr } = await execAsync(command, {
timeout: 10000, // 10s timeout
maxBuffer: 1024 * 1024, // 1MB max output
})
return NextResponse.json({
output: stdout || stderr,
status: 'success',
})
} catch (error) {
const err = error as Error & { code?: number }
// Timeout or execution error
if (err.message.includes('ETIMEDOUT')) {
return NextResponse.json(
{ error: 'Command timeout (10s limit)' },
{ status: 504 }
)
}
return NextResponse.json(
{ error: err.message || 'Command failed' },
{ status: 500 }
)
}
}