From d7467004a53f9dfe5b5c94368ae7888686089ae3 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Thu, 30 May 2024 14:32:38 -0700 Subject: [PATCH] chore: Add support for TLS tests to the contract test service. (#130) --- .github/workflows/ci.yml | 5 +- Makefile | 5 +- test-service/rebar.config | 8 +-- test-service/src/ts_sdk_config_params.erl | 72 ++++++++++++++++++- .../src/ts_service_request_handler.erl | 5 +- 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d99139c..a575e80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,12 +13,13 @@ on: jobs: build-test: strategy: + # We want to know the status of all OTP versions, not just if one of them fails. + fail-fast: false matrix: versions: - {os: 'ubuntu-20.04', otp: '21.x', rebar: '3.15.2'} - - {os: 'ubuntu-22.04', otp: '24.x', rebar: '3.18.0'} - {os: 'ubuntu-22.04', otp: '25.x', rebar: '3.18.0'} - - {os: 'ubuntu-22.04', otp: '26.x', rebar: '3.22.0'} + - {os: 'ubuntu-22.04', otp: '27.x', rebar: '3.23.0'} runs-on: ${{ matrix.versions.os }} name: Build and Test - ${{ matrix.versions.otp }} diff --git a/Makefile b/Makefile index 2ade212..6b4e471 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ REBAR3 = rebar3 +ERL_VERSION = `erl -eval 'io:fwrite("~s~n", [erlang:system_info(otp_release)]), halt().' -noshell` all: @$(REBAR3) do clean, compile, ct, dialyzer @@ -46,8 +47,8 @@ build-contract-tests: @mkdir -p test-service/_checkouts @rm -f $(CURDIR)/test-service/_checkouts/ldclient @ln -sf $(CURDIR)/ $(CURDIR)/test-service/_checkouts/ldclient - @if [ "$(OTP_VER)" = "26.x" ]; then\ - echo Dialyze for OTP 26;\ + @if [ "$(ERL_VERSION)" -ge "26" ]; then\ + echo Dialyze for OTP 26+;\ cd test-service && $(REBAR3) as otp26 dialyzer;\ else\ cd test-service && $(REBAR3) dialyzer;\ diff --git a/test-service/rebar.config b/test-service/rebar.config index 2227c5b..53c5941 100644 --- a/test-service/rebar.config +++ b/test-service/rebar.config @@ -1,14 +1,14 @@ {erl_opts, [debug_info]}. {deps, [ - {shotgun, "0.5.0"}, + {shotgun, "1.0.1"}, {jsx, "3.1.0"}, {verl, "1.0.1"}, {lru, "2.4.0"}, {backoff, "1.1.6"}, {uuid, "2.0.2", {pkg, uuid_erl}}, - {eredis, "1.4.0"}, - {yamerl, "0.8.1"}, - {certifi, "2.8.0"}, + {eredis, "1.7.1"}, + {yamerl, "0.10.0"}, + {certifi, "2.12.0"}, {cowboy, "2.8.0"}, ldclient ]}. diff --git a/test-service/src/ts_sdk_config_params.erl b/test-service/src/ts_sdk_config_params.erl index d5b5094..88e938b 100644 --- a/test-service/src/ts_sdk_config_params.erl +++ b/test-service/src/ts_sdk_config_params.erl @@ -20,7 +20,8 @@ streaming => sdk_config_streaming_params(), polling => sdk_config_polling_params(), events => sdk_config_event_params(), - tags => sdk_config_tags_params() + tags => sdk_config_tags_params(), + tls => sdk_config_tls_params() | undefined }. -type sdk_config_streaming_params() :: #{ @@ -47,10 +48,16 @@ application_version => binary() | undefined }. +-type sdk_config_tls_params() :: #{ + skip_verify_peer => boolean(), + custom_ca_file => binary() | undefined +}. + -export_type([sdk_config_params/0]). -export_type([sdk_config_streaming_params/0]). -export_type([sdk_config_event_params/0]). -export_type([sdk_config_tags_params/0]). +-export_type([sdk_config_tls_params/0]). -spec null_to_undefined(Item :: any()) -> any(). null_to_undefined(null) -> undefined; @@ -68,6 +75,7 @@ parse_config_params(Params) -> Polling = parse_config_polling_params(Params), Events = parse_config_events_params(Params), Tags = parse_config_tag_params(Params), + Tls = parse_config_tls_params(Params), #{ credential => Credential, start_wait_time_ms => StartWaitTimeMS, @@ -75,7 +83,8 @@ parse_config_params(Params) -> streaming => Streaming, events => Events, tags => Tags, - polling => Polling + polling => Polling, + tls => Tls }. -spec parse_config_tag_params(Params :: map()) -> sdk_config_tags_params(). @@ -135,6 +144,16 @@ parse_config_events_params(_Params) -> flush_interval_ms => undefined }. +parse_config_tls_params(#{<<"tls">> := TlsParams} = _Params) when is_map(TlsParams) -> + SkipVerifyPeer = map_get_null_default(<<"skipVerifyPeer">>, TlsParams, false), + CustomCAFile = map_get_null_default(<<"customCAFile">>, TlsParams, undefined), + #{ + skip_verify_peer => SkipVerifyPeer, + custom_ca_file => CustomCAFile + }; +parse_config_tls_params(_Params) -> + undefined. + -spec to_ldclient_options(Configuration :: sdk_config_params()) -> map(). to_ldclient_options(Configuration) -> WithEventsUri = add_events_uri(Configuration, #{}), @@ -146,7 +165,8 @@ to_ldclient_options(Configuration) -> WithTags = add_tags_config(Configuration, WithStreamRetryDelay), WithPollingUri = add_polling_uri(Configuration, WithTags), WithPollingInterval = add_poll_interval(Configuration, WithPollingUri), - WithPollingInterval. + WithTlsConfig = add_tls_config(Configuration, WithPollingInterval), + WithTlsConfig. -spec add_stream_retry_delay(Configuration :: sdk_config_params(), Options:: map()) -> map(). add_stream_retry_delay(#{streaming := #{ @@ -234,3 +254,49 @@ add_tag_application_version(_, Options) -> Options. application_or_empty(#{application := Application} = _Options) -> Application; application_or_empty(_Options) -> #{}. + +basic_tls_options(Options) -> + Options#{ + http_options => #{ + tls_options => ldclient_config:tls_basic_options() + } + }. + +%% When using OTP >= 26 the TLS configuration defaults to using verify_peer. +%% Before OTP 26 we must define our own options to pass the secure defaults. +-if(?OTP_RELEASE >= 26). +-define(BASIC_OPTIONS(X), X). +-else. +-define(BASIC_OPTIONS(X), basic_tls_options(X)). +-endif. + +add_tls_config(#{tls := undefined} = _Configuration, Options) -> + ?BASIC_OPTIONS(Options); +add_tls_config(#{tls := TlsOptions} = _Configuration, Options) -> + WithBasicTls = basic_tls_options(Options), + WithSkipVerifyPeer = add_skip_verify_peer(TlsOptions, WithBasicTls), + add_custom_ca(TlsOptions, WithSkipVerifyPeer). + +skip_verify_peer_to_option(true) -> verify_none; +skip_verify_peer_to_option(false) -> verify_peer. + +add_skip_verify_peer(#{ + skip_verify_peer := SkipVerifyPeer +} = _TlsConfiguration, #{http_options := HttpOptions} = Options) -> + TlsOptions = maps:get(tls_options, HttpOptions, []), + Options#{ + http_options => HttpOptions#{ + tls_options => [{verify, skip_verify_peer_to_option(SkipVerifyPeer)} | proplists:delete(verify, TlsOptions)] + } + }. + +add_custom_ca(#{custom_ca_file := undefined} = _TlsConfiguration, Options) -> + Options; +add_custom_ca(#{custom_ca_file := CaCertFile} = _TlsConfiguration, #{http_options := HttpOptions} = Options) -> + TlsOptions = maps:get(tls_options, HttpOptions, []), + Options#{ + http_options => HttpOptions#{ + %% Remove cacerts and/or cacertfile from the options and then specify the cert file. + tls_options => [{cacertfile, CaCertFile} | proplists:delete(cacerts, proplists:delete(cacertfile, TlsOptions))] + } + }. diff --git a/test-service/src/ts_service_request_handler.erl b/test-service/src/ts_service_request_handler.erl index 67e830d..bd70d52 100644 --- a/test-service/src/ts_service_request_handler.erl +++ b/test-service/src/ts_service_request_handler.erl @@ -75,7 +75,10 @@ get_service_detail(Req, State) -> <<"server-side-polling">>, <<"user-type">>, <<"inline-context">>, - <<"anonymous-redaction">> + <<"anonymous-redaction">>, + <<"tls:custom-ca">>, + <<"tls:skip-verify-peer">>, + <<"tls:verify-peer">> ], <<"clientVersion">> => ldclient_config:get_version() }),