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