Network Extension. Per-App VPN

Hello everyone!

I'm trying to set up NEAppProxyProvider according to this answer. I want to use App Proxy without MDM (for testing purpose). I've added NETestAppMapping key to Info.plist of my container app, so it looks like:


<key>NETestAppMapping</key>
<dict>
    <key>A2B6DB59-405C-4B6D-B9CD-6E25CFE865E3</key>
    <array>
         <string>ua.ky1vstar.testapp</string>
    </array>
</dict>


I build and install my app, but when I'm trying to install Configuration Profile through Apple Configurator 2/my iPhone says me that it's incorrect and can't be installed. What am I doing wrong? My container app bundle id is ua.ky1vstar.appproxy1 and my App Proxy Provider extension bundle id is ua.ky1vstar.appproxy1.httpproxy. Here is my mobileconfig:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
     <key>PayloadContent</key>
     <array>
          <dict>
               <key>PayloadContent</key>
               <array>
                    <dict>
                         <key>OverridePrimary</key>
                         <true/>
                         <key>ProviderType</key>
                         <string>app-proxy</string>
                         <key>UserDefinedName</key>
                         <string>AppHTTPProxyProvider</string>
                         <key>VPNSubType</key>
                         <string>ua.ky1vstar.appproxy1.httpproxy</string>
                         <key>VPNType</key>
                         <string>VPN</string>
                         <key>VPNUUID</key>
                         <string>A2B6DB59-405C-4B6D-B9CD-6E25CFE865E3</string>
                    </dict>
               </array>
               <key>PayloadDescription</key>
               <string>Provides customization of NEAppProxyProvider.</string>
               <key>PayloadDisplayName</key>
               <string>Advanced NEAppProxyProvider</string>
               <key>PayloadIdentifier</key>
               <string>custom.proxy.app</string>
               <key>PayloadOrganization</key>
               <string>KY1VSTAR</string>
               <key>PayloadType</key>
               <string>com.apple.vpn.managed.applayer</string>
               <key>PayloadUUID</key>
               <string>85004A94-9894-448E-8FE5-333652337EA3</string>
               <key>PayloadVersion</key>
               <integer>1</integer>
          </dict>
     </array>
     <key>PayloadDescription</key>
     <string>AppHTTPProxyProvider Profile</string>
     <key>PayloadDisplayName</key>
     <string>AppHTTPProxyProvider Profile</string>
     <key>PayloadIdentifier</key>
     <string>custom.proxy</string>
     <key>PayloadOrganization</key>
     <string>iphone.flexserve.net</string>
     <key>PayloadRemovalDisallowed</key>
     <false/>
     <key>PayloadType</key>
     <string>Configuration</string>
     <key>PayloadUUID</key>
     <string>9152B314-947F-475E-B09E-0536BF62ABCD</string>
     <key>PayloadVersion</key>
     <integer>1</integer>
</dict>
</plist>

Replies

You seem to be having problems with the overall structure of your profile. For example, you have one

PayloadContent
dictionary nested inside another
PayloadContent
, which isn’t right. I don’t have time right now to work through the details but pasted in below you’ll find a profile that I used for my own app proxy provider test app.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

