Skip to content

gprocunier/blastwall

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

blastwall

blastwall is a proof of concept for using SELinux user confinement as an exploit firewall for privileged automation.

This follows from the argument I made in privileged-automation-security: I do not want automation to inherit the same assumptions as an unconfined human operator and then move faster. I want automation to start constrained. I want movement out of that constraint to be narrow, deliberate, and visible.

The first concrete target is the Copy Fail exploit path. This project is inspired by Anthony Green's block-copyfail PoC. Anthony's project uses BPF LSM to block only AF_ALG binds for authencesn. That is the right shape when the mitigation needs kernel argument precision.

Blastwall asks a different question. If the vulnerable path should be blocked for privileged automation identities, can I make that part of the same SELinux, IdM, AAP, and content-delivery model I already expect RHEL operators to understand? SELinux cannot inspect the authencesn algorithm string, so this PoC blocks the broader surface for mapped automation sessions: blastwall_u domains are denied alg_socket access entirely.

The Argument

I do not think FreeIPA should author SELinux policy. I think FreeIPA should map named identities into SELinux users and roles that already exist on the endpoint. I also do not think AAP should discover halfway through a play that the target host was not in the right confinement state.

The model has three parts:

  1. Local policy on each managed RHEL host defines blastwall_u, blastwall_r, and the confined automation domain blastwall_root_local_t.
  2. FreeIPA/IdM maps AAP automation identities into blastwall_u with an HBAC-linked SELinux user map.
  3. AAP uses eigenstate.ipa before running jobs to select eligible hosts and fail closed when SELinux map, HBAC, sudo, or optional host policy markers do not match expectations.

The point is not to replace kernel patches. The point is to make privileged automation land inside a narrow domain where known exploit surfaces can be blocked quickly while the real fix is still moving through the fleet.

Inspiration: block-copyfail

block-copyfail is the reference point for this work because it shows the kernel-native shape of the mitigation:

  • hook the relevant LSM decision point;
  • inspect the syscall argument that contains the algorithm name;
  • deny only the vulnerable authencesn AF_ALG bind;
  • leave unrelated AF_ALG users alone.

That precision is the strength of BPF LSM. Blastwall does not compete with it on per-argument filtering. It borrows the same practical lesson, which is that the exploit should be stopped before it reaches the vulnerable kernel path, and then moves the control boundary into the automation trust model.

flowchart LR
    COPY["Copy Fail mitigation idea"] --> BPF["block-copyfail<br/>BPF LSM precision"]
    COPY --> BW["Blastwall<br/>SELinux automation boundary"]

    BPF --> BPFHOOK["socket_bind hook<br/>inspect authencesn"]
    BPFHOOK --> BPFDENY["Deny exact algorithm"]

    BW --> IDM["IdM maps identity<br/>to blastwall_u"]
    IDM --> AAP["AAP preflight<br/>chooses safe hosts"]
    AAP --> SELINUX["SELinux denies<br/>alg_socket for automation"]
Loading

Why I Would Build This

I would build this because the logistics matter.

In a RHEL automation estate, a versioned SELinux policy RPM is often easier to stage, promote, audit, and roll back than a new set of eBPF probes. That does not make SELinux more precise. It makes the mitigation fit the machinery that already exists around RHEL operations.

If the exploit can be mitigated by denying a broad surface for automation identities, this is the kind of workflow I want:

flowchart TB
    subgraph SIGNAL["1. Convert the exploit into managed policy"]
        EXP["New exploit surface"] --> POLICY["Update Blastwall policy"]
    end

    subgraph ARTIFACT["2. Give the mitigation an artifact lifecycle"]
        RPM["Build/sign policy RPM"] --> SAT["Publish via Satellite<br/>or content pipeline"]
    end

    subgraph ROLLOUT["3. Roll out and prove host-local enforcement"]
        AAP["AAP rollout workflow"] --> VERIFY["Local verification<br/>expected denial works"]
    end

    subgraph INVENTORY["4. Feed verified state back into automation"]
        IDM["Update IdM host marker"] --> EIGEN["eigenstate.ipa inventory"]
        EIGEN --> PREFLIGHT["AAP preflight<br/>select current hosts"]
    end

    POLICY --> RPM
    SAT --> AAP
    VERIFY --> IDM
Loading

