-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
feat(sources): add custom auth strategy for components with HTTP server #22236
base: master
Are you sure you want to change the base?
Conversation
This adds `custom` auth strategy for components with HTTP server (`http_server`, `datadog_agent`, `opentelemetry`, `prometheus`) besides the default basic auth. This is a breaking change because `strategy` is now required for auth - for existing configurations `strategy: "basic"` needs to be added. Related: vectordotdev#22213
I have made this a breaking change, requiring explicit |
Co-authored-by: Esther Kim <[email protected]>
src/common/http/server_auth.rs
Outdated
/// HTTP header without any additional encryption beyond what is provided by the transport itself. | ||
#[configurable_component] | ||
#[derive(Clone, Debug, Eq, PartialEq)] | ||
#[serde(deny_unknown_fields, rename_all = "snake_case", tag = "strategy")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like we have another use case for serde-rs/serde#2231.
Related: #22212 (comment)
💭 Thinking how to avoid breaking behavior for users. I will play locally with a custom deserializer and come back to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to HttpServerAuthConfig
to:
#[configurable_component(no_deser)]
Custom deserializer (mostly AI generated):
// Custom deserializer to default `strategy` to `basic`
impl<'de> Deserialize<'de> for HttpServerAuthConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct HttpServerAuthConfigVisitor;
impl<'de> Visitor<'de> for HttpServerAuthConfigVisitor {
type Value = HttpServerAuthConfig;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid authentication strategy (basic or custom)")
}
fn visit_map<A>(self, mut map: A) -> Result<HttpServerAuthConfig, A::Error>
where
A: MapAccess<'de>,
{
let mut strategy: Option<String> = None;
let mut username: Option<String> = None;
let mut password: Option<String> = None;
let mut source: Option<String> = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"strategy" => {
if strategy.is_some() {
return Err(de::Error::duplicate_field("strategy"));
}
strategy = Some(map.next_value()?);
}
"username" => {
if username.is_some() {
return Err(de::Error::duplicate_field("username"));
}
username = Some(map.next_value()?);
}
"password" => {
if password.is_some() {
return Err(de::Error::duplicate_field("password"));
}
password = Some(map.next_value()?);
}
"source" => {
if source.is_some() {
return Err(de::Error::duplicate_field("source"));
}
source = Some(map.next_value()?);
}
_ => {
return Err(de::Error::unknown_field(
&key,
&["strategy", "username", "password", "source"],
));
}
}
}
// Default to "basic" if strategy is missing
let strategy = strategy.unwrap_or_else(|| "basic".to_string());
match strategy.as_str() {
"basic" => {
let username = username.ok_or_else(|| de::Error::missing_field("username"))?;
let password = password.ok_or_else(|| de::Error::missing_field("password"))?;
Ok(HttpServerAuthConfig::Basic {
username,
password: SensitiveString::from(password),
})
}
"custom" => {
let source = source.ok_or_else(|| de::Error::missing_field("source"))?;
Ok(HttpServerAuthConfig::Custom { source })
}
_ => Err(de::Error::unknown_variant(&strategy, &["basic", "custom"])),
}
}
}
deserializer.deserialize_map(HttpServerAuthConfigVisitor)
}
}
Tried with, config 1:
sources:
s0:
type: http_server
address: 0.0.0.0:80
auth:
# not specifying strategy
username: foo
password: bar
...
..and config 2:
sources:
s0:
type: http_server
address: 0.0.0.0:80
auth:
strategy: custom
source: "true"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @esensar! Happy that we can avoid breaking existing behavior. This PR is almost there.
@@ -124,15 +115,13 @@ pub trait HttpSource: Clone + Send + Sync + 'static { | |||
}) | |||
.untuple_one() | |||
.and(warp::path::full()) | |||
.and(warp::header::optional::<String>("authorization")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this no longer needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Authorization is now handled by handle_auth
which takes in all headers, and they are already picked up below (warp::header::headers_cloned()
).
Summary
This adds
custom
auth strategy for components with HTTP server (http_server
,datadog_agent
,opentelemetry
,prometheus
) besides the default basic auth. This is a breaking change becausestrategy
is now required for auth - for existing configurationsstrategy: "basic"
needs to be added.Change Type
Is this a breaking change?
How did you test this PR?
Besides the tests added to the codebase, I ran basic tests with
http_server
source component:Tested by making calls via curl:
Does this PR include user facing changes?
Checklist
make check-all
is a good command to run locally. This check isdefined here. Some of these
checks might not be relevant to your PR. For Rust changes, at the very least you should run:
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
cargo nextest run --workspace
(alternatively, you can runcargo test --all
)Cargo.lock
), pleaserun
dd-rust-license-tool write
to regenerate the license inventory and commit the changes (if any). More details here.References
Related: #22213