-
Notifications
You must be signed in to change notification settings - Fork 0
OBE-8859: ip whitelist source only (PART 1) #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b4d123c
50c1a4d
e2b0a01
a6c0ba8
6c86c72
c9c27b0
57a68f8
1e8e038
ce681e6
e935100
ade833b
d15e34e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,22 @@ | ||
| use std::time::Duration; | ||
| use std::{convert::Infallible, fmt, net::SocketAddr}; | ||
|
|
||
| use futures::FutureExt; | ||
| use futures::{FutureExt, StreamExt}; | ||
| use hyper::{service::make_service_fn, Server}; | ||
| use tokio::net::TcpStream; | ||
| use tower::ServiceBuilder; | ||
| use tracing::Span; | ||
| use vector_lib::codecs::decoding::{DeserializerConfig, FramingConfig}; | ||
| use vector_lib::config::{LegacyKey, LogNamespace}; | ||
| use vector_lib::configurable::configurable_component; | ||
| use vector_lib::ipallowlist::IpAllowlistConfig; | ||
| use vector_lib::lookup::owned_value_path; | ||
| use vector_lib::sensitive_string::SensitiveString; | ||
| use vector_lib::tls::MaybeTlsIncomingStream; | ||
| use vrl::value::Kind; | ||
|
|
||
| use crate::http::{KeepaliveConfig, MaxConnectionAgeLayer}; | ||
| use crate::internal_events::HttpBadPeerConnectionError; | ||
| use crate::{ | ||
| codecs::DecodingConfig, | ||
| config::{ | ||
|
|
@@ -104,6 +106,9 @@ pub struct AwsKinesisFirehoseConfig { | |
| #[configurable(derived)] | ||
| #[serde(default)] | ||
| keepalive: KeepaliveConfig, | ||
|
|
||
| #[configurable(derived)] | ||
| pub permit_origin: Option<IpAllowlistConfig>, | ||
| } | ||
|
|
||
| const fn access_keys_example() -> [&'static str; 2] { | ||
|
|
@@ -181,6 +186,8 @@ impl SourceConfig for AwsKinesisFirehoseConfig { | |
|
|
||
| let tls = MaybeTlsSettings::from_config(self.tls.as_ref(), true)?; | ||
| let listener = tls.bind(&self.address).await?; | ||
| let listener = listener | ||
| .with_allowlist(self.permit_origin.clone().map(Into::into)); | ||
|
|
||
| let keepalive_settings = self.keepalive.clone(); | ||
| let shutdown = cx.shutdown; | ||
|
|
@@ -200,7 +207,21 @@ impl SourceConfig for AwsKinesisFirehoseConfig { | |
| futures_util::future::ok::<_, Infallible>(svc) | ||
| }); | ||
|
|
||
| Server::builder(hyper::server::accept::from_stream(listener.accept_stream())) | ||
| Server::builder(hyper::server::accept::from_stream( | ||
| listener.accept_stream().filter_map(|result| async move { | ||
| match result { | ||
| Ok(stream) => Some(Ok::<_, Infallible>(stream)), | ||
| Err(err) => { | ||
| if err.is_fatal() { | ||
| warn!(message = "Fatal error accepting connection.", error = %err); | ||
| } else { | ||
| emit!(HttpBadPeerConnectionError { error: &err }); | ||
| } | ||
| None | ||
| } | ||
| } | ||
| }), | ||
| )) | ||
| .serve(make_svc) | ||
| .with_graceful_shutdown(shutdown.map(|_| ())) | ||
| .await | ||
|
|
@@ -261,6 +282,7 @@ impl GenerateConfig for AwsKinesisFirehoseConfig { | |
| acknowledgements: Default::default(), | ||
| log_namespace: None, | ||
| keepalive: Default::default(), | ||
| permit_origin: None, | ||
| }) | ||
| .unwrap() | ||
| } | ||
|
|
@@ -354,6 +376,7 @@ mod tests { | |
| acknowledgements: true.into(), | ||
| log_namespace: Some(log_namespace), | ||
| keepalive: Default::default(), | ||
| permit_origin: None, | ||
| } | ||
| .build(cx) | ||
| .await | ||
|
|
@@ -937,4 +960,106 @@ mod tests { | |
| .get("aws_kinesis_firehose_access_key") | ||
| .is_none()); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn permit_origin_blocks_non_matching_ip() { | ||
| use vector_lib::ipallowlist::{IpAllowlistConfig, IpNetConfig}; | ||
| use tokio::time::{timeout, Duration}; | ||
| use futures::StreamExt; | ||
|
|
||
| let (sender, mut recv) = SourceSender::new_test_finalize(EventStatus::Delivered); | ||
| let address = next_addr(); | ||
| let cx = SourceContext::new_test(sender, None); | ||
|
|
||
| let permit_origin = Some(IpAllowlistConfig(vec![ | ||
| IpNetConfig("10.0.0.1/32".parse().unwrap()), | ||
| ])); | ||
|
|
||
| tokio::spawn(async move { | ||
| AwsKinesisFirehoseConfig { | ||
| address, | ||
| tls: None, | ||
| access_key: None, | ||
| access_keys: None, | ||
| store_access_key: false, | ||
| record_compression: Compression::None, | ||
| framing: default_framing_message_based(), | ||
| decoding: default_decoding(), | ||
| acknowledgements: true.into(), | ||
| log_namespace: None, | ||
| keepalive: Default::default(), | ||
| permit_origin, | ||
| } | ||
| .build(cx) | ||
| .await | ||
| .unwrap() | ||
| .await | ||
| .unwrap() | ||
| }); | ||
| wait_for_tcp(address).await; | ||
|
|
||
| // Send from localhost — should be blocked by allowlist | ||
| let _ = reqwest::Client::new() | ||
| .post(format!("http://{}", address)) | ||
| .header("x-amz-firehose-protocol-version", "1.0") | ||
| .header("x-amz-firehose-request-id", REQUEST_ID) | ||
| .header("x-amz-firehose-source-arn", SOURCE_ARN) | ||
| .header("content-type", "application/json") | ||
| .body(r#"{"requestId":"test","timestamp":1234567890,"records":[{"data":"dGVzdA=="}]}"#) | ||
| .send() | ||
| .await; | ||
|
|
||
| let result = timeout(Duration::from_millis(200), recv.next()).await; | ||
| assert!(result.is_err(), "expected no events from blocked IP"); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn permit_origin_allows_matching_ip() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Often it is possible to avoid so much duplication. One way I can think of is to create a helper that accepts permit-origin fragment (or in case of toml config-parsing tests, accepts the entire toml string) and returns the receive channel. Then the test can simply recv the message or assert timeout. |
||
| use vector_lib::ipallowlist::{IpAllowlistConfig, IpNetConfig}; | ||
|
|
||
| let (sender, _recv) = SourceSender::new_test_finalize(EventStatus::Delivered); | ||
| let address = next_addr(); | ||
| let cx = SourceContext::new_test(sender, None); | ||
|
|
||
| let permit_origin = Some(IpAllowlistConfig(vec![ | ||
| IpNetConfig("127.0.0.1/32".parse().unwrap()), | ||
| ])); | ||
|
|
||
| tokio::spawn(async move { | ||
| AwsKinesisFirehoseConfig { | ||
| address, | ||
| tls: None, | ||
| access_key: None, | ||
| access_keys: None, | ||
| store_access_key: false, | ||
| record_compression: Compression::None, | ||
| framing: default_framing_message_based(), | ||
| decoding: default_decoding(), | ||
| acknowledgements: true.into(), | ||
| log_namespace: None, | ||
| keepalive: Default::default(), | ||
| permit_origin, | ||
| } | ||
| .build(cx) | ||
| .await | ||
| .unwrap() | ||
| .await | ||
| .unwrap() | ||
| }); | ||
| wait_for_tcp(address).await; | ||
|
|
||
| // Send from localhost — should be accepted by allowlist | ||
| let response = reqwest::Client::new() | ||
| .post(format!("http://{}", address)) | ||
| .header("x-amz-firehose-protocol-version", "1.0") | ||
| .header("x-amz-firehose-request-id", REQUEST_ID) | ||
| .header("x-amz-firehose-source-arn", SOURCE_ARN) | ||
| .header("content-type", "application/json") | ||
| .body(r#"{"requestId":"test","timestamp":1234567890,"records":[{"data":"dGVzdA=="}]}"#) | ||
| .send() | ||
| .await; | ||
|
|
||
| assert!(response.is_ok(), "expected connection to be accepted for allowed IP"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assert the message was received (don't ignore _recv). |
||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.