mirror of
https://github.com/valitydev/riak_test.git
synced 2024-11-06 08:35:22 +00:00
844 lines
41 KiB
Erlang
844 lines
41 KiB
Erlang
-module(pb_security).
|
|
|
|
-behavior(riak_test).
|
|
-export([confirm/0]).
|
|
|
|
-export([map_object_value/3, reduce_set_union/2, mapred_modfun_input/3]).
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
-include_lib("riakc/include/riakc.hrl").
|
|
|
|
-define(assertDenied(Op), ?assertMatch({error, <<"Permission",_/binary>>}, Op)).
|
|
|
|
confirm() ->
|
|
application:start(crypto),
|
|
application:start(asn1),
|
|
application:start(public_key),
|
|
application:start(ssl),
|
|
application:start(inets),
|
|
|
|
CertDir = rt_config:get(rt_scratch_dir) ++ "/pb_security_certs",
|
|
|
|
%% make a bunch of crypto keys
|
|
make_certs:rootCA(CertDir, "rootCA"),
|
|
make_certs:intermediateCA(CertDir, "intCA", "rootCA"),
|
|
make_certs:intermediateCA(CertDir, "revokedCA", "rootCA"),
|
|
make_certs:endusers(CertDir, "intCA", ["site1.basho.com", "site2.basho.com"]),
|
|
make_certs:endusers(CertDir, "rootCA", ["site3.basho.com", "site4.basho.com", "site5.basho.com"]),
|
|
make_certs:enduser(CertDir, "revokedCA", "site6.basho.com"),
|
|
make_certs:revoke(CertDir, "rootCA", "site5.basho.com"),
|
|
make_certs:revoke(CertDir, "rootCA", "revokedCA"),
|
|
|
|
%% use a leaf certificate as a CA certificate and make a totally bogus new leaf certificate
|
|
make_certs:create_ca_dir(CertDir, "site1.basho.com", make_certs:ca_cnf("site1.basho.com")),
|
|
file:copy(filename:join(CertDir, "site1.basho.com/key.pem"), filename:join(CertDir, "site1.basho.com/private/key.pem")),
|
|
make_certs:enduser(CertDir, "site1.basho.com", "site7.basho.com"),
|
|
file:copy(filename:join([CertDir, "site1.basho.com", "cacerts.pem"]), filename:join(CertDir, "site7.basho.com/cacerts.pem")),
|
|
{ok, Bin} = file:read_file(filename:join(CertDir, "site1.basho.com/cert.pem")),
|
|
{ok, FD} = file:open(filename:join(CertDir, "site7.basho.com/cacerts.pem"), [append]),
|
|
file:write(FD, ["\n", Bin]),
|
|
file:close(FD),
|
|
make_certs:gencrl(CertDir, "site1.basho.com"),
|
|
|
|
%% start a HTTP server to serve the CRLs
|
|
%%
|
|
%% NB: we use the 'stand_alone' option to link the server to the
|
|
%% test process, so it exits when the test process exits.
|
|
{ok, _HTTPPid} = inets:start(httpd, [{port, 8000}, {server_name, "localhost"},
|
|
{server_root, "/tmp"},
|
|
{document_root, CertDir},
|
|
{modules, [mod_get]}], stand_alone),
|
|
|
|
lager:info("Deploy some nodes"),
|
|
PrivDir = rt:priv_dir(),
|
|
Conf = [
|
|
{riak_core, [
|
|
{default_bucket_props, [{allow_mult, true}, {dvv_enabled, true}]},
|
|
{ssl, [
|
|
{certfile, filename:join([CertDir,"site3.basho.com/cert.pem"])},
|
|
{keyfile, filename:join([CertDir, "site3.basho.com/key.pem"])},
|
|
{cacertfile, filename:join([CertDir, "site3.basho.com/cacerts.pem"])}
|
|
]},
|
|
{job_accept_class, undefined}
|
|
]},
|
|
{riak_search, [
|
|
{enabled, true}
|
|
]}
|
|
],
|
|
|
|
MD = riak_test_runner:metadata(),
|
|
HaveIndexes = case proplists:get_value(backend, MD) of
|
|
undefined -> false; %% default is da 'cask
|
|
bitcask -> false;
|
|
_ -> true
|
|
end,
|
|
|
|
Nodes = rt:build_cluster(4, Conf),
|
|
Node = hd(Nodes),
|
|
%% enable security on the cluster
|
|
ok = rpc:call(Node, riak_core_console, security_enable, [[]]),
|
|
|
|
[_, {pb, {"127.0.0.1", Port}}] = rt:connection_info(Node),
|
|
|
|
lager:info("Checking non-SSL results in error"),
|
|
%% can connect without credentials, but not do anything
|
|
{ok, PB0} = pbc([{host, "127.0.0.1"}, {port, Port}], []),
|
|
?assertEqual({error, <<"Security is enabled, please STARTTLS first">>},
|
|
riakc_pb_socket:ping(PB0)),
|
|
|
|
riakc_pb_socket:stop(PB0),
|
|
|
|
%% Hindi in Devanagari : हिन्दी
|
|
Username = [2361,2367,2344,2381,2342,2368],
|
|
UsernameBin = unicode:characters_to_binary(Username, utf8, utf8),
|
|
|
|
lager:info("Checking SSL requires peer cert validation"),
|
|
%% can't connect without specifying cacert to validate the server
|
|
?assertMatch({error, _}, pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, UsernameBin,
|
|
"pass"}])),
|
|
|
|
lager:info("Checking that authentication is required"),
|
|
%% invalid credentials should be invalid
|
|
?assertEqual({error, {tcp, <<"Authentication failed">>}},
|
|
pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, UsernameBin,
|
|
"pass"}, {cacertfile,
|
|
filename:join([CertDir, "rootCA/cert.pem"])}])),
|
|
|
|
lager:info("Creating user"),
|
|
%% grant the user credentials
|
|
ok = rpc:call(Node, riak_core_console, add_user, [[Username, "password=password"]]),
|
|
|
|
lager:info("Setting trust mode on user"),
|
|
%% trust 'user' on localhost
|
|
ok = rpc:call(Node, riak_core_console, add_source, [[Username, "127.0.0.1/32",
|
|
"trust"]]),
|
|
|
|
lager:info("Checking that credentials are ignored in trust mode"),
|
|
%% invalid credentials should be ignored in trust mode
|
|
{ok, PB1} = pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, UsernameBin,
|
|
"pass"}, {cacertfile,
|
|
filename:join([CertDir, "rootCA/cert.pem"])}]),
|
|
?assertEqual(pong, riakc_pb_socket:ping(PB1)),
|
|
riakc_pb_socket:stop(PB1),
|
|
|
|
lager:info("Setting password mode on user"),
|
|
%% require password on localhost
|
|
ok = rpc:call(Node, riak_core_console, add_source, [[Username, "127.0.0.1/32",
|
|
"password"]]),
|
|
|
|
lager:info("Checking that incorrect password fails auth"),
|
|
%% invalid credentials should be invalid
|
|
?assertEqual({error, {tcp, <<"Authentication failed">>}},
|
|
pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, UsernameBin,
|
|
"pass"}, {cacertfile,
|
|
filename:join([CertDir, "rootCA/cert.pem"])}])),
|
|
|
|
lager:info("Checking that correct password is successful"),
|
|
%% valid credentials should be valid
|
|
{ok, PB2} = pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, UsernameBin,
|
|
"password"}, {cacertfile,
|
|
filename:join([CertDir, "rootCA/cert.pem"])}]),
|
|
?assertEqual(pong, riakc_pb_socket:ping(PB2)),
|
|
riakc_pb_socket:stop(PB2),
|
|
|
|
lager:info("Creating a certificate-authenticated user"),
|
|
%% grant the user credential
|
|
ok = rpc:call(Node, riak_core_console, add_user, [["site4.basho.com"]]),
|
|
|
|
%% require certificate auth on localhost
|
|
ok = rpc:call(Node, riak_core_console, add_source, [["site4.basho.com",
|
|
"127.0.0.1/32",
|
|
"certificate"]]),
|
|
|
|
lager:info("Checking certificate authentication"),
|
|
%% valid credentials should be valid
|
|
{ok, PB3} = pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, "site4.basho.com",
|
|
"password"},
|
|
{cacertfile, filename:join([CertDir, "site4.basho.com/cacerts.pem"])},
|
|
{certfile, filename:join([CertDir, "site4.basho.com/cert.pem"])},
|
|
{keyfile, filename:join([CertDir, "site4.basho.com/key.pem"])}
|
|
]),
|
|
?assertEqual(pong, riakc_pb_socket:ping(PB3)),
|
|
riakc_pb_socket:stop(PB3),
|
|
|
|
lager:info("Creating another cert-auth user"),
|
|
%% grant the user credential
|
|
ok = rpc:call(Node, riak_core_console, add_user, [["site5.basho.com"]]),
|
|
|
|
%% require certificate auth on localhost
|
|
ok = rpc:call(Node, riak_core_console, add_source, [["site5.basho.com",
|
|
"127.0.0.1/32",
|
|
"certificate"]]),
|
|
|
|
lager:info("Checking auth with mismatched user/cert fails"),
|
|
%% authing with mismatched user should fail
|
|
?assertEqual({error, {tcp, <<"Authentication failed">>}},
|
|
pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, "site5.basho.com",
|
|
"password"},
|
|
{cacertfile, filename:join([CertDir, "rootCA/cert.pem"])},
|
|
{certfile, filename:join([CertDir, "site4.basho.com/cert.pem"])},
|
|
{keyfile, filename:join([CertDir, "site4.basho.com/key.pem"])}
|
|
])),
|
|
|
|
lager:info("Checking revoked certificates are denied"),
|
|
?assertMatch({error, {tcp, _Reason}},
|
|
pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, "site5.basho.com",
|
|
"password"},
|
|
{cacertfile, filename:join([CertDir, "rootCA/cert.pem"])},
|
|
{certfile, filename:join([CertDir, "site5.basho.com/cert.pem"])},
|
|
{keyfile, filename:join([CertDir, "site5.basho.com/key.pem"])}
|
|
])),
|
|
|
|
lager:info("Checking auth with non-peer certificate fails"),
|
|
%% authing with non-peer certificate should fail
|
|
?assertMatch({error, {tcp, _Reason}},
|
|
pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, "site5.basho.com",
|
|
"password"},
|
|
{cacertfile, filename:join([PrivDir,
|
|
"certs/CA/rootCA/cert.pem"])},
|
|
{certfile, filename:join([PrivDir,
|
|
"certs/cacert.org/ca-cert.pem"])},
|
|
{keyfile, filename:join([PrivDir,
|
|
"certs/cacert.org/ca-key.pem"])}
|
|
])),
|
|
|
|
lager:info("cert from intermediate CA should work"),
|
|
%% grant the user credential
|
|
ok = rpc:call(Node, riak_core_console, add_user, [["site1.basho.com"]]),
|
|
|
|
%% require certificate auth on localhost
|
|
ok = rpc:call(Node, riak_core_console, add_source, [["site1.basho.com",
|
|
"127.0.0.1/32",
|
|
"certificate"]]),
|
|
|
|
{ok, PB4} = pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, "site1.basho.com", "password"},
|
|
{cacertfile, filename:join([CertDir, "site1.basho.com/cacerts.pem"])},
|
|
{certfile, filename:join([CertDir, "site1.basho.com/cert.pem"])},
|
|
{keyfile, filename:join([CertDir, "site1.basho.com/key.pem"])}
|
|
]),
|
|
|
|
?assertEqual(pong, riakc_pb_socket:ping(PB4)),
|
|
riakc_pb_socket:stop(PB4),
|
|
|
|
lager:info("checking certificates from a revoked CA are denied"),
|
|
%% grant the user credential
|
|
ok = rpc:call(Node, riak_core_console, add_user, [["site6.basho.com"]]),
|
|
|
|
%% require certificate auth on localhost
|
|
ok = rpc:call(Node, riak_core_console, add_source, [["site6.basho.com",
|
|
"127.0.0.1/32",
|
|
"certificate"]]),
|
|
|
|
?assertMatch({error, {tcp, _Reason}},
|
|
pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, "site6.basho.com", "password"},
|
|
{cacertfile, filename:join([CertDir, "site6.basho.com/cacerts.pem"])},
|
|
{certfile, filename:join([CertDir, "site6.basho.com/cert.pem"])},
|
|
{keyfile, filename:join([CertDir, "site6.basho.com/key.pem"])}
|
|
])),
|
|
|
|
lager:info("checking a certificate signed by a leaf CA is not honored"),
|
|
%% grant the user credential
|
|
ok = rpc:call(Node, riak_core_console, add_user, [["site7.basho.com"]]),
|
|
|
|
%% require certificate auth on localhost
|
|
ok = rpc:call(Node, riak_core_console, add_source, [["site7.basho.com",
|
|
"127.0.0.1/32",
|
|
"certificate"]]),
|
|
|
|
?assertMatch({error, {tcp, _Reason}},
|
|
pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, "site7.basho.com", "password"},
|
|
{cacertfile, filename:join([CertDir, "site7.basho.com/cacerts.pem"])},
|
|
{certfile, filename:join([CertDir, "site7.basho.com/cert.pem"])},
|
|
{keyfile, filename:join([CertDir, "site7.basho.com/key.pem"])}
|
|
])),
|
|
|
|
%% time to actually do some stuff
|
|
{ok, PB} = pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, UsernameBin, "password"},
|
|
{cacertfile,
|
|
filename:join([CertDir, "rootCA/cert.pem"])}]),
|
|
?assertEqual(pong, riakc_pb_socket:ping(PB)),
|
|
|
|
lager:info("verifying that user cannot get/put without grants"),
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:get(PB,
|
|
<<"hello">>,
|
|
<<"world">>)),
|
|
|
|
lager:info("Granting riak_kv.get, checking get works but put doesn't"),
|
|
grant(Node, ["riak_kv.get", "on", "default", "hello", "to", Username]),
|
|
|
|
?assertMatch({error, notfound}, riakc_pb_socket:get(PB, <<"hello">>,
|
|
<<"world">>)),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new(<<"hello">>, <<"world">>,
|
|
<<"howareyou">>))),
|
|
|
|
lager:info("Granting riak_kv.put, checking put works and roundtrips with get"),
|
|
grant(Node, ["riak_kv.put", "on", "default", "hello", "to", Username]),
|
|
|
|
?assertEqual(ok,
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new(<<"hello">>, <<"world">>,
|
|
<<"1">>, "application/json"))),
|
|
|
|
?assertMatch({ok, _Obj}, riakc_pb_socket:get(PB, <<"hello">>,
|
|
<<"world">>)),
|
|
|
|
%% 1.4 counters
|
|
%%
|
|
grant(Node, ["riak_kv.put,riak_kv.get", "on", "default", "counters", "to", Username]),
|
|
%% ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.put,riak_kv.get", "on",
|
|
%% "default", "counters", "to", Username]]),
|
|
|
|
|
|
lager:info("Checking that counters work on resources that have get/put permitted"),
|
|
?assertEqual({error, notfound}, riakc_pb_socket:counter_val(PB,
|
|
<<"counters">>,
|
|
<<"numberofpies">>)),
|
|
ok = riakc_pb_socket:counter_incr(PB, <<"counters">>,
|
|
<<"numberofpies">>, 5),
|
|
?assertEqual({ok, 5}, riakc_pb_socket:counter_val(PB, <<"counters">>,
|
|
<<"numberofpies">>)),
|
|
|
|
lager:info("Revoking get, checking that counter_val fails"),
|
|
%% revoke get
|
|
ok = rpc:call(Node, riak_core_console, revoke,
|
|
[["riak_kv.get", "on", "default", "counters", "from", Username]]),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:counter_val(PB, <<"counters">>,
|
|
<<"numberofpies">>)),
|
|
ok = riakc_pb_socket:counter_incr(PB, <<"counters">>,
|
|
<<"numberofpies">>, 5),
|
|
|
|
lager:info("Revoking put, checking that counter_incr fails"),
|
|
%% revoke put
|
|
ok = rpc:call(Node, riak_core_console, revoke,
|
|
[["riak_kv.put", "on", "default", "counters", "from", Username]]),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:counter_incr(PB, <<"counters">>,
|
|
<<"numberofpies">>, 5)),
|
|
|
|
|
|
lager:info("Revoking get/put, checking that get/put are disallowed"),
|
|
ok = rpc:call(Node, riak_core_console, revoke, [["riak_kv.get,riak_kv.put", "on",
|
|
"default", "hello", "from", Username]]),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:get(PB,
|
|
<<"hello">>,
|
|
<<"world">>)),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new(<<"hello">>, <<"world">>,
|
|
<<"howareyou">>))),
|
|
|
|
%% try the 'any' grant
|
|
lager:info("Granting get on ANY, checking user can fetch any bucket/key"),
|
|
grant(Node, ["riak_kv.get", "on", "any", "to", Username]),
|
|
|
|
?assertMatch({ok, _Obj}, riakc_pb_socket:get(PB, <<"hello">>,
|
|
<<"world">>)),
|
|
|
|
lager:info("Revoking ANY permission, checking fetch fails"),
|
|
ok = rpc:call(Node, riak_core_console, revoke, [["riak_kv.get", "on",
|
|
"any", "from", Username]]),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:get(PB,
|
|
<<"hello">>,
|
|
<<"world">>)),
|
|
|
|
%% list keys
|
|
lager:info("Checking that list keys is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:list_keys(PB, <<"hello">>)),
|
|
|
|
lager:info("Granting riak_kv.list_keys, checking that list_keys succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.list_keys", "on",
|
|
"default", "hello", "to", Username]]),
|
|
|
|
?assertMatch({ok, [<<"world">>]}, riakc_pb_socket:list_keys(PB, <<"hello">>)),
|
|
|
|
lager:info("Checking that list buckets is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:list_buckets(PB)),
|
|
|
|
lager:info("Granting riak_kv.list_buckets, checking that list_buckets succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.list_buckets", "on",
|
|
"default", "to", Username]]),
|
|
|
|
{ok, BList} = riakc_pb_socket:list_buckets(PB),
|
|
?assertEqual([<<"counters">>, <<"hello">>], lists:sort(BList)),
|
|
|
|
%% still need mapreduce permission
|
|
lager:info("Checking that full-bucket mapred is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:mapred_bucket(PB, <<"hello">>,
|
|
[{map, {jsfun, <<"Riak.mapValuesJson">>}, undefined, false},
|
|
{reduce, {jsfun,
|
|
<<"Riak.reduceSum">>},
|
|
undefined, true}])),
|
|
|
|
lager:info("Granting mapreduce, checking that job succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.mapreduce", "on",
|
|
"default", "to", Username]]),
|
|
|
|
?assertEqual({ok, [{1, [1]}]},
|
|
riakc_pb_socket:mapred_bucket(PB, <<"hello">>,
|
|
[{map, {jsfun, <<"Riak.mapValuesJson">>}, undefined, false},
|
|
{reduce, {jsfun,
|
|
<<"Riak.reduceSum">>},
|
|
undefined, true}])),
|
|
|
|
lager:info("checking mapreduce with a whitelisted modfun works"),
|
|
?assertEqual({ok, [{1, [<<"1">>]}]},
|
|
riakc_pb_socket:mapred_bucket(PB, <<"hello">>,
|
|
[{map, {modfun, riak_kv_mapreduce,
|
|
map_object_value}, undefined, false},
|
|
{reduce, {modfun,
|
|
riak_kv_mapreduce,
|
|
reduce_set_union},
|
|
undefined, true}])),
|
|
|
|
%% load this module on all the nodes
|
|
ok = rt:load_modules_on_nodes([?MODULE], Nodes),
|
|
|
|
lager:info("checking mapreduce with a insecure modfun input fails"),
|
|
?assertMatch({error, <<"{inputs,{insecure_module_path",_/binary>>},
|
|
riakc_pb_socket:mapred_bucket(PB, {modfun, ?MODULE,
|
|
mapred_modfun_input, []},
|
|
[{map, {modfun, riak_kv_mapreduce,
|
|
map_object_value}, undefined, false},
|
|
{reduce, {modfun,
|
|
riak_kv_mapreduce,
|
|
reduce_set_union},
|
|
undefined, true}])),
|
|
|
|
lager:info("checking mapreduce with a insecure modfun phase fails"),
|
|
?assertMatch({error, <<"{query,{insecure_module_path",_/binary>>},
|
|
riakc_pb_socket:mapred_bucket(PB, <<"hello">>,
|
|
[{map, {modfun, ?MODULE,
|
|
map_object_value}, undefined, false},
|
|
{reduce, {modfun,
|
|
?MODULE,
|
|
reduce_set_union},
|
|
undefined, true}])),
|
|
|
|
lager:info("whitelisting module path"),
|
|
{?MODULE, _ModBin, ModFile} = code:get_object_code(?MODULE),
|
|
ok = rpc:call(Node, application, set_env, [riak_kv, add_paths, [filename:dirname(ModFile)]]),
|
|
|
|
lager:info("checking mapreduce with a insecure modfun input fails when"
|
|
" whitelisted but lacking permissions"),
|
|
?assertMatch({error, <<"Permission",_/binary>>},
|
|
riakc_pb_socket:mapred_bucket(PB, {modfun, ?MODULE,
|
|
mapred_modfun_input, []},
|
|
[{map, {modfun, riak_kv_mapreduce,
|
|
map_object_value}, undefined, false},
|
|
{reduce, {modfun,
|
|
riak_kv_mapreduce,
|
|
reduce_set_union},
|
|
undefined, true}])),
|
|
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.mapreduce", "on",
|
|
"any", "to", Username]]),
|
|
?assertEqual({ok, [{1, [<<"1">>]}]},
|
|
riakc_pb_socket:mapred_bucket(PB, {modfun, ?MODULE,
|
|
mapred_modfun_input, []},
|
|
[{map, {modfun, riak_kv_mapreduce,
|
|
map_object_value}, undefined, false},
|
|
{reduce, {modfun,
|
|
riak_kv_mapreduce,
|
|
reduce_set_union},
|
|
undefined, true}])),
|
|
|
|
ok = rpc:call(Node, riak_core_console, revoke, [["riak_kv.mapreduce", "on",
|
|
"any", "from", Username]]),
|
|
|
|
lager:info("checking mapreduce with a insecure modfun phase works when"
|
|
" whitelisted"),
|
|
?assertEqual({ok, [{1, [<<"1">>]}]},
|
|
riakc_pb_socket:mapred_bucket(PB, <<"hello">>,
|
|
[{map, {modfun, ?MODULE,
|
|
map_object_value}, undefined, false},
|
|
{reduce, {modfun,
|
|
?MODULE,
|
|
reduce_set_union},
|
|
undefined, true}])),
|
|
|
|
|
|
|
|
lager:info("link walking should fail with a deprecation error"),
|
|
?assertMatch({error, _}, riakc_pb_socket:mapred(PB, [{<<"lists">>, <<"mine">>}],
|
|
[{link, <<"items">>, '_', true}])),
|
|
|
|
%% revoke only the list_keys permission
|
|
lager:info("Revoking list-keys, checking that full-bucket mapred fails"),
|
|
ok = rpc:call(Node, riak_core_console, revoke, [["riak_kv.list_keys", "on",
|
|
"default", "hello", "from", Username]]),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:mapred_bucket(PB, <<"hello">>,
|
|
[{map, {jsfun, <<"Riak.mapValuesJson">>}, undefined, false},
|
|
{reduce, {jsfun,
|
|
<<"Riak.reduceSum">>},
|
|
undefined, true}])),
|
|
|
|
case HaveIndexes of
|
|
false -> ok;
|
|
true ->
|
|
%% 2i permission test
|
|
lager:info("Checking 2i is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:get_index(PB, <<"hello">>,
|
|
{binary_index,
|
|
"name"},
|
|
<<"John">>)),
|
|
|
|
lager:info("Granting 2i permissions, checking that results come back"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.index", "on",
|
|
"default", "to", Username]]),
|
|
|
|
%% don't actually have any indexes
|
|
?assertMatch({ok, ?INDEX_RESULTS{keys=[]}},
|
|
riakc_pb_socket:get_index(PB, <<"hello">>,
|
|
{binary_index,
|
|
"name"},
|
|
<<"John">>)),
|
|
ok
|
|
end,
|
|
|
|
%% get/set bprops
|
|
lager:info("Checking that get_bucket is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:get_bucket(PB, <<"mybucket">>)),
|
|
|
|
lager:info("Granting riak_core.get_bucket, checking that get_bucket succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_core.get_bucket", "on",
|
|
"default", "mybucket", "to", Username]]),
|
|
|
|
?assertEqual(3, proplists:get_value(n_val, element(2,
|
|
riakc_pb_socket:get_bucket(PB,
|
|
<<"mybucket">>)))),
|
|
|
|
lager:info("Checking that set_bucket is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:set_bucket(PB, <<"mybucket">>, [{n_val, 5}])),
|
|
|
|
lager:info("Granting set_bucket, checking that set_bucket succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_core.set_bucket", "on",
|
|
"default", "mybucket", "to", Username]]),
|
|
?assertEqual(ok,
|
|
riakc_pb_socket:set_bucket(PB, <<"mybucket">>, [{n_val, 5}])),
|
|
|
|
?assertEqual(5, proplists:get_value(n_val, element(2,
|
|
riakc_pb_socket:get_bucket(PB,
|
|
<<"mybucket">>)))),
|
|
|
|
%%%%%%%%%%%%
|
|
%%% bucket type tests
|
|
%%%%%%%%%%%%
|
|
|
|
%% create a new type
|
|
rt:create_and_activate_bucket_type(Node, <<"mytype">>, [{n_val, 3}]),
|
|
rt:wait_until_bucket_type_status(<<"mytype">>, active, Nodes),
|
|
rt:wait_until_bucket_type_visible(Nodes, <<"mytype">>),
|
|
|
|
lager:info("Checking that get on a new bucket type is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:get(PB,
|
|
{<<"mytype">>,
|
|
<<"hello">>},
|
|
<<"world">>)),
|
|
|
|
lager:info("Granting get on the new bucket type, checking that it succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.get", "on",
|
|
"mytype", "hello", "to", Username]]),
|
|
|
|
?assertMatch({error, notfound}, riakc_pb_socket:get(PB, {<<"mytype">>,
|
|
<<"hello">>},
|
|
<<"world">>)),
|
|
|
|
lager:info("Checking that permisisons are unchanged on the default bucket type"),
|
|
%% can't read from the default bucket, though
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:get(PB,
|
|
<<"hello">>,
|
|
<<"world">>)),
|
|
|
|
lager:info("Checking that put on the new bucket type is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new({<<"mytype">>, <<"hello">>}, <<"world">>,
|
|
<<"howareyou">>))),
|
|
|
|
lager:info("Granting put on a bucket in the new bucket type, checking that it succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.put", "on",
|
|
"mytype", "hello", "to", Username]]),
|
|
|
|
?assertEqual(ok,
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new({<<"mytype">>, <<"hello">>}, <<"world">>,
|
|
<<"howareyou">>))),
|
|
|
|
?assertEqual(ok,
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new({<<"mytype">>,
|
|
<<"hello">>}, <<"drnick">>,
|
|
<<"Hi, everybody">>))),
|
|
|
|
?assertMatch({ok, _Obj}, riakc_pb_socket:get(PB, {<<"mytype">>,
|
|
<<"hello">>},
|
|
<<"world">>)),
|
|
|
|
lager:info("Revoking get/put on the new bucket type, checking that they fail"),
|
|
ok = rpc:call(Node, riak_core_console, revoke, [["riak_kv.get,riak_kv.put", "on",
|
|
"mytype", "hello", "from", Username]]),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:get(PB,
|
|
{<<"mytype">>,
|
|
<<"hello">>},
|
|
<<"world">>)),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new({<<"mytype">>, <<"hello">>}, <<"world">>,
|
|
<<"howareyou">>))),
|
|
|
|
lager:info("Checking that list keys is disallowed on the new bucket type"),
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:list_keys(PB, {<<"mytype">>, <<"hello">>})),
|
|
|
|
lager:info("Granting list keys on a bucket in the new type, checking that it works"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.list_keys", "on",
|
|
"mytype", "hello", "to", Username]]),
|
|
|
|
?assertEqual([<<"drnick">>, <<"world">>], lists:sort(element(2, riakc_pb_socket:list_keys(PB,
|
|
{<<"mytype">>,
|
|
<<"hello">>})))),
|
|
|
|
lager:info("Creating another bucket type"),
|
|
%% create a new type
|
|
rt:create_and_activate_bucket_type(Node, <<"mytype2">>, [{allow_mult, true}]),
|
|
rt:wait_until_bucket_type_status(<<"mytype2">>, active, Nodes),
|
|
rt:wait_until_bucket_type_visible(Nodes, <<"mytype2">>),
|
|
|
|
lager:info("Checking that get on the new type is disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:get(PB,
|
|
{<<"mytype2">>,
|
|
<<"hello">>},
|
|
<<"world">>)),
|
|
|
|
lager:info("Granting get/put on all buckets in the new type, checking that get/put works"),
|
|
%% do a wildcard grant
|
|
ok = rpc:call(Node, riak_core_console, grant,
|
|
[["riak_kv.get,riak_kv.put", "on",
|
|
"mytype2", "to", Username]]),
|
|
|
|
?assertMatch({error, notfound}, riakc_pb_socket:get(PB, {<<"mytype2">>,
|
|
<<"hello">>},
|
|
<<"world">>)),
|
|
|
|
?assertEqual(ok,
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new({<<"mytype2">>,
|
|
<<"embiggen">>},
|
|
<<"the noblest heart">>,
|
|
<<"true">>))),
|
|
|
|
?assertEqual(ok,
|
|
riakc_pb_socket:put(PB,
|
|
riakc_obj:new({<<"mytype2">>,
|
|
<<"cromulent">>},
|
|
<<"perfectly">>,
|
|
<<"true">>))),
|
|
|
|
lager:info("Checking that list buckets is disallowed on the new type"),
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:list_buckets(PB, <<"mytype2">>)),
|
|
|
|
lager:info("Granting list buckets on the new type, checking that it succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant, [["riak_kv.list_buckets", "on",
|
|
"mytype2", "to", Username]]),
|
|
|
|
?assertMatch([<<"cromulent">>, <<"embiggen">>], lists:sort(element(2,
|
|
riakc_pb_socket:list_buckets(PB,
|
|
<<"mytype2">>)))),
|
|
|
|
|
|
%% get/set bucket type props
|
|
|
|
lager:info("Checking that get/set bucket-type properties are disallowed"),
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:set_bucket_type(PB, <<"mytype2">>,
|
|
[{n_val, 5}])),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>},
|
|
riakc_pb_socket:get_bucket_type(PB, <<"mytype2">>)),
|
|
|
|
lager:info("Granting get on bucket type props, checking it succeeds and put still fails"),
|
|
ok = rpc:call(Node, riak_core_console, grant,
|
|
[["riak_core.get_bucket_type", "on", "mytype2", "to", Username]]),
|
|
|
|
?assertEqual(3, proplists:get_value(n_val,
|
|
element(2, riakc_pb_socket:get_bucket_type(PB,
|
|
<<"mytype2">>)))),
|
|
|
|
?assertMatch({error, <<"Permission", _/binary>>}, riakc_pb_socket:set_bucket_type(PB, <<"mytype2">>,
|
|
[{n_val, 5}])),
|
|
|
|
lager:info("Granting set on bucket type props, checking it succeeds"),
|
|
ok = rpc:call(Node, riak_core_console, grant,
|
|
[["riak_core.set_bucket_type", "on", "mytype2", "to", Username]]),
|
|
|
|
riakc_pb_socket:set_bucket_type(PB, <<"mytype2">>, [{n_val, 5}]),
|
|
|
|
?assertEqual(5, proplists:get_value(n_val,
|
|
element(2, riakc_pb_socket:get_bucket_type(PB,
|
|
<<"mytype2">>)))),
|
|
|
|
riakc_pb_socket:set_bucket_type(PB, <<"mytype2">>, [{n_val, 3}]),
|
|
|
|
crdt_tests(Nodes, PB),
|
|
|
|
riakc_pb_socket:stop(PB),
|
|
|
|
group_test(Node, Port, CertDir).
|
|
|
|
group_test(Node, Port, CertDir) ->
|
|
%%%%%%%%%%%%%%%%
|
|
%% test groups
|
|
%%%%%%%%%%%%%%%%
|
|
|
|
lager:info("Creating a new group"),
|
|
%% create a new group
|
|
ok = rpc:call(Node, riak_core_console, add_group, [["group"]]),
|
|
|
|
lager:info("Creating a user in the group"),
|
|
%% create a new user in that group
|
|
ok = rpc:call(Node, riak_core_console, add_user, [["myuser", "groups=group"]]),
|
|
|
|
|
|
lager:info("Granting get/put/delete on a bucket type to the group, checking those requests work"),
|
|
|
|
%% do a wildcard grant
|
|
grant(Node,["riak_kv.get,riak_kv.put,riak_kv.delete", "on", "mytype2",
|
|
"to", "group"]),
|
|
|
|
%% trust 'myuser' on localhost
|
|
ok = rpc:call(Node, riak_core_console, add_source, [["myuser", "127.0.0.1/32",
|
|
"trust"]]),
|
|
|
|
{ok, PB} = pbc([{host, "127.0.0.1"}, {port, Port}],
|
|
[{credentials, "myuser", "password"},
|
|
{cacertfile,
|
|
filename:join([CertDir, "rootCA/cert.pem"])}]),
|
|
|
|
?assertMatch({error, notfound}, (riakc_pb_socket:get(PB, {<<"mytype2">>,
|
|
<<"hello">>},
|
|
<<"world">>))),
|
|
?assertEqual(ok,
|
|
(riakc_pb_socket:put(PB,
|
|
riakc_obj:new({<<"mytype2">>, <<"hello">>}, <<"world">>,
|
|
<<"howareyou">>)))),
|
|
|
|
{ok, Obj} = riakc_pb_socket:get(PB, {<<"mytype2">>,
|
|
<<"hello">>},
|
|
<<"world">>),
|
|
riakc_pb_socket:delete_obj(PB, Obj),
|
|
|
|
?assertMatch({error, notfound}, (riakc_pb_socket:get(PB, {<<"mytype2">>,
|
|
<<"hello">>},
|
|
<<"world">>))),
|
|
|
|
lager:info("riak search should not be running with security enabled"),
|
|
?assertMatch({error, <<"Riak Search 1.0 is deprecated", _/binary>>},
|
|
riakc_pb_socket:search(PB, <<"index">>, <<"foo:bar">>)),
|
|
|
|
riakc_pb_socket:stop(PB),
|
|
pass.
|
|
|
|
grant(Node, Args) ->
|
|
ok = rpc:call(Node, riak_core_console, grant, [Args]).
|
|
|
|
crdt_tests([Node|_]=Nodes, PB) ->
|
|
Username = [2361,2367,2344,2381,2342,2368],
|
|
|
|
%% rt:create_and_activate
|
|
lager:info("Creating bucket types for CRDTs"),
|
|
|
|
Types = [{<<"counters">>, counter, riakc_counter:to_op(riakc_counter:increment(5, riakc_counter:new()))},
|
|
{<<"sets">>, set, riakc_set:to_op(riakc_set:add_element(<<"foo">>, riakc_set:new()))},
|
|
{<<"maps">>, map, riakc_map:to_op(riakc_map:update({<<"bar">>, counter}, fun(In) -> riakc_counter:increment(In) end, riakc_map:new()))}],
|
|
[ begin
|
|
rt:create_and_activate_bucket_type(Node, BType, [{allow_mult, true}, {datatype, DType}]),
|
|
rt:wait_until_bucket_type_status(BType, active, Nodes),
|
|
rt:wait_until_bucket_type_visible(Nodes, BType)
|
|
end || {BType, DType, _Op} <- Types ],
|
|
|
|
lager:info("Checking that CRDT fetch is denied"),
|
|
|
|
[ ?assertDenied(riakc_pb_socket:fetch_type(PB, {BType, <<"bucket">>}, <<"key">>))
|
|
|| {BType, _, _} <- Types ],
|
|
|
|
lager:info("Granting CRDT riak_kv.get, checking that fetches succeed"),
|
|
|
|
[ grant(Node, ["riak_kv.get", "on", binary_to_list(Type), "to", Username]) || {Type, _, _} <- Types ],
|
|
|
|
[ ?assertEqual({error, {notfound, DType}},
|
|
riakc_pb_socket:fetch_type(PB, {BType, <<"bucket">>}, <<"key">>)) ||
|
|
{BType, DType, _} <- Types ],
|
|
|
|
lager:info("Checking that CRDT update is denied"),
|
|
|
|
[ ?assertDenied(riakc_pb_socket:update_type(PB, {BType, <<"bucket">>}, <<"key">>, Op))
|
|
|| {BType, _, Op} <- Types ],
|
|
|
|
|
|
lager:info("Granting CRDT riak_kv.put, checking that updates succeed"),
|
|
|
|
[ grant(Node, ["riak_kv.put", "on", binary_to_list(Type), "to", Username]) || {Type, _, _} <- Types ],
|
|
|
|
[ ?assertEqual(ok, riakc_pb_socket:update_type(PB, {BType, <<"bucket">>}, <<"key">>, Op))
|
|
|| {BType, _, Op} <- Types ],
|
|
|
|
ok.
|
|
|
|
map_object_value(RiakObject, A, B) ->
|
|
riak_kv_mapreduce:map_object_value(RiakObject, A, B).
|
|
|
|
reduce_set_union(List, A) ->
|
|
riak_kv_mapreduce:reduce_set_union(List, A).
|
|
|
|
mapred_modfun_input(Pipe, _Args, _Timeout) ->
|
|
riak_pipe:queue_work(Pipe, {{<<"hello">>, <<"world">>}, {struct, []}}),
|
|
riak_pipe:eoi(Pipe).
|
|
|
|
%% TODO: consider factoring down as an alternative to rt:pbc/2
|
|
pbc([{host, Host}, {port, Port}], Options) ->
|
|
Retries = 3,
|
|
%% NOTE: no rt:wait_for_service(Node, riak_kv), this connection timeout
|
|
%% pattern matches better how clients are expected to interact w/ Riak
|
|
pb_socket_start(Host, Port, Options, Retries, undefined).
|
|
pb_socket_start(_Host, _Port, _Options, _Retries = 0, Err) ->
|
|
Err;
|
|
pb_socket_start(Host, Port, Options, Retries, _Err) ->
|
|
case riakc_pb_socket:start(Host, Port, Options) of
|
|
Err0 = {error, {tcp, timeout}} ->
|
|
timer:sleep(100),
|
|
pb_socket_start(Host, Port, Options, Retries - 1, Err0);
|
|
Res = {ok, _Pid} -> Res;
|
|
Err1 = {error, _Reason} -> Err1
|
|
end.
|