Skip to content

Commit 62b7f85

Browse files
authored
Add TLS support for NIOPosix H2 client (grpc#2036)
## Motivation We currently have a `NIOPosix` client transport implementation in gRPC v2, but it doesn't support TLS. ## Modifications This PR adds support for TLS in the NIOPosix-backed HTTP/2 implementation of the client transport for gRPC v2. ## Result We now support TLS when using the NIOPosix client transport in gRPC V2.
1 parent 17b2054 commit 62b7f85

File tree

17 files changed

+303
-45
lines changed

17 files changed

+303
-45
lines changed

Examples/v2/echo/Subcommands/Collect.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct Collect: AsyncParsableCommand {
3232
let client = GRPCClient(
3333
transport: try .http2NIOPosix(
3434
target: self.arguments.target,
35-
config: .defaults()
35+
config: .defaults(transportSecurity: .plaintext)
3636
)
3737
)
3838

Examples/v2/echo/Subcommands/Expand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct Expand: AsyncParsableCommand {
3232
let client = GRPCClient(
3333
transport: try .http2NIOPosix(
3434
target: self.arguments.target,
35-
config: .defaults()
35+
config: .defaults(transportSecurity: .plaintext)
3636
)
3737
)
3838

Examples/v2/echo/Subcommands/Get.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ struct Get: AsyncParsableCommand {
3030
let client = GRPCClient(
3131
transport: try .http2NIOPosix(
3232
target: self.arguments.target,
33-
config: .defaults()
33+
config: .defaults(transportSecurity: .plaintext)
3434
)
3535
)
3636

Examples/v2/echo/Subcommands/Update.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct Update: AsyncParsableCommand {
3232
let client = GRPCClient(
3333
transport: try .http2NIOPosix(
3434
target: self.arguments.target,
35-
config: .defaults()
35+
config: .defaults(transportSecurity: .plaintext)
3636
)
3737
)
3838

Examples/v2/hello-world/Subcommands/Greet.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct Greet: AsyncParsableCommand {
3333
let client = GRPCClient(
3434
transport: try .http2NIOPosix(
3535
target: .ipv4(host: "127.0.0.1", port: self.port),
36-
config: .defaults()
36+
config: .defaults(transportSecurity: .plaintext)
3737
)
3838
)
3939

Examples/v2/route-guide/Subcommands/GetFeature.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct GetFeature: AsyncParsableCommand {
3939
func run() async throws {
4040
let transport = try HTTP2ClientTransport.Posix(
4141
target: .ipv4(host: "127.0.0.1", port: self.port),
42-
config: .defaults()
42+
config: .defaults(transportSecurity: .plaintext)
4343
)
4444
let client = GRPCClient(transport: transport)
4545

Examples/v2/route-guide/Subcommands/ListFeatures.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ struct ListFeatures: AsyncParsableCommand {
5353
func run() async throws {
5454
let transport = try HTTP2ClientTransport.Posix(
5555
target: .ipv4(host: "127.0.0.1", port: self.port),
56-
config: .defaults()
56+
config: .defaults(transportSecurity: .plaintext)
5757
)
5858
let client = GRPCClient(transport: transport)
5959

Examples/v2/route-guide/Subcommands/RecordRoute.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct RecordRoute: AsyncParsableCommand {
3232
func run() async throws {
3333
let transport = try HTTP2ClientTransport.Posix(
3434
target: .ipv4(host: "127.0.0.1", port: self.port),
35-
config: .defaults()
35+
config: .defaults(transportSecurity: .plaintext)
3636
)
3737
let client = GRPCClient(transport: transport)
3838

Examples/v2/route-guide/Subcommands/RouteChat.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct RouteChat: AsyncParsableCommand {
3232
func run() async throws {
3333
let transport = try HTTP2ClientTransport.Posix(
3434
target: .ipv4(host: "127.0.0.1", port: self.port),
35-
config: .defaults()
35+
config: .defaults(transportSecurity: .plaintext)
3636
)
3737
let client = GRPCClient(transport: transport)
3838

Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,38 @@ public import GRPCHTTP2Core // should be @usableFromInline
1919
public import NIOCore
2020
public import NIOPosix // has to be public because of default argument value in init
2121

22+
#if canImport(NIOSSL)
23+
import NIOSSL
24+
#endif
25+
2226
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
2327
extension HTTP2ClientTransport {
24-
/// A `ClientTransport` using HTTP/2 built on top of `NIOPosix`.
28+
/// A ``GRPCCore/ClientTransport`` using HTTP/2 built on top of `NIOPosix`.
2529
///
2630
/// This transport builds on top of SwiftNIO's Posix networking layer and is suitable for use
2731
/// on Linux and Darwin based platform (macOS, iOS, etc.) However, it's *strongly* recommended
2832
/// that if you are targeting Darwin platforms then you should use the `NIOTS` variant of
29-
/// the `HTTP2ClientTransport`.
33+
/// the ``GRPCHTTP2Core/HTTP2ClientTransport``.
3034
///
3135
/// To use this transport you need to provide a 'target' to connect to which will be resolved
3236
/// by an appropriate resolver from the resolver registry. By default the resolver registry can
3337
/// resolve DNS targets, IPv4 and IPv6 targets, Unix domain socket targets, and Virtual Socket
3438
/// targets. If you use a custom target you must also provide an appropriately configured
3539
/// registry.
3640
///
37-
/// You can control various aspects of connection creation, management and RPC behavior via the
38-
/// ``Config``. Load balancing policies and other RPC specific behavior can be configured via
41+
/// You can control various aspects of connection creation, management, security and RPC behavior via
42+
/// the ``Config``. Load balancing policies and other RPC specific behavior can be configured via
3943
/// the ``ServiceConfig`` (if it isn't provided by a resolver).
4044
///
4145
/// Beyond creating the transport you don't need to interact with it directly, instead, pass it
4246
/// to a `GRPCClient`:
4347
///
4448
/// ```swift
45-
/// try await withThrowingDiscardingTaskGroup {
46-
/// let transport = try HTTP2ClientTransport.Posix(target: .dns(host: "example.com"))
49+
/// try await withThrowingDiscardingTaskGroup { group in
50+
/// let transport = try HTTP2ClientTransport.Posix(
51+
/// target: .ipv4(host: "example.com"),
52+
/// config: .defaults(transportSecurity: .plaintext)
53+
/// )
4754
/// let client = GRPCClient(transport: transport)
4855
/// group.addTask {
4956
/// try await client.run()
@@ -87,7 +94,7 @@ extension HTTP2ClientTransport {
8794
// Configure a connector.
8895
self.channel = GRPCChannel(
8996
resolver: resolver,
90-
connector: Connector(eventLoopGroup: eventLoopGroup, config: config),
97+
connector: try Connector(eventLoopGroup: eventLoopGroup, config: config),
9198
config: GRPCChannel.Config(posix: config),
9299
defaultServiceConfig: serviceConfig
93100
)
@@ -125,9 +132,33 @@ extension HTTP2ClientTransport.Posix {
125132
private let config: HTTP2ClientTransport.Posix.Config
126133
private let eventLoopGroup: any EventLoopGroup
127134

128-
init(eventLoopGroup: any EventLoopGroup, config: HTTP2ClientTransport.Posix.Config) {
135+
#if canImport(NIOSSL)
136+
private let nioSSLContext: NIOSSLContext?
137+
private let serverHostname: String?
138+
#endif
139+
140+
init(eventLoopGroup: any EventLoopGroup, config: HTTP2ClientTransport.Posix.Config) throws {
129141
self.eventLoopGroup = eventLoopGroup
130142
self.config = config
143+
144+
#if canImport(NIOSSL)
145+
switch self.config.transportSecurity.wrapped {
146+
case .plaintext:
147+
self.nioSSLContext = nil
148+
self.serverHostname = nil
149+
case .tls(let tlsConfig):
150+
do {
151+
self.nioSSLContext = try NIOSSLContext(configuration: TLSConfiguration(tlsConfig))
152+
self.serverHostname = tlsConfig.serverHostname
153+
} catch {
154+
throw RuntimeError(
155+
code: .transportError,
156+
message: "Couldn't create SSL context, check your TLS configuration.",
157+
cause: error
158+
)
159+
}
160+
}
161+
#endif
131162
}
132163

133164
func establishConnection(
@@ -137,7 +168,18 @@ extension HTTP2ClientTransport.Posix {
137168
group: self.eventLoopGroup
138169
).connect(to: address) { channel in
139170
channel.eventLoop.makeCompletedFuture {
140-
try channel.pipeline.syncOperations.configureGRPCClientPipeline(
171+
#if canImport(NIOSSL)
172+
if let nioSSLContext = self.nioSSLContext {
173+
try channel.pipeline.syncOperations.addHandler(
174+
NIOSSLClientHandler(
175+
context: nioSSLContext,
176+
serverHostname: self.serverHostname
177+
)
178+
)
179+
}
180+
#endif
181+
182+
return try channel.pipeline.syncOperations.configureGRPCClientPipeline(
141183
channel: channel,
142184
config: GRPCChannel.Config(posix: self.config)
143185
)
@@ -164,31 +206,48 @@ extension HTTP2ClientTransport.Posix {
164206
/// Compression configuration.
165207
public var compression: HTTP2ClientTransport.Config.Compression
166208

209+
/// The transport's security.
210+
public var transportSecurity: TransportSecurity
211+
167212
/// Creates a new connection configuration.
168213
///
169-
/// See also ``defaults``.
214+
/// - Parameters:
215+
/// - http2: HTTP2 configuration.
216+
/// - backoff: Backoff configuration.
217+
/// - connection: Connection configuration.
218+
/// - compression: Compression configuration.
219+
/// - transportSecurity: The transport's security configuration.
220+
///
221+
/// - SeeAlso: ``defaults(_:)``.
170222
public init(
171223
http2: HTTP2ClientTransport.Config.HTTP2,
172224
backoff: HTTP2ClientTransport.Config.Backoff,
173225
connection: HTTP2ClientTransport.Config.Connection,
174-
compression: HTTP2ClientTransport.Config.Compression
226+
compression: HTTP2ClientTransport.Config.Compression,
227+
transportSecurity: TransportSecurity
175228
) {
176229
self.http2 = http2
177230
self.connection = connection
178231
self.backoff = backoff
179232
self.compression = compression
233+
self.transportSecurity = transportSecurity
180234
}
181235

182236
/// Default values.
183237
///
184238
/// - Parameters:
239+
/// - transportSecurity: The security settings applied to the transport.
185240
/// - configure: A closure which allows you to modify the defaults before returning them.
186-
public static func defaults(_ configure: (_ config: inout Self) -> Void = { _ in }) -> Self {
241+
public static func defaults(
242+
transportSecurity: TransportSecurity,
243+
configure: (_ config: inout Self) -> Void = { _ in }
244+
) -> Self {
187245
var config = Self(
188246
http2: .defaults,
189247
backoff: .defaults,
190248
connection: .defaults,
191-
compression: .defaults
249+
compression: .defaults,
250+
transportSecurity: transportSecurity
192251
)
193252
configure(&config)
194253
return config

0 commit comments

Comments
 (0)