Skip to content

Commit 7e88f84

Browse files
committed
Add docs and a new TLSConfig init
1 parent b36f8e0 commit 7e88f84

File tree

3 files changed

+96
-13
lines changed

3 files changed

+96
-13
lines changed

Sources/NIOCertificateReloading/CertificateReloader.swift

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import NIOSSL
2020
/// the form of a `NIOSSLContextConfigurationOverride`, which will be used when performing a TLS handshake in NIO.
2121
/// Each implementation can choose how to observe for changes, but they all require an ``sslContextConfigurationOverride``
2222
/// to be exposed.
23-
@available(macOS 11.0, iOS 14, tvOS 14, watchOS 7, *)
2423
public protocol CertificateReloader: Sendable {
2524
/// A `NIOSSLContextConfigurationOverride` that will be used as part of the NIO application's TLS configuration.
2625
/// Its certificate and private key will be kept up-to-date via whatever mechanism the specific ``CertificateReloader``
@@ -29,10 +28,53 @@ public protocol CertificateReloader: Sendable {
2928
}
3029

3130
extension TLSConfiguration {
31+
/// Errors thrown when creating a ``NIOSSL/TLSConfiguration`` with a ``CertificateReloader``.
32+
public struct CertificateReloaderError: Error {
33+
private enum _Backing {
34+
case missingCertificateChain
35+
case missingPrivateKey
36+
}
37+
38+
private let backing: _Backing
39+
40+
private init(backing: _Backing) {
41+
self.backing = backing
42+
}
43+
44+
/// The given ``CertificateReloader`` could not provide a certificate chain with which to create this config.
45+
public static let missingCertificateChain: Self = .init(backing: .missingCertificateChain)
46+
47+
/// The given ``CertificateReloader`` could not provide a private key with which to create this config.
48+
public static let missingPrivateKey: Self = .init(backing: .missingPrivateKey)
49+
}
50+
51+
/// Create a ``NIOSSL/TLSConfiguration`` for use with server-side contexts, with certificate reloading enabled.
52+
/// - Parameter certificateReloader: A ``CertificateReloader`` to watch for certificate and key pair updates.
53+
/// - Returns: A ``NIOSSL/TLSConfiguration`` for use with server-side contexts, that reloads the certificate and key
54+
/// used in its SSL handshake.
55+
public static func makeServerConfiguration(
56+
certificateReloader: some CertificateReloader
57+
) throws -> Self {
58+
let override = certificateReloader.sslContextConfigurationOverride
59+
60+
guard let certificateChain = override.certificateChain else {
61+
throw CertificateReloaderError.missingCertificateChain
62+
}
63+
64+
guard let privateKey = override.privateKey else {
65+
throw CertificateReloaderError.missingPrivateKey
66+
}
67+
68+
var configuration = Self.makeServerConfiguration(
69+
certificateChain: certificateChain,
70+
privateKey: privateKey
71+
)
72+
return configuration.setCertificateReloader(certificateReloader)
73+
}
74+
3275
/// Configure a ``CertificateReloader`` to observe updates for the certificate and key pair used.
3376
/// - Parameter reloader: A ``CertificateReloader`` to watch for certificate and key pair updates.
34-
/// - Returns: A `TLSConfiguration` that reloads the certificate and key used in its SSL handshake.
35-
@available(macOS 11.0, iOS 14, tvOS 14, watchOS 7, *)
77+
/// - Returns: A ``NIOSSL/TLSConfiguration`` that reloads the certificate and key used in its SSL handshake.
3678
mutating public func setCertificateReloader(_ reloader: some CertificateReloader) -> Self {
3779
self.sslContextCallback = { _, promise in
3880
promise.succeed(reloader.sslContextConfigurationOverride)

Sources/NIOCertificateReloading/TimedCertificateReloader.swift

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,56 @@ import Foundation
3131
/// key pair is updated at a fixed interval from the file path or memory location configured.
3232
///
3333
/// You initialize a ``TimedCertificateReloader`` by providing a refresh interval, and locations for the certificate and the private
34-
/// key. You must then call ``run()`` on this reloader for it to start observing changes.
35-
/// Once the reloader is running, call ``sslContextConfigurationOverride`` to get a
36-
/// `NIOSSLContextConfigurationOverride` which can be set on NIO's `TLSConfiguration`: this will keep the certificate
37-
/// and private key pair up to date.
38-
/// You may instead call ``NIOSSL/TLSConfiguration/setCertificateReloader(_:)`` to get a
39-
/// ``NIOSSL/TLSConfiguration`` with a configured reloader.
34+
/// key. You may then set it on your ``NIOSSL/TLSConfiguration`` using
35+
/// ``NIOSSL/TLSConfiguration/setCertificateReloader(_:)``:
36+
///
37+
/// ```
38+
/// var configuration = TLSConfiguration.makeServerConfiguration(
39+
/// certificateChain: chain,
40+
/// privateKey: key
41+
/// )
42+
/// let reloader = TimedCertificateReloader(
43+
/// refreshInterval: .seconds(500),
44+
/// certificateDescription: TimedCertificateReloader.CertificateDescription(...),
45+
/// privateKeyDescription: TimedCertificateReloader.PrivateKeyDescription(...)
46+
/// )
47+
/// configuration.setCertificateReloader(reloader)
48+
/// ```
49+
///
50+
/// If you're creating a server configuration, you can instead opt to use
51+
/// ``NIOSSL/TLSConfiguration/makeServerConfiguration(certificateReloader:)``, which will set the initial
52+
/// certificate chain and private key, as well as set the reloader:
53+
///
54+
/// ```
55+
/// let reloader = TimedCertificateReloader(
56+
/// refreshInterval: .seconds(500),
57+
/// certificateDescription: TimedCertificateReloader.CertificateDescription(...),
58+
/// privateKeyDescription: TimedCertificateReloader.PrivateKeyDescription(...)
59+
/// )
60+
/// let configuration = TLSConfiguration.makeServerConfiguration(
61+
/// certificateReloader: reloader
62+
/// )
63+
/// ```
64+
///
65+
/// Finally, you must call ``run()`` on the reloader for it to start observing changes.
66+
/// Once the reloader is running, you can also manually access its ``sslContextConfigurationOverride`` property to get a
67+
/// `NIOSSLContextConfigurationOverride`, although this will typically not be necessary, as it's the NIO channel that will
68+
/// handle the override when initiating TLS handshakes.
69+
///
70+
/// ```
71+
/// try await withThrowingTaskGroup(of: Void.self) { group in
72+
/// group.addTask {
73+
/// reloader.run()
74+
/// }
75+
///
76+
/// // ...
77+
/// let override = reloader.sslContextConfigurationOverride
78+
/// // ...
79+
/// }
80+
/// ```
81+
///
82+
/// ``TimedCertificateReloader`` conforms to `ServiceLifecycle`'s `Service` protocol, meaning you can simply create
83+
/// the reloader and add it to your `ServiceGroup` without having to manually run it.
4084
///
4185
/// If any errors occur during a reload attempt (such as: being unable to find the file(s) containing the certificate or the key; the format
4286
/// not being recognized or not matching the configured one; not being able to verify a certificate's signature against the given

Tests/NIOCertificateReloadingTests/TimedCertificateReloaderTests.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,7 @@ final class TimedCertificateReloaderTests: XCTestCase {
315315
group.addTask {
316316
try await reloader.run()
317317
}
318-
group.addTask {
319-
try await body(reloader)
320-
}
321-
try await group.next()
318+
try await body(reloader)
322319
group.cancelAll()
323320
}
324321
}

0 commit comments

Comments
 (0)