Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ API_KEY_CACHE_TTL=300
RATE_LIMIT_ENABLED=true

# Redis Configuration
# Deployment mode: standalone (default), cluster, or sentinel
REDIS_MODE=standalone
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
Expand All @@ -42,6 +44,33 @@ REDIS_MAX_CONNECTIONS=20
REDIS_SOCKET_TIMEOUT=5
REDIS_SOCKET_CONNECT_TIMEOUT=5

# Optional key prefix — useful when sharing a Redis instance across environments
# All keys will be stored as <prefix><key> (e.g. "prod:sessions:abc")
REDIS_KEY_PREFIX=

# Redis Cluster Mode (REDIS_MODE=cluster)
# Comma-separated list of host:port pairs for cluster startup nodes
# REDIS_CLUSTER_NODES=node1:6379,node2:6379,node3:6379

# Redis Sentinel Mode (REDIS_MODE=sentinel)
# Comma-separated list of host:port pairs for Sentinel instances
# REDIS_SENTINEL_NODES=sentinel1:26379,sentinel2:26379,sentinel3:26379
# REDIS_SENTINEL_MASTER=mymaster
# REDIS_SENTINEL_PASSWORD=

# Redis TLS/SSL Configuration
# Required for most managed Redis services (GCP Memorystore, AWS ElastiCache, Azure Cache)
REDIS_TLS_ENABLED=false
# REDIS_TLS_CA_CERT_FILE=/path/to/ca.crt
# REDIS_TLS_CERT_FILE=/path/to/client.crt
# REDIS_TLS_KEY_FILE=/path/to/client.key
# REDIS_TLS_INSECURE=false
# Hostname verification is off by default because managed Redis services
# and Redis Cluster mode expose node IPs that don't match cert CN/SAN.
# The CA certificate chain is still fully verified. Enable hostname
# checking when your Redis server hostnames match certificate CN/SAN.
# REDIS_TLS_CHECK_HOSTNAME=false

# MinIO/S3 Configuration
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
Expand Down Expand Up @@ -144,6 +173,37 @@ METRICS_ARCHIVE_RETENTION_DAYS=90
ENABLE_NETWORK_ISOLATION=true
ENABLE_FILESYSTEM_ISOLATION=true

# Kubernetes Execution Configuration
# Execution mode: 'agent' (default, recommended) or 'nsenter' (legacy)
# agent: Executor-agent binary runs inside the main container.
# No nsenter, no capabilities, no privilege escalation.
# Compatible with GKE Sandbox (gVisor) and restricted Pod Security Standards.
# nsenter: Sidecar uses nsenter to enter the main container's mount namespace.
# Requires shareProcessNamespace, SYS_PTRACE/SYS_ADMIN/SYS_CHROOT caps,
# and allowPrivilegeEscalation: true. NOT compatible with GKE Sandbox.
K8S_EXECUTION_MODE=agent
# K8S_EXECUTOR_PORT=9090 # Port for the executor-agent HTTP server (agent mode only)

# Sidecar image — must match the execution mode:
# agent mode: aronmuon/kubecoderun-sidecar-agent:latest (default)
# nsenter mode: aronmuon/kubecoderun-sidecar-nsenter:latest
# K8S_SIDECAR_IMAGE=aronmuon/kubecoderun-sidecar-agent:latest

# Image pull policy for execution pods (Always, IfNotPresent, Never)
# K8S_IMAGE_PULL_POLICY=Always

# Image pull secrets for private container registries (comma-separated secret names)
# These Kubernetes secrets must already exist in the execution namespace.
# Leave empty or unset if not using private registries.
# K8S_IMAGE_PULL_SECRETS=my-registry-secret,another-secret

# GKE Sandbox (gVisor) Configuration
# Requires K8S_EXECUTION_MODE=agent (nsenter is incompatible with gVisor)
# GKE_SANDBOX_ENABLED=false
# GKE_SANDBOX_RUNTIME_CLASS=gvisor
# GKE_SANDBOX_NODE_SELECTOR={}
# GKE_SANDBOX_CUSTOM_TOLERATIONS=[]

