Skip to content

Commit e76bc65

Browse files
authored
Fix TLS E2E test flakiness (#46)
Some of the E2E tests with TLS enabled were flaky on Darwin. This PR: - Slightly modifies the way they are implemented to make them not-flaky - Modifies the options passed to `SecPKCS12Import` to make sure the creation of the required `SecIdentity` happens in memory only and doesn't use the keychain - Tolerates a broken pipe error when the server fails client cert validation during an mTLS handshake, as it's possible for Network.framework to expose this error instead of the actual SSL validation error on the client
1 parent a7b2f50 commit e76bc65

File tree

1 file changed

+81
-68
lines changed

1 file changed

+81
-68
lines changed

Tests/GRPCNIOTransportHTTP2Tests/HTTP2TransportTLSEnabledTests.swift

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -103,52 +103,52 @@ struct HTTP2TransportTLSEnabledTests {
103103
let clientTransportConfig = self.makeDefaultTLSClientConfig(
104104
for: clientTransport,
105105
certificateKeyPairs: certificateKeyPairs,
106-
authority: nil
106+
authority: "wrong-hostname"
107107
)
108108
let serverTransportConfig = self.makeDefaultTLSServerConfig(
109109
for: serverTransport,
110110
certificateKeyPairs: certificateKeyPairs
111111
)
112112

113-
try await self.withClientAndServer(
114-
clientConfig: clientTransportConfig,
115-
serverConfig: serverTransportConfig
116-
) { control in
117-
await #expect {
113+
await #expect {
114+
try await self.withClientAndServer(
115+
clientConfig: clientTransportConfig,
116+
serverConfig: serverTransportConfig
117+
) { control in
118118
try await self.executeUnaryRPC(control: control)
119-
} throws: { error in
120-
let rootError = try #require(error as? RPCError)
121-
#expect(rootError.code == .unavailable)
122-
123-
switch clientTransport {
124-
case .posix:
125-
#expect(
126-
rootError.message
127-
== "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
128-
)
129-
let sslError = try #require(rootError.cause as? NIOSSLExtraError)
130-
guard sslError == .failedToValidateHostname else {
131-
Issue.record(
132-
"Should be a NIOSSLExtraError.failedToValidateHostname error, but was: \(String(describing: rootError.cause))"
133-
)
134-
return false
135-
}
119+
}
120+
} throws: { error in
121+
let rootError = try #require(error as? RPCError)
122+
#expect(rootError.code == .unavailable)
136123

137-
#if canImport(Network)
138-
case .transportServices:
139-
#expect(rootError.message.starts(with: "Could not establish a connection to"))
140-
let nwError = try #require(rootError.cause as? NWError)
141-
guard case .tls(Security.errSSLBadCert) = nwError else {
142-
Issue.record(
143-
"Should be a NWError.tls(-9808/errSSLBadCert) error, but was: \(String(describing: rootError.cause))"
144-
)
145-
return false
146-
}
147-
#endif
124+
switch clientTransport {
125+
case .posix:
126+
#expect(
127+
rootError.message
128+
== "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
129+
)
130+
let sslError = try #require(rootError.cause as? NIOSSLExtraError)
131+
guard sslError == .failedToValidateHostname else {
132+
Issue.record(
133+
"Should be a NIOSSLExtraError.failedToValidateHostname error, but was: \(String(describing: rootError.cause))"
134+
)
135+
return false
148136
}
149137

150-
return true
138+
#if canImport(Network)
139+
case .transportServices:
140+
#expect(rootError.message.starts(with: "Could not establish a connection to"))
141+
let nwError = try #require(rootError.cause as? NWError)
142+
guard case .tls(Security.errSSLBadCert) = nwError else {
143+
Issue.record(
144+
"Should be a NWError.tls(-9808/errSSLBadCert) error, but was: \(String(describing: rootError.cause))"
145+
)
146+
return false
147+
}
148+
#endif
151149
}
150+
151+
return true
152152
}
153153
}
154154

