diff --git a/apps/emqx/src/emqx_secret.erl b/apps/emqx/src/emqx_secret.erl index 5340f36ba..72c4f3c08 100644 --- a/apps/emqx/src/emqx_secret.erl +++ b/apps/emqx/src/emqx_secret.erl @@ -21,6 +21,10 @@ %% API: -export([wrap/1, unwrap/1]). +-export_type([t/1]). + +-opaque t(T) :: T | fun(() -> t(T)). + %%================================================================================ %% API funcions %%================================================================================ diff --git a/apps/emqx_s3/src/emqx_s3.erl b/apps/emqx_s3/src/emqx_s3.erl index 0c2592736..cc48cdb93 100644 --- a/apps/emqx_s3/src/emqx_s3.erl +++ b/apps/emqx_s3/src/emqx_s3.erl @@ -44,7 +44,7 @@ -type profile_config() :: #{ bucket := string(), access_key_id => string(), - secret_access_key => string(), + secret_access_key => emqx_secret:t(string()), host := string(), port := pos_integer(), url_expire_time := pos_integer(), diff --git a/apps/emqx_s3/src/emqx_s3_client.erl b/apps/emqx_s3/src/emqx_s3_client.erl index 3bc5861c6..c062cf1ca 100644 --- a/apps/emqx_s3/src/emqx_s3_client.erl +++ b/apps/emqx_s3/src/emqx_s3_client.erl @@ -60,7 +60,7 @@ acl := emqx_s3:acl() | undefined, url_expire_time := pos_integer(), access_key_id := string() | undefined, - secret_access_key := string() | undefined, + secret_access_key := emqx_secret:t(string()) | undefined, http_pool := http_pool(), pool_type := pool_type(), request_timeout := timeout() | undefined, @@ -230,7 +230,7 @@ aws_config(#{ s3_bucket_after_host = true, access_key_id = AccessKeyId, - secret_access_key = SecretAccessKey, + secret_access_key = emqx_secret:unwrap(SecretAccessKey), http_client = request_fun( HttpPool, PoolType, with_default(MaxRetries, ?DEFAULT_MAX_RETRIES) diff --git a/apps/emqx_s3/src/emqx_s3_schema.erl b/apps/emqx_s3/src/emqx_s3_schema.erl index a9b5e97e9..f02364969 100644 --- a/apps/emqx_s3/src/emqx_s3_schema.erl +++ b/apps/emqx_s3/src/emqx_s3_schema.erl @@ -34,11 +34,12 @@ fields(s3) -> )}, {secret_access_key, mk( - string(), + hoconsc:union([string(), function()]), #{ desc => ?DESC("secret_access_key"), required => false, - sensitive => true + sensitive => true, + converter => fun secret/2 } )}, {bucket, @@ -142,6 +143,14 @@ desc(s3) -> desc(transport_options) -> "Options for the HTTP transport layer used by the S3 client". +secret(undefined, #{}) -> + undefined; +secret(Secret, #{make_serializable := true}) -> + unicode:characters_to_binary(emqx_secret:unwrap(Secret)); +secret(Secret, #{}) -> + _ = is_binary(Secret) orelse throw({expected_type, string}), + emqx_secret:wrap(unicode:characters_to_list(Secret)). + translate(Conf) -> translate(Conf, #{}). diff --git a/apps/emqx_s3/test/emqx_s3_schema_SUITE.erl b/apps/emqx_s3/test/emqx_s3_schema_SUITE.erl index 63f659da0..3c7753857 100644 --- a/apps/emqx_s3/test/emqx_s3_schema_SUITE.erl +++ b/apps/emqx_s3/test/emqx_s3_schema_SUITE.erl @@ -49,7 +49,7 @@ t_full_config(_Config) -> host := "s3.us-east-1.endpoint.com", min_part_size := 10485760, port := 443, - secret_access_key := "secret_access_key", + secret_access_key := Secret, transport_options := #{ connect_timeout := 30000, @@ -74,7 +74,7 @@ t_full_config(_Config) -> versions := ['tlsv1.2'] } } - }, + } when is_function(Secret), emqx_s3_schema:translate(#{ <<"access_key_id">> => <<"access_key_id">>, <<"secret_access_key">> => <<"secret_access_key">>, @@ -126,6 +126,26 @@ t_sensitive_config_hidden(_Config) -> ) ). +t_sensitive_config_no_leak(_Config) -> + ?assertThrow( + {emqx_s3_schema, [ + Error = #{ + kind := validation_error, + path := "s3.secret_access_key", + reason := {expected_type, string} + } + ]} when map_size(Error) == 3, + emqx_s3_schema:translate( + #{ + <<"bucket">> => <<"bucket">>, + <<"host">> => <<"s3.us-east-1.endpoint.com">>, + <<"port">> => 443, + <<"access_key_id">> => <<"access_key_id">>, + <<"secret_access_key">> => #{<<"1">> => <<"secret_access_key">>} + } + ) + ). + t_invalid_limits(_Config) -> ?assertException( throw,