# WAN Network Access Configuration
# When enabled, execution containers can access the public internet
# but are blocked from accessing host, other containers, and private networks
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/docker-build-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ on:
type: string
default: ''
description: 'Version string to pass as VERSION build arg (for hatch-vcs)'
build_target:
required: false
type: string
default: ''
description: 'Docker build target (e.g., sidecar-agent, sidecar-nsenter). Empty = default target.'

jobs:
build:
Expand Down Expand Up @@ -74,6 +79,7 @@ jobs:
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
target: ${{ inputs.build_target || '' }}
push: true
tags: ghcr.io/${{ steps.vars.outputs.owner }}/${{ inputs.image_name }}:${{ inputs.image_tag }}-${{ steps.vars.outputs.tag }}
platforms: ${{ matrix.platform }}
Expand Down
21 changes: 19 additions & 2 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,26 @@ jobs:
uses: ./.github/workflows/docker-build-reusable.yml
secrets: inherit
with:
image_name: kubecoderun-sidecar
image_name: kubecoderun-sidecar-agent
dockerfile: docker/sidecar/Dockerfile
context: docker/sidecar
build_target: sidecar-agent
image_tag: ${{ needs.changes.outputs.image_tag }}
is_release: ${{ needs.changes.outputs.is_release == 'true' }}
version: ${{ needs.changes.outputs.version }}

sidecar-nsenter:
needs: changes
if: |
needs.changes.outputs.is_cross_repo_pr != 'true' &&
(needs.changes.outputs.sidecar == 'true' || needs.changes.outputs.force_all == 'true')
uses: ./.github/workflows/docker-build-reusable.yml
secrets: inherit
with:
image_name: kubecoderun-sidecar-nsenter
dockerfile: docker/sidecar/Dockerfile
context: docker/sidecar
build_target: sidecar-nsenter
image_tag: ${{ needs.changes.outputs.image_tag }}
is_release: ${{ needs.changes.outputs.is_release == 'true' }}
version: ${{ needs.changes.outputs.version }}
Expand Down Expand Up @@ -344,7 +361,7 @@ jobs:
uses: ./.github/workflows/docker-retag-reusable.yml
secrets: inherit
with:
image_name: kubecoderun-sidecar
image_name: kubecoderun-sidecar-agent
new_tag: ${{ needs.changes.outputs.image_tag }}
previous_tag: ${{ needs.changes.outputs.previous_tag }}

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,5 @@ config/local.py

# Hatch auto-generated version file
_version.py

.pdm-python
187 changes: 135 additions & 52 deletions docker/sidecar/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,85 +1,103 @@
# syntax=docker/dockerfile:1
# KubeCodeRun HTTP sidecar with Docker Hardened Images.
# KubeCodeRun HTTP Sidecar — Multi-target Dockerfile.
#
# Produces two distinct container images via Docker build targets:
#
# docker build --target sidecar-agent → kubecoderun-sidecar-agent (default)
# docker build --target sidecar-nsenter → kubecoderun-sidecar-nsenter
#
# sidecar-agent (default):
# - Contains the executor-agent Go binary (copied to main container via init container)
# - No nsenter, no setcap, no capabilities, no privilege escalation
# - Compatible with GKE Sandbox (gVisor) and restricted Pod Security Standards
#
# sidecar-nsenter (legacy):
# - Contains nsenter with file capabilities (setcap) for namespace entry
# - Requires shareProcessNamespace, SYS_PTRACE/SYS_ADMIN/SYS_CHROOT capabilities,
# and allowPrivilegeEscalation: true in the pod spec
# - For clusters that do not support agent mode or need legacy behavior

ARG BUILD_DATE
ARG VERSION
ARG VCS_REF

