use crate::common; use crate::common::ext::*; use aws_sdk_s3::{ model::{CorsConfiguration, CorsRule, ErrorDocument, IndexDocument, WebsiteConfiguration}, types::ByteStream, }; use http::Request; use hyper::{ body::{to_bytes, Body}, Client, }; const BODY: &[u8; 16] = b"<h1>bonjour</h1>"; const BODY_ERR: &[u8; 6] = b"erreur"; #[tokio::test] async fn test_website() { const BCKT_NAME: &str = "my-website"; let ctx = common::context(); let bucket = ctx.create_bucket(BCKT_NAME); let data = ByteStream::from_static(BODY); ctx.client .put_object() .bucket(&bucket) .key("index.html") .body(data) .send() .await .unwrap(); let client = Client::new(); let req = || { Request::builder() .method("GET") .uri(format!( "http://127.0.0.1:{}/", common::garage::DEFAULT_PORT + 2 )) .header("Host", format!("{}.web.garage", BCKT_NAME)) .body(Body::empty()) .unwrap() }; let mut resp = client.request(req()).await.unwrap(); assert_eq!(resp.status(), 404); assert_ne!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY.as_ref() ); /* check that we do not leak body */ ctx.garage .command() .args(["bucket", "website", "--allow", BCKT_NAME]) .quiet() .expect_success_status("Could not allow website on bucket"); resp = client.request(req()).await.unwrap(); assert_eq!(resp.status(), 200); assert_eq!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY.as_ref() ); ctx.garage .command() .args(["bucket", "website", "--deny", BCKT_NAME]) .quiet() .expect_success_status("Could not deny website on bucket"); resp = client.request(req()).await.unwrap(); assert_eq!(resp.status(), 404); assert_ne!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY.as_ref() ); /* check that we do not leak body */ } #[tokio::test] async fn test_website_s3_api() { const BCKT_NAME: &str = "my-cors"; let ctx = common::context(); let bucket = ctx.create_bucket(BCKT_NAME); let data = ByteStream::from_static(BODY); ctx.client .put_object() .bucket(&bucket) .key("site/home.html") .body(data) .send() .await .unwrap(); ctx.client .put_object() .bucket(&bucket) .key("err/error.html") .body(ByteStream::from_static(BODY_ERR)) .send() .await .unwrap(); let conf = WebsiteConfiguration::builder() .index_document(IndexDocument::builder().suffix("home.html").build()) .error_document(ErrorDocument::builder().key("err/error.html").build()) .build(); ctx.client .put_bucket_website() .bucket(&bucket) .website_configuration(conf) .send() .await .unwrap(); let cors = CorsConfiguration::builder() .cors_rules( CorsRule::builder() .id("main-rule") .allowed_headers("*") .allowed_methods("GET") .allowed_methods("PUT") .allowed_origins("*") .build(), ) .build(); ctx.client .put_bucket_cors() .bucket(&bucket) .cors_configuration(cors) .send() .await .unwrap(); { let cors_res = ctx .client .get_bucket_cors() .bucket(&bucket) .send() .await .unwrap(); let main_rule = cors_res.cors_rules().unwrap().iter().next().unwrap(); assert_eq!(main_rule.id.as_ref().unwrap(), "main-rule"); assert_eq!( main_rule.allowed_headers.as_ref().unwrap(), &vec!["*".to_string()] ); assert_eq!( main_rule.allowed_origins.as_ref().unwrap(), &vec!["*".to_string()] ); assert_eq!( main_rule.allowed_methods.as_ref().unwrap(), &vec!["GET".to_string(), "PUT".to_string()] ); } let client = Client::new(); // Test direct requests with CORS { let req = Request::builder() .method("GET") .uri(format!( "http://127.0.0.1:{}/site/", common::garage::DEFAULT_PORT + 2 )) .header("Host", format!("{}.web.garage", BCKT_NAME)) .header("Origin", "https://example.com") .body(Body::empty()) .unwrap(); let mut resp = client.request(req).await.unwrap(); assert_eq!(resp.status(), 200); assert_eq!( resp.headers().get("access-control-allow-origin").unwrap(), "*" ); assert_eq!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY.as_ref() ); } // Test ErrorDocument on 404 { let req = Request::builder() .method("GET") .uri(format!( "http://127.0.0.1:{}/wrong.html", common::garage::DEFAULT_PORT + 2 )) .header("Host", format!("{}.web.garage", BCKT_NAME)) .body(Body::empty()) .unwrap(); let mut resp = client.request(req).await.unwrap(); assert_eq!(resp.status(), 404); assert_eq!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY_ERR.as_ref() ); } // Test CORS with an allowed preflight request { let req = Request::builder() .method("OPTIONS") .uri(format!( "http://127.0.0.1:{}/site/", common::garage::DEFAULT_PORT + 2 )) .header("Host", format!("{}.web.garage", BCKT_NAME)) .header("Origin", "https://example.com") .header("Access-Control-Request-Method", "PUT") .body(Body::empty()) .unwrap(); let mut resp = client.request(req).await.unwrap(); assert_eq!(resp.status(), 200); assert_eq!( resp.headers().get("access-control-allow-origin").unwrap(), "*" ); assert_ne!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY.as_ref() ); } // Test CORS with a forbidden preflight request { let req = Request::builder() .method("OPTIONS") .uri(format!( "http://127.0.0.1:{}/site/", common::garage::DEFAULT_PORT + 2 )) .header("Host", format!("{}.web.garage", BCKT_NAME)) .header("Origin", "https://example.com") .header("Access-Control-Request-Method", "DELETE") .body(Body::empty()) .unwrap(); let mut resp = client.request(req).await.unwrap(); assert_eq!(resp.status(), 403); assert_ne!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY.as_ref() ); } //@TODO test CORS on the S3 endpoint. We need to handle auth manually to check it. // Delete cors ctx.client .delete_bucket_cors() .bucket(&bucket) .send() .await .unwrap(); // Check CORS are deleted from the API // @FIXME check what is the expected behavior when GetBucketCors is called on a bucket without // any CORS. assert!(ctx .client .get_bucket_cors() .bucket(&bucket) .send() .await .is_err()); // Test CORS are not sent anymore on a previously allowed request { let req = Request::builder() .method("OPTIONS") .uri(format!( "http://127.0.0.1:{}/site/", common::garage::DEFAULT_PORT + 2 )) .header("Host", format!("{}.web.garage", BCKT_NAME)) .header("Origin", "https://example.com") .header("Access-Control-Request-Method", "PUT") .body(Body::empty()) .unwrap(); let mut resp = client.request(req).await.unwrap(); assert_eq!(resp.status(), 403); assert_ne!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY.as_ref() ); } // Disallow website from the API ctx.client .delete_bucket_website() .bucket(&bucket) .send() .await .unwrap(); // Check that the website is not served anymore { let req = Request::builder() .method("GET") .uri(format!( "http://127.0.0.1:{}/site/", common::garage::DEFAULT_PORT + 2 )) .header("Host", format!("{}.web.garage", BCKT_NAME)) .body(Body::empty()) .unwrap(); let mut resp = client.request(req).await.unwrap(); assert_eq!(resp.status(), 404); assert_ne!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY_ERR.as_ref() ); assert_ne!( to_bytes(resp.body_mut()).await.unwrap().as_ref(), BODY.as_ref() ); } }