@@ -174,44 +174,53 @@ struct HTTP2TransportTLSEnabledTests {
174174
includeClientCertificateInTrustRoots: true
175175
)
176176

177-
try await self.withClientAndServer(
178-
clientConfig: clientTransportConfig,
179-
serverConfig: serverTransportConfig
180-
) { control in
181-
await #expect {
177+
await #expect {
178+
try await self.withClientAndServer(
179+
clientConfig: clientTransportConfig,
180+
serverConfig: serverTransportConfig
181+
) { control in
182182
try await self.executeUnaryRPC(control: control)
183-
} throws: { error in
184-
let rootError = try #require(error as? RPCError)
185-
#expect(rootError.code == .unavailable)
186-
#expect(
187-
rootError.message
188-
== "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
189-
)
183+
}
184+
} throws: { error in
185+
let rootError = try #require(error as? RPCError)
186+
#expect(rootError.code == .unavailable)
187+
#expect(
188+
rootError.message
189+
== "The server accepted the TCP connection but closed the connection before completing the HTTP/2 connection preface."
190+
)
190191

191-
switch clientTransport {
192-
case .posix:
193-
let sslError = try #require(rootError.cause as? NIOSSL.BoringSSLError)
194-
guard case .sslError = sslError else {
195-
Issue.record(
196-
"Should be a NIOSSL.sslError error, but was: \(String(describing: rootError.cause))"
197-
)
198-
return false
199-
}
192+
switch clientTransport {
193+
case .posix:
194+
let sslError = try #require(rootError.cause as? NIOSSL.BoringSSLError)
195+
guard case .sslError = sslError else {
196+
Issue.record(
197+
"Should be a NIOSSL.sslError error, but was: \(String(describing: rootError.cause))"
198+
)
199+
return false
200+
}
200201

201-
#if canImport(Network)
202-
case .transportServices:
203-
let nwError = try #require(rootError.cause as? NWError)
204-
guard case .tls(Security.errSSLPeerCertUnknown) = nwError else {
205-
Issue.record(
206-
"Should be a NWError.tls(-9829/errSSLPeerCertUnknown) error, but was: \(String(describing: rootError.cause))"
207-
)
208-
return false
202+
#if canImport(Network)
203+
case .transportServices:
204+
let nwError = try #require(rootError.cause as? NWError)
205+
guard case .tls(Security.errSSLPeerCertUnknown) = nwError else {
206+
// When the TLS handshake fails, the connection will be closed from the client.
207+
// Network.framework will generally surface the right SSL error (in this case, an "unknown
208+
// certificate" from the server), but it will sometimes instead return the broken pipe
209+
// error caused by the underlying TLS handshake handler closing the connection:
210+
// we should tolerate this.
211+
if case .posix(POSIXErrorCode.EPIPE) = nwError {
212+
return true
209213
}
210-
#endif
211-
}
212214

213-
return true
215+
Issue.record(
216+
"Should be a NWError.tls(-9829/errSSLPeerCertUnknown) error, but was: \(String(describing: rootError.cause))"
217+
)
218+
return false
219+
}
220+
#endif
214221
}
222+
223+
return true
215224
}
216225
}
217226

@@ -341,7 +350,11 @@ struct HTTP2TransportTLSEnabledTests {
341350
privateKey: try NIOSSLPrivateKey(bytes: privateKeyBytes, format: .der)
342351
)
343352
let pkcs12Bytes = try bundle.serialize(passphrase: password.utf8)
344-
let options = [kSecImportExportPassphrase as String: password]
353+
let options =
354+
[
355+
kSecImportExportPassphrase as String: password,
356+
kSecImportToMemoryOnly: kCFBooleanTrue!,
357+
] as [AnyHashable: Any]
345358
var rawItems: CFArray?
346359
let status = SecPKCS12Import(
347360
Data(pkcs12Bytes) as CFData,

0 commit comments

Comments
 (0)