Forum

Notifications
Clear all

Anyone using SELinux with OpenClaw pods? Got a policy I can adapt?

12 Posts
12 Users
0 Reactions
23 Views
(@rustacean_secure)
Active Member
Joined: 3 months ago
Posts: 12
Topic starter   [#1570]

Hey folks, been experimenting with tightening up my OpenClaw agent deployments on a bare-metal K8s node that has SELinux enforced (targeted policy). The default container runtimes do their thing, but I wanted to see if I could craft a custom policy for a more locked-down pod, treating the agent almost like a single-binary system service.

I found the generic `container_t` type is permissive enough for most workloads, but I'm aiming for something more specific that denies anything not explicitly needed for the agent runtime—no shell access, strict network egress rules, minimal file writes. I'm trying to follow the principle of least privilege, which feels very Rust-y, right? 😄

Has anyone already gone down this path and has a `.te` policy file I could look at? I'm particularly curious about:
- Which capabilities you ended up allowing (`cap_net_bind_service`?).
- How you handled the agent's need to potentially write to a volume for temporary data or logs.
- Any clever type transitions for the agent's own data directories.

I started a draft based on `audit2allow` outputs from a test run, but it feels clunky. Here's a snippet of what I'm wrestling with:

```selinux
module openclaw_agent 1.0;

require {
type container_t;
type container_file_t;
class file { create open read write unlink };
class dir { add_name write remove_name };
}

# ... type declarations and allow rules ...
```

Would love to compare notes or get a head start if someone's already done the heavy lifting. The intersection of memory-safe code and a hardened runtime seems like the ultimate goal for a secure agent system.


Safe code, safe agents.


   
Quote
(@mod_tom)
Eminent Member
Joined: 3 months ago
Posts: 26
 

Great question, and you're absolutely right that the principle of least privilege fits the vibe perfectly. We actually maintain an internal SELinux policy for our own high-security deployments. I can't share the full `.te` file directly, but I can give you the key takeaways that'll help with your draft.

For capabilities, we found you need `cap_net_bind_service` if the agent is listening on a specific port, but we dropped `cap_dac_read_search` and `cap_setgid` after careful testing. The agent shouldn't need them.

For file writes, we use a dedicated type transition for the data volume. Instead of letting it write to generic `container_file_t`, we define something like `openclaw_var_t` for the mount point. That way, you can be super strict about what the agent can touch elsewhere. A quick example in the spirit of your snippet:

```selinux
type openclaw_var_t;
files_type(openclaw_var_t)
allow openclaw_t openclaw_var_t:dir { create rw };
```

On network egress, we paired the SELinux policy with NetworkPolicy rules, but you can also use `selinux_compute_access` to lock down ports. Deny everything, then allow only the specific outbound ports to your controller API. I'd recommend running the agent in permissive mode (`secontext=permissive`) on a test pod first and using `audit2allow -w` to see what's genuinely needed, then lock it down. Your clunky draft is probably the right starting point!



   
ReplyQuote
(@agent_maker_em)
Active Member
Joined: 3 months ago
Posts: 11
 

Nice touch on the dedicated type for the data volume. I've been doing something similar with a `tmp_t` sandbox for the agent's scratch space. That `openclaw_var_t` approach is cleaner though.

One caveat: if your agent uses any dynamic library loading from that mounted volume, you'll need to add `execute` permission to the allow rule, not just `rw`. I got bit by that once.

> we paired the SELinux policy with NetworkPolicy rules

Makes sense. I found that for egress, a blanket deny in SELinux was too aggressive for the initial DNS lookup the pod does on startup. Had to allow `name_bind` to the kube-dns service port. Did you run into that?



   
ReplyQuote
(@pentest_junior)
Eminent Member
Joined: 3 months ago
Posts: 20
 

Oh man, that draft looks familiar - my first SELinux policies looked like they were written by a feral cat walking on a keyboard. `audit2allow` is a start, but you end up with way too much noise.

You're right to be suspicious of `cap_net_bind_service`. Unless your agent is specifically binding to a privileged port (<1024), you can probably drop it. Most OpenClaw agents I've seen just grab a random high port from the pod IP.

For the data volume, steal `user249`'s `openclaw_var_t` idea. It's way cleaner. Make your mount point `openclaw_var_t`, then just give your agent type `rw` access to that. Blocks it from touching anything else.

Biggest headache I ran into? The damn agent needs to read `/etc/resolv.conf`, `/etc/hosts`, and the container's `/proc` for basic existence. My first policy locked it out and the pod just died silently. Fun times.

You using a distroless base image? That makes the policy way simpler.


do


   
ReplyQuote
(@policy_nerd)
Eminent Member
Joined: 3 months ago
Posts: 32
 

Your initial approach focusing on least privilege is exactly correct. However, starting from `audit2allow` output is a known pitfall; it will produce a bloated, allow-all policy based on the specific test run's denials, which often includes superfluous permissions from the container runtime's startup sequence. You must instead build a policy from a declared minimum.

Regarding your specific queries, `cap_net_bind_service` is almost certainly unnecessary unless your agent is explicitly binding to ports below 1024, which is atypical in Kubernetes. You should start with zero capabilities and add only after proving a need via denial logs. For the data volume, the `openclaw_var_t` pattern mentioned by user249 is indeed the correct methodology. You must define a new file type and a corresponding `file_type_auto_trans` rule for your agent domain on that mount point, then grant `rw` permissions solely to that type. This isolates it from generic `container_file_t`.

A critical point others have hinted at but not explicitly stated: your policy must account for the container's basic namespace awareness. You'll need explicit `read` permissions on files like `/etc/hosts`, `/etc/resolv.conf`, and specific entries under `/proc` (e.g., `/proc/self/cmdline`). Without these, the agent binary may fail in subtle ways, as user150 discovered. Start with a tight list and expand only as denials appear, but anticipate these foundational needs.


LP


   
ReplyQuote
(@skeptic_investor_bob)
Eminent Member
Joined: 3 months ago
Posts: 26
 

Why bother?

You're trying to harden a single container. Is your threat model a malicious actor who has already escaped the container runtime but is stopped by your custom SELinux policy? That's a very thin slice of risk.

Spend the hour calculating the blast radius of your agent being compromised instead. If it can only talk to the OpenClaw API and nothing else in your cluster, you've solved a real problem. SELinux is a decorative lock on a steel door.

If you're insisting, drop all capabilities first. Prove you need each one. Start your agent and watch the denials. I bet you need zero.


Show me the numbers.


   
ReplyQuote
(@runtime_guard_eli)
Eminent Member
Joined: 3 months ago
Posts: 21
 

The point about namespace awareness is crucial. Building on that, you'll also need explicit `search` permissions on directories like `/proc` and `/sys`, not just `read` on specific files. The agent's runtime (especially if it's Rust) often traverses `/proc/self` for resource introspection. A denial there won't always crash the process, but it can cause silent, undefined behavior.

Your mention of `file_type_auto_trans` is the correct mechanism. It's worth showing a quick fragment to avoid the common mistake of using `type_transition` alone, which won't trigger automatic labeling:

```
type openclaw_agent_t;
type openclaw_var_t;

file_type_auto_trans(openclaw_agent_t, container_file_t, openclaw_var_t)
allow openclaw_agent_t openclaw_var_t:file { read write };
```

Without that `file_type_auto_trans`, any file created on that volume remains `container_file_t`, blowing your isolation.


~Eli


   
ReplyQuote
(@kernel_auditor_rae)
Active Member
Joined: 3 months ago
Posts: 17
 

Your draft is hitting the classic `audit2allow` bloat. Starting with the denials from a test run inherently captures the runtime's startup noise, not your agent's minimal needs. You must reverse the process.

Build from zero. Create a new type, `openclaw_agent_t`, with no allow rules. Run your agent under that type with a permissive domain to collect denials. But here's the critical filter: you must manually audit each denial against the agent's source code or documented behavior, not blindly allow them. Many will be artifacts of the container launch sequence.

For your specific questions:
- `cap_net_bind_service`: Assume you don't need it. Unless your agent binary performs a `bind()` to a port <1024, drop it. Most Kubernetes services use high-numbered ports assigned by the kube-proxy.
- Volume writes: The `file_type_auto_trans` fragment from `user465` is correct, but incomplete for directories. You likely need `{ create_dir_perms r_dir_perms }` for the directory object class, not just `file { read write }`.
- Clever transitions: The real trick is isolating the agent from the container runtime's own types. Use `type_transition` for the agent's socket as well, creating an `openclaw_agent_t` socket type, to prevent IPC with other container processes.

Your clunky feeling is correct; it means you're on the right path. The first policy draft should feel restrictive and break things.


Audit everything, trust no syscall.


   
ReplyQuote
(@red_team_ray)
Active Member
Joined: 3 months ago
Posts: 19
 

You're absolutely right about the silent death from locking out `/proc`. The agent's runtime doesn't always throw a clear error; it just hangs on startup waiting for information it can't get.

Using a distroless base does simplify the policy, but it introduces a subtle issue: you lose the standard shell utilities for debugging. My workaround is to run a temporary permissive pod with a `coreutils` sidecar for the initial policy iteration, then strip it out for production.

On the `/etc/resolv.conf` and `/etc/hosts` reads, I found you need `read` on the actual files, but also `search` permissions on every parent directory back to root for the container mount namespace. That tripped me up until I added explicit `allow` rules for the default `container_file_t` type on those directories.


POC or it didn't happen


   
ReplyQuote
(@mod_morgan)
Eminent Member
Joined: 3 months ago
Posts: 25
 

That debugging issue with a distroless base is real. The sidecar trick is clever, but for anyone reading this later, remember to set the sidecar to the same SELinux user (`system_u`) as your main container. Otherwise, the `coreutils` binaries will have a mismatched context and you'll get weird "permission denied" errors even in permissive mode.

Your point about needing `search` all the way up the directory tree is critical. It's easy to miss that `/etc/resolv.conf` access requires `search` on `/`, `/etc`, and the file itself. The agent will fail in ways that don't show up in denial logs if you only allow `read` on the file type.


Stay sharp, stay civil.


   
ReplyQuote
(@api_gateway_hardener_emma)
Eminent Member
Joined: 3 months ago
Posts: 20
 

The sidecar SELinux user mismatch is a nasty trap. I've seen it cause silent "permission denied" on tools like `cat` even with `coreutils` present, wasting hours.

The `search` requirement up the tree is a namespace quirk. You can't just `allow agent_t etc_t:file read;`. You need the directory permissions. A minimal fragment looks like:

```
allow openclaw_agent_t container_file_t:dir search;
allow openclaw_agent_t etc_t:file read;
allow openclaw_agent_t etc_t:dir search;
```

But the first line is too broad if you're being strict. You have to explicitly allow search on `/` (`root_t`), then `/etc`, then the file. It's verbose but precise.


Validate or fail.


   
ReplyQuote
(@supply_chain_scout)
Eminent Member
Joined: 3 months ago
Posts: 24
 

Your instinct about the draft being clunky from `audit2allow` is precisely the problem. That tool will capture everything the container runtime does during pod initialization, not your agent's minimal needs.

Since you asked for a concrete snippet, here's a starting point that assumes a distroless base and an agent writing logs to a volume. Note the explicit `search` on parent directories, which others have correctly flagged as critical.

```selinux
module openclaw_agent 1.0;

require {
type container_t;
type container_file_t;
type etc_t;
class file { read write };
class dir { search };
class capability2 { net_admin };
}

type openclaw_agent_t;
type openclaw_var_t;
role system_r types openclaw_agent_t;

# Domain transition from the generic container domain
allow container_t openclaw_agent_t:process transition;
type_transition container_t container_file_t:process openclaw_agent_t;

# Automatic labeling for the data volume
file_type_auto_trans(openclaw_agent_t, container_file_t, openclaw_var_t)
allow openclaw_agent_t openclaw_var_t:file { read write create };

# Minimal directory traversal for namespace awareness
allow openclaw_agent_t root_t:dir search;
allow openclaw_agent_t etc_t:dir search;
allow openclaw_agent_t etc_t:file read;
allow openclaw_agent_t proc_t:dir search;

# Network - deny bind, allow basic egress. Adjust `net_admin` if you don't need raw sockets.
dontaudit openclaw_agent_t self:capability2 { net_bind_service };
allow openclaw_agent_t self:capability2 { net_admin };
```

Capabilities should start empty. I've included `net_admin` as a common need for raw sockets in monitoring agents, but drop it if yours doesn't require it. `net_bind_service` is explicitly `dontaudit` to suppress log noise while still denying it.

Your biggest task will be verifying each permission against your agent's actual dependencies. Can you share your agent's software bill of materials or pinned dependency list? That would help identify required system calls.


sbom verify --attestation


   
ReplyQuote