This commit is contained in:
Bozhidar 2024-04-22 14:15:52 +03:00
parent a9d5fc38a1
commit c54d1956d3
10 changed files with 283 additions and 2 deletions

2
.gitignore vendored
View file

@ -1,4 +1,2 @@
web/storage/installed
/docker/e2e-tests/node_modules/
compilators/
web/thirdparty/

View file

@ -0,0 +1,9 @@
module.exports = {
env: {
browser: false,
node: true,
},
rules: {
'no-console': 'off',
},
};

View file

@ -0,0 +1,9 @@
Source: phyre-web-terminal
Package: phyre-web-terminal
Priority: optional
Version: 1.0.0
Section: admin
Maintainer: PhyrePanel <info@phyrepanel.com>
Homepage: https://www.phyrepanel.com
Architecture: amd64
Description: Phyre Web Terminal

View file

@ -0,0 +1,29 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: phyre
Source: https://www.phyrepanel.com
Files: *
Copyright: 2018-2023, Phyre Control Panel <info@phyrepanel.com>
License: GPL-3.0+
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in /usr/share/common-licenses/GPL-3.
# Please also look if there are files or directories which have a
# different copyright/license attached and list them here.
# Please avoid to pick license terms that are more restrictive than the
# packaged work, as it may make Debian's contributions unacceptable upstream.

View file

@ -0,0 +1,17 @@
{
"name": "cloudvisionapps/phyre-web-terminal-ws",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"node-pty": "^1.0.0",
"ws": "^8.16.0"
},
"devDependencies": {
"@types/ws": "^8.5.10",
"@types/node": "^20.12.5"
}
}

View file

@ -0,0 +1,16 @@
[Unit]
Description=Phyre Web Terminal
Documentation=https://phyrepanel.com/docs/
After=network.target
[Service]
User=root
Group=phyre-users
Environment=NODE_ENV=production
Environment=PHYRE=/usr/local/phyre
ExecStart=/usr/local/phyre/web-terminal/server.js
ExecStop=/bin/kill -s TERM $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,30 @@
#!/bin/bash
set -e
if [ "$1" != "configure" ]; then
exit 0
fi
# Run triggers below only on updates
if [ ! -e "/usr/local/phyre/data/users/admin" ]; then
exit
fi
###############################################################
# Initialize functions/variables #
###############################################################
if [ -z "$PHYRE" ]; then
export PHYRE='/usr/local/phyre'
PATH=$PATH:/usr/local/phyre/bin
export PATH
fi
# Restart hestia-web-terminal service if enabled
if [ -f "/etc/systemd/system/phyre-web-terminal.service" ]; then
systemctl daemon-reload > /dev/null 2>&1
if systemctl is-enabled phyre-web-terminal > /dev/null 2>&1; then
systemctl restart phyre-web-terminal
fi
fi

View file

@ -0,0 +1,2 @@
#!/bin/sh

View file

