Skip to content

Commit e0b1bd2

Browse files
authored
Disable fingerprint checking when storage is not available (#3928)
Motivation: Source compat test failure: https://ci.swift.org/job/swift-PR-source-compat-suite/5701/artifact/swift-source-compat-suite/ ``` error: Failed to get source control fingerprint for swift-log remoteSourceControl https://github.com/apple/swift-log.git version 1.4.2 from storage: Error Domain=NSCocoaErrorDomain Code=513 "You don't have permission to save the file "fingerprints" in the folder "security"." UserInfo={NSFilePath=/Users/buildnode/.swiftpm/security/fingerprints, NSUnderlyingError=0x7feaae439370 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}} error: Error Domain=NSCocoaErrorDomain Code=513 "You don't have permission to save the file "fingerprints" in the folder "security"." UserInfo={NSFilePath=/Users/buildnode/.swiftpm/security/fingerprints, NSUnderlyingError=0x7feaae439370 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}} ``` Modifications: - Make `PackageFingerprintStorage` optional in `RegistryClient` and `SourceControlPackageContainer`, which would turn off fingerprint read/write and essentially disable the TOFU feature. - `SwiftTool` will try to create the shared security directory (under which fingerprints are stored), and if it fails (e.g., permission errors) set `PackageFingerprintStorage` to none. - Don't perform integrity check on fingerprint write. The validation failure will happen on read.
1 parent e4de038 commit e0b1bd2

File tree

8 files changed

+93
-140
lines changed

8 files changed

+93
-140
lines changed

Sources/Commands/SwiftTool.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,20 @@ public class SwiftTool {
640640
return .none
641641
}
642642
}
643+
644+
private func getSharedSecurityDirectory() throws -> AbsolutePath? {
645+
do {
646+
let fileSystem = localFileSystem
647+
let sharedSecurityDirectory = fileSystem.swiftPMSecurityDirectory
648+
if !fileSystem.exists(sharedSecurityDirectory) {
649+
try fileSystem.createDirectory(sharedSecurityDirectory, recursive: true)
650+
}
651+
return sharedSecurityDirectory
652+
} catch {
653+
self.observabilityScope.emit(warning: "Failed creating shared security directory: \(error)")
654+
return .none
655+
}
656+
}
643657

