1use crate::client::TimeoutSettings;
2use crate::errors::{YdbError, YdbResult};
3use crate::grpc_connection_manager::GrpcConnectionManager;
4use crate::grpc_wrapper::raw_auth_service::client::RawAuthClient;
5use crate::grpc_wrapper::raw_auth_service::login::RawLoginRequest;
6use crate::grpc_wrapper::runtime_interceptors::MultiInterceptor;
7use crate::load_balancer::{SharedLoadBalancer, StaticLoadBalancer};
8use crate::pub_traits::{Credentials, TokenInfo};
9use chrono::DateTime;
10use http::Uri;
11
12use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
13use secrecy::{ExposeSecret, SecretString};
14use serde::{Deserialize, Serialize};
15use std::env;
16use std::fmt::Debug;
17use std::ops::Add;
18use std::process::Command;
19use std::sync::{Arc, Mutex};
20use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
21use tracing::{debug, trace};
22
23const YDB_ANONYMOUS_CREDENTIALS: &str = "YDB_ANONYMOUS_CREDENTIALS";
24const YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS: &str = "YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS";
25const YDB_METADATA_CREDENTIALS: &str = "YDB_METADATA_CREDENTIALS";
26const YDB_ACCESS_TOKEN_CREDENTIALS: &str = "YDB_ACCESS_TOKEN_CREDENTIALS";
27
28const YC_METADATA_URL: &str =
29 "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token";
30const GCE_METADATA_URL: &str =
31 "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
32
33const EMPTY_TOKEN: &str = "";
34
35#[deprecated(note = "use AccessTokenCredentials instead")]
36pub type StaticToken = AccessTokenCredentials;
37#[deprecated(note = "use CommandLineCredentials instead")]
38pub type CommandLineYcToken = CommandLineCredentials;
39#[deprecated(note = "use StaticCredentials instead")]
40pub type StaticCredentialsAuth = StaticCredentials;
41#[deprecated(note = "use MetadataUrlCredentials instead")]
42pub type YandexMetadata = MetadataUrlCredentials;
43
44pub(crate) type CredentialsRef = Arc<Box<dyn Credentials>>;
45
46pub(crate) fn credencials_ref<T: 'static + Credentials>(cred: T) -> CredentialsRef {
47 Arc::new(Box::new(cred))
48}
49
50pub struct MetadataUrlCredentials {
59 inner: GCEMetadata,
60}
61
62impl MetadataUrlCredentials {
63 pub fn new() -> Self {
64 Self {
65 inner: GCEMetadata::from_url(YC_METADATA_URL).unwrap(),
66 }
67 }
68
69 pub fn from_url<T: Into<String>>(url: T) -> YdbResult<Self> {
81 Ok(Self {
82 inner: GCEMetadata::from_url(url)?,
83 })
84 }
85}
86
87impl Default for MetadataUrlCredentials {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl Credentials for MetadataUrlCredentials {
94 fn create_token(&self) -> YdbResult<TokenInfo> {
95 self.inner.create_token()
96 }
97}
98
99pub struct AnonymousCredentials {
100 inner: AccessTokenCredentials,
101}
102
103impl AnonymousCredentials {
104 pub fn new() -> Self {
105 Self {
106 inner: AccessTokenCredentials::from(EMPTY_TOKEN),
107 }
108 }
109}
110
111impl Default for AnonymousCredentials {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117impl Credentials for AnonymousCredentials {
118 fn create_token(&self) -> YdbResult<TokenInfo> {
119 self.inner.create_token()
120 }
121}
122
123pub struct FromEnvCredentials {
124 inner: Box<dyn Credentials>,
125}
126
127impl FromEnvCredentials {
130 pub fn new() -> YdbResult<Self> {
131 Ok(Self {
132 inner: get_credentials_from_env()?,
133 })
134 }
135}
136
137impl Credentials for FromEnvCredentials {
138 fn create_token(&self) -> YdbResult<TokenInfo> {
139 self.inner.create_token()
140 }
141}
142
143fn get_credentials_from_env() -> YdbResult<Box<dyn Credentials>> {
144 if let Ok(file_creds) = env::var(YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS) {
145 return Ok(Box::new(ServiceAccountCredentials::from_file(file_creds)?));
146 }
147
148 if let Ok(v) = env::var(YDB_ANONYMOUS_CREDENTIALS) {
149 if v == "1" {
150 return Ok(Box::new(
151 AnonymousCredentials::new(),
153 ));
154 }
155 }
156
157 if let Ok(v) = env::var(YDB_METADATA_CREDENTIALS) {
158 if v == "1" {
159 return Ok(Box::new(MetadataUrlCredentials::new()));
160 }
161 }
162
163 if let Ok(token) = env::var(YDB_ACCESS_TOKEN_CREDENTIALS) {
164 return Ok(Box::new(AccessTokenCredentials::from(token)));
165 }
166
167 Ok(Box::new(MetadataUrlCredentials::new()))
168}
169
170#[derive(Debug, Clone)]
182pub struct AccessTokenCredentials {
183 pub(crate) token: String,
184}
185
186impl AccessTokenCredentials {
187 pub fn from<T: Into<String>>(token: T) -> Self {
195 AccessTokenCredentials {
196 token: token.into(),
197 }
198 }
199}
200
201impl Credentials for AccessTokenCredentials {
202 fn create_token(&self) -> YdbResult<TokenInfo> {
203 Ok(TokenInfo::token(self.token.clone()))
204 }
205
206 fn debug_string(&self) -> String {
207 let (begin, end) = if self.token.len() > 20 {
208 (
209 &self.token.as_str()[0..3],
210 &self.token.as_str()[(self.token.len() - 3)..self.token.len()],
211 )
212 } else {
213 ("xxx", "xxx")
214 };
215 format!("static token: {begin}...{end}")
216 }
217}
218
219#[derive(Debug)]
228pub struct CommandLineCredentials {
229 command: Arc<Mutex<Command>>,
230}
231
232impl CommandLineCredentials {
233 #[allow(dead_code)]
237 pub fn from_cmd<T: Into<String>>(cmd: T) -> YdbResult<Self> {
238 let cmd = cmd.into();
239 let cmd_parts: Vec<&str> = cmd.split_whitespace().collect();
240
241 if cmd_parts.is_empty() {
242 return Err(YdbError::Custom(format!(
243 "can't split get token command: '{cmd}'"
244 )));
245 }
246
247 let mut command = Command::new(cmd_parts[0]);
248 command.args(&cmd_parts.as_slice()[1..]);
249
250 Ok(CommandLineCredentials {
251 command: Arc::new(Mutex::new(command)),
252 })
253 }
254}
255
256impl Credentials for CommandLineCredentials {
257 fn create_token(&self) -> YdbResult<TokenInfo> {
258 let result = self.command.lock()?.output()?;
259 if !result.status.success() {
260 let err = String::from_utf8(result.stderr)?;
261 return Err(YdbError::Custom(format!(
262 "can't execute yc ({}): {}",
263 result.status.code().unwrap(),
264 err
265 )));
266 }
267 let token = String::from_utf8(result.stdout)?.trim().to_string();
268 Ok(TokenInfo::token(token))
269 }
270
271 fn debug_string(&self) -> String {
272 let token_describe: String = match self.create_token() {
273 Ok(token_info) => {
274 let token = token_info.token.expose_secret();
275 let desc: String = if token.len() > 20 {
276 format!(
277 "{}..{}",
278 &token.as_str()[0..3],
279 &token.as_str()[(token.len() - 3)..token.len()]
280 )
281 } else {
282 "short_token".to_string()
283 };
284 desc
285 }
286 Err(err) => {
287 format!("err: {err}")
288 }
289 };
290
291 token_describe
292 }
293}
294
295pub struct ServiceAccountCredentials {
323 audience_url: String,
324 private_key: SecretString,
325 service_account_id: String,
326 key_id: String,
327}
328
329impl ServiceAccountCredentials {
330 pub fn new(
331 service_account_id: impl Into<String>,
332 key_id: impl Into<String>,
333 private_key: impl Into<String>,
334 ) -> Self {
335 Self {
336 audience_url: Self::IAM_TOKEN_DEFAULT.to_string(),
337 private_key: SecretString::new(private_key.into()),
338 service_account_id: service_account_id.into(),
339 key_id: key_id.into(),
340 }
341 }
342
343 pub fn with_url(mut self, url: impl Into<String>) -> Self {
344 self.audience_url = url.into();
345 self
346 }
347
348 pub fn from_env() -> YdbResult<Self> {
349 let path = std::env::var(YDB_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS)?;
350
351 ServiceAccountCredentials::from_file(path)
352 }
353
354 pub fn from_file(path: impl AsRef<std::path::Path>) -> YdbResult<Self> {
355 let json_key = std::fs::read_to_string(path)?;
356 ServiceAccountCredentials::from_json(&json_key)
357 }
358
359 pub fn from_json(json_key: &str) -> YdbResult<Self> {
360 #[derive(Debug, Serialize, Deserialize)]
361 struct JsonKey {
362 public_key: String,
363 private_key: String,
364 service_account_id: String,
365 id: String,
366 }
367
368 let key: JsonKey = serde_json::from_str(json_key)?;
369
370 Ok(Self {
371 audience_url: Self::IAM_TOKEN_DEFAULT.to_string(),
372 key_id: key.id,
373 service_account_id: key.service_account_id,
374 private_key: SecretString::new(key.private_key),
375 })
376 }
377
378 const IAM_TOKEN_DEFAULT: &'static str = "https://iam.api.cloud.yandex.net/iam/v1/tokens";
379 const JWT_TOKEN_LIFE_TIME: usize = 720; fn build_jwt(&self) -> YdbResult<String> {
382 let private_key = self.private_key.expose_secret().as_bytes();
383
384 #[derive(Debug, Serialize, Deserialize)]
385 struct Claims {
386 aud: String, exp: usize, iat: usize, iss: String, }
391
392 let iat = SystemTime::now()
393 .duration_since(UNIX_EPOCH)
394 .expect("Time went backwards")
395 .as_secs() as usize;
396
397 let mut header = Header::new(Algorithm::PS256);
398 header.kid = Some(self.key_id.clone());
399 header.alg = Algorithm::PS256;
400 header.typ = Some("JWT".to_string());
401
402 let claims = Claims {
403 exp: iat + Self::JWT_TOKEN_LIFE_TIME,
404 aud: self.audience_url.clone(),
405 iat,
406 iss: self.service_account_id.clone(),
407 };
408 let token = encode(
409 &header,
410 &claims,
411 &EncodingKey::from_rsa_pem(private_key).map_err(|e| YdbError::custom(e.to_string()))?,
412 )
413 .map_err(|e| YdbError::custom(format!("can't build jwt: {e}")))?;
414
415 debug!("Token was built");
416 Ok(token)
417 }
418
419 fn get_renew_time_for_lifetime(time: chrono::DateTime<chrono::Utc>) -> Instant {
420 let duration = time - chrono::Utc::now();
421 let seconds = (0.1 * duration.num_seconds() as f64) as u64;
422 trace!("renew in: {}", seconds);
423
424 Instant::now() + Duration::from_secs(seconds)
425 }
426}
427
428impl Credentials for ServiceAccountCredentials {
429 fn create_token(&self) -> YdbResult<TokenInfo> {
430 use chrono::Utc;
431 #[derive(Deserialize)]
432 struct TokenResponse {
433 #[serde(rename = "iamToken")]
434 iam_token: String,
435 #[serde(rename = "expiresAt")]
436 expires_at: DateTime<Utc>,
437 }
438
439 #[derive(Serialize)]
440 struct TokenRequest {
441 jwt: String,
442 }
443
444 let jwt = self.build_jwt()?;
445
446 let req = TokenRequest { jwt };
447 let client = reqwest::blocking::Client::new();
448 let res: TokenResponse = client
449 .request(reqwest::Method::POST, self.audience_url.clone())
450 .json(&req)
451 .send()?
452 .json()?;
453
454 Ok(TokenInfo::token(format!("Bearer {}", res.iam_token))
455 .with_renew(Self::get_renew_time_for_lifetime(res.expires_at)))
456 }
457}
458
459pub struct GCEMetadata {
471 uri: String,
472}
473
474impl GCEMetadata {
475 pub fn new() -> Self {
477 Self::from_url(GCE_METADATA_URL).unwrap()
478 }
479
480 pub fn from_url<T: Into<String>>(url: T) -> YdbResult<Self> {
492 Ok(Self {
493 uri: url.into().parse()?,
494 })
495 }
496}
497
498impl Default for GCEMetadata {
499 fn default() -> Self {
500 Self::new()
501 }
502}
503
504impl Credentials for GCEMetadata {
505 fn create_token(&self) -> YdbResult<TokenInfo> {
506 #[derive(Deserialize)]
507 struct TokenResponse {
508 access_token: String,
509 expires_in: u64,
510 token_type: String,
511 }
512
513 let client = reqwest::blocking::Client::new();
514 let res: TokenResponse = client
515 .request(reqwest::Method::GET, self.uri.as_str())
516 .header("Metadata-Flavor", "Google")
517 .send()?
518 .json()?;
519 Ok(
520 TokenInfo::token(format!("{} {}", res.token_type, res.access_token))
521 .with_renew(Instant::now().add(Duration::from_secs(res.expires_in))),
522 )
523 }
524
525 fn debug_string(&self) -> String {
526 format!("GoogleComputeEngineMetadata from {}", self.uri.as_str())
527 }
528}
529
530pub struct StaticCredentials {
531 username: String,
532 password: SecretString,
533 database: String,
534 endpoint: Uri,
535 cert_path: Option<String>,
536}
537
538impl StaticCredentials {
539 pub async fn acquire_token(&self) -> YdbResult<String> {
540 let static_balancer = StaticLoadBalancer::new(self.endpoint.clone());
541 let empty_connection_manager = GrpcConnectionManager::new(
542 SharedLoadBalancer::new_with_balancer(Box::new(static_balancer)),
543 self.database.clone(),
544 MultiInterceptor::new(),
545 self.cert_path.clone(),
546 );
547
548 let mut auth_client = empty_connection_manager
549 .get_auth_service(RawAuthClient::new)
550 .await
551 .unwrap();
552
553 let raw_request = RawLoginRequest {
555 operation_params: TimeoutSettings::default().operation_params(),
556 user: self.username.clone(),
557 password: self.password.expose_secret().clone(),
558 };
559
560 let raw_response = auth_client.login(raw_request).await?;
561 Ok(raw_response.token)
562 }
563
564 pub fn new(username: String, password: String, endpoint: Uri, database: String) -> Self {
565 Self {
566 username,
567 password: SecretString::new(password),
568 database,
569 endpoint,
570 cert_path: None,
571 }
572 }
573
574 pub fn new_with_ca(
575 username: String,
576 password: String,
577 endpoint: Uri,
578 database: String,
579 cert_path: String,
580 ) -> Self {
581 Self {
582 username,
583 password: SecretString::new(password),
584 database,
585 endpoint,
586 cert_path: Some(cert_path),
587 }
588 }
589}
590
591impl Credentials for StaticCredentials {
592 #[tokio::main(flavor = "current_thread")]
593 async fn create_token(&self) -> YdbResult<TokenInfo> {
594 Ok(TokenInfo::token(self.acquire_token().await?))
595 }
596}