mirror of
https://github.com/chaos-zhu/easynode.git
synced 2026-05-06 21:40:35 +08:00
feat: 优化clientIp获取方式
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,4 +16,4 @@ plan.md
|
||||
local-script
|
||||
版本发布.md
|
||||
.vscode
|
||||
.windsurfrules
|
||||
plan
|
||||
@@ -18,7 +18,8 @@ async function initKeyDB() {
|
||||
const { _id, ipWhiteList = [] } = keyData
|
||||
try {
|
||||
let { ipWhiteList = [] } = await keyDB.findOneAsync({})
|
||||
if (ipWhiteList.filter(ip => Boolean(ip)).length > 0) global.ALLOWED_IPS = ipWhiteList
|
||||
const filteredList = ipWhiteList.filter(ip => typeof ip === 'string' && ip.trim() !== '')
|
||||
if (filteredList.length > 0) global.ALLOWED_IPS = filteredList
|
||||
} catch (error) {
|
||||
logger.error('设置全局IP白名单失败:', error)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// 白名单IP
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { isAllowedIp } = require('../utils/tools')
|
||||
const { isAllowedIp, getClientIP } = require('../utils/tools')
|
||||
|
||||
const htmlPath = path.join(__dirname, '../template/ipForbidden.html')
|
||||
const ipForbiddenHtml = fs.readFileSync(htmlPath, 'utf8')
|
||||
|
||||
const ipFilter = async (ctx, next) => {
|
||||
if (isAllowedIp(ctx.request.ip)) return await next()
|
||||
const requestIP = getClientIP(ctx.socket.remoteAddress, ctx.get('x-forwarded-for'))
|
||||
if (isAllowedIp(requestIP)) return await next()
|
||||
ctx.status = 403
|
||||
ctx.body = ipForbiddenHtml
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// log4.js
|
||||
const { DEBUG } = require('../config').logConfig
|
||||
const { getClientIP } = require('../utils/tools')
|
||||
|
||||
// ------------------ 脱敏 ------------------
|
||||
// 可能包含敏感信息的 header key(小写比较)
|
||||
@@ -108,9 +108,10 @@ const useLog = () => {
|
||||
query,
|
||||
body,
|
||||
headers,
|
||||
ip
|
||||
} = ctx.request
|
||||
|
||||
const ip = getClientIP(ctx.socket.remoteAddress, ctx.get('x-forwarded-for'))
|
||||
|
||||
const start = Date.now()
|
||||
|
||||
// 先让后续中间件 / 路由处理
|
||||
|
||||
@@ -12,7 +12,7 @@ const wsDocker = require('./socket/docker')
|
||||
const wsOnekey = require('./socket/onekey')
|
||||
const wsServerStatus = require('./socket/server-status')
|
||||
const wsFileTransfer = require('./socket/file-transfer')
|
||||
const { throwError, isAllowedIp } = require('./utils/tools')
|
||||
const { throwError, isAllowedIp, getClientIP } = require('./utils/tools')
|
||||
const { SessionDB } = require('./utils/db-class')
|
||||
const { parseCookies } = require('./utils/verify-auth')
|
||||
const { generateSelfSignedCert } = require('./utils/ssl-cert')
|
||||
@@ -89,8 +89,7 @@ const createServer = () => {
|
||||
if (request.url.startsWith('/rdp-proxy')) {
|
||||
try {
|
||||
// 验证 IP 白名单
|
||||
const requestIP = request.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
||||
request.socket.remoteAddress
|
||||
const requestIP = getClientIP(request.socket.remoteAddress, request.headers['x-forwarded-for'])
|
||||
if (!isAllowedIp(requestIP)) {
|
||||
logger.warn(`RDP 连接被拒绝: IP ${ requestIP } 不在白名单中`)
|
||||
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n')
|
||||
@@ -180,4 +179,4 @@ function serverHandler(app, server, httpsServer) {
|
||||
|
||||
module.exports = {
|
||||
createServer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +271,41 @@ const isProd = () => {
|
||||
return EXEC_ENV === 'production'
|
||||
}
|
||||
|
||||
// 将 IPv6 映射的 IPv4 地址(::ffff:x.x.x.x)规范化为纯 IPv4,保证比较一致性
|
||||
const normalizeIP = (ip) => {
|
||||
if (typeof ip !== 'string') return ''
|
||||
ip = ip.trim().toLowerCase()
|
||||
if (ip.startsWith('::ffff:')) {
|
||||
const ipv4Part = ip.slice(7)
|
||||
if (net.isIPv4(ipv4Part)) return ipv4Part
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
const isLoopbackIP = (ip) => {
|
||||
ip = normalizeIP(ip)
|
||||
if (ip === '::1') return true
|
||||
if (!net.isIPv4(ip)) return false
|
||||
const firstOctet = Number(ip.split('.')[0])
|
||||
return firstOctet === 127
|
||||
}
|
||||
|
||||
/**
|
||||
* 从连接中提取可信客户端 IP:
|
||||
* - 优先使用 TCP 层真实地址(socketRemoteAddress),不可被客户端伪造
|
||||
* - 仅当该地址为 loopback(即经过本机可信反向代理)时,才从 x-forwarded-for 取客户端 IP
|
||||
* - nginx 使用 proxy_add_x_forwarded_for 时会将真实 IP 追加到末尾,故取最后一个值
|
||||
* 以防御客户端在头部预置伪造 IP 的攻击(XFF 首个值可伪造,末尾值由代理追加不可伪造)
|
||||
*/
|
||||
const getClientIP = (socketRemoteAddress, xForwardedFor) => {
|
||||
const socketIP = normalizeIP(socketRemoteAddress || '')
|
||||
if (!isLoopbackIP(socketIP)) return socketIP
|
||||
if (!xForwardedFor) return socketIP
|
||||
const parts = xForwardedFor.split(',')
|
||||
const last = parts[parts.length - 1].trim()
|
||||
return normalizeIP(last) || socketIP
|
||||
}
|
||||
|
||||
const isAllowedIp = (requestIP) => {
|
||||
let allowedIPs = Array.isArray(global.ALLOWED_IPS) ? global.ALLOWED_IPS : []
|
||||
if (allowedIPs.length === 0) return true
|
||||
@@ -378,6 +413,8 @@ module.exports = {
|
||||
getLocalNetIP,
|
||||
throwError,
|
||||
isIP,
|
||||
isLocalIP,
|
||||
isLoopbackIP,
|
||||
randomStr,
|
||||
getUTCDate,
|
||||
formatTimestamp,
|
||||
@@ -385,8 +422,10 @@ module.exports = {
|
||||
shellThrottle,
|
||||
fileTransferThrottle,
|
||||
isProd,
|
||||
normalizeIP,
|
||||
getClientIP,
|
||||
isAllowedIp,
|
||||
ping,
|
||||
requestWithFailover,
|
||||
timingSafeEqual
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const jwt = require('jsonwebtoken')
|
||||
const { AESDecryptAsync } = require('./encrypt')
|
||||
const { isAllowedIp } = require('../utils/tools')
|
||||
const { isAllowedIp, getClientIP } = require('../utils/tools')
|
||||
const { SHA256Encrypt } = require('../utils/encrypt')
|
||||
const { KeyDB, SessionDB } = require('./db-class')
|
||||
const keyDB = new KeyDB().getInstance()
|
||||
@@ -57,8 +57,8 @@ const verifyAuthSync = async (token, session) => {
|
||||
}
|
||||
}
|
||||
|
||||
const verifyWsAuthSync = async (socket, next) => {
|
||||
const requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
|
||||
const verifyWsAuthSync = async (socket, next) => {
|
||||
const requestIP = getClientIP(socket.conn.remoteAddress, socket.handshake.headers['x-forwarded-for'])
|
||||
// console.log('ws terminal requestIP:', requestIP)
|
||||
// IP 白名单检查
|
||||
if (!isAllowedIp(requestIP)) {
|
||||
@@ -100,4 +100,4 @@ module.exports = {
|
||||
verifyAuthSync,
|
||||
verifyWsAuthSync,
|
||||
parseCookies
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user