[grade=A] P2-7: refactor tailscaleAuthMiddleware (complexity 24→7, nesting 6→3)
Extracted 3 helpers from the monolithic tailscaleAuthMiddleware: - isTailScaleProbePath(): probe-path bypass check (was 6 || chains) - extractTailscaleIPs(): IP collection + Tailscale classification - isIPInTailnet(): async tailnet membership verification Middleware is now a flat 15-line function that reads top-to-bottom. Probe paths extracted to a Set for O(1) lookup. Behavior-preserving: same bypass rules, same error codes, same log messages. ESLint complexity 24→7, max-depth 6→3. 1539/1539 tests pass.
This commit is contained in:
@@ -113,6 +113,41 @@ module.exports = function configureMiddleware(app, {
|
|||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Tailscale authentication helpers ──
|
||||||
|
|
||||||
|
const PROBE_PATHS_TAILSCALE = new Set([
|
||||||
|
'/health', '/health/live', '/health/ready', '/healthz', '/readyz',
|
||||||
|
]);
|
||||||
|
|
||||||
|
function isTailScaleProbePath(reqPath) {
|
||||||
|
return PROBE_PATHS_TAILSCALE.has(reqPath) || reqPath.startsWith('/probe/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTailscaleIPs(req) {
|
||||||
|
const clientIP = req.ip || req.socket?.remoteAddress || '';
|
||||||
|
const forwardedFor = req.headers['x-forwarded-for'];
|
||||||
|
const realIP = req.headers['x-real-ip'];
|
||||||
|
const ipsToCheck = [clientIP, forwardedFor, realIP].filter(Boolean);
|
||||||
|
const fromTailscale = ipsToCheck.some(ip =>
|
||||||
|
isTailscaleIP(ip.toString().split(',')[0].trim()));
|
||||||
|
const clientTailscaleIP = ipsToCheck
|
||||||
|
.map(ip => ip.toString().split(',')[0].trim())
|
||||||
|
.find(ip => isTailscaleIP(ip));
|
||||||
|
return { clientIP, ipsToCheck, fromTailscale, clientTailscaleIP };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isIPInTailnet(clientTailscaleIP) {
|
||||||
|
const status = await getTailscaleStatus();
|
||||||
|
if (!status) return true; // no status = can't verify = allow
|
||||||
|
|
||||||
|
const knownIPs = new Set();
|
||||||
|
for (const ip of (status.Self?.TailscaleIPs || [])) knownIPs.add(ip);
|
||||||
|
for (const peer of Object.values(status.Peer || {})) {
|
||||||
|
for (const ip of (peer.TailscaleIPs || [])) knownIPs.add(ip);
|
||||||
|
}
|
||||||
|
return knownIPs.has(clientTailscaleIP);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Tailscale authentication middleware (optional) ──
|
// ── Tailscale authentication middleware (optional) ──
|
||||||
const tailscaleAuthMiddleware = async (req, res, next) => {
|
const tailscaleAuthMiddleware = async (req, res, next) => {
|
||||||
if (!tailscaleConfig.enabled || !tailscaleConfig.requireAuth) {
|
if (!tailscaleConfig.enabled || !tailscaleConfig.requireAuth) {
|
||||||
@@ -121,25 +156,11 @@ module.exports = function configureMiddleware(app, {
|
|||||||
|
|
||||||
// Probe endpoints bypass Tailscale auth — k8s/Docker healthchecks
|
// Probe endpoints bypass Tailscale auth — k8s/Docker healthchecks
|
||||||
// don't carry a Tailscale identity header.
|
// don't carry a Tailscale identity header.
|
||||||
if (req.path === '/health'
|
if (isTailScaleProbePath(req.path) || req.path.startsWith('/api/v1/tailscale/')) {
|
||||||
|| req.path === '/health/live'
|
|
||||||
|| req.path === '/health/ready'
|
|
||||||
|| req.path === '/healthz'
|
|
||||||
|| req.path === '/readyz'
|
|
||||||
|| req.path.startsWith('/probe/')) {
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.path.startsWith('/api/v1/tailscale/')) {
|
const { clientIP, fromTailscale, clientTailscaleIP } = extractTailscaleIPs(req);
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientIP = req.ip || req.socket?.remoteAddress || '';
|
|
||||||
const forwardedFor = req.headers['x-forwarded-for'];
|
|
||||||
const realIP = req.headers['x-real-ip'];
|
|
||||||
|
|
||||||
const ipsToCheck = [clientIP, forwardedFor, realIP].filter(Boolean);
|
|
||||||
const fromTailscale = ipsToCheck.some(ip => isTailscaleIP(ip.toString().split(',')[0].trim()));
|
|
||||||
|
|
||||||
if (!fromTailscale) {
|
if (!fromTailscale) {
|
||||||
return errorResponse(res, 403, '[DC-120] Access denied. This dashboard requires Tailscale connection.', {
|
return errorResponse(res, 403, '[DC-120] Access denied. This dashboard requires Tailscale connection.', {
|
||||||
@@ -148,28 +169,15 @@ module.exports = function configureMiddleware(app, {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tailscaleConfig.allowedTailnet) {
|
if (tailscaleConfig.allowedTailnet && clientTailscaleIP) {
|
||||||
try {
|
try {
|
||||||
const status = await getTailscaleStatus();
|
const inTailnet = await isIPInTailnet(clientTailscaleIP);
|
||||||
if (status) {
|
if (!inTailnet) {
|
||||||
const clientTailscaleIP = ipsToCheck
|
|
||||||
.map(ip => ip.toString().split(',')[0].trim())
|
|
||||||
.find(ip => isTailscaleIP(ip));
|
|
||||||
|
|
||||||
if (clientTailscaleIP) {
|
|
||||||
const knownIPs = new Set();
|
|
||||||
for (const ip of (status.Self?.TailscaleIPs || [])) knownIPs.add(ip);
|
|
||||||
for (const peer of Object.values(status.Peer || {})) {
|
|
||||||
for (const ip of (peer.TailscaleIPs || [])) knownIPs.add(ip);
|
|
||||||
}
|
|
||||||
if (!knownIPs.has(clientTailscaleIP)) {
|
|
||||||
return errorResponse(res, 403, '[DC-121] Access denied. Device not in allowed tailnet.', {
|
return errorResponse(res, 403, '[DC-121] Access denied. Device not in allowed tailnet.', {
|
||||||
requiresTailscale: true,
|
requiresTailscale: true,
|
||||||
clientIP
|
clientIP
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.warn('tailscale', 'Tailnet verification failed, allowing request', { error: e.message });
|
log.warn('tailscale', 'Tailnet verification failed, allowing request', { error: e.message });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user