riak_test/tests/pb_security.erl

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.