1use std::backtrace::Backtrace;
18use std::error::Error as StdError;
19use std::fmt;
20use std::panic::Location;
21
22use rocketmq_error::fields;
23use rocketmq_error::CanonicalCondition;
24use rocketmq_error::DiagnosticView;
25use rocketmq_error::Error as CanonicalError;
26use rocketmq_error::ErrorCode;
27use rocketmq_error::ErrorContext;
28use rocketmq_error::ErrorDescriptor;
29use rocketmq_error::ErrorSeverity;
30use rocketmq_error::PublicErrorView;
31use rocketmq_error::RecoveryHint;
32use rocketmq_error::SharedError;
33use rocketmq_error::ViewContextViolation;
34
35use crate::SecurityBootstrapMaterial;
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum SecurityIdentifierRule {
40 NotEmpty,
42 MaximumLength,
44 CanonicalCharacters,
46}
47
48impl fmt::Display for SecurityIdentifierRule {
49 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50 formatter.write_str(match self {
51 Self::NotEmpty => "identifier-not-empty",
52 Self::MaximumLength => "identifier-maximum-length",
53 Self::CanonicalCharacters => "identifier-canonical-characters",
54 })
55 }
56}
57
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60pub enum MaintenancePolicyRule {
61 SchemaVersion,
63 PolicyIdentifier,
65 PolicyVersion,
67 AuthenticationRequired,
69 AuthorizationRequired,
71 FencingTokenRequired,
73 RequestLifetime,
75 ResourceLimits,
77 PrincipalIdentifier,
79 UniquePrincipalBinding,
81 PrincipalRoleRequired,
83 ReleaseOperatorRequired,
85 UniqueRoleGrant,
87 RoleCapabilityRequired,
89 AdministratorCheckpointForbidden,
91 ReleaseCheckpointRequired,
93}
94
95impl fmt::Display for MaintenancePolicyRule {
96 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97 formatter.write_str(match self {
98 Self::SchemaVersion => "maintenance-schema-version",
99 Self::PolicyIdentifier => "maintenance-policy-identifier",
100 Self::PolicyVersion => "maintenance-policy-version",
101 Self::AuthenticationRequired => "maintenance-authentication-required",
102 Self::AuthorizationRequired => "maintenance-authorization-required",
103 Self::FencingTokenRequired => "maintenance-fencing-token-required",
104 Self::RequestLifetime => "maintenance-request-lifetime",
105 Self::ResourceLimits => "maintenance-resource-limits",
106 Self::PrincipalIdentifier => "maintenance-principal-identifier",
107 Self::UniquePrincipalBinding => "maintenance-unique-principal-binding",
108 Self::PrincipalRoleRequired => "maintenance-principal-role-required",
109 Self::ReleaseOperatorRequired => "maintenance-release-operator-required",
110 Self::UniqueRoleGrant => "maintenance-unique-role-grant",
111 Self::RoleCapabilityRequired => "maintenance-role-capability-required",
112 Self::AdministratorCheckpointForbidden => "maintenance-administrator-checkpoint-forbidden",
113 Self::ReleaseCheckpointRequired => "maintenance-release-checkpoint-required",
114 })
115 }
116}
117
118#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
123pub enum SecurityContractViolation {
124 #[error("security identifier violates {rule}")]
126 InvalidIdentifier {
127 rule: SecurityIdentifierRule,
129 },
130 #[error("secret material must not be empty")]
132 EmptySecretMaterial,
133 #[error("security bootstrap profile is required")]
135 BootstrapProfileRequired,
136 #[error("security profile is unknown")]
138 UnknownSecurityProfile,
139 #[error("security bootstrap environment value is not valid Unicode")]
141 BootstrapEnvironmentEncoding,
142 #[error("security bootstrap is missing {material}")]
144 BootstrapMaterialRequired {
145 material: SecurityBootstrapMaterial,
147 },
148 #[error("security bootstrap {material} is not a regular file")]
150 BootstrapMaterialNotRegularFile {
151 material: SecurityBootstrapMaterial,
153 },
154 #[error("security bootstrap {material} is empty")]
156 BootstrapMaterialEmpty {
157 material: SecurityBootstrapMaterial,
159 },
160 #[error("secure bootstrap requires a secret provider")]
162 SecretProviderRequired,
163 #[error("configured secret provider is unsupported")]
165 SecretProviderUnsupported,
166 #[error("development-insecure profile requires loopback listeners")]
168 DevelopmentListenerNotLoopback,
169 #[error("secret provider configuration is invalid")]
171 ProviderConfigurationInvalid,
172 #[error("secret storage permissions are not owner-only")]
174 SecretStoragePermissionsInsecure,
175 #[error("secure transport requires a signing provider")]
177 SigningProviderRequired,
178 #[error("maintenance policy violates {rule}")]
180 MaintenancePolicy {
181 rule: MaintenancePolicyRule,
183 },
184}
185
186#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188pub enum SecurityProviderFailure {
189 NotFound,
191 Conflict,
193 Unsupported,
195 InvalidData,
197 ContractViolation,
199 Unavailable,
201 OperationFailed,
203}
204
205impl fmt::Display for SecurityProviderFailure {
206 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
207 formatter.write_str(match self {
208 Self::NotFound => "not_found",
209 Self::Conflict => "conflict",
210 Self::Unsupported => "unsupported",
211 Self::InvalidData => "invalid_data",
212 Self::ContractViolation => "contract_violation",
213 Self::Unavailable => "unavailable",
214 Self::OperationFailed => "operation_failed",
215 })
216 }
217}
218
219#[derive(Clone, Copy, Debug, Eq, PartialEq)]
221pub enum SecurityOperation {
222 Validate,
224 ResolveProvider,
226 RegisterProvider,
228 InspectBootstrapMaterial,
230 ReadSecret,
232 WriteSecret,
234 InspectSecret,
236 EncryptSecret,
238 DecryptSecret,
240 SynchronizeSecret,
242 SignRequest,
244 LoadPolicy,
246 RecordAudit,
248 RotateCredentials,
250}
251
252impl SecurityOperation {
253 #[must_use]
255 pub const fn as_str(self) -> &'static str {
256 match self {
257 Self::Validate => "validate",
258 Self::ResolveProvider => "resolve_provider",
259 Self::RegisterProvider => "register_provider",
260 Self::InspectBootstrapMaterial => "inspect_bootstrap_material",
261 Self::ReadSecret => "read_secret",
262 Self::WriteSecret => "write_secret",
263 Self::InspectSecret => "inspect_secret",
264 Self::EncryptSecret => "encrypt_secret",
265 Self::DecryptSecret => "decrypt_secret",
266 Self::SynchronizeSecret => "synchronize_secret",
267 Self::SignRequest => "sign_request",
268 Self::LoadPolicy => "load_policy",
269 Self::RecordAudit => "record_audit",
270 Self::RotateCredentials => "rotate_credentials",
271 }
272 }
273}
274
275impl fmt::Display for SecurityOperation {
276 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
277 formatter.write_str(self.as_str())
278 }
279}
280
281#[derive(Clone)]
287pub struct SecurityProviderError {
288 error: SharedError,
289 kind: SecurityProviderFailure,
290 operation: SecurityOperation,
291}
292
293impl SecurityProviderError {
294 #[must_use]
296 #[track_caller]
297 pub fn new(kind: SecurityProviderFailure, operation: SecurityOperation) -> Self {
298 let error = CanonicalError::new(provider_descriptor(kind)).with_context(provider_context(operation, false));
299 Self {
300 error: std::sync::Arc::new(error),
301 kind,
302 operation,
303 }
304 }
305
306 #[must_use]
308 #[track_caller]
309 pub fn caused_by(
310 kind: SecurityProviderFailure,
311 operation: SecurityOperation,
312 source: impl StdError + Send + Sync + 'static,
313 ) -> Self {
314 let error = CanonicalError::caused_by(provider_descriptor(kind), source)
315 .with_context(provider_context(operation, true));
316 Self {
317 error: std::sync::Arc::new(error),
318 kind,
319 operation,
320 }
321 }
322
323 #[must_use]
325 pub fn contract(operation: SecurityOperation, violation: SecurityContractViolation) -> Self {
326 Self::caused_by(SecurityProviderFailure::ContractViolation, operation, violation)
327 }
328
329 #[must_use]
331 pub const fn kind(&self) -> SecurityProviderFailure {
332 self.kind
333 }
334
335 #[must_use]
337 pub const fn operation(&self) -> SecurityOperation {
338 self.operation
339 }
340
341 #[must_use]
343 pub fn shared_error(&self) -> &SharedError {
344 &self.error
345 }
346
347 #[must_use]
349 pub fn into_shared_error(self) -> SharedError {
350 self.error
351 }
352
353 #[must_use]
355 pub fn descriptor(&self) -> &'static ErrorDescriptor {
356 self.error.descriptor()
357 }
358
359 #[must_use]
361 pub fn code(&self) -> ErrorCode {
362 self.error.code()
363 }
364
365 #[must_use]
367 pub fn condition(&self) -> CanonicalCondition {
368 self.error.condition()
369 }
370
371 #[must_use]
373 pub fn severity(&self) -> ErrorSeverity {
374 self.error.severity()
375 }
376
377 #[must_use]
379 pub fn recovery_hint(&self) -> RecoveryHint {
380 self.error.recovery_hint()
381 }
382
383 #[must_use]
385 pub fn context(&self) -> &ErrorContext {
386 self.error.context()
387 }
388
389 #[must_use]
391 pub fn location(&self) -> &'static Location<'static> {
392 self.error.location()
393 }
394
395 #[must_use]
397 pub fn backtrace(&self) -> Option<&Backtrace> {
398 self.error.backtrace()
399 }
400
401 pub fn public_view(&self) -> Result<PublicErrorView<'_>, ViewContextViolation> {
408 self.error.public_view()
409 }
410
411 pub fn diagnostic_view(&self) -> Result<DiagnosticView<'_>, ViewContextViolation> {
418 self.error.diagnostic_view()
419 }
420
421 #[must_use]
423 pub fn source(&self) -> Option<&(dyn StdError + 'static)> {
424 self.error.source()
425 }
426}
427
428fn provider_descriptor(kind: SecurityProviderFailure) -> &'static ErrorDescriptor {
429 match kind {
430 SecurityProviderFailure::NotFound => &rocketmq_error::SECURITY_PROVIDER_NOT_FOUND,
431 SecurityProviderFailure::Conflict => &rocketmq_error::SECURITY_PROVIDER_CONFLICT,
432 SecurityProviderFailure::Unsupported => &rocketmq_error::SECURITY_PROVIDER_UNSUPPORTED,
433 SecurityProviderFailure::InvalidData => &rocketmq_error::SECURITY_PROVIDER_INVALID_DATA,
434 SecurityProviderFailure::ContractViolation => &rocketmq_error::SECURITY_PROVIDER_CONTRACT_VIOLATION,
435 SecurityProviderFailure::Unavailable => &rocketmq_error::SECURITY_PROVIDER_UNAVAILABLE,
436 SecurityProviderFailure::OperationFailed => &rocketmq_error::SECURITY_PROVIDER_OPERATION_FAILED,
437 }
438}
439
440fn provider_context(operation: SecurityOperation, source_present: bool) -> ErrorContext {
441 let mut context = ErrorContext::new().with_text(fields::OPERATION_DIAGNOSTIC, operation.as_str());
442 if source_present {
443 context = context.with_secret_presence(fields::SOURCE_PRESENT);
444 }
445 context
446}
447
448impl From<SecurityContractViolation> for SecurityProviderError {
449 fn from(violation: SecurityContractViolation) -> Self {
450 Self::contract(SecurityOperation::Validate, violation)
451 }
452}
453
454impl fmt::Display for SecurityProviderError {
455 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
456 fmt::Display::fmt(self.error.as_ref(), formatter)
457 }
458}
459
460impl fmt::Debug for SecurityProviderError {
461 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
462 formatter
463 .debug_struct("SecurityProviderError")
464 .field("code", &self.code())
465 .field("condition", &self.condition())
466 .field("kind", &self.kind)
467 .field("operation", &self.operation)
468 .field("source_present", &self.error.source().is_some())
469 .finish()
470 }
471}
472
473impl StdError for SecurityProviderError {
474 fn source(&self) -> Option<&(dyn StdError + 'static)> {
475 self.error.source()
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use std::error::Error;
482 use std::io;
483 use std::mem::size_of;
484
485 use super::*;
486
487 #[test]
488 fn provider_error_redacts_and_retains_typed_source() {
489 let sentinel = "secret-provider-sentinel";
490 let error = SecurityProviderError::caused_by(
491 SecurityProviderFailure::Unavailable,
492 SecurityOperation::ReadSecret,
493 io::Error::other(sentinel),
494 );
495
496 assert!(!error.to_string().contains(sentinel));
497 assert!(!format!("{error:?}").contains(sentinel));
498 assert_eq!(error.kind(), SecurityProviderFailure::Unavailable);
499 assert_eq!(error.operation(), SecurityOperation::ReadSecret);
500 assert_eq!(error.descriptor(), &rocketmq_error::SECURITY_PROVIDER_UNAVAILABLE);
501 assert_eq!(error.condition(), CanonicalCondition::Unavailable);
502 assert_eq!(error.context().len(), 2);
503 assert!(error.public_view().is_ok());
504 assert!(error.diagnostic_view().is_ok());
505 assert!(Error::source(&error).unwrap().downcast_ref::<io::Error>().is_some());
506 }
507
508 #[test]
509 fn contract_conversion_is_value_free_and_small() {
510 let error = SecurityProviderError::from(SecurityContractViolation::EmptySecretMaterial);
511
512 assert_eq!(error.kind(), SecurityProviderFailure::ContractViolation);
513 assert_eq!(error.operation(), SecurityOperation::Validate);
514 assert_eq!(
515 error.descriptor(),
516 &rocketmq_error::SECURITY_PROVIDER_CONTRACT_VIOLATION
517 );
518 assert!(error
519 .source()
520 .unwrap()
521 .downcast_ref::<SecurityContractViolation>()
522 .is_some());
523 assert!(size_of::<SecurityProviderError>() <= 4 * size_of::<usize>());
524 }
525
526 #[test]
527 fn every_provider_failure_has_one_catalog_identity() {
528 let cases = [
529 (
530 SecurityProviderFailure::NotFound,
531 &rocketmq_error::SECURITY_PROVIDER_NOT_FOUND,
532 ),
533 (
534 SecurityProviderFailure::Conflict,
535 &rocketmq_error::SECURITY_PROVIDER_CONFLICT,
536 ),
537 (
538 SecurityProviderFailure::Unsupported,
539 &rocketmq_error::SECURITY_PROVIDER_UNSUPPORTED,
540 ),
541 (
542 SecurityProviderFailure::InvalidData,
543 &rocketmq_error::SECURITY_PROVIDER_INVALID_DATA,
544 ),
545 (
546 SecurityProviderFailure::ContractViolation,
547 &rocketmq_error::SECURITY_PROVIDER_CONTRACT_VIOLATION,
548 ),
549 (
550 SecurityProviderFailure::Unavailable,
551 &rocketmq_error::SECURITY_PROVIDER_UNAVAILABLE,
552 ),
553 (
554 SecurityProviderFailure::OperationFailed,
555 &rocketmq_error::SECURITY_PROVIDER_OPERATION_FAILED,
556 ),
557 ];
558
559 for (kind, descriptor) in cases {
560 let error = SecurityProviderError::new(kind, SecurityOperation::ReadSecret);
561 assert_eq!(error.descriptor(), descriptor);
562 assert_eq!(error.kind(), kind);
563 assert_eq!(error.context().len(), 1);
564 assert!(error.source().is_none());
565 }
566 }
567}