cm_config/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{Context, Error, format_err};
6use camino::Utf8PathBuf;
7use cm_rust::{CapabilityTypeName, FidlIntoNative};
8use cm_types::{Name, ParseError, Url, symmetrical_enums};
9use fidl::unpersist;
10use fidl_fuchsia_component_decl as fdecl;
11use fidl_fuchsia_component_internal::{
12    self as component_internal, BuiltinBootResolver, CapabilityPolicyAllowlists,
13    DebugRegistrationPolicyAllowlists, LogDestination, RealmBuilderResolverAndRunner,
14};
15use log::warn;
16use moniker::{ChildName, ExtendedMoniker, Moniker, MonikerError};
17use std::collections::{HashMap, HashSet};
18use std::str::FromStr;
19use std::sync::Arc;
20use thiserror::Error;
21use version_history::{AbiRevision, AbiRevisionError, VersionHistory};
22
23#[cfg(feature = "serde")]
24use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
25
26/// Runtime configuration options.
27/// This configuration intended to be "global", in that the same configuration
28/// is applied throughout a given running instance of component_manager.
29#[derive(Debug, PartialEq, Eq)]
30pub struct RuntimeConfig {
31    /// How many children, maximum, are returned by a call to `ChildIterator.next()`.
32    pub list_children_batch_size: usize,
33
34    /// Security policy configuration.
35    pub security_policy: Arc<SecurityPolicy>,
36
37    /// If true, component manager will be in debug mode. In this mode, component manager
38    /// provides the `EventSource` protocol and exposes this protocol. The root component
39    /// must be manually started using the LifecycleController protocol in the hub.
40    ///
41    /// This is done so that an external component (say an integration test) can subscribe
42    /// to events before the root component has started.
43    pub debug: bool,
44
45    /// Where to look for the trace provider: normal Namespace, or internal RootExposed.
46    /// This is ignored if tracing is not enabled as a feature.
47    pub trace_provider: TraceProvider,
48
49    /// Enables Component Manager's introspection APIs (RealmQuery, RealmExplorer,
50    /// RouteValidator, LifecycleController, etc.) for use by components.
51    pub enable_introspection: bool,
52
53    /// If true, component_manager will serve an instance of fuchsia.process.Launcher and use this
54    /// launcher for the built-in ELF component runner. The root component can additionally
55    /// use and/or offer this service using '/builtin/fuchsia.process.Launcher' from realm.
56    // This flag exists because the built-in process launcher *only* works when
57    // component_manager runs under a job that has ZX_POL_NEW_PROCESS set to allow, like the root
58    // job. Otherwise, the component_manager process cannot directly create process through
59    // zx_process_create. When we run component_manager elsewhere, like in test environments, it
60    // has to use the fuchsia.process.Launcher service provided through its namespace instead.
61    pub use_builtin_process_launcher: bool,
62
63    /// If true, component_manager will maintain a UTC kernel clock and vend write handles through
64    /// an instance of `fuchsia.time.Maintenance`. This flag should only be used with the top-level
65    /// component_manager.
66    pub maintain_utc_clock: bool,
67
68    // The number of threads to use for running component_manager's executor.
69    // Value defaults to 1.
70    pub num_threads: u8,
71
72    /// The list of capabilities offered from component manager's namespace.
73    pub namespace_capabilities: Vec<cm_rust::CapabilityDecl>,
74
75    /// The list of capabilities offered from component manager as built-in capabilities.
76    pub builtin_capabilities: Vec<cm_rust::CapabilityDecl>,
77
78    /// URL of the root component to launch. This field is used if no URL
79    /// is passed to component manager. If value is passed in both places, then
80    /// an error is raised.
81    pub root_component_url: Option<Url>,
82
83    /// Path to the component ID index, parsed from
84    /// `fuchsia.component.internal.RuntimeConfig.component_id_index_path`.
85    pub component_id_index_path: Option<Utf8PathBuf>,
86
87    /// Where to log to.
88    pub log_destination: LogDestination,
89
90    /// If true, component manager will log all events dispatched in the topology.
91    pub log_all_events: bool,
92
93    /// Which builtin resolver to use for the fuchsia-boot scheme. If not supplied this defaults to
94    /// the NONE option.
95    pub builtin_boot_resolver: BuiltinBootResolver,
96
97    /// If and how the realm builder resolver and runner are enabled.
98    pub realm_builder_resolver_and_runner: RealmBuilderResolverAndRunner,
99
100    /// The enforcement and validation policy to apply to component target ABI revisions.
101    pub abi_revision_policy: AbiRevisionPolicy,
102
103    /// Where to get the vmex resource from.
104    pub vmex_source: VmexSource,
105
106    /// Components that opt into health checks before an update is committed.
107    pub health_check: HealthCheck,
108
109    /// Capabilities to be injected into allowlisted components.
110    pub inject_capabilities: Vec<InjectedCapabilities>,
111
112    /// Runtime configuration for Scudo heap allocation, using a 'KEY=VALUE,KEY=VALUE'
113    /// format. See Scudo flags documentation for details. It is shadowed by `SCUDO_OPTION`
114    //  environ variable from the component manifest.
115    pub scudo_options: Option<String>,
116}
117
118/// A single security policy allowlist entry.
119#[derive(Debug, PartialEq, Eq, Hash, Clone)]
120pub struct AllowlistEntry {
121    // A list of matchers that apply to each child in a moniker.
122    // If this list is empty, we must only allow the root moniker.
123    pub matchers: Vec<AllowlistMatcher>,
124}
125
126impl AllowlistEntry {
127    pub fn matches(&self, target_moniker: &Moniker) -> bool {
128        let path = target_moniker.path();
129        let mut iter = path.iter();
130
131        if self.matchers.is_empty() && !target_moniker.is_root() {
132            // If there are no matchers in the allowlist, the moniker must be the root.
133            // Anything else will not match.
134            return false;
135        }
136
137        for matcher in &self.matchers {
138            let cur_child = if let Some(target_child) = iter.next() {
139                target_child
140            } else {
141                // We have more matchers, but the moniker has already ended.
142                return false;
143            };
144            match matcher {
145                AllowlistMatcher::Exact(child) => {
146                    if cur_child != &child {
147                        // The child does not exactly match.
148                        return false;
149                    }
150                }
151                // Any child is acceptable. Continue with remaining matchers.
152                AllowlistMatcher::AnyChild => continue,
153                // Any descendant at this point is acceptable.
154                AllowlistMatcher::AnyDescendant => return true,
155                AllowlistMatcher::AnyDescendantInCollection(expected_collection) => {
156                    if let Some(collection) = cur_child.collection() {
157                        if collection == expected_collection {
158                            // This child is in a collection and the name matches.
159                            // Because we allow any descendant, return true immediately.
160                            return true;
161                        } else {
162                            // This child is in a collection but the name does not match.
163                            return false;
164                        }
165                    } else {
166                        // This child is not in a collection, so it does not match.
167                        return false;
168                    }
169                }
170                AllowlistMatcher::AnyChildInCollection(expected_collection) => {
171                    if let Some(collection) = cur_child.collection() {
172                        if collection != expected_collection {
173                            // This child is in a collection but the name does not match.
174                            return false;
175                        }
176                    } else {
177                        // This child is not in a collection, so it does not match.
178                        return false;
179                    }
180                }
181            }
182        }
183
184        if iter.next().is_some() {
185            // We've gone through all the matchers, but there are still children
186            // in the moniker. Descendant cases are already handled above, so this
187            // must be a failure to match.
188            false
189        } else {
190            true
191        }
192    }
193}
194
195impl FromStr for AllowlistEntry {
196    type Err = AllowlistEntryParseError;
197
198    fn from_str(input: &str) -> Result<AllowlistEntry, AllowlistEntryParseError> {
199        let entry = if let Some(entry) = input.strip_prefix('/') {
200            entry
201        } else {
202            return Err(AllowlistEntryParseError::NoLeadingSlash(input.to_string()));
203        };
204
205        if entry.is_empty() {
206            return Ok(AllowlistEntry { matchers: vec![] });
207        }
208
209        if entry.contains("**") && !entry.ends_with("**") {
210            return Err(AllowlistEntryParseError::DescendantWildcardOnlyAtEnd(input.to_string()));
211        }
212
213        let mut parts = vec![];
214        for name in entry.split('/') {
215            let part = match name {
216                "**" => AllowlistMatcher::AnyDescendant,
217                "*" => AllowlistMatcher::AnyChild,
218                name => {
219                    if let Some(collection_name) = name.strip_suffix(":**") {
220                        let collection_name = Name::new(collection_name).map_err(|e| {
221                            AllowlistEntryParseError::InvalidCollectionName(
222                                collection_name.to_string(),
223                                e,
224                            )
225                        })?;
226                        AllowlistMatcher::AnyDescendantInCollection(collection_name)
227                    } else if let Some(collection_name) = name.strip_suffix(":*") {
228                        let collection_name = Name::new(collection_name).map_err(|e| {
229                            AllowlistEntryParseError::InvalidCollectionName(
230                                collection_name.to_string(),
231                                e,
232                            )
233                        })?;
234                        AllowlistMatcher::AnyChildInCollection(collection_name)
235                    } else {
236                        let child_moniker = ChildName::parse(name).map_err(|e| {
237                            AllowlistEntryParseError::InvalidChildName(name.to_string(), e)
238                        })?;
239                        AllowlistMatcher::Exact(child_moniker)
240                    }
241                }
242            };
243            parts.push(part);
244        }
245
246        Ok(AllowlistEntry { matchers: parts })
247    }
248}
249
250impl ToString for AllowlistEntry {
251    fn to_string(&self) -> String {
252        let mut parts = vec!["".to_string()];
253        for matcher in &self.matchers {
254            parts.push(match matcher {
255                AllowlistMatcher::AnyDescendant => "**".to_string(),
256                AllowlistMatcher::AnyChild => "*".to_string(),
257                AllowlistMatcher::AnyDescendantInCollection(bounded_name) => {
258                    format!("{bounded_name}:**")
259                }
260                AllowlistMatcher::AnyChildInCollection(bounded_name) => format!("{bounded_name}:*"),
261                AllowlistMatcher::Exact(child_name) => child_name.to_string(),
262            });
263        }
264        parts.join("/")
265    }
266}
267
268#[cfg(feature = "serde")]
269impl<'de> Deserialize<'de> for AllowlistEntry {
270    fn deserialize<D>(deserializer: D) -> Result<AllowlistEntry, D::Error>
271    where
272        D: Deserializer<'de>,
273    {
274        let s = String::deserialize(deserializer)?;
275        s.parse().map_err(de::Error::custom)
276    }
277}
278
279#[cfg(feature = "serde")]
280impl Serialize for AllowlistEntry {
281    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
282    where
283        S: Serializer,
284    {
285        serializer.serialize_str(&self.to_string())
286    }
287}
288
289#[derive(Debug, PartialEq, Eq, Hash, Clone)]
290pub enum AllowlistMatcher {
291    /// Allow the child with this exact ChildName.
292    /// Examples: "bar", "foo:bar", "baz"
293    Exact(ChildName),
294    /// Allow any descendant of this realm.
295    /// This is indicated by "**" in a config file.
296    AnyDescendant,
297    /// Allow any child of this realm.
298    /// This is indicated by "*" in a config file.
299    AnyChild,
300    /// Allow any child of a particular collection in this realm.
301    /// This is indicated by "<collection>:*" in a config file.
302    AnyChildInCollection(Name),
303    /// Allow any descendant of a particular collection in this realm.
304    /// This is indicated by "<collection>:**" in a config file.
305    AnyDescendantInCollection(Name),
306}
307
308pub struct AllowlistEntryBuilder {
309    parts: Vec<AllowlistMatcher>,
310}
311
312impl AllowlistEntryBuilder {
313    pub fn new() -> Self {
314        Self { parts: vec![] }
315    }
316
317    pub fn build_exact_from_moniker(m: &Moniker) -> AllowlistEntry {
318        Self::new().exact_from_moniker(m).build()
319    }
320
321    pub fn exact(mut self, name: &str) -> Self {
322        self.parts.push(AllowlistMatcher::Exact(ChildName::parse(name).unwrap()));
323        self
324    }
325
326    pub fn exact_from_moniker(mut self, m: &Moniker) -> Self {
327        let path = m.path();
328        let parts = path.iter().map(|c| AllowlistMatcher::Exact((*c).into()));
329        self.parts.extend(parts);
330        self
331    }
332
333    pub fn any_child(mut self) -> Self {
334        self.parts.push(AllowlistMatcher::AnyChild);
335        self
336    }
337
338    pub fn any_descendant(mut self) -> AllowlistEntry {
339        self.parts.push(AllowlistMatcher::AnyDescendant);
340        self.build()
341    }
342
343    pub fn any_descendant_in_collection(mut self, collection: &str) -> AllowlistEntry {
344        self.parts
345            .push(AllowlistMatcher::AnyDescendantInCollection(Name::new(collection).unwrap()));
346        self.build()
347    }
348
349    pub fn any_child_in_collection(mut self, collection: &str) -> Self {
350        self.parts.push(AllowlistMatcher::AnyChildInCollection(Name::new(collection).unwrap()));
351        self
352    }
353
354    pub fn build(self) -> AllowlistEntry {
355        AllowlistEntry { matchers: self.parts }
356    }
357}
358
359/// Runtime security policy.
360#[derive(Debug, Clone, Default, PartialEq, Eq)]
361pub struct SecurityPolicy {
362    /// Allowlists for Zircon job policy.
363    pub job_policy: JobPolicyAllowlists,
364
365    /// Capability routing policies. The key contains all the information required
366    /// to uniquely identify any routable capability and the set of monikers
367    /// define the set of component paths that are allowed to access this specific
368    /// capability.
369    pub capability_policy: HashMap<CapabilityAllowlistKey, HashSet<AllowlistEntry>>,
370
371    /// Debug Capability routing policies. The key contains all the absolute information
372    /// needed to identify a routable capability and the set of DebugCapabilityAllowlistEntries
373    /// define the allowed set of routing paths from the capability source to the environment
374    /// offering the capability.
375    pub debug_capability_policy:
376        HashMap<DebugCapabilityKey, HashSet<DebugCapabilityAllowlistEntry>>,
377
378    /// Allowlists component child policy. These allowlists control what components are allowed
379    /// to set privileged options on their children.
380    pub child_policy: ChildPolicyAllowlists,
381}
382
383/// Allowlist key for debug capability allowlists.
384/// This defines all portions of the allowlist that do not support globbing.
385#[derive(Debug, Hash, PartialEq, Eq, Clone)]
386pub struct DebugCapabilityKey {
387    pub name: Name,
388    pub source: CapabilityAllowlistSource,
389    pub capability: CapabilityTypeName,
390    pub env_name: Name,
391}
392
393/// Represents a single allowed route for a debug capability.
394#[derive(Debug, PartialEq, Eq, Clone, Hash)]
395pub struct DebugCapabilityAllowlistEntry {
396    dest: AllowlistEntry,
397}
398
399impl DebugCapabilityAllowlistEntry {
400    pub fn new(dest: AllowlistEntry) -> Self {
401        Self { dest }
402    }
403
404    pub fn matches(&self, dest: &Moniker) -> bool {
405        self.dest.matches(dest)
406    }
407}
408
409/// Allowlists for Zircon job policy. Part of runtime security policy.
410#[derive(Debug, Clone, Default, PartialEq, Eq)]
411pub struct JobPolicyAllowlists {
412    /// Entries for components allowed to be given the ZX_POL_AMBIENT_MARK_VMO_EXEC job policy.
413    ///
414    /// Components must request this policy by including "job_policy_ambient_mark_vmo_exec: true" in
415    /// their manifest's program object and must be using the ELF runner.
416    /// This is equivalent to the v1 'deprecated-ambient-replace-as-executable' feature.
417    pub ambient_mark_vmo_exec: Vec<AllowlistEntry>,
418
419    /// Entries for components allowed to have their original process marked as critical to
420    /// component_manager's job.
421    ///
422    /// Components must request this critical marking by including "main_process_critical: true" in
423    /// their manifest's program object and must be using the ELF runner.
424    pub main_process_critical: Vec<AllowlistEntry>,
425
426    /// Entries for components allowed to call zx_process_create directly (e.g., do not have
427    /// ZX_POL_NEW_PROCESS set to ZX_POL_ACTION_DENY).
428    ///
429    /// Components must request this policy by including "job_policy_create_raw_processes: true" in
430    /// their manifest's program object and must be using the ELF runner.
431    pub create_raw_processes: Vec<AllowlistEntry>,
432}
433
434/// Allowlists for child option policy. Part of runtime security policy.
435#[derive(Debug, Default, PartialEq, Eq, Clone)]
436pub struct ChildPolicyAllowlists {
437    /// Absolute monikers of component instances allowed to have the
438    /// `on_terminate=REBOOT` in their `children` declaration.
439    pub reboot_on_terminate: Vec<AllowlistEntry>,
440}
441
442/// The available capability sources for capability allow lists. This is a strict
443/// subset of all possible Ref types, with equality support.
444#[derive(Debug, PartialEq, Eq, Hash, Clone)]
445pub enum CapabilityAllowlistSource {
446    Self_,
447    Framework,
448    Capability,
449    Environment,
450    Void,
451}
452
453#[derive(Debug, Clone, Error, PartialEq, Eq)]
454pub enum CompatibilityCheckError {
455    #[error("Component did not present an ABI revision")]
456    AbiRevisionAbsent,
457    #[error(transparent)]
458    AbiRevisionInvalid(#[from] AbiRevisionError),
459}
460
461/// The enforcement and validation policy to apply to component target ABI
462/// revisions. By default, enforce ABI compatibility for all components.
463#[derive(Debug, PartialEq, Eq, Default, Clone)]
464pub struct AbiRevisionPolicy {
465    allowlist: Vec<AllowlistEntry>,
466}
467
468impl AbiRevisionPolicy {
469    pub fn new(allowlist: Vec<AllowlistEntry>) -> Self {
470        Self { allowlist }
471    }
472
473    /// Check if the abi_revision, if present, is supported by the platform and compatible with the
474    /// `AbiRevisionPolicy`. Regardless of the enforcement policy, log a warning if the
475    /// ABI revision is missing or not supported by the platform.
476    pub fn check_compatibility(
477        &self,
478        version_history: &VersionHistory,
479        moniker: &Moniker,
480        abi_revision: Option<AbiRevision>,
481    ) -> Result<(), CompatibilityCheckError> {
482        let only_warn = self.allowlist.iter().any(|matcher| matcher.matches(moniker));
483
484        let Some(abi_revision) = abi_revision else {
485            return if only_warn {
486                warn!("Ignoring missing ABI revision in {} because it is allowlisted.", moniker);
487                Ok(())
488            } else {
489                Err(CompatibilityCheckError::AbiRevisionAbsent)
490            };
491        };
492
493        let abi_error = match version_history.check_abi_revision_for_runtime(abi_revision) {
494            Ok(()) => return Ok(()),
495            Err(AbiRevisionError::PlatformMismatch { .. })
496            | Err(AbiRevisionError::UnstableMismatch { .. })
497            | Err(AbiRevisionError::Malformed { .. }) => {
498                // TODO(https://fxbug.dev/347724655): Make this an error.
499                warn!(
500                    "Unsupported platform ABI revision: 0x{}.
501This will become an error soon! See https://fxbug.dev/347724655",
502                    abi_revision
503                );
504                return Ok(());
505            }
506            Err(e @ AbiRevisionError::TooNew { .. })
507            | Err(e @ AbiRevisionError::Retired { .. })
508            | Err(e @ AbiRevisionError::Invalid) => e,
509        };
510
511        if only_warn {
512            warn!(
513                "Ignoring AbiRevisionError in {} because it is allowlisted: {}",
514                moniker, abi_error
515            );
516            Ok(())
517        } else {
518            Err(CompatibilityCheckError::AbiRevisionInvalid(abi_error))
519        }
520    }
521}
522
523impl TryFrom<component_internal::AbiRevisionPolicy> for AbiRevisionPolicy {
524    type Error = Error;
525
526    fn try_from(abi_revision_policy: component_internal::AbiRevisionPolicy) -> Result<Self, Error> {
527        Ok(Self::new(parse_allowlist_entries(&abi_revision_policy.allowlist)?))
528    }
529}
530
531/// Where to get the Vmex resource from, if this component_manager is hosting bootfs.
532/// Defaults to `SystemResource`.
533#[derive(Debug, PartialEq, Eq, Clone)]
534pub enum VmexSource {
535    SystemResource,
536    Namespace,
537}
538
539symmetrical_enums!(VmexSource, component_internal::VmexSource, SystemResource, Namespace);
540
541impl Default for VmexSource {
542    fn default() -> Self {
543        VmexSource::SystemResource
544    }
545}
546
547/// Where to look for the trace provider.
548/// Defaults to `Namespace`.
549#[derive(Debug, PartialEq, Eq, Clone)]
550pub enum TraceProvider {
551    Namespace,
552    RootExposed,
553}
554
555symmetrical_enums!(TraceProvider, component_internal::TraceProvider, Namespace, RootExposed);
556
557impl Default for TraceProvider {
558    fn default() -> Self {
559        TraceProvider::Namespace
560    }
561}
562
563/// Information about the health checks during the update process.
564#[derive(Debug, PartialEq, Eq, Default, Clone)]
565pub struct HealthCheck {
566    pub monikers: Vec<String>,
567}
568
569impl HealthCheck {
570    pub fn new(monikers: Vec<String>) -> Self {
571        Self { monikers }
572    }
573}
574
575impl TryFrom<component_internal::HealthCheck> for HealthCheck {
576    type Error = Error;
577
578    fn try_from(health_check: component_internal::HealthCheck) -> Result<Self, Error> {
579        Ok(Self::new(health_check.monikers.unwrap()))
580    }
581}
582
583/// Allowlist key for capability routing policy. Part of the runtime
584/// security policy. This defines all the required keying information to lookup
585/// whether a capability exists in the policy map or not.
586#[derive(Debug, PartialEq, Eq, Hash, Clone)]
587pub struct CapabilityAllowlistKey {
588    pub source_moniker: ExtendedMoniker,
589    pub source_name: Name,
590    pub source: CapabilityAllowlistSource,
591    pub capability: CapabilityTypeName,
592}
593
594impl Default for RuntimeConfig {
595    fn default() -> Self {
596        Self {
597            list_children_batch_size: 1000,
598            // security_policy must default to empty to ensure that it fails closed if no
599            // configuration is present or it fails to load.
600            security_policy: Default::default(),
601            debug: false,
602            trace_provider: Default::default(),
603            enable_introspection: false,
604            use_builtin_process_launcher: false,
605            maintain_utc_clock: false,
606            num_threads: 1,
607            namespace_capabilities: vec![],
608            builtin_capabilities: vec![],
609            root_component_url: Default::default(),
610            component_id_index_path: None,
611            log_destination: LogDestination::Syslog,
612            log_all_events: false,
613            builtin_boot_resolver: BuiltinBootResolver::None,
614            realm_builder_resolver_and_runner: RealmBuilderResolverAndRunner::None,
615            abi_revision_policy: Default::default(),
616            vmex_source: Default::default(),
617            health_check: Default::default(),
618            inject_capabilities: Default::default(),
619            scudo_options: Default::default(),
620        }
621    }
622}
623
624impl RuntimeConfig {
625    pub fn new_from_bytes(bytes: &Vec<u8>) -> Result<Self, Error> {
626        Ok(Self::try_from(unpersist::<component_internal::Config>(&bytes)?)?)
627    }
628
629    fn translate_namespace_capabilities(
630        capabilities: Option<Vec<fdecl::Capability>>,
631    ) -> Result<Vec<cm_rust::CapabilityDecl>, Error> {
632        let capabilities = capabilities.unwrap_or(vec![]);
633        if let Some(c) = capabilities.iter().find(|c| {
634            !matches!(c, fdecl::Capability::Protocol(_) | fdecl::Capability::Directory(_))
635        }) {
636            return Err(format_err!("Type unsupported for namespace capability: {:?}", c));
637        }
638        cm_fidl_validator::validate_namespace_capabilities(&capabilities)?;
639        Ok(capabilities.into_iter().map(FidlIntoNative::fidl_into_native).collect())
640    }
641
642    fn translate_builtin_capabilities(
643        capabilities: Option<Vec<fdecl::Capability>>,
644    ) -> Result<Vec<cm_rust::CapabilityDecl>, Error> {
645        let capabilities = capabilities.unwrap_or(vec![]);
646        cm_fidl_validator::validate_builtin_capabilities(&capabilities)?;
647        Ok(capabilities.into_iter().map(FidlIntoNative::fidl_into_native).collect())
648    }
649}
650
651#[derive(Debug, Clone, Error, PartialEq, Eq)]
652pub enum AllowlistEntryParseError {
653    #[error("Invalid child moniker ({0:?}) in allowlist entry: {1:?}")]
654    InvalidChildName(String, #[source] MonikerError),
655    #[error("Invalid collection name ({0:?}) in allowlist entry: {1:?}")]
656    InvalidCollectionName(String, #[source] ParseError),
657    #[error("Allowlist entry ({0:?}) must start with a '/'")]
658    NoLeadingSlash(String),
659    #[error("Allowlist entry ({0:?}) must have '**' wildcard only at the end")]
660    DescendantWildcardOnlyAtEnd(String),
661}
662
663fn parse_allowlist_entries(strs: &Option<Vec<String>>) -> Result<Vec<AllowlistEntry>, Error> {
664    let strs = match strs {
665        Some(strs) => strs,
666        None => return Ok(Vec::new()),
667    };
668
669    let mut entries = vec![];
670    for input in strs {
671        entries.push(input.parse()?);
672    }
673    Ok(entries)
674}
675
676fn parse_optional_vec<T, S: TryInto<T>>(src: Option<Vec<S>>) -> Result<Vec<T>, Error>
677where
678    Result<Vec<T>, Error>: FromIterator<Result<T, <S as TryInto<T>>::Error>>,
679{
680    src.unwrap_or_default().into_iter().map(TryInto::try_into).collect()
681}
682
683fn as_usize_or_default(value: Option<u32>, default: usize) -> usize {
684    match value {
685        Some(value) => value as usize,
686        None => default,
687    }
688}
689
690#[derive(Debug, Clone, Error, PartialEq, Eq)]
691pub enum PolicyConfigError {
692    #[error("Capability source name was empty in a capability policy entry.")]
693    EmptyCapabilitySourceName,
694    #[error("Capability type was empty in a capability policy entry.")]
695    EmptyAllowlistedCapability,
696    #[error("Debug registration type was empty in a debug policy entry.")]
697    EmptyAllowlistedDebugRegistration,
698    #[error("Target moniker was empty in a debug policy entry.")]
699    EmptyTargetMonikerDebugRegistration,
700    #[error("Environment name was empty or invalid in a debug policy entry.")]
701    InvalidEnvironmentNameDebugRegistration,
702    #[error("Capability from type was empty in a capability policy entry.")]
703    EmptyFromType,
704    #[error("Capability source_moniker was empty in a capability policy entry.")]
705    EmptySourceMoniker,
706    #[error("Invalid source capability.")]
707    InvalidSourceCapability,
708    #[error("Unsupported allowlist capability type")]
709    UnsupportedAllowlistedCapability,
710}
711
712impl TryFrom<component_internal::Config> for RuntimeConfig {
713    type Error = Error;
714
715    fn try_from(config: component_internal::Config) -> Result<Self, Error> {
716        let default = RuntimeConfig::default();
717
718        let list_children_batch_size =
719            as_usize_or_default(config.list_children_batch_size, default.list_children_batch_size);
720        let num_threads = config.num_threads.unwrap_or(default.num_threads);
721
722        let root_component_url = config.root_component_url.map(Url::new).transpose()?;
723
724        let security_policy = config
725            .security_policy
726            .map(SecurityPolicy::try_from)
727            .transpose()
728            .context("Unable to parse security policy")?
729            .unwrap_or_default();
730
731        let abi_revision_policy = config
732            .abi_revision_policy
733            .map(AbiRevisionPolicy::try_from)
734            .transpose()
735            .context("Unable to parse ABI revision policy")?
736            .unwrap_or_default();
737
738        let vmex_source = config.vmex_source.map(VmexSource::from).unwrap_or_default();
739
740        let trace_provider = config.trace_provider.map(TraceProvider::from).unwrap_or_default();
741
742        let health_check = config
743            .health_check
744            .map(HealthCheck::try_from)
745            .transpose()
746            .context("Unable to parse health checks policy")?
747            .unwrap_or_default();
748
749        Ok(RuntimeConfig {
750            list_children_batch_size,
751            security_policy: Arc::new(security_policy),
752            namespace_capabilities: Self::translate_namespace_capabilities(
753                config.namespace_capabilities,
754            )?,
755            builtin_capabilities: Self::translate_builtin_capabilities(
756                config.builtin_capabilities,
757            )?,
758            debug: config.debug.unwrap_or(default.debug),
759            trace_provider,
760            enable_introspection: config
761                .enable_introspection
762                .unwrap_or(default.enable_introspection),
763            use_builtin_process_launcher: config
764                .use_builtin_process_launcher
765                .unwrap_or(default.use_builtin_process_launcher),
766            maintain_utc_clock: config.maintain_utc_clock.unwrap_or(default.maintain_utc_clock),
767            num_threads,
768            root_component_url,
769            component_id_index_path: config.component_id_index_path.map(Into::into),
770            log_destination: config.log_destination.unwrap_or(default.log_destination),
771            log_all_events: config.log_all_events.unwrap_or(default.log_all_events),
772            builtin_boot_resolver: config
773                .builtin_boot_resolver
774                .unwrap_or(default.builtin_boot_resolver),
775            realm_builder_resolver_and_runner: config
776                .realm_builder_resolver_and_runner
777                .unwrap_or(default.realm_builder_resolver_and_runner),
778            abi_revision_policy,
779            vmex_source,
780            health_check,
781            inject_capabilities: parse_optional_vec(config.inject_capabilities)
782                .context("Unable to parse injected capabilities")?,
783            scudo_options: config.scudo_options,
784        })
785    }
786}
787
788fn parse_capability_policy(
789    capability_policy: Option<CapabilityPolicyAllowlists>,
790) -> Result<HashMap<CapabilityAllowlistKey, HashSet<AllowlistEntry>>, Error> {
791    let capability_policy = if let Some(capability_policy) = capability_policy {
792        if let Some(allowlist) = capability_policy.allowlist {
793            let mut policies = HashMap::new();
794            for e in allowlist.into_iter() {
795                let source_moniker = ExtendedMoniker::parse_str(
796                    e.source_moniker
797                        .as_deref()
798                        .ok_or_else(|| Error::new(PolicyConfigError::EmptySourceMoniker))?,
799                )?;
800                let source_name = if let Some(source_name) = e.source_name {
801                    Ok(source_name
802                        .parse()
803                        .map_err(|_| Error::new(PolicyConfigError::InvalidSourceCapability))?)
804                } else {
805                    Err(PolicyConfigError::EmptyCapabilitySourceName)
806                }?;
807                let source = match e.source {
808                    Some(fdecl::Ref::Self_(_)) => Ok(CapabilityAllowlistSource::Self_),
809                    Some(fdecl::Ref::Framework(_)) => Ok(CapabilityAllowlistSource::Framework),
810                    Some(fdecl::Ref::Capability(_)) => Ok(CapabilityAllowlistSource::Capability),
811                    Some(fdecl::Ref::Environment(_)) => Ok(CapabilityAllowlistSource::Environment),
812                    _ => Err(Error::new(PolicyConfigError::InvalidSourceCapability)),
813                }?;
814
815                let capability = if let Some(capability) = e.capability.as_ref() {
816                    match &capability {
817                        component_internal::AllowlistedCapability::Directory(_) => {
818                            Ok(CapabilityTypeName::Directory)
819                        }
820                        component_internal::AllowlistedCapability::Protocol(_) => {
821                            Ok(CapabilityTypeName::Protocol)
822                        }
823                        component_internal::AllowlistedCapability::Service(_) => {
824                            Ok(CapabilityTypeName::Service)
825                        }
826                        component_internal::AllowlistedCapability::Storage(_) => {
827                            Ok(CapabilityTypeName::Storage)
828                        }
829                        component_internal::AllowlistedCapability::Runner(_) => {
830                            Ok(CapabilityTypeName::Runner)
831                        }
832                        component_internal::AllowlistedCapability::Resolver(_) => {
833                            Ok(CapabilityTypeName::Resolver)
834                        }
835                        _ => Err(Error::new(PolicyConfigError::EmptyAllowlistedCapability)),
836                    }
837                } else {
838                    Err(Error::new(PolicyConfigError::EmptyAllowlistedCapability))
839                }?;
840
841                let target_monikers =
842                    HashSet::from_iter(parse_allowlist_entries(&e.target_monikers)?);
843
844                policies.insert(
845                    CapabilityAllowlistKey { source_moniker, source_name, source, capability },
846                    target_monikers,
847                );
848            }
849            policies
850        } else {
851            HashMap::new()
852        }
853    } else {
854        HashMap::new()
855    };
856    Ok(capability_policy)
857}
858
859fn parse_debug_capability_policy(
860    debug_registration_policy: Option<DebugRegistrationPolicyAllowlists>,
861) -> Result<HashMap<DebugCapabilityKey, HashSet<DebugCapabilityAllowlistEntry>>, Error> {
862    let debug_capability_policy = if let Some(debug_capability_policy) = debug_registration_policy {
863        if let Some(allowlist) = debug_capability_policy.allowlist {
864            let mut policies: HashMap<DebugCapabilityKey, HashSet<DebugCapabilityAllowlistEntry>> =
865                HashMap::new();
866            for e in allowlist.into_iter() {
867                let moniker = e
868                    .moniker
869                    .as_deref()
870                    .ok_or_else(|| Error::new(PolicyConfigError::EmptySourceMoniker))?
871                    .parse()?;
872                let name = if let Some(name) = e.name.as_ref() {
873                    Ok(name
874                        .parse()
875                        .map_err(|_| Error::new(PolicyConfigError::InvalidSourceCapability))?)
876                } else {
877                    Err(PolicyConfigError::EmptyCapabilitySourceName)
878                }?;
879
880                let capability = if let Some(capability) = e.debug.as_ref() {
881                    match &capability {
882                        component_internal::AllowlistedDebugRegistration::Protocol(_) => {
883                            Ok(CapabilityTypeName::Protocol)
884                        }
885                        _ => Err(Error::new(PolicyConfigError::EmptyAllowlistedDebugRegistration)),
886                    }
887                } else {
888                    Err(Error::new(PolicyConfigError::EmptyAllowlistedDebugRegistration))
889                }?;
890
891                let env_name = e
892                    .environment_name
893                    .map(|n| n.parse().ok())
894                    .flatten()
895                    .ok_or(PolicyConfigError::InvalidEnvironmentNameDebugRegistration)?;
896
897                let key = DebugCapabilityKey {
898                    name,
899                    source: CapabilityAllowlistSource::Self_,
900                    capability,
901                    env_name,
902                };
903                let value = DebugCapabilityAllowlistEntry::new(moniker);
904                if let Some(h) = policies.get_mut(&key) {
905                    h.insert(value);
906                } else {
907                    policies.insert(key, vec![value].into_iter().collect());
908                }
909            }
910            policies
911        } else {
912            HashMap::new()
913        }
914    } else {
915        HashMap::new()
916    };
917    Ok(debug_capability_policy)
918}
919
920impl TryFrom<component_internal::SecurityPolicy> for SecurityPolicy {
921    type Error = Error;
922
923    fn try_from(security_policy: component_internal::SecurityPolicy) -> Result<Self, Error> {
924        let job_policy = if let Some(job_policy) = &security_policy.job_policy {
925            let ambient_mark_vmo_exec = parse_allowlist_entries(&job_policy.ambient_mark_vmo_exec)?;
926            let main_process_critical = parse_allowlist_entries(&job_policy.main_process_critical)?;
927            let create_raw_processes = parse_allowlist_entries(&job_policy.create_raw_processes)?;
928            JobPolicyAllowlists {
929                ambient_mark_vmo_exec,
930                main_process_critical,
931                create_raw_processes,
932            }
933        } else {
934            JobPolicyAllowlists::default()
935        };
936
937        let capability_policy = parse_capability_policy(security_policy.capability_policy)?;
938
939        let debug_capability_policy =
940            parse_debug_capability_policy(security_policy.debug_registration_policy)?;
941
942        let child_policy = if let Some(child_policy) = &security_policy.child_policy {
943            let reboot_on_terminate = parse_allowlist_entries(&child_policy.reboot_on_terminate)?;
944            ChildPolicyAllowlists { reboot_on_terminate }
945        } else {
946            ChildPolicyAllowlists::default()
947        };
948
949        Ok(SecurityPolicy { job_policy, capability_policy, debug_capability_policy, child_policy })
950    }
951}
952
953#[derive(Debug, PartialEq, Eq, Hash, Clone)]
954#[cfg_attr(
955    feature = "serde",
956    derive(Deserialize, Serialize),
957    serde(deny_unknown_fields, rename_all = "snake_case", tag = "type")
958)]
959pub enum InjectedUse {
960    Protocol(InjectedUseProtocol),
961}
962
963impl TryFrom<component_internal::InjectedUse> for InjectedUse {
964    type Error = Error;
965
966    fn try_from(value: component_internal::InjectedUse) -> Result<Self, Error> {
967        match value {
968            component_internal::InjectedUse::Protocol(protocol) => {
969                Ok(InjectedUse::Protocol(protocol.try_into()?))
970            }
971            other => Err(format_err!("Invalid InjectedUse value: {other:?}")),
972        }
973    }
974}
975
976impl Into<component_internal::InjectedUse> for InjectedUse {
977    fn into(self) -> component_internal::InjectedUse {
978        match self {
979            InjectedUse::Protocol(protocol) => {
980                component_internal::InjectedUse::Protocol(protocol.into())
981            }
982        }
983    }
984}
985
986#[derive(Debug, PartialEq, Eq, Hash, Clone)]
987#[cfg_attr(feature = "serde", derive(Deserialize, Serialize), serde(deny_unknown_fields))]
988pub struct InjectedUseProtocol {
989    pub source_name: cm_types::Name,
990    pub target_path: cm_types::Path,
991}
992
993impl TryFrom<component_internal::InjectedUseProtocol> for InjectedUseProtocol {
994    type Error = Error;
995
996    fn try_from(value: component_internal::InjectedUseProtocol) -> Result<Self, Error> {
997        Ok(InjectedUseProtocol {
998            source_name: value.source_name.context("Missing source_name")?.parse()?,
999            target_path: value.target_path.context("Missing target_path")?.parse()?,
1000        })
1001    }
1002}
1003
1004impl Into<component_internal::InjectedUseProtocol> for InjectedUseProtocol {
1005    fn into(self) -> component_internal::InjectedUseProtocol {
1006        component_internal::InjectedUseProtocol {
1007            source_name: Some(self.source_name.to_string()),
1008            target_path: Some(self.target_path.to_string()),
1009            ..component_internal::InjectedUseProtocol::default()
1010        }
1011    }
1012}
1013
1014#[derive(Debug, PartialEq, Eq, Hash, Clone)]
1015#[cfg_attr(feature = "serde", derive(Deserialize, Serialize), serde(deny_unknown_fields))]
1016pub struct InjectedCapabilities {
1017    /// Components that will have these capabilities injected into.
1018    pub components: Vec<AllowlistEntry>,
1019
1020    /// Capabilities to be injected.
1021    #[cfg_attr(feature = "serde", serde(rename = "use"))]
1022    pub use_: Vec<InjectedUse>,
1023}
1024
1025impl InjectedCapabilities {
1026    pub fn new(components: Vec<AllowlistEntry>, use_: Vec<InjectedUse>) -> Self {
1027        Self { components, use_ }
1028    }
1029}
1030
1031impl TryFrom<component_internal::InjectedCapabilities> for InjectedCapabilities {
1032    type Error = Error;
1033
1034    fn try_from(value: component_internal::InjectedCapabilities) -> Result<Self, Error> {
1035        Ok(Self::new(parse_allowlist_entries(&value.components)?, parse_optional_vec(value.use_)?))
1036    }
1037}
1038
1039impl Into<component_internal::InjectedCapabilities> for InjectedCapabilities {
1040    fn into(self) -> component_internal::InjectedCapabilities {
1041        component_internal::InjectedCapabilities {
1042            components: Some(self.components.into_iter().map(|entry| entry.to_string()).collect()),
1043            use_: Some(self.use_.into_iter().map(Into::into).collect()),
1044            ..component_internal::InjectedCapabilities::default()
1045        }
1046    }
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051    use super::*;
1052    use assert_matches::assert_matches;
1053    use fidl_fuchsia_io as fio;
1054    use version_history::{ApiLevel, Version, VersionVec};
1055
1056    const FOO_PKG_URL: &str = "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm";
1057
1058    macro_rules! test_function_ok {
1059        ( $function:path, $($test_name:ident => ($input:expr, $expected:expr)),+ ) => {
1060            $(
1061                #[test]
1062                fn $test_name() {
1063                    assert_matches!($function($input), Ok(v) if v == $expected);
1064                }
1065            )+
1066        };
1067    }
1068
1069    macro_rules! test_function_err {
1070        ( $function:path, $($test_name:ident => ($input:expr, $type:ty, $expected:expr)),+ ) => {
1071            $(
1072                #[test]
1073                fn $test_name() {
1074                    assert_eq!(*$function($input).unwrap_err().downcast_ref::<$type>().unwrap(), $expected);
1075                }
1076            )+
1077        };
1078    }
1079
1080    macro_rules! test_config_ok {
1081        ( $($test_name:ident => ($input:expr, $expected:expr)),+ $(,)? ) => {
1082            test_function_ok! { RuntimeConfig::try_from, $($test_name => ($input, $expected)),+ }
1083        };
1084    }
1085
1086    macro_rules! test_config_err {
1087        ( $($test_name:ident => ($input:expr, $type:ty, $expected:expr)),+ $(,)? ) => {
1088            test_function_err! { RuntimeConfig::try_from, $($test_name => ($input, $type, $expected)),+ }
1089        };
1090    }
1091
1092    test_config_ok! {
1093        all_fields_none => (component_internal::Config {
1094            debug: None,
1095            trace_provider: None,
1096            enable_introspection: None,
1097            list_children_batch_size: None,
1098            security_policy: None,
1099            maintain_utc_clock: None,
1100            use_builtin_process_launcher: None,
1101            num_threads: None,
1102            namespace_capabilities: None,
1103            builtin_capabilities: None,
1104            root_component_url: None,
1105            component_id_index_path: None,
1106            ..Default::default()
1107        }, RuntimeConfig::default()),
1108        all_leaf_nodes_none => (component_internal::Config {
1109            debug: Some(false),
1110            trace_provider: Some(component_internal::TraceProvider::Namespace),
1111            enable_introspection: Some(false),
1112            list_children_batch_size: Some(5),
1113            maintain_utc_clock: Some(false),
1114            use_builtin_process_launcher: Some(true),
1115            security_policy: Some(component_internal::SecurityPolicy {
1116                job_policy: Some(component_internal::JobPolicyAllowlists {
1117                    main_process_critical: None,
1118                    ambient_mark_vmo_exec: None,
1119                    create_raw_processes: None,
1120                    ..Default::default()
1121                }),
1122                capability_policy: None,
1123                ..Default::default()
1124            }),
1125            num_threads: Some(10),
1126            namespace_capabilities: None,
1127            builtin_capabilities: None,
1128            root_component_url: None,
1129            component_id_index_path: None,
1130            log_destination: None,
1131            log_all_events: None,
1132            ..Default::default()
1133        }, RuntimeConfig {
1134            debug: false,
1135            trace_provider: TraceProvider::Namespace,
1136            enable_introspection: false,
1137            list_children_batch_size: 5,
1138            maintain_utc_clock: false,
1139            use_builtin_process_launcher:true,
1140            num_threads: 10,
1141            ..Default::default() }),
1142        all_fields_some => (
1143            component_internal::Config {
1144                debug: Some(true),
1145                trace_provider: Some(component_internal::TraceProvider::RootExposed),
1146                enable_introspection: Some(true),
1147                list_children_batch_size: Some(42),
1148                maintain_utc_clock: Some(true),
1149                use_builtin_process_launcher: Some(false),
1150                security_policy: Some(component_internal::SecurityPolicy {
1151                    job_policy: Some(component_internal::JobPolicyAllowlists {
1152                        main_process_critical: Some(vec!["/something/important".to_string()]),
1153                        ambient_mark_vmo_exec: Some(vec!["/".to_string(), "/foo/bar".to_string()]),
1154                        create_raw_processes: Some(vec!["/another/thing".to_string()]),
1155                        ..Default::default()
1156                    }),
1157                    capability_policy: Some(component_internal::CapabilityPolicyAllowlists {
1158                        allowlist: Some(vec![
1159                        component_internal::CapabilityAllowlistEntry {
1160                            source_moniker: Some("<component_manager>".to_string()),
1161                            source_name: Some("fuchsia.kernel.MmioResource".to_string()),
1162                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef {})),
1163                            capability: Some(component_internal::AllowlistedCapability::Protocol(component_internal::AllowlistedProtocol::default())),
1164                            target_monikers: Some(vec![
1165                                "/bootstrap".to_string(),
1166                                "/core/**".to_string(),
1167                                "/core/test_manager/tests:**".to_string()
1168                            ]),
1169                            ..Default::default()
1170                        },
1171                    ]), ..Default::default()}),
1172                    debug_registration_policy: Some(component_internal::DebugRegistrationPolicyAllowlists{
1173                        allowlist: Some(vec![
1174                            component_internal::DebugRegistrationAllowlistEntry {
1175                                name: Some("fuchsia.foo.bar".to_string()),
1176                                debug: Some(component_internal::AllowlistedDebugRegistration::Protocol(component_internal::AllowlistedProtocol::default())),
1177                                moniker: Some("/foo/bar".to_string()),
1178                                environment_name: Some("bar_env1".to_string()),
1179                                ..Default::default()
1180                            },
1181                            component_internal::DebugRegistrationAllowlistEntry {
1182                                name: Some("fuchsia.foo.bar".to_string()),
1183                                debug: Some(component_internal::AllowlistedDebugRegistration::Protocol(component_internal::AllowlistedProtocol::default())),
1184                                moniker: Some("/foo".to_string()),
1185                                environment_name: Some("foo_env1".to_string()),
1186                                ..Default::default()
1187                            },
1188                            component_internal::DebugRegistrationAllowlistEntry {
1189                                name: Some("fuchsia.foo.baz".to_string()),
1190                                debug: Some(component_internal::AllowlistedDebugRegistration::Protocol(component_internal::AllowlistedProtocol::default())),
1191                                moniker: Some("/foo/**".to_string()),
1192                                environment_name: Some("foo_env2".to_string()),
1193                                ..Default::default()
1194                            },
1195                            component_internal::DebugRegistrationAllowlistEntry {
1196                                name: Some("fuchsia.foo.baz".to_string()),
1197                                debug: Some(component_internal::AllowlistedDebugRegistration::Protocol(component_internal::AllowlistedProtocol::default())),
1198                                moniker: Some("/root".to_string()),
1199                                environment_name: Some("root_env".to_string()),
1200                                ..Default::default()
1201                            },
1202                        ]), ..Default::default()}),
1203                    child_policy: Some(component_internal::ChildPolicyAllowlists {
1204                        reboot_on_terminate: Some(vec!["/something/important".to_string()]),
1205                        ..Default::default()
1206                    }),
1207                    ..Default::default()
1208                }),
1209                num_threads: Some(24),
1210                namespace_capabilities: Some(vec![
1211                    fdecl::Capability::Protocol(fdecl::Protocol {
1212                        name: Some("foo_svc".into()),
1213                        source_path: Some("/svc/foo".into()),
1214                        ..Default::default()
1215                    }),
1216                    fdecl::Capability::Directory(fdecl::Directory {
1217                        name: Some("bar_dir".into()),
1218                        source_path: Some("/bar".into()),
1219                        rights: Some(fio::Operations::CONNECT),
1220                        ..Default::default()
1221                    }),
1222                ]),
1223                builtin_capabilities: Some(vec![
1224                    fdecl::Capability::Protocol(fdecl::Protocol {
1225                        name: Some("foo_protocol".into()),
1226                        source_path: None,
1227                        ..Default::default()
1228                    }),
1229                ]),
1230                root_component_url: Some(FOO_PKG_URL.to_string()),
1231                component_id_index_path: Some("/boot/config/component_id_index".to_string()),
1232                log_destination: Some(component_internal::LogDestination::Klog),
1233                log_all_events: Some(true),
1234                builtin_boot_resolver: Some(component_internal::BuiltinBootResolver::None),
1235                realm_builder_resolver_and_runner: Some(component_internal::RealmBuilderResolverAndRunner::None),
1236                abi_revision_policy: Some(component_internal::AbiRevisionPolicy{
1237                    allowlist: Some(vec!["/baz".to_string(), "/qux/**".to_string()]),
1238                    ..Default::default()
1239                }),
1240                vmex_source: Some(component_internal::VmexSource::Namespace),
1241                health_check: Some(component_internal::HealthCheck{ monikers: Some(vec!()), ..Default::default()}),
1242                ..Default::default()
1243            },
1244            RuntimeConfig {
1245                abi_revision_policy: AbiRevisionPolicy::new(vec![
1246                    AllowlistEntryBuilder::new().exact("baz").build(),
1247                    AllowlistEntryBuilder::new().exact("qux").any_descendant(),
1248                ]),
1249                debug: true,
1250                trace_provider: TraceProvider::RootExposed,
1251                enable_introspection: true,
1252                list_children_batch_size: 42,
1253                maintain_utc_clock: true,
1254                use_builtin_process_launcher: false,
1255                security_policy: Arc::new(SecurityPolicy {
1256                    job_policy: JobPolicyAllowlists {
1257                        ambient_mark_vmo_exec: vec![
1258                            AllowlistEntryBuilder::new().build(),
1259                            AllowlistEntryBuilder::new().exact("foo").exact("bar").build(),
1260                        ],
1261                        main_process_critical: vec![
1262                            AllowlistEntryBuilder::new().exact("something").exact("important").build(),
1263                        ],
1264                        create_raw_processes: vec![
1265                            AllowlistEntryBuilder::new().exact("another").exact("thing").build(),
1266                        ],
1267                    },
1268                    capability_policy: HashMap::from_iter(vec![
1269                        (CapabilityAllowlistKey {
1270                            source_moniker: ExtendedMoniker::ComponentManager,
1271                            source_name: "fuchsia.kernel.MmioResource".parse().unwrap(),
1272                            source: CapabilityAllowlistSource::Self_,
1273                            capability: CapabilityTypeName::Protocol,
1274                        },
1275                        HashSet::from_iter(vec![
1276                            AllowlistEntryBuilder::new().exact("bootstrap").build(),
1277                            AllowlistEntryBuilder::new().exact("core").any_descendant(),
1278                            AllowlistEntryBuilder::new().exact("core").exact("test_manager").any_descendant_in_collection("tests"),
1279                        ].iter().cloned())
1280                        ),
1281                    ].iter().cloned()),
1282                    debug_capability_policy: HashMap::from_iter(vec![
1283                        (
1284                            DebugCapabilityKey {
1285                                name: "fuchsia.foo.bar".parse().unwrap(),
1286                                source: CapabilityAllowlistSource::Self_,
1287                                capability: CapabilityTypeName::Protocol,
1288                                env_name: "bar_env1".parse().unwrap(),
1289                            },
1290                            HashSet::from_iter(vec![
1291                                DebugCapabilityAllowlistEntry::new(
1292                                    AllowlistEntryBuilder::new().exact("foo").exact("bar").build(),
1293                                )
1294                            ])
1295                        ),
1296                        (
1297                            DebugCapabilityKey {
1298                                name: "fuchsia.foo.bar".parse().unwrap(),
1299                                source: CapabilityAllowlistSource::Self_,
1300                                capability: CapabilityTypeName::Protocol,
1301                                env_name: "foo_env1".parse().unwrap(),
1302                            },
1303                            HashSet::from_iter(vec![
1304                                DebugCapabilityAllowlistEntry::new(
1305                                    AllowlistEntryBuilder::new().exact("foo").build(),
1306                                )
1307                            ])
1308                        ),
1309                        (
1310                            DebugCapabilityKey {
1311                                name: "fuchsia.foo.baz".parse().unwrap(),
1312                                source: CapabilityAllowlistSource::Self_,
1313                                capability: CapabilityTypeName::Protocol,
1314                                env_name: "foo_env2".parse().unwrap(),
1315                            },
1316                            HashSet::from_iter(vec![
1317                                DebugCapabilityAllowlistEntry::new(
1318                                    AllowlistEntryBuilder::new().exact("foo").any_descendant(),
1319                                )
1320                            ])
1321                        ),
1322                        (
1323                            DebugCapabilityKey {
1324                                name: "fuchsia.foo.baz".parse().unwrap(),
1325                                source: CapabilityAllowlistSource::Self_,
1326                                capability: CapabilityTypeName::Protocol,
1327                                env_name: "root_env".parse().unwrap(),
1328                            },
1329                            HashSet::from_iter(vec![
1330                                DebugCapabilityAllowlistEntry::new(
1331                                    AllowlistEntryBuilder::new().exact("root").build(),
1332                                )
1333                            ])
1334                        ),
1335                    ]),
1336                    child_policy: ChildPolicyAllowlists {
1337                        reboot_on_terminate: vec![
1338                            AllowlistEntryBuilder::new().exact("something").exact("important").build(),
1339                        ],
1340                    },
1341                }),
1342                num_threads: 24,
1343                namespace_capabilities: vec![
1344                    cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl {
1345                        name: "foo_svc".parse().unwrap(),
1346                        source_path: Some("/svc/foo".parse().unwrap()),
1347                        delivery: Default::default(),
1348                    }),
1349                    cm_rust::CapabilityDecl::Directory(cm_rust::DirectoryDecl {
1350                        name: "bar_dir".parse().unwrap(),
1351                        source_path: Some("/bar".parse().unwrap()),
1352                        rights: fio::Operations::CONNECT,
1353                    }),
1354                ],
1355                builtin_capabilities: vec![
1356                    cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl {
1357                        name: "foo_protocol".parse().unwrap(),
1358                        source_path: None,
1359                        delivery: Default::default(),
1360                    }),
1361                ],
1362                root_component_url: Some(Url::new(FOO_PKG_URL.to_string()).unwrap()),
1363                component_id_index_path: Some("/boot/config/component_id_index".into()),
1364                log_destination: LogDestination::Klog,
1365                log_all_events: true,
1366                builtin_boot_resolver: BuiltinBootResolver::None,
1367                realm_builder_resolver_and_runner: RealmBuilderResolverAndRunner::None,
1368                vmex_source: VmexSource::Namespace,
1369                health_check: HealthCheck{monikers: vec!()},
1370                inject_capabilities: vec![],
1371                scudo_options: None,
1372            }
1373        ),
1374    }
1375
1376    test_config_err! {
1377        invalid_job_policy => (
1378            component_internal::Config {
1379                debug: None,
1380                trace_provider: None,
1381                enable_introspection: None,
1382                list_children_batch_size: None,
1383                maintain_utc_clock: None,
1384                use_builtin_process_launcher: None,
1385                security_policy: Some(component_internal::SecurityPolicy {
1386                    job_policy: Some(component_internal::JobPolicyAllowlists {
1387                        main_process_critical: None,
1388                        ambient_mark_vmo_exec: Some(vec!["/".to_string(), "bad".to_string()]),
1389                        create_raw_processes: None,
1390                        ..Default::default()
1391                    }),
1392                    capability_policy: None,
1393                    ..Default::default()
1394                }),
1395                num_threads: None,
1396                namespace_capabilities: None,
1397                builtin_capabilities: None,
1398                root_component_url: None,
1399                component_id_index_path: None,
1400                ..Default::default()
1401            },
1402            AllowlistEntryParseError,
1403            AllowlistEntryParseError::NoLeadingSlash(
1404                "bad".into(),
1405            )
1406        ),
1407        invalid_capability_policy_empty_allowlist_cap => (
1408            component_internal::Config {
1409                debug: None,
1410                trace_provider: None,
1411                enable_introspection: None,
1412                list_children_batch_size: None,
1413                maintain_utc_clock: None,
1414                use_builtin_process_launcher: None,
1415                security_policy: Some(component_internal::SecurityPolicy {
1416                    job_policy: None,
1417                    capability_policy: Some(component_internal::CapabilityPolicyAllowlists {
1418                        allowlist: Some(vec![
1419                        component_internal::CapabilityAllowlistEntry {
1420                            source_moniker: Some("<component_manager>".to_string()),
1421                            source_name: Some("fuchsia.kernel.MmioResource".to_string()),
1422                            source: Some(fdecl::Ref::Self_(fdecl::SelfRef{})),
1423                            capability: None,
1424                            target_monikers: Some(vec!["/core".to_string()]),
1425                            ..Default::default()
1426                        }]),
1427                        ..Default::default()
1428                    }),
1429                    ..Default::default()
1430                }),
1431                num_threads: None,
1432                namespace_capabilities: None,
1433                builtin_capabilities: None,
1434                root_component_url: None,
1435                component_id_index_path: None,
1436                ..Default::default()
1437            },
1438            PolicyConfigError,
1439            PolicyConfigError::EmptyAllowlistedCapability
1440        ),
1441        invalid_capability_policy_empty_source_moniker => (
1442            component_internal::Config {
1443                debug: None,
1444                trace_provider: None,
1445                enable_introspection: None,
1446                list_children_batch_size: None,
1447                maintain_utc_clock: None,
1448                use_builtin_process_launcher: None,
1449                security_policy: Some(component_internal::SecurityPolicy {
1450                    job_policy: None,
1451                    capability_policy: Some(component_internal::CapabilityPolicyAllowlists {
1452                        allowlist: Some(vec![
1453                        component_internal::CapabilityAllowlistEntry {
1454                            source_moniker: None,
1455                            source_name: Some("fuchsia.kernel.MmioResource".to_string()),
1456                            capability: Some(component_internal::AllowlistedCapability::Protocol(component_internal::AllowlistedProtocol::default())),
1457                            target_monikers: Some(vec!["/core".to_string()]),
1458                            ..Default::default()
1459                        }]),
1460                        ..Default::default()
1461                    }),
1462                    ..Default::default()
1463                }),
1464                num_threads: None,
1465                namespace_capabilities: None,
1466                builtin_capabilities: None,
1467                root_component_url: None,
1468                component_id_index_path: None,
1469                ..Default::default()
1470            },
1471            PolicyConfigError,
1472            PolicyConfigError::EmptySourceMoniker
1473        ),
1474        invalid_root_component_url => (
1475            component_internal::Config {
1476                debug: None,
1477                trace_provider: None,
1478                enable_introspection: None,
1479                list_children_batch_size: None,
1480                maintain_utc_clock: None,
1481                use_builtin_process_launcher: None,
1482                security_policy: None,
1483                num_threads: None,
1484                namespace_capabilities: None,
1485                builtin_capabilities: None,
1486                root_component_url: Some("invalid url".to_string()),
1487                component_id_index_path: None,
1488                ..Default::default()
1489            },
1490            ParseError,
1491            ParseError::InvalidComponentUrl {
1492                details: String::from("Relative URL has no resource fragment.")
1493            }
1494        ),
1495    }
1496
1497    #[test]
1498    fn new_from_bytes_valid() -> Result<(), Error> {
1499        let config = component_internal::Config {
1500            debug: None,
1501            trace_provider: None,
1502            enable_introspection: None,
1503            list_children_batch_size: Some(42),
1504            security_policy: None,
1505            namespace_capabilities: None,
1506            builtin_capabilities: None,
1507            maintain_utc_clock: None,
1508            use_builtin_process_launcher: None,
1509            num_threads: None,
1510            root_component_url: Some(FOO_PKG_URL.to_string()),
1511            ..Default::default()
1512        };
1513        let bytes = fidl::persist(&config)?;
1514        let expected = RuntimeConfig {
1515            list_children_batch_size: 42,
1516            root_component_url: Some(Url::new(FOO_PKG_URL.to_string())?),
1517            ..Default::default()
1518        };
1519
1520        assert_matches!(
1521            RuntimeConfig::new_from_bytes(&bytes)
1522            , Ok(v) if v == expected);
1523        Ok(())
1524    }
1525
1526    #[test]
1527    fn new_from_bytes_invalid() -> Result<(), Error> {
1528        let bytes = vec![0xfa, 0xde];
1529        assert_matches!(RuntimeConfig::new_from_bytes(&bytes), Err(_));
1530        Ok(())
1531    }
1532
1533    #[test]
1534    fn abi_revision_policy_check_compatibility_empty_allowlist() -> Result<(), Error> {
1535        const UNKNOWN_ABI: AbiRevision = AbiRevision::from_u64(0x404);
1536        const RETIRED_ABI: AbiRevision = AbiRevision::from_u64(0x15);
1537        const SUPPORTED_ABI: AbiRevision = AbiRevision::from_u64(0x16);
1538
1539        const VERSIONS: &[Version] = &[
1540            Version {
1541                api_level: ApiLevel::from_u32(5),
1542                abi_revision: RETIRED_ABI,
1543                status: version_history::Status::Unsupported,
1544            },
1545            Version {
1546                api_level: ApiLevel::from_u32(6),
1547                abi_revision: SUPPORTED_ABI,
1548                status: version_history::Status::Supported,
1549            },
1550        ];
1551        let version_history = VersionHistory::new(&VERSIONS);
1552
1553        let policy = AbiRevisionPolicy::new(vec![]);
1554
1555        assert_eq!(
1556            policy.check_compatibility(&version_history, &Moniker::parse_str("/foo")?, None),
1557            Err(CompatibilityCheckError::AbiRevisionAbsent)
1558        );
1559        assert_eq!(
1560            policy.check_compatibility(
1561                &version_history,
1562                &Moniker::parse_str("/foo")?,
1563                Some(UNKNOWN_ABI)
1564            ),
1565            Err(CompatibilityCheckError::AbiRevisionInvalid(AbiRevisionError::TooNew {
1566                abi_revision: UNKNOWN_ABI,
1567                supported_versions: VersionVec(vec![VERSIONS[1].clone()])
1568            }))
1569        );
1570        assert_eq!(
1571            policy.check_compatibility(
1572                &version_history,
1573                &Moniker::parse_str("/foo")?,
1574                Some(RETIRED_ABI)
1575            ),
1576            Err(CompatibilityCheckError::AbiRevisionInvalid(AbiRevisionError::Retired {
1577                version: VERSIONS[0].clone(),
1578                supported_versions: VersionVec(vec![VERSIONS[1].clone()]),
1579            })),
1580        );
1581        assert_eq!(
1582            policy.check_compatibility(
1583                &version_history,
1584                &Moniker::parse_str("/foo")?,
1585                Some(SUPPORTED_ABI)
1586            ),
1587            Ok(())
1588        );
1589
1590        Ok(())
1591    }
1592
1593    #[test]
1594    fn abi_revision_policy_check_compatibility_allowlist() -> Result<(), Error> {
1595        const UNKNOWN_ABI: AbiRevision = AbiRevision::from_u64(0x404);
1596        const RETIRED_ABI: AbiRevision = AbiRevision::from_u64(0x15);
1597        const SUPPORTED_ABI: AbiRevision = AbiRevision::from_u64(0x16);
1598
1599        const VERSIONS: &[Version] = &[
1600            Version {
1601                api_level: ApiLevel::from_u32(5),
1602                abi_revision: RETIRED_ABI,
1603                status: version_history::Status::Unsupported,
1604            },
1605            Version {
1606                api_level: ApiLevel::from_u32(6),
1607                abi_revision: SUPPORTED_ABI,
1608                status: version_history::Status::Supported,
1609            },
1610        ];
1611        let version_history = VersionHistory::new(&VERSIONS);
1612
1613        let policy = AbiRevisionPolicy::new(vec![
1614            AllowlistEntryBuilder::new().exact("foo").any_child().build(),
1615        ]);
1616
1617        // "/bar" isn't on the allowlist, so bad usage should fail.
1618        assert_eq!(
1619            policy.check_compatibility(&version_history, &Moniker::parse_str("/bar")?, None),
1620            Err(CompatibilityCheckError::AbiRevisionAbsent)
1621        );
1622        assert_eq!(
1623            policy.check_compatibility(
1624                &version_history,
1625                &Moniker::parse_str("/bar")?,
1626                Some(UNKNOWN_ABI)
1627            ),
1628            Err(CompatibilityCheckError::AbiRevisionInvalid(AbiRevisionError::TooNew {
1629                abi_revision: UNKNOWN_ABI,
1630                supported_versions: VersionVec(vec![VERSIONS[1].clone()])
1631            }))
1632        );
1633        assert_eq!(
1634            policy.check_compatibility(
1635                &version_history,
1636                &Moniker::parse_str("/bar")?,
1637                Some(RETIRED_ABI)
1638            ),
1639            Err(CompatibilityCheckError::AbiRevisionInvalid(AbiRevisionError::Retired {
1640                version: VERSIONS[0].clone(),
1641                supported_versions: VersionVec(vec![VERSIONS[1].clone()]),
1642            })),
1643        );
1644        assert_eq!(
1645            policy.check_compatibility(
1646                &version_history,
1647                &Moniker::parse_str("/bar")?,
1648                Some(SUPPORTED_ABI)
1649            ),
1650            Ok(())
1651        );
1652
1653        // "/foo/baz" is on the allowlist. Allow whatever.
1654        assert_eq!(
1655            policy.check_compatibility(&version_history, &Moniker::parse_str("/foo/baz")?, None),
1656            Ok(())
1657        );
1658        assert_eq!(
1659            policy.check_compatibility(
1660                &version_history,
1661                &Moniker::parse_str("/foo/baz")?,
1662                Some(UNKNOWN_ABI)
1663            ),
1664            Ok(())
1665        );
1666        assert_eq!(
1667            policy.check_compatibility(
1668                &version_history,
1669                &Moniker::parse_str("/foo/baz")?,
1670                Some(RETIRED_ABI)
1671            ),
1672            Ok(())
1673        );
1674        assert_eq!(
1675            policy.check_compatibility(
1676                &version_history,
1677                &Moniker::parse_str("/foo/baz")?,
1678                Some(SUPPORTED_ABI)
1679            ),
1680            Ok(())
1681        );
1682
1683        Ok(())
1684    }
1685
1686    test_function_ok! { parse_allowlist_entries, missing_entries => (&None, vec![]) }
1687
1688    macro_rules! test_entries_ok {
1689        ( $($test_name:ident => ($input:expr, $expected:expr)),+ $(,)? ) => {
1690            $(
1691                #[test]
1692                fn $test_name() {
1693                    let input = $input;
1694                    let parsed = parse_allowlist_entries(&Some(input.clone())).unwrap();
1695                    assert_eq!(parsed, $expected);
1696                    let round_trip: Vec<_> = parsed.iter().map(AllowlistEntry::to_string).collect();
1697                    assert_eq!(round_trip, input);
1698                }
1699            )+
1700        };
1701    }
1702
1703    macro_rules! test_entries_err {
1704        ( $($test_name:ident => ($input:expr, $type:ty, $expected:expr)),+ $(,)? ) => {
1705            test_function_err! { parse_allowlist_entries, $($test_name => (&Some($input), $type, $expected)),+ }
1706        };
1707    }
1708
1709    test_entries_ok! {
1710        empty_entries => (vec![], vec![]),
1711        all_entry_types => (vec![
1712            "/core".into(),
1713            "/**".into(),
1714            "/foo/**".into(),
1715            "/coll:**".into(),
1716            "/core/test_manager/tests:**".into(),
1717            "/core/ffx-laboratory:*/echo_client".into(),
1718            "/core/*/ffx-laboratory:*/**".into(),
1719            "/core/*/bar".into(),
1720        ], vec![
1721            AllowlistEntryBuilder::new().exact("core").build(),
1722            AllowlistEntryBuilder::new().any_descendant(),
1723            AllowlistEntryBuilder::new().exact("foo").any_descendant(),
1724            AllowlistEntryBuilder::new().any_descendant_in_collection("coll"),
1725            AllowlistEntryBuilder::new().exact("core").exact("test_manager").any_descendant_in_collection("tests"),
1726            AllowlistEntryBuilder::new().exact("core").any_child_in_collection("ffx-laboratory").exact("echo_client").build(),
1727            AllowlistEntryBuilder::new().exact("core").any_child().any_child_in_collection("ffx-laboratory").any_descendant(),
1728            AllowlistEntryBuilder::new().exact("core").any_child().exact("bar").build(),
1729        ])
1730    }
1731
1732    test_entries_err! {
1733        invalid_realm_entry => (
1734            vec!["/foo/**".into(), "bar/**".into()],
1735            AllowlistEntryParseError,
1736            AllowlistEntryParseError::NoLeadingSlash("bar/**".into())),
1737        invalid_realm_in_collection_entry => (
1738            vec!["/foo/coll:**".into(), "bar/coll:**".into()],
1739            AllowlistEntryParseError,
1740            AllowlistEntryParseError::NoLeadingSlash("bar/coll:**".into())),
1741        missing_realm_in_collection_entry => (
1742            vec!["coll:**".into()],
1743            AllowlistEntryParseError,
1744            AllowlistEntryParseError::NoLeadingSlash("coll:**".into())),
1745        missing_collection_name => (
1746            vec!["/foo/coll:**".into(), "/:**".into()],
1747            AllowlistEntryParseError,
1748            AllowlistEntryParseError::InvalidCollectionName(
1749                "".into(),
1750                ParseError::Empty
1751            )),
1752        invalid_collection_name => (
1753            vec!["/foo/coll:**".into(), "/*:**".into()],
1754            AllowlistEntryParseError,
1755            AllowlistEntryParseError::InvalidCollectionName(
1756                "*".into(),
1757                ParseError::InvalidValue
1758            )),
1759        invalid_exact_entry => (
1760            vec!["/foo/bar*".into()],
1761            AllowlistEntryParseError,
1762            AllowlistEntryParseError::InvalidChildName(
1763                "bar*".into(),
1764                MonikerError::InvalidMonikerPart { 0: ParseError::InvalidValue }
1765            )),
1766        descendant_wildcard_in_between => (
1767            vec!["/foo/**/bar".into()],
1768            AllowlistEntryParseError,
1769            AllowlistEntryParseError::DescendantWildcardOnlyAtEnd(
1770                "/foo/**/bar".into(),
1771            )),
1772    }
1773
1774    #[test]
1775    fn allowlist_entry_matches() {
1776        let root = Moniker::root();
1777        let allowed = Moniker::try_from(["foo", "bar"]).unwrap();
1778        let disallowed_child_of_allowed = Moniker::try_from(["foo", "bar", "baz"]).unwrap();
1779        let disallowed = Moniker::try_from(["baz", "fiz"]).unwrap();
1780        let allowlist_exact = AllowlistEntryBuilder::new().exact_from_moniker(&allowed).build();
1781        assert!(allowlist_exact.matches(&allowed));
1782        assert!(!allowlist_exact.matches(&root));
1783        assert!(!allowlist_exact.matches(&disallowed));
1784        assert!(!allowlist_exact.matches(&disallowed_child_of_allowed));
1785
1786        let allowed_realm_root = Moniker::try_from(["qux"]).unwrap();
1787        let allowed_child_of_realm = Moniker::try_from(["qux", "quux"]).unwrap();
1788        let allowed_nested_child_of_realm = Moniker::try_from(["qux", "quux", "foo"]).unwrap();
1789        let allowlist_realm =
1790            AllowlistEntryBuilder::new().exact_from_moniker(&allowed_realm_root).any_descendant();
1791        assert!(!allowlist_realm.matches(&allowed_realm_root));
1792        assert!(allowlist_realm.matches(&allowed_child_of_realm));
1793        assert!(allowlist_realm.matches(&allowed_nested_child_of_realm));
1794        assert!(!allowlist_realm.matches(&disallowed));
1795        assert!(!allowlist_realm.matches(&root));
1796
1797        let collection_holder = Moniker::try_from(["corge"]).unwrap();
1798        let collection_child = Moniker::try_from(["corge", "collection:child"]).unwrap();
1799        let collection_nested_child =
1800            Moniker::try_from(["corge", "collection:child", "inner-child"]).unwrap();
1801        let non_collection_child = Moniker::try_from(["corge", "grault"]).unwrap();
1802        let allowlist_collection = AllowlistEntryBuilder::new()
1803            .exact_from_moniker(&collection_holder)
1804            .any_descendant_in_collection("collection");
1805        assert!(!allowlist_collection.matches(&collection_holder));
1806        assert!(allowlist_collection.matches(&collection_child));
1807        assert!(allowlist_collection.matches(&collection_nested_child));
1808        assert!(!allowlist_collection.matches(&non_collection_child));
1809        assert!(!allowlist_collection.matches(&disallowed));
1810        assert!(!allowlist_collection.matches(&root));
1811
1812        let collection_a = Moniker::try_from(["foo", "bar:a", "baz", "qux"]).unwrap();
1813        let collection_b = Moniker::try_from(["foo", "bar:b", "baz", "qux"]).unwrap();
1814        let parent_not_allowed = Moniker::try_from(["foo", "bar:b", "baz"]).unwrap();
1815        let collection_not_allowed = Moniker::try_from(["foo", "bar:b", "baz"]).unwrap();
1816        let different_collection_not_allowed =
1817            Moniker::try_from(["foo", "test:b", "baz", "qux"]).unwrap();
1818        let allowlist_exact_in_collection = AllowlistEntryBuilder::new()
1819            .exact("foo")
1820            .any_child_in_collection("bar")
1821            .exact("baz")
1822            .exact("qux")
1823            .build();
1824        assert!(allowlist_exact_in_collection.matches(&collection_a));
1825        assert!(allowlist_exact_in_collection.matches(&collection_b));
1826        assert!(!allowlist_exact_in_collection.matches(&parent_not_allowed));
1827        assert!(!allowlist_exact_in_collection.matches(&collection_not_allowed));
1828        assert!(!allowlist_exact_in_collection.matches(&different_collection_not_allowed));
1829
1830        let any_child_allowlist = AllowlistEntryBuilder::new().exact("core").any_child().build();
1831        let allowed = Moniker::try_from(["core", "abc"]).unwrap();
1832        let disallowed_1 = Moniker::try_from(["not_core", "abc"]).unwrap();
1833        let disallowed_2 = Moniker::try_from(["core", "abc", "def"]).unwrap();
1834        assert!(any_child_allowlist.matches(&allowed));
1835        assert!(!any_child_allowlist.matches(&disallowed_1));
1836        assert!(!any_child_allowlist.matches(&disallowed_2));
1837
1838        let multiwildcard_allowlist = AllowlistEntryBuilder::new()
1839            .exact("core")
1840            .any_child()
1841            .any_child_in_collection("foo")
1842            .any_descendant();
1843        let allowed = Moniker::try_from(["core", "abc", "foo:def", "ghi"]).unwrap();
1844        let disallowed_1 = Moniker::try_from(["not_core", "abc", "foo:def", "ghi"]).unwrap();
1845        let disallowed_2 = Moniker::try_from(["core", "abc", "not_foo:def", "ghi"]).unwrap();
1846        let disallowed_3 = Moniker::try_from(["core", "abc", "foo:def"]).unwrap();
1847        assert!(multiwildcard_allowlist.matches(&allowed));
1848        assert!(!multiwildcard_allowlist.matches(&disallowed_1));
1849        assert!(!multiwildcard_allowlist.matches(&disallowed_2));
1850        assert!(!multiwildcard_allowlist.matches(&disallowed_3));
1851    }
1852}