From d3652408a960d721e94c978fd0408dc41fe69cc6 Mon Sep 17 00:00:00 2001 From: Jamil Date: Mon, 15 Jan 2024 18:04:53 -0800 Subject: [PATCH] fix(apple): Persist Firezone ID to disk instead of using hardware tracking methods (#3244) Fixes #2981 --- .../apple/Firezone.xcodeproj/project.pbxproj | 4 - .../FirezoneNetworkExtension/Adapter.swift | 45 ++++++----- .../PrimaryMacAddress.swift | 80 ------------------- 3 files changed, 23 insertions(+), 106 deletions(-) delete mode 100644 swift/apple/FirezoneNetworkExtension/PrimaryMacAddress.swift diff --git a/swift/apple/Firezone.xcodeproj/project.pbxproj b/swift/apple/Firezone.xcodeproj/project.pbxproj index f37a2d771..d7fcb8725 100644 --- a/swift/apple/Firezone.xcodeproj/project.pbxproj +++ b/swift/apple/Firezone.xcodeproj/project.pbxproj @@ -32,7 +32,6 @@ 79756C6629704A7A0018E2D5 /* FirezoneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 79756C6529704A7A0018E2D5 /* FirezoneKit */; }; 8D28EB992B35FBD70083621C /* Resolv.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D28EB982B35FBD70083621C /* Resolv.swift */; }; 8D28EB9A2B35FBD70083621C /* Resolv.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D28EB982B35FBD70083621C /* Resolv.swift */; }; - 8D2F64EF2A973F7000B6176A /* PrimaryMacAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D2F64EC2A97336C00B6176A /* PrimaryMacAddress.swift */; }; 8DC08BCB2B296C4500675F46 /* libresolv.9.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC08BCA2B296C4500675F46 /* libresolv.9.tbd */; }; 8DC08BCD2B296C5900675F46 /* libresolv.9.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC08BCC2B296C5900675F46 /* libresolv.9.tbd */; }; 8DC08BD22B297B7B00675F46 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC08BD12B297B7B00675F46 /* libresolv.tbd */; }; @@ -118,7 +117,6 @@ 6FE93AFA2A738D7E002D278A /* NetworkSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettings.swift; sourceTree = ""; }; 6FFECD5B2AD6998400E00273 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 8D28EB982B35FBD70083621C /* Resolv.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolv.swift; sourceTree = ""; }; - 8D2F64EC2A97336C00B6176A /* PrimaryMacAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryMacAddress.swift; sourceTree = ""; }; 8DC08BCA2B296C4500675F46 /* libresolv.9.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.9.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.2.sdk/usr/lib/libresolv.9.tbd; sourceTree = DEVELOPER_DIR; }; 8DC08BCC2B296C5900675F46 /* libresolv.9.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.9.tbd; path = usr/lib/libresolv.9.tbd; sourceTree = SDKROOT; }; 8DC08BD12B297B7B00675F46 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; @@ -188,7 +186,6 @@ 6FE455082A5D110D006549B1 /* CallbackHandler.swift */, 6FE4550B2A5D111D006549B1 /* SwiftBridgeCore.swift */, 6FE4550E2A5D112C006549B1 /* connlib-client-apple.swift */, - 8D2F64EC2A97336C00B6176A /* PrimaryMacAddress.swift */, 6FE455112A5D13A2006549B1 /* FirezoneNetworkExtension-Bridging-Header.h */, 8D28EB982B35FBD70083621C /* Resolv.swift */, ); @@ -516,7 +513,6 @@ buildActionMask = 2147483647; files = ( 8DC08BD62B297DA400675F46 /* FirezoneNetworkExtension-Bridging-Header.h in Sources */, - 8D2F64EF2A973F7000B6176A /* PrimaryMacAddress.swift in Sources */, 6FE4550A2A5D110D006549B1 /* CallbackHandler.swift in Sources */, 8D28EB9A2B35FBD70083621C /* Resolv.swift in Sources */, 6FE455102A5D112C006549B1 /* connlib-client-apple.swift in Sources */, diff --git a/swift/apple/FirezoneNetworkExtension/Adapter.swift b/swift/apple/FirezoneNetworkExtension/Adapter.swift index ea9ee7abc..c8e277e67 100644 --- a/swift/apple/FirezoneNetworkExtension/Adapter.swift +++ b/swift/apple/FirezoneNetworkExtension/Adapter.swift @@ -91,6 +91,7 @@ class Adapter { private let logFilter: String private let connlibLogFolderPath: String + private let firezoneIdFileURL: URL init( controlPlaneURLString: String, token: String, @@ -103,6 +104,7 @@ class Adapter { self.state = .stoppedTunnel self.logFilter = logFilter self.connlibLogFolderPath = SharedAccess.connlibLogFolderURL?.path ?? "" + self.firezoneIdFileURL = SharedAccess.baseFolderURL.appendingPathComponent("firezone-id") } deinit { @@ -151,7 +153,7 @@ class Adapter { session: try WrappedSession.connect( self.controlPlaneURLString, self.token, - self.getDeviceId(), + self.getOrCreateFirezoneId(from: self.firezoneIdFileURL), self.getDeviceName(), self.getOSVersion(), self.connlibLogFolderPath, @@ -257,28 +259,27 @@ extension Adapter { #endif } - // uuidString and copyMACAddress() *should* reliably return valid Strings, but if - // for whatever reason they're nil, return a random UUID instead to prevent - // upsert collisions in the portal. - func getDeviceId() -> String { - #if os(iOS) - guard let extId = UIDevice.current.identifierForVendor?.uuidString else { - // FIXME: We should store this random deviceID fallback in the AppStore - // to use for upsert instead of generating a random UUID each time. - return UUID().uuidString + // Returns the Firezone ID as cached by the application or generates and persists a new one + // if that doesn't exist. The Firezone ID is a UUIDv4 that is used to dedup this device + // for upsert and identification in the admin portal. + func getOrCreateFirezoneId(from fileURL: URL) -> String { + do { + return try String(contentsOf: fileURL, encoding: .utf8) + } catch { + // Handle the error if the file doesn't exist or isn't readable + // Recreate the file, save a new UUIDv4, and return it + let newUUIDString = UUID().uuidString + + do { + try newUUIDString.write(to: fileURL, atomically: true, encoding: .utf8) + } catch { + self.logger.error( + "Adapter.getOrCreateFirezoneId: Could not save \(fileURL, privacy: .public)! Error: \(error, privacy: .public)" + ) } - #elseif os(macOS) - guard let extId = PrimaryMacAddress.copyMACAddress() as? String else { - // FIXME: We should store this random deviceID fallback in the AppStore - // to use for upsert instead of generating a random UUID each time. - return UUID().uuidString - } - #else - #error("Unsupported platform") - #endif - - return extId + return newUUIDString + } } } @@ -348,7 +349,7 @@ extension Adapter { session: try WrappedSession.connect( controlPlaneURLString, token, - self.getDeviceId(), + self.getOrCreateFirezoneId(from: self.firezoneIdFileURL), self.getDeviceName(), self.getOSVersion(), self.connlibLogFolderPath, diff --git a/swift/apple/FirezoneNetworkExtension/PrimaryMacAddress.swift b/swift/apple/FirezoneNetworkExtension/PrimaryMacAddress.swift deleted file mode 100644 index f10824f1e..000000000 --- a/swift/apple/FirezoneNetworkExtension/PrimaryMacAddress.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// PrimaryMacAddress.swift -// (c) 2023 Firezone, Inc. -// LICENSE: Apache-2.0 -// -// Contains convenience methods for getting a device ID for macOS. - -// Believe it or not, this is Apple's recommended way of doing things for macOS -// See https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device#//apple_ref/doc/uid/TP40010573-CH1-SW14 - -import Foundation -import IOKit -import OSLog - -public class PrimaryMacAddress { - // Returns an object with a +1 retain count; the caller needs to release. - private static func ioService(named name: String, wantBuiltIn: Bool) -> io_service_t? { - let defaultPort = kIOMainPortDefault - var iterator = io_iterator_t() - defer { - if iterator != IO_OBJECT_NULL { - IOObjectRelease(iterator) - } - } - - guard let matchingDict = IOBSDNameMatching(defaultPort, 0, name), - IOServiceGetMatchingServices( - defaultPort, - matchingDict as CFDictionary, - &iterator) == KERN_SUCCESS, - iterator != IO_OBJECT_NULL - else { - return nil - } - - var candidate = IOIteratorNext(iterator) - while candidate != IO_OBJECT_NULL { - if let cftype = IORegistryEntryCreateCFProperty( - candidate, - "IOBuiltin" as CFString, - kCFAllocatorDefault, - 0) - { - let isBuiltIn = cftype.takeRetainedValue() as! CFBoolean - if wantBuiltIn == CFBooleanGetValue(isBuiltIn) { - return candidate - } - } - - IOObjectRelease(candidate) - candidate = IOIteratorNext(iterator) - } - - return nil - } - - public static func copyMACAddress() -> CFData? { - // Prefer built-in network interfaces. - // For example, an external Ethernet adaptor can displace - // the built-in Wi-Fi as en0. - guard - let service = ioService(named: "en0", wantBuiltIn: true) - ?? ioService(named: "en1", wantBuiltIn: true) - ?? ioService(named: "en0", wantBuiltIn: false) - else { return nil } - defer { IOObjectRelease(service) } - - if let cftype = IORegistryEntrySearchCFProperty( - service, - kIOServicePlane, - "IOMACAddress" as CFString, - kCFAllocatorDefault, - IOOptionBits(kIORegistryIterateRecursively | kIORegistryIterateParents)) - { - return (cftype as! CFData) - } - - return nil - } -}