################################
# Builder stage - install Python dependencies and runtime tools
# Executor agent build stage — statically compiled Go binary.
# This binary runs in the main (language) container via init container copy,
# providing HTTP-based code execution without nsenter.
################################
FROM dhi.io/python:3.13-debian13-dev AS builder
FROM dhi.io/golang:1.26-debian13-dev AS agent-builder

WORKDIR /build
COPY executor-agent/ .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -trimpath -o /opt/executor-agent .

################################
# Python builder (common) — install Python dependencies and app code.
# Used by both agent and nsenter targets.
################################
FROM dhi.io/python:3.13-debian13-dev AS builder-common

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

ENV PIP_DISABLE_PIP_VERSION_CHECK=1

# Install runtime dependencies and set up nsenter with file capabilities
# - util-linux: provides nsenter for entering container namespaces
# - libcap2-bin: provides setcap for setting file capabilities
# Create both arch lib dirs to ensure COPY works on either architecture
# Create data directory and arch lib dirs (for COPY compatibility)
RUN mkdir -p /lib/x86_64-linux-gnu /lib/aarch64-linux-gnu && \
apt-get update && \
mkdir -p /mnt/data && chown 65532:65532 /mnt/data

WORKDIR /app

# Install Python dependencies
COPY requirements.txt /tmp/requirements.txt
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r /tmp/requirements.txt

# Copy application code
COPY main.py .

################################
# nsenter builder — extends common builder with nsenter + file capabilities.
# Only needed for the nsenter sidecar target.
################################
FROM builder-common AS builder-nsenter

