Просмотр исходного кода

fix(s3): wrap S3 secrets during config load

Andrew Mayorov 2 лет назад
Родитель
Сommit
48858dee33

+ 4 - 0
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
 %%================================================================================

+ 1 - 1
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(),

+ 2 - 2
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)

+ 11 - 2
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, #{}).
 

+ 22 - 2
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,