Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EQC tests and a simple eunit test to run them. #11

Merged
merged 7 commits into from Feb 28, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 14 additions & 14 deletions src/riak_control_security.erl
Expand Up @@ -87,35 +87,35 @@ https_redirect (RD,Ctx) ->
%% - `none' :: No authentication.
%%
enforce_auth(RD, Ctx) ->
case wrq:get_req_header("authorization", RD) of
"Basic "++Base64 ->
enforce_basic_auth(RD, Ctx, Base64);
_ ->
{?ADMIN_AUTH_HEAD, RD, Ctx}
case app_helper:get_env(riak_control,auth,none) of
none ->
{true, RD, Ctx};
Auth ->
case wrq:get_req_header("authorization", RD) of
"Basic "++Base64 ->
enforce_basic_auth(RD, Ctx, Base64, Auth);
_ ->
{?ADMIN_AUTH_HEAD, RD, Ctx}
end
end.

enforce_basic_auth(RD, Ctx, Base64) ->
enforce_basic_auth(RD, Ctx, Base64, Auth) ->
Str = base64:mime_decode_to_string(Base64),
case string:tokens(Str, ":") of
[User, Pass] ->
enforce_user_pass(RD, Ctx, User, Pass);
enforce_user_pass(RD, Ctx, User, Pass, Auth);
_ ->
{?ADMIN_AUTH_HEAD, RD, Ctx}
end.

enforce_user_pass(RD, Ctx, User, Pass) ->
case valid_userpass(User, Pass) of
enforce_user_pass(RD, Ctx, User, Pass, Auth) ->
case valid_userpass(User, Pass, Auth) of
true ->
{true, RD, Ctx};
false ->
{?ADMIN_AUTH_HEAD, RD, Ctx}
end.

%% validate the username and password
valid_userpass(User, Pass) ->
Auth=app_helper:get_env(riak_control, auth),
valid_userpass(User, Pass, Auth).

%% validate the username and password with the given auth style
valid_userpass(_User, _Pass, none) ->
true;
Expand Down
24 changes: 24 additions & 0 deletions src/riak_control_session.erl
Expand Up @@ -21,6 +21,10 @@
-module(riak_control_session).
-behavior(gen_server).

-ifdef (TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.

%% API
-export([start_link/0,
get_version/0,
Expand Down Expand Up @@ -321,3 +325,23 @@ get_vnode_status (Service,Ring,Index) ->
[] -> {Service,undefined}
end.


%% ---------------------------------------------------------------------------
%% EUNIT tests

-ifdef(TEST).

%% we want to see quickcheck output
-define(QC_OUT(P),
eqc:on_output(fun(Str, Args) ->
io:format(user, Str, Args)
end,
P)).

quickcheck_test_ () ->
{timeout, 120,
fun() ->
?assert(eqc:quickcheck(?QC_OUT(eqc_routes:prop_routes())))
end}.

-endif.
17 changes: 17 additions & 0 deletions test/cert.pem
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICvTCCAiYCCQDgxT3HogRJ/TANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC
VVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxEjAQBgNVBAcTCUNhbWJyaWRnZTEf
MB0GA1UEChMWQmFzaG8gVGVjaG5vbG9naWVzIEluYzENMAsGA1UECxMEUmlhazEY
MBYGA1UEAxMPSmVmZnJleSBNYXNzdW5nMR0wGwYJKoZIhvcNAQkBFg5qZWZmQGJh
c2hvLmNvbTAeFw0xMTEwMzExNjQ3NTNaFw0yMTEwMjgxNjQ3NTNaMIGiMQswCQYD
VQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czESMBAGA1UEBxMJQ2FtYnJp
ZGdlMR8wHQYDVQQKExZCYXNobyBUZWNobm9sb2dpZXMgSW5jMQ0wCwYDVQQLEwRS
aWFrMRgwFgYDVQQDEw9KZWZmcmV5IE1hc3N1bmcxHTAbBgkqhkiG9w0BCQEWDmpl
ZmZAYmFzaG8uY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkys/CB8Ce
fO19JsFiL2K5pODMWFmXxfQgvARB2rIvoJ4R4mNKI639xRbR+gCPreJvZRw8trgD
8sARFv8J1SlqqYRDN8zfMlvolXh6Atujou/LwjUpTA3pMe9lZrWU1+JZOMAk79lz
O/1etfR12By0SqNfDgUMIIZST7i3Fw2IkwIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
ABTOXMBYoEn0biwqaDcZzInZvZHupFkmkOABumgJ5xVDQ/9LIzy9mfg0Ko+JERlM
0w0dliu2sfFoXLps9EohjIzrU1B3CwSNvNqPmcj9V4k0iXrsvDfbG9eJ9nYaUY0Y
L5I9/KAOIf3fEmnFbjtmyiLVhrM6kBB3fvoAVQfwL6cZ
-----END CERTIFICATE-----
243 changes: 243 additions & 0 deletions test/eqc_routes.erl
@@ -0,0 +1,243 @@
-module(eqc_routes).
-compile(export_all).

-ifdef(EQC).

%% EQC headers
-include_lib("eqc/include/eqc.hrl").
-include_lib("eqc/include/eqc_statem.hrl").

%% command generators
-define(CLUSTER_LIST,{call,?MODULE,cluster_list,[]}).
-define(PARTITION_LIST,{call,?MODULE,partition_list,[]}).
-define(HTTP_CLUSTER_LIST(U),{call,?MODULE,http_cluster_list,[U]}).
-define(HTTP_PARTITION_LIST(U,F),{call,?MODULE,http_partition_list,[U,F]}).


%% ---------------------------------------------------------------------------
%% statem state

-record(state,{auth,ring_size}).


%% ---------------------------------------------------------------------------
%% properties

prop_routes () ->
?FORALL(Auth,g_auth(),
collect(Auth,
begin
setup(Auth),

%% start application dependencies
riak_core_util:start_app_deps(riak_core),
riak_core_util:start_app_deps(riak_control),

%% can now start riak control
application:start(riak_control),

%% ensure riak_control is up and running
riak_core:wait_for_application(riak_control),
{ok,Size}=application:get_env(riak_core,ring_creation_size),

%% now run the test with these auth settings
?ALWAYS(10,
?FORALL(Cmds,commands(?MODULE,#state{auth=Auth,
ring_size=Size
}),
begin
{H,S,Res}=run_commands(?MODULE,Cmds),

%% results
?WHENFAIL(
io:format("H: ~p\nS: ~p\nR: ~p\n",
[H,S,Res]),
Res==ok)
end))
end)).


%% ---------------------------------------------------------------------------
%% statem behavior

initial_state () ->
#state{}.

command (_State) ->
oneof([?CLUSTER_LIST,
?PARTITION_LIST,
?LET(User,g_userpass(),
oneof([?HTTP_CLUSTER_LIST(User),
?LET(Filter,g_qs(),
?HTTP_PARTITION_LIST(User,Filter))
]))
]).

precondition (_State,_Gen) ->
true.

postcondition (State,{call,_,http_cluster_list,[User]},Res) ->
http_validate(State,Res,User) andalso cluster_validate(State,Res);
postcondition (State,{call,_,http_partition_list,[User,Filter]},Res) ->
http_validate(State,Res,User) andalso filter_validate(State,Res,Filter);
postcondition (_State,_Gen,_Res) ->
true.

next_state (State,_V,_Gen) ->
State.


%% ---------------------------------------------------------------------------
%% negative testing common post-conditions

http_validate (#state{auth=userlist},{Code,_,_},{user,pass}) ->
Code < 300;
http_validate (#state{auth=userlist},{Code,_,_},_) ->
Code >= 300;
http_validate (#state{auth=none},{Code,_,_},_) ->
Code < 300.


cluster_validate (_State,{Code,"",_Url}) ->
not (Code < 300);
cluster_validate (_State,{_Code,Body,_Url}) ->
[{struct,Node}]=mochijson2:decode(Body),
{_,Name}=lists:keyfind(<<"name">>,1,Node),
{_,Me}=lists:keyfind(<<"me">>,1,Node),
{_,Reachable}=lists:keyfind(<<"reachable">>,1,Node),
binary_to_atom(Name,utf8) == node() andalso Me andalso Reachable.


filter_validate (_State,{Code,"",_Url},_Filter) ->
not (Code < 300);
filter_validate (#state{ring_size=Size},{_Code,Body,_Url},Filter) ->
{struct,Json}=mochijson2:decode(Body),
{_,Pages}=lists:keyfind(<<"pages">>,1,Json),
{_,Page}=lists:keyfind(<<"page">>,1,Json),
{_,Contents}=lists:keyfind(<<"contents">>,1,Json),
filter_validate_n(Size,Pages,Page,Contents,Filter) andalso
%% TODO: more validation here
true.


filter_validate_n (Size,Pages,_Page,Contents,Filter) ->
ContentSize=erlang:length(Contents),
N=case lists:keyfind(n,1,Filter) of
false -> Size div Pages;
{_,X} -> X
end,
%% less than is okay for now, we might have returned the last page
ContentSize =< max(N,16).


%% ---------------------------------------------------------------------------
%% eqc generators

%% valid authorization modes for app.config
g_auth () ->
oneof([userlist,none]).

%% username and password pairs (none = don't pass user/pass)
g_userpass () ->
oneof([{user,pass},
{user,bad_password},
{bad_user,pass},
{bad_user,bad_password},
none % not passing auth data
]).

%% query string generator
g_qs () ->
?LET(Filter,g_filter(),
?LET(Page,g_page(),
Filter ++ Page)).

%% valid and invalid query string filters for ring page
g_filter () ->
oneof([[{filter,node}],
[{filter,node},{q,g_node()}],
[{q,g_node()}],
[{filter,handoffs}],
[{filter,fallbacks}],
[]
]).

%% pagination data for ring page
g_page () ->
oneof([[{n,int()},{p,int()}],
[{n,int()}],
[{p,int()}],
[]
]).

%% this node or a dummy node
g_node () ->
oneof([node(),'dummy@nohost']).


%% ---------------------------------------------------------------------------
%% private

f (Fmt,Args) ->
lists:flatten(io_lib:format(Fmt,Args)).

set (K,V) ->
application:set_env(riak_core,K,V).

setup (Auth) ->
application:load(riak_core),
application:load(riak_control),

%% hide sasl output
application:set_env(sasl,sasl_error_logger,false),

%% local the pem files in the test folder
PrivDir=code:priv_dir(riak_control),
TestDir=filename:join([PrivDir,"..","test"]),
CertFile=filename:join([TestDir,"cert.pem"]),
KeyFile=filename:join([TestDir,"key.pem"]),

%% set env values for riak core
set(http, [{"127.0.0.1",18098}]),
set(https, [{"127.0.0.1",18069}]),
set(ssl, [{certfile, CertFile},
{keyfile, KeyFile}
]),

%% finally, enable riak control
application:set_env(riak_control,userlist,[{"user","pass"}]),
application:set_env(riak_control,enabled,true),
application:set_env(riak_control,admin,true),
application:set_env(riak_control,auth,Auth).

cluster_list () ->
{ok,_V,Nodes}=riak_control_session:get_nodes(),
Nodes.

partition_list () ->
{ok,_V,Ring}=riak_control_session:get_ring(),
Ring.

http_cluster_list (User) ->
http_request(User,"/admin/cluster/list",[]).

http_partition_list (User,Filter) ->
http_request(User,"/admin/ring/partitions",Filter).

http_request (User,Route,Query) ->
Url=request_url(User,Route,Query),
case httpc:request(get,{Url,[]},[],[]) of
{ok,{{_HTTP,Code,_OK},_Headers,Body}} -> {Code,Body,Url};
{ok,{{_HTTP,Code,_OK},Body}} -> {Code,Body,Url}
end.

request_url (User,Route,Query) ->
f("https://~slocalhost:18069~s?~s",[auth_user(User),Route,qs(Query)]).

auth_user ({User,Pass}) -> f("~s:~s@",[User,Pass]);
auth_user (_) -> "".

qs ([{K,V}|QS]) -> f("~s=~w&~s", [K,V,qs(QS)]);
qs ([]) -> "".

-endif. % EQC
15 changes: 15 additions & 0 deletions test/key.pem
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCkys/CB8CefO19JsFiL2K5pODMWFmXxfQgvARB2rIvoJ4R4mNK
I639xRbR+gCPreJvZRw8trgD8sARFv8J1SlqqYRDN8zfMlvolXh6Atujou/LwjUp
TA3pMe9lZrWU1+JZOMAk79lzO/1etfR12By0SqNfDgUMIIZST7i3Fw2IkwIDAQAB
AoGANAW2eolZ/G5xxo2ChQ1yfCqZsMi/V9NtExxnt6Zjk/d/jyPJtnD3D2K1ponm
vXTmQ8ZGmMAR7WUnzv1Ue/UoAntcyXwKAm+T+2IUJiir/qzYKLSn8FJ3wA+OWYKs
1nSryi54IuNenKUslxMPDPk/0bM6nZS2AvbNPYhX7a8evXkCQQDNdsvDO3Ofn0pJ
+3bMLH5Ch/adrJ0TfF1H8n9pxiuq813ppffXsyzv3haTbGHOtEM5tILYbHmO16h1
vY+hhoHHAkEAzVMVRaDefjp1qKfoyRm9ySa3GgH71t1dvm1jGTRxNk9M8pekLDz+
GWyTzffM2/+8Xz4RFzLjqAoAjzBGMeAC1QJAANiycjV2fnvbhH6CuMieJIwG2hNx
+jiS8c7v83GbkHK8OlAyuzLDxqE1mpnhtUZM2JoDx/x6a7o7uXB0fQfe1QJBAKxi
d/aYhJS4IjaymqfUm9m5TntgdP9FpcIOdugfdnmhhLochLK7lp7j4QhJZ07B3Hae
Vp0Clc5sb2HIpvaS2+0CQEV5NxPjavmlCQksQvU2OAQvTW3Sm9lahTl4XvdVxfj0
G9sZ2erg7MIo2LF4V6FM6Hbfoj/FhAMOXlXUoUGs1uI=
-----END RSA PRIVATE KEY-----