use gossan_cloud::{
apigateway::ApiGatewayProvider, cloudfront::CloudFrontProvider, lambda::LambdaProvider,
provider::CloudProvider, CloudScanner,
};
use gossan_core::{Config, DiscoverySource, DomainTarget, HostTarget, ScanInput, Scanner, Target};
use std::net::IpAddr;
use std::sync::Arc;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use hickory_resolver::{
config::{ResolverConfig, ResolverOpts},
TokioAsyncResolver,
};
fn dummy_resolver() -> Arc<TokioAsyncResolver> {
Arc::new(TokioAsyncResolver::tokio(
ResolverConfig::default(),
ResolverOpts::default(),
))
}
fn streaming_input(
seed: &str,
targets: Vec<Target>,
) -> (
ScanInput,
tokio::sync::mpsc::UnboundedReceiver<secfinding::Finding>,
) {
let (in_tx, in_rx) = tokio::sync::mpsc::unbounded_channel::<Target>();
for t in targets {
let _ = in_tx.send(t);
}
drop(in_tx);
let (live_tx, live_rx) = tokio::sync::mpsc::unbounded_channel();
let (target_tx, _target_rx) = tokio::sync::mpsc::unbounded_channel();
let input = ScanInput {
seed: seed.to_string(),
target_rx: tokio::sync::Mutex::new(in_rx),
live_tx,
target_tx,
resolver: dummy_resolver(),
};
(input, live_rx)
}
#[tokio::test]
async fn test_cloudfront_adversarial_403() {
let server: MockServer = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(ResponseTemplate::new(403).set_body_string("Some error message"))
.mount(&server)
.await;
let client = reqwest::Client::new();
let target = Target::Domain(DomainTarget {
domain: "example.com".into(),
source: DiscoverySource::Seed,
});
struct TestCFProvider(String);
#[async_trait::async_trait]
impl CloudProvider for TestCFProvider {
fn name(&self) -> &'static str {
"cloudfront"
}
fn endpoint(&self, _name: &str) -> String {
self.0.clone()
}
async fn probe(
&self,
c: &reqwest::Client,
n: &str,
t: &Target,
) -> anyhow::Result<Vec<secfinding::Finding>> {
let cf = CloudFrontProvider;
cf.probe(c, n, t).await
}
}
let url = server.uri() + "/";
let resp = client.get(&url).send().await.unwrap();
assert_eq!(resp.status().as_u16(), 403);
}
#[tokio::test]
async fn test_multi_cloud_correlation() {
let names = vec!["aws-assets", "gcp-assets"];
assert!(names.len() >= 2);
let result1 = gossan_cloud::permutations::generate(names[0]);
let result2 = gossan_cloud::permutations::generate(names[1]);
assert!(!result1.is_empty());
assert!(!result2.is_empty());
}
#[test]
fn test_cloudfront_endpoint_generation() {
let cf = CloudFrontProvider;
assert_eq!(
cf.endpoint("d111111abcdef8"),
"https://d111111abcdef8.cloudfront.net/"
);
}
#[tokio::test]
async fn test_apigateway_adversarial_403() {
let server: MockServer = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(ResponseTemplate::new(403).set_body_string("Missing Authentication Token"))
.mount(&server)
.await;
let client = reqwest::Client::new();
let target = Target::Domain(DomainTarget {
domain: "example.com".into(),
source: DiscoverySource::Seed,
});
struct TestAPIProvider(String);
#[async_trait::async_trait]
impl CloudProvider for TestAPIProvider {
fn name(&self) -> &'static str {
"apigateway"
}
fn endpoint(&self, _name: &str) -> String {
self.0.clone()
}
async fn probe(
&self,
c: &reqwest::Client,
n: &str,
t: &Target,
) -> anyhow::Result<Vec<secfinding::Finding>> {
let api = ApiGatewayProvider;
api.probe(c, n, t).await
}
}
let url = server.uri() + "/";
let resp = client.get(&url).send().await.unwrap();
assert_eq!(resp.status().as_u16(), 403);
}
#[test]
fn test_apigateway_endpoint_generation() {
let api = ApiGatewayProvider;
assert_eq!(
api.endpoint("test-api"),
"https://test-api.execute-api.us-east-1.amazonaws.com/"
);
}
#[tokio::test]
async fn test_lambda_adversarial_403() {
let server: MockServer = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/"))
.respond_with(ResponseTemplate::new(403).set_body_string("Forbidden"))
.mount(&server)
.await;
let client = reqwest::Client::new();
let target = Target::Domain(DomainTarget {
domain: "example.com".into(),
source: DiscoverySource::Seed,
});
struct TestLambdaProvider(String);
#[async_trait::async_trait]
impl CloudProvider for TestLambdaProvider {
fn name(&self) -> &'static str {
"lambda"
}
fn endpoint(&self, _name: &str) -> String {
self.0.clone()
}
async fn probe(
&self,
c: &reqwest::Client,
n: &str,
t: &Target,
) -> anyhow::Result<Vec<secfinding::Finding>> {
let lambda = LambdaProvider;
lambda.probe(c, n, t).await
}
}
let url = server.uri() + "/";
let resp = client.get(&url).send().await.unwrap();
assert_eq!(resp.status().as_u16(), 403);
}
#[test]
fn test_lambda_endpoint_generation() {
let lambda = LambdaProvider;
let url_id = "0123456789abcdef0123456789abcdef"; assert_eq!(
lambda.endpoint(url_id),
format!("https://{}.lambda-url.us-east-1.on.aws/", url_id)
);
}
async fn assert_no_findings(scanner: &CloudScanner, input: ScanInput) {
let config = Config::default();
scanner.run(input, &config).await.unwrap();
}
#[tokio::test]
async fn test_metadata_ssrf_protection_seed() {
let scanner = CloudScanner;
let (input, mut live_rx) = streaming_input("169.254.169.254", vec![]);
assert_no_findings(&scanner, input).await;
let mut emitted = Vec::new();
while let Ok(f) = live_rx.try_recv() {
emitted.push(f);
}
assert!(
emitted.is_empty(),
"SSRF attempt must return immediately with no findings."
);
}
#[tokio::test]
async fn test_metadata_ssrf_protection_target_domain() {
let scanner = CloudScanner;
let (input, mut live_rx) = streaming_input(
"example.com",
vec![Target::Domain(DomainTarget {
domain: "169.254.169.254".into(),
source: DiscoverySource::Seed,
})],
);
assert_no_findings(&scanner, input).await;
let mut emitted = Vec::new();
while let Ok(f) = live_rx.try_recv() {
emitted.push(f);
}
assert!(
emitted.is_empty(),
"SSRF attempt must return immediately with no findings."
);
}
#[tokio::test]
async fn test_metadata_ssrf_protection_target_ip() {
let scanner = CloudScanner;
let (input, mut live_rx) = streaming_input(
"example.com",
vec![Target::Host(HostTarget {
ip: "169.254.169.254".parse::<IpAddr>().unwrap(),
domain: None,
})],
);
assert_no_findings(&scanner, input).await;
let mut emitted = Vec::new();
while let Ok(f) = live_rx.try_recv() {
emitted.push(f);
}
assert!(
emitted.is_empty(),
"SSRF attempt must return immediately with no findings."
);
}