1use anyhow::Error;
6use fidl::endpoints::ProtocolMarker;
7use fuchsia_component::server::ServiceFs;
8use fuchsia_component_test::LocalComponentHandles;
9use fuchsia_url::{ComponentUrl, PackageUrl};
10use futures::{StreamExt, TryStreamExt};
11use itertools::Itertools;
12use log::warn;
13use std::collections::HashSet;
14use std::sync::Arc;
15use {
16 diagnostics_log as flog, fidl_fuchsia_component_resolution as fresolution,
17 fidl_fuchsia_logger as flogger, fidl_fuchsia_pkg as fpkg, fuchsia_async as fasync,
18};
19
20type LogSubscriber = dyn log::Log + std::marker::Send + std::marker::Sync + 'static;
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct AllowedPackages {
25 pkgs: Arc<HashSet<String>>,
27}
28
29impl AllowedPackages {
30 pub fn zero_allowed_pkgs() -> Self {
31 Self { pkgs: HashSet::new().into() }
32 }
33
34 pub fn from_iter<I>(iter: I) -> Self
35 where
36 I: IntoIterator<Item = String>,
37 {
38 Self { pkgs: Arc::new(HashSet::from_iter(iter)) }
39 }
40}
41
42async fn validate_hermetic_package(
43 component_url_str: &str,
44 logger: Arc<LogSubscriber>,
45 hermetic_test_package_name: &String,
46 other_allowed_packages: &AllowedPackages,
47) -> Result<(), fresolution::ResolverError> {
48 let component_url = ComponentUrl::parse(component_url_str).map_err(|err| {
49 warn!("cannot parse {}, {:?}", component_url_str, err);
50 fresolution::ResolverError::InvalidArgs
51 })?;
52
53 match component_url.package_url() {
54 PackageUrl::Absolute(pkg_url) => {
55 let package_name = pkg_url.name();
56 if hermetic_test_package_name != package_name.as_ref()
57 && !other_allowed_packages.pkgs.contains(package_name.as_ref())
58 {
59 let s = format!("failed to resolve component {}: package {} is not in the test package allowlist: '{}, {}'
60 \nSee https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework?hl=en#hermetic-resolver
61 for more information.",
62 &component_url_str, package_name, hermetic_test_package_name, other_allowed_packages.pkgs.iter().join(", "));
63 let mut builder = log::Record::builder();
66 builder.level(log::Level::Warn);
67 logger.log(&builder.args(format_args!("{}", s)).build());
68 warn!("{}", s);
69 return Err(fresolution::ResolverError::PackageNotFound);
70 }
71 }
72 PackageUrl::Relative(_url) => {
73 }
75 }
76 Ok(())
77}
78
79async fn validate_hermetic_url(
80 pkg_url_str: &str,
81 logger: Arc<LogSubscriber>,
82 hermetic_test_package_name: &String,
83 other_allowed_packages: &AllowedPackages,
84) -> Result<(), fpkg::ResolveError> {
85 let pkg_url = PackageUrl::parse(pkg_url_str).map_err(|err| {
86 warn!("cannot parse {}, {:?}", pkg_url_str, err);
87 fpkg::ResolveError::InvalidUrl
88 })?;
89
90 match pkg_url {
91 PackageUrl::Absolute(pkg_url) => {
92 let package_name = pkg_url.name();
93 if hermetic_test_package_name != package_name.as_ref()
94 && !other_allowed_packages.pkgs.contains(package_name.as_ref())
95 {
96 let s = format!("failed to resolve component {}: package {} is not in the test package allowlist: '{}, {}'
97 \nSee https://fuchsia.dev/fuchsia-src/development/testing/components/test_runner_framework?hl=en#hermetic-resolver
98 for more information.",
99 &pkg_url_str, package_name, hermetic_test_package_name, other_allowed_packages.pkgs.iter().join(", "));
100 let mut builder = log::Record::builder();
102 builder.level(log::Level::Warn);
103 logger.log(&builder.args(format_args!("{}", s)).build());
104 warn!("{}", s);
105 return Err(fpkg::ResolveError::PackageNotFound);
106 }
107 }
108 PackageUrl::Relative(_url) => {
109 }
111 }
112 Ok(())
113}
114
115async fn serve_resolver(
116 mut stream: fresolution::ResolverRequestStream,
117 logger: Arc<LogSubscriber>,
118 hermetic_test_package_name: Arc<String>,
119 other_allowed_packages: AllowedPackages,
120 full_resolver: Arc<fresolution::ResolverProxy>,
121) {
122 while let Some(request) = stream.try_next().await.expect("failed to serve component resolver") {
123 match request {
124 fresolution::ResolverRequest::Resolve { component_url, responder } => {
125 let result = if let Err(err) = validate_hermetic_package(
126 &component_url,
127 logger.clone(),
128 &hermetic_test_package_name,
129 &other_allowed_packages,
130 )
131 .await
132 {
133 Err(err)
134 } else {
135 let logger = logger.clone();
136 full_resolver.resolve(&component_url).await.unwrap_or_else(|err| {
137 let mut builder = log::Record::builder();
138 builder.level(log::Level::Warn);
139 logger.log(
140 &builder
141 .args(format_args!(
142 "failed to resolve component {}: {:?}",
143 component_url, err
144 ))
145 .build(),
146 );
147 Err(fresolution::ResolverError::Internal)
148 })
149 };
150 if let Err(e) = responder.send(result) {
151 warn!("Failed sending load response for {}: {}", component_url, e);
152 }
153 }
154 fresolution::ResolverRequest::ResolveWithContext {
155 component_url,
156 context,
157 responder,
158 } => {
159 let result = if let Err(err) = validate_hermetic_package(
162 &component_url,
163 logger.clone(),
164 &hermetic_test_package_name,
165 &other_allowed_packages,
166 )
167 .await
168 {
169 Err(err)
170 } else {
171 let logger = logger.clone();
172 full_resolver
173 .resolve_with_context(&component_url, &context)
174 .await
175 .unwrap_or_else(|err| {
176 let mut builder = log::Record::builder();
177 builder.level(log::Level::Warn);
178 logger.log(
179 &builder
180 .args(format_args!(
181 "failed to resolve component {} with context {:?}: {:?}",
182 component_url, context, err
183 ))
184 .build(),
185 );
186 Err(fresolution::ResolverError::Internal)
187 })
188 };
189 if let Err(e) = responder.send(result) {
190 warn!("Failed sending load response for {}: {}", component_url, e);
191 }
192 }
193 fresolution::ResolverRequest::_UnknownMethod { ordinal, .. } => {
194 warn!(ordinal:%; "Unknown Resolver request");
195 }
196 }
197 }
198}
199
200async fn serve_pkg_resolver(
201 mut stream: fpkg::PackageResolverRequestStream,
202 logger: Arc<LogSubscriber>,
203 hermetic_test_package_name: Arc<String>,
204 other_allowed_packages: AllowedPackages,
205 pkg_resolver: Arc<fpkg::PackageResolverProxy>,
206) {
207 while let Some(request) = stream.try_next().await.expect("failed to serve component resolver") {
208 match request {
209 fpkg::PackageResolverRequest::Resolve { package_url, dir, responder } => {
210 let result = if let Err(err) = validate_hermetic_url(
211 &package_url,
212 logger.clone(),
213 &hermetic_test_package_name,
214 &other_allowed_packages,
215 )
216 .await
217 {
218 Err(err)
219 } else {
220 let logger = logger.clone();
221 pkg_resolver.resolve(&package_url, dir).await.unwrap_or_else(|err| {
222 let mut builder = log::Record::builder();
223 builder.level(log::Level::Warn);
224 logger.log(
225 &builder
226 .args(format_args!(
227 "failed to resolve pkg {}: {:?}",
228 package_url, err
229 ))
230 .build(),
231 );
232 Err(fpkg::ResolveError::Internal)
233 })
234 };
235 let result_ref = result.as_ref();
236 let result_ref = result_ref.map_err(|e| e.to_owned());
237 if let Err(e) = responder.send(result_ref) {
238 warn!("Failed sending load response for {}: {}", package_url, e);
239 }
240 }
241 fpkg::PackageResolverRequest::ResolveWithContext {
242 package_url,
243 context,
244 dir,
245 responder,
246 } => {
247 let result = if let Err(err) = validate_hermetic_url(
250 &package_url,
251 logger.clone(),
252 &hermetic_test_package_name,
253 &other_allowed_packages,
254 )
255 .await
256 {
257 Err(err)
258 } else {
259 let logger = logger.clone();
260 pkg_resolver
261 .resolve_with_context(&package_url, &context, dir)
262 .await
263 .unwrap_or_else(|err| {
264 let mut builder = log::Record::builder();
265 builder.level(log::Level::Warn);
266 logger.log(
267 &builder
268 .args(format_args!(
269 "failed to resolve pkg {} with context {:?}: {:?}",
270 package_url, context, err
271 ))
272 .build(),
273 );
274 Err(fpkg::ResolveError::Internal)
275 })
276 };
277 let result_ref = result.as_ref();
278 let result_ref = result_ref.map_err(|e| e.to_owned());
279 if let Err(e) = responder.send(result_ref) {
280 warn!("Failed sending load response for {}: {}", package_url, e);
281 }
282 }
283 fpkg::PackageResolverRequest::GetHash { package_url, responder } => {
284 let result = if let Err(_err) = validate_hermetic_url(
285 package_url.url.as_str(),
286 logger.clone(),
287 &hermetic_test_package_name,
288 &other_allowed_packages,
289 )
290 .await
291 {
292 Err(zx::Status::INTERNAL.into_raw())
293 } else {
294 let logger = logger.clone();
295 pkg_resolver.get_hash(&package_url).await.unwrap_or_else(|err| {
296 let mut builder = log::Record::builder();
297 builder.level(log::Level::Warn);
298 logger.log(
299 &builder
300 .args(format_args!(
301 "failed to resolve pkg {}: {:?}",
302 package_url.url.as_str(),
303 err
304 ))
305 .build(),
306 );
307 Err(zx::Status::INTERNAL.into_raw())
308 })
309 };
310 let result_ref = result.as_ref();
311 let result_ref = result_ref.map_err(|e| e.to_owned());
312 if let Err(e) = responder.send(result_ref) {
313 warn!("Failed sending load response for {}: {}", package_url.url.as_str(), e);
314 }
315 }
316 }
317 }
318}
319
320struct NoOpLogger;
321
322impl log::Log for NoOpLogger {
323 fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
324 false
325 }
326
327 fn log(&self, _record: &log::Record<'_>) {}
328
329 fn flush(&self) {}
330}
331
332pub async fn serve_hermetic_resolver(
333 handles: LocalComponentHandles,
334 hermetic_test_package_name: Arc<String>,
335 other_allowed_packages: AllowedPackages,
336 full_resolver: Arc<fresolution::ResolverProxy>,
337 pkg_resolver: Arc<fpkg::PackageResolverProxy>,
338) -> Result<(), Error> {
339 let mut fs = ServiceFs::new();
340 let mut resolver_tasks = vec![];
341 let mut pkg_resolver_tasks = vec![];
342 let log_client = handles.connect_to_named_protocol(flogger::LogSinkMarker::DEBUG_NAME)?;
343 let tags = ["test_resolver"];
344 let log_publisher = match flog::Publisher::new(
345 flog::PublisherOptions::default().tags(&tags).use_log_sink(log_client),
346 ) {
347 Ok(publisher) => Arc::new(publisher) as Arc<LogSubscriber>,
348 Err(e) => {
349 warn!("Error creating log publisher for resolver: {:?}", e);
350 Arc::new(NoOpLogger) as Arc<LogSubscriber>
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 = NoOpLogger;
453 let task = fasync::Task::local(async move {
454 serve_resolver(
455 stream,
456 Arc::new(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}