fix(apple): Persist Firezone ID to disk instead of using hardware tracking methods (#3244)

Fixes #2981
This commit is contained in:
Jamil
2024-01-15 18:04:53 -08:00
committed by GitHub
parent b1738bdd46
commit d3652408a9
3 changed files with 23 additions and 106 deletions

View File

@@ -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 = "<group>"; };
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 = "<group>"; };
8D2F64EC2A97336C00B6176A /* PrimaryMacAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryMacAddress.swift; sourceTree = "<group>"; };
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 */,

View File

@@ -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,

View File

@@ -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
}
}