Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/actions/load_conan/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ runs:

- name: Restore Cache
id: restore-cache
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: |
~/.conan/data
key: ${{ inputs.key_prefix }}-${{ hashFiles(steps.hash-key-primary.outputs.key, steps.hash-key-3rd.outputs.keys) }}
fail-on-cache-miss: ${{ inputs.fail_on_cache_miss }}

- name: Restore Testing Cache
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: |
~/.conan/data
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/store_conan/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ runs:
rm -rf ~/.conan/data/*/*/*/*/source

- name: Save Cache
uses: actions/cache/save@v3
uses: actions/cache/save@v4
with:
path: |
~/.conan/data
Expand Down
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class SISLConan(ConanFile):
name = "sisl"
version = "8.6.7"
version = "8.7.0"
homepage = "https://github.com/eBay/sisl"
description = "Library for fast data structures, utilities"
topics = ("ebay", "components", "core", "efficiency")
Expand Down
16 changes: 1 addition & 15 deletions include/sisl/auth_manager/auth_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,7 @@ class LRUCache;
* they were extracted from decoded token.
*/
struct CachedToken {
AuthVerifyStatus response_status;
std::string msg;
bool valid;
std::chrono::system_clock::time_point expires_at;

inline void set_invalid(AuthVerifyStatus code, const std::string& reason) {
valid = false;
response_status = code;
msg = reason;
}

inline void set_valid() {
valid = true;
response_status = AuthVerifyStatus::OK;
}
};

class AuthManager {
Expand All @@ -68,4 +54,4 @@ class AuthManager {
// key_id -> signing public key
mutable LRUCache< std::string, std::string > m_cached_keys;
};
} // namespace sisl
} // namespace sisl
6 changes: 2 additions & 4 deletions include/sisl/auth_manager/trf_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ class TrfClient {
private:
void validate_grant_path() const;
bool grant_path_exists() const { return std::filesystem::exists(SECURITY_DYNAMIC_CONFIG(trf_client->grant_path)); }
bool access_token_expired() const {
return (std::chrono::system_clock::now() >
m_expiry + std::chrono::seconds(SECURITY_DYNAMIC_CONFIG(auth_manager->leeway)));
}
// If leeway is set, this will force us to download token ahead of its expiry
bool access_token_expired() const { return (std::chrono::system_clock::now() > m_expiry); }
static bool get_file_contents(const std::string& file_name, std::string& contents);

private:
Expand Down
25 changes: 8 additions & 17 deletions include/sisl/metrics/histogram_buckets.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,17 @@ typedef std::vector< double > hist_bucket_boundaries_t;

/* For any new histogram buckets, define a name and its values here */
#define HIST_BKTS_TYPES \
X(DefaultBuckets, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300, 320, \
340, 360, 380, 400, 425, 450, 475, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, 1100, 1200, 1300, \
1400, 1500, 1600, 1700, 1800, 1900, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, \
8000, 8500, 9000, 9500, 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 21000, \
30000, 40000, 50000, 60000, 70000, 80000, 90000, 100000, 110000, 130000, 150000, 180000, 210000, 240000, 300000, \
360000, 450000, 540000, 800000, 1200000, 1800000, 2700000, 4000000) \
X(DefaultBuckets, 10, 40, 70, 100, 160, 220, 280, 340, 400, 475, 600, 750, 900, 1100, 1400, 1700, 2000, 3500, \
5000, 6500, 8000, 10000, 13000, 16000, 20000, 50000, 80000, 100000, 150000, 180000, 200000, 500000, 2000000, \
3000000, 4000000) \
\
X(ExponentialOfTwoBuckets, 1, exp2(1), exp2(2), exp2(3), exp2(4), exp2(5), exp2(6), exp2(7), exp2(8), exp2(9), \
exp2(10), exp2(11), exp2(12), exp2(13), exp2(14), exp2(15), exp2(16), exp2(17), exp2(18), exp2(19), exp2(20), \
exp2(21), exp2(22), exp2(23), exp2(24), exp2(25), exp2(26), exp2(27), exp2(28), exp2(29), exp2(30), exp2(31)) \
X(ExponentialOfTwoBuckets, 1, exp2(4), exp2(7), exp2(10), exp2(13), exp2(16), exp2(19), exp2(22), exp2(25), \
exp2(28), exp2(31)) \
\
X(LinearUpto64Buckets, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, \
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, \
53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64) \
X(LinearUpto64Buckets, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64) \
\
X(LinearUpto128Buckets, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, \
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, \
53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, \
81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, \
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128) \
X(LinearUpto128Buckets, 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80, 84, 88, \
92, 96, 100, 104, 108, 112, 116, 120, 124, 128) \
\
X(SingleValueBucket, std::numeric_limits< double >::max())

Expand Down
35 changes: 13 additions & 22 deletions src/auth_manager/auth_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,12 @@ AuthVerifyStatus AuthManager::verify(const std::string& token, std::string& msg)
// if we have it in cache, just use it to make the decision
auto const token_hash = md5_sum(token);
if (auto const ct = m_cached_tokens.get(token_hash); ct) {
if (ct->valid) {
auto now = std::chrono::system_clock::now();
if (now > ct->expires_at + std::chrono::seconds(SECURITY_DYNAMIC_CONFIG(auth_manager->leeway))) {
m_cached_tokens.put(token_hash,
CachedToken{AuthVerifyStatus::UNAUTH, "token expired", false, ct->expires_at});
}
auto now = std::chrono::system_clock::now();
if (now > ct->expires_at + std::chrono::seconds(SECURITY_DYNAMIC_CONFIG(auth_manager->expiry_leeway_secs))) {
msg = "token expired";
return AuthVerifyStatus::UNAUTH;
}
msg = ct->msg;
return ct->response_status;
return AuthVerifyStatus::OK;
}

// not found in cache
Expand All @@ -63,31 +60,23 @@ AuthVerifyStatus AuthManager::verify(const std::string& token, std::string& msg)
verify_decoded(decoded);
app_name = get_app(decoded);
cached_token.expires_at = decoded.get_expires_at();
cached_token.set_valid();
} catch (const incomplete_verification_error& e) {
} catch (const std::exception& e) {
// verification incomplete, the token validity is not determined, shouldn't
// cache
msg = e.what();
return AuthVerifyStatus::UNAUTH;
} catch (const std::exception& e) {
cached_token.set_invalid(AuthVerifyStatus::UNAUTH, e.what());
m_cached_tokens.put(token_hash, cached_token);
msg = cached_token.msg;
return cached_token.response_status;
}

// check client application

if (SECURITY_DYNAMIC_CONFIG(auth_manager->auth_allowed_apps) != "all") {
if (SECURITY_DYNAMIC_CONFIG(auth_manager->auth_allowed_apps).find(app_name) == std::string::npos) {
cached_token.set_invalid(AuthVerifyStatus::FORBIDDEN,
fmt::format("application '{}' is not allowed to perform the request", app_name));
msg = fmt::format("application '{}' is not allowed to perform the request", app_name);
return AuthVerifyStatus::FORBIDDEN;
}
}

m_cached_tokens.put(token_hash, cached_token);
msg = cached_token.msg;
return cached_token.response_status;
return AuthVerifyStatus::OK;
}

