Skip to content

Commit

Permalink
✨ 支持IP白名单访问限制
Browse files Browse the repository at this point in the history
  • Loading branch information
chaos-zhu committed Aug 20, 2024
1 parent 997761f commit 9f04c8a
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 108 deletions.
3 changes: 3 additions & 0 deletions server/.env.template
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# 启动debug日志 0:关闭 1:开启
DEBUG=1

# 访问IP限制
allowedIPs=['127.0.0.1']
6 changes: 3 additions & 3 deletions server/app/middlewares/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ consola.info('路由白名单:', whitePath)
const useAuth = async ({ request, res }, next) => {
const { path, headers: { token } } = request
consola.info('verify path: ', path)
if(whitePath.includes(path)) return next()
if(!token) return res.fail({ msg: '未登录', status: 403 })
if (whitePath.includes(path)) return next()
if (!token) return res.fail({ msg: '未登录', status: 403 })
// 验证token
const { code, msg } = await verifyAuthSync(token, request.ip)
switch(code) {
switch (code) {
case 1:
return await next()
case -1:
Expand Down
3 changes: 2 additions & 1 deletion server/app/middlewares/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const ipFilter = require('./ipFilter') // IP过滤
const responseHandler = require('./response') // 统一返回格式, 错误捕获
const useAuth = require('./auth') // 鉴权
// const useCors = require('./cors') // 处理跨域[暂时禁止]
Expand All @@ -8,8 +9,8 @@ const useStatic = require('./static') // 静态目录
const compress = require('./compress') // br/gzip压缩
const history = require('./history') // vue-router的history模式

// 注意注册顺序
module.exports = [
ipFilter,
compress,
history,
useStatic, // staic先注册,不然会被jwt拦截
Expand Down
16 changes: 16 additions & 0 deletions server/app/middlewares/ipFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 白名单IP
const fs = require('fs')
const path = require('path')
const { isAllowedIp } = require('../utils/tools')

const htmlPath = path.join(__dirname, '../template/ipForbidden.html')
const ipForbiddenHtml = fs.readFileSync(htmlPath, 'utf8')

const ipFilter = async (ctx, next) => {
// console.log('requestIP:', ctx.request.ip)
if (isAllowedIp(ctx.request.ip)) return await next()
ctx.status = 403
ctx.body = ipForbiddenHtml
}

module.exports = ipFilter
2 changes: 1 addition & 1 deletion server/app/middlewares/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const useStatic = koaStatic(staticDir, {
maxage: 1000 * 60 * 60 * 24 * 30,
gzip: true,
setHeaders: (res, path) => {
if(path && path.endsWith('.html')) {
if (path && path.endsWith('.html')) {
res.setHeader('Cache-Control', 'max-age=0')
}
}
Expand Down
2 changes: 0 additions & 2 deletions server/app/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const { httpPort } = require('./config')
const middlewares = require('./middlewares')
const wsTerminal = require('./socket/terminal')
const wsSftp = require('./socket/sftp')
// const wsHostStatus = require('./socket/host-status')
const wsClientInfo = require('./socket/clients')
const wsOnekey = require('./socket/onekey')
const { throwError } = require('./utils/tools')
Expand All @@ -25,7 +24,6 @@ function serverHandler(app, server) {
app.proxy = true // 用于nginx反代时获取真实客户端ip
wsTerminal(server) // 终端
wsSftp(server) // sftp
// wsHostStatus(server) // 终端侧边栏host信息(单个host)
wsOnekey(server) // 一键指令
wsClientInfo(server) // 客户端信息
app.context.throwError = throwError // 常用方法挂载全局ctx上
Expand Down
10 changes: 8 additions & 2 deletions server/app/socket/clients.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { io: ClientIO } = require('socket.io-client')
const { readHostList } = require('../utils/storage')
const { clientPort } = require('../config')
const { verifyAuthSync } = require('../utils/verify-auth')
const { isAllowedIp } = require('../utils/tools')

let clientSockets = []
let clientsData = {}
Expand Down Expand Up @@ -66,9 +67,14 @@ module.exports = (httpServer) => {

serverIo.on('connection', (socket) => {
// 前者兼容nginx反代, 后者兼容nodejs自身服务
let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
let requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
if (!isAllowedIp(requestIP)) {
socket.emit('ip_forbidden', 'IP地址不在白名单中')
socket.disconnect()
return
}
socket.on('init_clients_data', async ({ token }) => {
const { code, msg } = await verifyAuthSync(token, clientIp)
const { code, msg } = await verifyAuthSync(token, requestIP)
if (code !== 1) {
socket.emit('token_verify_fail', msg || '鉴权失败')
socket.disconnect()
Expand Down
74 changes: 0 additions & 74 deletions server/app/socket/host-status.js

This file was deleted.

10 changes: 8 additions & 2 deletions server/app/socket/onekey.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { readSSHRecord, readHostList, writeOneKeyRecord } = require('../utils/sto
const { verifyAuthSync } = require('../utils/verify-auth')
const { shellThrottle } = require('../utils/tools')
const { AESDecryptSync } = require('../utils/encrypt')
const { isAllowedIp } = require('../utils/tools')

const execStatusEnum = {
connecting: '连接中',
Expand Down Expand Up @@ -90,7 +91,12 @@ module.exports = (httpServer) => {
})
serverIo.on('connection', (socket) => {
// 前者兼容nginx反代, 后者兼容nodejs自身服务
let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
let requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
if (!isAllowedIp(requestIP)) {
socket.emit('ip_forbidden', 'IP地址不在白名单中')
socket.disconnect()
return
}
consola.success('onekey-terminal websocket 已连接')
if (isExecuting) {
socket.emit('create_fail', '正在执行中, 请稍后再试')
Expand All @@ -99,7 +105,7 @@ module.exports = (httpServer) => {
}
isExecuting = true
socket.on('create', async ({ hosts, token, command, timeout }) => {
const { code } = await verifyAuthSync(token, clientIp)
const { code } = await verifyAuthSync(token, requestIP)
if (code !== 1) {
socket.emit('token_verify_fail')
socket.disconnect()
Expand Down
30 changes: 18 additions & 12 deletions server/app/socket/sftp.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { sftpCacheDir } = require('../config')
const { verifyAuthSync } = require('../utils/verify-auth')
const { AESDecryptSync } = require('../utils/encrypt')
const { readSSHRecord, readHostList } = require('../utils/storage')
const { isAllowedIp } = require('../utils/tools')

// 读取切片
const pipeStream = (path, writeStream) => {
Expand All @@ -23,7 +24,7 @@ const pipeStream = (path, writeStream) => {
function listenInput(sftpClient, socket) {
socket.on('open_dir', async (path, tips = true) => {
const exists = await sftpClient.exists(path)
if(!exists) return socket.emit('not_exists_dir', tips ? '目录不存在或当前不可访问' : '')
if (!exists) return socket.emit('not_exists_dir', tips ? '目录不存在或当前不可访问' : '')
try {
let dirLs = await sftpClient.list(path)
socket.emit('dir_ls', dirLs, path)
Expand All @@ -34,7 +35,7 @@ function listenInput(sftpClient, socket) {
})
socket.on('rm_dir', async (path) => {
const exists = await sftpClient.exists(path)
if(!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
if (!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
try {
let res = await sftpClient.rmdir(path, true) // 递归删除
socket.emit('rm_success', res)
Expand All @@ -45,7 +46,7 @@ function listenInput(sftpClient, socket) {
})
socket.on('rm_file', async (path) => {
const exists = await sftpClient.exists(path)
if(!exists) return socket.emit('not_exists_dir', '文件不存在或当前不可访问')
if (!exists) return socket.emit('not_exists_dir', '文件不存在或当前不可访问')
try {
let res = await sftpClient.delete(path)
socket.emit('rm_success', res)
Expand All @@ -65,13 +66,13 @@ function listenInput(sftpClient, socket) {
socket.on('down_file', async ({ path, name, size, target = 'down' }) => {
// target: down or preview
const exists = await sftpClient.exists(path)
if(!exists) return socket.emit('not_exists_dir', '文件不存在或当前不可访问')
if (!exists) return socket.emit('not_exists_dir', '文件不存在或当前不可访问')
try {
const localPath = rawPath.join(sftpCacheDir, name)
let timer = null
let res = await sftpClient.fastGet(path, localPath, {
step: step => {
if(timer) return
if (timer) return
timer = setTimeout(() => {
const percent = Math.ceil((step / size) * 100) // 下载进度为服务器下载到服务端的进度,前端无需*2
console.log(`从服务器下载进度:${ percent }%`)
Expand All @@ -83,7 +84,7 @@ function listenInput(sftpClient, socket) {
consola.success('sftp下载成功: ', res)
let buffer = fs.readFileSync(localPath)
let data = { buffer, name }
switch(target) {
switch (target) {
case 'down':
socket.emit('down_file_success', data)
break
Expand All @@ -102,7 +103,7 @@ function listenInput(sftpClient, socket) {
socket.on('up_file', async ({ targetPath, fullPath, name, file }) => {
// console.log({ targetPath, fullPath, name, file })
const exists = await sftpClient.exists(targetPath)
if(!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
if (!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
try {
const localPath = rawPath.join(sftpCacheDir, name)
fs.writeFileSync(localPath, file)
Expand Down Expand Up @@ -137,7 +138,7 @@ function listenInput(sftpClient, socket) {
socket.on('create_cache_dir', async ({ targetDirPath, name }) => {
// console.log({ targetDirPath, name })
const exists = await sftpClient.exists(targetDirPath)
if(!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
if (!exists) return socket.emit('not_exists_dir', '目录不存在或当前不可访问')
md5List = []
const localPath = rawPath.join(sftpCacheDir, name)
fs.emptyDirSync(localPath) // 不存在会创建,存在则清空
Expand Down Expand Up @@ -178,7 +179,7 @@ function listenInput(sftpClient, socket) {
let timer = null
let res = await sftpClient.fastPut(resultFilePath, targetFilePath, {
step: step => {
if(timer) return
if (timer) return
timer = setTimeout(() => {
const percent = Math.ceil((step / size) * 100)
console.log(`上传服务器进度:${ percent }%`)
Expand Down Expand Up @@ -210,13 +211,18 @@ module.exports = (httpServer) => {
})
serverIo.on('connection', (socket) => {
// 前者兼容nginx反代, 后者兼容nodejs自身服务
let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
let requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
if (!isAllowedIp(requestIP)) {
socket.emit('ip_forbidden', 'IP地址不在白名单中')
socket.disconnect()
return
}
let sftpClient = new SFTPClient()
consola.success('terminal websocket 已连接')

socket.on('create', async ({ host: ip, token }) => {
const { code } = await verifyAuthSync(token, clientIp)
if(code !== 1) {
const { code } = await verifyAuthSync(token, requestIP)
if (code !== 1) {
socket.emit('token_verify_fail')
socket.disconnect()
return
Expand Down
10 changes: 8 additions & 2 deletions server/app/socket/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { verifyAuthSync } = require('../utils/verify-auth')
const { AESDecryptSync } = require('../utils/encrypt')
const { readSSHRecord, readHostList } = require('../utils/storage')
const { asyncSendNotice } = require('../utils/notify')
const { isAllowedIp } = require('../utils/tools')

function createInteractiveShell(socket, sshClient) {
return new Promise((resolve) => {
Expand Down Expand Up @@ -113,11 +114,16 @@ module.exports = (httpServer) => {
})
serverIo.on('connection', (socket) => {
// 前者兼容nginx反代, 后者兼容nodejs自身服务
let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
let requestIP = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address
if (!isAllowedIp(requestIP)) {
socket.emit('ip_forbidden', 'IP地址不在白名单中')
socket.disconnect()
return
}
consola.success('terminal websocket 已连接')
let sshClient = null
socket.on('create', async ({ host: ip, token }) => {
const { code } = await verifyAuthSync(token, clientIp)
const { code } = await verifyAuthSync(token, requestIP)
if (code !== 1) {
socket.emit('token_verify_fail')
socket.disconnect()
Expand Down
43 changes: 43 additions & 0 deletions server/app/template/ipForbidden.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>403 禁止访问</title>
<link rel="icon" href="data:;base64,=">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}

.container {
text-align: center;
padding: 20px;
border-radius: 8px;
}

h1 {
color: #d9534f;
}

p {
color: #333;
}
</style>
</head>

<body>
<div class="container">
<h1>403 禁止访问</h1>
<p>抱歉,您没有权限访问此页面。</p>
</div>
</body>

</html>
Loading

0 comments on commit 9f04c8a

Please sign in to comment.