# Install nsenter (util-linux) and setcap (libcap2-bin)
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
util-linux \
libcap2-bin \
&& apt-get autoremove -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /mnt/data && chown 65532:65532 /mnt/data
&& rm -rf /var/lib/apt/lists/*

# Add file capabilities to nsenter binary so non-root users can use it
# - cap_sys_ptrace: access /proc/<pid>/ns/ of other processes
# - cap_sys_admin: call setns() to enter namespaces
# - cap_sys_chroot: required for mount namespace operations
RUN setcap 'cap_sys_ptrace,cap_sys_admin,cap_sys_chroot+eip' /usr/bin/nsenter

WORKDIR /app

# Install Python dependencies
COPY requirements.txt /tmp/requirements.txt
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r /tmp/requirements.txt

# Copy application code
COPY main.py .

################################
# Final stage - minimal runtime image
# Common runtime base — shared by both final targets.
# Contains Python runtime, sidecar app, and common configuration.
################################
FROM dhi.io/python:3.13-debian13 AS final
FROM dhi.io/python:3.13-debian13 AS runtime-base

ARG BUILD_DATE
ARG VERSION
ARG VCS_REF

LABEL org.opencontainers.image.title="KubeCodeRun Sidecar" \
org.opencontainers.image.description="HTTP sidecar for executing code in Kubernetes pods via nsenter" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.revision="${VCS_REF}"

# Copy nsenter with file capabilities from builder
COPY --from=builder /usr/bin/nsenter /usr/bin/

# Copy shared libraries needed by nsenter (libselinux, libpcre2) for both architectures
# Note: Only one arch directory will have content depending on build platform
COPY --from=builder /lib/x86_64-linux-gnu /lib/x86_64-linux-gnu
COPY --from=builder /lib/aarch64-linux-gnu /lib/aarch64-linux-gnu

# Copy installed Python packages from builder
# DHI Python is installed in /opt/python, not /usr/local
COPY --from=builder /opt/python/lib/python3.13/site-packages /opt/python/lib/python3.13/site-packages
COPY --from=builder /opt/python/bin /opt/python/bin

# Copy /usr/bin/env for execution patterns, sleep for CMD
COPY --from=builder /usr/bin/env /usr/bin/sleep /usr/bin/
COPY --from=builder-common /opt/python/lib/python3.13/site-packages /opt/python/lib/python3.13/site-packages
COPY --from=builder-common /opt/python/bin /opt/python/bin

# Copy data directory with correct ownership (DHI uses UID 65532)
COPY --from=builder /mnt/data /mnt/data
COPY --from=builder-common /mnt/data /mnt/data

# Copy application code
COPY --from=builder /app /app
COPY --from=builder-common /app /app

WORKDIR /app

Expand All @@ -102,15 +120,80 @@ ENV VERSION=${VERSION} \
MAX_EXECUTION_TIME=120 \
PYTHONUNBUFFERED=1

# Kubernetes pod spec still requires:
# - shareProcessNamespace: true (so sidecar can see main container's processes)
# - securityContext.capabilities.add: ["SYS_PTRACE", "SYS_ADMIN", "SYS_CHROOT"]
# (to allow the bounding set to include these caps)
# - securityContext.allowPrivilegeEscalation: true
# (required for file capabilities to be honored)

# DHI images run as non-root (UID 65532) by default
# File capabilities on nsenter allow this user to use nsenter with required privileges

# Run sidecar
CMD ["python", "main.py"]


################################
# TARGET: sidecar-agent (default)
#
# Agent mode sidecar — the recommended execution mode.
# Contains the executor-agent Go binary for init-container distribution.
# No nsenter, no capabilities, no privilege escalation needed.
#
# Kubernetes pod spec (agent mode):
# - No shareProcessNamespace needed
# - No capabilities needed (all dropped)
# - allowPrivilegeEscalation: false for all containers
# - Init container copies /opt/executor-agent to shared volume
# - Main container runs executor-agent instead of sleep infinity
# - Compatible with GKE Sandbox (gVisor) and restricted Pod Security Standards
#
# Build: docker build --target sidecar-agent -t kubecoderun-sidecar-agent .
################################
FROM runtime-base AS sidecar-agent

ARG BUILD_DATE
ARG VERSION
ARG VCS_REF

LABEL org.opencontainers.image.title="KubeCodeRun Sidecar (Agent)" \
org.opencontainers.image.description="HTTP sidecar for executing code in Kubernetes pods via executor agent" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.revision="${VCS_REF}"

# Copy executor agent binary (distributed to main container via init container)
COPY --from=agent-builder /opt/executor-agent /opt/executor-agent

ENV EXECUTION_MODE=agent


################################
# TARGET: sidecar-nsenter (legacy)
#
# nsenter mode sidecar — for backward compatibility.
# Contains nsenter with file capabilities for namespace entry.
#
# Kubernetes pod spec (nsenter mode):
# - shareProcessNamespace: true (so sidecar can see main container's processes)
# - securityContext.capabilities.add: ["SYS_PTRACE", "SYS_ADMIN", "SYS_CHROOT"]
# - securityContext.allowPrivilegeEscalation: true
# (required for file capabilities to be honored)
#
# Build: docker build --target sidecar-nsenter -t kubecoderun-sidecar-nsenter .
################################
FROM runtime-base AS sidecar-nsenter

ARG BUILD_DATE
ARG VERSION
ARG VCS_REF

LABEL org.opencontainers.image.title="KubeCodeRun Sidecar (nsenter)" \
org.opencontainers.image.description="HTTP sidecar for executing code in Kubernetes pods via nsenter" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.revision="${VCS_REF}"

# Copy nsenter with file capabilities from nsenter builder
COPY --from=builder-nsenter /usr/bin/nsenter /usr/bin/

# Copy /usr/bin/env from builder — nsenter mode uses /usr/bin/env -i for clean execution
COPY --from=builder-common /usr/bin/env /usr/bin/env

# Copy shared libraries needed by nsenter (libselinux, libpcre2) for both architectures
# Note: Only one arch directory will have content depending on build platform
COPY --from=builder-nsenter /lib/x86_64-linux-gnu /lib/x86_64-linux-gnu
COPY --from=builder-nsenter /lib/aarch64-linux-gnu /lib/aarch64-linux-gnu

ENV EXECUTION_MODE=nsenter
3 changes: 3 additions & 0 deletions docker/sidecar/executor-agent/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module executor-agent

go 1.26
Loading