I have a macOS app with Network Extension. It requests VPN permission with the code like this:
self.tunnelManager = [NETunnelProviderManager new];
NETunnelProviderProtocol *protocol = [NETunnelProviderProtocol new];
protocol.providerBundleIdentifier = @"com.myapp.macos.tunnelprovider";
self.tunnelManager.protocolConfiguration = protocol;
[self.tunnelManager setOnDemandRules:nil];
[self.tunnelManager setOnDemandEnabled:NO];
[self.tunnelManager setEnabled:YES];
[self.tunnelManager saveToPreferencesWithCompletionHandler:^(NSError * _Nullable saveError) {}];
A lot of my app users are businesses and they would like to have pre-install VPN config. We currently do it like this:
<array>
<dict>
<key>PayloadDisplayName</key>
<string>MyAppName</string>
<key>PayloadType</key>
<string>com.apple.vpn.managed</string>
<key>UserDefinedName</key>
<string>MyAppName</string>
<key>VPN</key>
<dict>
<key>AuthenticationMethod</key>
<string>Password</string>
<key>ProviderBundleIdentifier</key>
<string>com.myapp.macos.tunnelprovider</string>
<key>ProviderDesignatedRequirement</key>
<string>anchor apple generic and identifier "com.myapp.macos.tunnelprovider" and (certificate leaf[field.1.2.3] /* exists */ or certificate 1[field.1.2.3] /* exists */ and certificate leaf[field.1.2.3] /* exists */ and certificate leaf[subject.OU] = "123")</string>
<key>RemoteAddress</key>
<string/>
</dict>
<key>VPNSubType</key>
<string>com.myapp.macos</string>
<key>VPNType</key>
<string>VPN</string>
</dict>
</array>
Now, if the users installs my app first and allows the VPN permission, then MDM will set the profile above to the user, the user will end up with two VPN profiles in settings. They will be called "My App" and "My App 1"
At first we thought it's harmless, but users with two VPN profiles sometimes have app update issues, where after update the newer version of client fails to communicate with the older version of tunnel, it cannot even tell it to quit. The tunnel must be force-quit by the user in this case.
We suspect two profiles to be the reason for that. Is there a way to make sure duplicate VPN profiles do not happen?
Post
Replies
Boosts
Views
Activity
For browser extension to communicate with a native app there must be a helper app. It is launched by the browser and the communication happens via stdin and stdout. I wrote such a helper app in Swift, it works.
I'd like to add security checks to the helper app.
To make sure that the parent process is one of the approved browsers - I can do this with NSRunningApplication(processIdentifier: getppid())?.bundleIdentifier
To make sure the parent process has valid signature
To make sure that the other peer of the stdin/stdout pipes is the parent process
Do you know ways to achieve 2 and 3?
Does the way I am doing 1 look correct to you?
Context:
Our app has a network system extension. After updating it via MDM sometimes the app and the extension have different versions until you restart the app. I suspect that it is because the app is not quit before installing.
As a fix I am trying to create a .pkg with preinstall and postinstall script, that would close the app before installation and open it after installation.
My code:
FileUtils.mkdir_p("install_scripts")
File.open('install_scripts/preinstall', 'w') do |file|
file.puts "#!/bin/bash\nosascript -e 'quit app \"#{options[:app_name]}\"'\nexit 0"
end
File.open('install_scripts/postinstall', 'w') do |file|
file.puts "#!/bin/bash\nopen -a '#{options[:app_name]}'\nexit 0'"
end
sh "chmod a+x install_scripts/preinstall"
sh "chmod a+x install_scripts/postinstall"
sh(
"pkgbuild",
"--scripts", "install_scripts",
"--identifier", ***_identifier(options),
"--component", "../#{options[:build_path]}/#{options[:app_name]}.app",
"--install-location", "/Applications/#{options[:app_name]}.app",
"Distribution.pkg"
)
sh(
"productbuild",
"--synthesize",
"--package", "Distribution.pkg",
"Distribution.xml"
)
sh(
"productbuild",
"--distribution", "Distribution.xml",
"--sign", "Developer ID Installer: *** Inc. (#{ENV["APPLE_TEAM_ID"]})",
"--package-path", ".",
"../artifacts/#{options[:app_name]}.pkg"
)
Issue:
I haven't got it to actually install the app or to close or open the running app. I've tried several changes. The code above fails with this error. Do you see an issue in my code, or could you point me to alternative implementation?
installd[1101]: PackageKit: Install Failed: Error Domain=NSCocoaErrorDomain Code=516 ""Myapp Dev.app" couldn't be moved to "Applications" because an item with the same name already exists." UserInfo={NSSourceFilePathErrorKey=/Library/InstallerSandboxes/.PKInstallSandboxManager/8E84CB81-4724-4D4E-BE76-3A24DECC60A6.activeSandbox/Root/Applications/Myapp Dev.app/Myapp Dev.app, NSUserStringVariant=(
Move
), NSDestinationFilePath=/Library/InstallerSandboxes/.PKInstallSandboxManager/8E84CB81-4724-4D4E-BE76-3A24DECC60A6.activeSandbox/Root/Applications/Myapp Dev.app, NSFilePath=/Library/InstallerSandboxes/.PKInstallSandboxManager/8E84CB81-4724-4D4E-BE76-3A24DECC60A6.activeSandbox/Root/Applications/Myapp Dev.app/Myapp Dev.app, NSUnderlyingError=0x117e304b0 {Error Domain=NSPOSIXErrorDomain Code=17 "File exists"}} {
NSDestinationFilePath = "/Library/InstallerSandboxes/.PKInstallSandboxManager/8E84CB81-4724-4D4E-BE76-3A24DECC60A6.activeSandbox/Root/Applications/Myapp Dev.app";
NSFilePath = "/Library/InstallerSandboxes/.PKInstallSandboxManager/8E84CB81-4724-4D4E-BE76-3A24DECC60A6.activeSandbox/Root/Applications/Myapp Dev.app/Myapp Dev.app";
NSSourceFilePathErrorKey = "/Library/InstallerSandboxes/.PKInstallSandboxManager/8E84CB81-4724-4D4E-BE76-3A24DECC60A6.activeSandbox/Root/Applications/Myapp Dev.app/Myapp Dev.app";
NSUnderlyingError = "Error Domain=NSPOSIXErrorDomain Code=17 "File exists"";
NSUserStringVariant = (
Move
);
}
I have folder monitoring code using makeFileSystemObjectSource.
The events are triggered for everything else, but not for when I edit a file with nano terminal command.
Am I doing something wrong? Is it a bug? Is this intended and unfixable?
Code sample:
monitoredFolderFileDescriptor = open(currentlyMonitoredPath, O_EVTONLY)
folderMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: monitoredFolderFileDescriptor, eventMask: .all, queue: .main)
folderMonitorSource?.setEventHandler {
// ...
}
folderMonitorSource?.setCancelHandler {
// ...
}
folderMonitorSource?.resume()
Before:
We had an app with app extension. Both had user privilege. Both wrote file logs to FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupID) - /Users/myuser/Library/Group Containers/mygroupid/
Now:
We have to change app extension to system extension. Our previous logging approach broke, because system extension has root context. Result of FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupID) for system extension is /private/var/root/Library/Group Containers/mygroupid/
They do not have privilege to write to each other's folder. We can open logs folder for the user, but now the app does not have privilege to open Finder window for root logs folder. Ideally we would write file in a single folder.
Question:
Please suggest where to write logs from user and root process. Maybe there is a different approach on how to store a few days worth of logs and being able to upload them to our backend, or display them to the user, upon request.
I need to store auth keys somewhere, previously app network extension would store them in a shared keychain. Now we're trying to move to system extensions, for out of appstore distribution, and shared keychain will no longer work.
Is it possible to write to system keychain from system extension? If yes, how do I specify that I want to use system keychain?
Our current code returns errSecNotAvailable if run in System Extension instead of App Extension. The code looks like this. If uncommented, it will work from the App Extension.
NSString *teamID = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"Development Team"];
NSString *groupID = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"App Group ID"];
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:@{
(id)kSecClass: (id)kSecClassGenericPassword,
// (id)kSecAttrAccessGroup: [NSString stringWithFormat:@"%@.%@", teamID, groupID],
(id)kSecAttrService: groupID,
// (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
}];
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecUseDataProtectionKeychain];
[query setObject:@(key) forKey:(id)kSecAttrAccount];
[query setObject:[NSData dataWithBytes:buffer length:length] forKey:(id)kSecValueData];
SecItemAdd(cfQuery, NULL);
I have a working NEPacketTunnelProvider app extension macOS app on the App Store. The company wants to explore the possibility of switching to system extension, so that we can distribute the app outside of the appstore too.
I managed to do the switch and the extension works. But the communication is broken. DistributedNotificationCenter stopped working for me after switching to system extension, events are not received and I don't see any errors so I cannot say what's wrong.
I tried to adopt XPC from this Filtering Network Traffic Apple's sample, but I get sandbox error - domain code 4099, failed at lookup with error 159 - Sandbox restriction.
I get the same error if I try to run the sample with my company team id. I do these changes:
NEMachServiceName to $(TeamIdentifierPrefix)com.mycompanyname.macos.dev
App Groups to $(TeamIdentifierPrefix)com.mycompanyname.macos.dev
Bundle ids to com.mycompanyname.macos.dev and com.mycompanyname.macos.dev.tunnelprovider
com.mycompanyname.macos.dev has capabilities - App Groups, Network Extensions, System Extensions
com.mycompanyname.macos.dev.tunnelprovider - Network Extensions, System Extensions
Could you help me find the reason why DistributedNotificationCenter could stop receiving notifications?
Or are you able to run Apple's sample? What changes do you make to run it under your team? Because it looks like my changes are wrong
Either DistributedNotificationCenter or XPC would solve my problem