1use std::sync::{Arc, LazyLock};
16
17use common_base::secrets::{ExposeSecret, SecretString};
18use digest::Digest;
19use hmac::{Hmac, Mac};
20use pbkdf2::pbkdf2_hmac;
21use serde::Serializer;
22use sha1::Sha1;
23use sha2::Sha256;
24use snafu::{OptionExt, ensure};
25use subtle::ConstantTimeEq;
26
27use crate::error::{IllegalParamSnafu, InvalidConfigSnafu, Result, UserPasswordMismatchSnafu};
28use crate::user_info::DefaultUserInfo;
29use crate::user_provider::static_user_provider::{STATIC_USER_PROVIDER, StaticUserProvider};
30use crate::user_provider::watch_file_user_provider::{
31 WATCH_FILE_USER_PROVIDER, WatchFileUserProvider,
32};
33use crate::{UserInfoRef, UserProviderRef};
34
35pub(crate) const DEFAULT_USERNAME: &str = "greptime";
36pub const DEFAULT_PBKDF2_SHA256_ITERATIONS: u32 = 4096;
37pub const DEFAULT_PBKDF2_SHA256_SALT_LEN: usize = 16;
38pub const PBKDF2_SHA256_HASH_LEN: usize = 32;
39pub const MAX_PBKDF2_SHA256_ITERATIONS: u32 = 1_000_000;
40pub const MAX_PBKDF2_SHA256_SALT_LEN: usize = 1024;
41pub const PG_SCRAM_SHA256_KEY_LEN: usize = 32;
42
43pub(crate) const REDACTED_USER_PROVIDER_OPTION: &str = "[REDACTED]";
45
46type HmacSha256 = Hmac<Sha256>;
47
48static PG_SCRAM_MOCK_SECRET: LazyLock<[u8; PG_SCRAM_SHA256_KEY_LEN]> = LazyLock::new(rand::random);
51
52pub fn userinfo_by_name(username: Option<String>) -> UserInfoRef {
55 DefaultUserInfo::with_name(username.unwrap_or_else(|| DEFAULT_USERNAME.to_string()))
56}
57
58pub fn user_provider_from_option(opt: &str) -> Result<UserProviderRef> {
59 let (name, content) = opt.split_once(':').context(InvalidConfigSnafu {
60 value: REDACTED_USER_PROVIDER_OPTION,
61 msg: "UserProviderOption must be in format `<option>:<value>`",
62 })?;
63 match name {
64 STATIC_USER_PROVIDER => {
65 let provider =
66 StaticUserProvider::new(content).map(|p| Arc::new(p) as UserProviderRef)?;
67 Ok(provider)
68 }
69 WATCH_FILE_USER_PROVIDER => {
70 WatchFileUserProvider::new(content).map(|p| Arc::new(p) as UserProviderRef)
71 }
72 _ => InvalidConfigSnafu {
73 value: REDACTED_USER_PROVIDER_OPTION,
74 msg: "Invalid UserProviderOption",
75 }
76 .fail(),
77 }
78}
79
80pub fn serialize_user_provider_option<S>(
87 value: &Option<SecretString>,
88 serializer: S,
89) -> std::result::Result<S::Ok, S::Error>
90where
91 S: Serializer,
92{
93 match value {
94 Some(value) => {
95 serializer.serialize_some(&redact_user_provider_option(value.expose_secret()))
96 }
97 None => serializer.serialize_none(),
98 }
99}
100
101pub fn redact_user_provider_option(value: &str) -> String {
107 let is_file_provider = match value.split_once(':') {
108 Some((STATIC_USER_PROVIDER, content)) => content
109 .strip_prefix("file:")
110 .is_some_and(|path| !path.is_empty()),
111 Some((WATCH_FILE_USER_PROVIDER, path)) => !path.is_empty(),
112 _ => false,
113 };
114 if is_file_provider {
115 value.to_string()
116 } else {
117 REDACTED_USER_PROVIDER_OPTION.to_string()
118 }
119}
120
121pub fn static_user_provider_from_option(opt: &str) -> Result<StaticUserProvider> {
122 let (name, content) = opt.split_once(':').context(InvalidConfigSnafu {
123 value: REDACTED_USER_PROVIDER_OPTION,
124 msg: "UserProviderOption must be in format `<option>:<value>`",
125 })?;
126 match name {
127 STATIC_USER_PROVIDER => {
128 let provider = StaticUserProvider::new(content)?;
129 Ok(provider)
130 }
131 _ => InvalidConfigSnafu {
132 value: REDACTED_USER_PROVIDER_OPTION,
133 msg: format!("Invalid UserProviderOption, expect only {STATIC_USER_PROVIDER}"),
134 }
135 .fail(),
136 }
137}
138
139type Username<'a> = &'a str;
140type HostOrIp<'a> = &'a str;
141
142#[derive(Debug, Clone)]
143pub enum Identity<'a> {
144 UserId(Username<'a>, Option<HostOrIp<'a>>),
145}
146
147pub type HashedPassword<'a> = &'a [u8];
148pub type Salt<'a> = &'a [u8];
149
150pub enum Password<'a> {
152 PlainText(SecretString),
153 MysqlNativePassword(HashedPassword<'a>, Salt<'a>),
154 PgMD5(HashedPassword<'a>, Salt<'a>),
155}
156
157impl Password<'_> {
158 pub fn r#type(&self) -> &str {
159 match self {
160 Password::PlainText(_) => "plain_text",
161 Password::MysqlNativePassword(_, _) => "mysql_native_password",
162 Password::PgMD5(_, _) => "pg_md5",
163 }
164 }
165}
166
167pub fn auth_mysql(
168 auth_data: HashedPassword,
169 salt: Salt,
170 username: &str,
171 save_pwd: &[u8],
172) -> Result<()> {
173 let hash_stage_2 = mysql_native_password_hash(save_pwd);
174 auth_mysql_with_hash_stage_2(auth_data, salt, username, &hash_stage_2)
175}
176
177pub fn auth_mysql_with_verifier(
179 auth_data: HashedPassword,
180 salt: Salt,
181 username: &str,
182 verifier: &str,
183) -> Result<()> {
184 let hash_stage_2 = parse_mysql_native_password_verifier(verifier)?;
185 auth_mysql_with_hash_stage_2(auth_data, salt, username, &hash_stage_2)
186}
187
188pub fn validate_mysql_native_password_verifier(verifier: &str) -> Result<()> {
189 parse_mysql_native_password_verifier(verifier).map(|_| ())
190}
191
192pub(crate) fn parse_mysql_native_password_verifier(verifier: &str) -> Result<Vec<u8>> {
193 let Some(verifier) = verifier.strip_prefix("mysql_native_password:") else {
194 return InvalidConfigSnafu {
195 value: "mysql_native_password".to_string(),
196 msg: "Invalid mysql native password verifier format",
197 }
198 .fail();
199 };
200 let Ok(hash_stage_2) = hex::decode(verifier) else {
201 return InvalidConfigSnafu {
202 value: "mysql_native_password".to_string(),
203 msg: "Invalid mysql native password verifier encoding",
204 }
205 .fail();
206 };
207 ensure!(
208 hash_stage_2.len() == 20,
209 InvalidConfigSnafu {
210 value: "mysql_native_password".to_string(),
211 msg: "Illegal mysql native password verifier length",
212 }
213 );
214 Ok(hash_stage_2)
215}
216
217pub(crate) fn auth_mysql_with_hash_stage_2(
218 auth_data: HashedPassword,
219 salt: Salt,
220 username: &str,
221 hash_stage_2: &[u8],
222) -> Result<()> {
223 ensure!(
224 auth_data.len() == 20,
225 IllegalParamSnafu {
226 msg: "Illegal mysql password length"
227 }
228 );
229 ensure!(
230 hash_stage_2.len() == 20,
231 InvalidConfigSnafu {
232 value: hash_stage_2.len().to_string(),
233 msg: "Illegal mysql native password verifier length",
234 }
235 );
236 let tmp = sha1_two(salt, hash_stage_2);
238 let mut xor_result = [0u8; 20];
240 for i in 0..20 {
241 xor_result[i] = auth_data[i] ^ tmp[i];
242 }
243 let candidate_stage_2 = sha1_one(&xor_result);
244 if candidate_stage_2 == hash_stage_2 {
245 Ok(())
246 } else {
247 UserPasswordMismatchSnafu {
248 username: username.to_string(),
249 }
250 .fail()
251 }
252}
253
254pub fn mysql_native_password_hash(save_pwd: &[u8]) -> Vec<u8> {
255 double_sha1(save_pwd)
256}
257
258pub fn format_mysql_native_password_verifier(password: &[u8]) -> String {
259 format!(
260 "mysql_native_password:{}",
261 hex::encode(mysql_native_password_hash(password))
262 )
263}
264
265pub fn format_pbkdf2_sha256_password_verifier(
266 password: &[u8],
267 salt: &[u8],
268 iterations: u32,
269) -> Result<String> {
270 ensure!(
271 iterations > 0 && iterations <= MAX_PBKDF2_SHA256_ITERATIONS,
272 IllegalParamSnafu {
273 msg: format!(
274 "pbkdf2_sha256 iterations must be in 1..={}",
275 MAX_PBKDF2_SHA256_ITERATIONS
276 )
277 }
278 );
279 ensure!(
280 !salt.is_empty() && salt.len() <= MAX_PBKDF2_SHA256_SALT_LEN,
281 IllegalParamSnafu {
282 msg: format!(
283 "pbkdf2_sha256 salt length must be in 1..={}",
284 MAX_PBKDF2_SHA256_SALT_LEN
285 )
286 }
287 );
288
289 let mut hash = [0u8; PBKDF2_SHA256_HASH_LEN];
290 pbkdf2_hmac::<Sha256>(password, salt, iterations, &mut hash);
291 Ok(format!(
292 "pbkdf2_sha256:{iterations}:{}:{}",
293 hex::encode(salt),
294 hex::encode(hash)
295 ))
296}
297
298#[derive(Clone, PartialEq, Eq)]
299pub struct PgScramSha256Verifier {
300 iterations: u32,
301 salt: Vec<u8>,
302 stored_key: Vec<u8>,
303 server_key: Vec<u8>,
304}
305
306impl std::fmt::Debug for PgScramSha256Verifier {
307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308 f.debug_struct("PgScramSha256Verifier")
309 .field("iterations", &self.iterations)
310 .field("salt", &"<REDACTED>")
311 .field("stored_key", &"<REDACTED>")
312 .field("server_key", &"<REDACTED>")
313 .finish()
314 }
315}
316
317impl PgScramSha256Verifier {
318 pub fn new(
319 iterations: u32,
320 salt: Vec<u8>,
321 stored_key: Vec<u8>,
322 server_key: Vec<u8>,
323 ) -> Result<Self> {
324 ensure!(
325 iterations > 0 && iterations <= MAX_PBKDF2_SHA256_ITERATIONS,
326 IllegalParamSnafu {
327 msg: format!(
328 "pg_scram_sha256 iterations must be in 1..={}",
329 MAX_PBKDF2_SHA256_ITERATIONS
330 )
331 }
332 );
333 ensure!(
334 !salt.is_empty() && salt.len() <= MAX_PBKDF2_SHA256_SALT_LEN,
335 IllegalParamSnafu {
336 msg: format!(
337 "pg_scram_sha256 salt length must be in 1..={}",
338 MAX_PBKDF2_SHA256_SALT_LEN
339 )
340 }
341 );
342 ensure!(
343 stored_key.len() == PG_SCRAM_SHA256_KEY_LEN,
344 IllegalParamSnafu {
345 msg: "pg_scram_sha256 stored key must be 32 bytes"
346 }
347 );
348 ensure!(
349 server_key.len() == PG_SCRAM_SHA256_KEY_LEN,
350 IllegalParamSnafu {
351 msg: "pg_scram_sha256 server key must be 32 bytes"
352 }
353 );
354
355 Ok(Self {
356 iterations,
357 salt,
358 stored_key,
359 server_key,
360 })
361 }
362
363 pub fn from_password(password: &[u8], salt: &[u8], iterations: u32) -> Result<Self> {
364 let salted_password = pg_scram_sha256_salted_password(password, salt, iterations)?;
365 Self::from_salted_password(salted_password, salt.to_vec(), iterations)
366 }
367
368 pub fn from_salted_password(
369 salted_password: Vec<u8>,
370 salt: Vec<u8>,
371 iterations: u32,
372 ) -> Result<Self> {
373 ensure!(
374 salted_password.len() == PG_SCRAM_SHA256_KEY_LEN,
375 IllegalParamSnafu {
376 msg: "pg_scram_sha256 salted password must be 32 bytes"
377 }
378 );
379 let client_key = hmac_sha256(&salted_password, b"Client Key");
380 let stored_key = sha256(&client_key);
381 let server_key = hmac_sha256(&salted_password, b"Server Key");
382 Self::new(iterations, salt, stored_key, server_key)
383 }
384
385 pub fn mock_for_unknown_user(username: &[u8]) -> Self {
392 let mock_salt = hmac_sha256(PG_SCRAM_MOCK_SECRET.as_slice(), username);
393 Self {
394 iterations: DEFAULT_PBKDF2_SHA256_ITERATIONS,
395 salt: mock_salt[..DEFAULT_PBKDF2_SHA256_SALT_LEN].to_vec(),
396 stored_key: rand::random::<[u8; PG_SCRAM_SHA256_KEY_LEN]>().to_vec(),
397 server_key: rand::random::<[u8; PG_SCRAM_SHA256_KEY_LEN]>().to_vec(),
398 }
399 }
400
401 pub fn iterations(&self) -> u32 {
402 self.iterations
403 }
404
405 pub fn salt(&self) -> &[u8] {
406 &self.salt
407 }
408
409 pub fn stored_key(&self) -> &[u8] {
411 &self.stored_key
412 }
413
414 pub fn server_key(&self) -> &[u8] {
418 &self.server_key
419 }
420
421 pub fn verify_plain_password(&self, password: &[u8]) -> Result<bool> {
422 let salted_password =
423 pg_scram_sha256_salted_password(password, &self.salt, self.iterations)?;
424 let client_key = hmac_sha256(&salted_password, b"Client Key");
425 let stored_key = sha256(&client_key);
426 Ok(self.stored_key.ct_eq(&stored_key).into())
427 }
428
429 pub fn verify_client_proof(
430 &self,
431 auth_message: &[u8],
432 client_proof: &[u8],
433 ) -> Result<Option<Vec<u8>>> {
434 if client_proof.len() != PG_SCRAM_SHA256_KEY_LEN {
435 return Ok(None);
436 }
437
438 let client_signature = hmac_sha256(&self.stored_key, auth_message);
439 let client_key = xor(client_proof, &client_signature);
440 let stored_key = sha256(&client_key);
441 if self.stored_key.ct_eq(&stored_key).into() {
442 Ok(Some(hmac_sha256(&self.server_key, auth_message)))
443 } else {
444 Ok(None)
445 }
446 }
447}
448
449pub fn format_pg_scram_sha256_password_verifier(
450 password: &[u8],
451 salt: &[u8],
452 iterations: u32,
453) -> Result<String> {
454 let verifier = PgScramSha256Verifier::from_password(password, salt, iterations)?;
455 Ok(format!(
456 "pg_scram_sha256:{iterations}:{}:{}:{}",
457 hex::encode(verifier.salt),
458 hex::encode(verifier.stored_key),
459 hex::encode(verifier.server_key)
460 ))
461}
462
463pub fn parse_pg_scram_sha256_password_verifier(verifier: &str) -> Result<PgScramSha256Verifier> {
465 let Some(verifier) = verifier.strip_prefix("pg_scram_sha256:") else {
466 return InvalidConfigSnafu {
467 value: "pg_scram_sha256".to_string(),
468 msg: "Invalid pg scram sha256 verifier format",
469 }
470 .fail();
471 };
472 let mut parts = verifier.split(':');
473 let (Some(iterations), Some(salt), Some(stored_key), Some(server_key), None) = (
474 parts.next(),
475 parts.next(),
476 parts.next(),
477 parts.next(),
478 parts.next(),
479 ) else {
480 return InvalidConfigSnafu {
481 value: "pg_scram_sha256".to_string(),
482 msg: "Invalid pg scram sha256 verifier format",
483 }
484 .fail();
485 };
486 let (Ok(iterations), Ok(salt), Ok(stored_key), Ok(server_key)) = (
487 iterations.parse::<u32>(),
488 hex::decode(salt),
489 hex::decode(stored_key),
490 hex::decode(server_key),
491 ) else {
492 return InvalidConfigSnafu {
493 value: "pg_scram_sha256".to_string(),
494 msg: "Invalid pg scram sha256 verifier encoding",
495 }
496 .fail();
497 };
498
499 PgScramSha256Verifier::new(iterations, salt, stored_key, server_key)
500}
501
502fn pg_scram_sha256_salted_password(
503 password: &[u8],
504 salt: &[u8],
505 iterations: u32,
506) -> Result<Vec<u8>> {
507 ensure!(
508 iterations > 0 && iterations <= MAX_PBKDF2_SHA256_ITERATIONS,
509 IllegalParamSnafu {
510 msg: format!(
511 "pg_scram_sha256 iterations must be in 1..={}",
512 MAX_PBKDF2_SHA256_ITERATIONS
513 )
514 }
515 );
516 ensure!(
517 !salt.is_empty() && salt.len() <= MAX_PBKDF2_SHA256_SALT_LEN,
518 IllegalParamSnafu {
519 msg: format!(
520 "pg_scram_sha256 salt length must be in 1..={}",
521 MAX_PBKDF2_SHA256_SALT_LEN
522 )
523 }
524 );
525
526 let prepared_password = std::str::from_utf8(password)
527 .ok()
528 .and_then(|password| stringprep::saslprep(password).ok());
529 let password = prepared_password
530 .as_deref()
531 .map(str::as_bytes)
532 .unwrap_or(password);
533
534 let mut salted_password = [0u8; PG_SCRAM_SHA256_KEY_LEN];
535 pbkdf2_hmac::<Sha256>(password, salt, iterations, &mut salted_password);
536 Ok(salted_password.to_vec())
537}
538
539fn hmac_sha256(key: &[u8], msg: &[u8]) -> Vec<u8> {
540 let mut mac = HmacSha256::new_from_slice(key).expect("HMAC accepts any key length");
541 mac.update(msg);
542 mac.finalize().into_bytes().to_vec()
543}
544
545fn sha256(data: &[u8]) -> Vec<u8> {
546 let mut hasher = Sha256::new();
547 hasher.update(data);
548 hasher.finalize().to_vec()
549}
550
551fn xor(lhs: &[u8], rhs: &[u8]) -> Vec<u8> {
552 lhs.iter().zip(rhs).map(|(l, r)| l ^ r).collect()
553}
554
555fn sha1_two(input_1: &[u8], input_2: &[u8]) -> Vec<u8> {
556 let mut hasher = Sha1::new();
557 hasher.update(input_1);
558 hasher.update(input_2);
559 hasher.finalize().to_vec()
560}
561
562fn sha1_one(data: &[u8]) -> Vec<u8> {
563 let mut hasher = Sha1::new();
564 hasher.update(data);
565 hasher.finalize().to_vec()
566}
567
568fn double_sha1(data: &[u8]) -> Vec<u8> {
569 sha1_one(&sha1_one(data))
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575 use crate::error::Error;
576
577 fn mysql_native_password_auth_data(password: &[u8], salt: &[u8]) -> Vec<u8> {
578 let hash_stage_1 = sha1_one(password);
579 let hash_stage_2 = mysql_native_password_hash(password);
580 let scramble = sha1_two(salt, &hash_stage_2);
581 hash_stage_1
582 .iter()
583 .zip(scramble)
584 .map(|(lhs, rhs)| lhs ^ rhs)
585 .collect()
586 }
587
588 #[test]
589 fn test_sha() {
590 let sha_1_answer: Vec<u8> = vec![
591 124, 74, 141, 9, 202, 55, 98, 175, 97, 229, 149, 32, 148, 61, 194, 100, 148, 248, 148,
592 27,
593 ];
594 let sha_1 = sha1_one("123456".as_bytes());
595 assert_eq!(sha_1, sha_1_answer);
596
597 let double_sha1_answer: Vec<u8> = vec![
598 107, 180, 131, 126, 183, 67, 41, 16, 94, 228, 86, 141, 218, 125, 198, 126, 210, 202,
599 42, 217,
600 ];
601 let double_sha1 = double_sha1("123456".as_bytes());
602 assert_eq!(double_sha1, double_sha1_answer);
603
604 let sha1_2_answer: Vec<u8> = vec![
605 132, 115, 215, 211, 99, 186, 164, 206, 168, 152, 217, 192, 117, 47, 240, 252, 142, 244,
606 37, 204,
607 ];
608 let sha1_2 = sha1_two("123456".as_bytes(), "654321".as_bytes());
609 assert_eq!(sha1_2, sha1_2_answer);
610 }
611
612 #[test]
613 fn test_format_mysql_native_password_verifier() {
614 let verifier = format_mysql_native_password_verifier("123456".as_bytes());
615 assert_eq!(
616 "mysql_native_password:6bb4837eb74329105ee4568dda7dc67ed2ca2ad9",
617 verifier
618 );
619 assert_eq!(
620 mysql_native_password_hash(b"123456"),
621 parse_mysql_native_password_verifier(&verifier).unwrap()
622 );
623 assert!(parse_mysql_native_password_verifier("mysql_native_password:00").is_err());
624
625 let salt = b"01234567890123456789";
626 let auth_data = mysql_native_password_auth_data(b"123456", salt);
627 auth_mysql_with_verifier(&auth_data, salt, "greptime", &verifier).unwrap();
628 assert!(
629 auth_mysql_with_verifier(
630 &auth_data,
631 salt,
632 "greptime",
633 &format_mysql_native_password_verifier(b"wrong")
634 )
635 .is_err()
636 );
637 }
638
639 #[test]
640 fn test_format_pbkdf2_sha256_password_verifier() {
641 let verifier =
642 format_pbkdf2_sha256_password_verifier("password".as_bytes(), b"salt", 4096).unwrap();
643 assert_eq!(
644 "pbkdf2_sha256:4096:73616c74:c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a",
645 verifier
646 );
647
648 assert!(format_pbkdf2_sha256_password_verifier(b"password", b"", 4096).is_err());
649 assert!(format_pbkdf2_sha256_password_verifier(b"password", b"salt", 0).is_err());
650 assert!(
651 format_pbkdf2_sha256_password_verifier(
652 b"password",
653 b"salt",
654 MAX_PBKDF2_SHA256_ITERATIONS + 1,
655 )
656 .is_err()
657 );
658 }
659
660 #[test]
661 fn test_format_pg_scram_sha256_password_verifier() {
662 let verifier =
663 format_pg_scram_sha256_password_verifier("password".as_bytes(), b"salt", 4096).unwrap();
664 assert_eq!(
665 "pg_scram_sha256:4096:73616c74:945e1c466fc9932efadc23781edc5d1e78d5e10f005933652af1a6105154f084:b9bf0e811b1fb6793671c0cc3adedf7c75cd72291191092ad65878c5a02aad2c",
666 verifier
667 );
668 let parsed = parse_pg_scram_sha256_password_verifier(&verifier).unwrap();
669 assert_eq!(4096, parsed.iterations());
670 assert_eq!(b"salt", parsed.salt());
671 assert!(parse_pg_scram_sha256_password_verifier("pg_scram_sha256:bad").is_err());
672 }
673
674 #[test]
675 fn test_pg_scram_sha256_applies_saslprep() {
676 let normalized = PgScramSha256Verifier::from_password(b"pass word", b"salt", 4096).unwrap();
677 let non_breaking_space =
678 PgScramSha256Verifier::from_password("pass\u{00a0}word".as_bytes(), b"salt", 4096)
679 .unwrap();
680
681 assert_eq!(normalized, non_breaking_space);
682 assert!(
683 non_breaking_space
684 .verify_plain_password(b"pass word")
685 .unwrap()
686 );
687 }
688
689 #[test]
690 fn test_pg_scram_sha256_uses_original_bytes_when_saslprep_is_not_possible() {
691 let invalid_utf8 = b"password\xff";
692 let prohibited = b"password\x07";
693
694 for password in [invalid_utf8.as_slice(), prohibited.as_slice()] {
695 let verifier = PgScramSha256Verifier::from_password(password, b"salt", 4096).unwrap();
696 assert!(verifier.verify_plain_password(password).unwrap());
697 }
698 }
699
700 #[test]
701 fn test_redact_user_provider_option() {
702 for option in [
703 "static_user_provider:file:/etc/greptimedb/users",
704 "watch_file_user_provider:/etc/greptimedb/users",
705 "static_user_provider:file:users.txt",
706 "watch_file_user_provider:users.txt",
707 ] {
708 assert_eq!(redact_user_provider_option(option), option);
709 }
710
711 for option in [
712 "",
713 "secret",
714 "alice=secret",
715 "static_user_provider",
716 "static_user_provider:",
717 "static_user_provider:file",
718 "static_user_provider:file:",
719 "static_user_provider:filex:/etc/greptimedb/users",
720 "static_user_provider:cmd:alice=secret,bob=pbkdf2_sha256:4096:aa:bb",
721 "static_user_provider:cmd:alice",
722 "static_user_provider:cmd:alice:secret",
723 "static_user_provider:cmd:alice=plain:prefix,secret-tail",
724 "static_user_provider:cmdx:alice=secret",
725 "watch_file_user_provider",
726 "watch_file_user_provider:",
727 "ldap_user_provider:/etc/greptimedb/ldap.toml",
728 "unknown_user_provider:cmd:alice=secret",
729 ] {
730 assert_eq!(redact_user_provider_option(option), "[REDACTED]");
731 }
732 }
733
734 #[test]
735 fn test_user_provider_errors_redact_credentials() {
736 for option in [
737 "secret",
738 "alice=secret",
739 "alice=secret,bob=other_secret",
740 "alice=secret:cmd:alice=secret",
741 "static_user_provider:alice=secret",
742 "static_user_provider:alice=secret:cmd",
743 "static_user_provider:cmdx:alice=secret",
744 "unknown_user_provider:cmd:alice=secret",
745 "static_user_provider:cmd:alice:invalid=secret",
746 "static_user_provider:cmd:alice=secret=tail",
747 "static_user_provider:cmd:alice=pbkdf2_sha256:secret",
748 "static_user_provider:cmd:alice=mysql_native_password:secret",
749 "static_user_provider:cmd:alice=pg_scram_sha256:secret",
750 "static_user_provider:cmd:bob=secret,alice=pbkdf2_sha256:secret",
751 "static_user_provider:cmd:alice:secret",
752 "static_user_provider:cmd:alice=plain:prefix,secret-tail",
753 "static_user_provider:cmd:watch_file_user_provider:secret",
754 ] {
755 for error in [
756 user_provider_from_option(option).err().unwrap(),
757 static_user_provider_from_option(option).err().unwrap(),
758 ] {
759 assert!(!error.to_string().contains("secret"));
760 assert!(!format!("{error:?}").contains("secret"));
761 let Error::InvalidConfig { value, .. } = error else {
762 panic!("Expected an invalid config error");
763 };
764 assert_eq!(value, "[REDACTED]");
765 }
766 }
767 }
768
769 #[test]
770 fn test_mock_verifier_is_stable_per_username() {
771 let alice = PgScramSha256Verifier::mock_for_unknown_user(b"alice");
772 let alice_again = PgScramSha256Verifier::mock_for_unknown_user(b"alice");
773 let bob = PgScramSha256Verifier::mock_for_unknown_user(b"bob");
774
775 assert_eq!(alice.salt(), alice_again.salt());
778 assert_ne!(alice.salt(), bob.salt());
779 assert_eq!(alice.iterations(), DEFAULT_PBKDF2_SHA256_ITERATIONS);
780 assert_eq!(alice.salt().len(), DEFAULT_PBKDF2_SHA256_SALT_LEN);
781
782 assert!(
784 alice
785 .verify_client_proof(b"auth-message", &[0u8; PG_SCRAM_SHA256_KEY_LEN])
786 .unwrap()
787 .is_none()
788 );
789 }
790}