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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const config = require('../config');
|
|
2
|
+
const logger = require('../logger');
|
|
3
|
+
|
|
4
|
+
const service = 'policy';
|
|
5
|
+
|
|
6
|
+
// Pre-compile regex patterns
|
|
7
|
+
const blockedPatterns = config.policy.blockedCommands.map(pattern => ({
|
|
8
|
+
pattern: new RegExp(pattern, 'i'),
|
|
9
|
+
original: pattern,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a command is blocked
|
|
14
|
+
* @param {string} command - The command to check
|
|
15
|
+
* @returns {{ blocked: boolean, reason: string | null }}
|
|
16
|
+
*/
|
|
17
|
+
function checkCommand(command) {
|
|
18
|
+
if (!command || !command.trim()) {
|
|
19
|
+
return { blocked: false, reason: null };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const { pattern, original } of blockedPatterns) {
|
|
23
|
+
if (pattern.test(command.trim())) {
|
|
24
|
+
logger.warn(`${service}: Command blocked`, { command, pattern: original });
|
|
25
|
+
return { blocked: true, reason: `Command "${command.trim()}" is blocked by policy` };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { blocked: false, reason: null };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { checkCommand };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const { Client } = require('ssh2');
|
|
2
|
+
const config = require('../config');
|
|
3
|
+
const logger = require('../logger');
|
|
4
|
+
const audit = require('../audit');
|
|
5
|
+
|
|
6
|
+
const service = 'session';
|
|
7
|
+
|
|
8
|
+
// Active session counter
|
|
9
|
+
let activeSessions = 0;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create SSH client connection to target host
|
|
13
|
+
*/
|
|
14
|
+
function createClientConnection(hostInfo, userId, sessionId, serverStream) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const client = new Client();
|
|
17
|
+
|
|
18
|
+
client.on('ready', () => {
|
|
19
|
+
logger.info(`${service}: SSH client connected to target`, {
|
|
20
|
+
host: hostInfo.host,
|
|
21
|
+
port: hostInfo.port,
|
|
22
|
+
userId,
|
|
23
|
+
sessionId,
|
|
24
|
+
});
|
|
25
|
+
resolve(client);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
client.on('error', (err) => {
|
|
29
|
+
logger.error(`${service}: SSH client error`, { error: err.message, host: hostInfo.host });
|
|
30
|
+
audit.endSession(sessionId, 'error');
|
|
31
|
+
reject(err);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
client.on('close', () => {
|
|
35
|
+
logger.info(`${service}: SSH client closed`, { sessionId });
|
|
36
|
+
audit.endSession(sessionId, 'closed');
|
|
37
|
+
activeSessions--;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Connect to target host
|
|
41
|
+
client.connect({
|
|
42
|
+
host: hostInfo.host,
|
|
43
|
+
port: hostInfo.port || 22,
|
|
44
|
+
username: hostInfo.username,
|
|
45
|
+
password: hostInfo.credential,
|
|
46
|
+
readyTimeout: 20000,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Handle shell session (interactive terminal)
|
|
53
|
+
*/
|
|
54
|
+
async function handleShell(client, sessionId) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
client.shell({
|
|
57
|
+
term: 'xterm',
|
|
58
|
+
cols: 80,
|
|
59
|
+
rows: 30,
|
|
60
|
+
}, (err, stream) => {
|
|
61
|
+
if (err) {
|
|
62
|
+
logger.error(`${service}: Shell creation error`, { error: err.message });
|
|
63
|
+
reject(err);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
logger.info(`${service}: Shell session started`, { sessionId });
|
|
68
|
+
activeSessions++;
|
|
69
|
+
|
|
70
|
+
// Set idle timeout
|
|
71
|
+
let idleTimer = setTimeout(() => {
|
|
72
|
+
logger.warn(`${service}: Session timed out (idle)`, { sessionId });
|
|
73
|
+
stream.end();
|
|
74
|
+
resolve();
|
|
75
|
+
}, config.server.idleTimeoutSec * 1000);
|
|
76
|
+
|
|
77
|
+
stream.on('data', (data) => {
|
|
78
|
+
clearTimeout(idleTimer);
|
|
79
|
+
idleTimer = setTimeout(() => {
|
|
80
|
+
logger.warn(`${service}: Session timed out (idle)`, { sessionId });
|
|
81
|
+
stream.end();
|
|
82
|
+
resolve();
|
|
83
|
+
}, config.server.idleTimeoutSec * 1000);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
resolve(stream);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Handle exec session (single command)
|
|
93
|
+
*/
|
|
94
|
+
async function handleExec(client, command, sessionId) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
activeSessions++;
|
|
97
|
+
|
|
98
|
+
client.exec(command, (err, stream) => {
|
|
99
|
+
if (err) {
|
|
100
|
+
logger.error(`${service}: Exec error`, { error: err.message });
|
|
101
|
+
reject(err);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
resolve(stream);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Bridge two streams bidirectionally with command audit
|
|
112
|
+
*/
|
|
113
|
+
function bridgeStreams(serverStream, clientStream, sessionId) {
|
|
114
|
+
// Server -> Client
|
|
115
|
+
serverStream.on('data', (data) => {
|
|
116
|
+
clientStream.write(data);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Client -> Server (interactive shell passthrough, command audit happens in session handle)
|
|
120
|
+
clientStream.on('data', (data) => {
|
|
121
|
+
serverStream.write(data);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Close propagation
|
|
125
|
+
serverStream.on('close', () => {
|
|
126
|
+
clientStream.end();
|
|
127
|
+
});
|
|
128
|
+
clientStream.on('close', () => {
|
|
129
|
+
serverStream.end();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
createClientConnection,
|
|
135
|
+
handleShell,
|
|
136
|
+
handleExec,
|
|
137
|
+
bridgeStreams,
|
|
138
|
+
getActiveSessions: () => activeSessions,
|
|
139
|
+
getMaxSessions: () => config.server.maxSessions,
|
|
140
|
+
};
|