update restricted shell to force (–no-kill) option.
This commit is contained in:
+15
-12
@@ -134,6 +134,11 @@ RUN id -u socktop &>/dev/null || useradd -m -s /bin/bash socktop && \
|
||||
mkdir -p /home/socktop/.config/socktop && \
|
||||
chown -R socktop:socktop /home/socktop
|
||||
|
||||
# Unprivileged user that per-session shells run as (see docker/session-shell.sh).
|
||||
# Separate from socktop so a session cannot signal the server, the agent, or
|
||||
# anything else that matters — the kernel refuses cross-UID signals.
|
||||
RUN useradd -m -s /usr/sbin/nologin demo
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
@@ -155,7 +160,8 @@ COPY --from=node-builder /build/node_modules ./node_modules
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/init-config.sh /init-config.sh
|
||||
COPY docker/restricted-shell.sh /usr/local/bin/restricted-shell.sh
|
||||
RUN chmod +x /entrypoint.sh /init-config.sh /usr/local/bin/restricted-shell.sh
|
||||
COPY docker/session-shell.sh /usr/local/bin/session-shell.sh
|
||||
RUN chmod +x /entrypoint.sh /init-config.sh /usr/local/bin/restricted-shell.sh /usr/local/bin/session-shell.sh
|
||||
|
||||
# Expose ports
|
||||
# 8082 - webterm HTTP server
|
||||
@@ -166,18 +172,15 @@ EXPOSE 8082 3001
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8082/ || exit 1
|
||||
|
||||
# Create a wrapper script that detects if running as root or socktop user
|
||||
RUN echo '#!/bin/bash\n\
|
||||
if [ "$(id -u)" -eq 0 ]; then\n\
|
||||
# Running as root - use init-config.sh to set up and switch to socktop\n\
|
||||
exec /init-config.sh "$@"\n\
|
||||
else\n\
|
||||
# Running as socktop user - directly execute entrypoint\n\
|
||||
exec /entrypoint.sh "$@"\n\
|
||||
fi' > /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh
|
||||
# entrypoint.sh handles both cases itself: as root it prepares /home/demo,
|
||||
# runs the agent as the socktop user, and keeps webterm-server as root so it
|
||||
# can drop each session to the demo user (CAP_SETUID/CAP_SETGID); as non-root
|
||||
# it behaves as before (single-UID, no session drop).
|
||||
RUN ln -sf /entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
# Set entrypoint to the wrapper
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
# Default command - use restricted shell that only allows socktop commands
|
||||
CMD ["webterm-server", "--host", "0.0.0.0", "--port", "8082", "--command", "/usr/local/bin/restricted-shell.sh"]
|
||||
# Default command - sessions enter via session-shell.sh (per-session privilege
|
||||
# drop when root) which execs the restricted shell that only allows socktop
|
||||
CMD ["webterm-server", "--host", "0.0.0.0", "--port", "8082", "--command", "/usr/local/bin/session-shell.sh"]
|
||||
|
||||
@@ -36,6 +36,9 @@ services:
|
||||
# Optional: Set timezone
|
||||
- TZ=America/New_York
|
||||
|
||||
# Disable socktop's local process-kill feature in every session
|
||||
- SOCKTOP_NO_KILL=1
|
||||
|
||||
# Optional: Logging level
|
||||
- RUST_LOG=info
|
||||
|
||||
|
||||
+33
-1
@@ -54,12 +54,41 @@ setup_alacritty() {
|
||||
echo "Alacritty setup complete"
|
||||
}
|
||||
|
||||
# Prepare the home directory for the unprivileged `demo` user that sessions
|
||||
# run as (see docker/session-shell.sh). Sessions need read access to the
|
||||
# socktop profiles and CA certs, which are mounted under /home/socktop — copy
|
||||
# them across and rewrite the cert paths, since demo cannot traverse another
|
||||
# user's mounts reliably. Root-only: without root there is no demo user split.
|
||||
prepare_demo_home() {
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
return
|
||||
fi
|
||||
echo "Preparing /home/demo for session user..."
|
||||
mkdir -p /home/demo/.config/socktop/certs /home/demo/.config/alacritty
|
||||
if [ -f /home/socktop/.config/socktop/profiles.json ]; then
|
||||
cp /home/socktop/.config/socktop/profiles.json /home/demo/.config/socktop/profiles.json
|
||||
sed -i 's|/home/socktop/|/home/demo/|g; s|/var/lib/socktop/|/home/demo/|g' /home/demo/.config/socktop/profiles.json
|
||||
fi
|
||||
cp /home/socktop/.config/socktop/certs/*.pem /home/demo/.config/socktop/certs/ 2>/dev/null || true
|
||||
cp /home/socktop/.config/alacritty/*.toml /home/demo/.config/alacritty/ 2>/dev/null || true
|
||||
chown -R demo:demo /home/demo
|
||||
chmod -R go-w /home/demo
|
||||
echo " ✓ /home/demo ready"
|
||||
}
|
||||
|
||||
# Start socktop agent
|
||||
start_socktop_agent() {
|
||||
echo "Starting socktop-agent on port 3001..."
|
||||
|
||||
# Start socktop-agent in the background on port 3001
|
||||
# Start socktop-agent in the background on port 3001. When root, drop it
|
||||
# to the socktop user — it only reads /proc and system metrics, and a
|
||||
# separate UID keeps it out of reach of the demo session user.
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
setpriv --reuid socktop --regid socktop --clear-groups --inh-caps -all --no-new-privs \
|
||||
/usr/bin/socktop_agent --port 3001 > /tmp/socktop-agent.log 2>&1 &
|
||||
else
|
||||
/usr/bin/socktop_agent --port 3001 > /tmp/socktop-agent.log 2>&1 &
|
||||
fi
|
||||
AGENT_PID=$!
|
||||
|
||||
echo "socktop-agent started (PID: $AGENT_PID)"
|
||||
@@ -85,6 +114,9 @@ main() {
|
||||
# Set up Alacritty
|
||||
setup_alacritty
|
||||
|
||||
# Home directory for the per-session demo user
|
||||
prepare_demo_home
|
||||
|
||||
# Start socktop agent
|
||||
start_socktop_agent
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# History file
|
||||
HISTFILE="/home/socktop/.socktop_history"
|
||||
HISTFILE="${HOME:-/tmp}/.socktop_history"
|
||||
HISTSIZE=1000
|
||||
|
||||
# Load history from file
|
||||
@@ -135,7 +135,7 @@ main() {
|
||||
# Allow socktop with validated arguments only
|
||||
if [ "$cmd" = "$input" ]; then
|
||||
# No arguments, use default (local profile)
|
||||
/usr/bin/socktop -P local
|
||||
/usr/bin/socktop --no-kill -P local
|
||||
else
|
||||
# Validate and sanitize arguments to prevent command injection
|
||||
# Only allow: -P <profile_name> or ws://<url>
|
||||
@@ -144,11 +144,11 @@ main() {
|
||||
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"
|
||||
/usr/bin/socktop --no-kill -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"
|
||||
/usr/bin/socktop --no-kill "$args"
|
||||
else
|
||||
# Reject anything else as potentially dangerous
|
||||
echo -e "${RED}Error:${NC} Invalid arguments for socktop"
|
||||
|
||||
@@ -97,7 +97,7 @@ spec:
|
||||
"--port",
|
||||
"8082",
|
||||
"--command",
|
||||
"/usr/local/bin/restricted-shell.sh",
|
||||
"/usr/local/bin/session-shell.sh",
|
||||
]
|
||||
|
||||
ports:
|
||||
@@ -115,6 +115,13 @@ spec:
|
||||
value: "America/New_York"
|
||||
- name: RUST_LOG
|
||||
value: "info"
|
||||
# Disable socktop's local process-kill feature for every socktop
|
||||
# launched anywhere under this pod, regardless of command line.
|
||||
# The restricted shell also passes --no-kill, but this is the layer
|
||||
# a visitor cannot route around (no way to unset env from the
|
||||
# restricted shell).
|
||||
- name: SOCKTOP_NO_KILL
|
||||
value: "1"
|
||||
|
||||
resources:
|
||||
limits:
|
||||
@@ -158,14 +165,24 @@ spec:
|
||||
- name: socktop-home
|
||||
mountPath: /var/lib/socktop
|
||||
|
||||
# webterm-server runs as in-container root holding ONLY
|
||||
# CAP_SETUID/CAP_SETGID (everything else dropped, no privilege
|
||||
# escalation), so it can drop each websocket session to the
|
||||
# unprivileged `demo` user via session-shell.sh. The kernel then
|
||||
# refuses any signal a session aims at the server, the agent
|
||||
# (running as `socktop`), or another session's UID — the kill
|
||||
# feature's UI gating stops being the only line of defense.
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- SETUID
|
||||
- SETGID
|
||||
readOnlyRootFilesystem: false
|
||||
runAsUser: 100
|
||||
runAsGroup: 101
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
|
||||
volumes:
|
||||
- name: config
|
||||
|
||||
Reference in New Issue
Block a user