1pub(crate) mod static_user_provider;
16pub(crate) mod watch_file_user_provider;
17
18use std::collections::HashMap;
19use std::fs::File;
20use std::io::BufRead;
21use std::path::Path;
22use std::{fmt, io};
23
24use common_base::secrets::ExposeSecret;
25use pbkdf2::pbkdf2_hmac;
26use sha2::Sha256;
27use snafu::{OptionExt, ResultExt, ensure};
28use subtle::ConstantTimeEq;
29
30use crate::common::{
31 Identity, MAX_PBKDF2_SHA256_ITERATIONS, MAX_PBKDF2_SHA256_SALT_LEN, PBKDF2_SHA256_HASH_LEN,
32 Password, auth_mysql_with_hash_stage_2,
33};
34use crate::error::{
35 IllegalParamSnafu, InvalidConfigSnafu, IoSnafu, Result, UnsupportedPasswordTypeSnafu,
36 UserNotFoundSnafu, UserPasswordMismatchSnafu,
37};
38use crate::user_info::{DefaultUserInfo, PermissionMode};
39use crate::{UserInfoRef, auth_mysql};
40
41#[async_trait::async_trait]
42pub trait UserProvider: Send + Sync {
43 fn name(&self) -> &str;
44
45 async fn authenticate(&self, id: Identity<'_>, password: Password<'_>) -> Result<UserInfoRef>;
47
48 async fn authorize(&self, catalog: &str, schema: &str, user_info: &UserInfoRef) -> Result<()>;
52
53 async fn auth(
56 &self,
57 id: Identity<'_>,
58 password: Password<'_>,
59 catalog: &str,
60 schema: &str,
61 ) -> Result<UserInfoRef> {
62 let user_info = self.authenticate(id, password).await?;
63 self.authorize(catalog, schema, &user_info).await?;
64 Ok(user_info)
65 }
66
67 fn external(&self) -> bool {
69 false
70 }
71}
72
73#[derive(Clone, PartialEq, Eq)]
74pub(crate) enum PasswordVerifier {
75 PlainText(String),
76 Pbkdf2Sha256 {
77 iterations: u32,
78 salt: Vec<u8>,
79 hash: Vec<u8>,
80 },
81 MysqlNativePassword {
82 hash_stage_2: Vec<u8>,
83 },
84}
85
86impl fmt::Debug for PasswordVerifier {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 PasswordVerifier::PlainText(_) => {
90 f.debug_tuple("PlainText").field(&"<REDACTED>").finish()
91 }
92 PasswordVerifier::Pbkdf2Sha256 { iterations, .. } => f
93 .debug_struct("Pbkdf2Sha256")
94 .field("iterations", iterations)
95 .field("salt", &"<REDACTED>")
96 .field("hash", &"<REDACTED>")
97 .finish(),
98 PasswordVerifier::MysqlNativePassword { .. } => f
99 .debug_struct("MysqlNativePassword")
100 .field("hash_stage_2", &"<REDACTED>")
101 .finish(),
102 }
103 }
104}
105
106impl PasswordVerifier {
107 fn parse(input: &str) -> Option<Self> {
108 if let Some(password) = input.strip_prefix("plain:") {
109 return Some(Self::PlainText(password.to_string()));
110 }
111
112 if let Some(verifier) = input.strip_prefix("pbkdf2_sha256:") {
113 let mut parts = verifier.split(':');
114 let iterations = parts.next()?.parse::<u32>().ok()?;
115 let salt = hex::decode(parts.next()?).ok()?;
116 let hash = hex::decode(parts.next()?).ok()?;
117 if parts.next().is_some()
118 || iterations == 0
119 || iterations > MAX_PBKDF2_SHA256_ITERATIONS
120 || salt.is_empty()
121 || salt.len() > MAX_PBKDF2_SHA256_SALT_LEN
122 || hash.len() != PBKDF2_SHA256_HASH_LEN
123 {
124 return None;
125 }
126
127 return Some(Self::Pbkdf2Sha256 {
128 iterations,
129 salt,
130 hash,
131 });
132 }
133
134 if let Some(verifier) = input.strip_prefix("mysql_native_password:") {
135 let hash_stage_2 = hex::decode(verifier).ok()?;
136 if hash_stage_2.len() != 20 {
137 return None;
138 }
139
140 return Some(Self::MysqlNativePassword { hash_stage_2 });
141 }
142
143 Some(Self::PlainText(input.to_string()))
144 }
145
146 fn verify_plain_text(&self, password: &str) -> bool {
147 match self {
148 PasswordVerifier::PlainText(expected) => {
149 expected.as_bytes().ct_eq(password.as_bytes()).into()
150 }
151 PasswordVerifier::Pbkdf2Sha256 {
152 iterations,
153 salt,
154 hash,
155 } => {
156 if hash.len() != PBKDF2_SHA256_HASH_LEN {
157 return false;
158 }
159 let mut actual = [0u8; PBKDF2_SHA256_HASH_LEN];
160 pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, *iterations, &mut actual);
161 hash.as_slice().ct_eq(&actual[..]).into()
162 }
163 PasswordVerifier::MysqlNativePassword { .. } => false,
164 }
165 }
166
167 fn verify_mysql_native_password(
168 &self,
169 auth_data: &[u8],
170 salt: &[u8],
171 username: &str,
172 ) -> Result<()> {
173 match self {
174 PasswordVerifier::PlainText(password) => {
175 auth_mysql(auth_data, salt, username, password.as_bytes())
176 }
177 PasswordVerifier::MysqlNativePassword { hash_stage_2 } => {
178 auth_mysql_with_hash_stage_2(auth_data, salt, username, hash_stage_2)
179 }
180 PasswordVerifier::Pbkdf2Sha256 { .. } => UnsupportedPasswordTypeSnafu {
181 password_type: "mysql_native_password_with_pbkdf2_sha256_verifier",
182 }
183 .fail(),
184 }
185 }
186}
187
188pub type UserInfoMap = HashMap<String, (PasswordVerifier, PermissionMode)>;
191
192fn load_credential_from_file(filepath: &str) -> Result<UserInfoMap> {
193 let path = Path::new(filepath);
195 if !path.exists() {
196 return InvalidConfigSnafu {
197 value: filepath.to_string(),
198 msg: "UserProvider file must exist",
199 }
200 .fail();
201 }
202
203 ensure!(
204 path.is_file(),
205 InvalidConfigSnafu {
206 value: filepath,
207 msg: "UserProvider file must be a file",
208 }
209 );
210 let file = File::open(path).context(IoSnafu)?;
211 let credential = io::BufReader::new(file)
212 .lines()
213 .map_while(std::result::Result::ok)
214 .filter_map(|line| {
215 let line = line.trim();
221 if line.is_empty() || line.starts_with('#') {
222 return None;
223 }
224
225 parse_credential_line(line)
226 })
227 .collect::<HashMap<String, _>>();
228
229 ensure!(
230 !credential.is_empty(),
231 InvalidConfigSnafu {
232 value: filepath,
233 msg: "UserProvider's file must contains at least one valid credential",
234 }
235 );
236
237 Ok(credential)
238}
239
240pub(crate) fn parse_credential_line(
247 line: &str,
248) -> Option<(String, (PasswordVerifier, PermissionMode))> {
249 let parts = line.split('=').collect::<Vec<&str>>();
250 if parts.len() != 2 {
251 return None;
252 }
253
254 let (username_part, password) = (parts[0], parts[1]);
255 let (username, permission_mode) = if let Some((user, perm)) = username_part.split_once(':') {
256 (user, PermissionMode::from_str(perm))
257 } else {
258 (username_part, PermissionMode::default())
259 };
260
261 let verifier = PasswordVerifier::parse(password)?;
262
263 Some((username.to_string(), (verifier, permission_mode)))
264}
265
266fn authenticate_with_credential(
267 users: &UserInfoMap,
268 input_id: Identity<'_>,
269 input_pwd: Password<'_>,
270) -> Result<UserInfoRef> {
271 match input_id {
272 Identity::UserId(username, _) => {
273 ensure!(
274 !username.is_empty(),
275 IllegalParamSnafu {
276 msg: "blank username"
277 }
278 );
279 let (verifier, permission_mode) = users.get(username).context(UserNotFoundSnafu {
280 username: username.to_string(),
281 })?;
282
283 match input_pwd {
284 Password::PlainText(pwd) => {
285 ensure!(
286 !pwd.expose_secret().is_empty(),
287 IllegalParamSnafu {
288 msg: "blank password"
289 }
290 );
291 if verifier.verify_plain_text(pwd.expose_secret()) {
292 Ok(DefaultUserInfo::with_name_and_permission(
293 username,
294 *permission_mode,
295 ))
296 } else {
297 UserPasswordMismatchSnafu {
298 username: username.to_string(),
299 }
300 .fail()
301 }
302 }
303 Password::MysqlNativePassword(auth_data, salt) => verifier
304 .verify_mysql_native_password(auth_data, salt, username)
305 .map(|_| DefaultUserInfo::with_name_and_permission(username, *permission_mode)),
306 Password::PgMD5(_, _) => UnsupportedPasswordTypeSnafu {
307 password_type: "pg_md5",
308 }
309 .fail(),
310 }
311 }
312 }
313}
314#[cfg(test)]
315mod tests {
316 use digest::Digest;
317 use sha1::Sha1;
318
319 use super::*;
320 use crate::common::mysql_native_password_hash;
321
322 fn plain(password: &str) -> PasswordVerifier {
323 PasswordVerifier::PlainText(password.to_string())
324 }
325
326 fn sha1_one(data: &[u8]) -> Vec<u8> {
327 let mut hasher = Sha1::new();
328 hasher.update(data);
329 hasher.finalize().to_vec()
330 }
331
332 fn mysql_native_password_auth_data(password: &str, salt: &[u8]) -> Vec<u8> {
333 let hash_stage_1 = sha1_one(password.as_bytes());
334 let hash_stage_2 = mysql_native_password_hash(password.as_bytes());
335 let mut hasher = Sha1::new();
336 hasher.update(salt);
337 hasher.update(hash_stage_2);
338 let scramble = hasher.finalize();
339
340 hash_stage_1
341 .iter()
342 .zip(scramble.iter())
343 .map(|(lhs, rhs)| lhs ^ rhs)
344 .collect()
345 }
346
347 #[test]
348 fn test_parse_credential_line() {
349 let result = parse_credential_line("admin=password123");
351 assert_eq!(
352 result,
353 Some((
354 "admin".to_string(),
355 (plain("password123"), PermissionMode::default())
356 ))
357 );
358
359 let result = parse_credential_line("user:ReadOnly=secret");
361 assert_eq!(
362 result,
363 Some((
364 "user".to_string(),
365 (plain("secret"), PermissionMode::ReadOnly)
366 ))
367 );
368 let result = parse_credential_line("user:ro=secret");
369 assert_eq!(
370 result,
371 Some((
372 "user".to_string(),
373 (plain("secret"), PermissionMode::ReadOnly)
374 ))
375 );
376 let result = parse_credential_line("writer:WriteOnly=mypass");
378 assert_eq!(
379 result,
380 Some((
381 "writer".to_string(),
382 (plain("mypass"), PermissionMode::WriteOnly)
383 ))
384 );
385
386 let result = parse_credential_line("writer:wo=mypass");
388 assert_eq!(
389 result,
390 Some((
391 "writer".to_string(),
392 (plain("mypass"), PermissionMode::WriteOnly)
393 ))
394 );
395
396 let result = parse_credential_line("admin:rw=p@ssw0rd!123");
398 assert_eq!(
399 result,
400 Some((
401 "admin".to_string(),
402 (plain("p@ssw0rd!123"), PermissionMode::ReadWrite)
403 ))
404 );
405
406 let result = parse_credential_line("user name:WriteOnly=password");
408 assert_eq!(
409 result,
410 Some((
411 "user name".to_string(),
412 (plain("password"), PermissionMode::WriteOnly)
413 ))
414 );
415
416 let result = parse_credential_line("user=plain:password");
417 assert_eq!(
418 result,
419 Some((
420 "user".to_string(),
421 (plain("password"), PermissionMode::default())
422 ))
423 );
424
425 let iterations = 4096;
426 let salt = b"salt";
427 let mut hash = [0u8; 32];
428 pbkdf2_hmac::<Sha256>("password".as_bytes(), salt, iterations, &mut hash);
429 let result = parse_credential_line(&format!(
430 "user=pbkdf2_sha256:{iterations}:{}:{}",
431 hex::encode(salt),
432 hex::encode(hash)
433 ));
434 assert_eq!(
435 result,
436 Some((
437 "user".to_string(),
438 (
439 PasswordVerifier::Pbkdf2Sha256 {
440 iterations,
441 salt: salt.to_vec(),
442 hash: hash.to_vec(),
443 },
444 PermissionMode::default()
445 )
446 ))
447 );
448
449 let result = parse_credential_line("user=pbkdf2_sha256:4096:not-hex:abcd");
450 assert_eq!(result, None);
451
452 let result = parse_credential_line(&format!(
455 "user=pbkdf2_sha256:4096:{}:abcd",
456 hex::encode(salt)
457 ));
458 assert_eq!(result, None);
459
460 let result = parse_credential_line(&format!(
461 "user=pbkdf2_sha256:{}:{}:{}",
462 MAX_PBKDF2_SHA256_ITERATIONS + 1,
463 hex::encode(salt),
464 hex::encode(hash)
465 ));
466 assert_eq!(result, None);
467
468 let hash_stage_2 = mysql_native_password_hash("password".as_bytes());
469 let result = parse_credential_line(&format!(
470 "user=mysql_native_password:{}",
471 hex::encode(&hash_stage_2)
472 ));
473 assert_eq!(
474 result,
475 Some((
476 "user".to_string(),
477 (
478 PasswordVerifier::MysqlNativePassword { hash_stage_2 },
479 PermissionMode::default()
480 )
481 ))
482 );
483
484 let result = parse_credential_line("user=mysql_native_password:abcd");
485 assert_eq!(result, None);
486
487 let result = parse_credential_line("invalid_line");
489 assert_eq!(result, None);
490
491 let result = parse_credential_line("user=pass=word");
493 assert_eq!(result, None);
494
495 let result = parse_credential_line("user=");
497 assert_eq!(
498 result,
499 Some(("user".to_string(), (plain(""), PermissionMode::default())))
500 );
501
502 let result = parse_credential_line("=password");
504 assert_eq!(
505 result,
506 Some((
507 "".to_string(),
508 (plain("password"), PermissionMode::default())
509 ))
510 );
511 }
512
513 #[test]
514 fn test_authenticate_with_mysql_native_password_verifier() {
515 let password = "password";
516 let salt = b"12345678901234567890";
517 let hash_stage_2 = mysql_native_password_hash(password.as_bytes());
518 let auth_data = mysql_native_password_auth_data(password, salt);
519 let users = HashMap::from([(
520 "user".to_string(),
521 (
522 PasswordVerifier::MysqlNativePassword { hash_stage_2 },
523 PermissionMode::default(),
524 ),
525 )]);
526
527 let result = authenticate_with_credential(
528 &users,
529 Identity::UserId("user", None),
530 Password::MysqlNativePassword(&auth_data, salt),
531 );
532
533 assert!(result.is_ok());
534 }
535
536 #[test]
537 fn test_authenticate_with_plain_text_mysql_native_password() {
538 let password = "password";
539 let salt = b"12345678901234567890";
540 let auth_data = mysql_native_password_auth_data(password, salt);
541 let users = HashMap::from([(
542 "user".to_string(),
543 (
544 PasswordVerifier::PlainText(password.to_string()),
545 PermissionMode::default(),
546 ),
547 )]);
548
549 let result = authenticate_with_credential(
550 &users,
551 Identity::UserId("user", None),
552 Password::MysqlNativePassword(&auth_data, salt),
553 );
554
555 assert!(result.is_ok());
556 }
557
558 #[test]
559 fn test_pbkdf2_sha256_rejects_mysql_native_password() {
560 let password = "password";
561 let salt = b"salt";
562 let iterations = 4096;
563 let mut hash = [0u8; 32];
564 pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, iterations, &mut hash);
565 let users = HashMap::from([(
566 "user".to_string(),
567 (
568 PasswordVerifier::Pbkdf2Sha256 {
569 iterations,
570 salt: salt.to_vec(),
571 hash: hash.to_vec(),
572 },
573 PermissionMode::default(),
574 ),
575 )]);
576 let mysql_salt = b"12345678901234567890";
577 let auth_data = mysql_native_password_auth_data(password, mysql_salt);
578
579 let result = authenticate_with_credential(
580 &users,
581 Identity::UserId("user", None),
582 Password::MysqlNativePassword(&auth_data, mysql_salt),
583 );
584
585 assert!(result.is_err());
586 }
587
588 #[test]
589 fn test_password_verifier_debug_redacts_secrets() {
590 let debug = format!("{:?}", PasswordVerifier::PlainText("secret".to_string()));
591 assert!(debug.contains("<REDACTED>"));
592 assert!(!debug.contains("secret"));
593
594 let debug = format!(
595 "{:?}",
596 PasswordVerifier::Pbkdf2Sha256 {
597 iterations: 4096,
598 salt: b"super-secret-salt".to_vec(),
599 hash: b"super-secret-hash".to_vec(),
600 }
601 );
602 assert!(debug.contains("Pbkdf2Sha256"));
603 assert!(debug.contains("4096"));
604 assert!(!debug.contains("super-secret-salt"));
605 assert!(!debug.contains("super-secret-hash"));
606
607 let debug = format!(
608 "{:?}",
609 PasswordVerifier::MysqlNativePassword {
610 hash_stage_2: b"super-secret-hash".to_vec(),
611 }
612 );
613 assert!(debug.contains("MysqlNativePassword"));
614 assert!(!debug.contains("super-secret-hash"));
615 }
616}