That path is operationally ordinary:

  • RPM versioning gives the mitigation a concrete artifact name.
  • Satellite can stage, promote, pin, report, and roll back the package.
  • AAP can install the package with normal dnf workflows.
  • IdM can expose which identities and hosts are in scope.
  • eigenstate.ipa can make AAP aware of SELinux maps, HBAC access, sudo policy, and optional host coverage markers before a job starts.
  • Audit and change control can reason about blastwall-selinux-0.2.0-1 more easily than a dynamically attached probe.

The tradeoff is precision. BPF LSM can inspect kernel hook arguments and block one exact shape, such as authencesn in socket_bind. Blastwall blocks broader SELinux surfaces for a specific subject: privileged automation mapped into blastwall_u.

That is often acceptable for automation. Many AAP jobs should not need AF_ALG, BPF, perf, raw sockets, user namespace creation, or ptrace. Blocking those surfaces for automation identities is part of the point.

The short version:

BPF LSM is the precision tool.
Blastwall is the identity, policy, and delivery model.

What This Adds To AAP And IdM

The important question is not only "can this exploit be blocked?" It is also:

  • which automation identities are allowed to run on this host;
  • whether those identities land in a confined SELinux user;
  • whether sudo reaches root without escaping confinement;
  • whether AAP can tell which hosts have the required mitigation before running;
  • whether policy coverage can be versioned, rolled out, and audited through IdM-visible host state.

That is where the combination of eigenstate.ipa, AAP, and IdM matters. IdM is the source of truth for identity, host scope, HBAC, sudo, SELinux user mapping, and optional host policy markers. AAP consumes that truth before execution. SELinux provides the host-local enforcement once the automation session lands.

flowchart TB
    IDM["IdM / FreeIPA<br/>identity + host + policy truth"] --> EIGEN["eigenstate.ipa<br/>read policy state"]
    EIGEN --> AAP["AAP preflight<br/>gate and target selection"]
    AAP --> SSH["SSH to managed host"]
    SSH --> PAM["SSSD + pam_selinux<br/>apply SELinux user map"]
    PAM --> BW["blastwall_u confined session"]
    BW --> LOCAL["Local root work<br/>inside SELinux boundary"]
    BW --> BLOCK["Exploit surfaces denied<br/>by versioned policy"]
Loading

The host is not merely patched or unpatched. For a given job, it is suitable or unsuitable based on current IdM state and local policy coverage.

Coverage Expansion Model

I would treat Blastwall as a small policy product, not a one-off rule. If another exploit surface is identified before Copy Fail is fixed everywhere, the workflow is:

  1. Define the new exploit surface in terms SELinux can enforce.
  2. Add a new policy scope to the Blastwall policy source.
  3. Generate and review the updated policy module/RPM.
  4. Bump the Blastwall policy version.
  5. Roll out the policy to eligible hosts.
  6. Update the IdM host marker with the new version and coverage claims.
  7. Let AAP inventory and preflight prefer only hosts with the required coverage.

That keeps emergency mitigations from becoming untracked local state. Each new rule has a version, a stated scope, and an inventory-visible rollout state.

flowchart TB
    subgraph SCOPE_STAGE["1. Scope the new surface"]
        EXP["New exploit surface"] --> SCOPE["Define enforceable scope<br/>class, permission, type, or transition"]
    end

    subgraph POLICY_STAGE["2. Productize the policy change"]
        POLICY["Add Blastwall policy scope"] --> REVIEW["Review impact<br/>normal automation still works"]
        REVIEW --> VERSION["Bump policy version"]
    end

    subgraph SHIP_STAGE["3. Ship coverage through normal RHEL operations"]
        RPM["Build policy RPM/module"] --> ROLLOUT["Roll out to managed hosts"]
    end

    subgraph SELECT_STAGE["4. Make coverage selectable before jobs run"]
        MARK["Update IdM host marker"] --> INV["AAP inventory sync"]
        INV --> PREFLIGHT["Preflight selects hosts<br/>with required coverage"]
    end

    SCOPE --> POLICY
    VERSION --> RPM
    ROLLOUT --> MARK
Loading

Architecture Flow