@ -0,0 +1,121 @@
#!/usr/bin/env node
import { execSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { spawn } from 'node-pty';
import { WebSocketServer } from 'ws';
const sessionName = 'PHYRESID';
const hostname = execSync('hostname', { silent: true }).toString().trim();
// const systemIPs = JSON.parse(
// execSync(`${process.env.PHYRE}/bin/v-list-sys-ips json`, { silent: true }).toString(),
// );
const systemIPs = [];
// const { config } = JSON.parse(
// execSync(`${process.env.PHYRE}/bin/v-list-sys-config json`, { silent: true }).toString(),
// );
const config = {
WEB_TERMINAL_PORT: 3000,
BACKEND_PORT: 8083,
};
const wss = new WebSocketServer({
port: parseInt(config.WEB_TERMINAL_PORT, 10),
verifyClient: async (info, cb) => {
if (!info.req.headers.cookie.includes(sessionName)) {
cb(false, 401, 'Unauthorized');
return;
}
const origin = info.origin || info.req.headers.origin;
let matches = origin === `https://${hostname}:${config.BACKEND_PORT}`;
if (!matches) {
for (const ip of Object.keys(systemIPs)) {
if (origin === `https://${ip}:${config.BACKEND_PORT}`) {
matches = true;
break;
}
}
}
if (matches) {
cb(true);
return;
}
cb(false, 403, 'Forbidden');
},
});
wss.on('connection', (ws, req) => {
wss.clients.add(ws);
const remoteIP = req.headers['x-real-ip'] || req.socket.remoteAddress;
// Check if session is valid
const sessionID = req.headers.cookie.split(`${sessionName}=`)[1].split(';')[0];
console.log(`New connection from ${remoteIP} (${sessionID})`);
const file = readFileSync(`${process.env.HESTIA}/data/sessions/sess_${sessionID}`);
if (!file) {
console.error(`Invalid session ID ${sessionID}, refusing connection`);
ws.close(1000, 'Your session has expired.');
return;
}
const session = file.toString();
// Get username
const login = session.split('user|s:')[1].split('"')[1];
const impersonating = session.split('look|s:')[1].split('"')[1];
const username = impersonating.length > 0 ? impersonating : login;
// Get user info
const passwd = readFileSync('/etc/passwd').toString();
const userline = passwd.split('\n').find((line) => line.startsWith(`${username}:`));
if (!userline) {
console.error(`User ${username} not found, refusing connection`);
ws.close(1000, 'You are not allowed to access this server.');
return;
}
const [, , uid, gid, , homedir, shell] = userline.split(':');
if (shell.endsWith('nologin')) {
console.error(`User ${username} has no shell, refusing connection`);
ws.close(1000, 'You have no shell access.');
return;
}
// Spawn shell as logged in user
const pty = spawn(shell, [], {
name: 'xterm-color',
uid: parseInt(uid, 10),
gid: parseInt(gid, 10),
cwd: homedir,
env: {
SHELL: shell,
TERM: 'xterm-color',
USER: username,
HOME: homedir,
PWD: homedir,
PHYRE: process.env.PHYRE,
},
});
console.log(`New pty (${pty.pid}): ${shell} as ${username} (${uid}:${gid}) in ${homedir}`);
// Send/receive data from websocket/pty
pty.on('data', (data) => ws.send(data));
ws.on('message', (data) => pty.write(data));
// Ensure pty is killed when websocket is closed and vice versa
pty.on('exit', () => {
console.log(`Ended pty (${pty.pid})`);
if (ws.OPEN) {
ws.close();
}
});
ws.on('close', () => {
console.log(`Ended connection from ${remoteIP} (${sessionID})`);
pty.kill();
wss.clients.delete(ws);
});
});

View file

@ -0,0 +1,50 @@
#!/bin/bash
MAIN_DIR=$(pwd)
# Install dependencies
sudo apt-get update -y
sudo apt-get install -y build-essential dpkg-dev debhelper autotools-dev libgeoip-dev libssl-dev libpcre3-dev zlib1g-dev
sudo apt-get install -y npm nodejs
# Package main dir is the path of the clean debian package
# In PACKAGE_MAIN_DIR must exist only the directories that will be copied to the final debian package
sudo mkdir $MAIN_DIR/phyre-web-terminal-0.0.1
PACKAGE_MAIN_DIR=$MAIN_DIR/phyre-web-terminal-0.0.1
# Create debian package directories
sudo mkdir -p $PACKAGE_MAIN_DIR/DEBIAN
sudo mkdir -p $PACKAGE_MAIN_DIR/usr/local/phyre/web-terminal
# Copy web-terminal files
sudo cp $MAIN_DIR/.eslintrc.cjs $PACKAGE_MAIN_DIR/usr/local/phyre/web-terminal
sudo cp $MAIN_DIR/server.js $PACKAGE_MAIN_DIR/usr/local/phyre/web-terminal
sudo cp $MAIN_DIR/package.json $PACKAGE_MAIN_DIR/usr/local/phyre/web-terminal
cd $PACKAGE_MAIN_DIR/usr/local/phyre/web-terminal
sudo chmod +x $PACKAGE_MAIN_DIR/usr/local/phyre/web-terminal/server.js
# Compile web-terminal
cd $PACKAGE_MAIN_DIR/usr/local/phyre/web-terminal
sudo npm install
# Copy debian package META file
sudo cp $MAIN_DIR/control $PACKAGE_MAIN_DIR/DEBIAN
sudo cp $MAIN_DIR/postinst $PACKAGE_MAIN_DIR/DEBIAN
sudo cp $MAIN_DIR/postrm $PACKAGE_MAIN_DIR/DEBIAN
# Set debian package post files permissions
sudo chmod +x $PACKAGE_MAIN_DIR/DEBIAN/postinst
sudo chmod +x $PACKAGE_MAIN_DIR/DEBIAN/postrm
# Make debian package
sudo dpkg-deb --build $PACKAGE_MAIN_DIR
sudo dpkg --info $MAIN_DIR/phyre-web-terminal-0.0.1.deb
sudo dpkg --contents $MAIN_DIR/phyre-web-terminal-0.0.1.deb
# Move debian package to dist folder
sudo mkdir -p $MAIN_DIR/dist
sudo mv $MAIN_DIR/phyre-web-terminal-0.0.1.deb $MAIN_DIR/dist