Skip to content

Commit

Permalink
Separate out the security config from the rest of the transport config (
Browse files Browse the repository at this point in the history
#37)

Motivation:

Users must explicitly state whether they want TLS or plaintext. This
config for this is currently nested within the rest of the config for
the transport. In many cases this results in users writing
`.defaults(transportSecurity: .tls(.defaults(...)))` where
`.defaults(...)` often includes various TLS config. This is verbose and
a little bit finicky to use.

Modifications:

- Seprate out the transport security from the rest of the config and
have the client and server transport take a config _and_ a transport
security config.
- This allows for APIs which compose better while also allowing for the
transport config to just use a set of defaults.

Result:

Easier to use API.
  • Loading branch information
glbrntt authored Dec 4, 2024
1 parent 4ed582d commit 5152e8f
Show file tree
Hide file tree
Showing 19 changed files with 1,702 additions and 605 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ struct InteroperabilityTestsExecutable: AsyncParsableCommand {
let server = GRPCServer(
transport: .http2NIOPosix(
address: .ipv4(host: self.host, port: self.port),
config: .defaults(transportSecurity: .plaintext) {
transportSecurity: .plaintext,
config: .defaults {
$0.compression.enabledAlgorithms = .all
}
),
Expand Down Expand Up @@ -115,7 +116,8 @@ struct InteroperabilityTestsExecutable: AsyncParsableCommand {
return GRPCClient(
transport: try .http2NIOPosix(
target: .ipv4(host: host, port: port),
config: .defaults(transportSecurity: .plaintext) {
transportSecurity: .plaintext,
config: .defaults {
$0.compression.enabledAlgorithms = .all
},
serviceConfig: serviceConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ final class BenchmarkClient: Sendable {
}

internal func run() async throws {
let benchmarkClient = Grpc_Testing_BenchmarkService_Client(wrapping: self.client)
let benchmarkClient = Grpc_Testing_BenchmarkService.Client(wrapping: self.client)
return try await withThrowingTaskGroup(of: Void.self) { clientGroup in
// Start the client.
clientGroup.addTask {
Expand Down Expand Up @@ -148,7 +148,7 @@ final class BenchmarkClient: Sendable {
return (result, nanoseconds: Double(endTime - startTime))
}

private func unary(benchmark: Grpc_Testing_BenchmarkService_Client) async {
private func unary(benchmark: Grpc_Testing_BenchmarkService.Client) async {
let (errorCode, nanoseconds): (RPCError.Code?, Double) = await self.timeIt {
do {
try await benchmark.unaryCall(request: ClientRequest(message: self.message)) {
Expand All @@ -165,7 +165,7 @@ final class BenchmarkClient: Sendable {
self.record(latencyNanos: nanoseconds, errorCode: errorCode)
}

private func streaming(benchmark: Grpc_Testing_BenchmarkService_Client) async {
private func streaming(benchmark: Grpc_Testing_BenchmarkService.Client) async {
// Streaming RPCs ping-pong messages back and forth. To achieve this the response message
// stream is sent to the request closure, and the request closure indicates the outcome back
// to the response handler to keep the RPC alive for the appropriate amount of time.
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ struct PerformanceWorker: AsyncParsableCommand {
let server = GRPCServer(
transport: .http2NIOPosix(
address: .ipv4(host: "127.0.0.1", port: self.driverPort),
config: .defaults(transportSecurity: .plaintext)
transportSecurity: .plaintext
),
services: [WorkerService()]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,11 +388,12 @@ extension WorkerService {
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: numberOfThreads)

// Don't restrict the max payload size, the client is always trusted.
var config = HTTP2ServerTransport.Posix.Config.defaults(transportSecurity: .plaintext)
var config = HTTP2ServerTransport.Posix.Config.defaults
config.rpc.maxRequestPayloadSize = .max

let transport = HTTP2ServerTransport.Posix(
address: .ipv4(host: "127.0.0.1", port: Int(serverConfig.port)),
transportSecurity: .plaintext,
config: config,
eventLoopGroup: eventLoopGroup
)
Expand Down Expand Up @@ -457,7 +458,7 @@ extension WorkerService {
client: GRPCClient(
transport: try .http2NIOPosix(
target: target,
config: .defaults(transportSecurity: .plaintext)
transportSecurity: .plaintext
)
),
concurrentRPCs: Int(config.outstandingRpcsPerChannel),
Expand Down
84 changes: 80 additions & 4 deletions Sources/GRPCNIOTransportHTTP2Posix/Config+TLS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

extension HTTP2ServerTransport.Posix.Config {
extension HTTP2ServerTransport.Posix {
/// The security configuration for this connection.
public struct TransportSecurity: Sendable {
package enum Wrapped: Sendable {
Expand All @@ -27,12 +27,52 @@ extension HTTP2ServerTransport.Posix.Config {
/// This connection is plaintext: no encryption will take place.
public static let plaintext = Self(wrapped: .plaintext)

/// This connection will use TLS.
/// Secure connections with the given TLS configuration.
public static func tls(_ tls: TLS) -> Self {
Self(wrapped: .tls(tls))
}

/// Secure connections with TLS.
///
/// - Parameters:
/// - certificateChain: The certificates the server will offer during negotiation.
/// - privateKey: The private key associated with the leaf certificate.
/// - configure: A closure which allows you to modify the defaults before returning them.
public static func tls(
certificateChain: [TLSConfig.CertificateSource],
privateKey: TLSConfig.PrivateKeySource,
configure: (_ config: inout TLS) -> Void = { _ in }
) -> Self {
let tlsConfig: TLS = .defaults(
certificateChain: certificateChain,
privateKey: privateKey,
configure: configure
)
return .tls(tlsConfig)
}

/// Secure the connection with mutual TLS.
///
/// - Parameters:
/// - certificateChain: The certificates the client will offer during negotiation.
/// - privateKey: The private key associated with the leaf certificate.
/// - configure: A closure which allows you to modify the defaults before returning them.
public static func mTLS(
certificateChain: [TLSConfig.CertificateSource],
privateKey: TLSConfig.PrivateKeySource,
configure: (_ config: inout TLS) -> Void = { _ in }
) -> Self {
let tlsConfig: TLS = .mTLS(
certificateChain: certificateChain,
privateKey: privateKey,
configure: configure
)
return .tls(tlsConfig)
}
}
}

extension HTTP2ServerTransport.Posix.TransportSecurity {
public struct TLS: Sendable {
/// The certificates the server will offer during negotiation.
public var certificateChain: [TLSConfig.CertificateSource]
Expand Down Expand Up @@ -127,7 +167,7 @@ extension HTTP2ServerTransport.Posix.Config {
}
}

extension HTTP2ClientTransport.Posix.Config {
extension HTTP2ClientTransport.Posix {
/// The security configuration for this connection.
public struct TransportSecurity: Sendable {
package enum Wrapped: Sendable {
Expand All @@ -140,12 +180,48 @@ extension HTTP2ClientTransport.Posix.Config {
/// This connection is plaintext: no encryption will take place.
public static let plaintext = Self(wrapped: .plaintext)

/// This connection will use TLS.
/// Secure the connection with the given TLS configuration.
public static func tls(_ tls: TLS) -> Self {
Self(wrapped: .tls(tls))
}

/// Secure the connection with TLS using the default configuration.
///
/// - Parameters:
/// - configure: A closure which allows you to modify the defaults before returning them.
public static func tls(
configure: (_ config: inout TLS) -> Void = { _ in }
) -> Self {
Self.tls(.defaults(configure: configure))
}

/// Secure the connection with TLS using the default configuration.
public static var tls: Self {
Self.tls(.defaults())
}

/// Secure the connection with mutual TLS.
///
/// - Parameters:
/// - certificateChain: The certificates the client will offer during negotiation.
/// - privateKey: The private key associated with the leaf certificate.
/// - configure: A closure which allows you to modify the defaults before returning them.
public static func mTLS(
certificateChain: [TLSConfig.CertificateSource],
privateKey: TLSConfig.PrivateKeySource,
configure: (_ config: inout TLS) -> Void = { _ in }
) -> Self {
let tlsConfig: TLS = .mTLS(
certificateChain: certificateChain,
privateKey: privateKey,
configure: configure
)
return .tls(tlsConfig)
}
}
}

extension HTTP2ClientTransport.Posix.TransportSecurity {
public struct TLS: Sendable {
/// The certificates the client will offer during negotiation.
public var certificateChain: [TLSConfig.CertificateSource]
Expand Down
45 changes: 27 additions & 18 deletions Sources/GRPCNIOTransportHTTP2Posix/HTTP2ClientTransport+Posix.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ extension HTTP2ClientTransport {
/// try await withThrowingDiscardingTaskGroup { group in
/// let transport = try HTTP2ClientTransport.Posix(
/// target: .ipv4(host: "example.com"),
/// config: .defaults(transportSecurity: .plaintext)
/// transportSecurity: .plaintext
/// )
/// let client = GRPCClient(transport: transport)
/// group.addTask {
Expand All @@ -62,6 +62,7 @@ extension HTTP2ClientTransport {
///
/// - Parameters:
/// - target: A target to resolve.
/// - transportSecurity: The configuration for securing network traffic.
/// - config: Configuration for the transport.
/// - resolverRegistry: A registry of resolver factories.
/// - serviceConfig: Service config controlling how the transport should establish and
Expand All @@ -72,7 +73,8 @@ extension HTTP2ClientTransport {
/// - Throws: When no suitable resolver could be found for the `target`.
public init(
target: any ResolvableTarget,
config: Config,
transportSecurity: TransportSecurity,
config: Config = .defaults,
resolverRegistry: NameResolverRegistry = .defaults,
serviceConfig: ServiceConfig = ServiceConfig(),
eventLoopGroup: any EventLoopGroup = .singletonMultiThreadedEventLoopGroup
Expand All @@ -89,7 +91,11 @@ extension HTTP2ClientTransport {

self.channel = GRPCChannel(
resolver: resolver,
connector: try Connector(eventLoopGroup: eventLoopGroup, config: config),
connector: try Connector(
eventLoopGroup: eventLoopGroup,
config: config,
transportSecurity: transportSecurity
),
config: GRPCChannel.Config(posix: config),
defaultServiceConfig: serviceConfig
)
Expand Down Expand Up @@ -129,11 +135,15 @@ extension HTTP2ClientTransport.Posix {
private let sslContext: NIOSSLContext?
private let isPlainText: Bool

init(eventLoopGroup: any EventLoopGroup, config: HTTP2ClientTransport.Posix.Config) throws {
init(
eventLoopGroup: any EventLoopGroup,
config: HTTP2ClientTransport.Posix.Config,
transportSecurity: HTTP2ClientTransport.Posix.TransportSecurity
) throws {
self.eventLoopGroup = eventLoopGroup
self.config = config

switch self.config.transportSecurity.wrapped {
switch transportSecurity.wrapped {
case .plaintext:
self.sslContext = nil
self.isPlainText = true
Expand Down Expand Up @@ -198,48 +208,44 @@ extension HTTP2ClientTransport.Posix {
/// Compression configuration.
public var compression: HTTP2ClientTransport.Config.Compression

/// The transport's security.
public var transportSecurity: TransportSecurity

/// Creates a new connection configuration.
///
/// - Parameters:
/// - http2: HTTP2 configuration.
/// - backoff: Backoff configuration.
/// - connection: Connection configuration.
/// - compression: Compression configuration.
/// - transportSecurity: The transport's security configuration.
///
/// - SeeAlso: ``defaults(transportSecurity:configure:)``
/// - SeeAlso: ``defaults(configure:)`` and ``defaults``.
public init(
http2: HTTP2ClientTransport.Config.HTTP2,
backoff: HTTP2ClientTransport.Config.Backoff,
connection: HTTP2ClientTransport.Config.Connection,
compression: HTTP2ClientTransport.Config.Compression,
transportSecurity: TransportSecurity
compression: HTTP2ClientTransport.Config.Compression
) {
self.http2 = http2
self.connection = connection
self.backoff = backoff
self.compression = compression
self.transportSecurity = transportSecurity
}

/// Default configuration.
public static var defaults: Self {
Self.defaults()
}

/// Default values.
///
/// - Parameters:
/// - transportSecurity: The security settings applied to the transport.
/// - configure: A closure which allows you to modify the defaults before returning them.
public static func defaults(
transportSecurity: TransportSecurity,
configure: (_ config: inout Self) -> Void = { _ in }
) -> Self {
var config = Self(
http2: .defaults,
backoff: .defaults,
connection: .defaults,
compression: .defaults,
transportSecurity: transportSecurity
compression: .defaults
)
configure(&config)
return config
Expand All @@ -263,6 +269,7 @@ extension ClientTransport where Self == HTTP2ClientTransport.Posix {
///
/// - Parameters:
/// - target: A target to resolve.
/// - transportSecurity: The configuration for securing network traffic.
/// - config: Configuration for the transport.
/// - resolverRegistry: A registry of resolver factories.
/// - serviceConfig: Service config controlling how the transport should establish and
Expand All @@ -273,13 +280,15 @@ extension ClientTransport where Self == HTTP2ClientTransport.Posix {
/// - Throws: When no suitable resolver could be found for the `target`.
public static func http2NIOPosix(
target: any ResolvableTarget,
config: HTTP2ClientTransport.Posix.Config,
transportSecurity: HTTP2ClientTransport.Posix.TransportSecurity,
config: HTTP2ClientTransport.Posix.Config = .defaults,
resolverRegistry: NameResolverRegistry = .defaults,
serviceConfig: ServiceConfig = ServiceConfig(),
eventLoopGroup: any EventLoopGroup = .singletonMultiThreadedEventLoopGroup
) throws -> Self {
return try HTTP2ClientTransport.Posix(
target: target,
transportSecurity: transportSecurity,
config: config,
resolverRegistry: resolverRegistry,
serviceConfig: serviceConfig,
Expand Down
Loading

0 comments on commit 5152e8f

Please sign in to comment.