-
Notifications
You must be signed in to change notification settings - Fork 7.2k
[core] periodically reload Ray service account tokens #60778
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
base: master
Are you sure you want to change the base?
Changes from all commits
290ba21
e88e1e2
8b941ba
c0cad6a
8d83ebf
c855f26
b729301
7b8caa3
8e8d391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,11 @@ | |
| #include <memory> | ||
| #include <string> | ||
| #include <utility> | ||
| #include <vector> | ||
|
|
||
| #include "absl/strings/escaping.h" | ||
| #include "absl/strings/str_split.h" | ||
| #include "nlohmann/json.hpp" | ||
| #include "ray/rpc/authentication/authentication_mode.h" | ||
| #include "ray/rpc/authentication/k8s_constants.h" | ||
| #include "ray/util/logging.h" | ||
|
|
@@ -44,6 +48,36 @@ constexpr const char *kNoTokenErrorMessage = | |
| "or store the token in any file and set RAY_AUTH_TOKEN_PATH to point to it, " | ||
| "or set the RAY_AUTH_TOKEN environment variable."; | ||
|
|
||
| constexpr int kRaySATokenDefaultTTLSeconds = 600; | ||
| constexpr int kRaySATokenExpirationBufferSeconds = 300; | ||
|
|
||
| std::optional<std::chrono::system_clock::time_point> | ||
| AuthenticationTokenLoader::GetTokenExpiration(const std::string &token) { | ||
| std::vector<std::string> parts = absl::StrSplit(token, '.'); | ||
| if (parts.size() != 3) { | ||
| RAY_LOG(WARNING) << "Invalid JWT token format."; | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| std::string payload; | ||
| if (!absl::WebSafeBase64Unescape(parts[1], &payload)) { | ||
| RAY_LOG(WARNING) << "Unable to base64 decode JWT token."; | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| try { | ||
| auto json = nlohmann::json::parse(payload); | ||
| if (json.contains("exp") && json["exp"].is_number()) { | ||
| int64_t exp = json["exp"].get<int64_t>(); | ||
| return std::chrono::system_clock::from_time_t(exp); | ||
| } | ||
| } catch (...) { | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| return std::nullopt; | ||
| } | ||
|
|
||
| AuthenticationTokenLoader &AuthenticationTokenLoader::instance() { | ||
| static AuthenticationTokenLoader instance; | ||
| return instance; | ||
|
|
@@ -53,6 +87,16 @@ std::shared_ptr<const AuthenticationToken> AuthenticationTokenLoader::GetToken( | |
| bool ignore_auth_mode) { | ||
| absl::MutexLock lock(&token_mutex_); | ||
|
|
||
| // If k8s token auth is enabled, revoke cached token as Kubelet | ||
| // will expire and auto rotate new service account tokens every hour by default. | ||
| // Use 5 minutes as a default as users can configure the expiration time. | ||
| if (IsK8sTokenAuthEnabled()) { | ||
| if (cached_token_ && | ||
| std::chrono::system_clock::now() >= cached_token_expiration_time_) { | ||
| cached_token_ = nullptr; | ||
| } | ||
| } | ||
|
|
||
| // If already loaded, return cached value | ||
| if (cached_token_) { | ||
| return cached_token_; | ||
|
|
@@ -76,6 +120,17 @@ std::shared_ptr<const AuthenticationToken> AuthenticationTokenLoader::GetToken( | |
| // Cache and return the loaded token | ||
| if (has_token) { | ||
| cached_token_ = std::make_shared<const AuthenticationToken>(std::move(*result.token)); | ||
| if (IsK8sTokenAuthEnabled()) { | ||
| auto exp = GetTokenExpiration(cached_token_->GetRawValue()); | ||
| if (exp) { | ||
| cached_token_expiration_time_ = | ||
| *exp - std::chrono::seconds(kRaySATokenExpirationBufferSeconds); | ||
| } else { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Token thrashing when remaining lifetime under bufferHigh Severity When a K8s token has less than 5 minutes remaining before expiration,
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This won't be a problem as Kubernetes enforces a minimum expiration time of 10 minutes |
||
| cached_token_expiration_time_ = | ||
| std::chrono::system_clock::now() + | ||
| std::chrono::seconds(kRaySATokenDefaultTTLSeconds); | ||
| } | ||
| } | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| return cached_token_; | ||
| } | ||
|
|
@@ -84,6 +139,16 @@ TokenLoadResult AuthenticationTokenLoader::TryLoadToken(bool ignore_auth_mode) { | |
| absl::MutexLock lock(&token_mutex_); | ||
| TokenLoadResult result; | ||
|
|
||
| // If k8s token auth is enabled, revoke cached token as Kubelet | ||
| // will expire and auto rotate new service account tokens every hour by default. | ||
| // Use 5 minutes as a default as users can configure the expiration time. | ||
| if (IsK8sTokenAuthEnabled()) { | ||
| if (cached_token_ && | ||
| std::chrono::system_clock::now() >= cached_token_expiration_time_) { | ||
| cached_token_ = nullptr; | ||
| } | ||
| } | ||
|
|
||
| // If already loaded, return cached value | ||
| if (cached_token_) { | ||
| result.token = *cached_token_; // Copy from shared_ptr | ||
|
|
@@ -112,6 +177,16 @@ TokenLoadResult AuthenticationTokenLoader::TryLoadToken(bool ignore_auth_mode) { | |
| } | ||
| // Cache and return success | ||
| cached_token_ = std::make_shared<const AuthenticationToken>(std::move(*result.token)); | ||
| if (IsK8sTokenAuthEnabled()) { | ||
| auto exp = GetTokenExpiration(cached_token_->GetRawValue()); | ||
| if (exp) { | ||
| cached_token_expiration_time_ = | ||
| *exp - std::chrono::seconds(kRaySATokenExpirationBufferSeconds); | ||
| } else { | ||
| cached_token_expiration_time_ = std::chrono::system_clock::now() + | ||
| std::chrono::seconds(kRaySATokenDefaultTTLSeconds); | ||
| } | ||
| } | ||
| result.token = *cached_token_; // Copy back for return | ||
| return result; | ||
| } | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.