1use anyhow::Error;
6use diagnostics_log::Publisher;
7use fidl::endpoints::DiscoverableProtocolMarker;
8use fuchsia_component::server::ServiceFs;
9use fuchsia_component_test::LocalComponentHandles;
10use fuchsia_url::{ComponentUrl, PackageUrl};
11use futures::{StreamExt, TryStreamExt};
12use itertools::Itertools;
13use log::{Log, warn};
14use std::collections::HashSet;
15use std::sync::Arc;
16use {
17 diagnostics_log as flog, fidl_fuchsia_component_resolution as fresolution,
18 fidl_fuchsia_logger as flogger, fidl_fuchsia_pkg as fpkg, fuchsia_async as fasync,
19};
20
21#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct AllowedPackages {
24 pkgs: Arc<HashSet<String>>,
26}
27
28impl AllowedPackages {
29 pub fn zero_allowed_pkgs() -> Self {
30 Self { pkgs: HashSet::new().into() }
31 }
32
33 pub fn from_iter<I>(iter: I) -> Self
34 where
35 I: IntoIterator<Item = String>,
36 {
37 Self { pkgs: Arc::new(HashSet::from_iter(iter)) }
38 }
39}
40
41async fn validate_hermetic_package(
42 component_url_str: &str,
43 logger: OptionLogger,
44 hermetic_test_package_name: &String,
45 other_allowed_packages: &AllowedPackages,
46) -> Result<(), fresolution::ResolverError> {
47 let component_url = ComponentUrl::parse(component_url_str).map_err(|err| {
48 warn!("cannot parse {}, {:?}", component_url_str, err);
49 fresolution::ResolverError::InvalidArgs
50 })?;
51
52 match component_url.package_url() {
53 PackageUrl::Absolute(pkg_url) => {
54 let package_name = pkg_url.name();
55 if hermetic_test_package_name != package_name.as_ref()
56 && !other_allowed_packages.pkgs.contains(package_name.as_ref())
57 {
58 let s = format!("failed to resolve component {}: package {} is not in the test package allowlist: '{}, {}'
59 \nSee https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework?hl=en#hermetic-resolver
60 for more information.",
61 &component_url_str, package_name, hermetic_test_package_name, other_allowed_packages.pkgs.iter().join(", "));
62 let mut builder = log::Record::builder();
65 builder.level(log::Level::Warn);
66 logger.log(&builder.args(format_args!("{}", s)).build());
67 warn!("{}", s);
68 return Err(fresolution::ResolverError::PackageNotFound);
69 }
70 }
71 PackageUrl::Relative(_url) => {
72 }
74 }
75 Ok(())
76}
77
78async fn validate_hermetic_url(
79 pkg_url_str: &str,
80 logger: OptionLogger,
81 hermetic_test_package_name: &String,
82 other_allowed_packages: &AllowedPackages,
83) -> Result<(), fpkg::ResolveError> {
84 let pkg_url = PackageUrl::parse(pkg_url_str).map_err(|err| {
85 warn!("cannot parse {}, {:?}", pkg_url_str, err);
86 fpkg::ResolveError::InvalidUrl
87 })?;
88
89 match pkg_url {
90 PackageUrl::Absolute(pkg_url) => {
91 let package_name = pkg_url.name();
92 if hermetic_test_package_name != package_name.as_ref()
93 && !other_allowed_packages.pkgs.contains(package_name.as_ref())
94 {
95 let s = format!("failed to resolve component {}: package {} is not in the test package allowlist: '{}, {}'
96 \nSee https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework?hl=en#hermetic-resolver
97 for more information.",
98 &pkg_url_str, package_name, hermetic_test_package_name, other_allowed_packages.pkgs.iter().join(", "));
99 let mut builder = log::Record::builder();
101 builder.level(log::Level::Warn);
102 logger.log(&builder.args(format_args!("{}", s)).build());
103 warn!("{}", s);
104 return Err(fpkg::ResolveError::PackageNotFound);
105 }
106 }
107 PackageUrl::Relative(_url) => {
108 }
110 }
111 Ok(())
112}
113
114async fn serve_resolver(
115 mut stream: fresolution::ResolverRequestStream,
116 logger: OptionLogger,
117 hermetic_test_package_name: Arc<String>,
118 other_allowed_packages: AllowedPackages,
119 full_resolver: Arc<fresolution::ResolverProxy>,
120) {
121 while let Some(request) = stream.try_next().await.expect("failed to serve component resolver") {
122 match request {
123 fresolution::ResolverRequest::Resolve { component_url, responder } => {
124 let result = if let Err(err) = validate_hermetic_package(
125 &component_url,
126 logger.clone(),
127 &hermetic_test_package_name,
128 &other_allowed_packages,
129 )
130 .await
131 {
132 Err(err)
133 } else {
134 let logger = logger.clone();
135 full_resolver.resolve(&component_url).await.unwrap_or_else(|err| {
136 let mut builder = log::Record::builder();
137 builder.level(log::Level::Warn);
138 logger.log(
139 &builder
140 .args(format_args!(
141 "failed to resolve component {}: {:?}",
142 component_url, err
143 ))
144 .build(),
145 );
146 Err(fresolution::ResolverError::Internal)
147 })
148 };
149 if let Err(e) = responder.send(result) {
150 warn!("Failed sending load response for {}: {}", component_url, e);
151 }
152 }
153 fresolution::ResolverRequest::ResolveWithContext {
154 component_url,
155 context,
156 responder,
157 } => {
158 let result = if let Err(err) = validate_hermetic_package(
161 &component_url,
162 logger.clone(),
163 &hermetic_test_package_name,
164 &other_allowed_packages,
165 )
166 .await
167 {
168 Err(err)
169 } else {
170 let logger = logger.clone();
171 full_resolver
172 .resolve_with_context(&component_url, &context)
173 .await
174 .unwrap_or_else(|err| {
175 let mut builder = log::Record::builder();
176 builder.level(log::Level::Warn);
177 logger.log(
178 &builder
179 .args(format_args!(
180 "failed to resolve component {} with context {:?}: {:?}",
181 component_url, context, err
182 ))
183 .build(),
184 );
185 Err(fresolution::ResolverError::Internal)
186 })
187 };
188 if let Err(e) = responder.send(result) {
189 warn!("Failed sending load response for {}: {}", component_url, e);
190 }
191 }
192 fresolution::ResolverRequest::_UnknownMethod { ordinal, .. } => {
193 warn!(ordinal:%; "Unknown Resolver request");
194 }
195 }
196 }
197}
198
199async fn serve_pkg_resolver(
200 mut stream: fpkg::PackageResolverRequestStream,
201 logger: OptionLogger,
202 hermetic_test_package_name: Arc<String>,
203 other_allowed_packages: AllowedPackages,
204 pkg_resolver: Arc<fpkg::PackageResolverProxy>,
205) {
206 while let Some(request) = stream.try_next().await.expect("failed to serve component resolver") {
207 match request {
208 fpkg::PackageResolverRequest::Resolve { package_url, dir, responder } => {
209 let result = if let Err(err) = validate_hermetic_url(
210 &package_url,
211 logger.clone(),
212 &hermetic_test_package_name,
213 &other_allowed_packages,
214 )
215 .await
216 {
217 Err(err)
218 } else {
219 let logger = logger.clone();
220 pkg_resolver.resolve(&package_url, dir).await.unwrap_or_else(|err| {
221 let mut builder = log::Record::builder();
222 builder.level(log::Level::Warn);
223 logger.log(
224 &builder
225 .args(format_args!(
226 "failed to resolve pkg {}: {:?}",
227 package_url, err
228 ))
229 .build(),
230 );
231 Err(fpkg::ResolveError::Internal)
232 })
233 };
234 let result_ref = result.as_ref();
235 let result_ref = result_ref.map_err(|e| e.to_owned());
236 if let Err(e) = responder.send(result_ref) {
237 warn!("Failed sending load response for {}: {}", package_url, e);
238 }
239 }
240 fpkg::PackageResolverRequest::ResolveWithContext {
241 package_url,
242 context,
243 dir,
244 responder,
245 } => {
246 let result = if let Err(err) = validate_hermetic_url(
249 &package_url,
250 logger.clone(),
251 &hermetic_test_package_name,
252 &other_allowed_packages,
253 )
254 .await
255 {
256 Err(err)
257 } else {
258 let logger = logger.clone();
259 pkg_resolver
260 .resolve_with_context(&package_url, &context, dir)
261 .await
262 .unwrap_or_else(|err| {
263 let mut builder = log::Record::builder();
264 builder.level(log::Level::Warn);
265 logger.log(
266 &builder
267 .args(format_args!(
268 "failed to resolve pkg {} with context {:?}: {:?}",
269 package_url, context, err
270 ))
271 .build(),
272 );
273 Err(fpkg::ResolveError::Internal)
274 })
275 };
276 let result_ref = result.as_ref();
277 let result_ref = result_ref.map_err(|e| e.to_owned());
278 if let Err(e) = responder.send(result_ref) {
279 warn!("Failed sending load response for {}: {}", package_url, e);
280 }
281 }
282 fpkg::PackageResolverRequest::GetHash { package_url, responder } => {
283 let result = if let Err(_err) = validate_hermetic_url(
284 package_url.url.as_str(),
285 logger.clone(),
286 &hermetic_test_package_name,
287 &other_allowed_packages,
288 )
289 .await
290 {
291 Err(zx::Status::INTERNAL.into_raw())
292 } else {
293 let logger = logger.clone();
294 pkg_resolver.get_hash(&package_url).await.unwrap_or_else(|err| {
295 let mut builder = log::Record::builder();
296 builder.level(log::Level::Warn);
297 logger.log(
298 &builder
299 .args(format_args!(
300 "failed to resolve pkg {}: {:?}",
301 package_url.url.as_str(),
302 err
303 ))
304 .build(),
305 );
306 Err(zx::Status::INTERNAL.into_raw())
307 })
308 };
309 let result_ref = result.as_ref();
310 let result_ref = result_ref.map_err(|e| e.to_owned());
311 if let Err(e) = responder.send(result_ref) {
312 warn!("Failed sending load response for {}: {}", package_url.url.as_str(), e);
313 }
314 }
315 }
316 }
317}
318
319#[derive(Clone)]
320pub struct OptionLogger(Option<Publisher>);
321
322impl OptionLogger {
323 pub fn log(&self, record: &log::Record<'_>) {
324 if let Some(logger) = self.0.as_ref() {
325 logger.log(record);
326 }
327 }
328}
329
330pub async fn serve_hermetic_resolver(
331 handles: LocalComponentHandles,
332 hermetic_test_package_name: Arc<String>,
333 other_allowed_packages: AllowedPackages,
334 full_resolver: Arc<fresolution::ResolverProxy>,
335 pkg_resolver: Arc<fpkg::PackageResolverProxy>,
336) -> Result<(), Error> {
337 let mut fs = ServiceFs::new();
338 let mut resolver_tasks = vec![];
339 let mut pkg_resolver_tasks = vec![];
340 let log_client = handles.connect_to_named_protocol(flogger::LogSinkMarker::PROTOCOL_NAME)?;
341 let tags = ["test_resolver"];
342 let log_publisher = match flog::Publisher::new_async(
343 flog::PublisherOptions::default().tags(&tags).use_log_sink(log_client),
344 )
345 .await
346 {
347 Ok(publisher) => OptionLogger(Some(publisher)),
348 Err(e) => {
349 warn!("Error creating log publisher for resolver: {:?}", e);
350 OptionLogger(None)
351 }
352 };
353
354 let resolver_hermetic_test_package_name = hermetic_test_package_name.clone();
355 let resolver_other_allowed_packages = other_allowed_packages.clone();
356 let resolver_log_publisher = log_publisher.clone();
357
358 let pkg_resolver_hermetic_test_package_name = hermetic_test_package_name.clone();
359 let pkg_resolver_other_allowed_packages = other_allowed_packages.clone();
360 let pkg_resolver_log_publisher = log_publisher.clone();
361
362 fs.dir("svc").add_fidl_service(move |stream: fresolution::ResolverRequestStream| {
363 let full_resolver = full_resolver.clone();
364 let hermetic_test_package_name = resolver_hermetic_test_package_name.clone();
365 let other_allowed_packages = resolver_other_allowed_packages.clone();
366 let log_publisher = resolver_log_publisher.clone();
367 resolver_tasks.push(fasync::Task::local(async move {
368 serve_resolver(
369 stream,
370 log_publisher,
371 hermetic_test_package_name,
372 other_allowed_packages,
373 full_resolver,
374 )
375 .await;
376 }));
377 });
378 fs.dir("svc").add_fidl_service(move |stream: fpkg::PackageResolverRequestStream| {
379 let pkg_resolver = pkg_resolver.clone();
380 let hermetic_test_package_name = pkg_resolver_hermetic_test_package_name.clone();
381 let other_allowed_packages = pkg_resolver_other_allowed_packages.clone();
382 let log_publisher = pkg_resolver_log_publisher.clone();
383 pkg_resolver_tasks.push(fasync::Task::local(async move {
384 serve_pkg_resolver(
385 stream,
386 log_publisher,
387 hermetic_test_package_name,
388 other_allowed_packages,
389 pkg_resolver,
390 )
391 .await;
392 }));
393 });
394 fs.serve_connection(handles.outgoing_dir)?;
395 fs.collect::<()>().await;
396 Ok(())
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402 use fidl::endpoints::create_proxy_and_stream;
403 use maplit::hashset;
404
405 async fn respond_to_resolve_requests(mut stream: fresolution::ResolverRequestStream) {
406 while let Some(request) =
407 stream.try_next().await.expect("failed to serve component mock resolver")
408 {
409 match request {
410 fresolution::ResolverRequest::Resolve { component_url, responder } => {
411 match component_url.as_str() {
412 "fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm"
413 | "fuchsia-pkg://fuchsia.com/package-three#meta/comp.cm"
414 | "fuchsia-pkg://fuchsia.com/package-four#meta/comp.cm" => {
415 responder.send(Ok(fresolution::Component::default()))
416 }
417 "fuchsia-pkg://fuchsia.com/package-two#meta/comp.cm" => {
418 responder.send(Err(fresolution::ResolverError::ResourceUnavailable))
419 }
420 _ => responder.send(Err(fresolution::ResolverError::Internal)),
421 }
422 .expect("failed sending response");
423 }
424 fresolution::ResolverRequest::ResolveWithContext {
425 component_url,
426 context: _,
427 responder,
428 } => {
429 match component_url.as_str() {
430 "fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm" | "name#resource" => {
431 responder.send(Ok(fresolution::Component::default()))
432 }
433 _ => responder.send(Err(fresolution::ResolverError::PackageNotFound)),
434 }
435 .expect("failed sending response");
436 }
437 fresolution::ResolverRequest::_UnknownMethod { .. } => {
438 panic!("Unknown Resolver request");
439 }
440 }
441 }
442 }
443
444 fn run_resolver(
446 hermetic_test_package_name: Arc<String>,
447 other_allowed_packages: AllowedPackages,
448 mock_full_resolver: Arc<fresolution::ResolverProxy>,
449 ) -> (fasync::Task<()>, fresolution::ResolverProxy) {
450 let (proxy, stream) =
451 fidl::endpoints::create_proxy_and_stream::<fresolution::ResolverMarker>();
452 let logger = OptionLogger(None);
453 let task = fasync::Task::local(async move {
454 serve_resolver(
455 stream,
456 logger,
457 hermetic_test_package_name,
458 other_allowed_packages,
459 mock_full_resolver,
460 )
461 .await;
462 });
463 (task, proxy)
464 }
465
466 #[fuchsia::test]
467 async fn test_successful_resolve() {
468 let pkg_name = "package-one".to_string();
469
470 let (resolver_proxy, resolver_request_stream) =
471 create_proxy_and_stream::<fresolution::ResolverMarker>();
472 let _full_resolver_task = fasync::Task::spawn(async move {
473 respond_to_resolve_requests(resolver_request_stream).await;
474 });
475
476 let (_task, hermetic_resolver_proxy) = run_resolver(
477 pkg_name.into(),
478 AllowedPackages::zero_allowed_pkgs(),
479 Arc::new(resolver_proxy),
480 );
481
482 assert_eq!(
483 hermetic_resolver_proxy
484 .resolve("fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm")
485 .await
486 .unwrap(),
487 Ok(fresolution::Component::default())
488 );
489 let mock_context = fresolution::Context { bytes: vec![0] };
490 assert_eq!(
491 hermetic_resolver_proxy
492 .resolve_with_context("name#resource", &mock_context)
493 .await
494 .unwrap(),
495 Ok(fresolution::Component::default())
496 );
497 assert_eq!(
498 hermetic_resolver_proxy
499 .resolve_with_context("name#not_found", &mock_context)
500 .await
501 .unwrap(),
502 Err(fresolution::ResolverError::PackageNotFound)
503 );
504 assert_eq!(
505 hermetic_resolver_proxy
506 .resolve_with_context(
507 "fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm",
508 &mock_context
509 )
510 .await
511 .unwrap(),
512 Ok(fresolution::Component::default())
513 );
514 }
515
516 #[fuchsia::test]
517 async fn drop_connection_on_resolve() {
518 let pkg_name = "package-one".to_string();
519
520 let (resolver_proxy, resolver_request_stream) =
521 create_proxy_and_stream::<fresolution::ResolverMarker>();
522 let _full_resolver_task = fasync::Task::spawn(async move {
523 respond_to_resolve_requests(resolver_request_stream).await;
524 });
525
526 let (_task, hermetic_resolver_proxy) = run_resolver(
527 pkg_name.into(),
528 AllowedPackages::zero_allowed_pkgs(),
529 Arc::new(resolver_proxy),
530 );
531
532 let _ =
533 hermetic_resolver_proxy.resolve("fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm");
534 drop(hermetic_resolver_proxy); }
536
537 #[fuchsia::test]
538 async fn test_package_not_allowed() {
539 let (resolver_proxy, _) = create_proxy_and_stream::<fresolution::ResolverMarker>();
540
541 let (_task, hermetic_resolver_proxy) = run_resolver(
542 "package-two".to_string().into(),
543 AllowedPackages::zero_allowed_pkgs(),
544 Arc::new(resolver_proxy),
545 );
546
547 assert_eq!(
548 hermetic_resolver_proxy
549 .resolve("fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm")
550 .await
551 .unwrap(),
552 Err(fresolution::ResolverError::PackageNotFound)
553 );
554 let mock_context = fresolution::Context { bytes: vec![0] };
555 assert_eq!(
556 hermetic_resolver_proxy
557 .resolve_with_context(
558 "fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm",
559 &mock_context
560 )
561 .await
562 .unwrap(),
563 Err(fresolution::ResolverError::PackageNotFound)
564 );
565 }
566
567 #[fuchsia::test]
568 async fn other_packages_allowed() {
569 let (resolver_proxy, resolver_request_stream) =
570 create_proxy_and_stream::<fresolution::ResolverMarker>();
571
572 let list = hashset!("package-three".to_string(), "package-four".to_string());
573
574 let _full_resolver_task = fasync::Task::spawn(async move {
575 respond_to_resolve_requests(resolver_request_stream).await;
576 });
577
578 let (_task, hermetic_resolver_proxy) = run_resolver(
579 "package-two".to_string().into(),
580 AllowedPackages::from_iter(list),
581 Arc::new(resolver_proxy),
582 );
583
584 assert_eq!(
585 hermetic_resolver_proxy
586 .resolve("fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm")
587 .await
588 .unwrap(),
589 Err(fresolution::ResolverError::PackageNotFound)
590 );
591
592 assert_eq!(
593 hermetic_resolver_proxy
594 .resolve("fuchsia-pkg://fuchsia.com/package-three#meta/comp.cm")
595 .await
596 .unwrap(),
597 Ok(fresolution::Component::default())
598 );
599
600 assert_eq!(
601 hermetic_resolver_proxy
602 .resolve("fuchsia-pkg://fuchsia.com/package-four#meta/comp.cm")
603 .await
604 .unwrap(),
605 Ok(fresolution::Component::default())
606 );
607
608 assert_eq!(
609 hermetic_resolver_proxy
610 .resolve("fuchsia-pkg://fuchsia.com/package-two#meta/comp.cm")
611 .await
612 .unwrap(),
613 Err(fresolution::ResolverError::ResourceUnavailable)
615 );
616 }
617
618 #[fuchsia::test]
619 async fn test_failed_resolve() {
620 let (resolver_proxy, resolver_request_stream) =
621 create_proxy_and_stream::<fresolution::ResolverMarker>();
622 let _full_resolver_task = fasync::Task::spawn(async move {
623 respond_to_resolve_requests(resolver_request_stream).await;
624 });
625
626 let pkg_name = "package-two".to_string();
627 let (_task, hermetic_resolver_proxy) = run_resolver(
628 pkg_name.into(),
629 AllowedPackages::zero_allowed_pkgs(),
630 Arc::new(resolver_proxy),
631 );
632
633 assert_eq!(
634 hermetic_resolver_proxy
635 .resolve("fuchsia-pkg://fuchsia.com/package-two#meta/comp.cm")
636 .await
637 .unwrap(),
638 Err(fresolution::ResolverError::ResourceUnavailable)
639 );
640 }
641
642 #[fuchsia::test]
643 async fn test_invalid_url() {
644 let (resolver_proxy, _) = create_proxy_and_stream::<fresolution::ResolverMarker>();
645
646 let pkg_name = "package-two".to_string();
647 let (_task, hermetic_resolver_proxy) = run_resolver(
648 pkg_name.into(),
649 AllowedPackages::zero_allowed_pkgs(),
650 Arc::new(resolver_proxy),
651 );
652
653 assert_eq!(
654 hermetic_resolver_proxy.resolve("invalid_url").await.unwrap(),
655 Err(fresolution::ResolverError::InvalidArgs)
656 );
657 }
658}