From 20aa13735f6c3f653bd3c9ad698f17019d2a81b9 Mon Sep 17 00:00:00 2001 From: lukefoster11 Date: Wed, 29 Apr 2026 11:35:41 -0700 Subject: [PATCH] support agent sandbox (minio) --- Dockerfile | 8 +- compose.yaml | 105 +++++++++++ gvisor-seccomp.json | 447 ++++++++++++++++++++++++++++++++++++++++++++ install.sh | 36 +++- 4 files changed, 590 insertions(+), 6 deletions(-) create mode 100644 gvisor-seccomp.json diff --git a/Dockerfile b/Dockerfile index 9f295bd..c061ef8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -# Check Dockerhub for available tags: https://hub.docker.com/r/tryretool/backend/tags +ARG VERSION=dev-3.380.0-940f7d8 -ARG VERSION=X.Y.Z-stable +FROM --platform=linux/amd64 753800337063.dkr.ecr.us-west-2.amazonaws.com/agent-executor-service:${VERSION} AS agent-sandbox -FROM tryretool/code-executor-service:${VERSION} AS code-executor +FROM --platform=linux/amd64 753800337063.dkr.ecr.us-west-2.amazonaws.com/code-executor-service:${VERSION} AS code-executor -FROM tryretool/backend:${VERSION} +FROM --platform=linux/amd64 753800337063.dkr.ecr.us-west-2.amazonaws.com/onprem:${VERSION} CMD ./docker_scripts/start_api.sh diff --git a/compose.yaml b/compose.yaml index c1a4365..e568796 100644 --- a/compose.yaml +++ b/compose.yaml @@ -17,6 +17,7 @@ services: - code-executor depends_on: - postgres + - minio restart: always jobs-runner: @@ -102,6 +103,107 @@ services: networks: - code-executor restart: always + + agent-sandbox-controller: + build: + context: . + target: agent-sandbox + user: root + env_file: docker.env + environment: + - NODE_ENV=production + - AGENT_EXECUTOR_ROLE=controller + - ORCHESTRATOR=docker + - EXECUTOR_IMAGE=${EXECUTOR_IMAGE:-retool-onprem-agent-sandbox-controller} + - DOCKER_NETWORK=agent-sandbox + - EXECUTOR_SERVICE_NAME=agent-sandbox + - SANDBOX_BACKEND_URL=http://api:3000 + - EXECUTOR_EXTRA_ENV=SANDBOX_NETWORK_ENABLED=false,SANDBOX_HTTP_PROXY=http://agent-sandbox-proxy:3019 + - EXECUTOR_SECCOMP_PROFILE_PATH=/seccomp/gvisor-seccomp.json + # Lower pool sizes for local development + - PREWARM_POOL_SIZE=1 + - MAX_TOTAL_JOBS=10 + - MAX_CONCURRENT_CREATES=2 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./gvisor-seccomp.json:/seccomp/gvisor-seccomp.json:ro + networks: + - backend + - agent-sandbox + depends_on: + - postgres + restart: always + + agent-sandbox-proxy: + build: + context: . + target: agent-sandbox + env_file: docker.env + environment: + - NODE_ENV=production + - AGENT_EXECUTOR_ROLE=proxy + - ORCHESTRATOR=docker + - PROXY_PORT=3019 + - EXECUTOR_SERVICE_NAME=agent-sandbox + - BACKEND_URL=http://api:3000 + # Uncomment to restrict which external domains sandboxes can reach + # - ALLOWED_DOMAINS=api.openai.com,api.anthropic.com + ports: + - 3019:3019 + networks: + - backend + - agent-sandbox + depends_on: + - postgres + restart: always + + r2-agent-worker: + build: + context: . + env_file: docker.env + environment: + - SERVICE_TYPE=R2_AGENT_TEMPORAL_WORKER + - WORKER_TEMPORAL_TASKQUEUE=r2-agent + networks: + - backend + - agent-sandbox + depends_on: + - postgres + restart: always + + minio: + image: minio/minio:latest + command: server /data --console-address ":9001" + environment: + - MINIO_ROOT_USER=retool + - MINIO_ROOT_PASSWORD=retoolminio + - MINIO_DOMAIN=minio + ports: + - 9000:9000 + - 9001:9001 + networks: + backend: + aliases: + - retool-rr-git.minio + - retool-rr-snapshots.minio + volumes: + - minio-data:/data + restart: always + + minio-init: + image: minio/mc:latest + depends_on: + - minio + entrypoint: > + /bin/sh -c " + sleep 3; + mc alias set local http://minio:9000 retool retoolminio; + mc mb --ignore-existing local/retool-rr-git; + mc mb --ignore-existing local/retool-rr-snapshots; + echo 'Buckets created successfully'; + " + networks: + - backend # Retool's internal DB, we recommend using an externally hosted database: https://docs.retool.com/docs/configuring-retools-storage-database postgres: @@ -146,7 +248,10 @@ networks: frontend: backend: code-executor: + agent-sandbox: + name: agent-sandbox volumes: data: retooldb-data: + minio-data: diff --git a/gvisor-seccomp.json b/gvisor-seccomp.json new file mode 100644 index 0000000..9b2a1de --- /dev/null +++ b/gvisor-seccomp.json @@ -0,0 +1,447 @@ +{ + "comment": "Docker default seccomp profile extended with syscalls required by gVisor runsc (systrap platform, rootless mode). Use with: docker run --security-opt seccomp=gvisor-seccomp.json", + "defaultAction": "SCMP_ACT_ERRNO", + "defaultErrnoRet": 1, + "archMap": [ + { + "architecture": "SCMP_ARCH_X86_64", + "subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"] + }, + { + "architecture": "SCMP_ARCH_AARCH64", + "subArchitectures": [] + } + ], + "syscalls": [ + { + "comment": "Docker default allowlist (Docker 27.x, x86_64 + aarch64)", + "names": [ + "_llseek", + "_newselect", + "accept", + "accept4", + "access", + "acct", + "adjtimex", + "alarm", + "arch_prctl", + "bind", + "bpf", + "brk", + "cachestat", + "capget", + "capset", + "chdir", + "chmod", + "chown", + "chown32", + "chroot", + "clock_adjtime", + "clock_adjtime64", + "clock_getres", + "clock_getres_time64", + "clock_gettime", + "clock_gettime64", + "clock_nanosleep", + "clock_nanosleep_time64", + "clock_settime", + "clock_settime64", + "close", + "close_range", + "connect", + "copy_file_range", + "creat", + "delete_module", + "dup", + "dup2", + "dup3", + "epoll_create", + "epoll_create1", + "epoll_ctl", + "epoll_ctl_old", + "epoll_pwait", + "epoll_pwait2", + "epoll_wait", + "epoll_wait_old", + "eventfd", + "eventfd2", + "execve", + "execveat", + "exit", + "exit_group", + "faccessat", + "faccessat2", + "fadvise64", + "fadvise64_64", + "fallocate", + "fanotify_init", + "fanotify_mark", + "fchdir", + "fchmod", + "fchmodat", + "fchmodat2", + "fchown", + "fchown32", + "fchownat", + "fcntl", + "fcntl64", + "fdatasync", + "fgetxattr", + "finit_module", + "flistxattr", + "flock", + "fork", + "fremovexattr", + "fsconfig", + "fsetxattr", + "fsmount", + "fsopen", + "fspick", + "fstat", + "fstat64", + "fstatat64", + "fstatfs", + "fstatfs64", + "fsync", + "ftruncate", + "ftruncate64", + "futex", + "futex_requeue", + "futex_time64", + "futex_wait", + "futex_waitv", + "futex_wake", + "futimesat", + "get_mempolicy", + "get_robust_list", + "get_thread_area", + "getcpu", + "getcwd", + "getdents", + "getdents64", + "getegid", + "getegid32", + "geteuid", + "geteuid32", + "getgid", + "getgid32", + "getgroups", + "getgroups32", + "getitimer", + "getpeername", + "getpgid", + "getpgrp", + "getpid", + "getppid", + "getpriority", + "getrandom", + "getresgid", + "getresgid32", + "getresuid", + "getresuid32", + "getrlimit", + "getrusage", + "getsid", + "getsockname", + "getsockopt", + "gettid", + "gettimeofday", + "getuid", + "getuid32", + "getxattr", + "init_module", + "inotify_add_watch", + "inotify_init", + "inotify_init1", + "inotify_rm_watch", + "io_cancel", + "io_destroy", + "io_getevents", + "io_pgetevents", + "io_pgetevents_time64", + "io_setup", + "io_submit", + "io_uring_enter", + "io_uring_register", + "io_uring_setup", + "ioctl", + "ioperm", + "iopl", + "ioprio_get", + "ioprio_set", + "ipc", + "kcmp", + "kill", + "landlock_add_rule", + "landlock_create_ruleset", + "landlock_restrict_self", + "lchown", + "lchown32", + "lgetxattr", + "link", + "linkat", + "listen", + "listxattr", + "llistxattr", + "lookup_dcookie", + "lremovexattr", + "lseek", + "lsetxattr", + "lstat", + "lstat64", + "madvise", + "map_shadow_stack", + "mbind", + "membarrier", + "memfd_create", + "memfd_secret", + "mincore", + "mkdir", + "mkdirat", + "mknod", + "mknodat", + "mlock", + "mlock2", + "mlockall", + "mmap", + "mmap2", + "modify_ldt", + "mount_setattr", + "move_mount", + "mprotect", + "mq_getsetattr", + "mq_notify", + "mq_open", + "mq_timedreceive", + "mq_timedreceive_time64", + "mq_timedsend", + "mq_timedsend_time64", + "mq_unlink", + "mremap", + "msgctl", + "msgget", + "msgrcv", + "msgsnd", + "msync", + "munlock", + "munlockall", + "munmap", + "name_to_handle_at", + "nanosleep", + "newfstatat", + "open", + "open_by_handle_at", + "open_tree", + "openat", + "openat2", + "pause", + "perf_event_open", + "pidfd_getfd", + "pidfd_open", + "pidfd_send_signal", + "pipe", + "pipe2", + "pkey_alloc", + "pkey_free", + "pkey_mprotect", + "poll", + "ppoll", + "ppoll_time64", + "prctl", + "pread64", + "preadv", + "preadv2", + "prlimit64", + "process_madvise", + "process_mrelease", + "process_vm_readv", + "process_vm_writev", + "pselect6", + "pselect6_time64", + "pwrite64", + "pwritev", + "pwritev2", + "quotactl", + "quotactl_fd", + "read", + "readahead", + "readlink", + "readlinkat", + "readv", + "reboot", + "recv", + "recvfrom", + "recvmmsg", + "recvmmsg_time64", + "recvmsg", + "remap_file_pages", + "removexattr", + "rename", + "renameat", + "renameat2", + "restart_syscall", + "rmdir", + "rseq", + "rt_sigaction", + "rt_sigpending", + "rt_sigprocmask", + "rt_sigqueueinfo", + "rt_sigreturn", + "rt_sigsuspend", + "rt_sigtimedwait", + "rt_sigtimedwait_time64", + "rt_tgsigqueueinfo", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_getaffinity", + "sched_getattr", + "sched_getparam", + "sched_getscheduler", + "sched_rr_get_interval", + "sched_rr_get_interval_time64", + "sched_setaffinity", + "sched_setattr", + "sched_setparam", + "sched_setscheduler", + "sched_yield", + "seccomp", + "select", + "semctl", + "semget", + "semop", + "semtimedop", + "semtimedop_time64", + "send", + "sendfile", + "sendfile64", + "sendmmsg", + "sendmsg", + "sendto", + "set_mempolicy", + "set_mempolicy_home_node", + "set_robust_list", + "set_thread_area", + "set_tid_address", + "set_tls", + "setdomainname", + "setfsgid", + "setfsgid32", + "setfsuid", + "setfsuid32", + "setgid", + "setgid32", + "setgroups", + "setgroups32", + "setitimer", + "setpgid", + "setpriority", + "setregid", + "setregid32", + "setresgid", + "setresgid32", + "setresuid", + "setresuid32", + "setreuid", + "setreuid32", + "setrlimit", + "setsid", + "setsockopt", + "settimeofday", + "setuid", + "setuid32", + "setxattr", + "shmat", + "shmctl", + "shmdt", + "shmget", + "shutdown", + "sigaltstack", + "signalfd", + "signalfd4", + "sigprocmask", + "sigreturn", + "socket", + "socketcall", + "socketpair", + "splice", + "stat", + "stat64", + "statfs", + "statfs64", + "statx", + "stime", + "symlink", + "symlinkat", + "sync", + "sync_file_range", + "sync_file_range2", + "syncfs", + "sysinfo", + "syslog", + "tee", + "tgkill", + "time", + "timer_create", + "timer_delete", + "timer_getoverrun", + "timer_gettime", + "timer_gettime64", + "timer_settime", + "timer_settime64", + "timerfd_create", + "timerfd_gettime", + "timerfd_gettime64", + "timerfd_settime", + "timerfd_settime64", + "times", + "tkill", + "truncate", + "truncate64", + "ugetrlimit", + "umask", + "umount", + "uname", + "unlink", + "unlinkat", + "utime", + "utimensat", + "utimensat_time64", + "utimes", + "vfork", + "vhangup", + "vmsplice", + "wait4", + "waitid", + "waitpid", + "write", + "writev" + ], + "action": "SCMP_ACT_ALLOW" + }, + { + "comment": "gVisor + pasta: namespace creation and entry (clone/unshare with CLONE_NEW* flags, setns to join namespaces)", + "names": ["clone", "clone3", "unshare", "setns"], + "action": "SCMP_ACT_ALLOW" + }, + { + "comment": "pasta: set hostname inside namespace (cosmetic, avoids warning)", + "names": ["sethostname"], + "action": "SCMP_ACT_ALLOW" + }, + { + "comment": "gVisor: sandbox filesystem setup (tmpfs, proc, bind mounts)", + "names": ["mount", "umount2"], + "action": "SCMP_ACT_ALLOW" + }, + { + "comment": "gVisor: filesystem root isolation for sentry and gofer", + "names": ["pivot_root"], + "action": "SCMP_ACT_ALLOW" + }, + { + "comment": "gVisor systrap platform: workload executor thread initialization", + "names": ["ptrace"], + "action": "SCMP_ACT_ALLOW" + } + ] +} diff --git a/install.sh b/install.sh index efc8df1..a297abb 100755 --- a/install.sh +++ b/install.sh @@ -78,6 +78,13 @@ echo "" random() { cat /dev/urandom | base64 | head -c "$1" | tr -d +/ ; } +postgres_password=$(random 64) + +ae_private_pem=$(openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 2>/dev/null) +ae_public_pem=$(echo "$ae_private_pem" | openssl ec -pubout 2>/dev/null) +ae_private_key=$(echo "$ae_private_pem" | awk '{if(NR>1)printf "\\n";printf "%s",$0}') +ae_public_key=$(echo "$ae_public_pem" | awk '{if(NR>1)printf "\\n";printf "%s",$0}') + cat << EOF > docker.env # Environment variables reference: docs.retool.com/docs/environment-variables DEPLOYMENT_TEMPLATE_TYPE=docker-compose @@ -87,7 +94,7 @@ POSTGRES_HOST=postgres POSTGRES_DB=hammerhead_production POSTGRES_PORT=5432 POSTGRES_USER=retool_internal_user -POSTGRES_PASSWORD=$(random 64) +POSTGRES_PASSWORD=$postgres_password # Retool DB credentials RETOOLDB_POSTGRES_HOST=retooldb-postgres @@ -100,6 +107,31 @@ RETOOLDB_POSTGRES_PASSWORD=$(random 64) WORKFLOW_BACKEND_HOST=http://workflows-backend:3000 CODE_EXECUTOR_INGRESS_DOMAIN=http://code-executor:3004 +# Agent sandbox configuration +AGENT_EXECUTOR_ENABLED=true +RR_AGENT_PUBSUB_BACKEND=postgres +AGENT_EXECUTOR_CONTROLLER_INGRESS_DOMAIN=http://agent-sandbox-controller:3018 +AGENT_EXECUTOR_PROXY_INGRESS_DOMAIN=http://agent-sandbox-proxy:3019 +AGENT_EXECUTOR_FRONTEND_WS_PROXY_DOMAIN=http://$hostname:3019 +AGENT_EXECUTOR_JWT_PRIVATE_KEY="$ae_private_key" +AGENT_EXECUTOR_JWT_PUBLIC_KEY="$ae_public_key" +AGENT_EXECUTOR_ENCRYPTION_KEY=$(openssl rand -hex 32) +STATE_BACKEND=postgres +AGENT_EXECUTOR_POSTGRES_URL=postgres://retool_internal_user:$postgres_password@postgres:5432/hammerhead_production +AGENT_EXECUTOR_POSTGRES_SCHEMA=agent_executor + +# S3-compatible storage (MinIO) +AWS_ENDPOINT_URL=http://minio:9000 +RR_GIT_S3_BUCKET=retool-rr-git +RR_GIT_S3_ACCESS_KEY_ID=retool +RR_GIT_S3_SECRET_ACCESS_KEY=retoolminio +RR_GIT_S3_REGION=us-east-1 +RR_SNAPSHOTS_S3_BUCKET=retool-rr-snapshots +RR_SNAPSHOTS_S3_ACCESS_KEY_ID=retool +RR_SNAPSHOTS_S3_SECRET_ACCESS_KEY=retoolminio +RR_SNAPSHOTS_S3_REGION=us-east-1 +RR_SNAPSHOTS_S3_ENDPOINT=http://minio:9000 + # Comment out below to use Retool-managed Temporal (Enterprise license) WORKFLOW_TEMPORAL_CLUSTER_FRONTEND_HOST=temporal WORKFLOW_TEMPORAL_CLUSTER_FRONTEND_PORT=7233 @@ -121,7 +153,7 @@ DOMAINS=$hostname -> http://api:3000 BASE_DOMAIN=https://$hostname # If your domain/HTTPS isn't in place yet -# COOKIE_INSECURE=true +COOKIE_INSECURE=true EOF