Spaces:
Paused
Paused
BinaryONe
commited on
Commit
·
d89b239
1
Parent(s):
5669a3b
Changes
Browse files- DockerFile +81 -0
- WebSSH/Dockerfile +77 -0
- WebSSH/Fonts.py +34 -0
- WebSSH/Tools.py +70 -0
- WebSSH/__init__.py +0 -0
- WebSSH/__main__.py +165 -0
- WebSSH/maink.py +59 -0
- WebSSH/requirements.txt +5 -0
- WebSSH/static/img/favicon.png +0 -0
- WebSSH/templates/index.html +20 -0
- start.sh +92 -0
DockerFile
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use the official Ubuntu base image
|
| 2 |
+
FROM ubuntu:latest
|
| 3 |
+
|
| 4 |
+
# Set the maintainer label
|
| 5 |
+
LABEL maintainer="your-email@example.com"
|
| 6 |
+
|
| 7 |
+
# Prevent prompts during package installation
|
| 8 |
+
ARG DEBIAN_FRONTEND=noninteractive
|
| 9 |
+
|
| 10 |
+
RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000 ubuntu && \
|
| 11 |
+
echo 'ubuntu:ubuntu' | chpasswd && \
|
| 12 |
+
echo 'root:password' | chpasswd
|
| 13 |
+
|
| 14 |
+
# Copy the application code to the container
|
| 15 |
+
COPY . /app
|
| 16 |
+
|
| 17 |
+
# Install necessary packages
|
| 18 |
+
RUN apt update && apt install -y \
|
| 19 |
+
openssh-server \
|
| 20 |
+
openssh\
|
| 21 |
+
sudo \
|
| 22 |
+
bash \
|
| 23 |
+
python3 \
|
| 24 |
+
python3-pip \
|
| 25 |
+
python3-venv \
|
| 26 |
+
net-tools && \
|
| 27 |
+
apt clean && \
|
| 28 |
+
rm -rf /var/lib/apt/lists/*
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# SSH Configuration
|
| 32 |
+
RUN mkdir -p /var/run/sshd /app /app/ssh
|
| 33 |
+
RUN chmod -R 777 /app
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# Generate SSH keys
|
| 37 |
+
RUN ssh-keygen -t rsa -b 2048 -f /etc/ssh/ssh_host_rsa_key -N "" && \
|
| 38 |
+
ssh-keygen -t ecdsa -b 256 -f /etc/ssh/ssh_host_ecdsa_key -N "" && \
|
| 39 |
+
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
|
| 40 |
+
|
| 41 |
+
# Secure SSH Configuration
|
| 42 |
+
RUN sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config && \
|
| 43 |
+
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
|
| 44 |
+
sed -i 's/#ChallengeResponseAuthentication yes/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config && \
|
| 45 |
+
sed -i 's/#UsePAM yes/UsePAM no/' /etc/ssh/sshd_config && \
|
| 46 |
+
sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config && \
|
| 47 |
+
echo "AllowUsers admin" >> /etc/ssh/sshd_config
|
| 48 |
+
|
| 49 |
+
# Copy all the contents of /etc/ssh to /app/ssh
|
| 50 |
+
RUN mkdir -p /app/ssh && cp -r /etc/ssh/* /app/ssh
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# Set the permissions for the SSH keys
|
| 54 |
+
RUN chmod 777 /etc/ssh/ssh_host_* && \
|
| 55 |
+
touch /app/ssh/ssh_known_hosts && \
|
| 56 |
+
chmod 777 /app/ssh/ssh_* && \
|
| 57 |
+
chmod 777 /home
|
| 58 |
+
|
| 59 |
+
# List contents of /etc/ssh and /app/ssh
|
| 60 |
+
RUN ls -l /etc/ssh/ && \
|
| 61 |
+
ls -l /app/ssh/
|
| 62 |
+
|
| 63 |
+
# Install WebSSH
|
| 64 |
+
#RUN python3 -m venv /app/venv && \
|
| 65 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 66 |
+
pip install --no-cache-dir -r /app/WebSSH/requirements.txt && \
|
| 67 |
+
pip list
|
| 68 |
+
|
| 69 |
+
# Set the working directory
|
| 70 |
+
WORKDIR /app
|
| 71 |
+
|
| 72 |
+
USER root
|
| 73 |
+
|
| 74 |
+
# Expose the port the app runs on
|
| 75 |
+
EXPOSE 7860
|
| 76 |
+
|
| 77 |
+
# Expose the port the app runs on
|
| 78 |
+
EXPOSE 2222
|
| 79 |
+
|
| 80 |
+
# Define the command to run the application
|
| 81 |
+
CMD ["/app/start.sh"]
|
WebSSH/Dockerfile
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM alpine:latest
|
| 2 |
+
|
| 3 |
+
# Install necessary packages
|
| 4 |
+
RUN apk update && apk add --no-cache openssh openrc openssh-keygen bash sudo python3 py3-pip net-tools
|
| 5 |
+
|
| 6 |
+
# Create a non-root user (admin)
|
| 7 |
+
RUN adduser -D admin
|
| 8 |
+
RUN echo "admin:password" | chpasswd
|
| 9 |
+
RUN addgroup admin wheel
|
| 10 |
+
RUN adduser admin wheel
|
| 11 |
+
|
| 12 |
+
# SSH Configuration
|
| 13 |
+
RUN mkdir -p /var/run/sshd /app /app/ssh
|
| 14 |
+
RUN chmod -R 777 /app
|
| 15 |
+
|
| 16 |
+
# SSH Configuration
|
| 17 |
+
#RUN mkdir /app
|
| 18 |
+
|
| 19 |
+
# Copy all the contents of the /app folder
|
| 20 |
+
COPY . /app
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# Generate SSH keys and print them
|
| 24 |
+
#RUN ssh-keygen -t rsa -b 2048 -f /etc/ssh/ssh_host_rsa_key -N "" && \
|
| 25 |
+
# cat /etc/ssh/ssh_host_rsa_key && \
|
| 26 |
+
# ssh-keygen -t ecdsa -b 256 -f /etc/ssh/ssh_host_ecdsa_key -N "" && \
|
| 27 |
+
# cat /etc/ssh/ssh_host_ecdsa_key && \
|
| 28 |
+
# ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" && \
|
| 29 |
+
# cat /etc/ssh/ssh_host_ed25519_key
|
| 30 |
+
|
| 31 |
+
# Generate SSH keys
|
| 32 |
+
RUN ssh-keygen -t rsa -b 2048 -f /etc/ssh/ssh_host_rsa_key -N "" && \
|
| 33 |
+
ssh-keygen -t ecdsa -b 256 -f /etc/ssh/ssh_host_ecdsa_key -N "" && \
|
| 34 |
+
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
|
| 35 |
+
|
| 36 |
+
# Secure SSH Configuration
|
| 37 |
+
RUN sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config && \
|
| 38 |
+
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
|
| 39 |
+
sed -i 's/#ChallengeResponseAuthentication yes/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config && \
|
| 40 |
+
sed -i 's/#UsePAM yes/UsePAM no/' /etc/ssh/sshd_config && \
|
| 41 |
+
sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config && \
|
| 42 |
+
echo "AllowUsers admin" >> /etc/ssh/sshd_config
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# Copy all the contents of /etc/ssh to /app/ssh
|
| 46 |
+
RUN mkdir -p /app/ssh && cp -r /etc/ssh/* /app/ssh
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# Set the permissions for the SSH keys
|
| 50 |
+
RUN chmod 777 /etc/ssh/ssh_host_* && \
|
| 51 |
+
touch /app/ssh/ssh_known_hosts && \
|
| 52 |
+
chmod 777 /app/ssh/ssh_* && \
|
| 53 |
+
chmod 777 /home
|
| 54 |
+
|
| 55 |
+
# List contents of /etc/ssh and /app/ssh
|
| 56 |
+
RUN ls -l /etc/ssh/ && \
|
| 57 |
+
ls -l /app/ssh/
|
| 58 |
+
|
| 59 |
+
# Install WebSSH
|
| 60 |
+
RUN python3 -m venv /app/venv && \
|
| 61 |
+
/app/venv/bin/pip install --no-cache-dir --upgrade pip && \
|
| 62 |
+
/app/venv/bin/pip install --no-cache-dir -r /app/WebSSH/requirements.txt && \
|
| 63 |
+
/app/venv/bin/pip list
|
| 64 |
+
|
| 65 |
+
# Expose the new SSH port
|
| 66 |
+
EXPOSE 2222
|
| 67 |
+
|
| 68 |
+
RUN chmod -R 777 /app
|
| 69 |
+
|
| 70 |
+
# Copy the start.sh script
|
| 71 |
+
#RUN chmod 777 /app/venv/lib/python3.12/site-packages/
|
| 72 |
+
#RUN touch /app/venv/lib/python3.12/site-packages/known_hosts
|
| 73 |
+
#RUN chmod 777 /app/venv/lib/python3.12/site-packages/known_hosts
|
| 74 |
+
|
| 75 |
+
EXPOSE 7860
|
| 76 |
+
|
| 77 |
+
CMD ["/app/start.sh"]
|
WebSSH/Fonts.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import importlib.util
|
| 3 |
+
|
| 4 |
+
# Automatically detect the path to the webssh package
|
| 5 |
+
spec = importlib.util.find_spec('webssh')
|
| 6 |
+
base_dir = os.path.dirname(spec.origin) if spec else None
|
| 7 |
+
font_dirs = ['static', 'css', 'fonts']
|
| 8 |
+
max_body_size = 1 * 1024 * 1024
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Font(object):
|
| 12 |
+
|
| 13 |
+
def __init__(self, filename, dirs):
|
| 14 |
+
self.family = self.get_family(filename)
|
| 15 |
+
self.url = self.get_url(filename, dirs)
|
| 16 |
+
|
| 17 |
+
def get_family(self, filename):
|
| 18 |
+
return filename.split('.')[0]
|
| 19 |
+
|
| 20 |
+
def get_url(self, filename, dirs):
|
| 21 |
+
return '/'.join(dirs + [filename])
|
| 22 |
+
|
| 23 |
+
def get_font_filename(font, font_dir):
|
| 24 |
+
filenames = {f for f in os.listdir(font_dir) if not f.startswith('.')
|
| 25 |
+
and os.path.isfile(os.path.join(font_dir, f))}
|
| 26 |
+
if font:
|
| 27 |
+
if font not in filenames:
|
| 28 |
+
raise ValueError(
|
| 29 |
+
'Font file {!r} not found'.format(os.path.join(font_dir, font))
|
| 30 |
+
)
|
| 31 |
+
elif filenames:
|
| 32 |
+
font = filenames.pop()
|
| 33 |
+
|
| 34 |
+
return font
|
WebSSH/Tools.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import ssl
|
| 3 |
+
import sys
|
| 4 |
+
import logging
|
| 5 |
+
import os.path
|
| 6 |
+
import importlib.util
|
| 7 |
+
|
| 8 |
+
# Automatically detect the path to the webssh package
|
| 9 |
+
spec = importlib.util.find_spec('webssh')
|
| 10 |
+
base_dir = os.path.dirname(spec.origin) if spec else None
|
| 11 |
+
|
| 12 |
+
#font_dirs = ['static', 'css', 'fonts']
|
| 13 |
+
max_body_size = 1 * 1024 * 1024
|
| 14 |
+
|
| 15 |
+
#base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 16 |
+
#font_dirs = ['webssh', 'static', 'css', 'fonts']
|
| 17 |
+
|
| 18 |
+
def print_version(flag):
|
| 19 |
+
if flag:
|
| 20 |
+
print(__version__)
|
| 21 |
+
sys.exit(0)
|
| 22 |
+
|
| 23 |
+
def is_valid_encoding(encoding):
|
| 24 |
+
try:
|
| 25 |
+
u'test'.encode(encoding)
|
| 26 |
+
except LookupError:
|
| 27 |
+
return False
|
| 28 |
+
except ValueError:
|
| 29 |
+
return False
|
| 30 |
+
return True
|
| 31 |
+
|
| 32 |
+
def check_encoding_setting(encoding):
|
| 33 |
+
if encoding and not is_valid_encoding(encoding):
|
| 34 |
+
raise ValueError('Unknown character encoding {!r}.'.format(encoding))
|
| 35 |
+
|
| 36 |
+
def get_server_settings(options):
|
| 37 |
+
settings = dict(
|
| 38 |
+
xheaders=options.xheaders,
|
| 39 |
+
max_body_size=max_body_size,
|
| 40 |
+
trusted_downstream=get_trusted_downstream(options.tdstream)
|
| 41 |
+
)
|
| 42 |
+
return settings
|
| 43 |
+
|
| 44 |
+
def get_trusted_downstream(tdstream):
|
| 45 |
+
result = set()
|
| 46 |
+
for ip in tdstream.split(','):
|
| 47 |
+
ip = ip.strip()
|
| 48 |
+
if ip:
|
| 49 |
+
to_ip_address(ip)
|
| 50 |
+
result.add(ip)
|
| 51 |
+
return result
|
| 52 |
+
|
| 53 |
+
def get_ssl_context(options):
|
| 54 |
+
if not options.certfile and not options.keyfile:
|
| 55 |
+
return None
|
| 56 |
+
elif not options.certfile:
|
| 57 |
+
raise ValueError('certfile is not provided')
|
| 58 |
+
elif not options.keyfile:
|
| 59 |
+
raise ValueError('keyfile is not provided')
|
| 60 |
+
elif not os.path.isfile(options.certfile):
|
| 61 |
+
raise ValueError('File {!r} does not exist'.format(options.certfile))
|
| 62 |
+
elif not os.path.isfile(options.keyfile):
|
| 63 |
+
raise ValueError('File {!r} does not exist'.format(options.keyfile))
|
| 64 |
+
else:
|
| 65 |
+
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
| 66 |
+
ssl_ctx.load_cert_chain(options.certfile, options.keyfile)
|
| 67 |
+
return ssl_ctx
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
|
WebSSH/__init__.py
ADDED
|
File without changes
|
WebSSH/__main__.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import webssh
|
| 3 |
+
import logging
|
| 4 |
+
import paramiko
|
| 5 |
+
import subprocess
|
| 6 |
+
import tornado.web
|
| 7 |
+
import tornado.ioloop
|
| 8 |
+
from tornado.options import define, options
|
| 9 |
+
from webssh.handler import IndexHandler, WsockHandler, NotFoundHandler
|
| 10 |
+
from .Fonts import Font, get_font_filename, base_dir, font_dirs, max_body_size
|
| 11 |
+
from .Tools import get_ssl_context, get_server_settings, check_encoding_setting, print_version
|
| 12 |
+
|
| 13 |
+
logging.getLogger("tornado.access").setLevel(logging.DEBUG)
|
| 14 |
+
logging.getLogger("tornado.application").setLevel(logging.DEBUG)
|
| 15 |
+
logging.getLogger("tornado.general").setLevel(logging.DEBUG)
|
| 16 |
+
|
| 17 |
+
# Define application options
|
| 18 |
+
define('address', default='0.0.0.0', help='Listen address')
|
| 19 |
+
define('port', type=int, default=7860, help='Listen port')
|
| 20 |
+
define('ssladdress', default='', help='SSL listen address')
|
| 21 |
+
define('sslport', type=int, default=4433, help='SSL listen port')
|
| 22 |
+
define('certfile', default='', help='SSL certificate file')
|
| 23 |
+
define('keyfile', default='', help='SSL private key file')
|
| 24 |
+
define('debug', type=bool, default=True, help='Debug mode')
|
| 25 |
+
define('policy', default='autoadd', help='Missing host key policy, reject|autoadd|warning')
|
| 26 |
+
define('hostfile', default='', help='User defined host keys file')
|
| 27 |
+
define('syshostfile', default='', help='System wide host keys file')
|
| 28 |
+
define('tdstream', default='', help='Trusted downstream, separated by comma')
|
| 29 |
+
define('redirect', type=bool, default=True, help='Redirecting http to https')
|
| 30 |
+
define('fbidhttp', type=bool, default=False, help='Forbid public plain http incoming requests')
|
| 31 |
+
define('xheaders', type=bool, default=False, help='Support xheaders')
|
| 32 |
+
define('xsrf', type=bool, default=False, help='CSRF protection')
|
| 33 |
+
define(
|
| 34 |
+
"origin",
|
| 35 |
+
default="*",
|
| 36 |
+
help="""Origin policy,
|
| 37 |
+
'same': same origin policy, matches host name and port number;
|
| 38 |
+
'primary': primary domain policy, matches primary domain only;
|
| 39 |
+
'<domains>': custom domains policy, matches any domain in the <domains> list
|
| 40 |
+
separated by comma;
|
| 41 |
+
'*': wildcard policy, matches any domain, allowed in debug mode only.""",
|
| 42 |
+
)
|
| 43 |
+
define('wpintvl', type=float, default=0, help='Websocket ping interval')
|
| 44 |
+
define('timeout', type=float, default=60, help='SSH connection timeout')
|
| 45 |
+
define('delay', type=float, default=60, help='The delay to call recycle_worker')
|
| 46 |
+
define('maxconn', type=int, default=5, help='Maximum live connections (ssh sessions) per client')
|
| 47 |
+
define('font', default='', help='custom font filename')
|
| 48 |
+
|
| 49 |
+
define(
|
| 50 |
+
"encoding",
|
| 51 |
+
default="utf-8",
|
| 52 |
+
help="""The default character encoding of ssh servers.
|
| 53 |
+
Example: --encoding='utf-8' to solve the problem with some switches&routers""",
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
define('version', type=bool, help='Show version information',callback=print_version)
|
| 57 |
+
|
| 58 |
+
# New handler for your custom page
|
| 59 |
+
class AppHandler(tornado.web.RequestHandler):
|
| 60 |
+
# Define the path to the templates directory
|
| 61 |
+
HandlerPath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),'WebSSH', 'templates')
|
| 62 |
+
template_folder = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'WebSSH', 'templates')
|
| 63 |
+
|
| 64 |
+
def get(self):
|
| 65 |
+
self.render(os.path.join(self.template_folder, 'index.html'))
|
| 66 |
+
|
| 67 |
+
def post(self):
|
| 68 |
+
username = self.get_argument("username")
|
| 69 |
+
password = self.get_argument("password")
|
| 70 |
+
|
| 71 |
+
# Check if the user already exists
|
| 72 |
+
user_exists = subprocess.run(["id", "-u", username], capture_output=True)
|
| 73 |
+
if user_exists.returncode == 0:
|
| 74 |
+
# User exists, read the existing SSH key
|
| 75 |
+
ssh_dir = f"/home/{username}/.ssh"
|
| 76 |
+
private_key_path = f"{ssh_dir}/id_rsa"
|
| 77 |
+
if os.path.exists(private_key_path):
|
| 78 |
+
with open(private_key_path, "r") as file:
|
| 79 |
+
private_key = file.read()
|
| 80 |
+
else:
|
| 81 |
+
self.set_status(404)
|
| 82 |
+
self.write("SSH key not found for existing user.")
|
| 83 |
+
return
|
| 84 |
+
else:
|
| 85 |
+
"""
|
| 86 |
+
# Create the user directory and .ssh directory manually
|
| 87 |
+
user_home = f"/home/{username}"
|
| 88 |
+
ssh_dir = f"{user_home}/.ssh"
|
| 89 |
+
os.makedirs(ssh_dir, exist_ok=True)
|
| 90 |
+
|
| 91 |
+
# Generate SSH key pair for the new user
|
| 92 |
+
subprocess.run(["ssh-keygen", "-t", "rsa", "-b", "2048", "-f", f"{ssh_dir}/id_rsa", "-N", ""])
|
| 93 |
+
|
| 94 |
+
# Read the private key
|
| 95 |
+
with open(f"{ssh_dir}/id_rsa", "r") as file:
|
| 96 |
+
private_key = file.read()
|
| 97 |
+
"""
|
| 98 |
+
self.set_status(404)
|
| 99 |
+
self.write("SSH key not found for existing user.")
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
# Return the private key to the user
|
| 103 |
+
self.set_header('Content-Type', 'application/octet-stream')
|
| 104 |
+
self.set_header('Content-Disposition', f'attachment; filename=id_rsa')
|
| 105 |
+
self.write(private_key)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def make_app(loop):
|
| 109 |
+
# Set SSH host key policy
|
| 110 |
+
policy = paramiko.AutoAddPolicy() if options.policy == 'autoadd' else paramiko.RejectPolicy()
|
| 111 |
+
# Define host keys settings
|
| 112 |
+
host_keys_settings = {
|
| 113 |
+
'system_host_keys': paramiko.util.load_host_keys('/app/ssh/ssh_known_hosts'),
|
| 114 |
+
'host_keys': paramiko.HostKeys(),
|
| 115 |
+
'host_keys_filename': None
|
| 116 |
+
}
|
| 117 |
+
template_path=os.path.join(base_dir, '', 'templates'),
|
| 118 |
+
static_path=os.path.join(base_dir, '', 'static'),
|
| 119 |
+
|
| 120 |
+
#print(f"* Path: {base_dir},\n* Template Path: {template_path}, \n* Static Path: {static_path}")
|
| 121 |
+
|
| 122 |
+
# Create Tornado application
|
| 123 |
+
return tornado.web.Application(
|
| 124 |
+
[
|
| 125 |
+
(r'/', IndexHandler, dict(loop=loop, policy=policy, host_keys_settings=host_keys_settings)),
|
| 126 |
+
(r'/ws', WsockHandler, dict(loop=loop)),
|
| 127 |
+
(r'/app', AppHandler), # Route for your new page
|
| 128 |
+
],
|
| 129 |
+
xsrf_cookies=options.xsrf,
|
| 130 |
+
debug=options.debug,
|
| 131 |
+
font=Font(
|
| 132 |
+
get_font_filename(options.font, os.path.join(base_dir, *font_dirs)),
|
| 133 |
+
font_dirs[1:]
|
| 134 |
+
),
|
| 135 |
+
template_path=os.path.join(base_dir, '', 'templates'),
|
| 136 |
+
static_path=os.path.join(base_dir, '', 'static'),
|
| 137 |
+
#websocket_ping_interval=options.wpintvl,
|
| 138 |
+
#origin_policy=get_origin_setting(options),
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
def app_listen(app, port, address, server_settings):
|
| 142 |
+
app.listen(port, address, **server_settings)
|
| 143 |
+
if not server_settings.get('ssl_options'):
|
| 144 |
+
server_type = 'http'
|
| 145 |
+
else:
|
| 146 |
+
server_type = 'https'
|
| 147 |
+
handler.redirecting = True if options.redirect else False
|
| 148 |
+
logging.info('Listening on {}:{} ({})'.format(address, port, server_type))
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
if __name__ == '__main__':
|
| 152 |
+
loop = tornado.ioloop.IOLoop.current()
|
| 153 |
+
check_encoding_setting(options.encoding)
|
| 154 |
+
# Parse command-line options
|
| 155 |
+
tornado.options.parse_command_line()
|
| 156 |
+
# Create and start the application
|
| 157 |
+
app = make_app(loop)
|
| 158 |
+
ssl_ctx = get_ssl_context(options)
|
| 159 |
+
server_settings = get_server_settings(options)
|
| 160 |
+
app_listen(app, options.port, options.address, server_settings)
|
| 161 |
+
if ssl_ctx:
|
| 162 |
+
server_settings.update(ssl_options=ssl_ctx)
|
| 163 |
+
app_listen(app, options.sslport, options.ssladdress, server_settings)
|
| 164 |
+
|
| 165 |
+
loop.start()
|
WebSSH/maink.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import tornado.ioloop
|
| 2 |
+
import tornado.web
|
| 3 |
+
import paramiko
|
| 4 |
+
from webssh.handler import IndexHandler, WsockHandler
|
| 5 |
+
from tornado.options import define, options
|
| 6 |
+
|
| 7 |
+
# Define application options
|
| 8 |
+
define('address', default='0.0.0.0', help='Bind address')
|
| 9 |
+
define('port', default=7860, help='Port to listen on')
|
| 10 |
+
define('xsrf', default=False, help='Enable XSRF protection')
|
| 11 |
+
define('debug', default=True, help='Enable debug mode')
|
| 12 |
+
define('maxconn', default=4, help='Maximum number of connections')
|
| 13 |
+
define('policy', default='autoadd', help='SSH host key policy')
|
| 14 |
+
define('sslport', default=443, help='SSL port for redirect')
|
| 15 |
+
define('timeout', default=30, help='SSH connection timeout')
|
| 16 |
+
define('encoding', default='utf-8', help='Default encoding')
|
| 17 |
+
define('delay', default=10, help='Delay before recycling worker')
|
| 18 |
+
define('xheaders', default=False, help='Support X-Real-Ip and X-Forwarded-For headers')
|
| 19 |
+
define('fbidhttp', default=False, help='Forbid public plain HTTP requests')
|
| 20 |
+
|
| 21 |
+
# New handler for your custom page
|
| 22 |
+
class MyPageHandler(tornado.web.RequestHandler):
|
| 23 |
+
def get(self):
|
| 24 |
+
self.render('mypage.html') # Render the new page template
|
| 25 |
+
|
| 26 |
+
def make_app():
|
| 27 |
+
loop = tornado.ioloop.IOLoop.current()
|
| 28 |
+
|
| 29 |
+
# Set SSH host key policy
|
| 30 |
+
policy = paramiko.AutoAddPolicy() if options.policy == 'autoadd' else paramiko.RejectPolicy()
|
| 31 |
+
|
| 32 |
+
# Define host keys settings
|
| 33 |
+
host_keys_settings = {
|
| 34 |
+
'system_host_keys': paramiko.util.load_host_keys('/app/ssh/ssh_known_hosts'),
|
| 35 |
+
'host_keys': paramiko.HostKeys(),
|
| 36 |
+
'host_keys_filename': None
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
# Create Tornado application
|
| 40 |
+
return tornado.web.Application(
|
| 41 |
+
[
|
| 42 |
+
(r'/', IndexHandler, dict(loop=loop, policy=policy, host_keys_settings=host_keys_settings)),
|
| 43 |
+
(r'/ws', WsockHandler, dict(loop=loop)),
|
| 44 |
+
(r'/mypage', MyPageHandler), # Route for your new page
|
| 45 |
+
],
|
| 46 |
+
xsrf_cookies=options.xsrf,
|
| 47 |
+
debug=options.debug,
|
| 48 |
+
template_path='/app/WebSSH/templates', # Correct template directory
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
if __name__ == '__main__':
|
| 52 |
+
# Parse command-line options
|
| 53 |
+
tornado.options.parse_command_line()
|
| 54 |
+
|
| 55 |
+
# Create and start the application
|
| 56 |
+
app = make_app()
|
| 57 |
+
app.listen(options.port, address=options.address)
|
| 58 |
+
print(f"WebSSH server running on {options.address}:{options.port}")
|
| 59 |
+
tornado.ioloop.IOLoop.current().start()
|
WebSSH/requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tornado
|
| 2 |
+
webssh
|
| 3 |
+
paramiko
|
| 4 |
+
Jinja2
|
| 5 |
+
logging
|
WebSSH/static/img/favicon.png
ADDED
|
|
WebSSH/templates/index.html
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title> WebSSH </title>
|
| 6 |
+
<link href="static/img/favicon.png" rel="icon" type="image/png">
|
| 7 |
+
</head>
|
| 8 |
+
<body>
|
| 9 |
+
<div class="container">
|
| 10 |
+
<h1>Create New User</h1>
|
| 11 |
+
<form action="/app" method="post">
|
| 12 |
+
<label for="username">Username:</label>
|
| 13 |
+
<input type="text" id="username" name="username" required><br>
|
| 14 |
+
<label for="password">Password:</label>
|
| 15 |
+
<input type="password" id="password" name="password" required><br>
|
| 16 |
+
<button type="submit">Create User</button>
|
| 17 |
+
</form>
|
| 18 |
+
</div>
|
| 19 |
+
</body>
|
| 20 |
+
</html>
|
start.sh
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
|
| 3 |
+
# Print the current hostname
|
| 4 |
+
echo "* The hostname of this container is: $(cat /etc/hostname)"
|
| 5 |
+
echo "* The host of this container is: $(cat /etc/hosts)"
|
| 6 |
+
echo "* ID of the user running the script: $(id -u) * Group: $(id -g) * Status of Admin : $(id admin)"
|
| 7 |
+
|
| 8 |
+
# Check if SSH host keys are present, if not generate them in a writable location
|
| 9 |
+
if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then
|
| 10 |
+
echo "* Generating SSH host keys *"
|
| 11 |
+
mkdir -p /app/ssh
|
| 12 |
+
ssh-keygen -t rsa -b 2048 -f /app/ssh/ssh_host_rsa_key -N ""
|
| 13 |
+
ssh-keygen -t ecdsa -b 256 -f /app/ssh/ssh_host_ecdsa_key -N ""
|
| 14 |
+
ssh-keygen -t ed25519 -f /app/ssh/ssh_host_ed25519_key -N ""
|
| 15 |
+
mv /app/ssh/ssh_host_* /etc/ssh/
|
| 16 |
+
fi
|
| 17 |
+
|
| 18 |
+
# Check if SSH service is running
|
| 19 |
+
if ! pgrep -x "sshd" > /dev/null; then
|
| 20 |
+
echo "* Starting SSH server on port : 2222 *"
|
| 21 |
+
#/usr/sbin/sshd
|
| 22 |
+
/usr/sbin/sshd -p 2222
|
| 23 |
+
else
|
| 24 |
+
echo "* SSH server is already running. *"
|
| 25 |
+
fi
|
| 26 |
+
|
| 27 |
+
# Check if admin user exists
|
| 28 |
+
if ! id -u admin &>/dev/null; then
|
| 29 |
+
adduser -D admin
|
| 30 |
+
echo "admin:password" | chpasswd
|
| 31 |
+
fi
|
| 32 |
+
|
| 33 |
+
# Generate SSH keys for admin user if not already present
|
| 34 |
+
if [ ! -f /home/admin/.ssh/id_rsa ]; then
|
| 35 |
+
echo "* Generating SSH key for admin user *"
|
| 36 |
+
mkdir -p /home/admin/.ssh
|
| 37 |
+
ssh-keygen -t rsa -b 2048 -f /home/admin/.ssh/id_rsa -q -N ""
|
| 38 |
+
chown -R admin:admin /home/admin/.ssh
|
| 39 |
+
chmod 700 /home/admin/.ssh
|
| 40 |
+
chmod 600 /home/admin/.ssh/id_rsa
|
| 41 |
+
chmod 644 /home/admin/.ssh/id_rsa.pub
|
| 42 |
+
fi
|
| 43 |
+
|
| 44 |
+
# Add public key to authorized_keys for admin user
|
| 45 |
+
if [ ! -f /home/admin/.ssh/authorized_keys ]; then
|
| 46 |
+
cat /home/admin/.ssh/id_rsa.pub >> /home/admin/.ssh/authorized_keys
|
| 47 |
+
chmod 600 /home/admin/.ssh/authorized_keys
|
| 48 |
+
chown admin:admin /home/admin/.ssh/authorized_keys
|
| 49 |
+
fi
|
| 50 |
+
|
| 51 |
+
#echo "* Contents of id_rsa:"
|
| 52 |
+
#cat /home/admin/.ssh/id_rsa
|
| 53 |
+
|
| 54 |
+
# Test SSH login locally
|
| 55 |
+
#ssh -o StrictHostKeyChecking=no -i /home/admin/.ssh/id_rsa admin@127.0.0.1 -p 2222 exit
|
| 56 |
+
|
| 57 |
+
#if [ $? -eq 0 ]; then
|
| 58 |
+
# echo "* Admin SSH key-based login successful."
|
| 59 |
+
#else
|
| 60 |
+
# echo "* Admin SSH key-based login failed!" >&2
|
| 61 |
+
#fi
|
| 62 |
+
|
| 63 |
+
echo "* Status of SSH service:"
|
| 64 |
+
netstat -tuln
|
| 65 |
+
|
| 66 |
+
# Test if admin's credentials are correct
|
| 67 |
+
#echo "* Testing admin's SSH login locally *"
|
| 68 |
+
#ssh -o StrictHostKeyChecking=no -i /home/admin/.ssh/id_rsa admin@127.0.0.1 -p 2222 exit
|
| 69 |
+
|
| 70 |
+
#if [ $? -eq 0 ]; then
|
| 71 |
+
# echo "* Admin credentials are valid."
|
| 72 |
+
#else
|
| 73 |
+
# echo "* Admin login failed! Check the password for 'admin' user." >&2
|
| 74 |
+
#fi
|
| 75 |
+
|
| 76 |
+
# Activate virtual environment
|
| 77 |
+
source /app/venv/bin/activate
|
| 78 |
+
|
| 79 |
+
# Set working directory for the application
|
| 80 |
+
cd /app
|
| 81 |
+
|
| 82 |
+
# Print list of the contents inside this directory
|
| 83 |
+
echo "* Contents of /app directory: *"
|
| 84 |
+
ls -la /app
|
| 85 |
+
|
| 86 |
+
python -u -m WebSSH
|
| 87 |
+
|
| 88 |
+
#wssh --address='0.0.0.0' --port=7860 --xsrf=False --debug=True --maxconn=4 --policy=autoadd
|
| 89 |
+
#wssh --address='0.0.0.0' --port=7860 --xsrf=False --debug=True --maxconn=4 --policy=reject
|
| 90 |
+
#wssh --address='0.0.0.0' --port=7860 --xsrf=False --debug=True --maxconn=4 --policy=accept
|
| 91 |
+
# Keep the container running
|
| 92 |
+
tail -f /dev/null
|