fix(service-streams): register the enterprise routes in OSS so they answer 403 instead of 404

This commit is contained in:
Bhargav
2026-11-04 00:07:45 +05:30
parent cd1264bfd1
commit b46c2a70ca
4 changed files with 65 additions and 13 deletions
+63 -10
View File
@@ -33,8 +33,8 @@ use openobserve_api_management::request::profiling;
use openobserve_api_management::request::{
alerts, announcements, authz, dashboards, db_monitoring, downtimes, folders, gen_ai_health,
ingest_rejections, kv, metrics_usage, model_pricing, organization, public_dashboards,
query_history, rum_analytics, service_accounts, short_url, slack, slos, sourcemaps, status,
status_pages, stream, synthetics, users,
query_history, rum_analytics, service_accounts, service_streams, short_url, slack, slos,
sourcemaps, status, status_pages, stream, synthetics, users,
};
#[cfg(feature = "cloud")]
use openobserve_api_management::request::{cloud, organization::billing_group};
@@ -65,8 +65,7 @@ use {
openobserve_api_management::request::{
ai, alert_calibration, annotation_queues, annotations, anomaly_detection, datasets,
discovery, domain_management, eval_jobs, experiments, fleet, gen_ai, keys, license, oncall,
playground, prompts, providers, quality, remote_tasks, score_configs, scorers,
service_streams, workflows,
playground, prompts, providers, quality, remote_tasks, score_configs, scorers, workflows,
},
openobserve_api_pipelines::request::re_pattern,
openobserve_api_search::search::patterns,
@@ -1215,9 +1214,13 @@ pub fn service_routes() -> Router {
#[cfg(not(feature = "enterprise"))]
let server = cfg.common.instance_name_short.to_string();
// Downtimes and Slack live in their own tables to keep this function under the line limit.
let mut router =
public_dashboard_admin_routes(Router::new().merge(downtime_routes()).merge(slack_routes()));
// Downtimes, Slack and service streams live in their own tables to keep this function under the line limit.
let mut router = public_dashboard_admin_routes(
Router::new()
.merge(downtime_routes())
.merge(slack_routes())
.merge(service_streams_routes()),
);
// Full UI configuration — authenticated counterpart of the unauthenticated
// `/config` bootstrap in config_routes()
router = router.route("/{org_id}/config", get(status::zo_config));
@@ -1909,10 +1912,7 @@ pub fn service_routes() -> Router {
// Service streams
.route("/{org_id}/service_streams", get(service_streams::list_services))
.route("/{org_id}/service_streams/_analytics", get(service_streams::get_dimension_analytics))
.route("/{org_id}/service_streams/_correlate", post(service_streams::correlate_streams))
.route("/{org_id}/service_streams/config/identity", get(service_streams::get_identity_config).put(service_streams::save_identity_config))
.route("/{org_id}/service_streams/_reset", delete(service_streams::reset_services))
.route("/{org_id}/storage",get(organization::storage::get).post(organization::storage::save).put(organization::storage::update));
router = ai_chat_routes(router);
@@ -2287,6 +2287,15 @@ pub fn service_routes() -> Router {
}))
}
/// Registered in both builds so OSS answers the handlers' 403 enterprise-only stub, not a 404.
#[rustfmt::skip]
fn service_streams_routes() -> Router {
Router::new()
.route("/{org_id}/service_streams/_analytics", get(service_streams::get_dimension_analytics))
.route("/{org_id}/service_streams/_correlate", post(service_streams::correlate_streams))
.route("/{org_id}/service_streams/_reset", delete(service_streams::reset_services))
}
/// Downtimes routes; the literal segments stay before the {downtime_id} catch-all.
#[rustfmt::skip]
pub fn downtime_routes() -> Router {
@@ -3138,6 +3147,42 @@ mod tests {
}
}
#[cfg(not(feature = "enterprise"))]
#[tokio::test]
async fn service_streams_routes_answer_403_not_404_in_the_oss_build() {
let routes = [
(
Method::GET,
"/default/service_streams/_analytics",
Body::empty(),
),
(
Method::POST,
"/default/service_streams/_correlate",
Body::from(
r#"{"source_stream":"s","source_type":"logs","available_dimensions":{}}"#,
),
),
(
Method::DELETE,
"/default/service_streams/_reset",
Body::empty(),
),
];
for (method, uri, body) in routes {
let what = format!("{method} {uri}");
let req = Request::builder()
.method(method)
.uri(uri)
.header("user_id", "root@example.com")
.header("content-type", "application/json")
.body(body)
.unwrap();
let response = service_streams_routes().oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::FORBIDDEN, "{what}");
}
}
#[cfg(feature = "enterprise")]
#[tokio::test]
async fn a_bad_body_or_query_is_404_while_slack_is_off() {
@@ -4950,6 +4995,14 @@ mod tests {
.replace(",)", ")")
}
#[test]
fn service_streams_routes_are_merged_unconditionally_in_service_routes() {
assert!(
service_routes_registrations().contains(".merge(service_streams_routes())"),
"service_routes() must merge service_streams_routes() outside the enterprise block"
);
}
#[test]
fn metric_usage_route_is_registered_in_service_routes() {
assert!(
@@ -311,6 +311,7 @@ use crate::{
service_streams::correlate_streams,
service_streams::get_identity_config,
service_streams::save_identity_config,
service_streams::reset_services,
gen_ai::clear_agent_registry,
gen_ai::get_agent_mapping,
gen_ai::list_scored_agents,
@@ -1366,7 +1367,6 @@ pub struct ApiDoc;
openobserve_api_management::request::scorers::preview_llm_judge_output_schema,
openobserve_api_management::request::scorers::test_scorer,
openobserve_api_management::request::scorers::update_scorer,
openobserve_api_management::request::service_streams::reset_services,
openobserve_api_management::request::synthetics::agent_setup,
openobserve_api_management::request::users::service_accounts::exchange_token,
openobserve_api_pipelines::request::re_pattern::delete,
@@ -510,8 +510,6 @@ oss route POST /api/{org_id}/search_jobs/{job_id}/cancel
oss route GET /api/{org_id}/search_jobs/{job_id}/result
oss route POST /api/{org_id}/search_jobs/{job_id}/retry
oss route GET /api/{org_id}/service_streams
oss route GET /api/{org_id}/service_streams/_analytics
oss route POST /api/{org_id}/service_streams/_correlate
oss route GET /api/{org_id}/service_streams/config/identity
oss route PUT /api/{org_id}/service_streams/config/identity
oss route GET /api/{org_id}/settings/gen_ai/agent_mapping
@@ -64,6 +64,7 @@ const ROUTER_PREFIXES: &[(&str, &str)] = &[
("oncall_routes", "/api"),
("fleet_router", "/api"),
("slack_routes", "/api"),
("service_streams_routes", "/api"),
("es_compat_routes", "/api"),
("workflow_hook_routes", ""),
];