WWDC runs Mon, 5 Jun through to Fri, 9 Jun. During that time all of DTS will be at the conference, helping folks out face-to-face. http://developer.apple.com/wwdc/

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PayloadContent</key>
    <array>
        <dict>
            <key>PayloadUUID</key>
            <string>E6671FFB-66C2-49F7-AB1B-CD5A0AB4EE26</string>
            <key>PayloadType</key>
            <string>com.apple.vpn.managed.applayer</string>
            <key>PayloadIdentifier</key>
            <string>com.apple.vpn.managed.applayer.388257C2-7902-42B5-BDAE-6E69A441C3A2</string>
            <key>VPNType</key>
            <string>VPN</string>
            <key>VPNSubType</key>
            <string>com.example.apple-samplecode.QNE-iOS.AppProxy</string>
            <key>UserDefinedName</key>
            <string>QNEAppProxy</string>
            <key>PayloadDescription</key>
            <string>Configures VPN settings</string>
            <key>PayloadDisplayName</key>
            <string>VPN</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>VPNUUID</key>
            <string>825886EA-BB00-4805-ADD6-1674C531669E</string>
            <key>VPN</key>
            <dict>
                <key>RemoteAddress</key>
                <string>example.com</string>
                <key>AuthenticationMethod</key>
                <string>Password</string>
                <key>AuthName</key>
                <string>mrgumby</string>
                <key>AuthPassword</key>
                <string>opendoor</string>
            </dict>
            <key>Proxies</key>
            <dict>
                <key>HTTPEnable</key>
                <integer>0</integer>
                <key>HTTPSEnable</key>
                <integer>0</integer>
            </dict>
            <key>VendorConfig</key>
            <dict>
                <key>dummy</key>
                <integer>1</integer>
            </dict>
            <key>SafariDomains</key>
            <array>
                <string>httpbin.org</string>
            </array>
        </dict>
    </array>
    <key>PayloadDisplayName</key>
    <string>QNEAppProxy Test</string>
    <key>PayloadIdentifier</key>
    <string>com.example.apple-samplecode.QNE-iOS.AppProxy.298D4B2D-5961-433B-B204-E8A784E10C89</string>
    <key>PayloadOrganization</key>
    <string>Apple</string>
    <key>PayloadRemovalDisallowed</key>
    <false/>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadUUID</key>
    <string>C4B10884-86B3-4766-ADE1-9A5FC843F666</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
</dict>
</plist>

Hello Quinn, related to your sample configuration profile above, why did you include that VPN dictionary in the payload. I only saw the need to include a VPN dictionary on payloads with VPNType values of "IPSec", "PPP" and "IKEv2" (Not the value "VPN" which requires VPNSubType instead). I also noticed that you didnt include the ProviderType key in the configuration profile. Is there a reason why?

Hi Quinn, could you please also paste a profile with a dict of appmapping embeded? I'm working on a per-app proxy running on MAC OSX. The profile with SafariDomains works for me but I don't know how to redirect other applications' flow to the app proxy.
Here is the profile I used:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PayloadContent</key>
    <array>
        <dict>
            <key>IPv4</key>
            <dict>
                <key>OverridePrimary</key>
                <integer>0</integer>
            </dict>
            <key>PayloadDescription</key>
            <string>Configures VPN settings</string>
            <key>PayloadDisplayName</key>
            <string>VPN</string>
            <key>PayloadIdentifier</key>
            <string>com.apple.vpn.managed.applayer.330FBB83-639F-4F9E-9FA1-4FAC93E18B68</string>
            <key>PayloadType</key>
            <string>com.apple.vpn.managed.applayer</string>
            <key>PayloadUUID</key>
            <string>330FBB83-639F-4F9E-9FA1-4FAC93E18B68</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>Proxies</key>
            <dict>
                <key>HTTPEnable</key>
                <integer>0</integer>
                <key>HTTPSEnable</key>
                <integer>0</integer>
            </dict>
            <key>UserDefinedName</key>
            <string>appmapping</string>
            <key>VPN</key>
            <dict>
                <key>AuthName</key>
                <string>somebody</string>
                <key>AuthPassword</key>
                <string>opendoor</string>
                <key>AuthenticationMethod</key>
                <string>Password</string>
                <key>ProviderBundleIdentifier</key>
                <string>com.blob.macappproxy.macappproxy</string>
                <key>ProviderType</key>
                <string>app-proxy</string>
                <key>RemoteAddress</key>
                <string>127.0.0.1</string>
            </dict>
            <key>VPNSubType</key>
            <string>com.blob.macappproxy</string>
            <key>VPNType</key>
            <string>VPN</string>
            <key>OnDemandMatchAppEnabled</key>
            <integer>1</integer>
            <key>VendorConfig</key>
            <dict/>
            <key>VPNUUID</key>
            <string>3D7A07D8-97D0-4E5A-BB04-1EB82DD12A35</string>
        </dict>
        <dict>
            <key>PayloadDescription</key>
            <string>Configures Per APP VPN mapping</string>
            <key>PayloadDisplayName</key>
            <string>Per APP VPN mapping</string>
            <key>PayloadIdentifier</key>
            <string>com.apple.vpn.managed.appmapping.A88E1A77-2CC2-4BF9-879C-97C3DF491EB2</string>
            <key>PayloadType</key>
            <string>com.apple.vpn.managed.appmapping</string>
            <key>PayloadUUID</key>
            <string>A88E1A77-2CC2-4BF9-879C-97C3DF491EB2</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>UserDefinedName</key>
            <string>perappvpn</string>
            <key>AppLayerVPNMapping</key>
            <array>
                <dict>
                    <key>Identifier</key>
                    <string>com.google.Chrome</string>
                    <key>VPNUUID</key>
                    <string>3D7A07D8-97D0-4E5A-BB04-1EB82DD12A35</string>
                    <key>DesignatedRequirement</key>
                    <string>(identifier &quot;com.google.Chrome&quot; or identifier &quot;com.google.Chrome.beta&quot; or identifier &quot;com.google.Chrome.dev&quot; or identifier &quot;com.google.Chrome.canary&quot;) and certificate leaf = H&quot;c9a99324ca3fcb23dbcc36bd5fd4f9753305130a&quot;</string>
                    <key>SigningIdentifier</key>
                    <string>com.google.Chrome</string>
                </dict>
            </array>
        </dict>
    </array>
    <key>PayloadDisplayName</key>
    <string>some app proxy</string>
    <key>PayloadIdentifier</key>
    <string>blob-MacBook-Pro.A953E629-CD95-45B4-A42D-ECA2BA870A79</string>
    <key>PayloadRemovalDisallowed</key>
    <false/>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadUUID</key>
    <string>1AEA709E-46D3-4293-B1E3-23EB8DD5B361</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
