Skip to main content

rocketmq_auth/
lib.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod acl;
16mod authentication;
17mod authorization;
18mod bootstrap;
19mod config;
20mod credential_rotation;
21mod error;
22mod layered_authorization;
23mod maintenance;
24mod metadata_snapshot;
25mod migration;
26mod permission;
27mod provider_owner;
28mod provider_ports;
29mod remoting_auth_context;
30mod runtime;
31mod secret_provider;
32/// Runtime-neutral contracts implemented by authentication and authorization providers.
33mod security_api {
34    pub use rocketmq_security_api::*;
35}
36pub(crate) mod runtime_bridge;
37
38// Re-export commonly used authentication types
39pub use authentication::acl_signer::cal_signature;
40pub use authentication::acl_signer::cal_signature_segments;
41pub use authentication::acl_signer::cal_signature_segments_with_algorithm;
42pub use authentication::acl_signer::cal_signature_with_algorithm;
43pub use authentication::acl_signer::SignatureAlgorithm;
44pub use authentication::acl_signer::DEFAULT_CHARSET;
45pub use authentication::builder::AuthenticationContextBuilder;
46pub use authentication::builder::DefaultAuthenticationContextBuilder;
47pub use authentication::context::default_authentication_context::DefaultAuthenticationContext;
48pub use authentication::enums::subject_type::SubjectType;
49pub use authentication::enums::user_status::UserStatus;
50pub use authentication::enums::user_type::UserType;
51pub use authentication::evaluator::AuthenticationEvaluator;
52pub use authentication::factory::AuthenticationFactory;
53pub use authentication::manager::AuthenticationMetadataManager;
54pub use authentication::manager::AuthenticationMetadataManagerImpl;
55pub use authentication::model::subject::Subject;
56pub use authentication::model::user::User;
57pub use authentication::provider::AuthenticationMetadataProvider;
58pub use authentication::provider::AuthenticationProvider;
59pub use authentication::provider::DefaultAuthenticationProvider;
60pub use authentication::provider::LocalAuthenticationMetadataProvider;
61pub use authentication::strategy::AllowAllAuthenticationStrategy;
62pub use authentication::strategy::AuthenticationFuture;
63pub use authentication::strategy::AuthenticationStrategy;
64pub use authentication::strategy::StatefulAuthenticationStrategy;
65pub use authentication::strategy::StatelessAuthenticationStrategy;
66pub use authentication::AclClientRpcHook;
67
68// Re-export commonly used authorization types
69pub use acl::FileAclConfigStore;
70pub use acl::WhiteList;
71pub use authorization::chain::AclAuthorizationHandler;
72pub use authorization::chain::AuthorizationHandler;
73pub use authorization::chain::AuthorizationHandlerChain;
74pub use authorization::context::authentication_context::AuthenticationContext;
75pub use authorization::context::default_authorization_context::DefaultAuthorizationContext;
76/// Canonical decision type for authorization policy models.
77pub use authorization::enums::decision::Decision as PolicyDecision;
78pub use authorization::enums::policy_type::PolicyType;
79pub use authorization::evaluator::AuthorizationEvaluator;
80pub use authorization::factory::AuthorizationFactory;
81pub use authorization::manager::metadata_manager::AuthorizationMetadataManager;
82pub use authorization::manager::AuthorizationMetadataManagerImpl;
83pub use authorization::metadata_provider::AuthorizationMetadataProvider;
84pub use authorization::metadata_provider::LocalAuthorizationMetadataProvider;
85pub use authorization::metadata_provider::NoopMetadataProvider;
86pub use authorization::model::acl::Acl;
87pub use authorization::model::environment::Environment;
88pub use authorization::model::policy::Policy;
89pub use authorization::model::policy_entry::PolicyEntry;
90/// Canonical request type for authorization policy models.
91pub use authorization::model::request_context::RequestContext as AuthorizationRequest;
92/// Frozen 1.x compatibility name for [`AuthorizationRequest`].
93pub use authorization::model::request_context::RequestContext;
94/// Canonical resource type for authorization policy models.
95pub use authorization::model::resource::Resource as PolicyResource;
96/// Frozen 1.x compatibility name for [`PolicyResource`].
97pub use authorization::model::resource::Resource;
98pub use authorization::provider::AuthorizationProvider;
99pub use authorization::provider::DefaultAuthorizationProvider;
100pub use authorization::strategy::abstract_authorization_strategy::AuthorizationStrategy;
101pub use authorization::strategy::StatefulAuthorizationStrategy;
102pub use authorization::strategy::StatelessAuthorizationStrategy;
103#[doc(hidden)]
104pub use bench_support::AuthAclWatcherLifecycleProbe;
105pub use bootstrap::BootstrapAdminIdentity;
106pub use bootstrap::BootstrapAdminProvisioner;
107pub use bootstrap::BootstrapEnrollmentRequest;
108pub use bootstrap::BootstrapEnrollmentResult;
109pub use bootstrap::BootstrapGrant;
110pub use bootstrap::BootstrapStatus;
111pub use bootstrap::BootstrapTransportContext;
112pub use bootstrap::OneTimeBootstrap;
113pub use config::AuthConfig;
114pub use credential_rotation::BreakGlassReason;
115pub use credential_rotation::BreakGlassStatus;
116pub use credential_rotation::CredentialAuditAction;
117pub use credential_rotation::CredentialAuditEvent;
118pub use credential_rotation::CredentialAuditOutcome;
119pub use credential_rotation::CredentialAuditSink;
120pub use credential_rotation::CredentialBundleParser;
121pub use credential_rotation::CredentialDescriptor;
122pub use credential_rotation::CredentialId;
123pub use credential_rotation::CredentialRotationManager;
124pub use credential_rotation::CredentialRotationSnapshot;
125pub use credential_rotation::CredentialVerification;
126pub use credential_rotation::CredentialVerificationSource;
127pub use credential_rotation::RetiringCredentialSnapshot;
128pub use credential_rotation::ValidatedCredential;
129pub use error::AuthFailureKind;
130pub use error::AuthOperation;
131pub use error::AuthServiceError;
132pub use error::AuthServiceResult;
133pub use layered_authorization::project_authorization_error;
134pub use layered_authorization::project_policy_decision;
135pub use maintenance::LoadedMaintenancePolicy;
136pub use maintenance::MaintenancePolicyReference;
137pub use permission::Permission;
138pub use provider_owner::AclImportAccount;
139pub use provider_owner::AclMetadataHandle;
140pub use provider_owner::AclSnapshotImport;
141pub use provider_owner::ProviderBundle;
142pub use provider_owner::ProviderCleanupErrors;
143pub use provider_owner::ProviderControl;
144pub use provider_owner::ProviderOperation as AuthRequestGuard;
145pub use provider_owner::UserMetadataHandle;
146pub use provider_ports::AclMetadataPort;
147pub use provider_ports::AclMetadataRead;
148pub use provider_ports::ProviderFuture;
149pub use provider_ports::SubjectKey;
150pub use provider_ports::UserMetadataPort;
151pub use provider_ports::UserMetadataRead;
152pub use remoting_auth_context::RemotingAuthContext;
153pub use rocketmq_observability::metrics::auth::AuthMetricSample;
154pub use rocketmq_observability::metrics::auth::AuthMetrics;
155pub use rocketmq_observability::metrics::auth::AuthMetricsSnapshot;
156pub use rocketmq_security_api::AuthorizationDecision;
157pub use rocketmq_security_api::AuthorizationDenial;
158pub use runtime::AuthRuntime;
159pub use runtime::AuthRuntimeBuilder;
160pub use runtime::AuthStartupFailure;
161pub use runtime::AuthenticationService;
162pub use runtime::AuthorizationService;
163pub use runtime::ProviderRegistry;
164pub use secret_provider::EncryptedFileSecretProvider;
165pub use secret_provider::EnvironmentSecretProvider;
166pub use secret_provider::SecretProviderRegistry;
167#[deprecated(
168    since = "1.1.0",
169    note = "use rocketmq_security_api::Principal; removal is intended for a future 2.0 boundary and remains subject to compatibility, migration, and release gates"
170)]
171pub use security_api::Principal as SecurityPrincipal;
172#[deprecated(
173    since = "1.1.0",
174    note = "use rocketmq_security_api::Resource; removal is intended for a future 2.0 boundary and remains subject to compatibility, migration, and release gates"
175)]
176pub use security_api::Resource as SecurityResource;
177
178#[doc(hidden)]
179pub mod bench_support {
180    use std::fs;
181    use std::path::PathBuf;
182    use std::sync::atomic::AtomicU64;
183    use std::sync::atomic::Ordering;
184    use std::time::Duration;
185    use std::time::Instant;
186
187    use cheetah_string::CheetahString;
188    use rocketmq_runtime::ChildServiceContext;
189    use rocketmq_runtime::ShutdownReport;
190    use serde::Serialize;
191
192    use crate::config::AuthConfig;
193    use crate::runtime::AuthRuntimeBuilder;
194    use crate::AuthFailureKind;
195    use crate::AuthServiceError;
196    use crate::AuthServiceResult;
197
198    static NEXT_ACL_WATCHER_PROBE_ID: AtomicU64 = AtomicU64::new(0);
199
200    #[derive(Clone, Debug, Serialize)]
201    pub struct AuthAclWatcherLifecycleProbe {
202        pub scheduled_runs: u64,
203        pub scheduled_skips: u64,
204        pub scheduled_overlaps: u64,
205        pub scheduled_failures: u64,
206        pub reload_success: bool,
207        pub shutdown_elapsed_us: u128,
208        pub shutdown_report: Option<ShutdownReport>,
209        pub healthy: bool,
210    }
211
212    pub async fn run_auth_acl_watcher_lifecycle_probe(
213        service_context: ChildServiceContext,
214    ) -> AuthServiceResult<AuthAclWatcherLifecycleProbe> {
215        let root = unique_acl_watcher_probe_root();
216        let _ = fs::remove_dir_all(&root);
217        fs::create_dir_all(&root).map_err(AuthServiceError::storage_write_failed)?;
218        let acl_file = root.join("plain_acl.yml");
219        write_acl_file(&acl_file, "first")?;
220
221        let runtime = AuthRuntimeBuilder::new(
222            AuthConfig {
223                acl_file: CheetahString::from(acl_file.to_string_lossy().as_ref()),
224                acl_file_watch_enabled: true,
225                acl_file_watch_interval_millis: 5,
226                ..AuthConfig::default()
227            },
228            service_context,
229        )
230        .build()
231        .await?;
232        let authn_provider = runtime.provider_registry().authentication_metadata_provider();
233        write_acl_file(&acl_file, "second")?;
234
235        let deadline = Instant::now() + Duration::from_secs(2);
236        let reload_success = loop {
237            match authn_provider.get_user("alice").await {
238                Ok(user) if user.password().map(|value| value.as_str()) == Some("second") => {
239                    break true;
240                }
241                Ok(_) => {}
242                Err(error) if error.kind() == AuthFailureKind::NotFound => {}
243                Err(error) => return Err(error),
244            }
245            if Instant::now() >= deadline {
246                break false;
247            }
248            tokio::time::sleep(Duration::from_millis(5)).await;
249        };
250
251        let mut snapshots = runtime.acl_file_watcher_snapshot();
252        for _ in 0..50 {
253            if snapshots
254                .iter()
255                .any(|snapshot| snapshot.runs > 0 && snapshot.active_runs == 0)
256            {
257                break;
258            }
259            tokio::time::sleep(Duration::from_millis(1)).await;
260            snapshots = runtime.acl_file_watcher_snapshot();
261        }
262        let scheduled_runs = snapshots.iter().map(|snapshot| snapshot.runs).sum();
263        let scheduled_skips = snapshots.iter().map(|snapshot| snapshot.skips).sum();
264        let scheduled_overlaps = snapshots.iter().map(|snapshot| snapshot.overlaps).sum();
265        let scheduled_failures = snapshots.iter().map(|snapshot| snapshot.failures).sum();
266
267        let shutdown_started_at = Instant::now();
268        let shutdown_report = runtime.shutdown_with_report().await?;
269        let shutdown_elapsed_us = shutdown_started_at.elapsed().as_micros();
270        let shutdown_healthy = shutdown_report
271            .as_ref()
272            .map(ShutdownReport::is_healthy)
273            .unwrap_or(false);
274        let healthy = reload_success
275            && scheduled_runs > 0
276            && scheduled_overlaps == 0
277            && scheduled_failures == 0
278            && shutdown_healthy;
279
280        let _ = fs::remove_dir_all(root);
281        Ok(AuthAclWatcherLifecycleProbe {
282            scheduled_runs,
283            scheduled_skips,
284            scheduled_overlaps,
285            scheduled_failures,
286            reload_success,
287            shutdown_elapsed_us,
288            shutdown_report,
289            healthy,
290        })
291    }
292
293    fn unique_acl_watcher_probe_root() -> PathBuf {
294        let id = NEXT_ACL_WATCHER_PROBE_ID.fetch_add(1, Ordering::Relaxed);
295        std::env::temp_dir().join(format!("rocketmq-auth-acl-watcher-{}-{id}", std::process::id()))
296    }
297
298    fn write_acl_file(path: &std::path::Path, secret: &str) -> AuthServiceResult<()> {
299        let content = format!(
300            r#"
301accounts:
302  - accessKey: alice
303    secretKey: {secret}
304"#
305        );
306        let temp_file = temp_acl_file_path(path);
307        fs::write(&temp_file, content).map_err(AuthServiceError::storage_write_failed)?;
308
309        replace_acl_file(&temp_file, path)
310    }
311
312    fn temp_acl_file_path(path: &std::path::Path) -> PathBuf {
313        let file_name = path
314            .file_name()
315            .and_then(|value| value.to_str())
316            .unwrap_or("plain_acl.yml");
317        let id = NEXT_ACL_WATCHER_PROBE_ID.fetch_add(1, Ordering::Relaxed);
318        path.with_file_name(format!(".{file_name}.{id}.tmp"))
319    }
320
321    #[cfg(not(windows))]
322    fn replace_acl_file(temp_file: &std::path::Path, path: &std::path::Path) -> AuthServiceResult<()> {
323        fs::rename(temp_file, path).map_err(AuthServiceError::storage_write_failed)
324    }
325
326    #[cfg(windows)]
327    fn replace_acl_file(temp_file: &std::path::Path, path: &std::path::Path) -> AuthServiceResult<()> {
328        fs::copy(temp_file, path).map_err(AuthServiceError::storage_write_failed)?;
329        let _ = fs::remove_file(temp_file);
330        Ok(())
331    }
332}
333
334#[cfg(test)]
335mod bench_support_tests {
336    use rocketmq_runtime::RuntimeContext;
337
338    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
339    async fn auth_acl_watcher_probe_reports_clean_shutdown() {
340        let runtime = RuntimeContext::from_current("auth-acl-watcher-probe-test");
341        let probe = super::bench_support::run_auth_acl_watcher_lifecycle_probe(
342            runtime.service_context("auth-acl-watcher-probe"),
343        )
344        .await
345        .expect("auth ACL watcher lifecycle probe should run");
346
347        assert!(probe.healthy, "{probe:?}");
348        assert!(probe.reload_success, "{probe:?}");
349        assert_eq!(probe.scheduled_overlaps, 0, "{probe:?}");
350        assert_eq!(probe.scheduled_failures, 0, "{probe:?}");
351    }
352}