1use std::{sync::Arc, time::Duration};
17
18use axum::{BoxError, Router, error_handling::HandleErrorLayer};
19use endhost_api::routes::nest_endhost_api;
20use endhost_api_models::{
21 SegmentsDiscovery,
22 underlays::{ScionRouter, Underlays},
23};
24use http::StatusCode;
25use scion_sdk_observability::info_trace_layer;
26use sciparse::identifier::isd_asn::IsdAsn;
27use tower::{ServiceBuilder, timeout::TimeoutLayer};
28use tower_http::cors::CorsLayer;
29use url::Url;
30
31use crate::{
32 api::crpc::{
33 model::{SnapDataPlaneResolver, SnapTunIdentityRegistry},
34 nest_crpc_api,
35 },
36 model::UnderlayDiscovery,
37 pg_wap::crpc::model::ControlServiceAPIHandler,
38 server::{
39 auth::AuthMiddlewareLayer,
40 metrics::{Metrics, PrometheusMiddlewareLayer},
41 },
42};
43
44pub mod auth;
45pub mod identity_registry;
46pub mod jwks_key_store;
47pub mod metrics;
48pub mod state;
49pub mod token_verifier;
50
51pub use token_verifier::SnapTokenVerifier;
52
53const CONTROL_PLANE_API_TIMEOUT: Duration = Duration::from_secs(30);
54
55pub fn build_router<UD, SL, SR, IR>(
57 underlay_discovery: UD,
58 snap_cp_api: Url,
59 segment_lister: SL,
60 snap_resolver: SR,
61 identity_registry: Arc<IR>,
62 wap_control_api: Option<Arc<dyn ControlServiceAPIHandler>>,
63 token_verifier: SnapTokenVerifier,
64 metrics: Metrics,
65) -> std::io::Result<Router>
66where
67 UD: UnderlayDiscovery + 'static + Send + Sync,
68 SL: SegmentsDiscovery + 'static + Send + Sync,
69 SR: SnapDataPlaneResolver + 'static + Send + Sync,
70 IR: SnapTunIdentityRegistry + 'static + Send + Sync,
71{
72 let mut auth_router = Router::new();
74 auth_router = nest_endhost_api(
75 auth_router,
76 Arc::new(UnderlayDiscoveryAdapter::new(
77 Arc::new(underlay_discovery),
78 snap_cp_api,
79 )),
80 Arc::new(segment_lister),
81 );
82 auth_router = nest_crpc_api(auth_router, Arc::new(snap_resolver), identity_registry);
83 auth_router =
84 auth_router.layer(ServiceBuilder::new().layer(AuthMiddlewareLayer::new(token_verifier)));
85
86 let mut router = Router::new();
88 if let Some(wap_control_api) = wap_control_api {
91 let crpc_api_router =
97 crate::pg_wap::crpc::api::nest_crpc_api(Router::new(), wap_control_api).layer(
98 CorsLayer::new()
99 .allow_methods(tower_http::cors::Any)
100 .allow_headers(tower_http::cors::Any)
101 .allow_origin(tower_http::cors::AllowOrigin::mirror_request()),
102 );
103
104 router = router.merge(crpc_api_router);
105 }
106
107 router = router.merge(auth_router);
109
110 router = router.layer(
112 ServiceBuilder::new()
113 .layer(HandleErrorLayer::new(|err: BoxError| {
114 async move {
115 tracing::error!(error=%err, "Control plane API error");
116 (
117 StatusCode::INTERNAL_SERVER_ERROR,
118 format!("Unhandled error: {err}"),
119 )
120 }
121 }))
122 .layer(info_trace_layer())
123 .layer(TimeoutLayer::new(CONTROL_PLANE_API_TIMEOUT))
124 .layer(PrometheusMiddlewareLayer::new(metrics)),
125 );
126 Ok(router)
127}
128
129struct UnderlayDiscoveryAdapter<T: UnderlayDiscovery> {
131 underlay_discovery: Arc<T>,
132 snap_cp_api: Url,
133}
134
135impl<T: UnderlayDiscovery> UnderlayDiscoveryAdapter<T> {
136 fn new(underlay_discovery: Arc<T>, snap_cp_api: Url) -> Self {
137 Self {
138 underlay_discovery,
139 snap_cp_api,
140 }
141 }
142}
143
144impl<T: UnderlayDiscovery> endhost_api_models::UnderlayDiscovery for UnderlayDiscoveryAdapter<T> {
145 fn list_underlays(&self, isd_as: IsdAsn) -> Underlays {
146 let dps = self.underlay_discovery.list_udp_underlays();
147 let mut udp_underlay = Vec::new();
148 for dp in dps {
149 for router_as in dp.isd_ases {
150 if isd_as != IsdAsn::WILDCARD && router_as.isd_as != isd_as {
151 continue;
152 };
153
154 udp_underlay.push(ScionRouter {
155 isd_as: router_as.isd_as,
156 internal_interface: dp.endpoint,
157 interfaces: router_as.interfaces.clone(),
158 });
159 }
160 }
161
162 let sus = self.underlay_discovery.list_snap_underlays();
163 if sus.is_empty() {
164 return Underlays {
165 udp_underlay,
166 snap_underlay: Vec::new(),
167 };
168 }
169
170 let mut snap_underlay = Vec::new();
171 let all_ases: Vec<IsdAsn> = sus.iter().flat_map(|su| su.isd_ases.clone()).collect();
172 if isd_as == IsdAsn::WILDCARD || all_ases.contains(&isd_as) {
173 snap_underlay.push(endhost_api_models::underlays::Snap {
174 address: self.snap_cp_api.clone(),
175 isd_ases: all_ases,
176 });
177 }
178
179 Underlays {
180 udp_underlay,
181 snap_underlay,
182 }
183 }
184}