libastion 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/PLAN.md +168 -0
- package/README.md +213 -0
- package/bin/cli.js +4 -0
- package/bin/server.js +126 -0
- package/config.json +30 -0
- package/package.json +39 -0
- package/src/audit/index.js +128 -0
- package/src/auth/index.js +98 -0
- package/src/cli/index.js +179 -0
- package/src/config/index.js +31 -0
- package/src/crypto.js +52 -0
- package/src/db/index.js +116 -0
- package/src/hosts/index.js +63 -0
- package/src/logger.js +31 -0
- package/src/policy/index.js +32 -0
- package/src/session/index.js +140 -0
package/.env.example
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Bastion Master Key (AES-256-GCM encryption key)
|
|
2
|
+
# Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
3
|
+
BASTION_MASTER_KEY=
|
|
4
|
+
|
|
5
|
+
# SSH Server listen port (default: 2222)
|
|
6
|
+
BASTION_PORT=2222
|
|
7
|
+
|
|
8
|
+
# Log level: info, warn, error, debug
|
|
9
|
+
BASTION_LOG_LEVEL=info
|
package/PLAN.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# 基于 Node.js 的轻量级堡垒机实现方案
|
|
2
|
+
|
|
3
|
+
## 一、方案概述
|
|
4
|
+
|
|
5
|
+
用 Node.js 实现一个**原生 SSH 客户端接入**的轻量堡垒机:用户用 `ssh user@bastion -p 2222` 连接堡垒机,堡垒机完成本地账号认证与权限校验后,作为 SSH 客户端代理连接目标主机,在数据流转过程中实现命令审计、SFTP 文件传输审计与端口转发审计。审计数据落库,可查询追溯。
|
|
6
|
+
|
|
7
|
+
## 二、技术栈与依赖
|
|
8
|
+
|
|
9
|
+
- **Node.js** >= 18
|
|
10
|
+
- **ssh2**:SSH Server + SSH Client(核心,唯一协议依赖)
|
|
11
|
+
- **better-sqlite3**:审计日志与配置数据存储(轻量、同步、零运维)
|
|
12
|
+
- **bcryptjs**:用户密码哈希
|
|
13
|
+
- **winston**:运行日志
|
|
14
|
+
- **dotenv**:环境变量(主密钥等)
|
|
15
|
+
- **commander**(可选):CLI 管理命令
|
|
16
|
+
|
|
17
|
+
## 三、总体架构与数据流
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
用户(ssh客户端)
|
|
21
|
+
│ ssh user@bastion -p 2222
|
|
22
|
+
▼
|
|
23
|
+
┌─────────────────────────────┐
|
|
24
|
+
│ SSH Server (ssh2) │ ← 认证:本地用户库(bcrypt) + RBAC角色
|
|
25
|
+
│ ├─ session/shell │
|
|
26
|
+
│ ├─ session/exec │
|
|
27
|
+
│ ├─ session/sftp │ ← 文件传输审计
|
|
28
|
+
│ └─ session/direct-tcpip │ ← 端口转发审计
|
|
29
|
+
└──────────┬──────────────────┘
|
|
30
|
+
│ 桥接/代理(数据流经审计与命令过滤层)
|
|
31
|
+
▼
|
|
32
|
+
┌─────────────────────────────┐
|
|
33
|
+
│ SSH Client (ssh2) │ ← 用托管凭据(加密存储)连接目标主机
|
|
34
|
+
└──────────┬──────────────────┘
|
|
35
|
+
▼
|
|
36
|
+
目标主机 (host:port)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 四、模块划分
|
|
40
|
+
|
|
41
|
+
1. **入口模块** `bin/server.js`:加载配置、初始化存储、启动 SSH Server
|
|
42
|
+
2. **配置模块** `src/config/`:config.json + .env,含监听端口、并发上限、超时、黑名单规则
|
|
43
|
+
3. **认证与用户模块** `src/auth/`:本地用户 CRUD、bcrypt 校验、角色(admin/auditor/user)、登录限流与失败锁定
|
|
44
|
+
4. **会话代理模块** `src/session/`:会话生命周期管理、Server↔Client 桥接、心跳/空闲超时、并发会话数限制
|
|
45
|
+
5. **目标主机与凭据模块** `src/hosts/`:主机 CRUD、`user_host_perms` 授权、凭据 AES-256-GCM 加密存储
|
|
46
|
+
6. **命令过滤模块** `src/policy/`:命令黑名单/白名单正则,命中即拦截并记录
|
|
47
|
+
7. **审计模块** `src/audit/`:会话流水、命令日志、SFTP 传输、端口转发、审计查询接口
|
|
48
|
+
8. **存储模块** `src/db/`:better-sqlite3 连接与建表、迁移
|
|
49
|
+
9. **CLI 管理模块** `src/cli/`:`add-user`、`add-host`、`grant`、`list-audit` 等命令
|
|
50
|
+
|
|
51
|
+
## 五、数据库表结构(better-sqlite3)
|
|
52
|
+
|
|
53
|
+
- **users**
|
|
54
|
+
- `id` (PK), `username` (UNIQUE), `password_hash`, `role` (admin/auditor/user), `status` (active/locked), `failed_attempts`, `locked_until`, `created_at`
|
|
55
|
+
|
|
56
|
+
- **hosts**
|
|
57
|
+
- `id` (PK), `name`, `host`, `port`, `username`, `auth_type` (password/keyboard-interactive), `credential_enc` (encrypted), `created_at`
|
|
58
|
+
|
|
59
|
+
- **user_host_perms**
|
|
60
|
+
- `id` (PK), `user_id` (FK), `host_id` (FK), `UNIQUE(user_id, host_id)`
|
|
61
|
+
|
|
62
|
+
- **audit_sessions**
|
|
63
|
+
- `id` (PK), `user_id` (FK), `host_id` (FK), `client_ip`, `start_time`, `end_time`, `status` (active/closed/error), `session_key`
|
|
64
|
+
|
|
65
|
+
- **audit_commands**
|
|
66
|
+
- `id` (PK), `session_id` (FK), `command`, `timestamp`, `blocked` (boolean)
|
|
67
|
+
|
|
68
|
+
- **audit_transfers**
|
|
69
|
+
- `id` (PK), `session_id` (FK), `direction` (upload/download), `path`, `size`, `timestamp`
|
|
70
|
+
|
|
71
|
+
- **audit_forwards**
|
|
72
|
+
- `id` (PK), `session_id` (FK), `fwd_type` (direct-tcpip/forwarded-tcpip), `src_addr`, `src_port`, `dst_addr`, `dst_port`, `timestamp`
|
|
73
|
+
|
|
74
|
+
## 六、核心实现要点
|
|
75
|
+
|
|
76
|
+
### 1. SSH Server 认证与代理
|
|
77
|
+
- 使用 `ssh2.Server` 监听 2222 端口
|
|
78
|
+
- `authentication` 回调:验证用户密码,查角色,查授权目标
|
|
79
|
+
- 支持 `shell`(交互式终端)、`exec`(单命令执行)、`sftp`、`direct-tcpip`(端口转发)
|
|
80
|
+
|
|
81
|
+
### 2. 数据桥接与审计
|
|
82
|
+
- 双向 `pipe` 桥接 Server 侧与 Client 侧 channel
|
|
83
|
+
- 在 Server 侧 stream 上做**逐行命令解析**(按 `\r`/`\n` 切分输入,提取完整命令写入审计)
|
|
84
|
+
- SFTP 通过 `on('OPEN')` / `on('WRITE')` 事件解析文件名与读写方向,记录上传/下载行为
|
|
85
|
+
- 端口转发通过 `direct-tcpip` 事件记录源/目标地址与端口
|
|
86
|
+
|
|
87
|
+
### 3. 命令过滤
|
|
88
|
+
- 每条命令在转发前经 `policy` 模块匹配黑名单正则(如 `rm -rf.*`、`shutdown`、`mkfs`)
|
|
89
|
+
- 命中黑名单:向客户端回写拒绝提示,记录 `blocked=1`,**不转发**到目标主机
|
|
90
|
+
|
|
91
|
+
### 4. 凭据加密
|
|
92
|
+
- 主密钥仅存环境变量 `BASTION_MASTER_KEY`
|
|
93
|
+
- 凭据以 **AES-256-GCM** 加密后入库,密钥不落盘
|
|
94
|
+
|
|
95
|
+
### 5. 会话管理
|
|
96
|
+
- 全局并发上限(默认 50)、单用户并发上限
|
|
97
|
+
- 空闲超时(默认 30 分钟自动断开)
|
|
98
|
+
- 会话断开时写 `audit_sessions.end_time/status`
|
|
99
|
+
|
|
100
|
+
## 七、项目目录结构
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
bastion/
|
|
104
|
+
├── bin/
|
|
105
|
+
│ └── server.js # 启动入口
|
|
106
|
+
├── src/
|
|
107
|
+
│ ├── config/
|
|
108
|
+
│ │ └── index.js # 配置加载(config.json + .env)
|
|
109
|
+
│ ├── db/
|
|
110
|
+
│ │ └── index.js # sqlite 连接与建表
|
|
111
|
+
│ ├── auth/
|
|
112
|
+
│ │ └── index.js # 认证与用户管理
|
|
113
|
+
│ ├── hosts/
|
|
114
|
+
│ │ └── index.js # 目标主机与授权
|
|
115
|
+
│ ├── session/
|
|
116
|
+
│ │ └── index.js # 会话代理核心(桥接逻辑)
|
|
117
|
+
│ ├── policy/
|
|
118
|
+
│ │ └── index.js # 命令过滤
|
|
119
|
+
│ ├── audit/
|
|
120
|
+
│ │ └── index.js # 审计写入与查询
|
|
121
|
+
│ └── cli/
|
|
122
|
+
│ └── index.js # CLI 管理命令
|
|
123
|
+
├── config.json # 端口/规则/超时等配置
|
|
124
|
+
├── .env.example # 主密钥等环境变量模板
|
|
125
|
+
├── logs/ # winston 日志目录
|
|
126
|
+
├── package.json
|
|
127
|
+
└── README.md
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 八、实施步骤
|
|
131
|
+
|
|
132
|
+
| 步骤 | 内容 | 预估工作量 |
|
|
133
|
+
|------|------|-----------|
|
|
134
|
+
| 1 | 初始化工程:`npm init`,安装依赖,建立目录结构与配置模板 | 0.5h |
|
|
135
|
+
| 2 | 存储层:实现 db 模块,编写全部建表 SQL | 0.5h |
|
|
136
|
+
| 3 | 用户与认证:用户 CRUD、bcrypt 校验、角色校验、登录限流 | 1h |
|
|
137
|
+
| 4 | 主机与授权:主机 CRUD、凭据加解密、用户-主机授权 | 1h |
|
|
138
|
+
| 5 | SSH Server 接入:实现 server 监听与密码认证 | 1h |
|
|
139
|
+
| 6 | 会话代理:shell/exec 桥接、并发与超时控制 | 2h |
|
|
140
|
+
| 7 | 审计:命令解析落库、SFTP 与端口转发审计、会话汇总 | 2h |
|
|
141
|
+
| 8 | 命令过滤:黑名单正则拦截与记录 | 1h |
|
|
142
|
+
| 9 | CLI 管理:用户/主机/授权/审计查询命令 | 1h |
|
|
143
|
+
| 10 | 联调与部署:本地 ssh 实测、pm2/systemd 守护配置 | 1h |
|
|
144
|
+
|
|
145
|
+
## 九、部署与运维
|
|
146
|
+
|
|
147
|
+
- **启动**:`node bin/server.js`,默认监听 2222 端口
|
|
148
|
+
- **守护**:
|
|
149
|
+
- pm2: `pm2 start bin/server.js --name bastion`
|
|
150
|
+
- systemd: 提供 `bastion.service` unit 文件
|
|
151
|
+
- **审计查询**:CLI `bastion audit --user xxx --since 2025-01-01`
|
|
152
|
+
- **日志**:winston 输出到 `logs/`,按天轮转
|
|
153
|
+
|
|
154
|
+
## 十、安全基线
|
|
155
|
+
|
|
156
|
+
- 主密钥独立管理,通过 `BASTION_MASTER_KEY` 环境变量传入
|
|
157
|
+
- 失败登录锁定机制,防止暴力破解
|
|
158
|
+
- 空闲会话自动断开
|
|
159
|
+
- 并发上限控制
|
|
160
|
+
- 审计日志 append-only,后续可增强为哈希链防篡改
|
|
161
|
+
|
|
162
|
+
## 十一、后续扩展(可选)
|
|
163
|
+
|
|
164
|
+
- 对接 LDAP / AD / SSO 企业认证
|
|
165
|
+
- TOTP 双因素认证
|
|
166
|
+
- 完整会话逐字符录制与回放
|
|
167
|
+
- Web 管理面板(Express + xterm.js)
|
|
168
|
+
- 集群化与高可用部署
|
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# 轻量级堡垒机 - 基于 Node.js
|
|
2
|
+
|
|
3
|
+
> 一个基于 Node.js 实现的原生 SSH 堡垒机,支持命令审计、SFTP 文件传输审计、端口转发审计和命令黑白名单过滤。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- **SSH 原生接入**:用户通过标准 SSH 客户端连接堡垒机
|
|
8
|
+
- **本地认证**:用户名/密码认证,支持失败锁定机制
|
|
9
|
+
- **角色权限**:admin / auditor / user 三级角色
|
|
10
|
+
- **目标主机托管**:加密存储目标主机凭据
|
|
11
|
+
- **用户授权**:细粒度用户-主机授权管理
|
|
12
|
+
- **命令审计**:记录所有执行的命令
|
|
13
|
+
- **SFTP 审计**:记录文件上传/下载操作
|
|
14
|
+
- **端口转发审计**:记录所有 SSH 端口转发请求
|
|
15
|
+
- **命令黑白名单**:可配置的命令过滤规则
|
|
16
|
+
- **会话管理**:并发上限、空闲超时、最大会话时长
|
|
17
|
+
|
|
18
|
+
## 快速开始
|
|
19
|
+
|
|
20
|
+
### 1. 安装依赖
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. 配置环境
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cp .env.example .env
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
编辑 `.env` 文件:
|
|
33
|
+
|
|
34
|
+
```env
|
|
35
|
+
# 生成主密钥
|
|
36
|
+
# BASTION_MASTER_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
|
|
37
|
+
BASTION_MASTER_KEY=your-32-byte-hex-key-here
|
|
38
|
+
|
|
39
|
+
# 监听端口(默认 2222)
|
|
40
|
+
BASTION_PORT=2222
|
|
41
|
+
|
|
42
|
+
# 日志级别
|
|
43
|
+
BASTION_LOG_LEVEL=info
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. 启动服务
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm start
|
|
50
|
+
# 或
|
|
51
|
+
node bin/server.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 4. 使用 CLI 管理
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 添加目标主机
|
|
58
|
+
node bin/cli.js add-host "生产服务器" 192.168.1.100 22 deploy your_password
|
|
59
|
+
|
|
60
|
+
# 添加用户
|
|
61
|
+
node bin/cli.js add-user alice your_password
|
|
62
|
+
|
|
63
|
+
# 授权用户访问主机
|
|
64
|
+
node bin/cli.js grant alice "生产服务器"
|
|
65
|
+
|
|
66
|
+
# 列出目标主机
|
|
67
|
+
node bin/cli.js list-hosts
|
|
68
|
+
|
|
69
|
+
# 查看审计会话
|
|
70
|
+
node bin/cli.js list-sessions
|
|
71
|
+
|
|
72
|
+
# 查看会话命令日志
|
|
73
|
+
node bin/cli.js show-commands 1
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 5. 连接堡垒机
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
ssh admin@localhost -p 2222
|
|
80
|
+
# 默认管理员账号:admin / admin123(首次启动自动创建)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 项目结构
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
bastion/
|
|
87
|
+
├── bin/
|
|
88
|
+
│ ├── server.js # SSH 服务器入口
|
|
89
|
+
│ └── cli.js # CLI 管理工具
|
|
90
|
+
├── src/
|
|
91
|
+
│ ├── config/ # 配置加载
|
|
92
|
+
│ ├── db/ # 数据库 (SQLite)
|
|
93
|
+
│ ├── auth/ # 认证与授权
|
|
94
|
+
│ ├── hosts/ # 目标主机管理
|
|
95
|
+
│ ├── session/ # SSH 会话代理
|
|
96
|
+
│ ├── policy/ # 命令过滤
|
|
97
|
+
│ ├── audit/ # 审计日志
|
|
98
|
+
│ └── cli/ # CLI 命令实现
|
|
99
|
+
├── data/ # 数据库文件
|
|
100
|
+
├── logs/ # 日志文件
|
|
101
|
+
├── config.json # 配置文件
|
|
102
|
+
├── .env.example # 环境变量模板
|
|
103
|
+
├── package.json
|
|
104
|
+
└── README.md
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 配置说明
|
|
108
|
+
|
|
109
|
+
### config.json
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"server": {
|
|
114
|
+
"port": 2222,
|
|
115
|
+
"maxSessions": 50,
|
|
116
|
+
"sessionTimeoutSec": 1800,
|
|
117
|
+
"idleTimeoutSec": 900
|
|
118
|
+
},
|
|
119
|
+
"auth": {
|
|
120
|
+
"maxFailedAttempts": 5,
|
|
121
|
+
"lockDurationSec": 900
|
|
122
|
+
},
|
|
123
|
+
"policy": {
|
|
124
|
+
"blockedCommands": [
|
|
125
|
+
"^rm\\s+(-[a-z]*\\s+)*-[rfR].*",
|
|
126
|
+
"^shutdown\\b",
|
|
127
|
+
"^reboot\\b",
|
|
128
|
+
"^mkfs\\b"
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 部署
|
|
135
|
+
|
|
136
|
+
### 使用 pm2
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm install -g pm2
|
|
140
|
+
pm2 start bin/server.js --name bastion
|
|
141
|
+
pm2 save
|
|
142
|
+
pm2 startup
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 使用 systemd
|
|
146
|
+
|
|
147
|
+
创建 `/etc/systemd/system/bastion.service`:
|
|
148
|
+
|
|
149
|
+
```ini
|
|
150
|
+
[Unit]
|
|
151
|
+
Description=Bastion Host Service
|
|
152
|
+
After=network.target
|
|
153
|
+
|
|
154
|
+
[Service]
|
|
155
|
+
Type=simple
|
|
156
|
+
User=bastion
|
|
157
|
+
WorkingDirectory=/opt/bastion
|
|
158
|
+
ExecStart=/usr/bin/node bin/server.js
|
|
159
|
+
Restart=on-failure
|
|
160
|
+
RestartSec=5
|
|
161
|
+
EnvironmentFile=/opt/bastion/.env
|
|
162
|
+
|
|
163
|
+
[Install]
|
|
164
|
+
WantedBy=multi-user.target
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
sudo systemctl daemon-reload
|
|
169
|
+
sudo systemctl enable bastion
|
|
170
|
+
sudo systemctl start bastion
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 默认账号
|
|
174
|
+
|
|
175
|
+
- 用户名:`admin`
|
|
176
|
+
- 密码:`admin123`
|
|
177
|
+
|
|
178
|
+
**⚠️ 首次启动后请立即修改默认密码!**
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
node bin/cli.js update-password admin new_password
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## 技术栈
|
|
185
|
+
|
|
186
|
+
- Node.js >= 18
|
|
187
|
+
- ssh2 - SSH Server + Client
|
|
188
|
+
- better-sqlite3 - 数据库
|
|
189
|
+
- bcryptjs - 密码哈希
|
|
190
|
+
- winston - 日志
|
|
191
|
+
- commander - CLI 工具
|
|
192
|
+
- dotenv - 环境变量
|
|
193
|
+
|
|
194
|
+
## 安全建议
|
|
195
|
+
|
|
196
|
+
1. **主密钥管理**:通过环境变量传入,不要写入代码
|
|
197
|
+
2. **定期轮换**:定期更换 `BASTION_MASTER_KEY`
|
|
198
|
+
3. **日志轮转**:配置日志文件轮转策略
|
|
199
|
+
4. **审计完整性**:后续可扩展为哈希链防篡改
|
|
200
|
+
5. **网络隔离**:堡垒机应部署在受信任的网络中
|
|
201
|
+
|
|
202
|
+
## 后续扩展
|
|
203
|
+
|
|
204
|
+
- [ ] 对接 LDAP / AD / SSO
|
|
205
|
+
- [ ] TOTP 双因素认证
|
|
206
|
+
- [ ] 完整会话逐字符录制与回放
|
|
207
|
+
- [ ] Web 管理面板
|
|
208
|
+
- [ ] 集群化与高可用
|
|
209
|
+
- [ ] API 接口(REST/GraphQL)
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
package/bin/cli.js
ADDED
package/bin/server.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require('dotenv').config();
|
|
3
|
+
const { Server } = require('ssh2');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const config = require('../src/config');
|
|
7
|
+
const auth = require('../src/auth');
|
|
8
|
+
const hosts = require('../src/hosts');
|
|
9
|
+
const policy = require('../src/policy');
|
|
10
|
+
const audit = require('../src/audit');
|
|
11
|
+
const sessionMod = require('../src/session');
|
|
12
|
+
const logger = require('../src/logger');
|
|
13
|
+
const svc = 'server';
|
|
14
|
+
|
|
15
|
+
logger.info(`${svc}: starting on port ${config.server.port}`);
|
|
16
|
+
auth.initDefaults();
|
|
17
|
+
|
|
18
|
+
const hostKey = fs.readFileSync(path.join(__dirname, '..', 'host.key'), 'utf8');
|
|
19
|
+
const server = new Server({ hostKeys: [hostKey] });
|
|
20
|
+
|
|
21
|
+
server.on('connection', (client, info) => {
|
|
22
|
+
const connInfo = info;
|
|
23
|
+
logger.info(`${svc}: new conn from ${info.ip}`);
|
|
24
|
+
let user = null;
|
|
25
|
+
let host = null;
|
|
26
|
+
|
|
27
|
+
client.on('authentication', (ctx) => {
|
|
28
|
+
if (ctx.method !== 'password') return ctx.reject(['password']);
|
|
29
|
+
if (sessionMod.getActiveSessions() >= sessionMod.getMaxSessions()) return ctx.reject();
|
|
30
|
+
const u = auth.authenticate(ctx.username, ctx.password);
|
|
31
|
+
if (!u) return ctx.reject(['password']);
|
|
32
|
+
const hs = auth.getUserHosts(u.id);
|
|
33
|
+
if (hs.length === 0) return ctx.reject(['password']);
|
|
34
|
+
user = u;
|
|
35
|
+
host = hosts.getHost(hs[0].id);
|
|
36
|
+
ctx.accept();
|
|
37
|
+
logger.info(`${svc}: auth ok ${u.username}`);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
client.on('ready', () => logger.info(`${svc}: ${user ? user.username : ''} ready`));
|
|
41
|
+
|
|
42
|
+
client.on('session', (accept) => {
|
|
43
|
+
const ch = accept();
|
|
44
|
+
ch.on('pty', (a) => a && a());
|
|
45
|
+
ch.on('window-change', (a) => a && a());
|
|
46
|
+
|
|
47
|
+
ch.on('shell', (acceptShell) => {
|
|
48
|
+
if (!user || !host) { ch.close(); return; }
|
|
49
|
+
const stream = acceptShell();
|
|
50
|
+
const as = audit.createSession(user.id, host.id, connInfo.ip);
|
|
51
|
+
let last = Date.now();
|
|
52
|
+
const t = setInterval(() => {
|
|
53
|
+
if (Date.now() - last > config.server.idleTimeoutSec * 1000) {
|
|
54
|
+
stream.write('\r\n*** idle timeout ***\r\n');
|
|
55
|
+
stream.end();
|
|
56
|
+
client.end();
|
|
57
|
+
clearInterval(t);
|
|
58
|
+
}
|
|
59
|
+
}, 30000);
|
|
60
|
+
sessionMod.createClientConnection(host, user.id, as.id, null).then((c) => {
|
|
61
|
+
c.shell({ term: 'vt100' }, (err, rs) => {
|
|
62
|
+
if (err) { stream.end(); return; }
|
|
63
|
+
rs.on('data', (d) => { last = Date.now(); stream.write(d); });
|
|
64
|
+
stream.on('data', (d) => { last = Date.now(); rs.write(d); });
|
|
65
|
+
const close = () => { clearInterval(t); audit.endSession(as.id, 'closed'); client.end(); };
|
|
66
|
+
rs.on('close', close);
|
|
67
|
+
stream.on('close', close);
|
|
68
|
+
});
|
|
69
|
+
}).catch((e) => {
|
|
70
|
+
logger.error(`${svc}: upstream connect failed: ${e.message}`);
|
|
71
|
+
stream.write('\r\n*** upstream connect failed ***\r\n');
|
|
72
|
+
stream.end();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
ch.on('exec', (acceptExec, rejectExec, info) => {
|
|
77
|
+
if (!user || !host) { rejectExec && rejectExec(); return; }
|
|
78
|
+
const as = audit.createSession(user.id, host.id, connInfo.ip);
|
|
79
|
+
const bp = policy.checkCommand(info.command);
|
|
80
|
+
if (bp.blocked) {
|
|
81
|
+
const s = acceptExec();
|
|
82
|
+
audit.recordCommand(as.id, info.command, true);
|
|
83
|
+
s.write('*** command blocked by policy ***\n');
|
|
84
|
+
s.exit(1);
|
|
85
|
+
s.end();
|
|
86
|
+
audit.endSession(as.id, 'closed');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
audit.recordCommand(as.id, info.command, false);
|
|
90
|
+
sessionMod.createClientConnection(host, user.id, as.id, null).then((c) => {
|
|
91
|
+
c.exec(info.command, (err, rs) => {
|
|
92
|
+
if (err) { rejectExec && rejectExec(); return; }
|
|
93
|
+
const s = acceptExec();
|
|
94
|
+
rs.on('data', (d) => s.write(d));
|
|
95
|
+
rs.stderr.on('data', (d) => s.stderr && s.stderr.write(d));
|
|
96
|
+
rs.on('close', () => { s.exit(0); s.end(); c.end(); audit.endSession(as.id, 'closed'); });
|
|
97
|
+
});
|
|
98
|
+
}).catch((e) => { rejectExec && rejectExec(); });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
ch.on('sftp', (acceptSftp) => {
|
|
102
|
+
if (!user || !host) { ch.close(); return; }
|
|
103
|
+
const as = audit.createSession(user.id, host.id, connInfo.ip);
|
|
104
|
+
const sc = acceptSftp();
|
|
105
|
+
sc.on('OPEN', (reqid, filename, flags) => {
|
|
106
|
+
const dir = (flags & 3) ? 'upload' : 'download';
|
|
107
|
+
audit.recordTransfer(as.id, dir, filename, 0);
|
|
108
|
+
});
|
|
109
|
+
sessionMod.createClientConnection(host, user.id, as.id, null).then((c) => {
|
|
110
|
+
c.sftp((err) => { if (err) { sc.close(); } });
|
|
111
|
+
}).catch(() => sc.close());
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
ch.on('direct-tcpip', (acceptDst, rejectDst, info) => {
|
|
115
|
+
if (!user || !host) { rejectDst && rejectDst(new Error('unavailable')); return; }
|
|
116
|
+
const as = audit.createSession(user.id, host.id, connInfo.ip);
|
|
117
|
+
audit.recordForward(as.id, 'direct-tcpip', info.srcIP, info.srcPort, info.destIP, info.destPort);
|
|
118
|
+
rejectDst ? rejectDst(new Error('port forwarding disabled')) : acceptDst().end();
|
|
119
|
+
audit.endSession(as.id, 'closed');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
client.on('close', () => logger.info(`${svc}: conn closed`));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
server.listen(config.server.port, () => logger.info(`${svc}: ready on port ${config.server.port}`));
|
package/config.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"server": {
|
|
3
|
+
"port": 2222,
|
|
4
|
+
"maxSessions": 50,
|
|
5
|
+
"sessionTimeoutSec": 1800,
|
|
6
|
+
"idleTimeoutSec": 900
|
|
7
|
+
},
|
|
8
|
+
"auth": {
|
|
9
|
+
"maxFailedAttempts": 5,
|
|
10
|
+
"lockDurationSec": 900
|
|
11
|
+
},
|
|
12
|
+
"policy": {
|
|
13
|
+
"blockedCommands": [
|
|
14
|
+
"^rm\\s+(-[a-z]*\\s+)*-[rfR].*",
|
|
15
|
+
"^shutdown\\b",
|
|
16
|
+
"^halt\\b",
|
|
17
|
+
"^reboot\\b",
|
|
18
|
+
"^poweroff\\b",
|
|
19
|
+
"^mkfs\\b",
|
|
20
|
+
"^dd\\s+if=",
|
|
21
|
+
"^chmod\\s+777\\b",
|
|
22
|
+
"^chmod\\s+\\+s\\b",
|
|
23
|
+
"^chown\\s+root\\b",
|
|
24
|
+
"^iptables\\s+-F\\b",
|
|
25
|
+
"^sysctl\\s+.*=\\s*0\\b",
|
|
26
|
+
"^wget\\s+.*\\|.*sh",
|
|
27
|
+
"^curl\\s+.*\\|.*sh"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "libastion",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A lightweight bastion host built with Node.js",
|
|
5
|
+
"main": "bin/server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bastion": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/server.js",
|
|
11
|
+
"dev": "node --watch bin/server.js",
|
|
12
|
+
"cli": "node bin/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"bastion",
|
|
16
|
+
"ssh",
|
|
17
|
+
"audit",
|
|
18
|
+
"jump-server"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"ssh2": "^1.15.0",
|
|
23
|
+
"bcryptjs": "^2.4.3",
|
|
24
|
+
"winston": "^3.14.0",
|
|
25
|
+
"dotenv": "^16.4.0",
|
|
26
|
+
"commander": "^12.1.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin",
|
|
30
|
+
"src",
|
|
31
|
+
"config.json",
|
|
32
|
+
".env.example",
|
|
33
|
+
"README.md",
|
|
34
|
+
"PLAN.md"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
}
|
|
39
|
+
}
|