</dict>
</plist>


could you please also paste a profile with a dict of appmapping embeded?

Here’s the App-to-Per-App VPN Mapping (

com.apple.vpn.managed.appmapping
) payload that I use for my tests.
<dict>
    <key>PayloadUUID</key>
    <string>A959CFCB-BABF-4819-B2A6-41F95926AF78</string>
    <key>PayloadType</key>
    <string>com.apple.vpn.managed.appmapping</string>
    <key>PayloadIdentifier</key>
    <string>com.apple.vpn.managed.appmapping.663DE2E8-0B7D-46D7-B1AE-331985F4082B</string>
    <key>PayloadDescription</key>
    <string>Configures TestApp-macOS to use the per-app VPN connection.</string>
    <key>PayloadDisplayName</key>
    <string>Per-App VPN App Mappings</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
    <key>AppLayerVPNMapping</key>
    <array>
        <dict>
            <key>VPNUUID</key>
            <string>83A8720B-2BD5-4B7E-921D-3D9E312B4ACD</string>
            <key>Identifier</key>
            <string>com.example.apple-samplecode.QNEAppProxyTestApp-macOS</string>
            <key>SigningIdentifier</key>
            <string>com.example.apple-samplecode.QNEAppProxyTestApp-macOS</string>
            <key>DesignatedRequirement</key>
            <string>identifier &quot;com.example.apple-samplecode.QNEAppProxyTestApp-macOS&quot; and anchor apple generic</string>
        </dict>
    </array>
</dict>

Some notes:

  • I last tested this specific setup back in mid 2018, so probably with Xcode 9.x and macOS 10.13.x.

  • Rather than trying to target a complex app like Chrome, start by targeting a very simple app.

  • The best way to get a really simple app is to create it yourself.

  • You can get the

    SigningIdentifier
    and
    DesignatedRequirement
    (DR) properties using the
    codesign
    tool.
    $ codesign -d -vvv --requirements - TestApp-macOS.app
    …
    Identifier=com.example.apple-samplecode.QNEAppProxyTestApp-macOS
    …
    designated => identifier "com.example.apple-samplecode.QNEAppProxyTestApp-macOS" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: Quinn Quinn (7XFU7D52S4)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */

    As you can see, the DR is somewhat different from the example I posted, which is probably an artefact of tooling drift.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn, thanks for your response.

