Skip to content

Commit bf0b756

Browse files
committed
retry
1 parent 492cb38 commit bf0b756

File tree

7 files changed

+78
-18
lines changed

7 files changed

+78
-18
lines changed

Coder Desktop/Coder Desktop/Coder_Desktop.entitlements

-6
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,9 @@
88
</array>
99
<key>com.apple.developer.system-extension.install</key>
1010
<true/>
11-
<key>com.apple.security.app-sandbox</key>
12-
<true/>
1311
<key>com.apple.security.application-groups</key>
1412
<array>
1513
<string>$(TeamIdentifierPrefix)com.coder.Coder-Desktop</string>
1614
</array>
17-
<key>com.apple.security.files.user-selected.read-only</key>
18-
<true/>
19-
<key>com.apple.security.network.client</key>
20-
<true/>
2115
</dict>
2216
</plist>

Coder Desktop/Coder Desktop/NetworkExtension.swift

+5-4
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ enum NetworkExtensionState: Equatable {
2424
/// An actor that handles configuring, enabling, and disabling the VPN tunnel via the
2525
/// NetworkExtension APIs.
2626
extension CoderVPNService {
27-
func hasNetworkExtensionConfig() async -> Bool {
27+
func loadNetworkExtensionConfig() async {
2828
do {
29-
_ = try await getTunnelManager()
30-
return true
29+
let tm = try await getTunnelManager()
30+
neState = .disabled
31+
serverAddress = tm.protocolConfiguration?.serverAddress
3132
} catch {
32-
return false
33+
neState = .unconfigured
3334
}
3435
}
3536

Coder Desktop/Coder Desktop/VPNService.swift

+4-5
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,13 @@ final class CoderVPNService: NSObject, VPNService {
6363
// only stores a weak reference to the delegate.
6464
var systemExtnDelegate: SystemExtensionDelegate<CoderVPNService>?
6565

66+
var serverAddress: String?
67+
6668
override init() {
6769
super.init()
6870
installSystemExtension()
6971
Task {
70-
neState = if await hasNetworkExtensionConfig() {
71-
.disabled
72-
} else {
73-
.unconfigured
74-
}
72+
await loadNetworkExtensionConfig()
7573
}
7674
xpc.connect()
7775
xpc.getPeerState()
@@ -115,6 +113,7 @@ final class CoderVPNService: NSObject, VPNService {
115113
func configureTunnelProviderProtocol(proto: NETunnelProviderProtocol?) {
116114
Task {
117115
if let proto {
116+
serverAddress = proto.serverAddress
118117
await configureNetworkExtension(proto: proto)
119118
// this just configures the VPN, it doesn't enable it
120119
tunnelState = .disabled

Coder Desktop/Coder Desktop/XPCInterface.swift

+33
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,37 @@ import VPNLib
6464
svc.onExtensionPeerUpdate(data)
6565
}
6666
}
67+
68+
// The NE has verified the dylib and knows better than mac's Gatekeeper
69+
func removeQuarantine(path: String, reply: @escaping (Bool) -> Void) {
70+
let reply = CallbackWrapper(reply)
71+
Task { @MainActor in
72+
let prompt = """
73+
Coder Desktop wants to execute code downloaded from \
74+
\(svc.serverAddress ?? "the Coder deployment"). The code has been \
75+
verified to be signed by Coder.
76+
"""
77+
let source = """
78+
do shell script "xattr -d com.apple.quarantine \(path)" \
79+
with prompt "\(prompt)" \
80+
with administrator privileges
81+
"""
82+
do {
83+
try await withCheckedThrowingContinuation { continuation in
84+
guard let script = NSAppleScript(source: source) else {
85+
return
86+
}
87+
// Run on a background thread
88+
Task.detached {
89+
var error: NSDictionary?
90+
script.executeAndReturnError(&error)
91+
continuation.resume()
92+
}
93+
}
94+
reply(true)
95+
} catch {
96+
reply(false)
97+
}
98+
}
99+
}
67100
}

Coder Desktop/VPN/Manager.swift

+32
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ actor Manager {
4646
} catch {
4747
throw .validation(error)
4848
}
49+
50+
// HACK: The downloaded dylib may be quarantined, but we've validated it's signature
51+
// so it's safe to execute. However, the SE must be sandboxed, so we defer to the app.
52+
try await removeQuarantine(dest)
53+
4954
do {
5055
try tunnelHandle = TunnelHandle(dylibPath: dest)
5156
} catch {
@@ -228,6 +233,8 @@ enum ManagerError: Error {
228233
case serverInfo(String)
229234
case errorResponse(msg: String)
230235
case noTunnelFileDescriptor
236+
case noApp
237+
case permissionDenied
231238

232239
var description: String {
233240
switch self {
@@ -249,6 +256,10 @@ enum ManagerError: Error {
249256
msg
250257
case .noTunnelFileDescriptor:
251258
"Could not find a tunnel file descriptor"
259+
case .noApp:
260+
"The VPN must be started with the app open to perform first-time setup"
261+
case .permissionDenied:
262+
"Permission was not granted to run the CoderVPN dylib"
252263
}
253264
}
254265
}
@@ -273,3 +284,24 @@ func writeVpnLog(_ log: Vpn_Log) {
273284
let fields = log.fields.map { "\($0.name): \($0.value)" }.joined(separator: ", ")
274285
logger.log(level: level, "\(log.message, privacy: .public): \(fields, privacy: .public)")
275286
}
287+
288+
private func removeQuarantine(_ dest: URL) async throws(ManagerError) {
289+
var flag: AnyObject?
290+
let file = NSURL(fileURLWithPath: dest.path)
291+
try? file.getResourceValue(&flag, forKey: kCFURLQuarantinePropertiesKey as URLResourceKey)
292+
if flag != nil {
293+
guard let conn = globalXPCListenerDelegate.conn else {
294+
throw .noApp
295+
}
296+
// Wait for unsandboxed app to accept our file
297+
do {
298+
try await withCheckedThrowingContinuation { [dest] continuation in
299+
conn.removeQuarantine(path: dest.path) { _ in
300+
continuation.resume()
301+
}
302+
}
303+
} catch {
304+
throw .permissionDenied
305+
}
306+
}
307+
}

Coder Desktop/VPNLib/Util.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
public struct CallbackWrapper<T, U>: @unchecked Sendable {
2-
private let block: (T?) -> U
2+
private let block: (T) -> U
33

4-
public init(_ block: @escaping (T?) -> U) {
4+
public init(_ block: @escaping (T) -> U) {
55
self.block = block
66
}
77

8-
public func callAsFunction(_ error: T?) -> U {
8+
public func callAsFunction(_ error: T) -> U {
99
block(error)
1010
}
1111
}

Coder Desktop/VPNLib/XPC.swift

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ import Foundation
1010
@objc public protocol VPNXPCClientCallbackProtocol {
1111
// data is a serialized `Vpn_PeerUpdate`
1212
func onPeerUpdate(_ data: Data)
13+
func removeQuarantine(path: String, reply: @escaping (Bool) -> Void)
1314
}

0 commit comments

Comments
 (0)