644658
/// Returns the currently active workspace.
645659
func getActiveWorkspace() throws -> Workspace {
@@ -649,7 +663,8 @@ public class SwiftTool {
649663

650664
let delegate = ToolWorkspaceDelegate(self.outputStream, logLevel: self.logLevel, observabilityScope: self.observabilityScope)
651665
let provider = GitRepositoryProvider(processSet: processSet)
652-
let sharedCacheDirectory = try self.getSharedCacheDirectory()
666+
let sharedSecurityDirectory = try self.getSharedSecurityDirectory()
667+
let sharedCacheDirectory = try self.getSharedCacheDirectory()
653668
let sharedConfigurationDirectory = try self.getSharedConfigurationDirectory()
654669
let isXcodeBuildSystemEnabled = self.options.buildSystem == .xcode
655670
let workspace = try Workspace(
@@ -658,7 +673,7 @@ public class SwiftTool {
658673
workingDirectory: buildPath,
659674
editsDirectory: self.editsDirectory(),
660675
resolvedVersionsFile: self.resolvedVersionsFile(),
661-
sharedSecurityDirectory: localFileSystem.swiftPMSecurityDirectory,
676+
sharedSecurityDirectory: sharedSecurityDirectory,
662677
sharedCacheDirectory: sharedCacheDirectory,
663678
sharedConfigurationDirectory: sharedConfigurationDirectory
664679
),

Sources/PackageFingerprint/Model.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,4 @@ public typealias PackageFingerprints = [Version: [Fingerprint.Kind: Fingerprint]
6565
public enum FingerprintCheckingMode: String {
6666
case strict
6767
case warn
68-
case none
6968
}

Sources/PackageFingerprint/PackageFingerprintStorage.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,16 @@ public extension PackageFingerprintStorage {
4646
}
4747
}
4848

49-
public enum PackageFingerprintStorageError: Error, Equatable {
49+
public enum PackageFingerprintStorageError: Error, Equatable, CustomStringConvertible {
5050
case conflict(given: Fingerprint, existing: Fingerprint)
5151
case notFound
52+
53+
public var description: String {
54+
switch self {
55+
case .conflict(let given, let existing):
56+
return "Fingerprint \(given) is different from previously recorded value \(existing)"
57+
case .notFound:
58+
return "Not found"
59+
}
60+
}
5261
}

Sources/PackageRegistry/RegistryClient.swift

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,14 @@ public final class RegistryClient {
100100
private let archiverProvider: (FileSystem) -> Archiver
101101
private let httpClient: HTTPClient
102102
private let authorizationProvider: HTTPClientAuthorizationProvider?
103-
private let fingerprintStorage: PackageFingerprintStorage
103+
private let fingerprintStorage: PackageFingerprintStorage?
104104
private let fingerprintCheckingMode: FingerprintCheckingMode
105105
private let jsonDecoder: JSONDecoder
106106

107107
public init(
108108
configuration: RegistryConfiguration,
109109
identityResolver: IdentityResolver,
110-
fingerprintStorage: PackageFingerprintStorage,
110+
fingerprintStorage: PackageFingerprintStorage? = .none,
111111
fingerprintCheckingMode: FingerprintCheckingMode,
112112
authorizationProvider: HTTPClientAuthorizationProvider? = .none,
113113
customHTTPClient: HTTPClient? = .none,
@@ -423,27 +423,23 @@ public final class RegistryClient {
423423
throw RegistryError.invalidSourceArchive
424424
}
425425

426-
self.fingerprintStorage.put(package: package,
427-
version: version,
428-
fingerprint: .init(origin: .registry(registry.url), value: checksum),
429-
observabilityScope: observabilityScope,
430-
callbackQueue: callbackQueue) { storageResult in
431-
switch storageResult {
432-
case .success:
433-
completion(.success(checksum))
434-
case .failure(PackageFingerprintStorageError.conflict(_, let existing)):
435-
switch self.fingerprintCheckingMode {
436-
case .strict:
437-
completion(.failure(RegistryError.checksumChanged(latest: checksum, previous: existing.value)))
438-
case .warn:
439-
observabilityScope.emit(warning: "The checksum \(checksum) from \(registry.url.absoluteString) does not match previously recorded value \(existing.value) from \(String(describing: existing.origin.url?.absoluteString))")
426+
if let fingerprintStorage = self.fingerprintStorage {
427+
fingerprintStorage.put(package: package,
428+
version: version,
429+
fingerprint: .init(origin: .registry(registry.url), value: checksum),
430+
observabilityScope: observabilityScope,
431+
callbackQueue: callbackQueue) { storageResult in
432+
switch storageResult {
433+
case .success:
440434
completion(.success(checksum))
441-
case .none:
435+
case .failure(let error):
436+
// Don't throw write errors
437+
observabilityScope.emit(warning: "Failed to save checksum '\(checksum) from \(registry.url) to fingerprints storage: \(error)")
442438
completion(.success(checksum))
443439
}
444-
case .failure(let error):
445-
completion(.failure(error))
446440
}
441+
} else {
442+
completion(.success(checksum))
447443
}
448444
} catch {
449445
completion(.failure(RegistryError.failedRetrievingReleaseChecksum(error)))
@@ -535,8 +531,6 @@ public final class RegistryClient {
535531
return completion(.failure(RegistryError.invalidChecksum(expected: expectedChecksum, actual: actualChecksum)))
536532
case .warn:
537533
observabilityScope.emit(warning: "The checksum \(actualChecksum) does not match previously recorded value \(expectedChecksum)")
538-
case .none:
539-
break
540534
}
541535
}
542536

@@ -567,25 +561,33 @@ public final class RegistryClient {
567561

568562
// We either use a previously recorded checksum, or fetch it from the registry
569563
func withExpectedChecksum(body: @escaping (Result<String, Error>) -> Void) {
570-
self.fingerprintStorage.get(package: package,
571-
version: version,
572-
kind: .registry,
573-
observabilityScope: observabilityScope,
574-
callbackQueue: callbackQueue) { result in
575-
switch result {
576-
case .success(let fingerprint):
577-
body(.success(fingerprint.value))
578-
case .failure(let error):
579-
if error as? PackageFingerprintStorageError != .notFound {
580-
observabilityScope.emit(error: "Failed to get registry fingerprint for \(package) \(version) from storage: \(error)")
564+
if let fingerprintStorage = self.fingerprintStorage {
565+
fingerprintStorage.get(package: package,
566+
version: version,
567+
kind: .registry,
568+
observabilityScope: observabilityScope,
569+
callbackQueue: callbackQueue) { result in
570+
switch result {
571+
case .success(let fingerprint):
572+
body(.success(fingerprint.value))
573+
case .failure(let error):
574+
if error as? PackageFingerprintStorageError != .notFound {
575+
observabilityScope.emit(error: "Failed to get registry fingerprint for \(package) \(version) from storage: \(error)")
576+
}
577+
// Try fetching checksum from registry again no matter which kind of error it is
578+
self.fetchSourceArchiveChecksum(package: package,
579+
version: version,
580+
observabilityScope: observabilityScope,
581+
callbackQueue: callbackQueue,
582+
completion: body)
581583
}
582-
// Try fetching checksum from registry again no matter which kind of error it is
583-
self.fetchSourceArchiveChecksum(package: package,
584-
version: version,
585-
observabilityScope: observabilityScope,
586-
callbackQueue: callbackQueue,
587-
completion: body)
588584
}
585+
} else {
586+
self.fetchSourceArchiveChecksum(package: package,
587+
version: version,
588+
observabilityScope: observabilityScope,
589+
callbackQueue: callbackQueue,
590+
completion: body)
589591
}
590592
}
591593
}

Sources/Workspace/SourceControlPackageContainer.swift

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri
5555
private let manifestLoader: ManifestLoaderProtocol
5656
private let toolsVersionLoader: ToolsVersionLoaderProtocol
5757
private let currentToolsVersion: ToolsVersion
58-
private let fingerprintStorage: PackageFingerprintStorage
58+
private let fingerprintStorage: PackageFingerprintStorage?
5959
private let fingerprintCheckingMode: FingerprintCheckingMode
6060
private let observabilityScope: ObservabilityScope
6161

@@ -79,7 +79,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri
7979
manifestLoader: ManifestLoaderProtocol,
8080
toolsVersionLoader: ToolsVersionLoaderProtocol,
8181
currentToolsVersion: ToolsVersion,
82-
fingerprintStorage: PackageFingerprintStorage,
82+
fingerprintStorage: PackageFingerprintStorage?,
8383
fingerprintCheckingMode: FingerprintCheckingMode,
8484
observabilityScope: ObservabilityScope
8585
) throws {
@@ -150,6 +150,10 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri
150150
}
151151

152152
func checkIntegrity(version: Version, revision: Revision) throws {
153+
guard let fingerprintStorage = self.fingerprintStorage else {
154+
return
155+
}
156+
153157
guard case .remoteSourceControl(let sourceControlURL) = self.package.kind else {
154158
return
155159
}
@@ -158,7 +162,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri
158162
let fingerprint: Fingerprint
159163
do {
160164
fingerprint = try temp_await {
161-
self.fingerprintStorage.get(
165+
fingerprintStorage.get(
162166
package: packageIdentity,
163167
version: version,
164168
kind: .sourceControl,
@@ -172,7 +176,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri
172176
// Write to storage if fingerprint not yet recorded
173177
do {
174178
try temp_await {
175-
self.fingerprintStorage.put(
179+
fingerprintStorage.put(
176180
package: packageIdentity,
177181
version: version,
178182
fingerprint: fingerprint,
@@ -181,16 +185,8 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri
181185
callback: $0
182186
)
183187
}
184-
} catch PackageFingerprintStorageError.conflict(_, let existing) {
185-
let message = "Revision \(revision.identifier) for \(self.package) version \(version) does not match previously recorded value \(existing.value) from \(String(describing: existing.origin.url?.absoluteString))"
186-
switch self.fingerprintCheckingMode {
187-
case .strict:
188-
throw StringError(message)
189-
case .warn:
190-
observabilityScope.emit(warning: message)
191-
case .none:
192-
return
193-
}
188+
} catch {
189+
observabilityScope.emit(warning: "Failed to save revision '\(revision.identifier) from \(sourceControlURL) to fingerprints storage: \(error)")
194190
}
195191
} catch {
196192
self.observabilityScope.emit(error: "Failed to get source control fingerprint for \(self.package) version \(version) from storage: \(error)")
@@ -205,8 +201,6 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri
205201
throw StringError(message)
206202
case .warn:
207203
observabilityScope.emit(warning: message)
208-
case .none:
209-
return
210204
}
211205
}
212206
}

Sources/Workspace/Workspace.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public class Workspace {
217217
fileprivate let checksumAlgorithm: HashAlgorithm
218218

219219
/// The package fingerprint storage
220-
fileprivate let fingerprintStorage: PackageFingerprintStorage
220+
fileprivate let fingerprintStorage: PackageFingerprintStorage?
221221

222222
/// Enable prefetching containers in resolver.
223223
fileprivate let resolverPrefetchingEnabled: Bool
@@ -259,6 +259,7 @@ public class Workspace {
259259
/// - customHTTPClient: A custom http client.
260260
/// - customArchiver: A custom archiver.
261261
/// - customChecksumAlgorithm: A custom checksum algorithm.
262+
/// - customFingerprintStorage: A custom fingerprint storage.
262263
/// - additionalFileRules: File rules to determine resource handling behavior.
263264
/// - resolverUpdateEnabled: Enables the dependencies resolver automatic version update check. Enabled by default. When disabled the resolver relies only on the resolved version file
264265
/// - resolverPrefetchingEnabled: Enables the dependencies resolver prefetching based on the resolved version file. Enabled by default.
@@ -306,10 +307,12 @@ public class Workspace {
306307
delegate: delegate.map(WorkspaceRepositoryManagerDelegate.init(workspaceDelegate:)),
307308
cachePath: sharedRepositoriesCacheEnabled ? location.sharedRepositoriesCacheDirectory : .none
308309
)
309-
let fingerprintStorage = customFingerprintStorage ?? FilePackageFingerprintStorage(
310-
fileSystem: fileSystem,
311-
directoryPath: location.sharedFingerprintsDirectory
312-
)
310+
let fingerprintStorage = customFingerprintStorage ?? location.sharedFingerprintsDirectory.map {
311+
FilePackageFingerprintStorage(
312+
fileSystem: fileSystem,
313+
directoryPath: $0
314+
)
315+
}
313316

314317
let registryClient = customRegistryClient ?? registries.map { configuration in
315318
RegistryClient(

Sources/Workspace/WorkspaceConfiguration.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ extension Workspace {
3232
public var resolvedVersionsFile: AbsolutePath
3333

3434
/// Path to the shared security directory
35-
public var sharedSecurityDirectory: AbsolutePath
35+
public var sharedSecurityDirectory: AbsolutePath?
3636

3737
/// Path to the shared cache directory
3838
public var sharedCacheDirectory: AbsolutePath?
@@ -61,8 +61,8 @@ extension Workspace {
6161
}
6262

6363
/// Path to the shared fingerprints directory.
64-
public var sharedFingerprintsDirectory: AbsolutePath {
65-
self.sharedSecurityDirectory.appending(component: "fingerprints")
64+
public var sharedFingerprintsDirectory: AbsolutePath? {
65+
self.sharedSecurityDirectory.map { $0.appending(component: "fingerprints") }
6666
}
6767

6868
/// Path to the shared repositories cache.
@@ -103,7 +103,7 @@ extension Workspace {
103103
workingDirectory: AbsolutePath,
104104
editsDirectory: AbsolutePath,
105105
resolvedVersionsFile: AbsolutePath,
106-
sharedSecurityDirectory: AbsolutePath,
106+
sharedSecurityDirectory: AbsolutePath?,
107107
sharedCacheDirectory: AbsolutePath?,
108108
sharedConfigurationDirectory: AbsolutePath?
109109
) {

0 commit comments

Comments
 (0)