1use std::collections::HashSet;
16use std::sync::atomic::AtomicU64;
17use std::sync::atomic::Ordering as AtomicOrdering;
18use std::sync::Arc;
19use std::sync::RwLock;
20use std::time::Duration;
21
22use cheetah_string::CheetahString;
23use rocketmq_protocol::protocol::remoting_command::RemotingCommand;
24use rocketmq_runtime::BlockingExecutor;
25use rocketmq_runtime::ChildServiceContext;
26use rocketmq_runtime::MetadataIoActor;
27use rocketmq_runtime::MetadataIoConfig;
28use rocketmq_runtime::ScheduledExecutionPolicy;
29use rocketmq_runtime::ScheduledTaskConfig;
30use rocketmq_runtime::ScheduledTaskGroup;
31use rocketmq_runtime::ScheduledTaskSnapshot;
32use rocketmq_runtime::ShutdownReport;
33use rocketmq_security_api::AuthorizationDecision;
34use rocketmq_security_api::DetailedDecision;
35use rocketmq_security_api::LayerEvaluation;
36use rocketmq_security_api::LayerFailureKind;
37use rocketmq_security_api::LayerRequirement;
38use rocketmq_security_api::ResourcePattern;
39use rocketmq_security_api::ResourceType;
40use tracing::debug;
41use tracing::info;
42use tracing::warn;
43
44use crate::acl::AclConfigFingerprint;
45use crate::acl::FileAclConfigLoader;
46use crate::acl::WhiteList;
47use crate::authentication::builder::default_authentication_context_builder::DefaultAuthenticationContextBuilder;
48use crate::authentication::builder::AuthenticationContextBuilder;
49use crate::authentication::enums::subject_type::SubjectType;
50use crate::authentication::enums::user_status::UserStatus;
51use crate::authentication::enums::user_type::UserType;
52use crate::authentication::model::user::User;
53#[cfg(test)]
54use crate::authentication::provider::authentication_metadata_provider::AuthenticationMetadataProvider;
55use crate::authentication::provider::AuthenticationProvider;
56use crate::authentication::provider::DefaultAuthenticationProvider;
57use crate::authentication::provider::LocalAuthenticationMetadataProvider;
58use crate::authorization::enums::policy_type::PolicyType;
59use crate::authorization::metadata_provider::AuthorizationMetadataProvider;
60use crate::authorization::metadata_provider::LocalAuthorizationMetadataProvider;
61use crate::authorization::model::acl::Acl;
62use crate::authorization::model::policy::Policy;
63use crate::authorization::model::policy_entry::PolicyEntry;
64use crate::authorization::model::resource::Resource;
65use crate::authorization::provider::AuthorizationProvider;
66use crate::authorization::provider::DefaultAuthorizationProvider;
67use crate::config::AuthConfig;
68use crate::migration::alc::acl_config::AclConfig;
69use crate::migration::alc::plain_access_config::PlainAccessConfig;
70use crate::migration::alc::plain_permission_manager::PlainPermissionManager;
71use crate::permission::Permission;
72use crate::project_authorization_error;
73use crate::AuthFailureKind;
74use crate::AuthMetrics;
75use crate::AuthMetricsSnapshot;
76use crate::AuthOperation;
77use crate::AuthServiceError;
78use crate::AuthServiceResult;
79use crate::RemotingAuthContext;
80
81const ACCESS_KEY: &str = "AccessKey";
82
83mod startup;
84pub use startup::AuthStartupFailure;
85#[cfg(test)]
86mod provider_tests;
87
88#[derive(Clone)]
89pub struct ProviderRegistry {
90 owner: Arc<crate::provider_owner::ProviderOwner>,
91 acl_white_list_snapshot: Arc<RwLock<WhiteList>>,
92 acl_managed_access_keys: Arc<RwLock<HashSet<String>>>,
93 acl_generation: Arc<AtomicU64>,
94 acl_fingerprint: Arc<RwLock<Option<AclConfigFingerprint>>>,
95 metrics: AuthMetrics,
96}
97
98impl ProviderRegistry {
99 pub fn local(config: &AuthConfig) -> AuthServiceResult<Self> {
100 Self::local_with_metadata_io(config, None)
101 }
102
103 pub fn local_with_metadata_io(
104 config: &AuthConfig,
105 metadata_io: Option<MetadataIoActor>,
106 ) -> AuthServiceResult<Self> {
107 validate_metadata_provider_name(
108 "authenticationMetadataProvider",
109 config.authentication_metadata_provider.as_str(),
110 &[
111 "LocalAuthenticationMetadataProvider",
112 "FileSnapshotAuthenticationMetadataProvider",
113 "local",
114 "file",
115 ],
116 )?;
117 validate_metadata_provider_name(
118 "authorizationMetadataProvider",
119 config.authorization_metadata_provider.as_str(),
120 &[
121 "LocalAuthorizationMetadataProvider",
122 "FileSnapshotAuthorizationMetadataProvider",
123 "local",
124 "file",
125 ],
126 )?;
127
128 let authentication_metadata_provider = Arc::new(
129 LocalAuthenticationMetadataProvider::with_config_and_metadata_io(config, metadata_io.clone())?,
130 );
131 let mut authorization_metadata_provider = metadata_io.map_or_else(
132 LocalAuthorizationMetadataProvider::new,
133 LocalAuthorizationMetadataProvider::with_metadata_io,
134 );
135 authorization_metadata_provider.initialize(config.clone(), None)?;
136
137 Ok(Self::from_bundle(
138 crate::provider_owner::local_bundle(
139 authentication_metadata_provider,
140 Arc::new(authorization_metadata_provider),
141 ),
142 true,
143 ))
144 }
145
146 fn from_bundle(bundle: crate::ProviderBundle, initialized: bool) -> Self {
147 Self {
148 owner: crate::provider_owner::ProviderOwner::new(bundle, initialized),
149 acl_white_list_snapshot: Arc::new(RwLock::new(WhiteList::default())),
150 acl_managed_access_keys: Arc::new(RwLock::new(HashSet::new())),
151 acl_generation: Arc::new(AtomicU64::new(0)),
152 acl_fingerprint: Arc::new(RwLock::new(None)),
153 metrics: AuthMetrics::default(),
154 }
155 }
156
157 pub async fn load_with_metadata_io(
160 config: &AuthConfig,
161 metadata_io: MetadataIoActor,
162 blocking: BlockingExecutor,
163 ) -> AuthServiceResult<Self> {
164 let config = config.clone();
165 blocking
166 .spawn_io("auth.provider-registry.load", move || {
167 Self::local_with_metadata_io(&config, Some(metadata_io))
168 })
169 .await
170 .map_err(AuthServiceError::metadata_io)?
171 }
172
173 pub fn authentication_metadata_provider(&self) -> Arc<crate::UserMetadataHandle> {
174 self.owner.users.clone()
175 }
176
177 pub(crate) fn admission(&self) -> Arc<crate::provider_owner::ProviderAdmission> {
178 self.owner.admission.clone()
179 }
180
181 pub fn authorization_metadata_provider(&self) -> Arc<crate::AclMetadataHandle> {
182 self.owner.acls.clone()
183 }
184
185 pub async fn shutdown_until(&self, deadline: rocketmq_runtime::ShutdownDeadline) -> AuthServiceResult<()> {
186 self.owner.shutdown_until(deadline, true).await
187 }
188
189 fn set_acl_white_list_snapshot(&self, snapshot: WhiteList) -> AuthServiceResult<()> {
190 let mut guard = self
191 .acl_white_list_snapshot
192 .write()
193 .map_err(|_| AuthServiceError::storage_lock_failed("auth.acl.white-list"))?;
194 *guard = snapshot;
195 Ok(())
196 }
197
198 fn acl_managed_access_keys(&self) -> AuthServiceResult<HashSet<String>> {
199 let guard = self
200 .acl_managed_access_keys
201 .read()
202 .map_err(|_| AuthServiceError::storage_lock_failed("auth.acl.managed-keys"))?;
203 Ok(guard.clone())
204 }
205
206 fn set_acl_managed_access_keys(&self, access_keys: HashSet<String>) -> AuthServiceResult<()> {
207 let mut guard = self
208 .acl_managed_access_keys
209 .write()
210 .map_err(|_| AuthServiceError::storage_lock_failed("auth.acl.managed-keys"))?;
211 *guard = access_keys;
212 Ok(())
213 }
214
215 fn advance_acl_generation(&self) -> u64 {
216 self.metrics.record_cache_invalidation();
217 self.acl_generation.fetch_add(1, AtomicOrdering::AcqRel) + 1
218 }
219
220 pub fn acl_generation(&self) -> u64 {
221 self.acl_generation.load(AtomicOrdering::Acquire)
222 }
223
224 pub fn acl_generation_counter(&self) -> Arc<AtomicU64> {
225 self.acl_generation.clone()
226 }
227
228 pub fn metrics(&self) -> AuthMetrics {
229 self.metrics.clone()
230 }
231
232 pub fn metrics_snapshot(&self) -> AuthMetricsSnapshot {
233 self.metrics.snapshot()
234 }
235
236 fn acl_fingerprint(&self) -> AuthServiceResult<Option<AclConfigFingerprint>> {
237 let guard = self
238 .acl_fingerprint
239 .read()
240 .map_err(|_| AuthServiceError::storage_lock_failed("auth.acl.fingerprint"))?;
241 Ok(*guard)
242 }
243
244 fn set_acl_fingerprint(&self, fingerprint: Option<AclConfigFingerprint>) -> AuthServiceResult<()> {
245 let mut guard = self
246 .acl_fingerprint
247 .write()
248 .map_err(|_| AuthServiceError::storage_lock_failed("auth.acl.fingerprint"))?;
249 *guard = fingerprint;
250 Ok(())
251 }
252
253 pub fn is_acl_white_remote_address(
254 &self,
255 access_key: Option<&str>,
256 source_ip: Option<&str>,
257 ) -> AuthServiceResult<bool> {
258 let _operation = self.owner.admission.enter()?;
259 let guard = self
260 .acl_white_list_snapshot
261 .read()
262 .map_err(|_| AuthServiceError::storage_lock_failed("auth.acl.white-list"))?;
263 let matched = guard.matches(access_key, source_ip);
264 self.metrics.record_whitelist_check(matched);
265 Ok(matched)
266 }
267
268 pub fn update_global_white_remote_addresses<I, S>(&self, addresses: I) -> AuthServiceResult<u64>
269 where
270 I: IntoIterator<Item = S>,
271 S: AsRef<str>,
272 {
273 let _operation = self.owner.admission.enter()?;
274 let updated_snapshot = {
275 let guard = self
276 .acl_white_list_snapshot
277 .read()
278 .map_err(|_| AuthServiceError::storage_lock_failed("auth.acl.white-list"))?;
279 guard.with_global_patterns(addresses)
280 };
281 self.set_acl_white_list_snapshot(updated_snapshot)?;
282 Ok(self.advance_acl_generation())
283 }
284}
285
286fn validate_metadata_provider_name(key: &'static str, configured: &str, supported: &[&str]) -> AuthServiceResult<()> {
287 let configured = configured.trim();
288 if configured.is_empty()
289 || supported
290 .iter()
291 .any(|candidate| configured.eq_ignore_ascii_case(candidate))
292 {
293 return Ok(());
294 }
295
296 let _ = (key, configured, supported);
297 Err(AuthServiceError::configuration_error("unsupported metadata provider"))
298}
299
300pub struct AuthRuntimeBuilder {
301 config: AuthConfig,
302 service_context: ChildServiceContext,
303 provider_registry: Option<ProviderRegistry>,
304 metadata_io: Option<MetadataIoActor>,
305}
306
307impl AuthRuntimeBuilder {
308 pub fn new(config: AuthConfig, service_context: ChildServiceContext) -> Self {
309 Self {
310 config,
311 service_context,
312 provider_registry: None,
313 metadata_io: None,
314 }
315 }
316
317 pub fn with_provider_registry(mut self, provider_registry: ProviderRegistry) -> Self {
318 self.provider_registry = Some(provider_registry);
319 self
320 }
321
322 pub fn with_provider_bundle(mut self, providers: crate::ProviderBundle) -> Self {
323 self.provider_registry = Some(ProviderRegistry::from_bundle(providers, false));
324 self
325 }
326
327 pub fn with_metadata_io_actor(mut self, metadata_io: MetadataIoActor) -> Self {
328 self.metadata_io = Some(metadata_io);
329 self
330 }
331
332 pub async fn build(self) -> AuthServiceResult<AuthRuntime> {
333 let owns_metadata_io = self.metadata_io.is_none();
334 let metadata_io = match self.metadata_io.clone() {
335 Some(metadata_io) => metadata_io,
336 None => match MetadataIoConfig::default()
337 .into_plan()
338 .expect("default metadata I/O config is valid")
339 .start(&self.service_context.component("auth.metadata-io"))
340 {
341 Ok(metadata_io) => metadata_io,
342 Err(error) => {
343 return Err(startup::rollback(
344 AuthServiceError::provider_failed(AuthOperation::Initialize, error),
345 self.provider_registry.as_ref().map(|registry| registry.owner.clone()),
346 None,
347 self.service_context,
348 )
349 .await);
350 }
351 },
352 };
353 let registry_result = match self.provider_registry.clone() {
354 Some(provider_registry) => Ok(provider_registry),
355 None => {
356 ProviderRegistry::load_with_metadata_io(
357 &self.config,
358 metadata_io.clone(),
359 self.service_context.metadata_io().clone(),
360 )
361 .await
362 }
363 };
364
365 let provider_registry = match registry_result {
366 Ok(registry) => registry,
367 Err(error) => {
368 return Err(startup::rollback(
369 error,
370 None,
371 owns_metadata_io.then_some(metadata_io),
372 self.service_context,
373 )
374 .await);
375 }
376 };
377 let prepared = self.prepare_services(&provider_registry).await;
378 let (authentication_service, authorization_service, acl_file_watch_handle) = match prepared {
379 Ok(services) => services,
380 Err(error) => {
381 return Err(startup::rollback(
382 error,
383 Some(provider_registry.owner.clone()),
384 owns_metadata_io.then_some(metadata_io),
385 self.service_context,
386 )
387 .await);
388 }
389 };
390
391 Ok(AuthRuntime {
392 config: self.config,
393 service_context: self.service_context,
394 provider_registry,
395 authentication_service,
396 authorization_service,
397 acl_file_watch_handle,
398 owned_metadata_io: owns_metadata_io.then_some(metadata_io),
399 shutdown_report: Arc::new(tokio::sync::Mutex::new(None)),
400 })
401 }
402
403 async fn prepare_services(
404 &self,
405 provider_registry: &ProviderRegistry,
406 ) -> AuthServiceResult<(AuthenticationService, AuthorizationService, Option<AclFileWatchHandle>)> {
407 validate_metadata_provider_name(
408 "authenticationProvider",
409 self.config
410 .authentication_provider
411 .as_str()
412 .rsplit('.')
413 .next()
414 .unwrap_or_default(),
415 &["DefaultAuthenticationProvider", "default"],
416 )?;
417 validate_metadata_provider_name(
418 "authorizationProvider",
419 self.config
420 .authorization_provider
421 .as_str()
422 .rsplit('.')
423 .next()
424 .unwrap_or_default(),
425 &["DefaultAuthorizationProvider", "default"],
426 )?;
427 if (!self.config.acl_file.trim().is_empty() || self.config.migrate_auth_from_v1_enabled)
429 && provider_registry.owner.importer.is_none()
430 {
431 return Err(AuthServiceError::new(
432 AuthOperation::InitializeProvider,
433 AuthFailureKind::Unsupported,
434 ));
435 }
436
437 provider_registry
438 .owner
439 .initialize(&self.config, &self.service_context)
440 .await?;
441 seed_initial_users(provider_registry, &self.config).await?;
442 let migrated_acl_entries = migrate_auth_from_v1(provider_registry, &self.config).await?;
443 if migrated_acl_entries > 0 {
444 info!(
445 "Migrated {} legacy ACL account(s) into auth metadata",
446 migrated_acl_entries
447 );
448 }
449 let loaded_acl_entries = load_configured_acl_file(
450 provider_registry,
451 &self.config,
452 self.service_context.metadata_io().clone(),
453 true,
454 )
455 .await?
456 .account_count;
457 if loaded_acl_entries > 0 {
458 info!("Loaded {} ACL account(s) from configured ACL file", loaded_acl_entries);
459 }
460
461 let mut authentication_provider = DefaultAuthenticationProvider::new();
462 authentication_provider.initialize_with_registry(self.config.clone(), provider_registry.clone())?;
463
464 let mut authorization_provider = DefaultAuthorizationProvider::new();
465 authorization_provider.initialize_with_registry(self.config.clone(), provider_registry.clone())?;
466
467 let authentication_service = AuthenticationService::new(
468 self.config.clone(),
469 Arc::new(authentication_provider),
470 provider_registry.metrics(),
471 provider_registry.owner.admission.clone(),
472 );
473 let authorization_service = AuthorizationService::new(
474 self.config.clone(),
475 Arc::new(authorization_provider),
476 provider_registry.metrics(),
477 provider_registry.owner.admission.clone(),
478 );
479 let acl_file_watch_handle =
480 start_acl_file_watcher(&self.config, provider_registry.clone(), &self.service_context)?;
481
482 Ok((authentication_service, authorization_service, acl_file_watch_handle))
483 }
484}
485
486#[derive(Clone)]
487pub struct AuthRuntime {
488 config: AuthConfig,
489 service_context: ChildServiceContext,
490 provider_registry: ProviderRegistry,
491 authentication_service: AuthenticationService,
492 authorization_service: AuthorizationService,
493 acl_file_watch_handle: Option<AclFileWatchHandle>,
494 owned_metadata_io: Option<MetadataIoActor>,
495 shutdown_report: Arc<tokio::sync::Mutex<Option<ShutdownReport>>>,
496}
497
498impl AuthRuntime {
499 pub fn enter_request(&self) -> AuthServiceResult<crate::AuthRequestGuard> {
504 self.provider_registry.owner.admission.enter()
505 }
506
507 pub fn config(&self) -> &AuthConfig {
508 &self.config
509 }
510
511 pub fn provider_registry(&self) -> &ProviderRegistry {
512 &self.provider_registry
513 }
514
515 pub fn authentication_service(&self) -> &AuthenticationService {
516 &self.authentication_service
517 }
518
519 pub fn authorization_service(&self) -> &AuthorizationService {
520 &self.authorization_service
521 }
522
523 pub fn enabled(&self) -> bool {
524 self.config.authentication_enabled || self.config.authorization_enabled
525 }
526
527 #[must_use]
533 pub const fn detailed_authorization_requirement(&self) -> LayerRequirement {
534 if self.config.authorization_enabled {
535 LayerRequirement::Required
536 } else {
537 LayerRequirement::Optional
538 }
539 }
540
541 pub async fn reload_acl_file(&self) -> AuthServiceResult<usize> {
542 let _operation = self.provider_registry.owner.admission.enter()?;
543 Ok(load_configured_acl_file(
544 &self.provider_registry,
545 &self.config,
546 self.service_context.metadata_io().clone(),
547 true,
548 )
549 .await?
550 .account_count)
551 }
552
553 pub fn is_acl_white_remote_address(
554 &self,
555 access_key: Option<&str>,
556 source_ip: Option<&str>,
557 ) -> AuthServiceResult<bool> {
558 self.provider_registry
559 .is_acl_white_remote_address(access_key, source_ip)
560 }
561
562 pub fn acl_generation(&self) -> u64 {
563 self.provider_registry.acl_generation()
564 }
565
566 pub fn invalidate_acl_cache(&self) -> u64 {
567 self.provider_registry.advance_acl_generation()
568 }
569
570 pub fn acl_generation_counter(&self) -> Arc<AtomicU64> {
571 self.provider_registry.acl_generation_counter()
572 }
573
574 pub fn metrics(&self) -> AuthMetrics {
575 self.provider_registry.metrics()
576 }
577
578 pub fn metrics_snapshot(&self) -> AuthMetricsSnapshot {
579 self.provider_registry.metrics_snapshot()
580 }
581
582 pub fn update_global_white_remote_addresses<I, S>(&self, addresses: I) -> AuthServiceResult<u64>
583 where
584 I: IntoIterator<Item = S>,
585 S: AsRef<str>,
586 {
587 self.provider_registry.update_global_white_remote_addresses(addresses)
588 }
589
590 pub async fn shutdown(&self) -> AuthServiceResult<()> {
591 let _ = self.shutdown_with_report().await?;
592 Ok(())
593 }
594
595 pub async fn shutdown_with_report(&self) -> AuthServiceResult<Option<ShutdownReport>> {
596 self.shutdown_until(rocketmq_runtime::ShutdownDeadline::after(Duration::from_secs(5)))
597 .await
598 }
599
600 pub async fn shutdown_until(
601 &self,
602 deadline: rocketmq_runtime::ShutdownDeadline,
603 ) -> AuthServiceResult<Option<ShutdownReport>> {
604 self.provider_registry.owner.admission.close();
605 let at = tokio::time::Instant::from_std(deadline.instant());
606 let timeout = || AuthServiceError::new(AuthOperation::MaintainService, AuthFailureKind::Timeout);
607 let mut completed = tokio::time::timeout_at(at, self.shutdown_report.lock())
608 .await
609 .map_err(|_| timeout())?;
610 if completed.is_some() {
611 return Ok(completed.clone());
612 }
613 if let Some(watcher) = &self.acl_file_watch_handle {
614 let report = watcher.scheduled_tasks.shutdown(deadline.remaining()).await;
615 if !report.is_healthy() {
616 return Err(timeout());
617 }
618 }
619 self.provider_registry.shutdown_until(deadline).await?;
620 if let Some(metadata_io) = &self.owned_metadata_io {
621 let report = metadata_io
622 .shutdown_until(rocketmq_runtime::MetadataDeadline::at(at))
623 .await;
624 if report.timed_out || report.pending_operations != 0 {
625 return Err(timeout());
626 }
627 }
628 let report = self.service_context.task_group().shutdown(deadline.remaining()).await;
629 if !report.is_healthy() {
630 return Err(timeout());
631 }
632 *completed = Some(report.clone());
633 Ok(Some(report))
634 }
635
636 pub fn acl_file_watcher_snapshot(&self) -> Vec<ScheduledTaskSnapshot> {
637 self.acl_file_watch_handle
638 .as_ref()
639 .map(AclFileWatchHandle::snapshot)
640 .unwrap_or_default()
641 }
642
643 pub async fn check_remoting(
644 &self,
645 auth_context: &RemotingAuthContext,
646 command: &RemotingCommand,
647 ) -> AuthServiceResult<()> {
648 self.check_remoting_for_code(auth_context, command, command.code())
649 .await
650 }
651
652 pub async fn check_remoting_for_code(
655 &self,
656 auth_context: &RemotingAuthContext,
657 command: &RemotingCommand,
658 original_code: i32,
659 ) -> AuthServiceResult<()> {
660 let mut authoritative_command = command.clone();
661 authoritative_command.set_code_mut(original_code);
662 self.check_remoting_with_source_ip(
663 auth_context,
664 &authoritative_command,
665 auth_context.source_ip(),
666 auth_context.channel_id(),
667 )
668 .await
669 }
670
671 pub async fn check_remoting_with_source_ip(
672 &self,
673 auth_context: &RemotingAuthContext,
674 command: &RemotingCommand,
675 source_ip: Option<&str>,
676 channel_id: Option<&str>,
677 ) -> AuthServiceResult<()> {
678 let _operation = self.provider_registry.owner.admission.enter()?;
679 auth_context.validate()?;
680 if source_ip != auth_context.source_ip() || channel_id != auth_context.channel_id() {
681 return Err(AuthServiceError::new(
682 AuthOperation::Authenticate,
683 AuthFailureKind::Unauthenticated,
684 ));
685 }
686 if self.is_acl_white_remote_address(access_key_from_command(command), source_ip)? {
687 return Ok(());
688 }
689
690 self.authentication_service
691 .authenticate_remoting(command, channel_id)
692 .await?;
693 self.authorization_service
694 .authorize_remoting(auth_context, command)
695 .await
696 }
697
698 pub async fn evaluate_remoting_detailed(
705 &self,
706 auth_context: &RemotingAuthContext,
707 command: &RemotingCommand,
708 ) -> LayerEvaluation<DetailedDecision> {
709 self.evaluate_remoting_detailed_for_code(auth_context, command, command.code())
710 .await
711 }
712
713 pub async fn evaluate_remoting_detailed_for_code(
716 &self,
717 auth_context: &RemotingAuthContext,
718 command: &RemotingCommand,
719 original_code: i32,
720 ) -> LayerEvaluation<DetailedDecision> {
721 let mut authoritative_command = command.clone();
722 authoritative_command.set_code_mut(original_code);
723 let command = &authoritative_command;
724 auth_context.validate().map_err(|_| LayerFailureKind::Error)?;
725 let is_whitelisted = self
726 .is_acl_white_remote_address(access_key_from_command(command), auth_context.source_ip())
727 .map_err(|_| LayerFailureKind::Error)?;
728 if is_whitelisted {
729 return Ok(DetailedDecision::Allow);
730 }
731
732 self.authentication_service
733 .authenticate_remoting(command, auth_context.channel_id())
734 .await
735 .map_err(|_| LayerFailureKind::Error)?;
736 self.authorization_service
737 .authorize_remoting_detailed(auth_context, command)
738 .await
739 }
740
741 pub async fn authenticate_maintenance_principal(
747 &self,
748 command: &RemotingCommand,
749 channel_id: Option<&str>,
750 ) -> AuthServiceResult<CheetahString> {
751 self.authentication_service
752 .authenticate_maintenance_principal(command, channel_id)
753 .await
754 }
755}
756
757#[derive(Clone)]
758struct AclFileWatchHandle {
759 scheduled_tasks: ScheduledTaskGroup,
760}
761
762impl AclFileWatchHandle {
763 fn snapshot(&self) -> Vec<ScheduledTaskSnapshot> {
764 self.scheduled_tasks.snapshot()
765 }
766}
767
768#[derive(Clone)]
769pub struct AuthenticationService {
770 config: AuthConfig,
771 whitelist: HashSet<String>,
772 provider: Arc<DefaultAuthenticationProvider>,
773 builder: DefaultAuthenticationContextBuilder,
774 metrics: AuthMetrics,
775 admission: Arc<crate::provider_owner::ProviderAdmission>,
776}
777
778impl AuthenticationService {
779 fn new(
780 config: AuthConfig,
781 provider: Arc<DefaultAuthenticationProvider>,
782 metrics: AuthMetrics,
783 admission: Arc<crate::provider_owner::ProviderAdmission>,
784 ) -> Self {
785 Self {
786 whitelist: parse_whitelist(config.authentication_whitelist.as_str()),
787 config,
788 provider,
789 builder: DefaultAuthenticationContextBuilder::new(),
790 metrics,
791 admission,
792 }
793 }
794
795 pub async fn authenticate_remoting(
796 &self,
797 command: &RemotingCommand,
798 channel_id: Option<&str>,
799 ) -> AuthServiceResult<()> {
800 let _operation = self.admission.enter()?;
801 if !self.config.authentication_enabled {
802 return Ok(());
803 }
804 if self.whitelist.contains(&command.code().to_string()) {
805 self.metrics.record_whitelist_check(true);
806 return Ok(());
807 }
808
809 let context = self.builder.build_from_remoting(command, channel_id).inspect_err(|_| {
810 self.metrics.record_authentication_result(false);
811 })?;
812 self.provider.authenticate(&context).await
813 }
814
815 pub async fn authenticate_maintenance_principal(
823 &self,
824 command: &RemotingCommand,
825 channel_id: Option<&str>,
826 ) -> AuthServiceResult<CheetahString> {
827 let _operation = self.admission.enter()?;
828 if !self.config.authentication_enabled {
829 return Err(AuthServiceError::new(
830 AuthOperation::Authenticate,
831 AuthFailureKind::Unauthenticated,
832 ));
833 }
834 let context = self.builder.build_from_remoting(command, channel_id).inspect_err(|_| {
835 self.metrics.record_authentication_result(false);
836 })?;
837 let principal = context
838 .username()
839 .filter(|username| !username.trim().is_empty())
840 .cloned()
841 .ok_or_else(|| AuthServiceError::new(AuthOperation::Authenticate, AuthFailureKind::Unauthenticated))?;
842 self.provider.authenticate(&context).await?;
843 Ok(principal)
844 }
845}
846
847#[derive(Clone)]
848pub struct AuthorizationService {
849 config: AuthConfig,
850 whitelist: HashSet<String>,
851 provider: Arc<DefaultAuthorizationProvider>,
852 metrics: AuthMetrics,
853 admission: Arc<crate::provider_owner::ProviderAdmission>,
854}
855
856impl AuthorizationService {
857 fn new(
858 config: AuthConfig,
859 provider: Arc<DefaultAuthorizationProvider>,
860 metrics: AuthMetrics,
861 admission: Arc<crate::provider_owner::ProviderAdmission>,
862 ) -> Self {
863 Self {
864 whitelist: parse_whitelist(config.authorization_whitelist.as_str()),
865 config,
866 provider,
867 metrics,
868 admission,
869 }
870 }
871
872 pub async fn authorize_remoting(
873 &self,
874 auth_context: &RemotingAuthContext,
875 command: &RemotingCommand,
876 ) -> AuthServiceResult<()> {
877 let _operation = self.admission.enter()?;
878 auth_context.validate()?;
879 if !self.config.authorization_enabled {
880 return Ok(());
881 }
882 if self.whitelist.contains(&command.code().to_string()) {
883 self.metrics.record_whitelist_check(true);
884 return Ok(());
885 }
886
887 let contexts = self
888 .provider
889 .new_contexts_from_remoting_command(auth_context, command)
890 .inspect_err(|_| {
891 self.metrics.record_authorization_result(false);
892 })?;
893
894 for context in contexts {
895 let decision = self.provider.authorize(&context).await?;
896 require_authorization(decision)?;
897 }
898
899 Ok(())
900 }
901
902 pub async fn authorize_remoting_detailed(
908 &self,
909 auth_context: &RemotingAuthContext,
910 command: &RemotingCommand,
911 ) -> LayerEvaluation<DetailedDecision> {
912 let _operation = self.admission.enter().map_err(|_| LayerFailureKind::Error)?;
913 auth_context.validate().map_err(|_| LayerFailureKind::Error)?;
914 if !self.config.authorization_enabled {
915 return Ok(DetailedDecision::Abstain);
916 }
917 if self.whitelist.contains(&command.code().to_string()) {
918 self.metrics.record_whitelist_check(true);
919 return Ok(DetailedDecision::Allow);
920 }
921
922 let contexts = self
923 .provider
924 .new_contexts_from_remoting_command(auth_context, command)
925 .map_err(|error| {
926 self.metrics.record_authorization_result(false);
927 project_authorization_error(&error)
928 })?;
929
930 for context in contexts {
931 match self.provider.authorize(&context).await {
932 Ok(AuthorizationDecision::Allow) => {}
933 Ok(AuthorizationDecision::Deny(_)) => {
934 return Ok(DetailedDecision::Deny);
935 }
936 Err(error) => {
937 return Err(project_authorization_error(&error));
938 }
939 }
940 }
941
942 Ok(DetailedDecision::Allow)
943 }
944}
945
946fn parse_whitelist(value: &str) -> HashSet<String> {
947 value
948 .split(',')
949 .map(str::trim)
950 .filter(|entry| !entry.is_empty())
951 .map(ToOwned::to_owned)
952 .collect()
953}
954
955#[derive(Clone, Copy, Debug, PartialEq, Eq)]
956struct AclReloadResult {
957 account_count: usize,
958 changed: bool,
959}
960
961async fn load_configured_acl_file(
962 provider_registry: &ProviderRegistry,
963 config: &AuthConfig,
964 blocking: rocketmq_runtime::BlockingExecutor,
965 force: bool,
966) -> AuthServiceResult<AclReloadResult> {
967 let acl_file = config.acl_file.as_str().trim();
968 if acl_file.is_empty() {
969 return Ok(AclReloadResult {
970 account_count: 0,
971 changed: false,
972 });
973 }
974
975 let metrics = provider_registry.metrics();
976 metrics.record_acl_reload_attempt();
977 let result = async {
978 let loader = FileAclConfigLoader::new(acl_file.to_owned(), blocking);
979 let (acl_config, fingerprint) = loader.load_with_fingerprint().await?;
980 if !force && provider_registry.acl_fingerprint()? == Some(fingerprint) {
981 return Ok(AclReloadResult {
982 account_count: 0,
983 changed: false,
984 });
985 }
986
987 let count = apply_acl_config(provider_registry, &acl_config).await?;
988 provider_registry.set_acl_fingerprint(Some(fingerprint))?;
989 Ok(AclReloadResult {
990 account_count: count,
991 changed: true,
992 })
993 }
994 .await;
995
996 match &result {
997 Ok(reload_result) if reload_result.changed => metrics.record_acl_reload_success(),
998 Ok(_) => metrics.record_acl_reload_skipped(),
999 Err(_) => metrics.record_acl_reload_failure(),
1000 }
1001
1002 result
1003}
1004
1005async fn migrate_auth_from_v1(provider_registry: &ProviderRegistry, config: &AuthConfig) -> AuthServiceResult<usize> {
1006 if !config.migrate_auth_from_v1_enabled {
1007 return Ok(0);
1008 }
1009 let plain_permission_manager = PlainPermissionManager::new();
1010 migrate_auth_from_v1_manager(provider_registry, &plain_permission_manager).await
1011}
1012
1013async fn migrate_auth_from_v1_manager(
1014 provider_registry: &ProviderRegistry,
1015 plain_permission_manager: &PlainPermissionManager,
1016) -> AuthServiceResult<usize> {
1017 let acl_config = plain_permission_manager.get_all_acl_config()?;
1018 apply_acl_config(provider_registry, &acl_config).await
1019}
1020
1021fn start_acl_file_watcher(
1022 config: &AuthConfig,
1023 provider_registry: ProviderRegistry,
1024 service_context: &ChildServiceContext,
1025) -> AuthServiceResult<Option<AclFileWatchHandle>> {
1026 let acl_file = config.acl_file.as_str().trim();
1027 if acl_file.is_empty() || !config.acl_file_watch_enabled {
1028 return Ok(None);
1029 }
1030
1031 let watch_config = config.clone();
1032 let interval = Duration::from_millis(config.acl_file_watch_interval_millis.max(1));
1033 let watcher_context = service_context.component("auth.acl-file-watcher");
1034 let task_group = watcher_context.task_group().clone();
1035 let scheduled_tasks = ScheduledTaskGroup::new(task_group.clone());
1036 let blocking = service_context.metadata_io().clone();
1037 scheduled_tasks
1038 .schedule(
1039 ScheduledTaskConfig::fixed_rate_no_overlap("auth.acl-file-watcher.reload", interval),
1040 ScheduledExecutionPolicy::default(),
1041 move || {
1042 let provider_registry = provider_registry.clone();
1043 let watch_config = watch_config.clone();
1044 let blocking = blocking.clone();
1045 async move {
1046 match load_configured_acl_file(&provider_registry, &watch_config, blocking, false).await {
1047 Ok(result) if result.changed => {
1048 debug!(
1049 "Reloaded {} ACL account(s) from configured ACL file",
1050 result.account_count
1051 )
1052 }
1053 Ok(_) => debug!("ACL file unchanged; skipped reload"),
1054 Err(error) => warn!("Failed to reload ACL file: {error}"),
1055 }
1056 }
1057 },
1058 )
1059 .map_err(|error| AuthServiceError::provider_failed(AuthOperation::Initialize, error))?;
1060
1061 Ok(Some(AclFileWatchHandle { scheduled_tasks }))
1062}
1063
1064async fn apply_acl_config(provider_registry: &ProviderRegistry, acl_config: &AclConfig) -> AuthServiceResult<usize> {
1065 let prepared_config = prepare_acl_config(acl_config)?;
1066 let previous_access_keys = provider_registry.acl_managed_access_keys()?;
1067 let importer = provider_registry
1068 .owner
1069 .importer
1070 .as_ref()
1071 .ok_or_else(|| AuthServiceError::new(AuthOperation::InitializeProvider, AuthFailureKind::Unsupported))?;
1072 let removed: Vec<_> = previous_access_keys
1073 .difference(&prepared_config.access_keys)
1074 .map(|key| crate::SubjectKey::from_subject(&User::of(key.as_str())))
1075 .collect();
1076 let _operation = provider_registry.owner.admission.enter()?;
1077 importer.import(&prepared_config.accounts, &removed).await?;
1078
1079 provider_registry.set_acl_white_list_snapshot(prepared_config.white_list_snapshot)?;
1080 provider_registry.set_acl_managed_access_keys(prepared_config.access_keys)?;
1081 provider_registry.advance_acl_generation();
1082
1083 Ok(prepared_config.accounts_len)
1084}
1085
1086struct PreparedAclConfig {
1087 white_list_snapshot: WhiteList,
1088 access_keys: HashSet<String>,
1089 accounts: Vec<crate::AclImportAccount>,
1090 accounts_len: usize,
1091}
1092
1093fn prepare_acl_config(acl_config: &AclConfig) -> AuthServiceResult<PreparedAclConfig> {
1094 let mut access_keys = HashSet::new();
1095 let mut accounts = Vec::new();
1096
1097 if let Some(plain_accounts) = acl_config.plain_access_configs() {
1098 for account in plain_accounts {
1099 let access_key = required_plain_field(account.access_key(), "accessKey", "<missing>")?.to_owned();
1100 let user = user_from_plain_account(account)?;
1101 let acl = acl_from_plain_account(account)?;
1102 access_keys.insert(access_key);
1103 accounts.push(crate::AclImportAccount { user, acl });
1104 }
1105 }
1106
1107 let accounts_len = accounts.len();
1108 Ok(PreparedAclConfig {
1109 white_list_snapshot: WhiteList::from_acl_config(acl_config),
1110 access_keys,
1111 accounts,
1112 accounts_len,
1113 })
1114}
1115
1116fn user_from_plain_account(account: &PlainAccessConfig) -> AuthServiceResult<User> {
1117 let access_key = required_plain_field(account.access_key(), "accessKey", "<missing>")?;
1118 let secret_key = required_plain_field(account.secret_key(), "secretKey", access_key)?;
1119 let user_type = if account.is_admin() {
1120 UserType::Super
1121 } else {
1122 UserType::Normal
1123 };
1124 let mut user = User::of_with_type(access_key, secret_key, user_type);
1125 user.set_user_status(UserStatus::Enable);
1126 Ok(user)
1127}
1128
1129fn acl_from_plain_account(account: &PlainAccessConfig) -> AuthServiceResult<Option<Acl>> {
1130 let access_key = required_plain_field(account.access_key(), "accessKey", "<missing>")?;
1131 let mut policies = Vec::new();
1132 let mut default_entries = Vec::new();
1133 let mut custom_entries = Vec::new();
1134
1135 if let Some(permission) = account.default_topic_perm() {
1136 push_default_entry(&mut default_entries, ResourceType::Topic, permission.as_str());
1137 }
1138 if let Some(permission) = account.default_group_perm() {
1139 push_default_entry(&mut default_entries, ResourceType::Group, permission.as_str());
1140 }
1141 if let Some(topic_perms) = account.topic_perms() {
1142 push_named_entries(&mut custom_entries, ResourceType::Topic, topic_perms);
1143 }
1144 if let Some(group_perms) = account.group_perms() {
1145 push_named_entries(&mut custom_entries, ResourceType::Group, group_perms);
1146 }
1147 if let Some(permission) = account.cluster_perm() {
1148 push_cluster_entry(&mut default_entries, permission.as_str())?;
1149 }
1150
1151 if !custom_entries.is_empty() {
1152 policies.push(Policy::of_entries(PolicyType::Custom, custom_entries));
1153 }
1154 if !default_entries.is_empty() {
1155 policies.push(Policy::of_entries(PolicyType::Default, default_entries));
1156 }
1157
1158 if policies.is_empty() {
1159 return Ok(None);
1160 }
1161
1162 Ok(Some(Acl::of_with_policies(
1163 access_key.to_owned(),
1164 SubjectType::User,
1165 policies,
1166 )))
1167}
1168
1169fn push_cluster_entry(entries: &mut Vec<PolicyEntry>, permission: &str) -> AuthServiceResult<()> {
1170 let permission = permission.trim();
1171 let (actions, decision) = match permission {
1172 "GET" => (
1173 vec![rocketmq_security_api::Action::Get],
1174 crate::authorization::enums::decision::Decision::Allow,
1175 ),
1176 "DENY" => (
1177 vec![rocketmq_security_api::Action::All],
1178 crate::authorization::enums::decision::Decision::Deny,
1179 ),
1180 _ => {
1181 return Err(AuthServiceError::configuration_error("invalid cluster permission"));
1182 }
1183 };
1184 entries.push(PolicyEntry::of(
1185 Resource::of(ResourceType::Cluster, None, ResourcePattern::Any),
1186 actions,
1187 None,
1188 decision,
1189 ));
1190 Ok(())
1191}
1192
1193fn push_default_entry(entries: &mut Vec<PolicyEntry>, resource_type: ResourceType, permission: &str) {
1194 let (actions, decision) = Permission::migration_actions_and_decision(Some(permission));
1195 entries.push(PolicyEntry::of(
1196 Resource::of(resource_type, None, ResourcePattern::Any),
1197 actions,
1198 None,
1199 decision,
1200 ));
1201}
1202
1203fn push_named_entries(
1204 entries: &mut Vec<PolicyEntry>,
1205 resource_type: ResourceType,
1206 permissions: &[cheetah_string::CheetahString],
1207) {
1208 for permission_entry in permissions {
1209 let raw = permission_entry.as_str().trim();
1210 if raw.is_empty() {
1211 continue;
1212 }
1213 let Some((resource_name, permission)) = raw.split_once('=') else {
1214 warn!("Skipping ACL policy entry without permission assignment: {raw}");
1215 continue;
1216 };
1217 let resource_name = resource_name.trim();
1218 if resource_name.is_empty() {
1219 warn!("Skipping ACL policy entry with blank resource name");
1220 continue;
1221 }
1222
1223 let resource = match resource_type {
1224 ResourceType::Topic => Resource::of_topic(resource_name),
1225 ResourceType::Group => Resource::of_group(resource_name.to_owned()),
1226 _ => Resource::of(resource_type, Some(resource_name.to_owned()), ResourcePattern::Literal),
1227 };
1228 let (actions, decision) = Permission::migration_actions_and_decision(Some(permission));
1229 entries.push(PolicyEntry::of(resource, actions, None, decision));
1230 }
1231}
1232
1233fn required_plain_field<'a>(
1234 value: Option<&'a cheetah_string::CheetahString>,
1235 field_name: &'static str,
1236 access_key: &str,
1237) -> AuthServiceResult<&'a str> {
1238 value
1239 .map(|value| value.as_str().trim())
1240 .filter(|value| !value.is_empty())
1241 .ok_or_else(|| {
1242 let _ = (field_name, access_key);
1243 AuthServiceError::configuration_error("required ACL field is blank")
1244 })
1245}
1246
1247async fn seed_initial_users(provider_registry: &ProviderRegistry, config: &AuthConfig) -> AuthServiceResult<()> {
1248 seed_init_authentication_user(provider_registry.authentication_metadata_provider(), config).await?;
1249 seed_inner_client_user(provider_registry.authentication_metadata_provider(), config).await
1250}
1251
1252async fn seed_init_authentication_user(
1253 provider: Arc<crate::UserMetadataHandle>,
1254 config: &AuthConfig,
1255) -> AuthServiceResult<()> {
1256 let init_user = config.init_authentication_user.as_str().trim();
1257 if init_user.is_empty() {
1258 return Ok(());
1259 }
1260
1261 if init_user.starts_with('{') {
1262 if let Ok(mut user) = serde_json::from_str::<User>(init_user) {
1263 user.set_user_type(UserType::Super);
1264 if user.user_status().is_none() {
1265 user.set_user_status(UserStatus::Enable);
1266 }
1267 return create_user_if_absent(provider, user).await;
1268 }
1269 }
1270
1271 let parts: Vec<&str> = init_user.splitn(2, ':').collect();
1272 if parts.len() == 2 {
1273 let username = parts[0].trim();
1274 let password = parts[1].trim();
1275 if !username.is_empty() && !password.is_empty() {
1276 let mut user = User::of_with_type(username, password, UserType::Super);
1277 user.set_user_status(UserStatus::Enable);
1278 return create_user_if_absent(provider, user).await;
1279 }
1280 }
1281
1282 Ok(())
1283}
1284
1285async fn seed_inner_client_user(
1286 provider: Arc<crate::UserMetadataHandle>,
1287 config: &AuthConfig,
1288) -> AuthServiceResult<()> {
1289 #[derive(serde::Deserialize)]
1290 struct SessionCredentials {
1291 #[serde(rename = "accessKey")]
1292 access_key: String,
1293 #[serde(rename = "secretKey")]
1294 secret_key: String,
1295 }
1296
1297 let credentials = config.inner_client_authentication_credentials.as_str().trim();
1298 if credentials.is_empty() {
1299 return Ok(());
1300 }
1301
1302 let Ok(credentials) = serde_json::from_str::<SessionCredentials>(credentials) else {
1303 return Ok(());
1304 };
1305
1306 let mut user = User::of_with_type(credentials.access_key, credentials.secret_key, UserType::Super);
1307 user.set_user_status(UserStatus::Enable);
1308 create_user_if_absent(provider, user).await
1309}
1310
1311async fn create_user_if_absent(provider: Arc<crate::UserMetadataHandle>, user: User) -> AuthServiceResult<()> {
1312 match provider.get_user(user.username().as_str()).await {
1313 Ok(_) => Ok(()),
1314 Err(error) if error.kind() == AuthFailureKind::NotFound => provider.create_user(user).await,
1315 Err(error) => Err(error),
1316 }
1317}
1318
1319fn require_authorization(decision: AuthorizationDecision) -> AuthServiceResult<()> {
1320 match decision {
1321 AuthorizationDecision::Allow => Ok(()),
1322 AuthorizationDecision::Deny(_) => Err(AuthServiceError::new(
1323 AuthOperation::Authorize,
1324 AuthFailureKind::PermissionDenied,
1325 )),
1326 }
1327}
1328
1329fn access_key_from_command(command: &RemotingCommand) -> Option<&str> {
1330 command.ext_fields().and_then(|fields| {
1331 fields
1332 .get(&CheetahString::from_static_str(ACCESS_KEY))
1333 .map(|value| value.as_str().trim())
1334 .filter(|value| !value.is_empty())
1335 })
1336}
1337
1338#[cfg(test)]
1339mod tests {
1340 use std::collections::HashMap;
1341 use std::fs;
1342 use std::time::Duration;
1343 use std::time::Instant;
1344
1345 use cheetah_string::CheetahString;
1346 use rocketmq_protocol::code::request_code::RequestCode;
1347 use rocketmq_security_api::Action;
1348 use rocketmq_security_api::AuthorizationDenial;
1349 use tempfile::TempDir;
1350 use tokio::time::sleep;
1351
1352 use super::*;
1353 use crate::authentication::acl_signer;
1354 use crate::authentication::enums::subject_type::SubjectType;
1355 use crate::authorization::enums::decision::Decision;
1356 use crate::authorization::enums::policy_type::PolicyType;
1357 use crate::authorization::model::acl::Acl;
1358 use crate::authorization::model::policy::Policy;
1359 use crate::authorization::model::resource::Resource;
1360
1361 #[test]
1362 fn ordinary_denials_use_one_fixed_public_error_and_r16_projection() {
1363 for denial in [
1364 AuthorizationDenial::SubjectUnknown,
1365 AuthorizationDenial::ResourceUnknown,
1366 AuthorizationDenial::PermissionDenied,
1367 AuthorizationDenial::PolicyNotApplicable,
1368 AuthorizationDenial::MaintenanceRestricted,
1369 ] {
1370 let error = require_authorization(AuthorizationDecision::Deny(denial)).unwrap_err();
1371 assert_eq!(error.operation(), AuthOperation::Authorize);
1372 assert_eq!(error.kind(), AuthFailureKind::PermissionDenied);
1373 let canonical = rocketmq_error::Error::from(error);
1374 assert_eq!(canonical.descriptor(), &rocketmq_error::AUTH_PERMISSION_DENIED);
1375 assert_eq!(canonical.to_string(), "auth.permission.denied: Permission was denied");
1376 assert_eq!(canonical.descriptor().projection().remoting().code.as_i32(), 16);
1377 }
1378 }
1379
1380 struct AuthRuntimeBuilder;
1381
1382 impl AuthRuntimeBuilder {
1383 #[allow(
1384 clippy::new_ret_no_self,
1385 reason = "test facade preserves concise existing builder call sites"
1386 )]
1387 fn new(config: AuthConfig) -> super::AuthRuntimeBuilder {
1388 let runtime = rocketmq_runtime::RuntimeContext::from_current("auth-runtime-test");
1389 super::AuthRuntimeBuilder::new(config, runtime.service_context("auth-runtime"))
1390 }
1391 }
1392
1393 fn send_message_command(topic: &str, access_key: &str, signature: &str) -> RemotingCommand {
1394 let mut ext_fields = HashMap::new();
1395 ext_fields.insert(CheetahString::from_static_str("topic"), CheetahString::from(topic));
1396 ext_fields.insert(
1397 CheetahString::from_static_str("AccessKey"),
1398 CheetahString::from(access_key),
1399 );
1400 ext_fields.insert(
1401 CheetahString::from_static_str("Signature"),
1402 CheetahString::from(signature),
1403 );
1404 RemotingCommand::create_remoting_command(RequestCode::SendMessage.to_i32()).set_ext_fields(ext_fields)
1405 }
1406
1407 fn send_message_command_without_credentials(topic: &str) -> RemotingCommand {
1408 let mut ext_fields = HashMap::new();
1409 ext_fields.insert(CheetahString::from_static_str("topic"), CheetahString::from(topic));
1410 RemotingCommand::create_remoting_command(RequestCode::SendMessage.to_i32()).set_ext_fields(ext_fields)
1411 }
1412
1413 fn signed_command(
1414 request_code: RequestCode,
1415 access_key: &str,
1416 secret_key: &str,
1417 fields: &[(&str, &str)],
1418 ) -> RemotingCommand {
1419 let mut ext_fields = HashMap::new();
1420 ext_fields.insert(
1421 CheetahString::from_static_str("AccessKey"),
1422 CheetahString::from(access_key),
1423 );
1424 let mut signed_values = std::collections::BTreeMap::new();
1425 signed_values.insert("AccessKey", access_key);
1426 for (name, value) in fields {
1427 ext_fields.insert(CheetahString::from(*name), CheetahString::from(*value));
1428 signed_values.insert(*name, *value);
1429 }
1430 let content = signed_values.values().copied().collect::<String>();
1431 let signature = acl_signer::cal_signature(content.as_bytes(), secret_key).unwrap();
1432 ext_fields.insert(
1433 CheetahString::from_static_str("Signature"),
1434 CheetahString::from(signature),
1435 );
1436 RemotingCommand::create_remoting_command(request_code.to_i32()).set_ext_fields(ext_fields)
1437 }
1438
1439 #[test]
1440 fn provider_registry_rejects_unsupported_metadata_provider() {
1441 let config = AuthConfig {
1442 authentication_metadata_provider: CheetahString::from_static_str("RocksDBAuthenticationMetadataProvider"),
1443 ..AuthConfig::default()
1444 };
1445
1446 let error = match ProviderRegistry::local(&config) {
1447 Ok(_) => panic!("unsupported provider should fail fast"),
1448 Err(error) => error,
1449 };
1450
1451 assert_eq!(error.operation(), AuthOperation::Initialize);
1452 assert_eq!(error.kind(), AuthFailureKind::InvalidConfiguration);
1453 }
1454
1455 #[test]
1456 fn provider_registry_accepts_file_snapshot_metadata_provider_aliases() {
1457 let config = AuthConfig {
1458 authentication_metadata_provider: CheetahString::from_static_str(
1459 "FileSnapshotAuthenticationMetadataProvider",
1460 ),
1461 authorization_metadata_provider: CheetahString::from_static_str(
1462 "FileSnapshotAuthorizationMetadataProvider",
1463 ),
1464 ..AuthConfig::default()
1465 };
1466
1467 let registry = ProviderRegistry::local(&config).expect("file snapshot aliases should use local providers");
1468
1469 assert_eq!(registry.acl_generation(), 0);
1470 }
1471
1472 #[test]
1473 fn migrated_sre_reader_can_get_but_cannot_mutate_cluster_resources() {
1474 let mut account = PlainAccessConfig::new();
1475 account.set_access_key(CheetahString::from_static_str("sre-reader"));
1476 account.set_cluster_perm(CheetahString::from_static_str("GET"));
1477 account.set_default_topic_perm(CheetahString::from_static_str("GET"));
1478 account.set_default_group_perm(CheetahString::from_static_str("GET"));
1479
1480 let acl = acl_from_plain_account(&account).unwrap().unwrap();
1481 let entries = acl.get_policy(PolicyType::Default).unwrap().entries();
1482
1483 for resource_type in [ResourceType::Cluster, ResourceType::Topic, ResourceType::Group] {
1484 let entry = entries
1485 .iter()
1486 .find(|entry| entry.resource().resource_type() == resource_type)
1487 .unwrap();
1488 assert_eq!(entry.decision(), Decision::Allow);
1489 assert!(entry.is_match_action(&[Action::Get]));
1490 assert!(!entry.is_match_action(&[Action::Update]));
1491 assert!(!entry.is_match_action(&[Action::Delete]));
1492 }
1493 }
1494
1495 #[test]
1496 fn migrated_cluster_permission_rejects_non_read_permissions() {
1497 let mut account = PlainAccessConfig::new();
1498 account.set_access_key(CheetahString::from_static_str("sre-reader"));
1499 account.set_cluster_perm(CheetahString::from_static_str("UPDATE"));
1500
1501 let error = acl_from_plain_account(&account).unwrap_err();
1502
1503 assert_eq!(error.operation(), AuthOperation::Initialize);
1504 assert_eq!(error.kind(), AuthFailureKind::InvalidConfiguration);
1505 }
1506
1507 #[tokio::test]
1508 async fn migrated_sre_reader_accepts_broker_get_and_rejects_topic_mutation_rpc() {
1509 let temp = TempDir::new().unwrap();
1510 let acl_file = temp.path().join("plain_acl.yml");
1511 fs::write(
1512 &acl_file,
1513 r#"
1514accounts:
1515 - accessKey: sre-reader
1516 secretKey: reader-secret
1517 admin: false
1518 defaultTopicPerm: GET
1519 defaultGroupPerm: GET
1520 clusterPerm: GET
1521"#,
1522 )
1523 .unwrap();
1524 let runtime = AuthRuntimeBuilder::new(AuthConfig {
1525 cluster_name: CheetahString::from_static_str("SreDev"),
1526 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
1527 authentication_enabled: true,
1528 authorization_enabled: true,
1529 ..AuthConfig::default()
1530 })
1531 .build()
1532 .await
1533 .unwrap();
1534
1535 let broker_get = signed_command(RequestCode::GetBrokerRuntimeInfo, "sre-reader", "reader-secret", &[]);
1536 runtime
1537 .check_remoting(&RemotingAuthContext::embedded("test-session"), &broker_get)
1538 .await
1539 .unwrap();
1540
1541 let topic_mutation = signed_command(
1542 RequestCode::UpdateAndCreateTopic,
1543 "sre-reader",
1544 "reader-secret",
1545 &[("topic", "Orders")],
1546 );
1547 let error = runtime
1548 .check_remoting(&RemotingAuthContext::embedded("test-session"), &topic_mutation)
1549 .await
1550 .unwrap_err();
1551 assert_eq!(error.operation(), AuthOperation::Authorize);
1552 assert_eq!(error.kind(), AuthFailureKind::PermissionDenied);
1553
1554 runtime.shutdown().await.unwrap();
1555 }
1556
1557 #[tokio::test]
1558 async fn build_runtime_seeds_initial_super_user() {
1559 let runtime = AuthRuntimeBuilder::new(AuthConfig {
1560 init_authentication_user: CheetahString::from_static_str("admin:secret"),
1561 ..AuthConfig::default()
1562 })
1563 .build()
1564 .await
1565 .unwrap();
1566
1567 let user = runtime
1568 .provider_registry()
1569 .authentication_metadata_provider()
1570 .get_user("admin")
1571 .await
1572 .unwrap();
1573 assert_eq!(user.user_type(), Some(UserType::Super));
1574 assert_eq!(user.user_status(), Some(UserStatus::Enable));
1575 }
1576
1577 #[tokio::test]
1578 async fn runtime_authenticates_and_authorizes_remoting_command() {
1579 let runtime = AuthRuntimeBuilder::new(AuthConfig {
1580 authentication_enabled: true,
1581 authorization_enabled: true,
1582 ..AuthConfig::default()
1583 })
1584 .build()
1585 .await
1586 .unwrap();
1587
1588 let authn_provider = runtime.provider_registry().authentication_metadata_provider();
1589 let mut user = User::of_with_type("alice", "secret", UserType::Normal);
1590 user.set_user_status(UserStatus::Enable);
1591 authn_provider.create_user(user).await.unwrap();
1592
1593 let authz_provider = runtime.provider_registry().authorization_metadata_provider();
1594 let acl = Acl::of(
1595 "alice",
1596 SubjectType::User,
1597 Policy::of(
1598 vec![Resource::of_topic("topic-a")],
1599 vec![Action::Pub],
1600 None,
1601 Decision::Allow,
1602 ),
1603 );
1604 authz_provider.create_acl(acl).await.unwrap();
1605
1606 let signature = acl_signer::cal_signature("alicetopic-a".as_bytes(), "secret").unwrap();
1607 let command = send_message_command("topic-a", "alice", &signature);
1608
1609 runtime
1610 .check_remoting(&RemotingAuthContext::embedded("test-session"), &command)
1611 .await
1612 .unwrap();
1613 }
1614
1615 #[tokio::test]
1616 async fn remoting_runtime_rejects_missing_or_mismatched_ingress_metadata() {
1617 let runtime = AuthRuntimeBuilder::new(AuthConfig::default()).build().await.unwrap();
1618 let command = RemotingCommand::create_remoting_command(RequestCode::SendMessage.to_i32());
1619
1620 runtime
1621 .check_remoting(&RemotingAuthContext::default(), &command)
1622 .await
1623 .expect_err("missing session and source metadata must fail closed");
1624
1625 let context = RemotingAuthContext::network("192.0.2.10", "session-a");
1626 runtime
1627 .check_remoting_with_source_ip(&context, &command, Some("192.0.2.11"), Some("session-a"))
1628 .await
1629 .expect_err("metadata overrides must match the typed ingress context");
1630 }
1631
1632 #[tokio::test]
1633 async fn authoritative_operation_code_cannot_be_replaced_by_a_mutated_command_code() {
1634 let runtime = AuthRuntimeBuilder::new(AuthConfig {
1635 authentication_enabled: true,
1636 authentication_whitelist: CheetahString::from(RequestCode::HeartBeat.to_i32().to_string()),
1637 ..AuthConfig::default()
1638 })
1639 .build()
1640 .await
1641 .unwrap();
1642 let context = RemotingAuthContext::embedded("test-session");
1643 let mutated = RemotingCommand::create_remoting_command(RequestCode::HeartBeat.to_i32());
1644
1645 runtime
1646 .check_remoting(&context, &mutated)
1647 .await
1648 .expect("the test command code is explicitly whitelisted");
1649 runtime
1650 .check_remoting_for_code(&context, &mutated, RequestCode::SendMessage.to_i32())
1651 .await
1652 .expect_err("the immutable SendMessage operation must still require authentication");
1653 }
1654
1655 #[tokio::test]
1656 async fn detailed_authorization_records_each_provider_decision_once() {
1657 let runtime = AuthRuntimeBuilder::new(AuthConfig {
1658 authentication_enabled: true,
1659 authorization_enabled: true,
1660 ..AuthConfig::default()
1661 })
1662 .build()
1663 .await
1664 .expect("test auth runtime should initialize");
1665
1666 let authn_provider = runtime.provider_registry().authentication_metadata_provider();
1667 let mut user = User::of_with_type("alice", "secret", UserType::Normal);
1668 user.set_user_status(UserStatus::Enable);
1669 authn_provider
1670 .create_user(user)
1671 .await
1672 .expect("test user should be stored");
1673
1674 let authz_provider = runtime.provider_registry().authorization_metadata_provider();
1675 authz_provider
1676 .create_acl(Acl::of(
1677 "alice",
1678 SubjectType::User,
1679 Policy::of(
1680 vec![Resource::of_topic("topic-a")],
1681 vec![Action::Pub],
1682 None,
1683 Decision::Allow,
1684 ),
1685 ))
1686 .await
1687 .expect("test ACL should be stored");
1688
1689 let command = send_message_command(
1690 "topic-a",
1691 "alice",
1692 &acl_signer::cal_signature("alicetopic-a".as_bytes(), "secret")
1693 .expect("test signature should be generated"),
1694 );
1695 assert_eq!(
1696 runtime
1697 .evaluate_remoting_detailed(&RemotingAuthContext::embedded("test-session"), &command)
1698 .await,
1699 Ok(DetailedDecision::Allow)
1700 );
1701
1702 let snapshot = runtime.metrics_snapshot();
1703 assert_eq!(snapshot.authorization_successes, 1);
1704 assert_eq!(snapshot.authorization_failures, 0);
1705 runtime.shutdown().await.expect("test auth runtime should shut down");
1706 }
1707
1708 #[tokio::test]
1709 async fn build_runtime_loads_configured_acl_file() {
1710 let temp = TempDir::new().unwrap();
1711 let acl_file = temp.path().join("plain_acl.yml");
1712 fs::write(
1713 &acl_file,
1714 r#"
1715accounts:
1716 - accessKey: alice
1717 secretKey: secret
1718 admin: false
1719 defaultTopicPerm: DENY
1720 defaultGroupPerm: SUB
1721 topicPerms:
1722 - TopicA=PUB
1723 groupPerms:
1724 - GroupA=SUB
1725"#,
1726 )
1727 .unwrap();
1728
1729 let runtime = AuthRuntimeBuilder::new(AuthConfig {
1730 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
1731 ..AuthConfig::default()
1732 })
1733 .build()
1734 .await
1735 .unwrap();
1736
1737 let authn_provider = runtime.provider_registry().authentication_metadata_provider();
1738 let user = authn_provider.get_user("alice").await.unwrap();
1739 assert_eq!(user.password().map(|value| value.as_str()), Some("secret"));
1740 assert_eq!(user.user_type(), Some(UserType::Normal));
1741 assert_eq!(user.user_status(), Some(UserStatus::Enable));
1742
1743 let authz_provider = runtime.provider_registry().authorization_metadata_provider();
1744 let acl = authz_provider.get_acl(&User::of("alice")).await.unwrap().unwrap();
1745 assert!(acl.get_policy(PolicyType::Custom).is_some());
1746 assert!(acl.get_policy(PolicyType::Default).is_some());
1747 }
1748
1749 #[tokio::test]
1750 async fn migrate_auth_from_v1_manager_loads_legacy_plain_acl_files() {
1751 let temp = TempDir::new().unwrap();
1752 let auth_config = AuthConfig {
1753 auth_config_path: CheetahString::from(temp.path().join("auth-store").to_string_lossy().as_ref()),
1754 ..AuthConfig::default()
1755 };
1756 let context = rocketmq_runtime::RuntimeContext::try_from_current("auth-v1-migration-test").unwrap();
1757 let metadata_io = MetadataIoConfig::default()
1758 .into_plan()
1759 .expect("default metadata I/O config is valid")
1760 .start(&context.service_context("auth.v1-migration"))
1761 .unwrap();
1762 let registry = ProviderRegistry::local_with_metadata_io(&auth_config, Some(metadata_io)).unwrap();
1763 let acl_file = temp.path().join("conf").join("acl").join("legacy.yml");
1764 fs::create_dir_all(acl_file.parent().unwrap()).unwrap();
1765 fs::write(
1766 &acl_file,
1767 r#"
1768globalWhiteRemoteAddresses:
1769 - 10.1.*.*
1770accounts:
1771 - accessKey: legacy
1772 secretKey: secret
1773 admin: false
1774 topicPerms:
1775 - TopicA=PUB
1776"#,
1777 )
1778 .unwrap();
1779 let manager = PlainPermissionManager {
1780 file_home: temp.path().to_string_lossy().into_owned(),
1781 default_acl_dir: temp.path().join("conf").join("acl").to_string_lossy().into_owned(),
1782 default_acl_file: temp
1783 .path()
1784 .join("conf")
1785 .join("plain_acl.yml")
1786 .to_string_lossy()
1787 .into_owned(),
1788 file_list: vec![acl_file.to_string_lossy().into_owned()],
1789 };
1790
1791 let migrated = migrate_auth_from_v1_manager(®istry, &manager).await.unwrap();
1792
1793 assert_eq!(migrated, 1);
1794 let user = registry
1795 .authentication_metadata_provider()
1796 .get_user("legacy")
1797 .await
1798 .unwrap();
1799 assert_eq!(user.password().map(|value| value.as_str()), Some("secret"));
1800 let acl = registry
1801 .authorization_metadata_provider()
1802 .get_acl(&User::of("legacy"))
1803 .await
1804 .unwrap()
1805 .unwrap();
1806 assert!(acl.get_policy(PolicyType::Custom).is_some());
1807 assert!(registry.is_acl_white_remote_address(None, Some("10.1.2.3")).unwrap());
1808 }
1809
1810 #[tokio::test]
1811 async fn migrate_auth_from_v1_is_disabled_by_default() {
1812 let temp = TempDir::new().unwrap();
1813 let config = AuthConfig {
1814 auth_config_path: CheetahString::from(temp.path().join("auth-store").to_string_lossy().as_ref()),
1815 ..AuthConfig::default()
1816 };
1817 let registry = ProviderRegistry::local(&config).unwrap();
1818
1819 let migrated = migrate_auth_from_v1(®istry, &config).await.unwrap();
1820
1821 assert_eq!(migrated, 0);
1822 }
1823
1824 #[tokio::test]
1825 async fn acl_white_remote_address_short_circuits_remoting_auth_and_authorization() {
1826 let temp = TempDir::new().unwrap();
1827 let acl_file = temp.path().join("plain_acl.yml");
1828 fs::write(
1829 &acl_file,
1830 r#"
1831globalWhiteRemoteAddresses:
1832 - 10.10.*.*
1833accounts:
1834 - accessKey: alice
1835 secretKey: secret
1836 whiteRemoteAddress: 192.168.0.*
1837 defaultTopicPerm: DENY
1838"#,
1839 )
1840 .unwrap();
1841
1842 let runtime = AuthRuntimeBuilder::new(AuthConfig {
1843 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
1844 authentication_enabled: true,
1845 authorization_enabled: true,
1846 ..AuthConfig::default()
1847 })
1848 .build()
1849 .await
1850 .unwrap();
1851
1852 assert!(runtime.is_acl_white_remote_address(None, Some("10.10.1.2")).unwrap());
1853 assert!(runtime
1854 .is_acl_white_remote_address(Some("alice"), Some("192.168.0.7"))
1855 .unwrap());
1856 assert!(!runtime
1857 .is_acl_white_remote_address(Some("alice"), Some("192.168.1.7"))
1858 .unwrap());
1859
1860 let global_command = send_message_command_without_credentials("TopicA");
1861 runtime
1862 .check_remoting(
1863 &RemotingAuthContext::network("10.10.1.2", "test-session"),
1864 &global_command,
1865 )
1866 .await
1867 .unwrap();
1868
1869 let account_command = send_message_command("TopicA", "alice", "");
1870 runtime
1871 .check_remoting(
1872 &RemotingAuthContext::network("192.168.0.7", "test-session"),
1873 &account_command,
1874 )
1875 .await
1876 .unwrap();
1877
1878 runtime
1879 .check_remoting(
1880 &RemotingAuthContext::network("192.168.1.7", "test-session"),
1881 &account_command,
1882 )
1883 .await
1884 .expect_err("non-whitelisted source should still require a valid signature");
1885 }
1886
1887 #[tokio::test]
1888 async fn runtime_global_white_remote_address_update_preserves_account_whitelist_and_advances_generation() {
1889 let temp = TempDir::new().unwrap();
1890 let acl_file = temp.path().join("plain_acl.yml");
1891 fs::write(
1892 &acl_file,
1893 r#"
1894globalWhiteRemoteAddresses:
1895 - 10.10.*.*
1896accounts:
1897 - accessKey: alice
1898 secretKey: secret
1899 whiteRemoteAddress: 192.168.0.*
1900"#,
1901 )
1902 .unwrap();
1903
1904 let runtime = AuthRuntimeBuilder::new(AuthConfig {
1905 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
1906 ..AuthConfig::default()
1907 })
1908 .build()
1909 .await
1910 .unwrap();
1911 let generation = runtime.acl_generation();
1912
1913 runtime
1914 .update_global_white_remote_addresses(vec!["172.16.*.*"])
1915 .unwrap();
1916
1917 assert!(runtime.acl_generation() > generation);
1918 assert!(!runtime.is_acl_white_remote_address(None, Some("10.10.1.2")).unwrap());
1919 assert!(runtime.is_acl_white_remote_address(None, Some("172.16.1.2")).unwrap());
1920 assert!(runtime
1921 .is_acl_white_remote_address(Some("alice"), Some("192.168.0.7"))
1922 .unwrap());
1923 }
1924
1925 #[tokio::test]
1926 async fn provider_registry_local_loads_persisted_authentication_users() {
1927 let temp = TempDir::new().unwrap();
1928 let config = AuthConfig {
1929 auth_config_path: CheetahString::from(temp.path().join("auth.json").to_string_lossy().as_ref()),
1930 ..AuthConfig::default()
1931 };
1932
1933 let mut provider = LocalAuthenticationMetadataProvider::new();
1934 provider.initialize(config.clone(), None).await.unwrap();
1935 provider
1936 .create_user(User::of_with_password("persisted", "secret"))
1937 .await
1938 .unwrap();
1939
1940 let registry = ProviderRegistry::local(&config).unwrap();
1941 let restored = registry
1942 .authentication_metadata_provider()
1943 .get_user("persisted")
1944 .await
1945 .unwrap();
1946
1947 assert_eq!(restored.password().map(|value| value.as_str()), Some("secret"));
1948 }
1949
1950 #[tokio::test]
1951 async fn auth_runtime_restores_persisted_metadata_after_restart() {
1952 let temp = TempDir::new().unwrap();
1953 let config = AuthConfig {
1954 auth_config_path: CheetahString::from(temp.path().join("auth.json").to_string_lossy().as_ref()),
1955 authentication_enabled: true,
1956 authorization_enabled: true,
1957 ..AuthConfig::default()
1958 };
1959
1960 let runtime = AuthRuntimeBuilder::new(config.clone()).build().await.unwrap();
1961 let authn_provider = runtime.provider_registry().authentication_metadata_provider();
1962 let mut user = User::of_with_type("alice", "secret", UserType::Normal);
1963 user.set_user_status(UserStatus::Enable);
1964 authn_provider.create_user(user).await.unwrap();
1965
1966 let authz_provider = runtime.provider_registry().authorization_metadata_provider();
1967 authz_provider
1968 .create_acl(Acl::of(
1969 "alice",
1970 SubjectType::User,
1971 Policy::of(
1972 vec![Resource::of_topic("topic-a")],
1973 vec![Action::Pub],
1974 None,
1975 Decision::Allow,
1976 ),
1977 ))
1978 .await
1979 .unwrap();
1980 runtime.shutdown().await.unwrap();
1981
1982 let restarted = AuthRuntimeBuilder::new(config).build().await.unwrap();
1983 let signature = acl_signer::cal_signature("alicetopic-a".as_bytes(), "secret").unwrap();
1984 let command = send_message_command("topic-a", "alice", &signature);
1985
1986 restarted
1987 .check_remoting(&RemotingAuthContext::embedded("test-session"), &command)
1988 .await
1989 .unwrap();
1990 restarted.shutdown().await.unwrap();
1991 }
1992
1993 #[tokio::test]
1994 async fn auth_runtime_rejects_corrupted_persisted_users_snapshot() {
1995 let temp = TempDir::new().unwrap();
1996 let config = AuthConfig {
1997 auth_config_path: CheetahString::from(temp.path().join("auth.json").to_string_lossy().as_ref()),
1998 authentication_enabled: true,
1999 ..AuthConfig::default()
2000 };
2001 let snapshot = temp.path().join("auth").join("users.json");
2002 fs::create_dir_all(snapshot.parent().unwrap()).unwrap();
2003 fs::write(&snapshot, b"{not valid json").unwrap();
2004
2005 let error = match AuthRuntimeBuilder::new(config).build().await {
2006 Ok(_) => panic!("runtime must reject corrupted users snapshot"),
2007 Err(error) => error,
2008 };
2009
2010 assert_eq!(error.operation(), AuthOperation::DecodeMetadata);
2011 assert_eq!(error.kind(), AuthFailureKind::InvalidData);
2012 assert!(!error.to_string().contains("users.json"));
2013 }
2014
2015 #[tokio::test]
2016 async fn auth_runtime_rejects_corrupted_persisted_acls_snapshot() {
2017 let temp = TempDir::new().unwrap();
2018 let config = AuthConfig {
2019 auth_config_path: CheetahString::from(temp.path().join("auth.json").to_string_lossy().as_ref()),
2020 authorization_enabled: true,
2021 ..AuthConfig::default()
2022 };
2023 let snapshot = temp.path().join("auth").join("acls.json");
2024 fs::create_dir_all(snapshot.parent().unwrap()).unwrap();
2025 fs::write(&snapshot, b"{not valid json").unwrap();
2026
2027 let error = match AuthRuntimeBuilder::new(config).build().await {
2028 Ok(_) => panic!("runtime must reject corrupted ACL snapshot"),
2029 Err(error) => error,
2030 };
2031
2032 assert_eq!(error.operation(), AuthOperation::DecodeMetadata);
2033 assert_eq!(error.kind(), AuthFailureKind::InvalidData);
2034 assert!(!error.to_string().contains("acls.json"));
2035 let canonical = rocketmq_error::Error::from(error);
2036 assert_eq!(canonical.descriptor(), &rocketmq_error::CORE_SERIALIZATION_FAILED);
2037 let auth_error = std::error::Error::source(&canonical)
2038 .and_then(|source| source.downcast_ref::<crate::AuthServiceError>())
2039 .expect("canonical error must retain the auth facade source");
2040 assert_eq!(auth_error.operation(), crate::AuthOperation::DecodeMetadata);
2041 assert_eq!(auth_error.kind(), crate::AuthFailureKind::InvalidData);
2042 let startup = std::error::Error::source(auth_error)
2043 .and_then(|source| source.downcast_ref::<AuthStartupFailure>())
2044 .expect("startup error must retain its rollback owner and primary failure");
2045 assert!(startup.cleanup_error().is_none());
2046 assert!(std::error::Error::source(startup.primary())
2047 .and_then(|source| source.downcast_ref::<serde_json::Error>())
2048 .is_some());
2049 }
2050
2051 #[tokio::test]
2052 async fn acl_file_watcher_reloads_changed_user_secret() {
2053 let temp = TempDir::new().unwrap();
2054 let acl_file = temp.path().join("plain_acl.yml");
2055 fs::write(
2056 &acl_file,
2057 r#"
2058accounts:
2059 - accessKey: alice
2060 secretKey: first
2061"#,
2062 )
2063 .unwrap();
2064
2065 let runtime = AuthRuntimeBuilder::new(AuthConfig {
2066 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
2067 acl_file_watch_enabled: true,
2068 acl_file_watch_interval_millis: 25,
2069 ..AuthConfig::default()
2070 })
2071 .build()
2072 .await
2073 .unwrap();
2074
2075 let authn_provider = runtime.provider_registry().authentication_metadata_provider();
2076 let user = authn_provider.get_user("alice").await.unwrap();
2077 assert_eq!(user.password().map(|value| value.as_str()), Some("first"));
2078
2079 fs::write(
2080 &acl_file,
2081 r#"
2082accounts:
2083 - accessKey: alice
2084 secretKey: second
2085"#,
2086 )
2087 .unwrap();
2088
2089 let deadline = Instant::now() + Duration::from_secs(2);
2090 loop {
2091 let user = authn_provider.get_user("alice").await.unwrap();
2092 if user.password().map(|value| value.as_str()) == Some("second") {
2093 break;
2094 }
2095 assert!(
2096 Instant::now() < deadline,
2097 "ACL file watcher did not reload the updated user secret"
2098 );
2099 sleep(Duration::from_millis(25)).await;
2100 }
2101
2102 runtime.shutdown().await.unwrap();
2103 }
2104
2105 #[tokio::test]
2106 async fn acl_file_watcher_skips_unchanged_file_without_advancing_generation() {
2107 let temp = TempDir::new().unwrap();
2108 let acl_file = temp.path().join("plain_acl.yml");
2109 fs::write(
2110 &acl_file,
2111 r#"
2112accounts:
2113 - accessKey: alice
2114 secretKey: first
2115"#,
2116 )
2117 .unwrap();
2118
2119 let runtime = AuthRuntimeBuilder::new(AuthConfig {
2120 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
2121 acl_file_watch_enabled: true,
2122 acl_file_watch_interval_millis: 20,
2123 ..AuthConfig::default()
2124 })
2125 .build()
2126 .await
2127 .unwrap();
2128 let generation = runtime.acl_generation();
2129
2130 sleep(Duration::from_millis(120)).await;
2131
2132 assert_eq!(
2133 runtime.acl_generation(),
2134 generation,
2135 "watcher must not advance generation when ACL file content is unchanged"
2136 );
2137
2138 runtime.shutdown().await.unwrap();
2139 }
2140
2141 #[tokio::test]
2142 async fn manual_acl_reload_failure_preserves_previous_snapshot_and_generation() {
2143 let temp = TempDir::new().unwrap();
2144 let acl_file = temp.path().join("plain_acl.yml");
2145 fs::write(
2146 &acl_file,
2147 r#"
2148globalWhiteRemoteAddresses:
2149 - 10.10.*.*
2150accounts:
2151 - accessKey: alice
2152 secretKey: first
2153"#,
2154 )
2155 .unwrap();
2156
2157 let runtime = AuthRuntimeBuilder::new(AuthConfig {
2158 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
2159 ..AuthConfig::default()
2160 })
2161 .build()
2162 .await
2163 .unwrap();
2164 let authn_provider = runtime.provider_registry().authentication_metadata_provider();
2165 let generation = runtime.acl_generation();
2166 assert!(runtime.is_acl_white_remote_address(None, Some("10.10.1.2")).unwrap());
2167 let user = authn_provider.get_user("alice").await.unwrap();
2168 assert_eq!(user.password().map(|value| value.as_str()), Some("first"));
2169
2170 fs::write(
2171 &acl_file,
2172 r#"
2173globalWhiteRemoteAddresses:
2174 - 172.16.*.*
2175accounts:
2176 - accessKey: alice
2177"#,
2178 )
2179 .unwrap();
2180
2181 let error = runtime.reload_acl_file().await.unwrap_err();
2182 assert_eq!(error.operation(), AuthOperation::Initialize);
2183 assert_eq!(error.kind(), AuthFailureKind::InvalidConfiguration);
2184 assert_eq!(runtime.acl_generation(), generation);
2185
2186 let user = authn_provider.get_user("alice").await.unwrap();
2187 assert_eq!(user.password().map(|value| value.as_str()), Some("first"));
2188 assert!(runtime.is_acl_white_remote_address(None, Some("10.10.1.2")).unwrap());
2189 assert!(!runtime.is_acl_white_remote_address(None, Some("172.16.1.2")).unwrap());
2190 }
2191
2192 #[tokio::test]
2193 async fn acl_file_watcher_failure_preserves_snapshot_and_recovers_after_fix() {
2194 let temp = TempDir::new().unwrap();
2195 let acl_file = temp.path().join("plain_acl.yml");
2196 fs::write(
2197 &acl_file,
2198 r#"
2199globalWhiteRemoteAddresses:
2200 - 10.10.*.*
2201accounts:
2202 - accessKey: alice
2203 secretKey: first
2204"#,
2205 )
2206 .unwrap();
2207
2208 let runtime = AuthRuntimeBuilder::new(AuthConfig {
2209 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
2210 acl_file_watch_enabled: true,
2211 acl_file_watch_interval_millis: 25,
2212 ..AuthConfig::default()
2213 })
2214 .build()
2215 .await
2216 .unwrap();
2217 let authn_provider = runtime.provider_registry().authentication_metadata_provider();
2218 let generation = runtime.acl_generation();
2219
2220 fs::write(
2221 &acl_file,
2222 r#"
2223globalWhiteRemoteAddresses:
2224 - 172.16.*.*
2225accounts:
2226 - accessKey: alice
2227"#,
2228 )
2229 .unwrap();
2230
2231 sleep(Duration::from_millis(150)).await;
2232
2233 assert_eq!(runtime.acl_generation(), generation);
2234 let user = authn_provider.get_user("alice").await.unwrap();
2235 assert_eq!(user.password().map(|value| value.as_str()), Some("first"));
2236 assert!(runtime.is_acl_white_remote_address(None, Some("10.10.1.2")).unwrap());
2237 assert!(!runtime.is_acl_white_remote_address(None, Some("172.16.1.2")).unwrap());
2238
2239 fs::write(
2240 &acl_file,
2241 r#"
2242globalWhiteRemoteAddresses:
2243 - 172.16.*.*
2244accounts:
2245 - accessKey: alice
2246 secretKey: second
2247"#,
2248 )
2249 .unwrap();
2250
2251 let deadline = Instant::now() + Duration::from_secs(2);
2252 loop {
2253 let user = authn_provider.get_user("alice").await.unwrap();
2254 if user.password().map(|value| value.as_str()) == Some("second")
2255 && runtime.is_acl_white_remote_address(None, Some("172.16.1.2")).unwrap()
2256 {
2257 break;
2258 }
2259 assert!(
2260 Instant::now() < deadline,
2261 "ACL file watcher did not recover after a failed reload"
2262 );
2263 sleep(Duration::from_millis(25)).await;
2264 }
2265
2266 assert!(runtime.acl_generation() > generation);
2267 runtime.shutdown().await.unwrap();
2268 }
2269
2270 #[tokio::test]
2271 async fn manual_acl_reload_updates_watcher_fingerprint() {
2272 let temp = TempDir::new().unwrap();
2273 let acl_file = temp.path().join("plain_acl.yml");
2274 fs::write(
2275 &acl_file,
2276 r#"
2277accounts:
2278 - accessKey: alice
2279 secretKey: first
2280"#,
2281 )
2282 .unwrap();
2283
2284 let runtime = AuthRuntimeBuilder::new(AuthConfig {
2285 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
2286 acl_file_watch_enabled: true,
2287 acl_file_watch_interval_millis: 20,
2288 ..AuthConfig::default()
2289 })
2290 .build()
2291 .await
2292 .unwrap();
2293
2294 fs::write(
2295 &acl_file,
2296 r#"
2297accounts:
2298 - accessKey: alice
2299 secretKey: second
2300"#,
2301 )
2302 .unwrap();
2303
2304 runtime.reload_acl_file().await.unwrap();
2305 let generation = runtime.acl_generation();
2306
2307 sleep(Duration::from_millis(120)).await;
2308
2309 assert_eq!(
2310 runtime.acl_generation(),
2311 generation,
2312 "watcher must not re-apply a file that was already manually reloaded"
2313 );
2314
2315 runtime.shutdown().await.unwrap();
2316 }
2317
2318 #[tokio::test]
2319 async fn acl_file_watcher_reloads_white_remote_address_snapshot() {
2320 let temp = TempDir::new().unwrap();
2321 let acl_file = temp.path().join("plain_acl.yml");
2322 fs::write(
2323 &acl_file,
2324 r#"
2325accounts:
2326 - accessKey: alice
2327 secretKey: first
2328"#,
2329 )
2330 .unwrap();
2331
2332 let runtime = AuthRuntimeBuilder::new(AuthConfig {
2333 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
2334 acl_file_watch_enabled: true,
2335 acl_file_watch_interval_millis: 25,
2336 ..AuthConfig::default()
2337 })
2338 .build()
2339 .await
2340 .unwrap();
2341
2342 assert!(!runtime.is_acl_white_remote_address(None, Some("10.10.1.2")).unwrap());
2343
2344 fs::write(
2345 &acl_file,
2346 r#"
2347globalWhiteRemoteAddresses:
2348 - 10.10.*.*
2349accounts:
2350 - accessKey: alice
2351 secretKey: first
2352"#,
2353 )
2354 .unwrap();
2355
2356 let deadline = Instant::now() + Duration::from_secs(2);
2357 loop {
2358 if runtime.is_acl_white_remote_address(None, Some("10.10.1.2")).unwrap() {
2359 break;
2360 }
2361 assert!(
2362 Instant::now() < deadline,
2363 "ACL file watcher did not reload the updated white remote address"
2364 );
2365 sleep(Duration::from_millis(25)).await;
2366 }
2367
2368 runtime.shutdown().await.unwrap();
2369 }
2370
2371 #[tokio::test]
2372 async fn acl_file_watcher_shutdown_is_idempotent_and_stops_future_reloads() {
2373 let temp = TempDir::new().unwrap();
2374 let acl_file = temp.path().join("plain_acl.yml");
2375 fs::write(
2376 &acl_file,
2377 r#"
2378accounts:
2379 - accessKey: alice
2380 secretKey: first
2381"#,
2382 )
2383 .unwrap();
2384
2385 let runtime = AuthRuntimeBuilder::new(AuthConfig {
2386 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
2387 acl_file_watch_enabled: true,
2388 acl_file_watch_interval_millis: 20,
2389 ..AuthConfig::default()
2390 })
2391 .build()
2392 .await
2393 .unwrap();
2394 runtime.shutdown().await.unwrap();
2395 runtime.shutdown().await.unwrap();
2396
2397 let generation = runtime.acl_generation();
2398 let reload_attempts = runtime.metrics_snapshot().acl_reload_attempts;
2399 fs::write(
2400 &acl_file,
2401 r#"
2402accounts:
2403 - accessKey: alice
2404 secretKey: second
2405"#,
2406 )
2407 .unwrap();
2408 sleep(Duration::from_millis(120)).await;
2409
2410 assert_eq!(runtime.acl_generation(), generation);
2411 assert_eq!(
2412 runtime.metrics_snapshot().acl_reload_attempts,
2413 reload_attempts,
2414 "shutdown watcher must not poll or reload after shutdown",
2415 );
2416 }
2417
2418 #[tokio::test]
2419 async fn auth_metrics_track_reload_whitelist_signature_and_auth_outcomes() {
2420 let temp = TempDir::new().unwrap();
2421 let acl_file = temp.path().join("plain_acl.yml");
2422 fs::write(
2423 &acl_file,
2424 r#"
2425globalWhiteRemoteAddresses:
2426 - 10.10.*.*
2427accounts:
2428 - accessKey: alice
2429 secretKey: secret
2430 topicPerms:
2431 - TopicA=PUB
2432"#,
2433 )
2434 .unwrap();
2435
2436 let runtime = AuthRuntimeBuilder::new(AuthConfig {
2437 acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
2438 authentication_enabled: true,
2439 authorization_enabled: true,
2440 ..AuthConfig::default()
2441 })
2442 .build()
2443 .await
2444 .unwrap();
2445
2446 assert!(runtime.is_acl_white_remote_address(None, Some("10.10.1.2")).unwrap());
2447
2448 let signature = acl_signer::cal_signature("aliceTopicA".as_bytes(), "secret").unwrap();
2449 let valid_command = send_message_command("TopicA", "alice", &signature);
2450 runtime
2451 .check_remoting(&RemotingAuthContext::network("127.0.0.1", "channel-a"), &valid_command)
2452 .await
2453 .unwrap();
2454
2455 let invalid_command = send_message_command("TopicA", "alice", "bad-signature");
2456 runtime
2457 .check_remoting(
2458 &RemotingAuthContext::network("127.0.0.1", "channel-b"),
2459 &invalid_command,
2460 )
2461 .await
2462 .expect_err("invalid signature should fail authentication");
2463
2464 let snapshot = runtime.metrics_snapshot();
2465 assert_eq!(snapshot.acl_reload_attempts, 1);
2466 assert_eq!(snapshot.acl_reload_successes, 1);
2467 assert!(snapshot.cache_invalidations >= 1);
2468 assert!(snapshot.whitelist_hits >= 1);
2469 assert!(snapshot.whitelist_misses >= 2);
2470 assert_eq!(snapshot.signature_successes, 1);
2471 assert_eq!(snapshot.signature_failures, 1);
2472 assert_eq!(snapshot.authentication_successes, 1);
2473 assert_eq!(snapshot.authentication_failures, 1);
2474 assert_eq!(snapshot.authorization_successes, 1);
2475
2476 runtime.shutdown().await.unwrap();
2477 }
2478}