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

Add function ranch_proxy_header:to_connection_info/1

Loïc Hoguin 3 лет назад
Родитель
Сommit
9cbc272ddb

+ 1 - 0
doc/src/manual/ranch_proxy_header.asciidoc

@@ -13,6 +13,7 @@ for parsing and building the PROXY protocol header.
 
 * link:man:ranch_proxy_header:parse(3)[ranch_proxy_header:parse(3)] - Parse a PROXY protocol header
 * link:man:ranch_proxy_header:header(3)[ranch_proxy_header:header(3)] - Build a PROXY protocol header
+* link:man:ranch_proxy_header:to_connection_info(3)[ranch_proxy_header:to_connection_info(3)] - Convert proxy_info() to ssl:connection_info()
 
 == Types
 

+ 1 - 0
doc/src/manual/ranch_proxy_header.parse.asciidoc

@@ -46,4 +46,5 @@ error.
 == See also
 
 link:man:ranch_proxy_header:header(3)[ranch_proxy_header:header(3)],
+link:man:ranch_proxy_header:to_connection_info(3)[ranch_proxy_header:to_connection_info(3)],
 link:man:ranch_proxy_header(3)[ranch_proxy_header(3)]

+ 49 - 0
doc/src/manual/ranch_proxy_header.to_connection_info.asciidoc

@@ -0,0 +1,49 @@
+= ranch_proxy_header:to_connection_info(3)
+
+== Name
+
+ranch_proxy_header:to_connection_info - Convert proxy_info() to ssl:connection_info()
+
+== Description
+
+[source,erlang]
+----
+to_connection_info(ProxyInfo :: proxy_info())
+    -> ssl:connection_info()
+----
+
+Convert `ranch_proxy_header:proxy_info()` information
+to the `ssl:connection_info()` format returned by
+`ssl:connection_information/1,2`.
+
+== Arguments
+
+ProxyInfo::
+
+The PROXY protocol information.
+
+== Return value
+
+Connection information is returned as a proplist.
+
+Because the PROXY protocol header includes limited
+information, only the keys `protocol`, `selected_cipher_suite`
+and `sni_hostname` will be returned, at most. All keys
+are optional.
+
+== Changelog
+
+* *2.1*: Function introduced.
+
+== Examples
+
+.Convert the PROXY protocol information
+[source,erlang]
+----
+ConnInfo = ranch_proxy_header:to_connection_info(ProxyInfo).
+----
+
+== See also
+
+link:man:ranch_proxy_header:parse(3)[ranch_proxy_header:parse(3)],
+link:man:ranch_proxy_header(3)[ranch_proxy_header(3)]

+ 128 - 1
src/ranch_proxy_header.erl

@@ -17,6 +17,7 @@
 -export([parse/1]).
 -export([header/1]).
 -export([header/2]).
+-export([to_connection_info/1]).
 
 -type proxy_info() :: #{
 	%% Mandatory part.
@@ -830,7 +831,7 @@ v2_tlvs_test() ->
 	Test4 = Common#{ssl => #{
 		client => [ssl, cert_conn, cert_sess],
 		verified => true,
-		version => <<"TLSv1.3">>, %% Note that I'm not sure this example value is correct.
+		version => <<"TLSv1.3">>,
 		cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
 		sig_alg => <<"SHA256">>,
 		key_alg => <<"RSA2048">>,
@@ -878,3 +879,129 @@ v2_padding_test() ->
 	{ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{padding => 123}))),
 	ok.
 -endif.
