aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2021-09-03 15:27:15 +0200
committerLoïc Hoguin <[email protected]>2021-09-03 15:27:15 +0200
commit9cbc272ddbff2da9c8ac18c2c6b0f29d22500cd5 (patch)
tree50af69aed3cd33292e7ae6c7cc6b37f305a5ddeb
parent8c6e0c21b2707777d9091d45cd514a62fcaededd (diff)
downloadranch-9cbc272ddbff2da9c8ac18c2c6b0f29d22500cd5.tar.gz
ranch-9cbc272ddbff2da9c8ac18c2c6b0f29d22500cd5.tar.bz2
ranch-9cbc272ddbff2da9c8ac18c2c6b0f29d22500cd5.zip
Add function ranch_proxy_header:to_connection_info/1
-rw-r--r--doc/src/manual/ranch_proxy_header.asciidoc1
-rw-r--r--doc/src/manual/ranch_proxy_header.parse.asciidoc1
-rw-r--r--doc/src/manual/ranch_proxy_header.to_connection_info.asciidoc49
-rw-r--r--src/ranch_proxy_header.erl129
4 files changed, 179 insertions, 1 deletions
diff --git a/doc/src/manual/ranch_proxy_header.asciidoc b/doc/src/manual/ranch_proxy_header.asciidoc
index 04f183b..c194d98 100644
--- a/doc/src/manual/ranch_proxy_header.asciidoc
+++ b/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
diff --git a/doc/src/manual/ranch_proxy_header.parse.asciidoc b/doc/src/manual/ranch_proxy_header.parse.asciidoc
index 6c22672..5e03a83 100644
--- a/doc/src/manual/ranch_proxy_header.parse.asciidoc
+++ b/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)]
diff --git a/doc/src/manual/ranch_proxy_header.to_connection_info.asciidoc b/doc/src/manual/ranch_proxy_header.to_connection_info.asciidoc
new file mode 100644
index 0000000..9a53723
--- /dev/null
+++ b/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)]
diff --git a/src/ranch_proxy_header.erl b/src/ranch_proxy_header.erl
index ed0a380..5f141e6 100644
--- a/src/ranch_proxy_header.erl
+++ b/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.