Skip to content

[feat][PLAT-1416] Add AppArmor profile installers for code-executor and agent-sandbox - #353

Open
mako-taco wants to merge 8 commits into
mainfrom
jake/apparmor-profiles
Open

[feat][PLAT-1416] Add AppArmor profile installers for code-executor and agent-sandbox#353
mako-taco wants to merge 8 commits into
mainfrom
jake/apparmor-profiles

Conversation

@mako-taco

Copy link
Copy Markdown

Summary

On Ubuntu 24.04+ with kernel.apparmor_restrict_unprivileged_userns=1, the default unconfined AppArmor annotation prevents nsjail (code-executor/js-executor) and gVisor/pasta (agent-sandbox) from creating unprivileged user namespaces. Some customers (e.g. P&G) cannot accept unconfined profiles due to security governance.

This adds opt-in AppArmor profile installers that load confined profiles with an explicit userns, rule, so sandboxing works without requiring unconfined.

code-executor / js-executor (nsjail):

  • New apparmor_nsjail_daemonset.yaml + apparmor_nsjail_configmap.yaml — DaemonSet loads usr.bin.nsjail and retool-executor profiles on each node via nsenter -t 1 -m -- apparmor_parser -r
  • Gated by codeExecutor.appArmorProfileInstaller: true (default false)
  • Switches the annotation from unconfined to localhost/retool-executor
  • Requires codeExecutor.useSeccompProfile: true (the annotation block is gated on it)

agent-sandbox (gVisor/pasta):

  • New apparmor_agent_sandbox_configmap.yaml with the retool-agent-sandbox profile
  • Extends the existing agent_sandbox_seccomp.yaml node-installer DaemonSet with an install-apparmor init container
  • Gated by rr.agentSandbox.appArmorProfileInstaller: true (default false)
  • Switches the job template appArmorProfile from Unconfined to {"type": "Localhost", "localhostProfile": "retool-agent-sandbox"}

CI coverage:

  • test-apparmor-nsjail-option.yaml and test-apparmor-agent-sandbox-option.yaml for kubeconform schema validation
  • test-apparmor-install-values.yaml for ct-install startup testing in kind

Closes PLAT-1416, PLAT-1426.

Test Plan

  • Verified on an Ubuntu 24.04 Lima VM (aarch64, Rosetta, k3s) with kernel.apparmor_restrict_unprivileged_userns=1:
    • DaemonSet loads profiles; retool-executor (enforce) and retool-agent-sandbox (enforce) confirmed in /sys/kernel/security/apparmor/profiles
    • code-executor pod runs under retool-executor (enforce) and nsjail successfully creates user/mount/PID namespaces
    • Test pod under retool-agent-sandbox (enforce) successfully creates user/mount/PID namespaces
  • kubeconform passes for all values × option combinations across K8s 1.27–1.31
  • Toggle off (default): templates fall back to unconfined / Unconfined — no behavioral change for existing users

Made with Cursor

…nd agent-sandbox

On Ubuntu 24.04+ with kernel.apparmor_restrict_unprivileged_userns=1,
the default "unconfined" AppArmor annotation prevents nsjail and
gVisor/pasta from creating unprivileged user namespaces. This adds
opt-in AppArmor profile installers that load confined profiles with an
explicit `userns,` rule, allowing sandboxing to work without requiring
the customer to set unconfined (which some security-conscious customers
like P&G cannot accept).

For code-executor / js-executor (nsjail):
- New DaemonSet + ConfigMap load usr.bin.nsjail and retool-executor
  profiles on each node via nsenter + apparmor_parser
- Gated by codeExecutor.appArmorProfileInstaller (default false)
- Switches the annotation from "unconfined" to "localhost/retool-executor"

For agent-sandbox (gVisor/pasta):
- Extends the existing node-installer DaemonSet with an install-apparmor
  init container + ConfigMap for the retool-agent-sandbox profile
- Gated by rr.agentSandbox.appArmorProfileInstaller (default false)
- Switches the job template from Unconfined to Localhost profile

CI: kubeconform option files for schema validation, plus a ct-install
values file that exercises the nsjail AppArmor path end-to-end in kind.