flowchart LR
    AAP["AAP job<br/>automation identity"] --> INV["eigenstate.ipa.idm<br/>IdM-backed inventory"]
    INV --> HBAC["HBAC-scoped hosts<br/>blastwall-automation-ssh"]
    INV --> MARKERS["Host markers<br/>current or stale policy"]
    HBAC --> PREFLIGHT["Preflight gate"]
    MARKERS --> PREFLIGHT
    PREFLIGHT --> MAP["SELinux user map<br/>blastwall-root-local-map"]
    PREFLIGHT --> SUDO["Sudo policy<br/>blastwall-root-local-sudo"]
    PREFLIGHT --> RUN["Run job on selected hosts"]
    RUN --> LOGIN["SSH login via SSSD + pam_selinux"]
    LOGIN --> DOMAIN["blastwall_u:blastwall_r:blastwall_t<br/>alias: blastwall_root_local_t"]
    DOMAIN --> DENY["CIL deny<br/>no alg_socket access"]
Loading

Repository Layout

  • policy/ - SELinux reference-policy module and login context template.
  • scripts/ - local install/cleanup helpers, IdM command emitters, and the asciinema PoC demo bundle.
  • idm/ - IdM object creation example for group, HBAC, sudo, and user map.
  • inventory/ - eigenstate.ipa.idm inventory source for AAP.
  • playbooks/ - AAP/controller preflight, policy deployment, and host checks.
  • tests/ - safe AF_ALG/authencesn trigger used to verify denial.

IdM Relationship Model

flowchart TB
    GROUP["IdM group<br/>blastwall-automation"] --> HBAC["HBAC rule<br/>blastwall-automation-ssh"]
    HOSTGROUP["IdM hostgroup<br/>blastwall-managed-hosts"] --> HBAC
    SERVICE["HBAC service<br/>sshd"] --> HBAC
    HBAC --> MAP["SELinux user map<br/>blastwall-root-local-map"]
    MAP --> SEUSER["blastwall_u:s0"]
    GROUP --> SUDO["Sudo rule<br/>blastwall-root-local-sudo"]
    HOSTGROUP --> SUDO
    SUDO --> ROOT["RunAs root<br/>no unconfined role/type option"]
Loading

Requirements

Managed RHEL hosts need:

  • SELinux enforcing.
  • SELinux userspace 3.6 or newer for CIL deny rules.
  • selinux-policy-devel
  • checkpolicy
  • policycoreutils-python-utils
  • make

AAP execution environments that run the preflight need:

  • eigenstate.ipa
  • python3-ipalib
  • python3-ipaclient
  • krb5-workstation
  • a mounted IdM keytab and CA certificate

Install Ansible collection dependencies with:

ansible-galaxy collection install -r requirements.yml

Quick Demo Flow

On each managed host:

sudo ./scripts/install-local.sh

In IdM, create the demo group, HBAC rule, sudo rule, and SELinux user map:

./scripts/configure-idm-map.sh --print

The script prints the ipa commands by default. Review them, then run them with --apply from a host with an admin ticket.

In AAP, sync inventory from:

inventory/blastwall-idm.yml

Before any mutation job, run:

playbooks/preflight.yml

Then run the managed-host verification:

playbooks/verify-managed-host.yml

Runtime Enforcement Flow

sequenceDiagram
    participant A as AAP
    participant I as IdM / FreeIPA
    participant S as SSSD + pam_selinux
    participant H as Managed RHEL host
    participant K as SELinux kernel policy

    A->>I: Read inventory, HBAC, SELinux map, sudo policy
    I-->>A: Eligible hosts + confinement policy state
    A->>I: hbactest automation identity against host:sshd
    I-->>A: access granted or denied
    A->>H: SSH as automation identity
    H->>S: Resolve SELinux user map at login
    S-->>H: Launch session as blastwall_u:blastwall_r:blastwall_t
    A->>H: Run sudo for local-root task
    H->>K: Enforce blastwall_t permissions
    K-->>H: Deny alg_socket create/bind/send/recv
Loading

Optional Host Suitability Markers

eigenstate.ipa.idm can export IdM host attributes into inventory hostvars. This PoC uses idm_description as a simple marker carrier because it is already available from normal host records.

The policy deployment play writes markers like:

blastwall_policy_rpm=blastwall-selinux-0.1.0-6
blastwall_policy_state=active
blastwall_policy_alg_socket=denied

When coverage expands, add a marker for each enforced surface rather than overloading a single boolean. For example:

blastwall_policy_rpm=blastwall-selinux-0.2.0-1
blastwall_policy_state=active
blastwall_policy_alg_socket=denied
blastwall_policy_userns_create=denied
blastwall_policy_perf_event_open=denied

Inventory groups then split eligible hosts into:

  • blastwall_policy_current
  • blastwall_policy_stale

