From ab75a72b43fb2fc5a44600992ff26f4b5aecb25f Mon Sep 17 00:00:00 2001 From: Ted Burghart Date: Thu, 12 Mar 2015 15:07:36 -0400 Subject: [PATCH 1/2] Restructured how connectivity tests are configured and run [RIAK-1608]. Due to leftover configuration attributes from prior tests within this module (see RIAK-1607), the order of tests has to be managed. In order to accomplish this and be able to see exactly what configurations are used for each test, configuration blocks have been more directly associated with each connectivity test. --- tests/replication2_ssl.erl | 489 ++++++++++++++++--------------------- 1 file changed, 207 insertions(+), 282 deletions(-) diff --git a/tests/replication2_ssl.erl b/tests/replication2_ssl.erl index e3d4608b2..03e5f0f87 100644 --- a/tests/replication2_ssl.erl +++ b/tests/replication2_ssl.erl @@ -1,9 +1,44 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2012-2015 Basho Technologies, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- -module(replication2_ssl). -behavior(riak_test). + -export([confirm/0]). --compile(export_all). + -include_lib("eunit/include/eunit.hrl"). +%% Certificate Names +-define(DEF_DOM, ".basho.com"). +-define(DOM_WC, "*" ++ ?DEF_DOM). +-define(CERTN(S), S ++ ?DEF_DOM). +-define(SITEN(N), ?CERTN("site" ++ ??N)). +-define(CERTP(S), filename:join(CertDir, S)). + +%% Certificate Information +-record(ci, { + cn, %% common name of the certificate + rd = 0, %% required ssl_depth + wc = ?DOM_WC, %% acceptable *.domain wildcard + ssl %% options returned from ssl_paths +}). + confirm() -> %% test requires allow_mult=false @@ -14,221 +49,153 @@ confirm() -> CertDir = rt_config:get(rt_scratch_dir) ++ "/certs", - %% make a bunch of crypto keys + %% make a bunch of certificates and matching ci records make_certs:rootCA(CertDir, "rootCA"), make_certs:intermediateCA(CertDir, "intCA", "rootCA"), - make_certs:endusers(CertDir, "rootCA", ["site3.basho.com", "site4.basho.com"]), - make_certs:endusers(CertDir, "intCA", ["site1.basho.com", "site2.basho.com"]), - make_certs:enduser(CertDir, "intCA", "*.basho.com", "wildcard.basho.com"), - - lager:info("Deploy ~p nodes", [NumNodes]), - BaseConf = [ - {riak_core, - [ - {ssl_enabled, false} - ]}, - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]} - ], - PrivDir = rt:priv_dir(), - - SSLConfig1 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {certfile, filename:join([CertDir, - "site1.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "site1.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "site1.basho.com/cacerts.pem"])} - ]} - ], - - SSLConfig2 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {certfile, filename:join([CertDir, - "site2.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "site2.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "site2.basho.com/cacerts.pem"])} - ]} - ], + S1Name = ?SITEN(1), + S2Name = ?SITEN(2), + make_certs:endusers(CertDir, "intCA", [S1Name, S2Name]), + CIsite1 = #ci{cn = S1Name, rd = 1, ssl = ssl_paths(?CERTP(S1Name))}, + CIsite2 = #ci{cn = S2Name, rd = 1, ssl = ssl_paths(?CERTP(S2Name))}, - SSLConfig3 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {certfile, filename:join([CertDir, - "site3.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "site3.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "site3.basho.com/cacerts.pem"])} - ]} - ], + S3Name = ?SITEN(3), + S4Name = ?SITEN(4), + make_certs:endusers(CertDir, "rootCA", [S3Name, S4Name]), + CIsite3 = #ci{cn = S3Name, rd = 0, ssl = ssl_paths(?CERTP(S3Name))}, + CIsite4 = #ci{cn = S4Name, rd = 0, ssl = ssl_paths(?CERTP(S4Name))}, - %% same as above,with a depth of 0 - SSLConfig3A = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - - {ssl_enabled, true}, - {ssl_depth, 0}, - {certfile, filename:join([CertDir, - "site3.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "site3.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "site3.basho.com/cacerts.pem"])} - ]} - ], + WCName = ?CERTN("wildcard"), + make_certs:enduser(CertDir, "intCA", ?DOM_WC, WCName), + CIsiteWC = #ci{cn = WCName, rd = 1, ssl = ssl_paths(?CERTP(WCName))}, - SSLConfig4 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {ssl_depth, 0}, - {certfile, filename:join([CertDir, - "site4.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "site4.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "site4.basho.com/cacerts.pem"])} - ]} - ], + % crufty old certs really need to be replaced + CIexpired = #ci{cn = "ny.cataclysm-software.net", rd = 0, + wc = "*.cataclysm-software.net", ssl = ssl_paths( + filename:join([rt:priv_dir(), "certs", "cacert.org"]), + "ny-cert-old.pem", "ny-key.pem", "ca")}, - SSLConfig5 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {ssl_depth, 1}, - {peer_common_name_acl, ["*.basho.com"]}, - {certfile, filename:join([CertDir, - "site1.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "site1.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "site1.basho.com/cacerts.pem"])} - ]} - ], - - SSLConfig6 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {ssl_depth, 1}, - {peer_common_name_acl, ["site1.basho.com"]}, - {certfile, filename:join([CertDir, - "site2.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "site2.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "site2.basho.com/cacerts.pem"])} - ]} - ], - - SSLConfig7 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {peer_common_name_acl, ["ca.cataclysm-software.net"]}, - {certfile, filename:join([PrivDir, - "certs/cacert.org/ny-cert-old.pem"])}, - {keyfile, filename:join([PrivDir, - "certs/cacert.org/ny-key.pem"])}, - {cacertdir, filename:join([PrivDir, - "certs/cacert.org/ca"])} - ]} - ], - - SSLConfig8 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {certfile, filename:join([CertDir, - "wildcard.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "wildcard.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "wildcard.basho.com/cacerts.pem"])} - ]} - ], + lager:info("Deploy ~p nodes", [NumNodes]), - SSLConfig9 = [ - {riak_repl, - [ - {fullsync_on_connect, false}, - {fullsync_interval, disabled} - ]}, - {riak_core, - [ - {ssl_enabled, true}, - {peer_common_name_acl, ["*.basho.com"]}, - {certfile, filename:join([CertDir, - "wildcard.basho.com/cert.pem"])}, - {keyfile, filename:join([CertDir, - "wildcard.basho.com/key.pem"])}, - {cacertdir, filename:join([CertDir, - "wildcard.basho.com/cacerts.pem"])} - ]} + ConfRepl = {riak_repl, + [{fullsync_on_connect, false}, {fullsync_interval, disabled}]}, + + ConfTcpBasic = [ConfRepl, {riak_core, [{ssl_enabled, false}]}], + + %% + %% !!! IMPORTANT !!! + %% Properties added to node configurations CANNOT currently be removed, + %% only overwritten. As such, configurations that include ACLs MUST come + %% after ALL non-ACL configurations! This has been learned the hard way :( + %% + + %% + %% Connection Test descriptors + %% Each is a tuple: {Description, Node1Config, Node2Config, Should Pass} + %% + SslConnTests = [ + {"identical certificate CN is disallowed", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite1#ci.ssl }], + false}, + {"non-SSL peer fails", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite1#ci.ssl }], + ConfTcpBasic, + false}, + {"non-SSL local fails", + ConfTcpBasic, + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite2#ci.ssl }], + false}, + {"basic SSL connectivity", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite2#ci.ssl }], + true}, + {"SSL connectivity with intermediate CA", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite3#ci.ssl }], + true}, + {"wildcard certifictes on both ends", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsiteWC#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsiteWC#ci.ssl }], + true}, + {"wildcard certifictes on one end", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsiteWC#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIsite3#ci.ssl }], + true}, + %% first use of ssl_depth, all subsequent tests must specify it + {"disallowing intermediate CA setting", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, 0} + ] ++ CIsite3#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, 0} + ] ++ CIsite4#ci.ssl }], + true}, + {"disallowing intermediate CA connection", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, 0} + ] ++ CIsite3#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsite3#ci.rd} + ] ++ CIsite1#ci.ssl }], + false}, + %% first use of peer_common_name_acl, all subsequent tests must specify it + {"wildcard certifictes on one end with ACL", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsite1#ci.rd} + ] ++ CIsiteWC#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsiteWC#ci.rd} + , {peer_common_name_acl, [?DOM_WC]} + ] ++ CIsite1#ci.ssl }], + true}, + {"wildcard and strict ACL", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsite2#ci.rd} + , {peer_common_name_acl, [?DOM_WC]} + ] ++ CIsite1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsite1#ci.rd} + , {peer_common_name_acl, [CIsite1#ci.cn]} + ] ++ CIsite2#ci.ssl }], + true}, + {"wildcard certifictes on both ends with ACLs", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsiteWC#ci.rd} + , {peer_common_name_acl, [CIsiteWC#ci.wc]} + ] ++ CIsiteWC#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsiteWC#ci.rd} + , {peer_common_name_acl, [CIsiteWC#ci.wc]} + ] ++ CIsiteWC#ci.ssl }], + true}, + {"expired certificates fail", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIexpired#ci.rd} + , {peer_common_name_acl, [CIexpired#ci.wc]} + ] ++ CIsite1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsite1#ci.rd} + , {peer_common_name_acl, [CIsite1#ci.wc]} + ] ++ CIexpired#ci.ssl }], + false} ], - lager:info("===testing basic connectivity"), + lager:info("Deploying 2 nodes for connectivity tests"), - [Node1, Node2] = rt:deploy_nodes(2, BaseConf, [riak_kv, riak_repl]), + [Node1, Node2] = rt:deploy_nodes(2, ConfTcpBasic, [riak_kv, riak_repl]), repl_util:name_cluster(Node1, "A"), repl_util:name_cluster(Node2, "B"), @@ -240,7 +207,8 @@ confirm() -> rt:wait_for_service(Node1, [riak_kv, riak_repl]), rt:wait_for_service(Node2, [riak_kv, riak_repl]), - rt:log_to_nodes([Node1, Node2], "Basic connectivity test"), + lager:info("=== Testing basic connectivity"), + rt:log_to_nodes([Node1, Node2], "Testing basic connectivity"), {ok, {_IP, Port}} = rpc:call(Node2, application, get_env, [riak_core, cluster_mgr]), @@ -251,74 +219,10 @@ confirm() -> ?assertEqual(ok, repl_util:wait_for_connection(Node1, "B")), - lager:info("===testing you can't connect to a server with a cert with the same common name"), - rt:log_to_nodes([Node1, Node2], "Testing identical cert is disallowed"), - ?assertMatch({fail, _}, test_connection({Node1, merge_config(SSLConfig1, BaseConf)}, - {Node2, merge_config(SSLConfig1, BaseConf)})), - - lager:info("===testing you can't connect when peer doesn't support SSL"), - rt:log_to_nodes([Node1, Node2], "Testing missing ssl on peer fails"), - ?assertMatch({fail, _}, test_connection({Node1, merge_config(SSLConfig1, BaseConf)}, - {Node2, BaseConf})), - - lager:info("===testing you can't connect when local doesn't support SSL"), - rt:log_to_nodes([Node1, Node2], "Testing missing ssl locally fails"), - ?assertMatch({fail, _}, test_connection({Node1, BaseConf}, - {Node2, merge_config(SSLConfig2, BaseConf)})), - - lager:info("===testing simple SSL connectivity"), - rt:log_to_nodes([Node1, Node2], "Basic SSL test"), - ?assertEqual(ok, test_connection({Node1, merge_config(SSLConfig1, BaseConf)}, - {Node2, merge_config(SSLConfig2, BaseConf)})), - - lager:info("testing SSL connectivity with an intermediate CA"), - rt:log_to_nodes([Node1, Node2], "Intermediate CA test"), - ?assertEqual(ok, test_connection({Node1, merge_config(SSLConfig1, BaseConf)}, - {Node2, merge_config(SSLConfig3, BaseConf)})), - - lager:info("===testing disallowing intermediate CAs works"), - rt:log_to_nodes([Node1, Node2], "Disallowing intermediate CA test"), - ?assertEqual(ok, test_connection({Node1, merge_config(SSLConfig3A, BaseConf)}, - {Node2, merge_config(SSLConfig4, BaseConf)})), - - lager:info("===testing disallowing intermediate CAs disallows connections"), - rt:log_to_nodes([Node1, Node2], "Disallowing intermediate CA test 2"), - ?assertMatch({fail, _}, test_connection({Node1, merge_config(SSLConfig3A, BaseConf)}, - {Node2, merge_config(SSLConfig1, BaseConf)})), - - lager:info("===testing wildcard and strict ACLs"), - rt:log_to_nodes([Node1, Node2], "wildcard and strict ACL test"), - ?assertEqual(ok, test_connection({Node1, merge_config(SSLConfig5, BaseConf)}, - {Node2, merge_config(SSLConfig6, BaseConf)})), - - %% WILDCARD CERT TESTS - - lager:info("===testing wildcard certifictes on both ends"), - rt:log_to_nodes([Node1, Node2], "wildcard certificates test - both end"), - ?assertEqual(ok, test_connection({Node1, merge_config(SSLConfig8, BaseConf)}, - {Node2, merge_config(SSLConfig8, BaseConf)})), - - lager:info("===testing wildcard certifictes on one end"), - rt:log_to_nodes([Node1, Node2], "wildcard certificates test - one end"), - ?assertEqual(ok, test_connection({Node1, merge_config(SSLConfig8, BaseConf)}, - {Node2, merge_config(SSLConfig3, BaseConf)})), - - lager:info("===testing wildcard certifictes on one end with ACL"), - rt:log_to_nodes([Node1, Node2], "wildcard certificates test - one end with ACLs"), - ?assertEqual(ok, test_connection({Node1, merge_config(SSLConfig8, BaseConf)}, - {Node2, merge_config(SSLConfig5, BaseConf)})), - - lager:info("===testing wildcard certifictes on both ends with ACLs"), - rt:log_to_nodes([Node1, Node2], "wildcard certificates test - both end with ACLs"), - ?assertEqual(ok, test_connection({Node1, merge_config(SSLConfig9, BaseConf)}, - {Node2, merge_config(SSLConfig9, BaseConf)})), - - %% END WILDCARD CERT TESTS - - lager:info("===testing expired certificates fail"), - rt:log_to_nodes([Node1, Node2], "expired certificates test"), - ?assertMatch({fail, _}, test_connection({Node1, merge_config(SSLConfig5, BaseConf)}, - {Node2, merge_config(SSLConfig7, BaseConf)})), + %% run each of the SSL connectivity tests + lists:foreach(fun({Desc, Conf1, Conf2, ShouldPass}) -> + test_connection(Desc, {Node1, Conf1}, {Node2, Conf2}, ShouldPass) + end, SslConnTests), lager:info("Connectivity tests passed"), @@ -326,18 +230,23 @@ confirm() -> lager:info("Re-deploying 6 nodes"), - Nodes = rt:deploy_nodes(6, BaseConf, [riak_kv, riak_repl]), + Nodes = rt:deploy_nodes(6, ConfTcpBasic, [riak_kv, riak_repl]), [rt:wait_until_pingable(N) || N <- Nodes], {ANodes, BNodes} = lists:split(ClusterASize, Nodes), lager:info("Reconfiguring nodes with SSL options"), - [rt:update_app_config(N, merge_config(SSLConfig5, BaseConf)) || N <- - ANodes], - - [rt:update_app_config(N, merge_config(SSLConfig6, BaseConf)) || N <- - BNodes], + ConfANodes = [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsite2#ci.rd} + , {peer_common_name_acl, [CIsite2#ci.cn]} + ] ++ CIsite1#ci.ssl }], + ConfBNodes = [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIsite1#ci.rd} + , {peer_common_name_acl, [CIsite1#ci.cn]} + ] ++ CIsite2#ci.ssl }], + [rt:update_app_config(N, ConfANodes) || N <- ANodes], + [rt:update_app_config(N, ConfBNodes) || N <- BNodes], [rt:wait_until_pingable(N) || N <- Nodes], @@ -353,8 +262,17 @@ confirm() -> pass. -merge_config(Mixin, Base) -> - lists:ukeymerge(1, lists:keysort(1, Mixin), lists:keysort(1, Base)). +test_connection(Desc, {N1, C1}, {N2, C2}, ShouldPass) -> + lager:info("=== Testing " ++ Desc), + rt:log_to_nodes([N1, N2], "Testing " ++ Desc), + test_connection({N1, C1}, {N2, C2}, ShouldPass). + +test_connection(Left, Right, true) -> + ?assertEqual(ok, test_connection(Left, Right)), + lager:info("Connection succeeded"); +test_connection(Left, Right, false) -> + ?assertMatch({fail, _}, test_connection(Left, Right)), + lager:info("Connection rejected"). test_connection({Node1, Config1}, {Node2, Config2}) -> repl_util:disconnect_cluster(Node1, "B"), @@ -371,3 +289,10 @@ test_connection({Node1, Config1}, {Node2, Config2}) -> rt:log_to_nodes([Node1, Node2], "connect A to B"), repl_util:connect_cluster(Node1, "127.0.0.1", Port), repl_util:wait_for_connection(Node1, "B"). + +ssl_paths(Dir) -> + ssl_paths(Dir, "cert.pem", "key.pem", "cacerts.pem"). +ssl_paths(Dir, Cert, Key, CaCerts) -> + [{certfile, filename:join(Dir, Cert)} + ,{keyfile, filename:join(Dir, Key)} + ,{cacertdir, filename:join(Dir, CaCerts)}]. From 5c9169fe49e4a5970a5d96c8db204cb656f061a5 Mon Sep 17 00:00:00 2001 From: Ted Burghart Date: Fri, 13 Mar 2015 07:09:05 -0400 Subject: [PATCH 2/2] Fixed typos. Reordered tests to use lighter configurations. Added test for mismatched ACL. Added timeout override. --- tests/replication2_ssl.erl | 240 +++++++++++++++++++++++++------------ 1 file changed, 166 insertions(+), 74 deletions(-) diff --git a/tests/replication2_ssl.erl b/tests/replication2_ssl.erl index 03e5f0f87..c080b424e 100644 --- a/tests/replication2_ssl.erl +++ b/tests/replication2_ssl.erl @@ -27,6 +27,7 @@ %% Certificate Names -define(DEF_DOM, ".basho.com"). -define(DOM_WC, "*" ++ ?DEF_DOM). +-define(BAD_WC, "*.bahso.com"). -define(CERTN(S), S ++ ?DEF_DOM). -define(SITEN(N), ?CERTN("site" ++ ??N)). -define(CERTP(S), filename:join(CertDir, S)). @@ -39,35 +40,64 @@ ssl %% options returned from ssl_paths }). +%% +%% @doc Tests various TLS (SSL) connection scenarios for MDC. +%% The following configiration options are recognized: +%% +%% num_nodes [default 6] +%% How many nodes to use to build two clusters. +%% +%% cluster_a_size [default (num_nodes div 2)] +%% How many nodes to use in cluster "A". The remainder is used in cluster "B". +%% +%% conn_fail_time [default rt_max_wait_time] +%% A (presumably shortened) timout to use in tests where the connection is +%% expected to be rejected due to invalid TLS configurations. Something around +%% one minute is appropriate. Using the default ten-minute timeout, this +%% test will take more than an hour and a half to run successfully. +%% confirm() -> %% test requires allow_mult=false rt:set_conf(all, [{"buckets.default.allow_mult", "false"}]), NumNodes = rt_config:get(num_nodes, 6), - ClusterASize = rt_config:get(cluster_a_size, 3), + ClusterASize = rt_config:get(cluster_a_size, (NumNodes div 2)), CertDir = rt_config:get(rt_scratch_dir) ++ "/certs", - %% make a bunch of certificates and matching ci records - make_certs:rootCA(CertDir, "rootCA"), - make_certs:intermediateCA(CertDir, "intCA", "rootCA"), + %% make some CAs + make_certs:rootCA(CertDir, "CA_0"), + make_certs:intermediateCA(CertDir, "CA_1", "CA_0"), + make_certs:intermediateCA(CertDir, "CA_2", "CA_1"), + %% make a bunch of certificates and matching ci records S1Name = ?SITEN(1), S2Name = ?SITEN(2), - make_certs:endusers(CertDir, "intCA", [S1Name, S2Name]), - CIsite1 = #ci{cn = S1Name, rd = 1, ssl = ssl_paths(?CERTP(S1Name))}, - CIsite2 = #ci{cn = S2Name, rd = 1, ssl = ssl_paths(?CERTP(S2Name))}, - S3Name = ?SITEN(3), S4Name = ?SITEN(4), - make_certs:endusers(CertDir, "rootCA", [S3Name, S4Name]), - CIsite3 = #ci{cn = S3Name, rd = 0, ssl = ssl_paths(?CERTP(S3Name))}, - CIsite4 = #ci{cn = S4Name, rd = 0, ssl = ssl_paths(?CERTP(S4Name))}, + S5Name = ?SITEN(5), + S6Name = ?SITEN(6), + W1Name = ?CERTN("wildcard1"), + W2Name = ?CERTN("wildcard2"), + + make_certs:endusers(CertDir, "CA_0", [S1Name, S2Name]), + CIdep0s1 = #ci{cn = S1Name, rd = 0, ssl = ssl_paths(?CERTP(S1Name))}, + CIdep0s2 = #ci{cn = S2Name, rd = 0, ssl = ssl_paths(?CERTP(S2Name))}, + + make_certs:endusers(CertDir, "CA_1", [S3Name, S4Name]), + CIdep1s1 = #ci{cn = S3Name, rd = 1, ssl = ssl_paths(?CERTP(S3Name))}, + CIdep1s2 = #ci{cn = S4Name, rd = 1, ssl = ssl_paths(?CERTP(S4Name))}, + + make_certs:endusers(CertDir, "CA_2", [S5Name, S6Name]), + CIdep2s1 = #ci{cn = S5Name, rd = 2, ssl = ssl_paths(?CERTP(S5Name))}, + CIdep2s2 = #ci{cn = S6Name, rd = 2, ssl = ssl_paths(?CERTP(S6Name))}, + + make_certs:enduser(CertDir, "CA_1", ?DOM_WC, W1Name), + CIdep1wc = #ci{cn = ?DOM_WC, rd = 1, ssl = ssl_paths(?CERTP(W1Name))}, - WCName = ?CERTN("wildcard"), - make_certs:enduser(CertDir, "intCA", ?DOM_WC, WCName), - CIsiteWC = #ci{cn = WCName, rd = 1, ssl = ssl_paths(?CERTP(WCName))}, + make_certs:enduser(CertDir, "CA_2", ?DOM_WC, W2Name), + CIdep2wc = #ci{cn = ?DOM_WC, rd = 2, ssl = ssl_paths(?CERTP(W2Name))}, % crufty old certs really need to be replaced CIexpired = #ci{cn = "ny.cataclysm-software.net", rd = 0, @@ -87,6 +117,8 @@ confirm() -> %% Properties added to node configurations CANNOT currently be removed, %% only overwritten. As such, configurations that include ACLs MUST come %% after ALL non-ACL configurations! This has been learned the hard way :( + %% The same applies to the ssl_depth option, though it's much easier to + %% contend with - make sure it works, then always use a valid depth. %% %% @@ -94,103 +126,159 @@ confirm() -> %% Each is a tuple: {Description, Node1Config, Node2Config, Should Pass} %% SslConnTests = [ - {"identical certificate CN is disallowed", - [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite1#ci.ssl }], - [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite1#ci.ssl }], - false}, + %% + %% basic tests + %% {"non-SSL peer fails", [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite1#ci.ssl }], + ] ++ CIdep0s1#ci.ssl }], ConfTcpBasic, false}, {"non-SSL local fails", ConfTcpBasic, [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite2#ci.ssl }], + ] ++ CIdep0s2#ci.ssl }], false}, {"basic SSL connectivity", [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite1#ci.ssl }], + ] ++ CIdep0s1#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite2#ci.ssl }], + ] ++ CIdep0s2#ci.ssl }], true}, - {"SSL connectivity with intermediate CA", + {"expired peer certificate fails", [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite1#ci.ssl }], + ] ++ CIdep0s1#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite3#ci.ssl }], + ] ++ CIexpired#ci.ssl }], + false}, + {"expired local certificate fails", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIexpired#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep0s2#ci.ssl }], + false}, + {"identical certificate CN is disallowed", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep0s1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep0s1#ci.ssl }], + false}, + {"identical wildcard certificate CN is allowed", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep1wc#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep1wc#ci.ssl }], true}, - {"wildcard certifictes on both ends", + {"SSL connectivity with one intermediate CA is allowed by default", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep1s1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep1s2#ci.ssl }], + true}, + {"SSL connectivity with two intermediate CAs is disallowed by default", + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep2s1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + ] ++ CIdep2s2#ci.ssl }], + false}, + {"wildcard certificates on both ends", [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsiteWC#ci.ssl }], + ] ++ CIdep1wc#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsiteWC#ci.ssl }], + ] ++ CIdep1wc#ci.ssl }], true}, - {"wildcard certifictes on one end", + {"wildcard certificate on one end", [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsiteWC#ci.ssl }], + ] ++ CIdep1wc#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - ] ++ CIsite3#ci.ssl }], + ] ++ CIdep0s1#ci.ssl }], true}, - %% first use of ssl_depth, all subsequent tests must specify it - {"disallowing intermediate CA setting", + %% + %% first use of ssl_depth, all subsequent tests must specify + %% + {"disallowing intermediate CA setting allows direct-signed certs", [ConfRepl, {riak_core, [{ssl_enabled, true} , {ssl_depth, 0} - ] ++ CIsite3#ci.ssl }], + ] ++ CIdep0s1#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} , {ssl_depth, 0} - ] ++ CIsite4#ci.ssl }], + ] ++ CIdep0s2#ci.ssl }], true}, - {"disallowing intermediate CA connection", + {"disallowing intermediate CA disallows intermediate-signed peer", [ConfRepl, {riak_core, [{ssl_enabled, true} , {ssl_depth, 0} - ] ++ CIsite3#ci.ssl }], + ] ++ CIdep0s1#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsite3#ci.rd} - ] ++ CIsite1#ci.ssl }], + , {ssl_depth, CIdep0s1#ci.rd} + ] ++ CIdep1s2#ci.ssl }], false}, - %% first use of peer_common_name_acl, all subsequent tests must specify it - {"wildcard certifictes on one end with ACL", + {"disallowing intermediate CA disallows intermediate-signed local", [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsite1#ci.rd} - ] ++ CIsiteWC#ci.ssl }], + , {ssl_depth, CIdep0s2#ci.rd} + ] ++ CIdep1s1#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsiteWC#ci.rd} + , {ssl_depth, 0} + ] ++ CIdep0s2#ci.ssl }], + false}, + {"allow arbitrary-depth intermediate CAs", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIdep2s2#ci.rd} + ] ++ CIdep2s1#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIdep2s1#ci.rd} + ] ++ CIdep2s2#ci.ssl }], + true}, + %% + %% first use of peer_common_name_acl, all subsequent tests must specify + %% + {"wildcard certificate on one end with matching ACL", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIdep1s1#ci.rd} + ] ++ CIdep1wc#ci.ssl }], + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIdep1wc#ci.rd} , {peer_common_name_acl, [?DOM_WC]} - ] ++ CIsite1#ci.ssl }], + ] ++ CIdep1s1#ci.ssl }], true}, - {"wildcard and strict ACL", + {"wildcard certificate on one end with mismatched ACL", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIdep1s1#ci.rd} + ] ++ CIdep1wc#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsite2#ci.rd} + , {ssl_depth, CIdep1wc#ci.rd} + , {peer_common_name_acl, [?BAD_WC]} + ] ++ CIdep1s1#ci.ssl }], + false}, + {"one wildcard ACL and one strict ACL", + [ConfRepl, {riak_core, [{ssl_enabled, true} + , {ssl_depth, CIdep1s2#ci.rd} , {peer_common_name_acl, [?DOM_WC]} - ] ++ CIsite1#ci.ssl }], + ] ++ CIdep1s1#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsite1#ci.rd} - , {peer_common_name_acl, [CIsite1#ci.cn]} - ] ++ CIsite2#ci.ssl }], + , {ssl_depth, CIdep1s1#ci.rd} + , {peer_common_name_acl, [CIdep1s1#ci.cn]} + ] ++ CIdep1s2#ci.ssl }], true}, - {"wildcard certifictes on both ends with ACLs", + {"wildcard certificates on both ends with ACLs", [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsiteWC#ci.rd} - , {peer_common_name_acl, [CIsiteWC#ci.wc]} - ] ++ CIsiteWC#ci.ssl }], + , {ssl_depth, CIdep2wc#ci.rd} + , {peer_common_name_acl, [CIdep2wc#ci.wc]} + ] ++ CIdep1wc#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsiteWC#ci.rd} - , {peer_common_name_acl, [CIsiteWC#ci.wc]} - ] ++ CIsiteWC#ci.ssl }], + , {ssl_depth, CIdep1wc#ci.rd} + , {peer_common_name_acl, [CIdep1wc#ci.wc]} + ] ++ CIdep2wc#ci.ssl }], true}, - {"expired certificates fail", + {"explicit certificates with strict ACLs", [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIexpired#ci.rd} - , {peer_common_name_acl, [CIexpired#ci.wc]} - ] ++ CIsite1#ci.ssl }], + , {ssl_depth, CIdep2s2#ci.rd} + , {peer_common_name_acl, [CIdep2s2#ci.cn]} + ] ++ CIdep1s1#ci.ssl }], [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsite1#ci.rd} - , {peer_common_name_acl, [CIsite1#ci.wc]} - ] ++ CIexpired#ci.ssl }], - false} + , {ssl_depth, CIdep1s1#ci.rd} + , {peer_common_name_acl, [CIdep1s1#ci.cn]} + ] ++ CIdep2s2#ci.ssl }], + true} ], lager:info("Deploying 2 nodes for connectivity tests"), @@ -238,13 +326,13 @@ confirm() -> lager:info("Reconfiguring nodes with SSL options"), ConfANodes = [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsite2#ci.rd} - , {peer_common_name_acl, [CIsite2#ci.cn]} - ] ++ CIsite1#ci.ssl }], + , {ssl_depth, CIdep1s2#ci.rd} + , {peer_common_name_acl, [CIdep1s2#ci.cn]} + ] ++ CIdep1s1#ci.ssl }], ConfBNodes = [ConfRepl, {riak_core, [{ssl_enabled, true} - , {ssl_depth, CIsite1#ci.rd} - , {peer_common_name_acl, [CIsite1#ci.cn]} - ] ++ CIsite2#ci.ssl }], + , {ssl_depth, CIdep1s1#ci.rd} + , {peer_common_name_acl, [CIdep1s1#ci.cn]} + ] ++ CIdep1s2#ci.ssl }], [rt:update_app_config(N, ConfANodes) || N <- ANodes], [rt:update_app_config(N, ConfBNodes) || N <- BNodes], @@ -271,7 +359,11 @@ test_connection(Left, Right, true) -> ?assertEqual(ok, test_connection(Left, Right)), lager:info("Connection succeeded"); test_connection(Left, Right, false) -> + DefaultTimeout = rt_config:get(rt_max_wait_time), + ConnFailTimeout = rt_config:get(conn_fail_time, DefaultTimeout), + rt_config:set(rt_max_wait_time, ConnFailTimeout), ?assertMatch({fail, _}, test_connection(Left, Right)), + rt_config:set(rt_max_wait_time, DefaultTimeout), lager:info("Connection rejected"). test_connection({Node1, Config1}, {Node2, Config2}) ->