Co-authored-by: Cursor <cursoragent@cursor.com>
@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
charts/retool/templates/apparmor_nsjail_daemonset.yaml Loads the nsjail and executor profiles before atomically signaling node-local readiness; the previously reported parser and marker-lifecycle issues are addressed.
charts/retool/templates/agent_sandbox_seccomp.yaml Extends the node installer to load the agent-sandbox profile and publishes readiness only after successful parsing.
charts/retool/templates/deployment_code_executor.yaml Selects the Localhost profile when enabled and waits for confirmed node-local installation before starting the protected container.
charts/retool/templates/deployment_js_executor.yaml Mirrors the code-executor profile selection and readiness coordination for js-executor.
charts/retool/templates/deployment_agent_sandbox.yaml Adds profile readiness coordination and a Localhost AppArmor security context to generated sandbox Jobs.
charts/retool/templates/apparmor_nsjail_configmap.yaml Defines the nsjail and retool-executor AppArmor profiles with explicit user-namespace permission.
charts/retool/templates/apparmor_agent_sandbox_configmap.yaml Defines the confined agent-sandbox profile with explicit user-namespace permission.
.github/test-apparmor.sh Exercises profile loading, profile attachment, and user-namespace creation on an AppArmor-enabled test host.
.github/workflows/ci.yaml Adds the AppArmor integration test as a prerequisite of PR validation.

Reviews (8): Last reviewed commit: "fix: wait for k3s node registration befo..." | Re-trigger Greptile

Comment thread charts/retool/templates/apparmor_nsjail_daemonset.yaml Outdated
Comment thread charts/retool/templates/deployment_code_executor.yaml
The root values.yaml was missing the AppArmor settings that were added
to charts/retool/values.yaml, causing the "Ensure values.yaml files
match" CI check to fail. Also bumps the chart version from 6.11.12 to
6.11.13 so ct lint passes the version-bump check.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread charts/retool/values.yaml
# "unconfined" to "localhost/retool-executor", which satisfies Ubuntu 24.04+'s
# kernel.apparmor_restrict_unprivileged_userns=1 restriction by providing an
# explicit userns rule. Requires useSeccompProfile: true to take effect.
appArmorProfileInstaller: false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably want this on by default

profile retool-agent-sandbox flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>

network,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the very explicit app armor profile but it'd need thorough manual testing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can first do this change for our cloud and once confirm that the allow/deny list here works well then push this change

Jake Scott and others added 2 commits July 27, 2026 14:05
The kind clusters used by ct-install don't have AppArmor enabled, so
pods requesting localhost/ profiles fail with "Cannot enforce AppArmor:
AppArmor is not enabled on the host" and the Helm install times out.

AppArmor template rendering is already validated by kubeconform via the
option files (test-apparmor-nsjail-option.yaml and
test-apparmor-agent-sandbox-option.yaml). Actual cluster testing
requires an AppArmor-enabled host, which isn't available in CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Fail installer init containers when apparmor_parser is missing instead of
  silently skipping, so pods aren't left requiring profiles that were never
  loaded.
- Add wait-for-apparmor init containers to code-executor, js-executor, and
  agent-sandbox workloads so they block until the DaemonSet has installed
  the required profile on the node.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mako-taco

Copy link
Copy Markdown
Author

@greptile review

Comment thread charts/retool/templates/apparmor_nsjail_daemonset.yaml
…e: 3/5)

Installers now write a .loaded sentinel file only after apparmor_parser
successfully loads the profile into the kernel. Wait init containers
check the sentinel instead of the profile file, closing the race where a
workload could proceed before the profile was actually loaded.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mako-taco

Copy link
Copy Markdown
Author

@greptile review

Comment thread charts/retool/templates/apparmor_nsjail_daemonset.yaml Outdated
…: 3/5)

Sentinel files now live under /run/retool/apparmor/ (a tmpfs cleared on
every boot) instead of /etc/apparmor.d/. This prevents stale markers
from surviving node reboots while the kernel's loaded profiles are
cleared, closing the last race between installer DaemonSets and workload
pods.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mako-taco

Copy link
Copy Markdown
Author

@greptile review

Jake Scott and others added 2 commits July 29, 2026 14:40
The test-apparmor CI check was failing because `helm dependency build`
couldn't fetch the postgresql chart without the bitnami repo configured.
Other CI jobs use chart-testing which handles this via ct.yaml, but the
apparmor test calls helm directly.

Also includes env var renames (AGENT_SANDBOX -> AGENT_EXECUTOR) and
adds /tmp emptyDir volume mounts to controller/proxy pods.

Co-authored-by: Cursor <cursoragent@cursor.com>
kubectl get nodes returns success even with an empty node list, causing
the wait loop to break prematurely. Check that at least one node appears
in the output before proceeding to kubectl wait --for=condition=Ready.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mako-taco
mako-taco marked this pull request as ready for review July 29, 2026 22:58
@mako-taco
mako-taco requested review from a team, gavin-retool and westrik July 29, 2026 22:58

Copy link
Copy Markdown
Author

@tryretool/security we bug bashed this and things look functional, but we're definitely not experts on apparmor. would you mind doing a look over?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants