mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-30 18:29:21 +08:00
VNet daemon: Set code signing requirements (#44639)
* Move Go error definitions to common_darwin.go Both client_darwin.go and serivce_darwin.go are going to use them, so it doesn't make sense to keep them in either of those files or scattered across them. * Roll protocol_darwin into common_darwin There's little need for those two files to be kept separate. * Add function for getting code signing requirement Both the client and the daemon are going to set a requirement towards each other, hence why this function is in common. * Set code signing requirement towards daemon client * Set code signing requirement towards daemon service * Add info about mdfind to README * Keep #cgo declarations in a single file * Fix typos * Move const to where it's used
This commit is contained in:
@@ -181,6 +181,15 @@ launch the daemon with the following error:
|
||||
|
||||
After resetting the db and restarting the device, everything seemed to be working again.
|
||||
|
||||
In theory, it's possible to list all app bundles with a certain bundle identifier by running the
|
||||
following command:
|
||||
|
||||
```
|
||||
mdfind kMDItemCFBundleIdentifier = "com.goteleport.tshdev"
|
||||
```
|
||||
|
||||
In practice, getting rid of all but one bundle didn't appear to solve the problem.
|
||||
|
||||
### Daemon does not start
|
||||
|
||||
List all jobs loaded into launchd. The second column is the status which you can then inspect.
|
||||
|
||||
@@ -19,11 +19,9 @@
|
||||
|
||||
package daemon
|
||||
|
||||
// #cgo CFLAGS: -Wall -xobjective-c -fblocks -fobjc-arc -mmacosx-version-min=10.15
|
||||
// #cgo LDFLAGS: -framework Foundation -framework ServiceManagement
|
||||
// #include <stdlib.h>
|
||||
// #include "client_darwin.h"
|
||||
// #include "protocol_darwin.h"
|
||||
// #include "common_darwin.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
@@ -304,6 +302,28 @@ func startByCalling(ctx context.Context, bundlePath string, config Config) error
|
||||
return
|
||||
}
|
||||
|
||||
if errorDomain == nsCocoaErrorDomain && errorCode == errorCodeNSXPCConnectionInterrupted {
|
||||
const clientNSXPCConnectionInterruptedDebugMsg = "The connection was interrupted when trying to " +
|
||||
"reach the XPC service. If there's no clear error logs on the daemon side, it might mean that " +
|
||||
"the client does not satisfy the code signing requirement enforced by the daemon. " +
|
||||
"Start capturing logs in Console.app and repeat the scenario. Look for " +
|
||||
"\"xpc_support_check_token: <private> error: <private> status: -67050\" in the logs to verify " +
|
||||
"that the connection was interrupted due to the code signing requirement."
|
||||
log.DebugContext(ctx, clientNSXPCConnectionInterruptedDebugMsg)
|
||||
errC <- trace.Wrap(errXPCConnectionInterrupted)
|
||||
return
|
||||
}
|
||||
|
||||
if errorDomain == vnetErrorDomain && errorCode == errorCodeMissingCodeSigningIdentifiers {
|
||||
errC <- trace.Wrap(errMissingCodeSigningIdentifiers)
|
||||
return
|
||||
}
|
||||
|
||||
if errorDomain == nsCocoaErrorDomain && errorCode == errorCodeNSXPCConnectionCodeSigningRequirementFailure {
|
||||
errC <- trace.Wrap(errXPCConnectionCodeSigningRequirementFailure, "the daemon does not appear to be code signed correctly")
|
||||
return
|
||||
}
|
||||
|
||||
errC <- trace.Errorf("could not start VNet daemon: %v", C.GoString(res.error_description))
|
||||
return
|
||||
}
|
||||
@@ -319,15 +339,6 @@ func startByCalling(ctx context.Context, bundlePath string, config Config) error
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// vnetErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet.
|
||||
vnetErrorDomain = C.GoString(C.VNEErrorDomain)
|
||||
// errorCodeAlreadyRunning is returned within [vnetErrorDomain] errors to indicate that the daemon
|
||||
// received a message to start after it was already running.
|
||||
errorCodeAlreadyRunning = int(C.VNEAlreadyRunningError)
|
||||
errAlreadyRunning = errors.New("VNet is already running")
|
||||
)
|
||||
|
||||
func sleepOrDone(ctx context.Context, d time.Duration) error {
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define TELEPORT_LIB_VNET_DAEMON_CLIENT_DARWIN_H_
|
||||
|
||||
#include "common_darwin.h"
|
||||
#include "protocol_darwin.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@@ -44,10 +43,15 @@ typedef struct StartVnetRequest {
|
||||
|
||||
typedef struct StartVnetResult {
|
||||
bool ok;
|
||||
// error_domain is either VNEErrorDomain, NSOSStatusErrorDomain, or NSCocoaErrorDomain.
|
||||
const char *error_domain;
|
||||
// error_code is code taken from an NSError instance encountered during the call to StartVnet.
|
||||
// If ok is false, error_code is greater than zero and identifies the reason behind the error.
|
||||
// If error_domain is set to VNEErrorDomain, error_code is one of the VNE codes from common_darwin.h.
|
||||
// If error_domain is NSOSStatusErrorDomain, error_code comes from OSStatus of Code Signing framework.
|
||||
// https://developer.apple.com/documentation/security/1574088-code_signing_services_result_cod?language=objc
|
||||
// If error_domain is NSCocoaErrorDomain, it's likely to be about XPC. It's best to inspect it
|
||||
// on https://osstatus.com in that case.
|
||||
int error_code;
|
||||
// error_description includes the full representation of the error, including domain and code.
|
||||
const char *error_description;
|
||||
} StartVnetResult;
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include "client_darwin.h"
|
||||
#include "common_darwin.h"
|
||||
#include "protocol_darwin.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ServiceManagement/ServiceManagement.h>
|
||||
@@ -78,15 +77,17 @@ void OpenSystemSettingsLoginItems(void) {
|
||||
|
||||
@property(nonatomic, strong, readwrite) NSXPCConnection *connection;
|
||||
@property(nonatomic, strong, readonly) NSString *bundlePath;
|
||||
@property(nonatomic, strong, readonly) NSString *codeSigningRequirement;
|
||||
|
||||
@end
|
||||
|
||||
@implementation VNEDaemonClient
|
||||
|
||||
- (id)initWithBundlePath:(NSString *)bundlePath {
|
||||
- (id)initWithBundlePath:(NSString *)bundlePath codeSigningRequirement:(NSString *)codeSigningRequirement {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_bundlePath = bundlePath;
|
||||
_codeSigningRequirement = codeSigningRequirement;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -102,6 +103,12 @@ void OpenSystemSettingsLoginItems(void) {
|
||||
self->_connection = nil;
|
||||
};
|
||||
|
||||
// The daemon won't even be started on macOS < 13.0, so we don't have to handle the else branch
|
||||
// of this condition.
|
||||
if (@available(macOS 13, *)) {
|
||||
[_connection setCodeSigningRequirement:_codeSigningRequirement];
|
||||
}
|
||||
|
||||
// New connections always start in a suspended state.
|
||||
[_connection resume];
|
||||
}
|
||||
@@ -134,7 +141,18 @@ static VNEDaemonClient *daemonClient = NULL;
|
||||
|
||||
void StartVnet(StartVnetRequest *request, StartVnetResult *outResult) {
|
||||
if (!daemonClient) {
|
||||
daemonClient = [[VNEDaemonClient alloc] initWithBundlePath:@(request->bundle_path)];
|
||||
NSString *requirement = nil;
|
||||
NSError *error = nil;
|
||||
bool ok = getCodeSigningRequirement(&requirement, &error);
|
||||
if (!ok) {
|
||||
outResult->ok = false;
|
||||
outResult->error_domain = VNECopyNSString([error domain]);
|
||||
outResult->error_code = (int)[error code];
|
||||
outResult->error_description = VNECopyNSString([error description]);
|
||||
return;
|
||||
}
|
||||
|
||||
daemonClient = [[VNEDaemonClient alloc] initWithBundlePath:@(request->bundle_path) codeSigningRequirement:requirement];
|
||||
}
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
@@ -142,6 +160,7 @@ void StartVnet(StartVnetRequest *request, StartVnetResult *outResult) {
|
||||
[daemonClient startVnet:request->vnet_config
|
||||
completion:^(NSError *error) {
|
||||
if (error) {
|
||||
outResult->ok = false;
|
||||
outResult->error_domain = VNECopyNSString([error domain]);
|
||||
outResult->error_code = (int)[error code];
|
||||
outResult->error_description = VNECopyNSString([error description]);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2024 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build vnetdaemon
|
||||
// +build vnetdaemon
|
||||
|
||||
package daemon
|
||||
|
||||
// #cgo CFLAGS: -Wall -xobjective-c -fblocks -fobjc-arc -mmacosx-version-min=10.15
|
||||
// #cgo LDFLAGS: -framework Foundation -framework ServiceManagement
|
||||
// #include "common_darwin.h"
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// vnetErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet.
|
||||
vnetErrorDomain = C.GoString(C.VNEErrorDomain)
|
||||
|
||||
// errorCodeAlreadyRunning is returned within [vnetErrorDomain] errors to indicate that the daemon
|
||||
// received a message to start after it was already running.
|
||||
errorCodeAlreadyRunning = int(C.VNEAlreadyRunningError)
|
||||
errAlreadyRunning = errors.New("VNet is already running")
|
||||
|
||||
// errorCodeMissingCodeSigningIdentifiers is returned within [vnetErrorDomain] Obj-C errors and
|
||||
// transformed to [errMissingCodeSigningIdentifiers] in Go.
|
||||
errorCodeMissingCodeSigningIdentifiers = int(C.VNEMissingCodeSigningIdentifiersError)
|
||||
errMissingCodeSigningIdentifiers = errors.New("either identifier or team identifier is missing in code signing information; is the binary signed?")
|
||||
)
|
||||
|
||||
var (
|
||||
// nsCocoaErrorDomain is a generic error domain used in a lot of Apple's Cocoa frameworks.
|
||||
nsCocoaErrorDomain = "NSCocoaErrorDomain"
|
||||
|
||||
// https://developer.apple.com/documentation/foundation/1448136-nserror_codes/nsxpcconnectioninterrupted?changes=latest_major&language=objc
|
||||
errorCodeNSXPCConnectionInterrupted = int(C.NSXPCConnectionInterrupted)
|
||||
errXPCConnectionInterrupted = errors.New("XPC connection interrupted")
|
||||
|
||||
// https://developer.apple.com/documentation/foundation/1448136-nserror_codes/nsxpcconnectioncodesigningrequirementfailure?language=objc
|
||||
errorCodeNSXPCConnectionCodeSigningRequirementFailure = int(C.NSXPCConnectionCodeSigningRequirementFailure)
|
||||
errXPCConnectionCodeSigningRequirementFailure = errors.New("code signing requirement failed")
|
||||
)
|
||||
@@ -3,6 +3,35 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// VNEErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet.
|
||||
extern const char* const VNEErrorDomain;
|
||||
|
||||
// VNEAlreadyRunningError indicates that the daemon already received a VNet config.
|
||||
// It won't accept a new one during its lifetime, instead it's expected to stop, after
|
||||
// which the client might spawn a new instance of the daemon.
|
||||
extern const int VNEAlreadyRunningError;
|
||||
// VNEMissingCodeSigningIdentifiersError indicates that either the identifier or the team identifier are missing.
|
||||
// This can happen if the binary is unsigned, see the docs for SecCodeCopySigningInformation.
|
||||
// https://developer.apple.com/documentation/security/1395809-seccodecopysigninginformation?language=objc
|
||||
extern const int VNEMissingCodeSigningIdentifiersError;
|
||||
|
||||
typedef struct VnetConfig {
|
||||
const char *socket_path;
|
||||
const char *ipv6_prefix;
|
||||
const char *dns_addr;
|
||||
const char *home_path;
|
||||
} VnetConfig;
|
||||
|
||||
@protocol VNEDaemonProtocol
|
||||
// startVnet passes the config back to Go code (which then starts VNet in a separate thread)
|
||||
// and returns immediately.
|
||||
//
|
||||
// Only the first call to this method starts VNet. Subsequent calls return VNEAlreadyRunningError.
|
||||
// The daemon process exits after VNet is stopped, after which it can be spawned again by calling
|
||||
// this method.
|
||||
- (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *error))completion;
|
||||
@end
|
||||
|
||||
// Returns the label for the daemon by getting the identifier of the bundle
|
||||
// this executable is shipped in and appending ".vnetd" to it.
|
||||
//
|
||||
@@ -16,4 +45,15 @@ NSString *DaemonLabel(NSString *bundlePath);
|
||||
// The caller is expected to free the returned pointer.
|
||||
const char *VNECopyNSString(NSString *val);
|
||||
|
||||
// getCodeSigningRequirement calculates the requirement that will be matched against
|
||||
// the designated requirement of the app on the other side of an XPC connection.
|
||||
// It does so based on the code signing information of the current binary, as it assumes that
|
||||
// both the VNet client and the VNet daemon use the same binary.
|
||||
//
|
||||
// On success, it returns true and sets outRequirement.
|
||||
// On error, it returns false and sets outError. Returns errors of VNEErrorDomain and
|
||||
// NSOSStatusErrorDomain. Errors with the latter domain are likely to match Code Signing OSStatus values.
|
||||
// https://developer.apple.com/documentation/security/1574088-code_signing_services_result_cod?language=objc
|
||||
bool getCodeSigningRequirement(NSString **outRequirement, NSError **outError);
|
||||
|
||||
#endif /* TELEPORT_LIB_VNET_DAEMON_COMMON_DARWIN_H_ */
|
||||
|
||||
@@ -17,10 +17,17 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Security/CodeSigning.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
const char* const VNEErrorDomain = "com.Gravitational.Vnet.ErrorDomain";
|
||||
|
||||
const int VNEAlreadyRunningError = 1;
|
||||
const int VNEMissingCodeSigningIdentifiersError = 2;
|
||||
|
||||
NSString *DaemonLabel(NSString *bundlePath) {
|
||||
NSBundle *main = [NSBundle bundleWithPath:bundlePath];
|
||||
if (!main) {
|
||||
@@ -41,3 +48,73 @@ const char *VNECopyNSString(NSString *val) {
|
||||
}
|
||||
return strdup("");
|
||||
}
|
||||
|
||||
bool getCodeSigningRequirement(NSString **outRequirement, NSError **outError) {
|
||||
SecCodeRef codeObj = nil;
|
||||
OSStatus status = SecCodeCopySelf(kSecCSDefaultFlags, &codeObj);
|
||||
if (status != errSecSuccess) {
|
||||
if (outError) {
|
||||
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CFDictionaryRef cfCodeSignInfo = nil;
|
||||
// kSecCSSigningInformation must be provided as a flag for the team identifier to be included
|
||||
// in the returned dictionary.
|
||||
status = SecCodeCopySigningInformation(codeObj, kSecCSSigningInformation, &cfCodeSignInfo);
|
||||
// codeObj is no longer needed. Manually release it, as we own it since we got it from a function
|
||||
// with "Copy" in its name.
|
||||
// https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/writerid/cfCreateRule
|
||||
CFRelease(codeObj);
|
||||
if (status != errSecSuccess) {
|
||||
if (outError) {
|
||||
*outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Transfer ownership of cfCodeSignInfo to Obj-C, which means we don't have to CFRelease it manually.
|
||||
// We can transfer the ownership of cfCodeSignInfo because we own it (we got it from a function
|
||||
// with "Copy" in its name).
|
||||
// https://developer.apple.com/documentation/foundation/1587932-cfbridgingrelease
|
||||
NSDictionary *codeSignInfo = (NSDictionary *)CFBridgingRelease(cfCodeSignInfo);
|
||||
// We don't own kSecCodeInfoIdentifier, so we cannot call CFBridgingRelease on it.
|
||||
// __bridge transfers a pointer between Obj-C and CoreFoundation with no transfer of ownership.
|
||||
// Values extracted out of codeSignInfo are cast to toll-free bridged Obj-C types.
|
||||
// https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html#//apple_ref/doc/uid/TP40010677-SW2
|
||||
// https://stackoverflow.com/questions/18067108/when-should-you-use-bridge-vs-cfbridgingrelease-cfbridgingretain
|
||||
NSString *identifier = codeSignInfo[(__bridge NSString *)kSecCodeInfoIdentifier];
|
||||
NSString *teamIdentifier = codeSignInfo[(__bridge NSString *)kSecCodeInfoTeamIdentifier];
|
||||
|
||||
if (!identifier || [identifier length] == 0 || !teamIdentifier || [teamIdentifier length] == 0) {
|
||||
if (outError) {
|
||||
*outError = [NSError errorWithDomain:@(VNEErrorDomain) code:VNEMissingCodeSigningIdentifiersError userInfo:nil];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The requirement will be matched against the designated requirement of the application on
|
||||
// the other side of an XPC connection. It is based on the designated requirement of tsh.app.
|
||||
// To inspect the designated requirement of an app, use the following command:
|
||||
//
|
||||
// codesign --display -r - <path to app>
|
||||
//
|
||||
// Breakdown of individual parts of the requirement:
|
||||
// * `identifier "foo"` is satisfied if the code signing identifier matches the provided one.
|
||||
// It is not the same as the bundle identifier.
|
||||
// * `anchor apple generic` is satisfied by any code signed with any code signing identity issued
|
||||
// by Apple.
|
||||
// * `certificate leaf[field(bunch of specific numbers)]` is satisfied by code signed with
|
||||
// Developer ID Application certs.
|
||||
// * `certificate leaf[subject.OU]` is satisfied by certs with a specific Team ID.
|
||||
//
|
||||
// Read more at:
|
||||
// https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements#Designated-requirement
|
||||
// https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements#Xcode-designated-requirement-for-Developer-ID-code
|
||||
if (outRequirement) {
|
||||
*outRequirement = [NSString stringWithFormat:@"identifier \"%@\" and anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = %@", identifier, teamIdentifier];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef TELEPORT_LIB_VNET_DAEMON_PROTOCOL_DARWIN_H_
|
||||
#define TELEPORT_LIB_VNET_DAEMON_PROTOCOL_DARWIN_H_
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// VNEErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet.
|
||||
extern const char* const VNEErrorDomain;
|
||||
|
||||
// VNEAlreadyRunningError indicates that the daemon already received a VNet config.
|
||||
// It won't accept a new one during its lifetime, instead it's expected to stop, after
|
||||
// which the client might spawn a new instance of the daemon.
|
||||
extern const int VNEAlreadyRunningError;
|
||||
|
||||
typedef struct VnetConfig {
|
||||
const char *socket_path;
|
||||
const char *ipv6_prefix;
|
||||
const char *dns_addr;
|
||||
const char *home_path;
|
||||
} VnetConfig;
|
||||
|
||||
@protocol VNEDaemonProtocol
|
||||
// startVnet passes the config back to Go code (which then starts VNet in a separate thread)
|
||||
// and returns immediately.
|
||||
//
|
||||
// Only the first call to this method starts VNet. Subsequent calls return VNEAlreadyRunningError.
|
||||
// The daemon process exits after VNet is stopped, after which it can be spawned again by calling
|
||||
// this method.
|
||||
- (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *error))completion;
|
||||
@end
|
||||
|
||||
#endif /* TELEPORT_LIB_VNET_DAEMON_PROTOCOL_DARWIN_H_ */
|
||||
@@ -1,22 +0,0 @@
|
||||
//go:build vnetdaemon
|
||||
// +build vnetdaemon
|
||||
|
||||
// Teleport
|
||||
// Copyright (C) 2024 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
const char* const VNEErrorDomain = "com.Gravitational.Vnet.ErrorDomain";
|
||||
|
||||
const int VNEAlreadyRunningError = 1;
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
package daemon
|
||||
|
||||
// #cgo CFLAGS: -Wall -xobjective-c -fblocks -fobjc-arc -mmacosx-version-min=10.15
|
||||
// #cgo LDFLAGS: -framework Foundation
|
||||
// #include <stdlib.h>
|
||||
// #include "service_darwin.h"
|
||||
import "C"
|
||||
@@ -47,7 +45,23 @@ func Start(ctx context.Context, workFn func(context.Context, Config) error) erro
|
||||
cBundlePath := C.CString(bundlePath)
|
||||
defer C.free(unsafe.Pointer(cBundlePath))
|
||||
|
||||
C.DaemonStart(cBundlePath)
|
||||
var result C.DaemonStartResult
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(result.error_domain))
|
||||
C.free(unsafe.Pointer(result.error_description))
|
||||
}()
|
||||
C.DaemonStart(cBundlePath, &result)
|
||||
if !result.ok {
|
||||
errorDomain := C.GoString(result.error_domain)
|
||||
errorCode := int(result.error_code)
|
||||
|
||||
if errorDomain == vnetErrorDomain && errorCode == errorCodeMissingCodeSigningIdentifiers {
|
||||
return trace.Wrap(errMissingCodeSigningIdentifiers)
|
||||
}
|
||||
|
||||
return trace.Errorf("could not start daemon: %s", C.GoString(result.error_description))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
log.InfoContext(ctx, "Stopping daemon")
|
||||
C.DaemonStop()
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
@interface VNEDaemonService : NSObject
|
||||
|
||||
- (id)initWithBundlePath:(NSString *)bundlePath;
|
||||
- (id)initWithBundlePath:(NSString *)bundlePath codeSigningRequirement:(NSString *)codeSigningRequirement;
|
||||
|
||||
// start begins listening for incoming XPC connections.
|
||||
- (void)start;
|
||||
@@ -19,9 +19,23 @@
|
||||
|
||||
@end
|
||||
|
||||
typedef struct DaemonStartResult {
|
||||
bool ok;
|
||||
// error_domain is set to either VNEErrorDomain or NSOSStatusErrorDomain if ok is false.
|
||||
const char *error_domain;
|
||||
// If error_domain is set to VNEErrorDomain, error_code is one of the VNE codes from common_darwin.h.
|
||||
// If error_domain is NSOSStatusErrorDomain, error_code comes from OSStatus of Code Signing framework.
|
||||
// https://developer.apple.com/documentation/security/1574088-code_signing_services_result_cod?language=objc
|
||||
int error_code;
|
||||
// error_description includes the full representation of the error, including domain and code.
|
||||
const char *error_description;
|
||||
} DaemonStartResult;
|
||||
|
||||
// DaemonStart initializes the XPC service and starts listening for new connections.
|
||||
// It's expected to be called only once, noop if the daemon was already started.
|
||||
void DaemonStart(const char *bundle_path);
|
||||
// It might fail if it runs into problems with Code Signing APIs while calucating the code signing
|
||||
// requirement. In such case, outResult.ok is set to false and the error fields are populated.
|
||||
void DaemonStart(const char *bundle_path, DaemonStartResult *outResult);
|
||||
// DaemonStop stops the XPC service. Noop if DaemonStart wasn't called.
|
||||
void DaemonStop(void);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "common_darwin.h"
|
||||
#include "protocol_darwin.h"
|
||||
#include "service_darwin.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@@ -44,14 +43,19 @@
|
||||
|
||||
@implementation VNEDaemonService
|
||||
|
||||
- (id)initWithBundlePath:(NSString *)bundlePath {
|
||||
- (id)initWithBundlePath:(NSString *)bundlePath codeSigningRequirement:(NSString *)codeSigningRequirement {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// Launch daemons must configure their listener with the machServiceName
|
||||
// initializer.
|
||||
// Launch daemons must configure their listener with the machServiceName initializer.
|
||||
_listener = [[NSXPCListener alloc] initWithMachServiceName:DaemonLabel(bundlePath)];
|
||||
_listener.delegate = self;
|
||||
|
||||
// The daemon won't even be started on macOS < 13.0, so we don't have to handle the else branch
|
||||
// of this condition.
|
||||
if (@available(macOS 13, *)) {
|
||||
[_listener setConnectionCodeSigningRequirement:codeSigningRequirement];
|
||||
}
|
||||
|
||||
_started = NO;
|
||||
_gotVnetConfigSema = dispatch_semaphore_create(0);
|
||||
}
|
||||
@@ -126,12 +130,26 @@
|
||||
|
||||
static VNEDaemonService *daemonService = NULL;
|
||||
|
||||
void DaemonStart(const char *bundle_path) {
|
||||
void DaemonStart(const char *bundle_path, DaemonStartResult *outResult) {
|
||||
if (daemonService) {
|
||||
outResult->ok = true;
|
||||
return;
|
||||
}
|
||||
daemonService = [[VNEDaemonService alloc] initWithBundlePath:@(bundle_path)];
|
||||
|
||||
NSString *requirement = nil;
|
||||
NSError *error = nil;
|
||||
bool ok = getCodeSigningRequirement(&requirement, &error);
|
||||
if (!ok) {
|
||||
outResult->ok = false;
|
||||
outResult->error_domain = VNECopyNSString([error domain]);
|
||||
outResult->error_code = (int)[error code];
|
||||
outResult->error_description = VNECopyNSString([error description]);
|
||||
return;
|
||||
}
|
||||
|
||||
daemonService = [[VNEDaemonService alloc] initWithBundlePath:@(bundle_path) codeSigningRequirement:requirement];
|
||||
[daemonService start];
|
||||
outResult->ok = true;
|
||||
}
|
||||
|
||||
void DaemonStop(void) {
|
||||
|
||||
Reference in New Issue
Block a user