Skip to content

Commit

Permalink
Add function ranch_proxy_header:to_connection_info/1
Browse files Browse the repository at this point in the history
  • Loading branch information
essen committed Sep 3, 2021
1 parent 8c6e0c2 commit 9cbc272
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 1 deletion.
1 change: 1 addition & 0 deletions doc/src/manual/ranch_proxy_header.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions doc/src/manual/ranch_proxy_header.parse.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 49 additions & 0 deletions doc/src/manual/ranch_proxy_header.to_connection_info.asciidoc
Original file line number Diff line number Diff line change
@@ -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)]
129 changes: 128 additions & 1 deletion src/ranch_proxy_header.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
-export([parse/1]).
-export([header/1]).
-export([header/2]).
-export([to_connection_info/1]).

-type proxy_info() :: #{
%% Mandatory part.
Expand Down Expand Up @@ -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">>,
Expand Down Expand Up @@ -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.

0 comments on commit 9cbc272

Please sign in to comment.