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_proxy = handles
343 .connect_to_named_protocol::<flogger::LogSinkMarker>(flogger::LogSinkMarker::DEBUG_NAME)?;
344 let tags = ["test_resolver"];
345 let log_publisher = match flog::Publisher::new(
346 flog::PublisherOptions::default().tags(&tags).use_log_sink(log_proxy),
347 ) {
348 Ok(publisher) => Arc::new(publisher) as Arc<LogSubscriber>,
349 Err(e) => {
350 warn!("Error creating log publisher for resolver: {:?}", e);
351 Arc::new(NoOpLogger) as Arc<LogSubscriber>
352 }
353 };
354
355 let resolver_hermetic_test_package_name = hermetic_test_package_name.clone();
356 let resolver_other_allowed_packages = other_allowed_packages.clone();
357 let resolver_log_publisher = log_publisher.clone();
358
359 let pkg_resolver_hermetic_test_package_name = hermetic_test_package_name.clone();
360 let pkg_resolver_other_allowed_packages = other_allowed_packages.clone();
361 let pkg_resolver_log_publisher = log_publisher.clone();
362
363 fs.dir("svc").add_fidl_service(move |stream: fresolution::ResolverRequestStream| {
364 let full_resolver = full_resolver.clone();
365 let hermetic_test_package_name = resolver_hermetic_test_package_name.clone();
366 let other_allowed_packages = resolver_other_allowed_packages.clone();
367 let log_publisher = resolver_log_publisher.clone();
368 resolver_tasks.push(fasync::Task::local(async move {
369 serve_resolver(
370 stream,
371 log_publisher,
372 hermetic_test_package_name,
373 other_allowed_packages,
374 full_resolver,
375 )
376 .await;
377 }));
378 });
379 fs.dir("svc").add_fidl_service(move |stream: fpkg::PackageResolverRequestStream| {
380 let pkg_resolver = pkg_resolver.clone();
381 let hermetic_test_package_name = pkg_resolver_hermetic_test_package_name.clone();
382 let other_allowed_packages = pkg_resolver_other_allowed_packages.clone();
383 let log_publisher = pkg_resolver_log_publisher.clone();
384 pkg_resolver_tasks.push(fasync::Task::local(async move {
385 serve_pkg_resolver(
386 stream,
387 log_publisher,
388 hermetic_test_package_name,
389 other_allowed_packages,
390 pkg_resolver,
391 )
392 .await;
393 }));
394 });
395 fs.serve_connection(handles.outgoing_dir)?;
396 fs.collect::<()>().await;
397 Ok(())
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use fidl::endpoints::create_proxy_and_stream;
404 use maplit::hashset;
405
406 async fn respond_to_resolve_requests(mut stream: fresolution::ResolverRequestStream) {
407 while let Some(request) =
408 stream.try_next().await.expect("failed to serve component mock resolver")
409 {
410 match request {
411 fresolution::ResolverRequest::Resolve { component_url, responder } => {
412 match component_url.as_str() {
413 "fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm"
414 | "fuchsia-pkg://fuchsia.com/package-three#meta/comp.cm"
415 | "fuchsia-pkg://fuchsia.com/package-four#meta/comp.cm" => {
416 responder.send(Ok(fresolution::Component::default()))
417 }
418 "fuchsia-pkg://fuchsia.com/package-two#meta/comp.cm" => {
419 responder.send(Err(fresolution::ResolverError::ResourceUnavailable))
420 }
421 _ => responder.send(Err(fresolution::ResolverError::Internal)),
422 }
423 .expect("failed sending response");
424 }
425 fresolution::ResolverRequest::ResolveWithContext {
426 component_url,
427 context: _,
428 responder,
429 } => {
430 match component_url.as_str() {
431 "fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm" | "name#resource" => {
432 responder.send(Ok(fresolution::Component::default()))
433 }
434 _ => responder.send(Err(fresolution::ResolverError::PackageNotFound)),
435 }
436 .expect("failed sending response");
437 }
438 fresolution::ResolverRequest::_UnknownMethod { .. } => {
439 panic!("Unknown Resolver request");
440 }
441 }
442 }
443 }
444
445 fn run_resolver(
447 hermetic_test_package_name: Arc<String>,
448 other_allowed_packages: AllowedPackages,
449 mock_full_resolver: Arc<fresolution::ResolverProxy>,
450 ) -> (fasync::Task<()>, fresolution::ResolverProxy) {
451 let (proxy, stream) =
452 fidl::endpoints::create_proxy_and_stream::<fresolution::ResolverMarker>();
453 let logger = NoOpLogger;
454 let task = fasync::Task::local(async move {
455 serve_resolver(
456 stream,
457 Arc::new(logger),
458 hermetic_test_package_name,
459 other_allowed_packages,
460 mock_full_resolver,
461 )
462 .await;
463 });
464 (task, proxy)
465 }
466
467 #[fuchsia::test]
468 async fn test_successful_resolve() {
469 let pkg_name = "package-one".to_string();
470
471 let (resolver_proxy, resolver_request_stream) =
472 create_proxy_and_stream::<fresolution::ResolverMarker>();
473 let _full_resolver_task = fasync::Task::spawn(async move {
474 respond_to_resolve_requests(resolver_request_stream).await;
475 });
476
477 let (_task, hermetic_resolver_proxy) = run_resolver(
478 pkg_name.into(),
479 AllowedPackages::zero_allowed_pkgs(),
480 Arc::new(resolver_proxy),
481 );
482
483 assert_eq!(
484 hermetic_resolver_proxy
485 .resolve("fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm")
486 .await
487 .unwrap(),
488 Ok(fresolution::Component::default())
489 );
490 let mock_context = fresolution::Context { bytes: vec![0] };
491 assert_eq!(
492 hermetic_resolver_proxy
493 .resolve_with_context("name#resource", &mock_context)
494 .await
495 .unwrap(),
496 Ok(fresolution::Component::default())
497 );
498 assert_eq!(
499 hermetic_resolver_proxy
500 .resolve_with_context("name#not_found", &mock_context)
501 .await
502 .unwrap(),
503 Err(fresolution::ResolverError::PackageNotFound)
504 );
505 assert_eq!(
506 hermetic_resolver_proxy
507 .resolve_with_context(
508 "fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm",
509 &mock_context
510 )
511 .await
512 .unwrap(),
513 Ok(fresolution::Component::default())
514 );
515 }
516
517 #[fuchsia::test]
518 async fn drop_connection_on_resolve() {
519 let pkg_name = "package-one".to_string();
520
521 let (resolver_proxy, resolver_request_stream) =
522 create_proxy_and_stream::<fresolution::ResolverMarker>();
523 let _full_resolver_task = fasync::Task::spawn(async move {
524 respond_to_resolve_requests(resolver_request_stream).await;
525 });
526
527 let (_task, hermetic_resolver_proxy) = run_resolver(
528 pkg_name.into(),
529 AllowedPackages::zero_allowed_pkgs(),
530 Arc::new(resolver_proxy),
531 );
532
533 let _ =
534 hermetic_resolver_proxy.resolve("fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm");
535 drop(hermetic_resolver_proxy); }
537
538 #[fuchsia::test]
539 async fn test_package_not_allowed() {
540 let (resolver_proxy, _) = create_proxy_and_stream::<fresolution::ResolverMarker>();
541
542 let (_task, hermetic_resolver_proxy) = run_resolver(
543 "package-two".to_string().into(),
544 AllowedPackages::zero_allowed_pkgs(),
545 Arc::new(resolver_proxy),
546 );
547
548 assert_eq!(
549 hermetic_resolver_proxy
550 .resolve("fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm")
551 .await
552 .unwrap(),
553 Err(fresolution::ResolverError::PackageNotFound)
554 );
555 let mock_context = fresolution::Context { bytes: vec![0] };
556 assert_eq!(
557 hermetic_resolver_proxy
558 .resolve_with_context(
559 "fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm",
560 &mock_context
561 )
562 .await
563 .unwrap(),
564 Err(fresolution::ResolverError::PackageNotFound)
565 );
566 }
567
568 #[fuchsia::test]
569 async fn other_packages_allowed() {
570 let (resolver_proxy, resolver_request_stream) =
571 create_proxy_and_stream::<fresolution::ResolverMarker>();
572
573 let list = hashset!("package-three".to_string(), "package-four".to_string());
574
575 let _full_resolver_task = fasync::Task::spawn(async move {
576 respond_to_resolve_requests(resolver_request_stream).await;
577 });
578
579 let (_task, hermetic_resolver_proxy) = run_resolver(
580 "package-two".to_string().into(),
581 AllowedPackages::from_iter(list),
582 Arc::new(resolver_proxy),
583 );
584
585 assert_eq!(
586 hermetic_resolver_proxy
587 .resolve("fuchsia-pkg://fuchsia.com/package-one#meta/comp.cm")
588 .await
589 .unwrap(),
590 Err(fresolution::ResolverError::PackageNotFound)
591 );
592
593 assert_eq!(
594 hermetic_resolver_proxy
595 .resolve("fuchsia-pkg://fuchsia.com/package-three#meta/comp.cm")
596 .await
597 .unwrap(),
598 Ok(fresolution::Component::default())
599 );
600
601 assert_eq!(
602 hermetic_resolver_proxy
603 .resolve("fuchsia-pkg://fuchsia.com/package-four#meta/comp.cm")
604 .await
605 .unwrap(),
606 Ok(fresolution::Component::default())
607 );
608
609 assert_eq!(
610 hermetic_resolver_proxy
611 .resolve("fuchsia-pkg://fuchsia.com/package-two#meta/comp.cm")
612 .await
613 .unwrap(),
614 Err(fresolution::ResolverError::ResourceUnavailable)
616 );
617 }
618
619 #[fuchsia::test]
620 async fn test_failed_resolve() {
621 let (resolver_proxy, resolver_request_stream) =
622 create_proxy_and_stream::<fresolution::ResolverMarker>();
623 let _full_resolver_task = fasync::Task::spawn(async move {
624 respond_to_resolve_requests(resolver_request_stream).await;
625 });
626
627 let pkg_name = "package-two".to_string();
628 let (_task, hermetic_resolver_proxy) = run_resolver(
629 pkg_name.into(),
630 AllowedPackages::zero_allowed_pkgs(),
631 Arc::new(resolver_proxy),
632 );
633
634 assert_eq!(
635 hermetic_resolver_proxy
636 .resolve("fuchsia-pkg://fuchsia.com/package-two#meta/comp.cm")
637 .await
638 .unwrap(),
639 Err(fresolution::ResolverError::ResourceUnavailable)
640 );
641 }
642
643 #[fuchsia::test]
644 async fn test_invalid_url() {
645 let (resolver_proxy, _) = create_proxy_and_stream::<fresolution::ResolverMarker>();
646
647 let pkg_name = "package-two".to_string();
648 let (_task, hermetic_resolver_proxy) = run_resolver(
649 pkg_name.into(),
650 AllowedPackages::zero_allowed_pkgs(),
651 Arc::new(resolver_proxy),
652 );
653
654 assert_eq!(
655 hermetic_resolver_proxy.resolve("invalid_url").await.unwrap(),
656 Err(fresolution::ResolverError::InvalidArgs)
657 );
658 }
659}