Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit4bb3a24

Browse files
committed
fix: add toggle for Coder deployments behind a VPN
1 parent5bf788f commit4bb3a24

File tree

12 files changed

+109
-20
lines changed

12 files changed

+109
-20
lines changed

‎Coder-Desktop/Coder-Desktop/State.swift‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import KeychainAccess
44
import NetworkExtension
55
import os
66
import SwiftUI
7+
import VPNLib
78

89
@MainActor
910
classAppState:ObservableObject{
@@ -70,6 +71,14 @@ class AppState: ObservableObject {
7071
}
7172
}
7273

74+
@PublishedvaruseSoftNetIsolation:Bool=UserDefaults.standard.bool(forKey:Keys.useSoftNetIsolation){
75+
didSet{
76+
reconfigure()
77+
guard persistentelse{return}
78+
UserDefaults.standard.set(useSoftNetIsolation, forKey:Keys.useSoftNetIsolation)
79+
}
80+
}
81+
7382
@PublishedvarskipHiddenIconAlert:Bool=UserDefaults.standard.bool(forKey:Keys.skipHiddenIconAlert){
7483
didSet{
7584
guard persistentelse{return}
@@ -81,11 +90,18 @@ class AppState: ObservableObject {
8190
if !hasSession{returnnil}
8291
letproto=NETunnelProviderProtocol()
8392
proto.providerBundleIdentifier="\(appId).VPN"
84-
// HACK: We can't write to the system keychain, and the user keychain
85-
// isn't accessible, so we'll use providerConfiguration, which is over XPC.
86-
proto.providerConfiguration=["token": sessionToken!]
87-
if useLiteralHeaders,let headers=try?JSONEncoder().encode(literalHeaders){
88-
proto.providerConfiguration?["literalHeaders"]= headers
93+
94+
proto.providerConfiguration=[
95+
// HACK: We can't write to the system keychain, and the user keychain
96+
// isn't accessible, so we'll use providerConfiguration, which
97+
// writes to disk.
98+
VPNConfigurationKeys.token: sessionToken!,
99+
VPNConfigurationKeys.useSoftNetIsolation: useSoftNetIsolation,
100+
]
101+
if useLiteralHeaders{
102+
proto.providerConfiguration?[
103+
VPNConfigurationKeys.literalHeaders
104+
]= literalHeaders.map{($0.name, $0.value)}
89105
}
90106
proto.serverAddress= baseAccessURL!.absoluteString
91107
return proto
@@ -188,6 +204,7 @@ class AppState: ObservableObject {
188204
}
189205

190206
publicfunc clearSession(){
207+
logger.info("clearing session")
191208
hasSession=false
192209
sessionToken=nil
193210
refreshTask?.cancel()
@@ -216,6 +233,7 @@ class AppState: ObservableObject {
216233

217234
staticletuseLiteralHeaders="UseLiteralHeaders"
218235
staticletliteralHeaders="LiteralHeaders"
236+
staticletuseSoftNetIsolation="UseSoftNetIsolation"
219237
staticletstopVPNOnQuit="StopVPNOnQuit"
220238
staticletstartVPNOnLaunch="StartVPNOnLaunch"
221239

‎Coder-Desktop/Coder-Desktop/Views/Settings/NetworkTab.swift‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,30 @@ struct NetworkTab<VPN: VPNService>: View {
44
varbody:someView{
55
Form{
66
LiteralHeadersSection<VPN>()
7+
SoftNetIsolationSection<VPN>()
78
}
89
.formStyle(.grouped)
910
}
1011
}
1112

13+
structSoftNetIsolationSection<VPN:VPNService>:View{
14+
@EnvironmentObjectvarstate:AppState
15+
@EnvironmentObjectvarvpn:VPN
16+
varbody:someView{
17+
Section{
18+
Toggle(isOn: $state.useSoftNetIsolation){
19+
Text("Enable support for corporate VPNs")
20+
if !vpn.state.canBeStarted{Text("Cannot be modified while Coder Connect is enabled.")}
21+
}
22+
Text("This setting loosens the VPN loop protection in Coder Connect, allowing traffic to flow to a"+
23+
"Coder deployment behind a corporate VPN. We only recommend enabling this option if Coder Connect"+
24+
"doesn't work with your Coder deployment behind a corporate VPN.")
25+
.font(.subheadline)
26+
.foregroundStyle(.secondary)
27+
}.disabled(!vpn.state.canBeStarted)
28+
}
29+
}
30+
1231
#if DEBUG
1332
#Preview{
1433
NetworkTab<PreviewVPN>()

‎Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ class HelperNEXPCListener: NSObject, NSXPCListenerDelegate, HelperNEXPCInterface
4040

4141
letstartSymbol="OpenTunnel"
4242

43+
// swiftlint:disable:next function_parameter_count
4344
func startDaemon(
4445
accessURL:URL,
4546
token:String,
4647
tun:FileHandle,
4748
headers:Data?,
49+
useSoftNetIsolation:Bool,
4850
reply:@escaping(Error?)->Void
4951
){
5052
logger.info("startDaemon called")
@@ -57,6 +59,7 @@ class HelperNEXPCListener: NSObject, NSXPCListenerDelegate, HelperNEXPCInterface
5759
apiToken: token,
5860
serverUrl: accessURL,
5961
tunFd: tun.fileDescriptor,
62+
useSoftNetIsolation: useSoftNetIsolation,
6063
literalHeaders: headers.flatMap{try?JSONDecoder().decode([HTTPHeader].self, from: $0)}??[]
6164
)
6265
)

‎Coder-Desktop/Coder-DesktopHelper/Manager.swift‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ actor Manager {
160160
resp=tryawait speaker.unaryRPC(
161161
.with{ msgin
162162
msg.start=.with{ reqin
163+
req.tunnelUseSoftNetIsolation= cfg.useSoftNetIsolation
163164
req.tunnelFileDescriptor= cfg.tunFd
164165
req.apiToken= cfg.apiToken
165166
req.coderURL= cfg.serverUrl.absoluteString
@@ -234,6 +235,7 @@ struct ManagerConfig {
234235
letapiToken:String
235236
letserverUrl:URL
236237
lettunFd:Int32
238+
letuseSoftNetIsolation:Bool
237239
letliteralHeaders:[HTTPHeader]
238240
}
239241

‎Coder-Desktop/VPN/HelperXPCSpeaker.swift‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,13 @@ final class HelperXPCSpeaker: NEXPCInterface, @unchecked Sendable {
5858

5959
// These methods are called to start and stop the daemon run by the Helper.
6060
extensionHelperXPCSpeaker{
61-
func startDaemon(accessURL:URL, token:String, tun:FileHandle, headers:Data?)asyncthrows{
61+
func startDaemon(
62+
accessURL:URL,
63+
token:String,
64+
tun:FileHandle,
65+
headers:Data?,
66+
useSoftNetIsolation:Bool
67+
)asyncthrows{
6268
letconn=connect()
6369
returntryawaitwithCheckedThrowingContinuation{ continuationin
6470
guardlet proxy= conn.remoteObjectProxyWithErrorHandler({ errin
@@ -69,7 +75,13 @@ extension HelperXPCSpeaker {
6975
continuation.resume(throwing:XPCError.wrongProxyType)
7076
return
7177
}
72-
proxy.startDaemon(accessURL: accessURL, token: token, tun: tun, headers: headers){ errin
78+
proxy.startDaemon(
79+
accessURL: accessURL,
80+
token: token,
81+
tun: tun,
82+
headers: headers,
83+
useSoftNetIsolation: useSoftNetIsolation
84+
){ errin
7385
iflet error= err{
7486
self.logger.error("Failed to start daemon:\(error.localizedDescription, privacy:.public)")
7587
continuation.resume(throwing: error)

‎Coder-Desktop/VPN/PacketTunnelProvider.swift‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,31 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
4848
)asyncthrows{
4949
globalHelperXPCSpeaker.ptp=self
5050
guardlet proto= protocolConfigurationas?NETunnelProviderProtocol,
51-
letbaseAccessURL= proto.serverAddress
51+
letaccessURL= proto.serverAddress
5252
else{
5353
logger.error("startTunnel called with nil protocolConfiguration")
5454
throwmakeNSError(suffix:"PTP", desc:"Missing Configuration")
5555
}
5656
// HACK: We can't write to the system keychain, and the NE can't read the user keychain.
57-
guardlet token= proto.providerConfiguration?["token"]as?Stringelse{
57+
guardlet token= proto.providerConfiguration?[VPNConfigurationKeys.token]as?Stringelse{
5858
logger.error("startTunnel called with nil token")
5959
throwmakeNSError(suffix:"PTP", desc:"Missing Token")
6060
}
61-
letheaders= proto.providerConfiguration?["literalHeaders"]as?Data
62-
logger.debug("retrieved token & access URL")
61+
letheaders= proto.providerConfiguration?[VPNConfigurationKeys.literalHeaders]as?Data
62+
letuseSoftNetIsolation= proto.providerConfiguration?[
63+
VPNConfigurationKeys.useSoftNetIsolation
64+
]as?Bool??false
65+
logger.debug("retrieved vpn configuration settings")
6366
guardlet tunFd= tunnelFileDescriptorelse{
6467
logger.error("startTunnel called with nil tunnelFileDescriptor")
6568
throwmakeNSError(suffix:"PTP", desc:"Missing Tunnel File Descriptor")
6669
}
6770
tryawait globalHelperXPCSpeaker.startDaemon(
68-
accessURL:.init(string:baseAccessURL)!,
71+
accessURL:.init(string:accessURL)!,
6972
token: token,
7073
tun:FileHandle(fileDescriptor: tunFd),
71-
headers: headers
74+
headers: headers,
75+
useSoftNetIsolation: useSoftNetIsolation
7276
)
7377
}
7478

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Keys for the `providerConfiguration` dictionary in the VPN configuration plist.
2+
publicenumVPNConfigurationKeys{
3+
// String
4+
publicstaticlettoken="token"
5+
// [(String, String)]
6+
publicstaticletliteralHeaders="literalHeaders"
7+
// Bool
8+
publicstaticletuseSoftNetIsolation="useSoftNetIsolation"
9+
}

‎Coder-Desktop/VPNLib/Download.swift‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,15 @@ extension DownloadManager: URLSessionDownloadDelegate {
150150
}
151151

152152
publicrequiredconvenienceinit?(coder:NSCoder){
153-
letwritten= coder.decodeInt64(forKey:"written")
154-
lettotal= coder.containsValue(forKey:"total")? coder.decodeInt64(forKey:"total"):nil
153+
letwritten= coder.decodeInt64(forKey:Keys.written)
154+
lettotal= coder.containsValue(forKey:Keys.total)? coder.decodeInt64(forKey:Keys.total):nil
155155
self.init(totalBytesWritten: written, totalBytesToWrite: total)
156156
}
157157

158158
publicfunc encode(with coder:NSCoder){
159-
coder.encode(totalBytesWritten, forKey:"written")
159+
coder.encode(totalBytesWritten, forKey:Keys.written)
160160
iflet total= totalBytesToWrite{
161-
coder.encode(total, forKey:"total")
161+
coder.encode(total, forKey:Keys.total)
162162
}
163163
}
164164

@@ -169,4 +169,9 @@ extension DownloadManager: URLSessionDownloadDelegate {
169169
lettotal= totalBytesToWrite.map{ fmt.string(fromByteCount: $0)}??"Unknown"
170170
return"\(done) /\(total)"
171171
}
172+
173+
enumKeys{
174+
staticletwritten="written"
175+
staticlettotal="total"
176+
}
172177
}

‎Coder-Desktop/VPNLib/XPC.swift‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,16 @@ public let helperNEMachServiceName = "4399GN35BJ.com.coder.Coder-Desktop.HelperN
2525
// This is the XPC interface the Helper exposes to the Network Extension.
2626
@preconcurrency
2727
@objcpublicprotocolHelperNEXPCInterface{
28-
// headers is a JSON `[HTTPHeader]`
29-
func startDaemon(accessURL:URL, token:String, tun:FileHandle, headers:Data?, reply:@escaping(Error?)->Void)
28+
// swiftlint:disable:next function_parameter_count
29+
func startDaemon(
30+
accessURL:URL,
31+
token:String,
32+
tun:FileHandle,
33+
// headers is a JSON encoded `[HTTPHeader]`
34+
headers:Data?,
35+
useSoftNetIsolation:Bool,
36+
reply:@escaping(Error?)->Void
37+
)
3038
func stopDaemon(reply:@escaping(Error?)->Void)
3139
}
3240

‎Coder-Desktop/VPNLib/vpn.pb.swift‎

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp