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 && \
|
mkdir -p /home/socktop/.config/socktop && \
|
||||||
chown -R socktop:socktop /home/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
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -155,7 +160,8 @@ COPY --from=node-builder /build/node_modules ./node_modules
|
|||||||
COPY docker/entrypoint.sh /entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
COPY docker/init-config.sh /init-config.sh
|
COPY docker/init-config.sh /init-config.sh
|
||||||
COPY docker/restricted-shell.sh /usr/local/bin/restricted-shell.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
|
# Expose ports
|
||||||
# 8082 - webterm HTTP server
|
# 8082 - webterm HTTP server
|
||||||
@@ -166,18 +172,15 @@ EXPOSE 8082 3001
|
|||||||
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
|
||||||
|
|
||||||
# Create a wrapper script that detects if running as root or socktop user
|
# entrypoint.sh handles both cases itself: as root it prepares /home/demo,
|
||||||
RUN echo '#!/bin/bash\n\
|
# runs the agent as the socktop user, and keeps webterm-server as root so it
|
||||||
if [ "$(id -u)" -eq 0 ]; then\n\
|
# can drop each session to the demo user (CAP_SETUID/CAP_SETGID); as non-root
|
||||||
# Running as root - use init-config.sh to set up and switch to socktop\n\
|
# it behaves as before (single-UID, no session drop).
|
||||||
exec /init-config.sh "$@"\n\
|
RUN ln -sf /entrypoint.sh /docker-entrypoint.sh
|
||||||
else\n\
|
|
||||||
# Running as socktop user - directly execute entrypoint\n\
|
|
||||||
exec /entrypoint.sh "$@"\n\
|
|
||||||
fi' > /docker-entrypoint.sh && chmod +x /docker-entrypoint.sh
|
|
||||||
|
|
||||||
# Set entrypoint to the wrapper
|
# Set entrypoint to the wrapper
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
|
||||||
# Default command - use restricted shell that only allows socktop commands
|
# Default command - sessions enter via session-shell.sh (per-session privilege
|
||||||
CMD ["webterm-server", "--host", "0.0.0.0", "--port", "8082", "--command", "/usr/local/bin/restricted-shell.sh"]
|
# 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
|
# Optional: Set timezone
|
||||||
- TZ=America/New_York
|
- TZ=America/New_York
|
||||||
|
|
||||||
|
# Disable socktop's local process-kill feature in every session
|
||||||
|
- SOCKTOP_NO_KILL=1
|
||||||
|
|
||||||
# Optional: Logging level
|
# Optional: Logging level
|
||||||
- RUST_LOG=info
|
- RUST_LOG=info
|
||||||
|
|
||||||
|
|||||||
+34
-2
@@ -54,12 +54,41 @@ setup_alacritty() {
|
|||||||
echo "Alacritty setup complete"
|
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
|
||||||
start_socktop_agent() {
|
start_socktop_agent() {
|
||||||
echo "Starting socktop-agent on port 3001..."
|
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
|
||||||
/usr/bin/socktop_agent --port 3001 > /tmp/socktop-agent.log 2>&1 &
|
# 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=$!
|
AGENT_PID=$!
|
||||||
|
|
||||||
echo "socktop-agent started (PID: $AGENT_PID)"
|
echo "socktop-agent started (PID: $AGENT_PID)"
|
||||||
@@ -85,6 +114,9 @@ main() {
|
|||||||
# Set up Alacritty
|
# Set up Alacritty
|
||||||
setup_alacritty
|
setup_alacritty
|
||||||
|
|
||||||
|
# Home directory for the per-session demo user
|
||||||
|
prepare_demo_home
|
||||||
|
|
||||||
# Start socktop agent
|
# Start socktop agent
|
||||||
start_socktop_agent
|
start_socktop_agent
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ CYAN='\033[0;36m'
|
|||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
# History file
|
# History file
|
||||||
HISTFILE="/home/socktop/.socktop_history"
|
HISTFILE="${HOME:-/tmp}/.socktop_history"
|
||||||
HISTSIZE=1000
|
HISTSIZE=1000
|
||||||
|
|
||||||
# Load history from file
|
# Load history from file
|
||||||
@@ -135,7 +135,7 @@ main() {
|
|||||||
# Allow socktop with validated arguments only
|
# 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 --no-kill -P local
|
||||||
else
|
else
|
||||||
# Validate and sanitize arguments to prevent command injection
|
# Validate and sanitize arguments to prevent command injection
|
||||||
# Only allow: -P <profile_name> or ws://<url>
|
# Only allow: -P <profile_name> or ws://<url>
|
||||||
@@ -144,11 +144,11 @@ main() {
|
|||||||
if [[ "$args" =~ ^-P[[:space:]]+[a-zA-Z0-9_-]+$ ]]; then
|
if [[ "$args" =~ ^-P[[:space:]]+[a-zA-Z0-9_-]+$ ]]; then
|
||||||
# Extract profile name and validate it
|
# Extract profile name and validate it
|
||||||
profile=$(echo "$args" | sed 's/-P[[:space:]]\+//')
|
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://)
|
# Check for websocket URL (ws:// or wss://)
|
||||||
elif [[ "$args" =~ ^wss?://[a-zA-Z0-9\.\:/_-]+$ ]]; then
|
elif [[ "$args" =~ ^wss?://[a-zA-Z0-9\.\:/_-]+$ ]]; then
|
||||||
# Validate websocket URL format
|
# Validate websocket URL format
|
||||||
/usr/bin/socktop "$args"
|
/usr/bin/socktop --no-kill "$args"
|
||||||
else
|
else
|
||||||
# Reject anything else as potentially dangerous
|
# Reject anything else as potentially dangerous
|
||||||
echo -e "${RED}Error:${NC} Invalid arguments for socktop"
|
echo -e "${RED}Error:${NC} Invalid arguments for socktop"
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ spec:
|
|||||||
"--port",
|
"--port",
|
||||||
"8082",
|
"8082",
|
||||||
"--command",
|
"--command",
|
||||||
"/usr/local/bin/restricted-shell.sh",
|
"/usr/local/bin/session-shell.sh",
|
||||||
]
|
]
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
@@ -115,6 +115,13 @@ spec:
|
|||||||
value: "America/New_York"
|
value: "America/New_York"
|
||||||
- name: RUST_LOG
|
- name: RUST_LOG
|
||||||
value: "info"
|
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:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
@@ -158,14 +165,24 @@ spec:
|
|||||||
- name: socktop-home
|
- name: socktop-home
|
||||||
mountPath: /var/lib/socktop
|
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:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop:
|
drop:
|
||||||
- ALL
|
- ALL
|
||||||
|
add:
|
||||||
|
- SETUID
|
||||||
|
- SETGID
|
||||||
readOnlyRootFilesystem: false
|
readOnlyRootFilesystem: false
|
||||||
runAsUser: 100
|
runAsUser: 0
|
||||||
runAsGroup: 101
|
runAsGroup: 0
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- name: config
|
- name: config
|
||||||
|
|||||||
Reference in New Issue
Block a user