rama 0.3.0

modular service framework
Documentation
# 🍎 Operating Transparent Proxies on macOS

On macOS, transparent proxying is implemented using Apple’s **Network Extension** framework. Unlike Linux, where you directly manipulate packet flow using firewall rules, macOS provides a structured, system-managed environment for intercepting and handling traffic.

This makes the model safer and more controlled, but also more opinionated. You do not intercept packets manually. Instead, you register a system extension that macOS invokes whenever matching traffic flows occur.

## 1. The Network Extension Model

At the core of transparent proxying on macOS is the **`NETransparentProxyProvider`**.

Rather than working with raw packets, macOS gives you **flow-based abstractions**. Each intercepted connection is delivered as an `NEAppProxyFlow`, which you can read from and write to as a stream.

### How to Operate:

To run a transparent proxy on macOS, you need three components:

1. **A Host Application**
   Responsible for installing and managing the proxy configuration using `NETransparentProxyManager`.

2. **A Network Extension**
   A sandboxed system extension that implements `NETransparentProxyProvider` and receives intercepted traffic.

3. **A Proxy Runtime**
   In Rama’s case, this is typically a Rust static library that performs the actual proxying, MITM logic, and routing decisions.

Once installed and started, macOS owns the lifecycle of the extension. Your host application acts only as a controller.

## 2. Lifecycle and Control Plane

Unlike Linux or Windows setups, you do not keep a long running daemon to manage traffic interception.

Instead:

1. The host application installs a **transparent proxy profile**.
2. macOS activates the extension based on that profile.
3. The system automatically starts, stops, and restarts the extension as needed.

This has a few important consequences:

* Your proxy must be **stateless enough to restart cleanly**
* Configuration must be **persisted externally** and passed into the extension
* Failures are handled by the OS, not your process supervisor

A typical control surface exposes commands such as:

* `start` to install and activate the profile
* `stop` to disable it
* `status` to inspect current state

## 3. Traffic Interception

Traffic selection is not done via firewall rules, but through **`NENetworkRule`** objects.

These rules define what traffic should be intercepted. For example:

* All TCP traffic on port 443
* All outbound connections except local network ranges
* Traffic originating from specific applications

Once a rule matches, macOS delivers the connection to your extension as a flow.

From there:

1. The flow is bridged into your proxy runtime
2. Your runtime establishes an upstream connection
3. Data is proxied bidirectionally

Because this is flow based:

* You do not deal with packet fragmentation or reassembly
* You operate at a higher abstraction level than Linux TPROXY
* You still retain full control over routing, MITM, and filtering

## 4. Integrating Rama

In a Rama based setup, the architecture typically looks like this:

<div class="book-article-image-center">

```dot process
digraph macos_transparent_proxy {
    rankdir=TB;
    node [shape=box, style=rounded];

    Host [label="Host Controller\nstart / stop / status"];
    Manager [label="NETransparentProxyManager\nsystem profile"];
    Extension [label="Network Extension\nNETransparentProxyProvider"];
    Provider [label="Rama Apple Provider\nFlow <-> Stream bridge"];
    Runtime [label="Rama Proxy Runtime\nMITM / routing / filtering"];

    Host -> Manager;
    Manager -> Extension;
    Extension -> Provider;
    Provider -> Runtime;
}
```

</div>

Key responsibilities are split as follows:

* **Swift / host layer** manages system integration and lifecycle
* **Network Extension** handles flow delivery
* **Rama** provides the proxy engine and abstraction layer
* **Rust runtime** implements the actual logic

This separation keeps platform specific concerns isolated from your proxy logic.

> **Security note:** The opaque config payload is intended for non-sensitive runtime
> settings only — things like timeouts, domain exclusions, and feature flags.
> Apple logs this payload automatically; it will appear in system diagnostic output
> with no ability to suppress it. Never embed secrets, private keys, or credentials
> in the opaque config. Use the system keychain for any sensitive material
> or transport it over a secure XPC connection yourself.

## 5. TLS and Certificate Authority Modes

Transparent MITM proxies require a Certificate Authority. On macOS, there are two common approaches.

### Self Managed CA

In this mode, the proxy generates and stores its own CA certificate.

* If no CA exists, one is created on first run
* The same CA is reused across restarts
* The certificate must be installed into the system or application trust stores

This mode is useful for:

* Local development
* Testing environments
* Non managed deployments

A common workflow is:

1. Start the proxy
2. Retrieve the generated CA certificate
3. Install it into the system or container environments

### Managed Identity via System Keychain

In managed environments, a centrally issued identity can be used.

In this setup:

1. The device receives a certificate and key via device management
2. The identity is stored in a system keychain access group
3. The proxy retrieves and uses this identity at runtime

This allows:

* Organization wide trust
* Central rotation of certificates
* No per device CA installation steps

One important constraint is that:

* The private key must be usable by the proxy runtime
* If direct export is not allowed, integration must use native APIs

## 6. Deployment and Packaging

On macOS, transparent proxies must be packaged as an **app bundle** containing:

* The host executable
* The embedded Network Extension

Installation typically involves:

1. Building the signed application bundle
2. Copying it into `/Applications`
3. Registering the extension with the system

Once installed:

* The extension appears under **System Settings → Network → Filters & Proxies**
* The user or system can enable or disable it
* The system persists its state across reboots

## 7. Observability and Debugging

macOS uses the unified logging system for both the host and the extension.

Typical workflows include:

* Streaming live logs for development
* Querying recent logs for debugging
* Exporting logs for analysis

Because the extension runs in a sandboxed environment:

* Traditional stdout logging is not sufficient
* You must rely on structured system logs

Be aware that:

* Debug level logging is not persisted by default
* Enabling persistent debug logs should only be done in development environments

## 8. Operational Considerations

Transparent proxying on macOS is powerful, but comes with constraints:

### Sandboxing

The Network Extension runs in a restricted environment:

* Limited filesystem access
* Controlled network capabilities
* Explicit entitlements required

### Lifecycle Ownership

macOS owns the lifecycle:

* Your extension may be restarted at any time
* You must handle reconnects and state recovery

### Application Awareness

Unlike system wide packet interception, macOS provides:

* Application level identity via audit tokens
* The ability to filter based on originating app

This enables fine grained policies such as:

* Proxy browser traffic but not system services
* Apply different rules per application

## 9. Fail Safe Behaviour

As with any transparent proxy, you must define a failure strategy.

On macOS this is especially important because:

* The system will continue routing traffic through your extension
* A broken proxy can disrupt all connectivity

Recommended approach:

* Implement a **fail open strategy** where possible
* Detect TLS handshake failures and bypass selectively
* Cache failing flows to avoid repeated disruption

This is particularly important for applications that:

* Do not trust the system certificate store
* Use certificate pinning
* Embed their own trust roots

---

**Final Note:** macOS provides a clean and structured way to build transparent proxies, but it trades low level control for safety and system integration.

Where Linux gives you raw power with TPROXY, macOS gives you a managed pipeline. Rama fits naturally into this model by bridging the flow based APIs into a familiar proxy runtime, allowing you to focus on policy and behavior rather than platform mechanics.