void AuthManager::verify_decoded(const jwt::decoded_jwt& decoded) const {
Expand Down Expand Up @@ -125,7 +114,9 @@ void AuthManager::verify_decoded(const jwt::decoded_jwt& decoded) const {
const auto verifier{jwt::verify()
.with_issuer(SECURITY_DYNAMIC_CONFIG(auth_manager->issuer))
.allow_algorithm(jwt::algorithm::rs256(signing_key))
.expires_at_leeway(SECURITY_DYNAMIC_CONFIG(auth_manager->leeway))};
.expires_at_leeway(SECURITY_DYNAMIC_CONFIG(auth_manager->expiry_leeway_secs))
.issued_at_leeway(SECURITY_DYNAMIC_CONFIG(auth_manager->iat_leeway_secs))
.not_before_leeway(SECURITY_DYNAMIC_CONFIG(auth_manager->nbf_leeway_secs))};

// if verification fails, an instance of std::system_error subclass is thrown.
verifier.verify(decoded);
Expand Down Expand Up @@ -166,4 +157,4 @@ std::string AuthManager::get_app(const jwt::decoded_jwt& decoded) const {
const auto end{client_id.find_first_of(",", start)};
return client_id.substr(start, end - start);
}
} // namespace sisl
} // namespace sisl
8 changes: 7 additions & 1 deletion src/auth_manager/security_config.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ table AuthManager {
tf_token_url: string;

// leeway to the token expiration
leeway: uint32 = 0;
expiry_leeway_secs: uint32 = 0;

// leeway to the token issued_at
iat_leeway_secs: uint32 = 5;

// leeway to the token not_before
nbf_leeway_secs: uint32 = 5;

// ssl verification for the signing key download url
verify: bool = true;
Expand Down
67 changes: 59 additions & 8 deletions src/auth_manager/tests/AuthTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ class MockAuthManager : public AuthManager {
public:
using AuthManager::AuthManager;
MOCK_METHOD(std::string, download_key, (const std::string&), (const));
AuthVerifyStatus verify(const std::string& token) {
std::string msg;
return AuthManager::verify(token, msg);
}
std::string msg;
AuthVerifyStatus verify(const std::string& token) { return verify(token, msg); }
AuthVerifyStatus verify(const std::string& token, std::string& msg) { return AuthManager::verify(token, msg); }
};

class AuthTest : public ::testing::Test {
Expand All @@ -57,7 +56,6 @@ class AuthTest : public ::testing::Test {
SECURITY_SETTINGS_FACTORY().modifiable_settings([](auto& s) {
s.auth_manager->auth_allowed_apps = "app1, testapp, app2";
s.auth_manager->tf_token_url = "http://127.0.0.1";
s.auth_manager->leeway = 0;
s.auth_manager->issuer = "trustfabric";
});
SECURITY_SETTINGS_FACTORY().save();
Expand Down Expand Up @@ -140,6 +138,48 @@ TEST_F(AuthTest, reject_unauthorized_app) {
EXPECT_EQ(mock_auth_mgr->verify(token.sign_rs256()), AuthVerifyStatus::FORBIDDEN);
}

TEST_F(AuthTest, leeway_test) {
auto test_token = TestToken();
auto& trf_token = test_token.get_token();

// default leeway is 0 seconds for exp
trf_token.set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds(1));
// default leeway is 5 seconds for iat and nbf
trf_token.set_issued_at(std::chrono::system_clock::now() + std::chrono::seconds(4));
trf_token.set_not_before(std::chrono::system_clock::now() + std::chrono::seconds(4));
auto raw_token = test_token.sign_rs256();

EXPECT_CALL(*mock_auth_mgr, download_key(_)).Times(1).WillOnce(Return(rsa_pub_key));
EXPECT_EQ(mock_auth_mgr->verify(raw_token), AuthVerifyStatus::OK);

std::string unauth_msg;
// token expired
trf_token.set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds(1));
raw_token = test_token.sign_rs256();
EXPECT_CALL(*mock_auth_mgr, download_key(_)).Times(0);
EXPECT_EQ(mock_auth_mgr->verify(raw_token, unauth_msg), AuthVerifyStatus::UNAUTH);
EXPECT_EQ(unauth_msg, "token verification failed: token expired");

unauth_msg.clear();
// iat expired
trf_token.set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds(1));
trf_token.set_issued_at(std::chrono::system_clock::now() + std::chrono::seconds(6));
trf_token.set_key_id("new_key_id");
raw_token = test_token.sign_rs256();
EXPECT_CALL(*mock_auth_mgr, download_key(_)).Times(1).WillOnce(Return(rsa_pub_key));
EXPECT_EQ(mock_auth_mgr->verify(raw_token, unauth_msg), AuthVerifyStatus::UNAUTH);
EXPECT_EQ(unauth_msg, "token verification failed: token expired");

unauth_msg.clear();
// nbf expired
trf_token.set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds(1));
trf_token.set_not_before(std::chrono::system_clock::now() + std::chrono::seconds(6));
raw_token = test_token.sign_rs256();
EXPECT_CALL(*mock_auth_mgr, download_key(_)).Times(0);
EXPECT_EQ(mock_auth_mgr->verify(raw_token, unauth_msg), AuthVerifyStatus::UNAUTH);
EXPECT_EQ(unauth_msg, "token verification failed: token expired");
}

