From 9cbc272ddbff2da9c8ac18c2c6b0f29d22500cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Fri, 3 Sep 2021 15:27:15 +0200 Subject: Add function ranch_proxy_header:to_connection_info/1 --- doc/src/manual/ranch_proxy_header.asciidoc | 1 + doc/src/manual/ranch_proxy_header.parse.asciidoc | 1 + .../ranch_proxy_header.to_connection_info.asciidoc | 49 ++++++++ src/ranch_proxy_header.erl | 129 ++++++++++++++++++++- 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 doc/src/manual/ranch_proxy_header.to_connection_info.asciidoc 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. -- cgit v1.2.3