I work for a company that is replacing their current endpoint security product that uses an NKE implementation for something that will work in future macOS versions. We need to capture various metadata about connections (ip/port, etc.) and also to possibly divert web traffic to a remediation/capture page for dangerous or unwanted (in the IT sense) browser traffic.
Since the content filter APIs won't divert traffic (from what I've read), I have started looking into the Transparent Proxy APIs. I have taken the SimpleFirewall example and replaced the content filtering calls with transparent proxy calls. At this point, my goal is to prove I can get notified of new flows, get metadata about those flows, and eventually see data from those flows.
This is how I am creating the AppProxyProvider:
override func startProxy(options: [String : Any]? = nil, completionHandler: @escaping (Error?) -> Void) {
let includeRule = NENetworkRule(destinationHost: NWHostEndpoint(hostname: "sophostest.com", port: "80"), protocol: .TCP)
let proxySettings = NETransparentProxyNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
proxySettings.includedNetworkRules = [includeRule]
setTunnelNetworkSettings(proxySettings) { error in
if let applyError = error {
os_log("Failed to apply proxy settings: %{public}@", log:.networkExtension, applyError.localizedDescription)
}
completionHandler(error)
}
}
To make things simple, I am just looking at one sites traffic on one port, as to not overwhelm the logging/developer. 🙂 My main area of concern with this code is the creation of the NETransparentProxyNetworkSettings object. If I don't supply a tunnelRemoteAddress or if I try to supply a port, it doesn't "connect" and I get this error in the console
Failed to apply proxy settings: Missing NETunnelNetworkSettings tunnelRemoteAddress
At this point, I am not trying to proxy the data at all. I am assuming that the app would be able to view the traffic before deciding if it was going to proxy it.
For some reason I have to go into the network control panel to enable the connection (like a VPN). I'm not sure why this needs to happen, and it is something I will need to automate eventually. I can't rely on the user to do this.
My handleNewFlow looks like this
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
if let tcpflow = flow as? NEAppProxyTCPFlow
{
os_log("tcpflow handled - %{public}@",log:.networkExtension, tcpflow.metaData.debugDescription)
}
else if let udpflow = flow as? NEAppProxyUDPFlow
{
os_log("udpflow handled - %{public}@", log:.networkExtension, udpflow.metaData.debugDescription)
}
else
{
os_log("flow handled - %{public}@",log:.networkExtension, flow.metaData.debugDescription)
}
return false
}
As you can see if this method is called, something should be sent to os_log. When I browse to sophostest.com with the transparent proxy started and connected (via the network control panel), I get an "operation not permitted" page in safari (understandable) and no os_log messages at all.