Commit 99ea8bb4 authored by Georgios Bitzes's avatar Georgios Bitzes
Browse files

Implement hmac challenges as a better alternative to AUTH

parent 6f64e297
Pipeline #408327 passed with stages
in 27 minutes and 12 seconds
......@@ -35,7 +35,10 @@ struct cmdMapInit {
redis_cmd_map["debug"] = {RedisCommand::DEBUG, CommandType::CONTROL};
redis_cmd_map["monitor"] = {RedisCommand::MONITOR, CommandType::CONTROL};
redis_cmd_map["client_id"] = {RedisCommand::CLIENT_ID, CommandType::CONTROL};
redis_cmd_map["auth"] = {RedisCommand::AUTH, CommandType::AUTHENTICATION};
redis_cmd_map["hmac_auth_generate_challenge"] = {RedisCommand::HMAC_AUTH_GENERATE_CHALLENGE, CommandType::AUTHENTICATION};
redis_cmd_map["hmac_auth_validate_challenge"] = {RedisCommand::HMAC_AUTH_VALIDATE_CHALLENGE, CommandType::AUTHENTICATION};
redis_cmd_map["get"] = {RedisCommand::GET, CommandType::READ};
redis_cmd_map["exists"] = {RedisCommand::EXISTS, CommandType::READ};
......
......@@ -38,13 +38,16 @@ enum class RedisCommand {
FLUSHALL,
AUTH,
HMAC_AUTH_GENERATE_CHALLENGE,
HMAC_AUTH_VALIDATE_CHALLENGE,
GET,
SET,
EXISTS,
DEL,
KEYS,
SCAN,
AUTH,
HGET,
HSET,
......
......@@ -29,6 +29,7 @@
#include "BufferedWriter.hh"
#include "Formatter.hh"
#include "redis/MultiHandler.hh"
#include "redis/Authenticator.hh"
#include <queue>
#define OUTPUT_BUFFER_SIZE (16*1024)
......@@ -105,6 +106,7 @@ private:
//------------------------------------------------------------------------------
class Dispatcher; class InFlightTracker;
class RedisEncodedResponse;
class Authenticator;
class Connection {
public:
......@@ -139,6 +141,7 @@ public:
bool raftStaleReads = false;
bool raftAuthorization = false;
bool authorization = false;
std::unique_ptr<Authenticator> authenticator;
LinkStatus processRequests(Dispatcher *dispatcher, const InFlightTracker &tracker);
void setResponseBuffering(bool value);
......
......@@ -34,7 +34,7 @@ AuthenticationDispatcher::AuthenticationDispatcher(const std::string &secr)
}
RedisEncodedResponse AuthenticationDispatcher::dispatch(const RedisRequest &req, bool &authorized) {
RedisEncodedResponse AuthenticationDispatcher::dispatch(const RedisRequest &req, bool &authorized, std::unique_ptr<Authenticator> &authenticator) {
authorized = secret.empty();
switch(req.getCommand()) {
......@@ -51,6 +51,36 @@ RedisEncodedResponse AuthenticationDispatcher::dispatch(const RedisRequest &req,
authorized = true;
return Formatter::ok();
}
case RedisCommand::HMAC_AUTH_GENERATE_CHALLENGE: {
if(req.size() != 2u) return Formatter::errArgs(req[0]);
if(secret.empty()) return Formatter::err("no password is set");
if(req[1].size() != 64) return Formatter::err("exactly 64 random bytes must be provided");
authenticator.reset(new Authenticator(secret));
return Formatter::string(authenticator->generateChallenge(req[1]));
}
case RedisCommand::HMAC_AUTH_VALIDATE_CHALLENGE: {
if(req.size() != 2u) return Formatter::errArgs(req[0]);
if(secret.empty()) return Formatter::err("no password is set");
if(!authenticator) return Formatter::err("no challenge is in progress");
Authenticator::ValidationStatus validationStatus = authenticator->validateSignature(req[1]);
authenticator.reset();
if(validationStatus == Authenticator::ValidationStatus::kInvalidSignature) {
return Formatter::err("invalid signature");
}
if(validationStatus == Authenticator::ValidationStatus::kDeadlinePassed) {
return Formatter::err("deadline passed");
}
qdb_assert(validationStatus == Authenticator::ValidationStatus::kOk);
authorized = true;
return Formatter::ok();
}
default: {
qdb_throw("internal dispatching error for command " << req.toPrintableString());
}
......@@ -58,5 +88,5 @@ RedisEncodedResponse AuthenticationDispatcher::dispatch(const RedisRequest &req,
}
LinkStatus AuthenticationDispatcher::dispatch(Connection *conn, RedisRequest &req) {
return conn->raw(dispatch(req, conn->authorization));
return conn->raw(dispatch(req, conn->authorization, conn->authenticator));
}
......@@ -35,7 +35,7 @@ public:
AuthenticationDispatcher(const std::string &secret);
virtual LinkStatus dispatch(Connection *conn, RedisRequest &req) override final;
RedisEncodedResponse dispatch(const RedisRequest &req, bool &authorized);
RedisEncodedResponse dispatch(const RedisRequest &req, bool &authorized, std::unique_ptr<Authenticator> &authenticator);
private:
std::string secret;
......
......@@ -23,10 +23,13 @@
#include "utils/FileUtils.hh"
#include "utils/Macros.hh"
#include "utils/Random.hh"
#include "auth/AuthenticationDispatcher.hh"
#include "test-utils.hh"
#include <gtest/gtest.h>
#include "qclient/QClient.hh"
using namespace qclient;
using namespace quarkdb;
TEST(FilePermissionChecking, BasicSanity) {
......@@ -70,12 +73,19 @@ TEST(ReadPasswordFile, BasicSanity) {
TEST(AuthenticationDispatcher, NoPassword) {
AuthenticationDispatcher dispatcher("");
std::unique_ptr<Authenticator> unused;
bool authorized = false;
ASSERT_EQ(Formatter::errArgs("AUTH"), dispatcher.dispatch(make_req("AUTH"), authorized));
ASSERT_EQ(Formatter::errArgs("AUTH"), dispatcher.dispatch(make_req("AUTH"), authorized, unused));
ASSERT_TRUE(authorized);
ASSERT_EQ(Formatter::err("Client sent AUTH, but no password is set").val, dispatcher.dispatch(make_req("AUTH", "test"), authorized).val);
ASSERT_EQ(Formatter::err("Client sent AUTH, but no password is set").val, dispatcher.dispatch(make_req("AUTH", "test"), authorized, unused).val);
ASSERT_TRUE(authorized);
ASSERT_EQ(Formatter::err("no password is set"), dispatcher.dispatch(make_req("HMAC-AUTH-GENERATE-CHALLENGE", generateSecureRandomBytes(64)), authorized, unused));
ASSERT_TRUE(authorized);
ASSERT_EQ(Formatter::err("no password is set"), dispatcher.dispatch(make_req("HMAC-AUTH-VALIDATE-CHALLENGE", generateSecureRandomBytes(64)), authorized, unused));
ASSERT_TRUE(authorized);
}
......@@ -85,15 +95,53 @@ TEST(AuthenticationDispatcher, TooSmallPassword) {
TEST(AuthenticationDispatcher, AuthBasicSanity) {
AuthenticationDispatcher dispatcher("hunter2_hunter2_hunter2_hunter2_hunter2");
std::unique_ptr<Authenticator> unused;
bool authorized = false;
ASSERT_EQ(Formatter::errArgs("AUTH"), dispatcher.dispatch(make_req("AUTH"), authorized, unused));
ASSERT_FALSE(authorized);
ASSERT_EQ(Formatter::err("invalid password"), dispatcher.dispatch(make_req("AUTH", "hunter3"), authorized, unused ));
ASSERT_FALSE(authorized);
ASSERT_EQ(Formatter::ok(), dispatcher.dispatch(make_req("AUTH", "hunter2_hunter2_hunter2_hunter2_hunter2"), authorized, unused ));
ASSERT_TRUE(authorized);
}
TEST(AuthenticationDispatcher, ChallengesBasicSanity) {
std::string secretKey = "hunter2_hunter2_hunter2_hunter2_hunter2";
AuthenticationDispatcher dispatcher(secretKey);
std::unique_ptr<Authenticator> authenticator1, authenticator2;
bool authorized = false;
ASSERT_EQ(Formatter::errArgs("AUTH"), dispatcher.dispatch(make_req("AUTH"), authorized));
ASSERT_EQ(Formatter::errArgs("HMAC-AUTH-GENERATE-CHALLENGE"), dispatcher.dispatch(make_req("HMAC-AUTH-GENERATE-CHALLENGE"), authorized, authenticator1));
ASSERT_FALSE(authorized);
ASSERT_EQ(Formatter::err("no challenge is in progress"), dispatcher.dispatch(make_req("HMAC-AUTH-VALIDATE-CHALLENGE", "asdf"), authorized, authenticator1));
ASSERT_FALSE(authorized);
ASSERT_EQ(Formatter::err("exactly 64 random bytes must be provided").val, dispatcher.dispatch(make_req("HMAC-AUTH-GENERATE-CHALLENGE", "1234"), authorized, authenticator1).val);
ASSERT_FALSE(authorized);
ASSERT_EQ(Formatter::err("invalid password"), dispatcher.dispatch(make_req("AUTH", "hunter3"), authorized ));
RedisEncodedResponse resp = dispatcher.dispatch(make_req("HMAC-AUTH-GENERATE-CHALLENGE", generateSecureRandomBytes(64)), authorized, authenticator1);
ASSERT_FALSE(authorized);
ASSERT_EQ(Formatter::ok(), dispatcher.dispatch(make_req("AUTH", "hunter2_hunter2_hunter2_hunter2_hunter2"), authorized ));
// parse..
redisReader* reader = redisReaderCreate();
redisReaderFeed(reader, resp.val.c_str(), resp.val.size());
void* reply = nullptr;
ASSERT_EQ(redisReaderGetReply(reader, &reply), REDIS_OK);
ASSERT_TRUE(reply != nullptr);
redisReplyPtr rr = redisReplyPtr(redisReplyPtr((redisReply*) reply, freeReplyObject));
ASSERT_EQ(rr->type, REDIS_REPLY_STRING);
std::string challengeString = std::string(rr->str, rr->len);
resp = dispatcher.dispatch(make_req("HMAC-AUTH-VALIDATE-CHALLENGE", Authenticator::generateSignature(challengeString, secretKey)), authorized, authenticator1);
ASSERT_EQ(Formatter::ok(), resp);
ASSERT_TRUE(authorized);
redisReaderFree(reader);
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment