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
4 changes: 4 additions & 0 deletions charts/durpdeploy/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ spec:
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
env:
# This pod intentionally has no mount capabilities. Refuse to run
# deployment scripts rather than exposing pod credentials.
- name: DURPDEPLOY_REQUIRE_SANDBOX
value: "1"
# --- Database (Postgres DSN) ---
# postgres://user:password@host:port/db?sslmode=...
# ponytail: assembled inline rather than via a Secret+envFrom
Expand Down
3 changes: 3 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ services:
# Don't publish it to the host.
environment:
DURPDEPLOY_DB: /data/durpdeploy.db
# Fail closed: this profile does not grant the privileges needed for
# chroot isolation, so deployment scripts must never run beside the DB.
DURPDEPLOY_REQUIRE_SANDBOX: "1"
# Optional. The mounted secret file at /etc/durpdeploy/key is the
# primary path; secret/secret.go reads file first, env fallback. Set
# this only when delivering the key via env (k8s, swarm, etc.).
Expand Down
6 changes: 6 additions & 0 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ func (r *DeploymentRunner) runStepAttempt(
// mounts aren't permitted, e.g. local dev without CAP_SYS_ADMIN.
chrooted := r.sandbox.setupChroot(tmpDir)
defer r.sandbox.teardownChroot(tmpDir)
if os.Getenv("DURPDEPLOY_REQUIRE_SANDBOX") == "1" &&
!r.sandbox.isolated(chrooted) {
return fmt.Errorf(
"runner sandbox is required but credential and chroot isolation are unavailable",
)
}

var cmd *exec.Cmd
if chrooted {
Expand Down
4 changes: 4 additions & 0 deletions internal/runner/sandbox_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ type Sandbox struct {
chrootWarned atomic.Bool
}

func (s *Sandbox) isolated(chrooted bool) bool {
return s.enabled && chrooted
}

// newSandbox looks up the durpdeploy-runner account. If it does not exist
// (e.g. local dev/CI where docs/deploy.md Step 5 was never run), the
// sandbox is disabled and steps keep running as the server's own user —
Expand Down
2 changes: 2 additions & 0 deletions internal/runner/sandbox_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type Sandbox struct{}

func newSandbox() *Sandbox { return &Sandbox{} }

func (s *Sandbox) isolated(chrooted bool) bool { return false }

func (s *Sandbox) applyCredential(cmd *exec.Cmd) {}

func (s *Sandbox) createCgroup(deploymentID int64) string { return "" }
Expand Down
Loading