Skip to content

Commit

Permalink
Add test support for H2 (#2070)
Browse files Browse the repository at this point in the history
Description: Allow tests to avoid TLS Cert Verification
- With this PR, only one `UpstreamTlsContext` supports the configurable `trust_chain_verification`:  [`&base_tls_h2_socket`](https://github.com/envoyproxy/envoy-mobile/blob/v0.4.5.02082022/library/common/config/config.cc#L112). The other one, `&base_tls_socket`, does not.

- `&base_tls_h2_socket` has been inlined. Consequently, `&base_tls_h2_socket` does not exist anymore. Unfortunately, when a config variable is assigned in the Java code, it won't get substituted when encountered inside another config variable.

- Swift/objective-c/c++ configuration classes were not updated. Only Java and Kotlin were.

Risk Level: Moderate
Testing: New test and CI
Docs Changes: N/A
Release Notes: N/A
Signed-off-by: Charles Le Borgne <[email protected]>
Signed-off-by: JP Simard <[email protected]>
  • Loading branch information
carloseltuerto authored and jpsim committed Nov 28, 2022
1 parent 70c14cf commit a6771d3
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 46 deletions.
25 changes: 13 additions & 12 deletions mobile/library/common/config/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const std::string config_header = R"(
- &statsd_port 8125
- &stream_idle_timeout 15s
- &per_try_idle_timeout 15s
- &trust_chain_verification VERIFY_TRUST_CHAIN
- &virtual_clusters []
!ignore stats_defs:
Expand Down Expand Up @@ -110,17 +111,6 @@ R"(
validation_context:
trusted_ca:
inline_string: *tls_root_certs
- &base_tls_h2_socket
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
alpn_protocols: [h2]
tls_params:
tls_maximum_protocol_version: TLSv1_3
validation_context:
trusted_ca:
inline_string: *tls_root_certs
)";

const char* config_template = R"(
Expand Down Expand Up @@ -345,7 +335,18 @@ R"(
connect_timeout: *connect_timeout
lb_policy: CLUSTER_PROVIDED
cluster_type: *base_cluster_type
transport_socket: *base_tls_h2_socket
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
alpn_protocols: [h2]
tls_params:
tls_maximum_protocol_version: TLSv1_3
validation_context:
trusted_ca:
inline_string: *tls_root_certs
trust_chain_verification: *trust_chain_verification
upstream_connection_options: *upstream_opts
circuit_breakers: *circuit_breakers_settings
typed_extension_protocol_options: *base_protocol_options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@

/* Typed configuration that may be used for starting Envoy. */
public class EnvoyConfiguration {
// Peer certificate verification mode.
// Must match the CertificateValidationContext.TrustChainVerification proto enum.
public enum TrustChainVerification {
// Perform default certificate verification (e.g., against CA / verification lists)
VERIFY_TRUST_CHAIN,
// Connections where the certificate fails verification will be permitted.
// For HTTP connections, the result of certificate verification can be used in route matching.
// Used for testing.
ACCEPT_UNTRUSTED;
}

public final Boolean adminInterfaceEnabled;
public final String grpcStatsDomain;
public final Integer statsdPort;
Expand All @@ -33,6 +44,7 @@ public class EnvoyConfiguration {
public final Integer perTryIdleTimeoutSeconds;
public final String appVersion;
public final String appId;
public final TrustChainVerification trustChainVerification;
public final String virtualClusters;
public final List<EnvoyNativeFilterConfig> nativeFilterChain;
public final Map<String, EnvoyStringAccessor> stringAccessors;
Expand Down Expand Up @@ -63,25 +75,24 @@ public class EnvoyConfiguration {
* @param perTryIdleTimeoutSeconds per try idle timeout for HTTP streams.
* @param appVersion the App Version of the App using this Envoy Client.
* @param appId the App ID of the App using this Envoy Client.
* @param trustChainVerification whether to mute TLS Cert verification - for tests.
* @param virtualClusters the JSON list of virtual cluster configs.
* @param nativeFilterChain the configuration for native filters.
* @param httpPlatformFilterFactories the configuration for platform filters.
* @param stringAccessors platform string accessors to register.
*/
public EnvoyConfiguration(Boolean adminInterfaceEnabled, String grpcStatsDomain,
@Nullable Integer statsdPort, int connectTimeoutSeconds,
int dnsRefreshSeconds, int dnsFailureRefreshSecondsBase,
int dnsFailureRefreshSecondsMax, int dnsQueryTimeoutSeconds,
String dnsPreresolveHostnames, List<String> dnsFallbackNameservers,
Boolean dnsFilterUnroutableFamilies, boolean enableHappyEyeballs,
boolean enableInterfaceBinding,
int h2ConnectionKeepaliveIdleIntervalMilliseconds,
int h2ConnectionKeepaliveTimeoutSeconds, int statsFlushSeconds,
int streamIdleTimeoutSeconds, int perTryIdleTimeoutSeconds,
String appVersion, String appId, String virtualClusters,
List<EnvoyNativeFilterConfig> nativeFilterChain,
List<EnvoyHTTPFilterFactory> httpPlatformFilterFactories,
Map<String, EnvoyStringAccessor> stringAccessors) {
public EnvoyConfiguration(
Boolean adminInterfaceEnabled, String grpcStatsDomain, @Nullable Integer statsdPort,
int connectTimeoutSeconds, int dnsRefreshSeconds, int dnsFailureRefreshSecondsBase,
int dnsFailureRefreshSecondsMax, int dnsQueryTimeoutSeconds, String dnsPreresolveHostnames,
List<String> dnsFallbackNameservers, Boolean dnsFilterUnroutableFamilies,
boolean enableHappyEyeballs, boolean enableInterfaceBinding,
int h2ConnectionKeepaliveIdleIntervalMilliseconds, int h2ConnectionKeepaliveTimeoutSeconds,
int statsFlushSeconds, int streamIdleTimeoutSeconds, int perTryIdleTimeoutSeconds,
String appVersion, String appId, TrustChainVerification trustChainVerification,
String virtualClusters, List<EnvoyNativeFilterConfig> nativeFilterChain,
List<EnvoyHTTPFilterFactory> httpPlatformFilterFactories,
Map<String, EnvoyStringAccessor> stringAccessors) {
this.adminInterfaceEnabled = adminInterfaceEnabled;
this.grpcStatsDomain = grpcStatsDomain;
this.statsdPort = statsdPort;
Expand All @@ -103,6 +114,7 @@ public EnvoyConfiguration(Boolean adminInterfaceEnabled, String grpcStatsDomain,
this.perTryIdleTimeoutSeconds = perTryIdleTimeoutSeconds;
this.appVersion = appVersion;
this.appId = appId;
this.trustChainVerification = trustChainVerification;
this.virtualClusters = virtualClusters;
this.nativeFilterChain = nativeFilterChain;
this.httpPlatformFilterFactories = httpPlatformFilterFactories;
Expand Down Expand Up @@ -177,6 +189,7 @@ String resolveTemplate(final String templateYAML, final String platformFilterTem
.append(String.format("- &per_try_idle_timeout %ss\n", perTryIdleTimeoutSeconds))
.append(String.format("- &metadata { device_os: %s, app_version: %s, app_id: %s }\n",
"Android", appVersion, appId))
.append(String.format("- &trust_chain_verification %s\n", trustChainVerification.name()))
.append("- &virtual_clusters ")
.append(virtualClusters)
.append("\n");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package org.chromium.net.impl;

import static io.envoyproxy.envoymobile.engine.EnvoyConfiguration.TrustChainVerification.VERIFY_TRUST_CHAIN;

import android.content.Context;
import io.envoyproxy.envoymobile.engine.AndroidEngineImpl;
import io.envoyproxy.envoymobile.engine.AndroidJniLibrary;
import io.envoyproxy.envoymobile.engine.AndroidNetworkMonitor;
import io.envoyproxy.envoymobile.engine.EnvoyConfiguration;
import io.envoyproxy.envoymobile.engine.EnvoyConfiguration.TrustChainVerification;
import io.envoyproxy.envoymobile.engine.EnvoyEngine;
import io.envoyproxy.envoymobile.engine.EnvoyNativeFilterConfig;
import io.envoyproxy.envoymobile.engine.types.EnvoyEventTracker;
Expand Down Expand Up @@ -45,6 +48,7 @@ public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl {
private int mPerTryIdleTimeoutSeconds = 15;
private String mAppVersion = "unspecified";
private String mAppId = "unspecified";
private TrustChainVerification mTrustChainVerification = VERIFY_TRUST_CHAIN;
private String mVirtualClusters = "[]";
private List<EnvoyHTTPFilterFactory> mPlatformFilterChain = Collections.emptyList();
private List<EnvoyNativeFilterConfig> mNativeFilterChain = Collections.emptyList();
Expand Down Expand Up @@ -82,6 +86,7 @@ private EnvoyConfiguration createEnvoyConfiguration() {
mEnableDnsFilterUnroutableFamilies, mEnableHappyEyeballs, mEnableInterfaceBinding,
mH2ConnectionKeepaliveIdleIntervalMilliseconds, mH2ConnectionKeepaliveTimeoutSeconds,
mStatsFlushSeconds, mStreamIdleTimeoutSeconds, mPerTryIdleTimeoutSeconds, mAppVersion,
mAppId, mVirtualClusters, mNativeFilterChain, mPlatformFilterChain, mStringAccessors);
mAppId, mTrustChainVerification, mVirtualClusters, mNativeFilterChain, mPlatformFilterChain,
mStringAccessors);
}
}
16 changes: 16 additions & 0 deletions mobile/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.envoyproxy.envoymobile

import io.envoyproxy.envoymobile.engine.EnvoyConfiguration
import io.envoyproxy.envoymobile.engine.EnvoyConfiguration.TrustChainVerification
import io.envoyproxy.envoymobile.engine.EnvoyEngine
import io.envoyproxy.envoymobile.engine.EnvoyEngineImpl
import io.envoyproxy.envoymobile.engine.EnvoyNativeFilterConfig
Expand Down Expand Up @@ -46,6 +47,7 @@ open class EngineBuilder(
private var perTryIdleTimeoutSeconds = 15
private var appVersion = "unspecified"
private var appId = "unspecified"
private var trustChainVerification = TrustChainVerification.VERIFY_TRUST_CHAIN
private var virtualClusters = "[]"
private var platformFilterChain = mutableListOf<EnvoyHTTPFilterFactory>()
private var nativeFilterChain = mutableListOf<EnvoyNativeFilterConfig>()
Expand Down Expand Up @@ -377,6 +379,18 @@ open class EngineBuilder(
return this
}

/**
* Set how the TrustChainVerification must be handled.
*
* @param trustChainVerification whether to mute TLS Cert verification - intended for testing
*
* @return this builder.
*/
fun setTrustChainVerification(trustChainVerification: TrustChainVerification): EngineBuilder {
this.trustChainVerification = trustChainVerification
return this
}

/**
* Add virtual cluster configuration.
*
Expand Down Expand Up @@ -433,6 +447,7 @@ open class EngineBuilder(
perTryIdleTimeoutSeconds,
appVersion,
appId,
trustChainVerification,
virtualClusters,
nativeFilterChain,
platformFilterChain,
Expand Down Expand Up @@ -466,6 +481,7 @@ open class EngineBuilder(
perTryIdleTimeoutSeconds,
appVersion,
appId,
trustChainVerification,
virtualClusters,
nativeFilterChain,
platformFilterChain,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.envoyproxy.envoymobile.engine

import io.envoyproxy.envoymobile.engine.EnvoyConfiguration.TrustChainVerification
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.fail
import org.junit.Test
Expand Down Expand Up @@ -28,9 +29,10 @@ class EnvoyConfigurationTest {
@Test
fun `resolving with default configuration resolves with values`() {
val envoyConfiguration = EnvoyConfiguration(
false, "stats.foo.com", null, 123, 234, 345, 456, 321, "[hostname]", listOf("8.8.8.8"), true, true, true, 222, 333, 567, 678, 910, "v1.2.3", "com.mydomain.myapp", "[test]",
listOf(EnvoyNativeFilterConfig("filter_name", "test_config")),
emptyList(), emptyMap()
false, "stats.foo.com", null, 123, 234, 345, 456, 321, "[hostname]", listOf("8.8.8.8"), true,
true, true, 222, 333, 567, 678, 910, "v1.2.3", "com.mydomain.myapp",
TrustChainVerification.ACCEPT_UNTRUSTED, "[test]",
listOf(EnvoyNativeFilterConfig("filter_name", "test_config")), emptyList(), emptyMap()
)

val resolvedTemplate = envoyConfiguration.resolveTemplate(
Expand Down Expand Up @@ -73,6 +75,9 @@ class EnvoyConfigurationTest {
assertThat(resolvedTemplate).contains("&stream_idle_timeout 678s")
assertThat(resolvedTemplate).contains("&per_try_idle_timeout 910s")

// TlS Verification
assertThat(resolvedTemplate).contains("&trust_chain_verification ACCEPT_UNTRUSTED")

// Filters
assertThat(resolvedTemplate).contains("filter_name")
assertThat(resolvedTemplate).contains("test_config")
Expand All @@ -81,9 +86,10 @@ class EnvoyConfigurationTest {
@Test
fun `resolving with alternate values also sets appropriate config`() {
val envoyConfiguration = EnvoyConfiguration(
false, "stats.foo.com", null, 123, 234, 345, 456, 321, "[hostname]", emptyList(), false, false, false, 222, 333, 567, 678, 910, "v1.2.3", "com.mydomain.myapp", "[test]",
listOf(EnvoyNativeFilterConfig("filter_name", "test_config")),
emptyList(), emptyMap()
false, "stats.foo.com", null, 123, 234, 345, 456, 321, "[hostname]", emptyList(), false,
false, false, 222, 333, 567, 678, 910, "v1.2.3", "com.mydomain.myapp",
TrustChainVerification.ACCEPT_UNTRUSTED, "[test]",
listOf(EnvoyNativeFilterConfig("filter_name", "test_config")), emptyList(), emptyMap()
)

val resolvedTemplate = envoyConfiguration.resolveTemplate(
Expand All @@ -102,8 +108,9 @@ class EnvoyConfigurationTest {
@Test
fun `resolve templates with invalid templates will throw on build`() {
val envoyConfiguration = EnvoyConfiguration(
false, "stats.foo.com", null, 123, 234, 345, 456, 321, "[hostname]", emptyList(), false, false, false, 123, 123, 567, 678, 910, "v1.2.3", "com.mydomain.myapp", "[test]",
emptyList(), emptyList(), emptyMap()
false, "stats.foo.com", null, 123, 234, 345, 456, 321, "[hostname]", emptyList(), false,
false, false, 123, 123, 567, 678, 910, "v1.2.3", "com.mydomain.myapp",
TrustChainVerification.ACCEPT_UNTRUSTED, "[test]", emptyList(), emptyList(), emptyMap()
)

try {
Expand All @@ -117,8 +124,9 @@ class EnvoyConfigurationTest {
@Test
fun `cannot configure both statsD and gRPC stat sink`() {
val envoyConfiguration = EnvoyConfiguration(
false, "stats.foo.com", 5050, 123, 234, 345, 456, 321, "[hostname]", emptyList(), false, false, false, 123, 123, 567, 678, 910, "v1.2.3", "com.mydomain.myapp", "[test]",
emptyList(), emptyList(), emptyMap()
false, "stats.foo.com", 5050, 123, 234, 345, 456, 321, "[hostname]", emptyList(), false,
false, false, 123, 123, 567, 678, 910, "v1.2.3", "com.mydomain.myapp",
TrustChainVerification.ACCEPT_UNTRUSTED, "[test]", emptyList(), emptyList(), emptyMap()
)

try {
Expand Down
21 changes: 20 additions & 1 deletion mobile/test/java/org/chromium/net/testing/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ android_library(
"TestUrlRequestCallback.java",
"UrlUtils.java",
],
data = glob(["data/*"]),
data = glob(["data/*"]) + ["@envoy//test/config/integration/certs"],
visibility = ["//test:__subpackages__"],
deps = [
"//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib",
Expand Down Expand Up @@ -70,3 +70,22 @@ envoy_mobile_android_test(
"//library/kotlin/io/envoyproxy/envoymobile:envoy_lib",
],
)

envoy_mobile_android_test(
name = "http2_test_server_test",
srcs = [
"Http2TestServerTest.java",
],
exec_properties = {
# TODO(lfpino): Remove this once the sandboxNetwork=off works for ipv4 localhost addresses.
"sandboxNetwork": "standard",
},
native_deps = [
"//library/common/jni:libndk_envoy_jni.so",
"//library/common/jni:libndk_envoy_jni.jnilib",
],
deps = [
":testing",
"//library/kotlin/io/envoyproxy/envoymobile:envoy_lib",
],
)
6 changes: 4 additions & 2 deletions mobile/test/java/org/chromium/net/testing/CronetTestRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ public final class CronetTestRule implements TestRule {
/**
* Name of the file that contains the test server certificate in PEM format.
*/
public static final String SERVER_CERT_PEM = "quic-chain.pem";
public static final String SERVER_CERT_PEM =
"../envoy/test/config/integration/certs/upstreamcert.pem";

/**
* Name of the file that contains the test server private key in PKCS8 PEM format.
*/
public static final String SERVER_KEY_PKCS8_PEM = "quic-leaf-cert.key.pkcs8.pem";
public static final String SERVER_KEY_PKCS8_PEM =
"../envoy/test/config/integration/certs/upstreamkey.pem";

private static final String TAG = CronetTestRule.class.getSimpleName();

Expand Down
10 changes: 4 additions & 6 deletions mobile/test/java/org/chromium/net/testing/Http2TestServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.ApplicationProtocolConfig;
Expand All @@ -25,9 +24,8 @@
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
import io.netty.handler.ssl.OpenSslServerContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;

/**
* Wrapper class to start a HTTP/2 test server.
Expand Down Expand Up @@ -157,9 +155,9 @@ private static class Http2TestServerRunnable implements Runnable {
// exist. Just avoid a KeyManagerFactory as it's unnecessary for our testing.
System.setProperty("io.netty.handler.ssl.openssl.useKeyManagerFactory", "false");

mSslCtx = new OpenSslServerContext(certFile, keyFile, null, null, Http2SecurityUtil.CIPHERS,
SupportedCipherSuiteFilter.INSTANCE,
applicationProtocolConfig, 0, 0);
mSslCtx = SslContextBuilder.forServer(certFile, keyFile)
.applicationProtocolConfig(applicationProtocolConfig)
.build();

mHangingUrlLatch = hangingUrlLatch;
}
Expand Down
Loading

0 comments on commit a6771d3

Please sign in to comment.