I compared your appmapping profile with mine word by word and can't find any difference except app's name and UUIDs. But it just doesn't work for me.
As your suggestion I do the following things and checked some key points:

  • created a local tcp client connect to a remote host and exchange some stream and got its DR by using the `codesign` tool. The platform is macOS Mojave 10.14.5 and Xcode 10.2.1(10E1001). Set the VPNUUID/identifier/SigningIdentifier/DR into the appmapping dict in the config profile.
  • As you said on this thread to test app proxy provider running on macOS all need to do is to compose a correct profile( a .mobileconfig file) . So
    NETestAppMapping
    property is checked and removed from all info.plist in my project since it only works for iOS. I also tried with NETestAppMapping settings in info.plist according to this and it doesn't work either.
  • I also checked the VPNUUID. It's generated by apple configurator 2. Have also tried the VPNUUID in your profile and let the VPNUUID in AppLayerVPNMapping to be the same value as in the com.apple.vpn.managed.applayer dict.

It seems I have come to a dead end.

Now my questions are as following:

  • Are there any special requirements for the app whose flow will be captured by the app proxy provider?
  • After the profile installed how many settings will be seen in System Preferences->Profiles? Since there're 2 dicts(one is com.apple.vpn.managed.applayer dict and one is com.apple.vpn.managed.appmapping dict) in the profile so I deem there should be 2 settings shown for this profile? But I always see only 1 setting in that profile.
  • I notice that in your profile that \" is written as "&quot;". Should it be like this? Should I copy the whole DR output of codesign into the profile including the comment "/* exists */" at the end?
  • Could you please share your whole test project for appmapping including the whole config profile? It will be great helpful. As in this link SimpleTunnel there isn't any profile example included. And I've googled around just found no one ever shared a workable profile.

Thanks a lot!

It seems I have come to a dead end.

Bummer. At this point my recommendation is that you open a DTS tech support incident so that I can look at the issue in detail.

Note DTS is super busy this week helping out with WWDC preparations, and will be at the conference all next week. If you’re attending WWDC, come by the lab and I can look at your issue in person. If not, it’s most likely going to have to wait until after the show.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hello,

please, I am trying to make App Proxy work as a part of transition from kext to system extensions in 10.15+, however I seem to be unable to configure the NEAppProxyProviderManager via the configuration profile installation.

Since there is a lot of stuff that may be wrong (beta of 10.15, beta of xcode, Apple Development certificate vs Mac Development certificate, system extension instead of extension), I would like to ask: what is the minimal setup to test a configuration profile? EG. lets say I create a default cocoa app, enable network extension (app proxy) in entitlements, set up signing using provisioning profile with Network Extensions capability enabled, add this to the generated view controller:

    override func viewDidLoad() {
        super.viewDidLoad()
        
        NEAppProxyProviderManager.loadAllFromPreferences { [weak self] (savedManagers: [NEAppProxyProviderManager]?, error: Error?) in
            guard error == nil else {
                os_log("loadAllFromPreferences failed with %@", error!.localizedDescription)
                return
            }
            if (savedManagers != nil) {
                let count = savedManagers!.count
                os_log("managers count %@", savedManagers!.count)
            }
        }
    }

I take the profile from your first answer to the original post of this thread (eskimo May 23, 2017 4:54 AM), replace "com.example.apple-samplecode.QNE-iOS.AppProxy” by “com.my.se.test” which is bundle id of the new app. I build the app in xcode, install the profile via

sudo profiles install -path=my_modified.confprofile

which seems to succeed, the profile is visible in the installed list. After that run the app from the xcode. I always get the log "managers count (null)” - the configuration is not loaded. Should the configuration be visible in this case?

Thanks for help,

Ondra