+
+%% Helper to convert proxy_info() to ssl:connection_info().
+%%
+%% Because there isn't a lot of fields common to both types
+%% this only ends up returning the keys protocol, selected_cipher_suite
+%% and sni_hostname *at most*.
+
+-spec to_connection_info(proxy_info()) -> ssl:connection_info().
+to_connection_info(ProxyInfo=#{ssl := SSL}) ->
+	ConnInfo0 = case ProxyInfo of
+		#{authority := Authority} ->
+			[{sni_hostname, Authority}];
+		_ ->
+			[]
+	end,
+	ConnInfo = case SSL of
+		#{cipher := Cipher} ->
+			case ssl:str_to_suite(binary_to_list(Cipher)) of
+				{error, {not_recognized, _}} ->
+					ConnInfo0;
+				CipherInfo ->
+					[{selected_cipher_suite, CipherInfo}|ConnInfo0]
+			end;
+		_ ->
+			ConnInfo0
+	end,
+	%% https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html
+	case SSL of
+		#{version := <<"TLSv1.3">>} -> [{protocol, 'tlsv1.3'}|ConnInfo];
+		#{version := <<"TLSv1.2">>} -> [{protocol, 'tlsv1.2'}|ConnInfo];
+		#{version := <<"TLSv1.1">>} -> [{protocol, 'tlsv1.1'}|ConnInfo];
+		#{version := <<"TLSv1">>} -> [{protocol, tlsv1}|ConnInfo];
+		#{version := <<"SSLv3">>} -> [{protocol, sslv3}|ConnInfo];
+		#{version := <<"SSLv2">>} -> [{protocol, sslv2}|ConnInfo];
+		%% <<"unknown">>, unsupported or missing version.
+		_ -> ConnInfo
+	end;
+%% No SSL/TLS information available.
+to_connection_info(_) ->
+	[].
+
+-ifdef(TEST).
+to_connection_info_test() ->
+	Common = #{
+		version => 2,
+		command => proxy,
+		transport_family => ipv4,
+		transport_protocol => stream,
+		src_address => {127, 0, 0, 1},
+		src_port => 1234,
+		dest_address => {10, 11, 12, 13},
+		dest_port => 23456
+	},
+	%% Version 1.
+	[] = to_connection_info(#{
+		version => 1,
+		command => proxy,
+		transport_family => undefined,
+		transport_protocol => undefined
+	}),
+	[] = to_connection_info(Common#{version => 1}),
+	%% Version 2, no ssl data.
+	[] = to_connection_info(#{
+		version => 2,
+		command => local
+	}),
+	[] = to_connection_info(#{
+		version => 2,
+		command => proxy,
+		transport_family => undefined,
+		transport_protocol => undefined
+	}),
+	[] = to_connection_info(Common),
+	[] = to_connection_info(#{
+		version => 2,
+		command => proxy,
+		transport_family => unix,
+		transport_protocol => dgram,
+		src_address => <<"/run/source.sock">>,
+		dest_address => <<"/run/destination.sock">>
+	}),
+	[] = to_connection_info(Common#{netns => <<"/var/run/netns/example">>}),
+	[] = to_connection_info(Common#{raw_tlvs => [
+		{16#ff, <<1, 2, 3, 4, 5, 6, 7, 8, 9, 0>>}
+	]}),
+	%% Version 2, with ssl-related data.
+	[] = to_connection_info(Common#{alpn => <<"h2">>}),
+	%% The authority alone is not enough to deduce that this is SNI.
+	[] = to_connection_info(Common#{authority => <<"internal.example.org">>}),
+	[
+		{protocol, 'tlsv1.3'},
+		{selected_cipher_suite, #{
+			cipher := aes_128_gcm,
+			key_exchange := ecdhe_rsa,
+			mac := aead,
+			prf := sha256
+		}}
+	] = to_connection_info(Common#{ssl => #{
+		client => [ssl, cert_conn, cert_sess],
+		verified => true,
+		version => <<"TLSv1.3">>,
+		cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
+		sig_alg => <<"SHA256">>,
+		key_alg => <<"RSA2048">>,
+		cn => <<"example.com">>
+	}}),
+	[
+		{protocol, 'tlsv1.3'},
+		{selected_cipher_suite, #{
+			cipher := aes_128_gcm,
+			key_exchange := ecdhe_rsa,
+			mac := aead,
+			prf := sha256
+		}},
+		{sni_hostname, <<"internal.example.org">>}
+	] = to_connection_info(Common#{authority => <<"internal.example.org">>, ssl => #{
+		client => [ssl, cert_conn, cert_sess],
+		verified => true,
+		version => <<"TLSv1.3">>,
+		cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
+		sig_alg => <<"SHA256">>,
+		key_alg => <<"RSA2048">>,
+		cn => <<"example.com">>
+	}}),
+	ok.
+-endif.