From 6c7ae7b6f0bea5cca5f2fa38d9d8b091ef2d4252 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Fri, 14 Feb 2025 20:28:00 +1100 Subject: [PATCH] fix: pass configured http headers to network extension --- Coder Desktop/Coder Desktop/State.swift | 16 +++++++++++----- .../Views/Settings/LiteralHeaderModal.swift | 4 ++-- .../Views/Settings/LiteralHeadersSection.swift | 2 +- Coder Desktop/CoderSDK/Client.swift | 2 +- Coder Desktop/CoderSDK/HTTP.swift | 8 ++++---- Coder Desktop/CoderSDKTests/CoderSDKTests.swift | 2 +- Coder Desktop/VPN/Manager.swift | 8 +++++++- Coder Desktop/VPN/PacketTunnelProvider.swift | 6 +++++- 8 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Coder Desktop/Coder Desktop/State.swift b/Coder Desktop/Coder Desktop/State.swift index 2013d90..b80f831 100644 --- a/Coder Desktop/Coder Desktop/State.swift +++ b/Coder Desktop/Coder Desktop/State.swift @@ -31,6 +31,7 @@ class AppState: ObservableObject { @Published var useLiteralHeaders: Bool = UserDefaults.standard.bool(forKey: Keys.useLiteralHeaders) { didSet { + if let onChange { onChange(tunnelProviderProtocol()) } guard persistent else { return } UserDefaults.standard.set(useLiteralHeaders, forKey: Keys.useLiteralHeaders) } @@ -38,6 +39,7 @@ class AppState: ObservableObject { @Published var literalHeaders: [LiteralHeader] { didSet { + if let onChange { onChange(tunnelProviderProtocol()) } guard persistent else { return } try? UserDefaults.standard.set(JSONEncoder().encode(literalHeaders), forKey: Keys.literalHeaders) } @@ -57,6 +59,9 @@ class AppState: ObservableObject { // HACK: We can't write to the system keychain, and the user keychain // isn't accessible, so we'll use providerConfiguration, which is over XPC. proto.providerConfiguration = ["token": sessionToken!] + if useLiteralHeaders, let headers = try? JSONEncoder().encode(literalHeaders) { + proto.providerConfiguration?["literalHeaders"] = headers + } proto.serverAddress = baseAccessURL!.absoluteString return proto } @@ -64,6 +69,7 @@ class AppState: ObservableObject { private let keychain: Keychain private let persistent: Bool + // This closure must be called when any property used to configure the VPN changes let onChange: ((NETunnelProviderProtocol?) -> Void)? public init(onChange: ((NETunnelProviderProtocol?) -> Void)? = nil, @@ -125,20 +131,20 @@ class AppState: ObservableObject { } struct LiteralHeader: Hashable, Identifiable, Equatable, Codable { - var header: String + var name: String var value: String var id: String { - "\(header):\(value)" + "\(name):\(value)" } - init(header: String, value: String) { - self.header = header + init(name: String, value: String) { + self.name = name self.value = value } } extension LiteralHeader { func toSDKHeader() -> HTTPHeader { - .init(header: header, value: value) + .init(name: name, value: value) } } diff --git a/Coder Desktop/Coder Desktop/Views/Settings/LiteralHeaderModal.swift b/Coder Desktop/Coder Desktop/Views/Settings/LiteralHeaderModal.swift index 5892fdb..5f8e8b5 100644 --- a/Coder Desktop/Coder Desktop/Views/Settings/LiteralHeaderModal.swift +++ b/Coder Desktop/Coder Desktop/Views/Settings/LiteralHeaderModal.swift @@ -26,7 +26,7 @@ struct LiteralHeaderModal: View { }.padding(20) }.onAppear { if let existingHeader { - header = existingHeader.header + header = existingHeader.name value = existingHeader.value } } @@ -37,7 +37,7 @@ struct LiteralHeaderModal: View { if let existingHeader { state.literalHeaders.removeAll { $0 == existingHeader } } - let newHeader = LiteralHeader(header: header, value: value) + let newHeader = LiteralHeader(name: header, value: value) if !state.literalHeaders.contains(newHeader) { state.literalHeaders.append(newHeader) } diff --git a/Coder Desktop/Coder Desktop/Views/Settings/LiteralHeadersSection.swift b/Coder Desktop/Coder Desktop/Views/Settings/LiteralHeadersSection.swift index 5a5e53a..e3a47b9 100644 --- a/Coder Desktop/Coder Desktop/Views/Settings/LiteralHeadersSection.swift +++ b/Coder Desktop/Coder Desktop/Views/Settings/LiteralHeadersSection.swift @@ -20,7 +20,7 @@ struct LiteralHeadersSection: View { .controlSize(.large) Table(state.literalHeaders, selection: $selectedHeader) { - TableColumn("Header", value: \.header) + TableColumn("Header", value: \.name) TableColumn("Value", value: \.value) }.opacity(state.useLiteralHeaders ? 1 : 0.5) .frame(minWidth: 400, minHeight: 200) diff --git a/Coder Desktop/CoderSDK/Client.swift b/Coder Desktop/CoderSDK/Client.swift index 601c577..881ae99 100644 --- a/Coder Desktop/CoderSDK/Client.swift +++ b/Coder Desktop/CoderSDK/Client.swift @@ -33,7 +33,7 @@ public struct Client { if let token { req.addValue(token, forHTTPHeaderField: Headers.sessionToken) } req.httpMethod = method.rawValue for header in headers { - req.addValue(header.value, forHTTPHeaderField: header.header) + req.addValue(header.value, forHTTPHeaderField: header.name) } req.httpBody = body let data: Data diff --git a/Coder Desktop/CoderSDK/HTTP.swift b/Coder Desktop/CoderSDK/HTTP.swift index d10d469..d984c87 100644 --- a/Coder Desktop/CoderSDK/HTTP.swift +++ b/Coder Desktop/CoderSDK/HTTP.swift @@ -6,11 +6,11 @@ public struct HTTPResponse { let req: URLRequest } -public struct HTTPHeader: Sendable { - public let header: String +public struct HTTPHeader: Sendable, Codable { + public let name: String public let value: String - public init(header: String, value: String) { - self.header = header + public init(name: String, value: String) { + self.name = name self.value = value } } diff --git a/Coder Desktop/CoderSDKTests/CoderSDKTests.swift b/Coder Desktop/CoderSDKTests/CoderSDKTests.swift index 69a4664..8184730 100644 --- a/Coder Desktop/CoderSDKTests/CoderSDKTests.swift +++ b/Coder Desktop/CoderSDKTests/CoderSDKTests.swift @@ -28,7 +28,7 @@ struct CoderSDKTests { let url = URL(string: "https://example.com")! let token = "fake-token" - let client = Client(url: url, token: token, headers: [.init(header: "X-Test-Header", value: "foo")]) + let client = Client(url: url, token: token, headers: [.init(name: "X-Test-Header", value: "foo")]) var mock = try Mock( url: url.appending(path: "api/v2/users/johndoe"), contentType: .json, diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift index 4506dfa..c6946ae 100644 --- a/Coder Desktop/VPN/Manager.swift +++ b/Coder Desktop/VPN/Manager.swift @@ -54,7 +54,6 @@ actor Manager { do { try tunnelHandle = TunnelHandle(dylibPath: dest) } catch { - logger.error("couldn't open dylib \(error, privacy: .public)") throw .tunnelSetup(error) } speaker = await Speaker( @@ -164,6 +163,12 @@ actor Manager { req.tunnelFileDescriptor = tunFd req.apiToken = cfg.apiToken req.coderURL = cfg.serverUrl.absoluteString + req.headers = cfg.literalHeaders.map { header in + .with { req in + req.name = header.name + req.value = header.value + } + } } }) } catch { @@ -223,6 +228,7 @@ actor Manager { struct ManagerConfig { let apiToken: String let serverUrl: URL + let literalHeaders: [HTTPHeader] } enum ManagerError: Error { diff --git a/Coder Desktop/VPN/PacketTunnelProvider.swift b/Coder Desktop/VPN/PacketTunnelProvider.swift index 0102295..3569062 100644 --- a/Coder Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder Desktop/VPN/PacketTunnelProvider.swift @@ -1,3 +1,4 @@ +import CoderSDK import NetworkExtension import os import VPNLib @@ -65,6 +66,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { completionHandler(makeNSError(suffix: "PTP", desc: "Missing Token")) return } + let headers: [HTTPHeader] = (proto.providerConfiguration?["literalHeaders"] as? Data) + .flatMap { try? JSONDecoder().decode([HTTPHeader].self, from: $0) } ?? [] logger.debug("retrieved token & access URL") let completionHandler = CallbackWrapper(completionHandler) Task { @@ -73,7 +76,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { let manager = try await Manager( with: self, cfg: .init( - apiToken: token, serverUrl: .init(string: baseAccessURL)! + apiToken: token, serverUrl: .init(string: baseAccessURL)!, + literalHeaders: headers ) ) globalXPCListenerDelegate.vpnXPCInterface.manager = manager