Skip to content

Commit

Permalink
retry
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanndickson committed Feb 7, 2025
1 parent edb1d1a commit e71d934
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 18 deletions.
6 changes: 0 additions & 6 deletions Coder Desktop/Coder Desktop/Coder_Desktop.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,9 @@
</array>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(TeamIdentifierPrefix)com.coder.Coder-Desktop</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
9 changes: 5 additions & 4 deletions Coder Desktop/Coder Desktop/NetworkExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ enum NetworkExtensionState: Equatable {
/// An actor that handles configuring, enabling, and disabling the VPN tunnel via the
/// NetworkExtension APIs.
extension CoderVPNService {
func hasNetworkExtensionConfig() async -> Bool {
func loadNetworkExtensionConfig() async {
do {
_ = try await getTunnelManager()
return true
let tm = try await getTunnelManager()
neState = .disabled
serverAddress = tm.protocolConfiguration?.serverAddress
} catch {
return false
neState = .unconfigured
}
}

Expand Down
9 changes: 4 additions & 5 deletions Coder Desktop/Coder Desktop/VPNService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,13 @@ final class CoderVPNService: NSObject, VPNService {
// only stores a weak reference to the delegate.
var systemExtnDelegate: SystemExtensionDelegate<CoderVPNService>?

var serverAddress: String?

override init() {
super.init()
installSystemExtension()
Task {
neState = if await hasNetworkExtensionConfig() {
.disabled
} else {
.unconfigured
}
await loadNetworkExtensionConfig()
}
xpc.connect()
xpc.getPeerState()
Expand Down Expand Up @@ -115,6 +113,7 @@ final class CoderVPNService: NSObject, VPNService {
func configureTunnelProviderProtocol(proto: NETunnelProviderProtocol?) {
Task {
if let proto {
serverAddress = proto.serverAddress
await configureNetworkExtension(proto: proto)
// this just configures the VPN, it doesn't enable it
tunnelState = .disabled
Expand Down
33 changes: 33 additions & 0 deletions Coder Desktop/Coder Desktop/XPCInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,37 @@ import VPNLib
svc.onExtensionPeerUpdate(data)
}
}

// The NE has verified the dylib and knows better than mac's Gatekeeper
func removeQuarantine(path: String, reply: @escaping (Bool) -> Void) {
let reply = CallbackWrapper(reply)
Task { @MainActor in
let prompt = """
Coder Desktop wants to execute code downloaded from \
\(svc.serverAddress ?? "the Coder deployment"). The code has been \
verified to be signed by Coder.
"""
let source = """
do shell script "xattr -d com.apple.quarantine \(path)" \
with prompt "\(prompt)" \
with administrator privileges
"""
do {
try await withCheckedThrowingContinuation { continuation in
guard let script = NSAppleScript(source: source) else {
return
}
// Run on a background thread
Task.detached {
var error: NSDictionary?
script.executeAndReturnError(&error)
continuation.resume()
}
}
reply(true)
} catch {
reply(false)
}
}
}
}
32 changes: 32 additions & 0 deletions Coder Desktop/VPN/Manager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ actor Manager {
} catch {
throw .validation(error)
}

// HACK: The downloaded dylib may be quarantined, but we've validated it's signature
// so it's safe to execute. However, the SE must be sandboxed, so we defer to the app.
try await removeQuarantine(dest)

do {
try tunnelHandle = TunnelHandle(dylibPath: dest)
} catch {
Expand Down Expand Up @@ -228,6 +233,8 @@ enum ManagerError: Error {
case serverInfo(String)
case errorResponse(msg: String)
case noTunnelFileDescriptor
case noApp
case permissionDenied

var description: String {
switch self {
Expand All @@ -249,6 +256,10 @@ enum ManagerError: Error {
msg
case .noTunnelFileDescriptor:
"Could not find a tunnel file descriptor"
case .noApp:
"The VPN must be started with the app open to perform first-time setup"
case .permissionDenied:
"Permission was not granted to run the CoderVPN dylib"
}
}
}
Expand All @@ -273,3 +284,24 @@ func writeVpnLog(_ log: Vpn_Log) {
let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ")
logger.log(level: level, "\(log.message, privacy: .public): \(fields, privacy: .public)")
}

private func removeQuarantine(_ dest: URL) async throws(ManagerError) {
var flag: AnyObject?
let file = NSURL(fileURLWithPath: dest.path)
try? file.getResourceValue(&flag, forKey: kCFURLQuarantinePropertiesKey as URLResourceKey)
if flag != nil {
guard let conn = globalXPCListenerDelegate.conn else {
throw .noApp
}
// Wait for unsandboxed app to accept our file
do {
try await withCheckedThrowingContinuation { [dest] continuation in
conn.removeQuarantine(path: dest.path) { _ in
continuation.resume()
}
}
} catch {
throw .permissionDenied
}
}
}
6 changes: 3 additions & 3 deletions Coder Desktop/VPNLib/Util.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
public struct CallbackWrapper<T, U>: @unchecked Sendable {
private let block: (T?) -> U
private let block: (T) -> U

public init(_ block: @escaping (T?) -> U) {
public init(_ block: @escaping (T) -> U) {
self.block = block
}

public func callAsFunction(_ error: T?) -> U {
public func callAsFunction(_ error: T) -> U {
block(error)
}
}
Expand Down
1 change: 1 addition & 0 deletions Coder Desktop/VPNLib/XPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ import Foundation
@objc public protocol VPNXPCClientCallbackProtocol {
// data is a serialized `Vpn_PeerUpdate`
func onPeerUpdate(_ data: Data)
func removeQuarantine(path: String, reply: @escaping (Bool) -> Void)
}

0 comments on commit e71d934

Please sign in to comment.