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 = auth_router
84 .layer(ServiceBuilder::new().layer(AuthMiddlewareLayer::new(token_verifier.clone())));
85
86 let mut router = Router::new();
87 if let Some(wap_control_api) = wap_control_api {
88 let crpc_api_router =
91 crate::pg_wap::crpc::api::nest_crpc_api(Router::new(), wap_control_api)
92 .layer(AuthMiddlewareLayer::new(token_verifier))
93 .layer(
94 CorsLayer::new()
95 .allow_methods(tower_http::cors::Any)
96 .allow_headers(tower_http::cors::Any)
97 .allow_origin(tower_http::cors::AllowOrigin::mirror_request()),
98 );
99
100 router = router.merge(crpc_api_router);
101 }
102
103 router = router.merge(auth_router);
105
106 router = router.layer(
108 ServiceBuilder::new()
109 .layer(HandleErrorLayer::new(|err: BoxError| {
110 async move {
111 tracing::error!(error=%err, "Control plane API error");
112 (
113 StatusCode::INTERNAL_SERVER_ERROR,
114 format!("Unhandled error: {err}"),
115 )
116 }
117 }))
118 .layer(info_trace_layer())
119 .layer(TimeoutLayer::new(CONTROL_PLANE_API_TIMEOUT))
120 .layer(PrometheusMiddlewareLayer::new(metrics)),
121 );
122 Ok(router)
123}
124
125struct UnderlayDiscoveryAdapter<T: UnderlayDiscovery> {
127 underlay_discovery: Arc<T>,
128 snap_cp_api: Url,
129}
130
131impl<T: UnderlayDiscovery> UnderlayDiscoveryAdapter<T> {
132 fn new(underlay_discovery: Arc<T>, snap_cp_api: Url) -> Self {
133 Self {
134 underlay_discovery,
135 snap_cp_api,
136 }
137 }
138}
139
140impl<T: UnderlayDiscovery> endhost_api_models::UnderlayDiscovery for UnderlayDiscoveryAdapter<T> {
141 fn list_underlays(&self, isd_as: IsdAsn) -> Underlays {
142 let dps = self.underlay_discovery.list_udp_underlays();
143 let mut udp_underlay = Vec::new();
144 for dp in dps {
145 for router_as in dp.isd_ases {
146 if isd_as != IsdAsn::WILDCARD && router_as.isd_as != isd_as {
147 continue;
148 };
149
150 udp_underlay.push(ScionRouter {
151 isd_as: router_as.isd_as,
152 internal_interface: dp.endpoint,
153 interfaces: router_as.interfaces.clone(),
154 });
155 }
156 }
157
158 let sus = self.underlay_discovery.list_snap_underlays();
159 if sus.is_empty() {
160 return Underlays {
161 udp_underlay,
162 snap_underlay: Vec::new(),
163 };
164 }
165
166 let mut snap_underlay = Vec::new();
167 let all_ases: Vec<IsdAsn> = sus.iter().flat_map(|su| su.isd_ases.clone()).collect();
168 if isd_as == IsdAsn::WILDCARD || all_ases.contains(&isd_as) {
169 snap_underlay.push(endhost_api_models::underlays::Snap {
170 address: self.snap_cp_api.clone(),
171 isd_ases: all_ases,
172 });
173 }
174
175 Underlays {
176 udp_underlay,
177 snap_underlay,
178 }
179 }
180}