// Testing trf client
class MockTrfClient : public TrfClient {
public:
Expand Down Expand Up @@ -169,7 +209,7 @@ static void load_trf_settings() {
s.trf_client->grant_path = grant_path;
s.trf_client->server = "127.0.0.1:12346/token";
s.auth_manager->verify = false;
s.auth_manager->leeway = 30;
s.auth_manager->expiry_leeway_secs = 30;
});
SECURITY_SETTINGS_FACTORY().save();
}
Expand Down Expand Up @@ -200,7 +240,8 @@ TEST_F(AuthTest, trf_allow_valid_token) {
const auto raw_token{TestToken().sign_rs256()};
// mock_trf_client is expected to be called twice
// 1. First time when access_token is empty
// 2. When token is set to be expired
// 2. When expiry - leeway is less than current time
// 3. When access_token is expired
EXPECT_CALL(mock_trf_client, request_with_grant_token()).Times(2);
ON_CALL(mock_trf_client, request_with_grant_token())
.WillByDefault(
Expand All @@ -212,6 +253,8 @@ TEST_F(AuthTest, trf_allow_valid_token) {
// use the acces_token saved from the previous call
EXPECT_CALL(*mock_auth_mgr, download_key(_)).Times(0);
EXPECT_EQ(mock_auth_mgr->verify(mock_trf_client.get_token()), AuthVerifyStatus::OK);
mock_trf_client.set_expiry(std::chrono::system_clock::now() + std::chrono::seconds(25));
EXPECT_EQ(mock_auth_mgr->verify(mock_trf_client.get_token()), AuthVerifyStatus::OK);

// set token to be expired invoking request_with_grant_token
mock_trf_client.set_expiry(std::chrono::system_clock::now() - std::chrono::seconds(100));
Expand All @@ -222,9 +265,11 @@ TEST_F(AuthTest, trf_allow_valid_token) {
static const std::string trf_token_server_ip{"127.0.0.1"};
static const uint32_t trf_token_server_port{12346};
static std::string token_response;
static uint32_t token_expiry{4000};
static void set_token_response(const std::string& raw_token) {
token_response = "{\"access_token\":\"" + raw_token +
"\",\"token_type\":\"Bearer\",\"expires_in\":2000,\"refresh_token\":\"dummy_refresh_token\"}\n";
"\",\"token_type\":\"Bearer\",\"expires_in\":" + std::to_string(token_expiry) +
",\"refresh_token\":\"dummy_refresh_token\"}\n";
}

class TokenApiImpl : public TokenApi {
Expand Down Expand Up @@ -294,6 +339,9 @@ TEST_F(TrfClientTest, request_with_grant_token) {
mock_trf_client.__request_with_grant_token();
}));
mock_trf_client.get_token();
auto time_to_expiry = std::chrono::duration_cast< std::chrono::seconds >(mock_trf_client.get_expiry() -
std::chrono::system_clock::now());
EXPECT_LT(time_to_expiry, std::chrono::seconds{token_expiry} / 2);
EXPECT_EQ(raw_token, mock_trf_client.get_access_token());
EXPECT_EQ("Bearer", mock_trf_client.get_token_type());
}
Expand All @@ -309,6 +357,9 @@ TEST(TrfClientParseTest, parse_token) {
EXPECT_EQ(raw_token, mock_trf_client.get_access_token());
EXPECT_EQ("Bearer", mock_trf_client.get_token_type());
EXPECT_TRUE(mock_trf_client.get_expiry() > std::chrono::system_clock::now());
auto time_to_expiry = std::chrono::duration_cast< std::chrono::seconds >(mock_trf_client.get_expiry() -
std::chrono::system_clock::now());
EXPECT_LT(time_to_expiry, std::chrono::seconds{token_expiry} / 2);
remove_grant_path();
}
} // namespace sisl::testing
Expand Down
9 changes: 7 additions & 2 deletions src/auth_manager/trf_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ bool TrfClient::get_file_contents(const std::string& file_path, std::string& con
return false;
}

static std::chrono::seconds get_expiry(const std::chrono::seconds& token_expiry) {
// refresh after half the expiry time
return token_expiry / 2;
}

void TrfClient::request_with_grant_token() {
std::string grant_token;
if (!get_file_contents(SECURITY_DYNAMIC_CONFIG(trf_client->grant_path), grant_token)) {
Expand Down Expand Up @@ -72,7 +77,7 @@ void TrfClient::request_with_grant_token() {

try {
const nlohmann::json resp_json = nlohmann::json::parse(resp.text);
m_expiry = std::chrono::system_clock::now() + std::chrono::seconds(resp_json["expires_in"]);
m_expiry = std::chrono::system_clock::now() + get_expiry(std::chrono::seconds(resp_json["expires_in"]));
m_access_token = resp_json["access_token"];
m_token_type = resp_json["token_type"];
} catch ([[maybe_unused]] const nlohmann::detail::exception& e) {
Expand All @@ -91,7 +96,7 @@ void TrfClient::parse_response(const std::string& resp) {
if (m_token_type = get_quoted_string(resp, token2); m_access_token.empty()) { return; }
auto expiry_str = get_string(resp, token3);
if (expiry_str.empty()) { return; }
m_expiry = std::chrono::system_clock::now() + std::chrono::seconds(std::stol(expiry_str));
m_expiry = std::chrono::system_clock::now() + get_expiry(std::chrono::seconds(std::stol(expiry_str)));
} catch (const std::exception& e) { LOGERROR("failed to parse response: {}, what: {}", resp, e.what()); }
}

Expand Down
Loading