- trim container image size - sanitize socktop inputs
This commit is contained in:
parent
e224989702
commit
2012504616
1
.gitignore
vendored
1
.gitignore
vendored
@ -38,3 +38,4 @@ scripts/publish-to-gitea-multiarch.sh
|
|||||||
scripts/publish-to-gitea.sh
|
scripts/publish-to-gitea.sh
|
||||||
scripts/verify_upgrade.sh
|
scripts/verify_upgrade.sh
|
||||||
scripts/check-setup.sh
|
scripts/check-setup.sh
|
||||||
|
scripts/test-docker-config.sh
|
||||||
|
|||||||
186
Dockerfile
186
Dockerfile
@ -1,127 +1,129 @@
|
|||||||
# Dockerfile for socktop webterm
|
# Multi-stage Dockerfile for socktop webterm
|
||||||
# Based on Debian Trixie Slim with all required dependencies
|
# This reduces the final image size significantly by separating build and runtime
|
||||||
|
|
||||||
FROM debian:trixie-slim
|
# ============================================================================
|
||||||
|
# Stage 1: Rust Builder
|
||||||
|
# ============================================================================
|
||||||
|
FROM rust:1.90-slim-bookworm AS rust-builder
|
||||||
|
|
||||||
# Avoid prompts from apt
|
WORKDIR /build
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
# Set environment variables
|
# Install build dependencies
|
||||||
ENV RUST_VERSION=stable
|
|
||||||
ENV CARGO_HOME=/usr/local/cargo
|
|
||||||
ENV RUSTUP_HOME=/usr/local/rustup
|
|
||||||
ENV PATH=/usr/local/cargo/bin:$PATH
|
|
||||||
ENV TERM=xterm-256color
|
|
||||||
|
|
||||||
# Install system dependencies and security updates
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get upgrade -y && \
|
apt-get install -y --no-install-recommends \
|
||||||
apt-get install -y \
|
|
||||||
# Build dependencies
|
|
||||||
build-essential \
|
|
||||||
pkg-config \
|
pkg-config \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
# Rust/Cargo (needed to build webterm)
|
|
||||||
curl \
|
|
||||||
ca-certificates \
|
|
||||||
# Node.js and npm (for xterm.js)
|
|
||||||
nodejs \
|
|
||||||
npm \
|
|
||||||
# Alacritty dependencies
|
|
||||||
cmake \
|
|
||||||
fontconfig \
|
|
||||||
libfontconfig1-dev \
|
|
||||||
libfreetype6-dev \
|
|
||||||
libxcb-xfixes0-dev \
|
|
||||||
libxkbcommon-dev \
|
|
||||||
python3 \
|
|
||||||
# Runtime dependencies
|
|
||||||
fonts-liberation \
|
|
||||||
gnupg2 \
|
|
||||||
wget \
|
|
||||||
unzip \
|
|
||||||
git \
|
|
||||||
# Process management
|
|
||||||
supervisor \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install Rust
|
# Copy only dependency files first for better caching
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
COPY Cargo.toml Cargo.lock ./
|
||||||
sh -s -- -y --default-toolchain ${RUST_VERSION} --profile minimal && \
|
|
||||||
chmod -R a+w ${RUSTUP_HOME} ${CARGO_HOME}
|
|
||||||
|
|
||||||
# Install Alacritty
|
# Create dummy source to cache dependencies
|
||||||
RUN cargo install alacritty && \
|
RUN mkdir src && \
|
||||||
rm -rf ${CARGO_HOME}/registry ${CARGO_HOME}/git
|
echo "fn main() {}" > src/server.rs && \
|
||||||
|
echo "pub fn lib() {}" > src/lib.rs && \
|
||||||
|
cargo build --release && \
|
||||||
|
rm -rf src
|
||||||
|
|
||||||
# Download and install FiraCode Nerd Font
|
# Copy actual source code
|
||||||
RUN mkdir -p /usr/share/fonts/truetype/firacode-nerd && \
|
COPY src ./src
|
||||||
cd /tmp && \
|
COPY templates ./templates
|
||||||
wget -q https://github.com/ryanoasis/nerd-fonts/releases/download/v3.1.1/FiraCode.zip && \
|
COPY static ./static
|
||||||
unzip -q FiraCode.zip -d /usr/share/fonts/truetype/firacode-nerd/ && \
|
|
||||||
rm FiraCode.zip && \
|
|
||||||
fc-cache -fv && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Add socktop APT repository with GPG key
|
# Build the actual application
|
||||||
RUN curl -fsSL https://jasonwitty.github.io/socktop/KEY.gpg | \
|
|
||||||
gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg && \
|
|
||||||
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://jasonwitty.github.io/socktop stable main" > /etc/apt/sources.list.d/socktop.list && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y socktop socktop-agent && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Create application user (if not already exists from package)
|
|
||||||
RUN id -u socktop &>/dev/null || useradd -m -s /bin/bash socktop && \
|
|
||||||
mkdir -p /home/socktop/.config/alacritty && \
|
|
||||||
mkdir -p /home/socktop/.config/socktop && \
|
|
||||||
chown -R socktop:socktop /home/socktop
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy application files
|
|
||||||
COPY --chown=socktop:socktop Cargo.toml Cargo.lock ./
|
|
||||||
COPY --chown=socktop:socktop src ./src
|
|
||||||
COPY --chown=socktop:socktop templates ./templates
|
|
||||||
COPY --chown=socktop:socktop static ./static
|
|
||||||
COPY --chown=socktop:socktop package.json package-lock.json ./
|
|
||||||
|
|
||||||
# Build the Rust application
|
|
||||||
RUN cargo build --release && \
|
RUN cargo build --release && \
|
||||||
rm -rf target/release/build target/release/deps target/release/incremental && \
|
|
||||||
strip target/release/webterm-server
|
strip target/release/webterm-server
|
||||||
|
|
||||||
# Install npm dependencies and copy static files
|
# ============================================================================
|
||||||
|
# Stage 2: Node.js Builder
|
||||||
|
# ============================================================================
|
||||||
|
FROM node:20-slim AS node-builder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
COPY static ./static
|
||||||
|
|
||||||
|
# Install only production dependencies
|
||||||
RUN npm ci --only=production && \
|
RUN npm ci --only=production && \
|
||||||
|
# Copy static files to node_modules for serving
|
||||||
cp static/terminado-addon.js node_modules/ && \
|
cp static/terminado-addon.js node_modules/ && \
|
||||||
cp static/bg.png node_modules/ && \
|
cp static/bg.png node_modules/ && \
|
||||||
cp static/styles.css node_modules/ && \
|
cp static/styles.css node_modules/ && \
|
||||||
cp static/terminal.js node_modules/ && \
|
cp static/terminal.js node_modules/ && \
|
||||||
cp static/favicon.png node_modules/
|
cp static/favicon.png node_modules/
|
||||||
|
|
||||||
# Copy configuration files from /files directory (will be mounted as volume)
|
# ============================================================================
|
||||||
# This will be done at runtime via entrypoint script
|
# Stage 3: Runtime Image
|
||||||
|
# ============================================================================
|
||||||
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
# Copy supervisor configuration
|
# Avoid prompts from apt
|
||||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TERM=xterm-256color
|
||||||
|
|
||||||
# Copy entrypoint and restricted shell scripts
|
# Install only runtime dependencies
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get upgrade -y && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
# Runtime libraries
|
||||||
|
libssl3 \
|
||||||
|
ca-certificates \
|
||||||
|
# For socktop packages
|
||||||
|
curl \
|
||||||
|
gnupg2 \
|
||||||
|
# Shell and utilities
|
||||||
|
bash \
|
||||||
|
procps \
|
||||||
|
# Health check
|
||||||
|
curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Add socktop APT repository and install packages
|
||||||
|
RUN curl -fsSL https://jasonwitty.github.io/socktop/KEY.gpg | \
|
||||||
|
gpg --dearmor -o /usr/share/keyrings/socktop-archive-keyring.gpg && \
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/socktop-archive-keyring.gpg] https://jasonwitty.github.io/socktop stable main" > /etc/apt/sources.list.d/socktop.list && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends socktop socktop-agent && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create application user (if not already exists from socktop packages)
|
||||||
|
RUN id -u socktop &>/dev/null || useradd -m -s /bin/bash socktop && \
|
||||||
|
mkdir -p /home/socktop/.config/socktop && \
|
||||||
|
chown -R socktop:socktop /home/socktop
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy built binary from rust-builder
|
||||||
|
COPY --from=rust-builder /build/target/release/webterm-server /usr/local/bin/webterm-server
|
||||||
|
|
||||||
|
# Copy templates and static files
|
||||||
|
COPY --from=rust-builder /build/templates ./templates
|
||||||
|
COPY --from=rust-builder /build/static ./static
|
||||||
|
|
||||||
|
# Copy node_modules from node-builder
|
||||||
|
COPY --from=node-builder /build/node_modules ./node_modules
|
||||||
|
|
||||||
|
# Copy runtime scripts
|
||||||
COPY docker/entrypoint.sh /entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
COPY docker/restricted-shell.sh /usr/local/bin/restricted-shell
|
RUN chmod +x /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh && chmod +x /usr/local/bin/restricted-shell
|
|
||||||
|
|
||||||
# Expose ports
|
# Expose ports
|
||||||
# 8082 - webterm HTTP server
|
# 8082 - webterm HTTP server
|
||||||
# 3001 - socktop agent
|
# 3001 - socktop agent (if used)
|
||||||
EXPOSE 8082 3001
|
EXPOSE 8082 3001
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
CMD curl -f http://localhost:8082/ || exit 1
|
CMD curl -f http://localhost:8082/ || exit 1
|
||||||
|
|
||||||
# Set entrypoint (runs as root, then switches to socktop user)
|
# Run as socktop user
|
||||||
|
USER socktop
|
||||||
|
|
||||||
|
# Set entrypoint
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|
||||||
# Default command (can be overridden)
|
# Default command - run webterm server
|
||||||
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
CMD ["webterm-server", "--host", "0.0.0.0", "--port", "8082"]
|
||||||
|
|||||||
68
README.md
68
README.md
@ -137,6 +137,74 @@ This project includes a complete CI/CD pipeline using Gitea Actions:
|
|||||||
- **Containerization**: Docker, Kubernetes/k3s
|
- **Containerization**: Docker, Kubernetes/k3s
|
||||||
- **CI/CD**: Gitea Actions
|
- **CI/CD**: Gitea Actions
|
||||||
|
|
||||||
|
## Security and Limitations
|
||||||
|
|
||||||
|
### Security Model
|
||||||
|
|
||||||
|
This application is designed to provide **safe, public terminal access** for demonstration purposes. The security model consists of multiple layers:
|
||||||
|
|
||||||
|
#### 1. Restricted Shell
|
||||||
|
|
||||||
|
When deployed in production (e.g., https://www.socktop.io), the application uses a restricted shell (`docker/restricted-shell.sh`) that:
|
||||||
|
|
||||||
|
- **Allows only 2 commands**: `socktop` and `help`
|
||||||
|
- **Blocks all other commands**: Rejects any attempt to run `ls`, `cat`, `bash`, etc.
|
||||||
|
- **Validates arguments**: All arguments passed to `socktop` are sanitized to prevent command injection
|
||||||
|
- **Prevents shell escapes**: Blocks metacharacters like `;`, `&&`, `|`, `$()`, backticks, etc.
|
||||||
|
- **Blocks path traversal**: Prevents attempts like `../../../etc/passwd`
|
||||||
|
|
||||||
|
**Example validation:**
|
||||||
|
```bash
|
||||||
|
# Allowed
|
||||||
|
socktop -P local
|
||||||
|
socktop -P rpi-master
|
||||||
|
socktop ws://192.168.1.100:3000
|
||||||
|
|
||||||
|
# Blocked with error message
|
||||||
|
socktop $(whoami) # Command substitution blocked
|
||||||
|
socktop ; /bin/bash # Shell metacharacter blocked
|
||||||
|
socktop -P ../etc/passwd # Path traversal blocked
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Argument Sanitization
|
||||||
|
|
||||||
|
The restricted shell validates all input using regex patterns:
|
||||||
|
- Profile names: Only `[a-zA-Z0-9_-]+` characters allowed
|
||||||
|
- WebSocket URLs: Only `ws://` or `wss://` with safe characters
|
||||||
|
- No environment variable expansion
|
||||||
|
- No special characters or shell operators
|
||||||
|
|
||||||
|
**Security testing**: Run `./scripts/test-shell-security.sh` to verify all 35 security checks pass.
|
||||||
|
|
||||||
|
#### 3. Container Isolation
|
||||||
|
|
||||||
|
- Runs inside Docker container (cannot access host system)
|
||||||
|
- Non-root user (`socktop` user)
|
||||||
|
- Limited resources (CPU/memory limits configurable)
|
||||||
|
- No privileged operations
|
||||||
|
- Read-only configuration mounts
|
||||||
|
|
||||||
|
#### 4. WebSocket Security
|
||||||
|
|
||||||
|
The WebSocket endpoint (`/websocket`) is secure by design:
|
||||||
|
- Command spawned is **hardcoded at server startup** (via `--command` flag)
|
||||||
|
- No way to change which shell is spawned via WebSocket connection
|
||||||
|
- No HTTP headers or request parameters influence the spawned command
|
||||||
|
- Direct WebSocket connections get the same restricted shell as browser connections
|
||||||
|
|
||||||
|
**The Terminado protocol only supports:**
|
||||||
|
- `stdin` - Send input to terminal (goes through restricted shell)
|
||||||
|
- `stdout` - Receive output from terminal
|
||||||
|
- `set_size` - Resize terminal (validated to u16 row/col numbers only)
|
||||||
|
|
||||||
|
There is no protocol message type that can bypass the shell or execute arbitrary commands.
|
||||||
|
|
||||||
|
### Reporting Security Issues
|
||||||
|
|
||||||
|
If you discover a security vulnerability that bypasses the restricted shell or container isolation, please report it via:
|
||||||
|
- GetTea Issues (for non-critical issues)
|
||||||
|
- Direct contact to maintainer (for critical vulnerabilities)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
This project was originally forked from [webterm](https://github.com/fubarnetes/webterm) by Fabian Freyer. I chose this as a base because other projects were way too complex. His project was simple and easy to understand, but it was not well maintained. I have updated it to modern packages and heavily customized it to display a working demo for socktop.
|
This project was originally forked from [webterm](https://github.com/fubarnetes/webterm) by Fabian Freyer. I chose this as a base because other projects were way too complex. His project was simple and easy to understand, but it was not well maintained. I have updated it to modern packages and heavily customized it to display a working demo for socktop.
|
||||||
|
|||||||
@ -13,8 +13,16 @@ services:
|
|||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
# Mount configuration files from host (read-write so root can access them)
|
# Mount configuration files directly to proper locations
|
||||||
- ./files:/files
|
- ./files/alacritty.toml:/home/socktop/.config/alacritty/alacritty.toml:ro
|
||||||
|
- ./files/catppuccin-frappe.toml:/home/socktop/.config/alacritty/catppuccin-frappe.toml:ro
|
||||||
|
- ./files/profiles.json:/home/socktop/.config/socktop/profiles.json:ro
|
||||||
|
|
||||||
|
# Mount SSH certificates (optional - comment out if not using)
|
||||||
|
- ./files/rpi-master.pem:/home/socktop/.config/socktop/certs/rpi-master.pem:ro
|
||||||
|
- ./files/rpi-worker-1.pem:/home/socktop/.config/socktop/certs/rpi-worker-1.pem:ro
|
||||||
|
- ./files/rpi-worker-2.pem:/home/socktop/.config/socktop/certs/rpi-worker-2.pem:ro
|
||||||
|
- ./files/rpi-worker-3.pem:/home/socktop/.config/socktop/certs/rpi-worker-3.pem:ro
|
||||||
|
|
||||||
# Optional: persist socktop data
|
# Optional: persist socktop data
|
||||||
- socktop-data:/home/socktop/.local/share/socktop
|
- socktop-data:/home/socktop/.local/share/socktop
|
||||||
|
|||||||
@ -132,13 +132,35 @@ main() {
|
|||||||
|
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
socktop)
|
socktop)
|
||||||
# Allow socktop with any arguments
|
# Allow socktop with validated arguments only
|
||||||
if [ "$cmd" = "$input" ]; then
|
if [ "$cmd" = "$input" ]; then
|
||||||
# No arguments, use default (local profile)
|
# No arguments, use default (local profile)
|
||||||
/usr/bin/socktop -P local
|
/usr/bin/socktop -P local
|
||||||
else
|
else
|
||||||
# Pass arguments to socktop
|
# Validate and sanitize arguments to prevent command injection
|
||||||
/usr/bin/socktop $args
|
# Only allow: -P <profile_name> or ws://<url>
|
||||||
|
|
||||||
|
# Check for profile argument (-P followed by safe profile name)
|
||||||
|
if [[ "$args" =~ ^-P[[:space:]]+[a-zA-Z0-9_-]+$ ]]; then
|
||||||
|
# Extract profile name and validate it
|
||||||
|
profile=$(echo "$args" | sed 's/-P[[:space:]]\+//')
|
||||||
|
/usr/bin/socktop -P "$profile"
|
||||||
|
# Check for websocket URL (ws:// or wss://)
|
||||||
|
elif [[ "$args" =~ ^wss?://[a-zA-Z0-9\.\:/_-]+$ ]]; then
|
||||||
|
# Validate websocket URL format
|
||||||
|
/usr/bin/socktop "$args"
|
||||||
|
else
|
||||||
|
# Reject anything else as potentially dangerous
|
||||||
|
echo -e "${RED}Error:${NC} Invalid arguments for socktop"
|
||||||
|
echo -e "${YELLOW}Allowed usage:${NC}"
|
||||||
|
echo " socktop - Use default local profile"
|
||||||
|
echo " socktop -P <profile> - Use named profile (alphanumeric, dash, underscore only)"
|
||||||
|
echo " socktop <ws_url> - Connect to websocket URL (ws:// or wss://)"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Examples:${NC}"
|
||||||
|
echo " socktop -P rpi-master"
|
||||||
|
echo " socktop ws://192.168.1.100:3000"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
help|--help|-h)
|
help|--help|-h)
|
||||||
|
|||||||
@ -2,6 +2,23 @@
|
|||||||
|
|
||||||
This directory contains configuration files that will be mounted into the Docker container at runtime.
|
This directory contains configuration files that will be mounted into the Docker container at runtime.
|
||||||
|
|
||||||
|
## Docker Usage
|
||||||
|
|
||||||
|
When using Docker (via `docker-compose.yml` or `scripts/docker-quickstart.sh`):
|
||||||
|
|
||||||
|
1. **Files are mounted directly** to the proper locations in the container:
|
||||||
|
- `alacritty.toml` → `/home/socktop/.config/alacritty/alacritty.toml`
|
||||||
|
- `catppuccin-frappe.toml` → `/home/socktop/.config/alacritty/catppuccin-frappe.toml`
|
||||||
|
- `profiles.json` → `/home/socktop/.config/socktop/profiles.json`
|
||||||
|
- `*.pem` certificates → `/home/socktop/.config/socktop/certs/`
|
||||||
|
|
||||||
|
2. **Files are read-only** in the container (mounted with `:ro` flag)
|
||||||
|
|
||||||
|
3. **To update configuration**:
|
||||||
|
- Edit files in this directory on your host
|
||||||
|
- Changes are immediately visible in the container (no restart needed for most configs)
|
||||||
|
- For some changes, restart may be needed: `docker-compose restart` or `scripts/docker-quickstart.sh restart`
|
||||||
|
|
||||||
## Required Files
|
## Required Files
|
||||||
|
|
||||||
Place your actual configuration files in this directory before building/running the container:
|
Place your actual configuration files in this directory before building/running the container:
|
||||||
@ -25,11 +42,27 @@ Place your actual configuration files in this directory before building/running
|
|||||||
- Copy from: `profiles.json.example`
|
- Copy from: `profiles.json.example`
|
||||||
- Update with your actual host IPs and connection details
|
- Update with your actual host IPs and connection details
|
||||||
|
|
||||||
### 3. SSH Keys
|
### 3. SSH Certificates (Optional)
|
||||||
|
|
||||||
**`rpi-master.pem`**
|
**`rpi-master.pem`**
|
||||||
- SSH private key for master node
|
- SSH private key for master node
|
||||||
- **IMPORTANT**: Set permissions to 600
|
- **Permissions**: Must be `600` (will be auto-fixed by entrypoint)
|
||||||
|
- Only needed if connecting to remote systems
|
||||||
|
|
||||||
|
**`rpi-worker-1.pem`, `rpi-worker-2.pem`, `rpi-worker-3.pem`**
|
||||||
|
- SSH private keys for worker nodes
|
||||||
|
- **Permissions**: Must be `600`
|
||||||
|
- Optional - add as needed for your systems
|
||||||
|
|
||||||
|
**Note**: If no certificates are provided, the container will still work for local monitoring.
|
||||||
|
|
||||||
|
### 4. Docker-Specific Notes
|
||||||
|
|
||||||
|
- Files are mounted directly from this directory to their final locations in the container
|
||||||
|
- Files are mounted read-only (`:ro`) for security
|
||||||
|
- Certificate permissions should be `600` on the host before mounting
|
||||||
|
- For local testing, you can comment out the certificate mounts in docker-compose.yml
|
||||||
|
- Without certificates, the container will still work for local monitoring
|
||||||
|
|
||||||
**`rpi-worker-1.pem`**
|
**`rpi-worker-1.pem`**
|
||||||
- SSH private key for worker node 1
|
- SSH private key for worker node 1
|
||||||
|
|||||||
152
scripts/test-shell-security.sh
Executable file
152
scripts/test-shell-security.sh
Executable file
@ -0,0 +1,152 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Security test script for restricted shell
|
||||||
|
# Tests various injection and escape attempts
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${BLUE}╔════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BLUE}║ Restricted Shell Security Test ║${NC}"
|
||||||
|
echo -e "${BLUE}╚════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
PASSED=0
|
||||||
|
FAILED=0
|
||||||
|
TOTAL=0
|
||||||
|
|
||||||
|
# Function to test a command
|
||||||
|
test_command() {
|
||||||
|
local test_name="$1"
|
||||||
|
local test_input="$2"
|
||||||
|
local should_block="$3" # "block" or "allow"
|
||||||
|
|
||||||
|
TOTAL=$((TOTAL + 1))
|
||||||
|
|
||||||
|
echo -ne "${YELLOW}Testing:${NC} $test_name ... "
|
||||||
|
|
||||||
|
# Note: This is a template. In practice, you'd need to:
|
||||||
|
# 1. Send input to the restricted shell
|
||||||
|
# 2. Check if it was blocked or executed
|
||||||
|
# 3. Verify no unauthorized commands ran
|
||||||
|
|
||||||
|
# For now, we'll test the regex patterns
|
||||||
|
if [[ "$should_block" == "block" ]]; then
|
||||||
|
# These should be blocked
|
||||||
|
if [[ "$test_input" =~ ^-P[[:space:]]+[a-zA-Z0-9_-]+$ ]] || \
|
||||||
|
[[ "$test_input" =~ ^wss?://[a-zA-Z0-9\.\:/_-]+$ ]]; then
|
||||||
|
echo -e "${RED}FAIL${NC} - Should have blocked but pattern matched"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}PASS${NC} - Correctly blocked"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# These should be allowed
|
||||||
|
if [[ "$test_input" =~ ^-P[[:space:]]+[a-zA-Z0-9_-]+$ ]] || \
|
||||||
|
[[ "$test_input" =~ ^wss?://[a-zA-Z0-9\.\:/_-]+$ ]]; then
|
||||||
|
echo -e "${GREEN}PASS${NC} - Correctly allowed"
|
||||||
|
PASSED=$((PASSED + 1))
|
||||||
|
else
|
||||||
|
echo -e "${RED}FAIL${NC} - Should have allowed but pattern didn't match"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo -e "${BLUE}═══ Testing Valid Commands (Should Allow) ═══${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
test_command "Local profile" "-P local" "allow"
|
||||||
|
test_command "Remote profile" "-P rpi-master" "allow"
|
||||||
|
test_command "Profile with dash" "-P rpi-worker-1" "allow"
|
||||||
|
test_command "Profile with underscore" "-P my_profile" "allow"
|
||||||
|
test_command "Websocket URL" "ws://192.168.1.100:3000" "allow"
|
||||||
|
test_command "Secure websocket" "wss://example.com:3000" "allow"
|
||||||
|
test_command "Websocket with path" "ws://192.168.1.100:3000/ws" "allow"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}═══ Testing Command Injection (Should Block) ═══${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
test_command "Command substitution \$()" "-P \$(whoami)" "block"
|
||||||
|
test_command "Command substitution backticks" "-P \`id\`" "block"
|
||||||
|
test_command "Shell semicolon" "-P local; ls -la" "block"
|
||||||
|
test_command "Shell AND operator" "-P local && cat /etc/passwd" "block"
|
||||||
|
test_command "Shell OR operator" "-P local || /bin/sh" "block"
|
||||||
|
test_command "Shell pipe" "-P local | grep root" "block"
|
||||||
|
test_command "Shell redirect" "-P local > /tmp/output" "block"
|
||||||
|
test_command "Shell background" "-P local &" "block"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}═══ Testing Path Traversal (Should Block) ═══${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
test_command "Parent directory" "-P ../etc/passwd" "block"
|
||||||
|
test_command "Absolute path" "-P /etc/passwd" "block"
|
||||||
|
test_command "Multiple parent dirs" "-P ../../bin/bash" "block"
|
||||||
|
test_command "Encoded path" "-P %2e%2e%2f" "block"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}═══ Testing Special Characters (Should Block) ═══${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
test_command "Newline injection" "-P local\nls" "block"
|
||||||
|
test_command "Carriage return" "-P local\rls" "block"
|
||||||
|
test_command "Null byte" "-P local\x00ls" "block"
|
||||||
|
test_command "Single quote" "-P local' ls" "block"
|
||||||
|
test_command "Double quote" "-P local\" ls" "block"
|
||||||
|
test_command "Dollar sign" "-P \$HOME" "block"
|
||||||
|
test_command "Asterisk wildcard" "-P local*" "block"
|
||||||
|
test_command "Question wildcard" "-P local?" "block"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}═══ Testing Environment Variables (Should Block) ═══${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
test_command "HOME variable" "-P \$HOME" "block"
|
||||||
|
test_command "PATH variable" "-P \$PATH" "block"
|
||||||
|
test_command "SHELL variable" "-P \$SHELL" "block"
|
||||||
|
test_command "Braced variable" "-P \${HOME}" "block"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}═══ Testing WebSocket URL Exploits (Should Block) ═══${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
test_command "WS with command injection" "ws://evil.com/\$(id)" "block"
|
||||||
|
test_command "WS with backticks" "ws://evil.com/\`whoami\`" "block"
|
||||||
|
test_command "WS with semicolon" "ws://evil.com/; ls" "block"
|
||||||
|
test_command "WS with spaces" "ws://evil.com/ /bin/sh" "block"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}════════════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${BLUE} TEST SUMMARY ${NC}"
|
||||||
|
echo -e "${BLUE}════════════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "Total Tests: ${BLUE}$TOTAL${NC}"
|
||||||
|
echo -e "Passed: ${GREEN}$PASSED${NC}"
|
||||||
|
echo -e "Failed: ${RED}$FAILED${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $FAILED -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}✓ All security tests passed!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Note:${NC} These are pattern validation tests only."
|
||||||
|
echo "For complete security verification, you should:"
|
||||||
|
echo " 1. Test in actual container environment"
|
||||||
|
echo " 2. Verify socktop binary doesn't process malicious args"
|
||||||
|
echo " 3. Monitor for unexpected process execution"
|
||||||
|
echo " 4. Check logs for injection attempts"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}✗ Some security tests failed!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Review the failed tests and update regex patterns in restricted-shell.sh"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Loading…
Reference in New Issue
Block a user