These markers are supplementary. They help AAP choose the best candidates when several hosts are HBAC-eligible. They do not replace live SELinux, HBAC, sudo, and runtime verification.

flowchart TB
    subgraph LOCAL["1. Host-local proof"]
        DEPLOY["Policy deployment workflow"] --> RPM["Install/update<br/>blastwall-selinux RPM"]
        RPM --> VERIFY["Verify local policy<br/>alg_socket denied"]
    end

    subgraph IDM_STATE["2. Inventory-visible claim"]
        IDM["Update IdM host description marker"] --> INV["eigenstate.ipa.idm inventory sync"]
    end

    subgraph AAP_DECISION["3. AAP target decision"]
        CURRENT{"Marker matches<br/>required policy?"}
        CURRENT -->|yes| GOOD["blastwall_policy_current<br/>preferred candidates"]
        CURRENT -->|no| STALE["blastwall_policy_stale<br/>report/remediate"]
    end

    VERIFY --> IDM
    INV --> CURRENT
Loading

Candidate Selection With Multiple Coverages

When two hosts are both reachable and mapped into blastwall_u, AAP should prefer the host with the newest policy and the coverage required by the job. A job that only needs the Copy Fail mitigation can accept alg_socket=denied; a job responding to a newer exploit can require both the older and newer markers.

flowchart TD
    JOB["AAP job declares required coverage"] --> REQ["Required markers<br/>policy version + scopes"]
    REQ --> H1{"Host A markers<br/>0.1.0 alg_socket"}
    REQ --> H2{"Host B markers<br/>0.2.0 alg_socket + userns"}
    H1 -->|missing new scope| STALE["Eligible but stale<br/>skip or remediate"]
    H2 -->|all required scopes| SELECT["Preferred candidate"]
    STALE --> REMEDIATE["Run policy update workflow"]
    REMEDIATE --> MARKER["Refresh IdM marker"]
    MARKER --> SELECT
Loading

The markers are not proof by themselves. The rollout workflow should update the marker only after local verification confirms the policy is installed and the expected denial is observable.

Type Alias Note

The reference-policy user template creates the concrete domain blastwall_t. The policy also defines blastwall_root_local_t as a type alias so the public PoC context names describe the root-local automation intent. Some SELinux tools canonicalize aliases back to blastwall_t; the verification play accepts both names as long as the SELinux user and role remain blastwall_u and blastwall_r.

Important Limitation

SELinux can block alg_socket use for a confined automation domain. It cannot match authencesn inside struct sockaddr_alg. If the requirement is algorithm-level matching while preserving other AF_ALG use, keep using an eBPF LSM mitigation like Anthony Green's block-copyfail.

flowchart TD
    EXPLOIT["Copy Fail path"] --> SOCK["AF_ALG socket"]
    SOCK --> BIND["bind(authencesn...)"]
    BIND --> VULN["vulnerable kernel path"]

    BPF["block-copyfail<br/>BPF LSM approach"] --> PRECISE["Inspect sockaddr_alg<br/>deny only authencesn"]
    SELINUX["Blastwall<br/>SELinux approach"] --> BROAD["Deny alg_socket class<br/>for automation domain"]

    PRECISE --> SOCK
    BROAD --> SOCK
Loading

This PoC uses a CIL deny module because current distribution policy may grant socket permissions through inherited user-domain attributes. The deny rule subtracts alg_socket access from blastwall_t; the paired neverallow keeps future policy changes from silently restoring the permission.

When To Choose Which

I would use block-copyfail-style BPF LSM when I need the narrowest runtime mitigation and I want to preserve unrelated AF_ALG use. It is shaped around the vulnerable kernel argument, which is exactly why it is useful.

I would use Blastwall when the mitigation should be tied to automation identity, host scope, and the way RHEL estates are actually operated. The advantage is not finer kernel inspection. The advantage is that the same control plane answers:

  • who may automate this host;
  • what SELinux user they receive;
  • what sudo policy they inherit;
  • what exploit surfaces the host policy claims to cover;
  • whether AAP should run, skip, or remediate the host.

The two approaches compose well. BPF LSM gives me precision at the kernel hook. Blastwall gives me an identity-scoped automation boundary and a rollout record that AAP can use before it touches the host.

About

SELinux/IdM proof of concept for confining privileged automation identities and blocking exploit surfaces such as Copy Fail with AAP-aware policy gates.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors