diff --git a/CHANGELOG.md b/CHANGELOG.md index 780e1b4..546e609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Below is a non-exhaustive list of changes between `gen_rpc` versions. - Deprecate support for Erlang < 21.0 - Support monitoring nodes - Support EC SSL certificates +- Support cookie per node configuration +- Support external cookie validation mechanism +- Support keepalive gen_server that actively keeps a client connection alive ## 2.1.0 diff --git a/TODO.md b/TODO.md index 2f45bb5..c5aa49e 100644 --- a/TODO.md +++ b/TODO.md @@ -2,4 +2,4 @@ This is a list of pending features or code technical debt for `gen_rpc`: -N/A +- Alternative Distribution Driver that transparently uses gen_rpc diff --git a/priv/ec_ssl/ca.cert.pem b/priv/ec_ssl/ca.cert.pem index 1f71751..46bbd9f 100644 --- a/priv/ec_ssl/ca.cert.pem +++ b/priv/ec_ssl/ca.cert.pem @@ -1,14 +1,14 @@ -----BEGIN CERTIFICATE----- -MIICOjCCAd+gAwIBAgIDALcKMAoGCCqGSM49BAMCMHQxCzAJBgNVBAYTAlVTMRMw -EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYD -VQQKDAdnZW5fcnBjMSYwJAYDVQQDDB1nZW5fcnBjIENlcnRpZmljYXRlIEF1dGhv -cml0eTAeFw0xODEyMjEyMzA2MjBaFw0zODEyMTYyMzA2MjBaMHQxCzAJBgNVBAYT -AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv -MRAwDgYDVQQKDAdnZW5fcnBjMSYwJAYDVQQDDB1nZW5fcnBjIENlcnRpZmljYXRl -IEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEW4KqioyN4DIk33 -CpzgoQK+mg4zyKnWicN1aLG84ky0wHVWESdnIdK2wi3vo0/LHyiA0zCskShGMsPY -XdDM2qOjYDBeMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQW -BBSTq10vS6KeXFyubf7hcis6BSDtfTAfBgNVHSMEGDAWgBSTq10vS6KeXFyubf7h -cis6BSDtfTAKBggqhkjOPQQDAgNJADBGAiEAl/QdCTV1Bxt7HrYNAcjjCvD2rfca -7FstJoeyKrMQisMCIQDajEboxeZfdFKqp9/RxFS6kZ3uZ5HXyBMDLSX6l1jbzw== +MIICOTCCAd6gAwIBAgICMwwwCgYIKoZIzj0EAwIwdDELMAkGA1UEBhMCVVMxEzAR +BgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEDAOBgNV +BAoMB2dlbl9ycGMxJjAkBgNVBAMMHWdlbl9ycGMgQ2VydGlmaWNhdGUgQXV0aG9y +aXR5MB4XDTE4MTIyMTIzMTUwNVoXDTM4MTIxNjIzMTUwNVowdDELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x +EDAOBgNVBAoMB2dlbl9ycGMxJjAkBgNVBAMMHWdlbl9ycGMgQ2VydGlmaWNhdGUg +QXV0aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpZT2IwQ8QcG0BMeY +9OyzZN6lcTsNFpXv3dhEAsug8zhY5uUz1GVRWDFAtjrlFQ7mOERQHUlFiFXqRoYm +2c/BTKNgMF4wDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYE +FAF6LCa2P8J2MNb33CWg82MCfAgNMB8GA1UdIwQYMBaAFAF6LCa2P8J2MNb33CWg +82MCfAgNMAoGCCqGSM49BAMCA0kAMEYCIQDGq0LtZ2O7ks0m+wc3a7qJN8WCyeAz +vK8tGQOKLzTlSQIhAIMJcudk09EZt7cqtQUt5Fc6U8P9OsEPXe8WGginL/FU -----END CERTIFICATE----- diff --git a/priv/ec_ssl/gen_rpc_master@127.0.0.1.cert.pem b/priv/ec_ssl/gen_rpc_master@127.0.0.1.cert.pem index 975e655..8f3a141 100644 --- a/priv/ec_ssl/gen_rpc_master@127.0.0.1.cert.pem +++ b/priv/ec_ssl/gen_rpc_master@127.0.0.1.cert.pem @@ -1,16 +1,15 @@ -----BEGIN CERTIFICATE----- -MIICczCCAhmgAwIBAgICfVowCgYIKoZIzj0EAwIwdDELMAkGA1UEBhMCVVMxEzAR -BgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEDAOBgNV -BAoMB2dlbl9ycGMxJjAkBgNVBAMMHWdlbl9ycGMgQ2VydGlmaWNhdGUgQXV0aG9y -aXR5MB4XDTE4MTIyMTIzMDY1NloXDTM4MTIxNjIzMDY1NlowbzELMAkGA1UEBhMC -VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x -EDAOBgNVBAoMB2dlbl9ycGMxITAfBgNVBAMMGGdlbl9ycGNfbWFzdGVyQDEyNy4w -LjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGqGMcueq46aofqZXe74Wfll -oUQ3+PrkbTzlZSghR3uO6fGbjrCqy//7LH8kpJMEd7xJiQkm1eoS5fNv0LUeyWCj -gZ8wgZwwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH -AwEGCCsGAQUFBwMCMCMGA1UdEQQcMBqCGGdlbl9ycGNfbWFzdGVyQDEyNy4wLjAu -MTAdBgNVHQ4EFgQUQ62gnNWgC+ZY8dO/852yG+V5YnYwHwYDVR0jBBgwFoAUk6td -L0uinlxcrm3+4XIrOgUg7X0wCgYIKoZIzj0EAwIDSAAwRQIgDk6V1oViPdnPhPPc -bngl0QM01yss89jc7Bhr9BykKtQCIQDbvTZGhh/Z7MzzUAZ99eirsmaX/4bG+WlJ -8xSzgANbbg== +MIICTTCCAfOgAwIBAgIDAI7gMAoGCCqGSM49BAMCMHQxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYD +VQQKDAdnZW5fcnBjMSYwJAYDVQQDDB1nZW5fcnBjIENlcnRpZmljYXRlIEF1dGhv +cml0eTAeFw0xODEyMjEyMzE1MzNaFw0zODEyMTYyMzE1MzNaMG8xCzAJBgNVBAYT +AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv +MRAwDgYDVQQKDAdnZW5fcnBjMSEwHwYDVQQDDBhnZW5fcnBjX21hc3RlckAxMjcu +MC4wLjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXE5wPPspbIS4IHn7xs3JJ +hqqVtS710Ys9q2eNzKSkFJ9Av0mXKwhw8gsYbCyCZtEw89QV1Yzg2f8MARzfU6nG +o3kwdzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcD +AQYIKwYBBQUHAwIwHQYDVR0OBBYEFB6s40nETDtvzmXuxeatcNdjTisVMB8GA1Ud +IwQYMBaAFAF6LCa2P8J2MNb33CWg82MCfAgNMAoGCCqGSM49BAMCA0gAMEUCIHHt +aCD+Ihp/ATDdapFL5fbYJPAI0Ah0bFdul5ip4ekQAiEA11BZwECTDQo1/I/58kQi +E8y9hyoxh8FD/SzimQ7qZHY= -----END CERTIFICATE----- diff --git a/priv/ec_ssl/gen_rpc_master@127.0.0.1.key.pem b/priv/ec_ssl/gen_rpc_master@127.0.0.1.key.pem index f545692..9a8ef1a 100644 --- a/priv/ec_ssl/gen_rpc_master@127.0.0.1.key.pem +++ b/priv/ec_ssl/gen_rpc_master@127.0.0.1.key.pem @@ -2,7 +2,7 @@ BggqhkjOPQMBBw== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- -MHcCAQEEIF4x8cU8IXkvLX2AsqSkAyW1QyvPwc0QajMe/p26tdKVoAoGCCqGSM49 -AwEHoUQDQgAEaoYxy56rjpqh+pld7vhZ+WWhRDf4+uRtPOVlKCFHe47p8ZuOsKrL -//ssfySkkwR3vEmJCSbV6hLl82/QtR7JYA== +MHcCAQEEIKegvLV7VLEr7dOKdsilVH06/edAt9Mcc+k3raAOtIM1oAoGCCqGSM49 +AwEHoUQDQgAElxOcDz7KWyEuCB5+8bNySYaqlbUu9dGLPatnjcykpBSfQL9JlysI +cPILGGwsgmbRMPPUFdWM4Nn/DAEc31Opxg== -----END EC PRIVATE KEY----- diff --git a/priv/ec_ssl/gen_rpc_slave@127.0.0.1.cert.pem b/priv/ec_ssl/gen_rpc_slave@127.0.0.1.cert.pem index f00715d..69f740d 100644 --- a/priv/ec_ssl/gen_rpc_slave@127.0.0.1.cert.pem +++ b/priv/ec_ssl/gen_rpc_slave@127.0.0.1.cert.pem @@ -1,16 +1,15 @@ -----BEGIN CERTIFICATE----- -MIICcTCCAhegAwIBAgICUk4wCgYIKoZIzj0EAwIwdDELMAkGA1UEBhMCVVMxEzAR -BgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEDAOBgNV -BAoMB2dlbl9ycGMxJjAkBgNVBAMMHWdlbl9ycGMgQ2VydGlmaWNhdGUgQXV0aG9y -aXR5MB4XDTE4MTIyMTIzMDcyMVoXDTM4MTIxNjIzMDcyMVowbjELMAkGA1UEBhMC -VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x -EDAOBgNVBAoMB2dlbl9ycGMxIDAeBgNVBAMMF2dlbl9ycGNfc2xhdmVAMTI3LjAu -MC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErlcsM5lJg+SzFRqayZRsiQow -AvowhkniRTWdzhFIwpDF+9bY0peZwRJjmDdiEWjWTheCTlC7vxhd8A5XKxsEtKOB -njCBmzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcD -AQYIKwYBBQUHAwIwIgYDVR0RBBswGYIXZ2VuX3JwY19zbGF2ZUAxMjcuMC4wLjEw -HQYDVR0OBBYEFCXjGbZN73TvhyVBAgHOnnDDGZzVMB8GA1UdIwQYMBaAFJOrXS9L -op5cXK5t/uFyKzoFIO19MAoGCCqGSM49BAMCA0gAMEUCIQD9H7vcUcH9+C2/m7A4 -e8gtjrAZftmZiLvYyhX3bmDNxgIgXHEzICSgV95Wo/I1wuDxEdG8irNqFlE24ehy -/QoPAQQ= +MIICTDCCAfKgAwIBAgIDAMEGMAoGCCqGSM49BAMCMHQxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYD +VQQKDAdnZW5fcnBjMSYwJAYDVQQDDB1nZW5fcnBjIENlcnRpZmljYXRlIEF1dGhv +cml0eTAeFw0xODEyMjEyMzE1MjJaFw0zODEyMTYyMzE1MjJaMG4xCzAJBgNVBAYT +AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv +MRAwDgYDVQQKDAdnZW5fcnBjMSAwHgYDVQQDDBdnZW5fcnBjX3NsYXZlQDEyNy4w +LjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBmLXBysYtqQjvxAVxfihlQ5 +OTDCJNNADxa6uuaW4BElOIc4tWEzouN+yCCjxI4AMs3g/7RitHHwYt6bnAuY8Iyj +eTB3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMB +BggrBgEFBQcDAjAdBgNVHQ4EFgQUCt8m+xW2pR3O6vx1zqBWmTH+Z5AwHwYDVR0j +BBgwFoAUAXosJrY/wnYw1vfcJaDzYwJ8CA0wCgYIKoZIzj0EAwIDSAAwRQIgR1PV +dClqz5h5W1RGHO2yffAqlCinWDzzCg2VO/1eD24CIQC7l0vlTGsoBtjhciGlI+ej +S2Ravs4obm9OgS5YEIFPCw== -----END CERTIFICATE----- diff --git a/priv/ec_ssl/gen_rpc_slave@127.0.0.1.key.pem b/priv/ec_ssl/gen_rpc_slave@127.0.0.1.key.pem index 590dca2..aa2a191 100644 --- a/priv/ec_ssl/gen_rpc_slave@127.0.0.1.key.pem +++ b/priv/ec_ssl/gen_rpc_slave@127.0.0.1.key.pem @@ -2,7 +2,7 @@ BggqhkjOPQMBBw== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- -MHcCAQEEIHYIK9+zg7Q6Gb3LtrzLpDcN1s30TlsmF+uH2c5L1BFFoAoGCCqGSM49 -AwEHoUQDQgAErlcsM5lJg+SzFRqayZRsiQowAvowhkniRTWdzhFIwpDF+9bY0peZ -wRJjmDdiEWjWTheCTlC7vxhd8A5XKxsEtA== +MHcCAQEEIGSRUX8RPDRYPL1RAn3LM2yIW56F2nuPH43PKo/JKKEJoAoGCCqGSM49 +AwEHoUQDQgAEGYtcHKxi2pCO/EBXF+KGVDk5MMIk00APFrq65pbgESU4hzi1YTOi +437IIKPEjgAyzeD/tGK0cfBi3pucC5jwjA== -----END EC PRIVATE KEY----- diff --git a/rebar3 b/rebar3 index b9e599d..8324a5a 100755 Binary files a/rebar3 and b/rebar3 differ diff --git a/src/driver/gen_rpc_driver_ssl.erl b/src/driver/gen_rpc_driver_ssl.erl index 4dbae72..0c0d5e0 100644 --- a/src/driver/gen_rpc_driver_ssl.erl +++ b/src/driver/gen_rpc_driver_ssl.erl @@ -30,7 +30,7 @@ get_peer/1, send/2, activate_socket/1, - authenticate_server/1, + authenticate_to_server/2, authenticate_client/3, copy_sock_opts/2, set_controlling_process/2, @@ -92,11 +92,10 @@ activate_socket(Socket) when is_tuple(Socket) -> ok. %% Authenticate to a server --spec authenticate_server(ssl:sslsocket()) -> ok | {error, {badtcp | badrpc, term()}}. -authenticate_server(Socket) -> - Cookie = erlang:get_cookie(), - NodeStr = erlang:atom_to_list(node()), - Packet = erlang:term_to_binary({gen_rpc_authenticate_connection, NodeStr, Cookie}), +-spec authenticate_to_server(atom(), ssl:sslsocket()) -> ok | {error, {badtcp | badrpc, term()}}. +authenticate_to_server(Node, Socket) -> + Cookie = gen_rpc_helper:get_cookie_per_node(Node), + Packet = erlang:term_to_binary({gen_rpc_authenticate_connection, node(), Cookie}), SendTO = gen_rpc_helper:get_send_timeout(undefined), RecvTO = gen_rpc_helper:get_call_receive_timeout(undefined), ok = set_send_timeout(Socket, SendTO), @@ -135,45 +134,49 @@ authenticate_server(Socket) -> %% Authenticate a connected client -spec authenticate_client(ssl:sslsocket(), tuple(), binary()) -> ok | {error, {badtcp | badrpc, term()}}. authenticate_client(Socket, Peer, Data) -> - Cookie = erlang:get_cookie(), try erlang:binary_to_term(Data) of - {gen_rpc_authenticate_connection, Node, Cookie} -> - PeerCert = extract_peer_certificate(Socket), - {SocketResponse, AuthResult} = case ssl_verify_hostname:verify_cert_hostname(PeerCert, Node) of - {fail, AuthReason} -> - ?log(error, "event=node_certificate_mismatch socket=\"~s\" peer=\"~s\" reason=\"~p\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), AuthReason]), - {{gen_rpc_connection_rejected,node_certificate_mismatch}, {error,{badrpc,node_certificate_mismatch}}}; - {valid, _Hostname} -> - ?log(debug, "event=certificate_validated socket=\"~s\" peer=\"~s\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]), - {gen_rpc_connection_authenticated, ok} - end, - Packet = erlang:term_to_binary(SocketResponse), - case send(Socket, Packet) of - {error, Reason} -> - ?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" reason=\"~p\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Reason]), - {error, {badtcp,Reason}}; - ok -> - ?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]), - ok = activate_socket(Socket), - AuthResult + {gen_rpc_authenticate_connection, Node, Cookie} when is_atom(Node), is_atom(Cookie) -> + ValidCookie = gen_rpc_helper:get_cookie_per_node(Node), + if + ValidCookie == Cookie -> + PeerCert = extract_peer_certificate(Socket), + NodeStr = gen_rpc_helper:to_string(Node), + {SocketResponse, AuthResult} = case ssl_verify_hostname:verify_cert_hostname(PeerCert, NodeStr) of + {fail, AuthReason} -> + ?log(error, "event=node_certificate_mismatch socket=\"~s\" peer=\"~s\" node=\"~s\" reason=\"~p\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node, AuthReason]), + {{gen_rpc_connection_rejected,node_certificate_mismatch}, {error,{badrpc,node_certificate_mismatch}}}; + {valid, _Hostname} -> + ?log(debug, "event=certificate_validated socket=\"~s\" peer=\"~s\" node=\"~s\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]), + {gen_rpc_connection_authenticated, ok} + end, + Packet = erlang:term_to_binary(SocketResponse), + case send(Socket, Packet) of + {error, Reason} -> + ?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" node=\"~s\" reason=\"~p\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node, Reason]), + {error, {badtcp,Reason}}; + ok -> + ?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\" node=\"~s\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]), + ok = activate_socket(Socket), + AuthResult + end; + true -> + ?log(error, "event=invalid_cookie_received socket=\"~s\" peer=\"~s\" node=\"~s\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]), + Packet = erlang:term_to_binary({gen_rpc_connection_rejected, invalid_cookie}), + ok = case send(Socket, Packet) of + {error, Reason} -> + ?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" node=\"~s\" reason=\"~p\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node, Reason]); + ok -> + ?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\" node=\"~s\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]) + end, + {error, {badrpc,invalid_cookie}} end; - {gen_rpc_authenticate_connection, _Node, _IncorrectCookie} -> - ?log(error, "event=invalid_cookie_received socket=\"~s\" peer=\"~s\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]), - Packet = erlang:term_to_binary({gen_rpc_connection_rejected, invalid_cookie}), - ok = case send(Socket, Packet) of - {error, Reason} -> - ?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" reason=\"~p\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Reason]); - ok -> - ?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]) - end, - {error, {badrpc,invalid_cookie}}; OtherData -> ?log(debug, "event=erroneous_data_received socket=\"~s\" peer=\"~s\" data=\"~p\"", [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), OtherData]), @@ -216,7 +219,7 @@ getstat(Socket, OptNames) -> %%% =================================================== merge_ssl_options(client, Node) -> {ok, ExtraOpts} = application:get_env(?APP, ssl_client_options), - NodeStr = atom_to_list(Node), + NodeStr = gen_rpc_helper:to_string(Node), DefaultOpts = lists:append(?SSL_DEFAULT_COMMON_OPTS, ?SSL_DEFAULT_CLIENT_OPTS), VerifyOpts = [{verify_fun, {fun ssl_verify_hostname:verify_fun/3,[{check_hostname,NodeStr}]}}|DefaultOpts], gen_rpc_helper:merge_sockopt_lists(ExtraOpts, VerifyOpts); diff --git a/src/driver/gen_rpc_driver_tcp.erl b/src/driver/gen_rpc_driver_tcp.erl index 0038919..f1b13ac 100644 --- a/src/driver/gen_rpc_driver_tcp.erl +++ b/src/driver/gen_rpc_driver_tcp.erl @@ -28,7 +28,7 @@ get_peer/1, send/2, activate_socket/1, - authenticate_server/1, + authenticate_to_server/2, authenticate_client/3, copy_sock_opts/2, set_controlling_process/2, @@ -82,10 +82,10 @@ send(Socket, Data) when is_port(Socket), is_binary(Data) -> end. %% Authenticate to a server --spec authenticate_server(port()) -> ok | {error, {badtcp | badrpc, term()}}. -authenticate_server(Socket) -> - Cookie = erlang:get_cookie(), - Packet = erlang:term_to_binary({gen_rpc_authenticate_connection, Cookie}), +-spec authenticate_to_server(atom(), port()) -> ok | {error, {badtcp | badrpc, term()}}. +authenticate_to_server(Node, Socket) -> + Cookie = gen_rpc_helper:get_cookie_per_node(Node), + Packet = erlang:term_to_binary({gen_rpc_authenticate_connection, Node, Cookie}), SendTO = gen_rpc_helper:get_send_timeout(undefined), RecvTO = gen_rpc_helper:get_call_receive_timeout(undefined), ok = set_send_timeout(Socket, SendTO), @@ -125,35 +125,38 @@ authenticate_server(Socket) -> %% Authenticate a connected client -spec authenticate_client(port(), tuple(), binary()) -> ok | {error, {badtcp | badrpc, term()}}. authenticate_client(Socket, Peer, Data) -> - Cookie = erlang:get_cookie(), try erlang:binary_to_term(Data) of - {gen_rpc_authenticate_connection, Cookie} -> - Packet = erlang:term_to_binary(gen_rpc_connection_authenticated), - Result = case send(Socket, Packet) of - {error, Reason} -> - ?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" reason=\"~p\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Reason]), - {error, {badtcp,Reason}}; - ok -> - ?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]), - ok = activate_socket(Socket), - ok - end, - Result; - {gen_rpc_authenticate_connection, _IncorrectCookie} -> - ?log(error, "event=invalid_cookie_received socket=\"~s\" peer=\"~s\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]), - Packet = erlang:term_to_binary({gen_rpc_connection_rejected, invalid_cookie}), - ok = case send(Socket, Packet) of - {error, Reason} -> - ?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" reason=\"~p\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Reason]); - ok -> - ?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\"", - [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]) - end, - {error, {badrpc,invalid_cookie}}; + {gen_rpc_authenticate_connection, Node, Cookie} when is_atom(Node), is_atom(Cookie) -> + ValidCookie = gen_rpc_helper:get_cookie_per_node(Node), + if + ValidCookie == Cookie -> + Packet = erlang:term_to_binary(gen_rpc_connection_authenticated), + Result = case send(Socket, Packet) of + {error, Reason} -> + ?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" node=\"~s\" reason=\"~p\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node, Reason]), + {error, {badtcp,Reason}}; + ok -> + ?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\" node=\"~s\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]), + ok = activate_socket(Socket), + ok + end, + Result; + true -> + ?log(error, "event=invalid_cookie_received socket=\"~s\" peer=\"~s\" node=\"~s\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]), + Packet = erlang:term_to_binary({gen_rpc_connection_rejected, invalid_cookie}), + ok = case send(Socket, Packet) of + {error, Reason} -> + ?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" node=\"~s\" reason=\"~p\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node, Reason]); + ok -> + ?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\" node=\"~s\"", + [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]) + end, + {error, {badrpc,invalid_cookie}} + end; OtherData -> ?log(debug, "event=erroneous_data_received socket=\"~s\" peer=\"~s\" data=\"~p\"", [gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), OtherData]), diff --git a/src/gen_rpc.app.src b/src/gen_rpc.app.src index b60f5f5..0e3fa45 100644 --- a/src/gen_rpc.app.src +++ b/src/gen_rpc.app.src @@ -40,7 +40,7 @@ %% Fine-graned driver/port control %% for each outgoing client connection %% The internal implementation expects - %% {internal, Map} whereh Map is a map of + %% {internal, Map} where Map is a map of %% node_name => {driver, port} or node_name => driver %% which uses the default port for the specified client driver %% If you have an external service that allows discovery @@ -79,7 +79,15 @@ %% Seconds between probes {socket_keepalive_interval, 5}, %% Probes lost to close the connection - {socket_keepalive_count, 2} + {socket_keepalive_count, 2}, + %% Cookie per node configuration + %% Set a separate cookie per node by setting + %% {internal, #{ Node::atom() => Cookie()::atom() }} + %% If you have an external service that can validate an incoming + %% client has provided the correct cookie / send the correct cookie per + %% node to the server, you can change this setting to + %% {external, Module} + {cookie_per_node, {internal, #{}}} ]}, {modules, []}] }. diff --git a/src/gen_rpc_client.erl b/src/gen_rpc_client.erl index 66d881a..5f146b4 100644 --- a/src/gen_rpc_client.erl +++ b/src/gen_rpc_client.erl @@ -244,7 +244,7 @@ init({Node}) -> ?log(info, "event=initializing_client driver=~s node=\"~s\" port=~B", [Driver, Node, Port]), case DriverMod:connect(Node, Port) of {ok, Socket} -> - case DriverMod:authenticate_server(Socket) of + case DriverMod:authenticate_to_server(Node, Socket) of ok -> ok = gen_rpc_monitor:register_node(Node, self()), Interval = application:get_env(?APP, keepalive_interval, 60), % 60s diff --git a/src/gen_rpc_driver.erl b/src/gen_rpc_driver.erl index 067a639..aadfd7e 100644 --- a/src/gen_rpc_driver.erl +++ b/src/gen_rpc_driver.erl @@ -14,7 +14,7 @@ -callback activate_socket(term()) -> ok. --callback authenticate_server(term()) -> ok | {error, {badtcp | badrpc, term()}}. +-callback authenticate_to_server(atom(), term()) -> ok | {error, {badtcp | badrpc, term()}}. -callback authenticate_client(term(), tuple(), binary()) -> ok | {error, {badtcp | badrpc, term()}}. diff --git a/src/gen_rpc_helper.erl b/src/gen_rpc_helper.erl index bb0e87d..ad92063 100644 --- a/src/gen_rpc_helper.erl +++ b/src/gen_rpc_helper.erl @@ -17,7 +17,8 @@ -include("types.hrl"). %%% Public API --export([peer_to_string/1, +-export([to_string/1, + peer_to_string/1, socket_to_string/1, host_from_node/1, set_optimal_process_flags/0, @@ -27,6 +28,7 @@ get_server_driver_options/1, get_client_config_per_node/1, get_client_driver_options/1, + get_cookie_per_node/1, get_connect_timeout/0, get_send_timeout/1, get_rpc_module_control/0, @@ -40,6 +42,14 @@ %%% =================================================== %%% Public API %%% =================================================== +%% Convert any type to atom +to_string(Str) when is_list(Str) -> + Str; +to_string(Atom) when is_atom(Atom) -> + erlang:atom_to_list(Atom); +to_string(Bin) when is_binary(Bin) -> + binary:bin_to_list(Bin). + %% Return the connected peer's IP -spec peer_to_string({inet:ip4_address(), inet:port_number()} | inet:ip4_address()) -> string(). peer_to_string({{A,B,C,D}, Port}) when is_integer(A), is_integer(B), is_integer(C), is_integer(D), is_integer(Port) -> @@ -160,6 +170,20 @@ get_client_config_per_node(Node) when is_atom(Node) -> get_client_config_from_map(Node, NodeMap) end. +-spec get_cookie_per_node(atom()) -> atom(). +get_cookie_per_node(Node) when is_atom(Node) -> + {ok, CookieConfig} = application:get_env(?APP, cookie_per_node), + case CookieConfig of + {external, Module} when is_atom(Module) -> + case Module:get_cookie(Node) of + Cookie when is_atom(Cookie) -> Cookie; + {error, Reason} -> {error, Reason} + end; + {internal, NodeMap} -> + maps:get(Node, NodeMap, erlang:get_cookie()) + end. + + -spec get_connect_timeout() -> timeout(). get_connect_timeout() -> {ok, ConnTO} = application:get_env(?APP, connect_timeout), diff --git a/src/gen_rpc_keepalive.erl b/src/gen_rpc_keepalive.erl index c32803a..747af74 100644 --- a/src/gen_rpc_keepalive.erl +++ b/src/gen_rpc_keepalive.erl @@ -25,12 +25,12 @@ -export([start/3, check/1, cancel/1, resume/1]). --record(keepalive, {statfun :: function(), - statval :: integer(), - tsec :: integer(), +-record(keepalive, {statfun :: function() | undefined, + statval :: integer() | undefined, + tsec :: integer() | undefined, tmsg :: term(), - tref :: reference(), - repeat :: integer()}). + tref :: reference() | undefined, + repeat :: integer() | undefined}). -type(keepalive() :: #keepalive{}). diff --git a/test/gen_rpc.master.config b/test/gen_rpc.master.config index 38437b7..a3c08c7 100644 --- a/test/gen_rpc.master.config +++ b/test/gen_rpc.master.config @@ -24,6 +24,7 @@ }}} ]}, {sasl, [ + {sasl_error_logger, false}, {errlog_type, error}, {error_logger_mf_dir, false} ]}, @@ -33,8 +34,10 @@ {crash_log_size, 0}, {colored, true}, {handlers, [ - {lager_console_backend, [debug, - {lager_default_formatter, ["[", date, " ", time, "] severity=", severity, " node=\"", {node, "undefined"}, "\" pid=\"", pid, + {lager_console_backend, [ + {level, debug}, + {formatter, lager_default_formatter}, + {formatter_config, ["[", date, " ", time, "] severity=", severity, " node=\"", {node, "undefined"}, "\" pid=\"", pid, "\" module=", {module, "gen_rpc"}, " function=", {function, "undefined"}, " ", message, "\n"]} ]} ]} diff --git a/test/gen_rpc.slave.config b/test/gen_rpc.slave.config index 24073f4..fa5dd9a 100644 --- a/test/gen_rpc.slave.config +++ b/test/gen_rpc.slave.config @@ -24,6 +24,7 @@ }}} ]}, {sasl, [ + {sasl_error_logger, false}, {errlog_type, error}, {error_logger_mf_dir, false} ]}, @@ -33,8 +34,10 @@ {crash_log_size, 0}, {colored, true}, {handlers, [ - {lager_console_backend, [debug, - {lager_default_formatter, ["[", date, " ", time, "] severity=", severity, " node=\"", {node, "undefined"}, "\" pid=\"", pid, + {lager_console_backend, [ + {level, debug}, + {formatter, lager_default_formatter}, + {formatter_config, ["[", date, " ", time, "] severity=", severity, " node=\"", {node, "undefined"}, "\" pid=\"", pid, "\" module=", {module, "gen_rpc"}, " function=", {function, "undefined"}, " ", message, "\n"]} ]} ]} diff --git a/test/include/ct.hrl b/test/include/ct.hrl index 7e10620..8f81d52 100644 --- a/test/include/ct.hrl +++ b/test/include/ct.hrl @@ -18,7 +18,9 @@ -define(DEFAULT_DRIVER, tcp). --define(TEST_APPLICATION_ENV, [{sasl, errlog_type, error}, +-define(TEST_APPLICATION_ENV, [ + {sasl, sasl_error_logger, false}, + {sasl, errlog_type, error}, {sasl, error_logger_mf_dir, false}, {?APP, tcp_server_port, false}, {?APP, ssl_server_port, false}, @@ -26,6 +28,7 @@ ?MASTER => ?MASTER_PORT, ?SLAVE => ?SLAVE_PORT }}}, + {?APP, cookie_per_node, {internal, #{}}}, {?APP, connect_timeout, 500}, {?APP, send_timeout, 500}, {lager, log_root, "./log"}, @@ -34,11 +37,21 @@ {lager, colored, false}, {lager, handlers, [ %% Commented out to reduce test output polution, uncomment during development - % {lager_common_test_backend, [debug, - % {lager_default_formatter, ["[", date, " ", time, "] severity=", severity, " node=\"", {node, "undefined"}, "\" pid=\"", pid, - % "\" module=", {module, "gen_rpc"}, " function=", {function, "undefined"}, " ", message, "\n"]}]}, - {lager_file_backend, [{file, "messages.log"}, {level, debug}, {formatter, lager_default_formatter}, {size, 0}, {date, "$D0"}, {count, 7}, + % {lager_common_test_backend, [ + % {level, debug}, + % {formatter, lager_default_formatter}, + % {formatter_config, ["[", date, " ", time, "] severity=", severity, " node=\"", {node, "undefined"}, "\" pid=\"", pid, + % "\" module=", {module, "gen_rpc"}, " function=", {function, "undefined"}, " ", message, "\n"]} + % ]}, + {lager_file_backend, [ + {file, "messages.log"}, + {level, debug}, + {formatter, lager_default_formatter}, + {size, 0}, + {date, "$D0"}, + {count, 7}, {formatter_config, ["[", date, " ", time, "] severity=", severity, " node=\"", {node, "undefined"}, "\" pid=\"", pid, - "\" module=", {module, "gen_rpc"}, " function=", {function, "undefined"}, " ", message, "\n"]}]} + "\" module=", {module, "gen_rpc"}, " function=", {function, "undefined"}, " ", message, "\n"]} + ]} ]} ]).