Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
554f22e
Add a TimedCertificateReloader
gjcairo Apr 30, 2025
6b6a5b5
Remove unneeded imports
gjcairo Apr 30, 2025
7a755d7
Refactor some stuff
gjcairo Apr 30, 2025
744d803
Add conformance to ServiceLifecycle/Service
gjcairo Apr 30, 2025
0dae5b2
Format
gjcairo Apr 30, 2025
617d1ac
Fix broken imports
gjcairo Apr 30, 2025
222a050
Add more docs
gjcairo Apr 30, 2025
09520a7
Make sslContextConfigurationOverride async
gjcairo Apr 30, 2025
b8eb9fb
Fix imports
gjcairo Apr 30, 2025
ca8e42a
Add init using `Duration`
gjcairo May 1, 2025
65a4600
PR comments
gjcairo May 1, 2025
c9402e9
Format
gjcairo May 1, 2025
76a3dc5
Add missing import
gjcairo May 1, 2025
452b193
Fix docs
gjcairo May 1, 2025
ef88924
Add missing import
gjcairo May 1, 2025
ca5962e
Rename targets and move around some code
gjcairo May 2, 2025
e295c15
Add throwing init
gjcairo May 6, 2025
2fd8dca
Use `AsyncTimerSequence` to handle graceful shutdown properly
gjcairo May 6, 2025
ed45e53
Add docs and a new TLSConfig init
gjcairo May 6, 2025
9771210
Formatting
gjcairo May 6, 2025
6eb1e0c
Fix availability guards
gjcairo May 7, 2025
47d25bb
Annotate `setCertificateReloader` with `@discardableResult`
gjcairo May 7, 2025
9e3d133
Add missing import
gjcairo May 7, 2025
eb78ed3
Format
gjcairo May 7, 2025
8fbba2d
Add logging when reloading fails
gjcairo May 7, 2025
2f52c35
PR changes
gjcairo May 7, 2025
fa65c5b
Rename `CertificateDescription` to `CertificateSource`
gjcairo May 7, 2025
516c61c
Rename `PrivateKeyDescription` to `PrivateKeySource`
gjcairo May 7, 2025
f3cce7f
Rename some missing CertificateDescription arguments
gjcairo May 7, 2025
33fe740
PR changes
gjcairo May 8, 2025
d8c0f43
Improve docs
gjcairo May 8, 2025
87b0335
Return `NIOSSLContextConfigurationOverride.noChanges` when no pair pr…
gjcairo May 8, 2025
c34a816
PR changes
gjcairo May 12, 2025
6570045
Set cert and pkey in client config
gjcairo May 13, 2025
2852c84
PR changes
gjcairo May 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,29 @@ var targets: [PackageDescription.Target] = [
],
swiftSettings: strictConcurrencySettings
),
.target(
name: "NIOCertificateReloading",
dependencies: [
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOSSL", package: "swift-nio-ssl"),
.product(name: "X509", package: "swift-certificates"),
.product(name: "SwiftASN1", package: "swift-asn1"),
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
],
swiftSettings: strictConcurrencySettings
),
.testTarget(
name: "NIOCertificateReloadingTests",
dependencies: [
"NIOCertificateReloading",
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOSSL", package: "swift-nio-ssl"),
.product(name: "X509", package: "swift-certificates"),
.product(name: "SwiftASN1", package: "swift-asn1"),
],
swiftSettings: strictConcurrencySettings
),
]

