Skip to content

Commit b741798

Browse files
authored
Registry configuration - security (#6159)
* Registry configuration - security Implement new `security` configuration per registry publish proposal * Fix compilation error * Add test
1 parent dc436c0 commit b741798

File tree

3 files changed

+709
-10
lines changed

3 files changed

+709
-10
lines changed

Sources/PackageRegistry/RegistryConfiguration.swift

Lines changed: 281 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift open source project
44
//
5-
// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See http://swift.org/LICENSE.txt for license information
@@ -24,15 +24,17 @@ public struct RegistryConfiguration: Hashable {
2424
public var defaultRegistry: Registry?
2525
public var scopedRegistries: [PackageIdentity.Scope: Registry]
2626
public var registryAuthentication: [String: Authentication]
27+
public var security: Security?
2728

2829
public init() {
2930
self.defaultRegistry = nil
3031
self.scopedRegistries = [:]
3132
self.registryAuthentication = [:]
33+
self.security = nil
3234
}
3335

3436
public var isEmpty: Bool {
35-
return self.defaultRegistry == nil && self.scopedRegistries.isEmpty
37+
self.defaultRegistry == nil && self.scopedRegistries.isEmpty
3638
}
3739

3840
public mutating func merge(_ other: RegistryConfiguration) {
@@ -57,6 +59,33 @@ public struct RegistryConfiguration: Hashable {
5759
guard let host = registryURL.host else { return nil }
5860
return self.registryAuthentication[host]
5961
}
62+
63+
public func signing(for package: PackageIdentity, registry: Registry) throws -> Security.Signing {
64+
guard case (let scope, _)? = package.scopeAndName else {
65+
throw StringError("Only package identity in <scope>.<name> format is supported: '\(package)'")
66+
}
67+
68+
let global = self.security?.default.signing
69+
let registryOverrides = registry.url.host.flatMap { host in self.security?.registryOverrides[host]?.signing }
70+
let scopeOverrides = self.security?.scopeOverrides[scope]?.signing
71+
let packageOverrides = self.security?.packageOverrides[package]?.signing
72+
73+
var signing = Security.Signing.default
74+
if let global = global {
75+
signing.merge(global)
76+
}
77+
if let registryOverrides = registryOverrides {
78+
signing.merge(registryOverrides)
79+
}
80+
if let scopeOverrides = scopeOverrides {
81+
signing.merge(scopeOverrides)
82+
}
83+
if let packageOverrides = packageOverrides {
84+
signing.merge(packageOverrides)
85+
}
86+
87+
return signing
88+
}
6089
}
6190

6291
extension RegistryConfiguration {
@@ -76,16 +105,178 @@ extension RegistryConfiguration {
76105
}
77106
}
78107

108+
extension RegistryConfiguration {
109+
public struct Security: Hashable {
110+
public var `default`: Global
111+
public var registryOverrides: [String: RegistryOverride]
112+
public var scopeOverrides: [PackageIdentity.Scope: ScopePackageOverride]
113+
public var packageOverrides: [PackageIdentity: ScopePackageOverride]
114+
115+
public init() {
116+
self.default = Global()
117+
self.registryOverrides = [:]
118+
self.scopeOverrides = [:]
119+
self.packageOverrides = [:]
120+
}
121+
122+
public struct Global: Hashable, Codable {
123+
public var signing: Signing?
124+
125+
public init() {
126+
self.signing = nil
127+
}
128+
}
129+
130+
public struct RegistryOverride: Hashable, Codable {
131+
public var signing: Signing?
132+
133+
public init() {
134+
self.signing = nil
135+
}
136+
}
137+
138+
public struct Signing: Hashable, Codable {
139+
static let `default`: Signing = {
140+
var signing = Signing()
141+
signing.onUnsigned = .prompt
142+
signing.onUntrustedCertificate = .prompt
143+
signing.trustedRootCertificatesPath = nil
144+
signing.includeDefaultTrustedRootCertificates = true
145+
146+
var validationChecks = Signing.ValidationChecks()
147+
validationChecks.certificateExpiration = .disabled
148+
validationChecks.certificateRevocation = .disabled
149+
signing.validationChecks = validationChecks
150+
151+
return signing
152+
}()
153+
154+
public var onUnsigned: OnUnsignedAction?
155+
public var onUntrustedCertificate: OnUntrustedCertificateAction?
156+
public var trustedRootCertificatesPath: String?
157+
public var includeDefaultTrustedRootCertificates: Bool?
158+
public var validationChecks: ValidationChecks?
159+
160+
public init() {
161+
self.onUnsigned = nil
162+
self.onUntrustedCertificate = nil
163+
self.trustedRootCertificatesPath = nil
164+
self.includeDefaultTrustedRootCertificates = nil
165+
self.validationChecks = nil
166+
}
167+
168+
mutating func merge(_ other: Signing) {
169+
if let onUnsigned = other.onUnsigned {
170+
self.onUnsigned = onUnsigned
171+
}
172+
if let onUntrustedCertificate = other.onUntrustedCertificate {
173+
self.onUntrustedCertificate = onUntrustedCertificate
174+
}
175+
if let trustedRootCertificatesPath = other.trustedRootCertificatesPath {
176+
self.trustedRootCertificatesPath = trustedRootCertificatesPath
177+
}
178+
if let includeDefaultTrustedRootCertificates = other.includeDefaultTrustedRootCertificates {
179+
self.includeDefaultTrustedRootCertificates = includeDefaultTrustedRootCertificates
180+
}
181+
if let validationChecks = other.validationChecks {
182+
self.validationChecks?.merge(validationChecks)
183+
}
184+
}
185+
186+
mutating func merge(_ other: ScopePackageOverride.Signing) {
187+
if let trustedRootCertificatesPath = other.trustedRootCertificatesPath {
188+
self.trustedRootCertificatesPath = trustedRootCertificatesPath
189+
}
190+
if let includeDefaultTrustedRootCertificates = other.includeDefaultTrustedRootCertificates {
191+
self.includeDefaultTrustedRootCertificates = includeDefaultTrustedRootCertificates
192+
}
193+
}
194+
195+
public enum OnUnsignedAction: String, Hashable, Codable {
196+
case error
197+
case prompt
198+
case warn
199+
case silentAllow
200+
}
201+
202+
public enum OnUntrustedCertificateAction: String, Hashable, Codable {
203+
case error
204+
case prompt
205+
case warn
206+
case silentTrust
207+
}
208+
209+
public struct ValidationChecks: Hashable, Codable {
210+
public var certificateExpiration: CertificateExpirationCheck?
211+
public var certificateRevocation: CertificateRevocationCheck?
212+
213+
public init() {
214+
self.certificateExpiration = nil
215+
self.certificateRevocation = nil
216+
}
217+
218+
mutating func merge(_ other: ValidationChecks) {
219+
if let certificateExpiration = other.certificateExpiration {
220+
self.certificateExpiration = certificateExpiration
221+
}
222+
if let certificateRevocation = other.certificateRevocation {
223+
self.certificateRevocation = certificateRevocation
224+
}
225+
}
226+
227+
public enum CertificateExpirationCheck: String, Hashable, Codable {
228+
case enabled
229+
case disabled
230+
}
231+
232+
public enum CertificateRevocationCheck: String, Hashable, Codable {
233+
case strict
234+
case allowSoftFail
235+
case disabled
236+
}
237+
}
238+
}
239+
240+
public struct ScopePackageOverride: Hashable, Codable {
241+
public var signing: Signing?
242+
243+
public init() {
244+
self.signing = nil
245+
}
246+
247+
public struct Signing: Hashable, Codable {
248+
public var trustedRootCertificatesPath: String?
249+
public var includeDefaultTrustedRootCertificates: Bool?
250+
251+
public init() {
252+
self.trustedRootCertificatesPath = nil
253+
self.includeDefaultTrustedRootCertificates = nil
254+
}
255+
256+
mutating func merge(_ other: Signing) {
257+
if let trustedRootCertificatesPath = other.trustedRootCertificatesPath {
258+
self.trustedRootCertificatesPath = trustedRootCertificatesPath
259+
}
260+
if let includeDefaultTrustedRootCertificates = other.includeDefaultTrustedRootCertificates {
261+
self.includeDefaultTrustedRootCertificates = includeDefaultTrustedRootCertificates
262+
}
263+
}
264+
}
265+
}
266+
}
267+
}
268+
79269
// MARK: - Codable
80270

81271
extension RegistryConfiguration: Codable {
82272
private enum CodingKeys: String, CodingKey {
83273
case registries
84274
case authentication
275+
case security
85276
case version
86277
}
87278

88-
private struct ScopeCodingKey: CodingKey, Hashable {
279+
fileprivate struct ScopeCodingKey: CodingKey, Hashable {
89280
static let `default` = ScopeCodingKey(stringValue: "[default]")
90281

91282
var stringValue: String
@@ -96,11 +287,11 @@ extension RegistryConfiguration: Codable {
96287
}
97288

98289
init?(intValue: Int) {
99-
return nil
290+
nil
100291
}
101292
}
102293

103-
private struct AuthenticationCodingKey: CodingKey, Hashable {
294+
fileprivate struct PackageCodingKey: CodingKey, Hashable {
104295
var stringValue: String
105296
var intValue: Int? { nil }
106297

@@ -109,7 +300,7 @@ extension RegistryConfiguration: Codable {
109300
}
110301

111302
init?(intValue: Int) {
112-
return nil
303+
nil
113304
}
114305
}
115306

@@ -130,9 +321,17 @@ extension RegistryConfiguration: Codable {
130321
}
131322
self.scopedRegistries = scopedRegistries
132323

133-
self.registryAuthentication = try container.decodeIfPresent([String: Authentication].self, forKey: .authentication) ?? [:]
324+
self.registryAuthentication = try container.decodeIfPresent(
325+
[String: Authentication].self,
326+
forKey: .authentication
327+
) ?? [:]
328+
self.security = try container.decodeIfPresent(Security.self, forKey: .security) ?? nil
134329
case nil:
135-
throw DecodingError.dataCorruptedError(forKey: .version, in: container, debugDescription: "invalid version: \(version)")
330+
throw DecodingError.dataCorruptedError(
331+
forKey: .version,
332+
in: container,
333+
debugDescription: "invalid version: \(version)"
334+
)
136335
}
137336
}
138337

@@ -151,5 +350,79 @@ extension RegistryConfiguration: Codable {
151350
}
152351

153352
try container.encode(self.registryAuthentication, forKey: .authentication)
353+
try container.encodeIfPresent(self.security, forKey: .security)
354+
}
355+
}
356+
357+
extension RegistryConfiguration.Security: Codable {
358+
private enum CodingKeys: String, CodingKey {
359+
case `default`
360+
case registryOverrides
361+
case scopeOverrides
362+
case packageOverrides
363+
}
364+
365+
public init(from decoder: Decoder) throws {
366+
let container = try decoder.container(keyedBy: CodingKeys.self)
367+
368+
self.default = try container.decodeIfPresent(Global.self, forKey: .default) ?? Global()
369+
self.registryOverrides = try container.decodeIfPresent(
370+
[String: RegistryOverride].self,
371+
forKey: .registryOverrides
372+
) ?? [:]
373+
374+
let scopeOverridesContainer = try container.decodeIfPresent(
375+
[String: ScopePackageOverride].self,
376+
forKey: .scopeOverrides
377+
) ?? [:]
378+
var scopeOverrides: [PackageIdentity.Scope: ScopePackageOverride] = [:]
379+
for (key, scopeOverride) in scopeOverridesContainer {
380+
let scope = try PackageIdentity.Scope(validating: key)
381+
scopeOverrides[scope] = scopeOverride
382+
}
383+
self.scopeOverrides = scopeOverrides
384+
385+
let packageOverridesContainer = try container.decodeIfPresent(
386+
[String: ScopePackageOverride].self,
387+
forKey: .packageOverrides
388+
) ?? [:]
389+
var packageOverrides: [PackageIdentity: ScopePackageOverride] = [:]
390+
for (key, packageOverride) in packageOverridesContainer {
391+
let packageIdentity = PackageIdentity.plain(key)
392+
guard packageIdentity.scopeAndName != nil else {
393+
throw DecodingError.dataCorruptedError(
394+
forKey: .packageOverrides,
395+
in: container,
396+
debugDescription: "invalid package identity: \(key)"
397+
)
398+
}
399+
packageOverrides[packageIdentity] = packageOverride
400+
}
401+
self.packageOverrides = packageOverrides
402+
}
403+
404+
public func encode(to encoder: Encoder) throws {
405+
var container = encoder.container(keyedBy: CodingKeys.self)
406+
407+
try container.encode(self.default, forKey: .default)
408+
try container.encode(self.registryOverrides, forKey: .registryOverrides)
409+
410+
var scopeOverridesContainer = container.nestedContainer(
411+
keyedBy: RegistryConfiguration.ScopeCodingKey.self,
412+
forKey: .scopeOverrides
413+
)
414+
for (scope, scopeOverride) in self.scopeOverrides {
415+
let key = RegistryConfiguration.ScopeCodingKey(stringValue: scope.description)
416+
try scopeOverridesContainer.encode(scopeOverride, forKey: key)
417+
}
418+
419+
var packageOverridesContainer = container.nestedContainer(
420+
keyedBy: RegistryConfiguration.PackageCodingKey.self,
421+
forKey: .packageOverrides
422+
)
423+
for (packageIdentity, packageOverride) in self.packageOverrides {
424+
let key = RegistryConfiguration.PackageCodingKey(stringValue: packageIdentity.description)
425+
try packageOverridesContainer.encode(packageOverride, forKey: key)
426+
}
154427
}
155428
}

Sources/Workspace/Workspace+Configuration.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ extension Workspace {
116116
self.sharedSecurityDirectory.map { $0.appending(component: "fingerprints") }
117117
}
118118

119+
/// Path to the shared trusted root certificates directory.
120+
public var sharedTrustedRootCertificatesDirectory: AbsolutePath? {
121+
self.sharedSecurityDirectory.map { $0.appending(component: "trusted-root-certs") }
122+
}
123+
119124
// cache locations
120125

121126
/// Path to the shared manifests cache.

0 commit comments

Comments
 (0)