let package = Package(
Expand All @@ -270,6 +293,7 @@ let package = Package(
.library(name: "NIOHTTPTypesHTTP2", targets: ["NIOHTTPTypesHTTP2"]),
.library(name: "NIOResumableUpload", targets: ["NIOResumableUpload"]),
.library(name: "NIOHTTPResponsiveness", targets: ["NIOHTTPResponsiveness"]),
.library(name: "NIOCertificateReloading", targets: ["NIOCertificateReloading"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.81.0"),
Expand All @@ -278,6 +302,11 @@ let package = Package(
.package(url: "https://github.com/apple/swift-http-structured-headers.git", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-certificates.git", branch: "1.10.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.29.3"),
.package(url: "https://github.com/apple/swift-asn1.git", from: "1.3.1"),
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.8.0"),
.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"),

],
targets: targets
Expand Down
86 changes: 86 additions & 0 deletions Sources/NIOCertificateReloading/CertificateReloader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2025 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import NIOCore
import NIOSSL

/// A protocol that defines a certificate reloader.
///
/// A certificate reloader is a service that can provide you with updated versions of a certificate and private key pair, in
/// the form of a `NIOSSLContextConfigurationOverride`, which will be used when performing a TLS handshake in NIO.
/// Each implementation can choose how to observe for changes, but they all require an ``sslContextConfigurationOverride``
/// to be exposed.
public protocol CertificateReloader: Sendable {
/// A `NIOSSLContextConfigurationOverride` that will be used as part of the NIO application's TLS configuration.
/// Its certificate and private key will be kept up-to-date via whatever mechanism the specific ``CertificateReloader``
/// implementation provides.
var sslContextConfigurationOverride: NIOSSLContextConfigurationOverride { get }
}

extension TLSConfiguration {
/// Errors thrown when creating a ``NIOSSL/TLSConfiguration`` with a ``CertificateReloader``.
public struct CertificateReloaderError: Error {
private enum _Backing {
case missingCertificateChain
case missingPrivateKey
}

private let backing: _Backing

private init(backing: _Backing) {
self.backing = backing
}

/// The given ``CertificateReloader`` could not provide a certificate chain with which to create this config.
public static let missingCertificateChain: Self = .init(backing: .missingCertificateChain)

/// The given ``CertificateReloader`` could not provide a private key with which to create this config.
public static let missingPrivateKey: Self = .init(backing: .missingPrivateKey)
}

/// Create a ``NIOSSL/TLSConfiguration`` for use with server-side contexts, with certificate reloading enabled.
/// - Parameter certificateReloader: A ``CertificateReloader`` to watch for certificate and key pair updates.
/// - Returns: A ``NIOSSL/TLSConfiguration`` for use with server-side contexts, that reloads the certificate and key
/// used in its SSL handshake.
public static func makeServerConfiguration(
certificateReloader: some CertificateReloader
) throws -> Self {
let override = certificateReloader.sslContextConfigurationOverride

guard let certificateChain = override.certificateChain else {
throw CertificateReloaderError.missingCertificateChain
}

guard let privateKey = override.privateKey else {
throw CertificateReloaderError.missingPrivateKey
}

var configuration = Self.makeServerConfiguration(
certificateChain: certificateChain,
privateKey: privateKey
)
return configuration.setCertificateReloader(certificateReloader)
}

/// Configure a ``CertificateReloader`` to observe updates for the certificate and key pair used.
/// - Parameter reloader: A ``CertificateReloader`` to watch for certificate and key pair updates.
/// - Returns: A ``NIOSSL/TLSConfiguration`` that reloads the certificate and key used in its SSL handshake.
@discardableResult
mutating public func setCertificateReloader(_ reloader: some CertificateReloader) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is mildly confusing to me, it's mutating but also returns self

If the return is just for convenience, then we should maybe make it discardable result

Or was it intended to not mutate the original and make a copy?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, xcode says you need to import NIOCore

Instance method 'succeed' is not available due to missing import of defining module 'NIOCore'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for convenience, so you can chain calls. I've added @discardableResult.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm with @hamzahrmalik on this one: it's a bit confusing.

Chaining calls isn't a common pattern for config structs or for TLSConfiguration so this shouldn't return Self.

self.sslContextCallback = { _, promise in
promise.succeed(reloader.sslContextConfigurationOverride)
}
return self
}
}
Loading
Loading