1use std::{
17 sync::Arc,
18 time::{Instant, SystemTime},
19};
20
21use axum::{
22 Extension, Router,
23 extract::{ConnectInfo, State},
24};
25use axum_connect_rpc::{
26 error::{CrpcError, CrpcErrorCode},
27 extractor::ConnectRpc,
28};
29use scion_sdk_token_validator::validator::Token;
30use snap_tokens::AnyClaims;
31use x25519_dalek::PublicKey;
32
33use crate::{
34 api::crpc::model::{SnapDataPlaneResolver, SnapTunIdentityRegistry},
35 proto::anapaya::snap::v1::{
36 GetSnapDataPlaneAddressRequest, GetSnapDataPlaneAddressResponse,
37 RegisterSnapTunIdentityRequest, RegisterSnapTunIdentityResponse,
38 },
39};
40
41pub mod model {
43 use std::{
44 net::{IpAddr, SocketAddr},
45 time::{Duration, Instant},
46 };
47
48 use axum::http::StatusCode;
49 use snap_tokens::AnyClaims;
50 use url::Url;
51 use x25519_dalek::PublicKey;
52
53 pub trait SnapDataPlaneResolver: Send + Sync {
55 fn get_data_plane_address(
57 &self,
58 endhost_ip: IpAddr,
59 ) -> Result<SnapDataPlane, (StatusCode, anyhow::Error)>;
60 }
61
62 pub struct SnapDataPlane {
64 pub address: SocketAddr,
67 pub snap_tun_control_address: Option<Url>,
71 pub snap_static_x25519: Option<PublicKey>,
74 }
75
76 pub trait SnapTunIdentityRegistry: Send + Sync {
78 fn register(
88 &self,
89 now: Instant,
90 key: &str,
92 initiator_identity: [u8; 32],
94 psk_share: Option<[u8; 32]>,
96 lifetime: Duration,
99 claims: &AnyClaims,
101 ) -> anyhow::Result<bool>;
102
103 fn remove_expired(&self, now: Instant);
109 }
110}
111
112pub(crate) mod convert {
113 use std::net::{AddrParseError, SocketAddr};
114
115 use url::Url;
116 use x25519_dalek::PublicKey;
117
118 use crate::{api::crpc::model::SnapDataPlane, proto::anapaya::snap::v1 as rpc};
119
120 #[derive(thiserror::Error, Debug)]
122 pub enum ConvertError {
123 #[error("failed to parse data plane address: {0}")]
124 ParseAddr(AddrParseError),
125 #[error("failed to parse server control address: {0}")]
126 ParseSnapTunControlAddr(AddrParseError),
127 #[error("server static identity is not 32 bytes")]
128 InvalidServerStaticIdentityLength,
129 }
130
131 impl TryFrom<rpc::GetSnapDataPlaneAddressResponse> for SnapDataPlane {
133 type Error = ConvertError;
134 fn try_from(value: rpc::GetSnapDataPlaneAddressResponse) -> Result<Self, Self::Error> {
135 let snap_tun_control_address = value
136 .snap_tun_control_address
137 .map(|address| {
138 if let Ok(url) = Url::parse(&address) {
140 return Ok(url);
141 }
142 match address.parse::<SocketAddr>() {
143 Ok(addr) => {
144 let mut u = Url::parse("http://.").unwrap();
145 let _ = u.set_ip_host(addr.ip());
146 let _ = u.set_port(Some(addr.port()));
147 Ok(u)
148 }
149 Err(e) => Err(ConvertError::ParseSnapTunControlAddr(e)),
150 }
151 })
152 .transpose()?;
153 let snap_static_x25519 = value
154 .snap_static_x25519
155 .map(|key| {
156 TryInto::<[u8; 32]>::try_into(key.as_slice())
157 .map_err(|_| ConvertError::InvalidServerStaticIdentityLength)
158 .map(PublicKey::from)
159 })
160 .transpose()?;
161 Ok(SnapDataPlane {
162 address: value.address.parse().map_err(ConvertError::ParseAddr)?,
163 snap_tun_control_address,
164 snap_static_x25519,
165 })
166 }
167 }
168}
169
170pub(crate) const SERVICE_PATH: &str = "/anapaya.snap.v1.SnapControl";
171pub(crate) const GET_SNAP_DATA_PLANE_ADDRESS: &str = "/GetSnapDataPlaneAddress";
172pub(crate) const REGISTER_SNAPTUN_IDENTITY: &str = "/RegisterSnapTunIdentity";
173
174pub fn nest_crpc_api(
176 router: axum::Router,
177 snap_resolver: Arc<dyn SnapDataPlaneResolver>,
178 identity_registrar: Arc<dyn SnapTunIdentityRegistry>,
179) -> axum::Router {
180 router.nest(
181 SERVICE_PATH,
182 Router::new()
183 .route(
184 GET_SNAP_DATA_PLANE_ADDRESS,
185 axum::routing::post(get_snap_data_plane_address_handler),
186 )
187 .with_state(snap_resolver)
188 .route(
189 REGISTER_SNAPTUN_IDENTITY,
190 axum::routing::post(register_snaptun_identity_handler),
191 )
192 .with_state(identity_registrar),
193 )
194}
195
196async fn get_snap_data_plane_address_handler(
197 State(rendezvous_hasher): State<Arc<dyn SnapDataPlaneResolver>>,
198 _snap_token: Extension<AnyClaims>,
199 ConnectInfo(addr): ConnectInfo<std::net::SocketAddr>,
200 ConnectRpc(_request): ConnectRpc<GetSnapDataPlaneAddressRequest>,
201) -> Result<ConnectRpc<GetSnapDataPlaneAddressResponse>, CrpcError> {
202 let addr = rendezvous_hasher.get_data_plane_address(addr.ip())?;
203 Ok(ConnectRpc(GetSnapDataPlaneAddressResponse {
204 address: addr.address.to_string(),
205 snap_tun_control_address: addr
206 .snap_tun_control_address
207 .map(|address| address.to_string()),
208 snap_static_x25519: addr.snap_static_x25519.map(|key| key.to_bytes().to_vec()),
209 }))
210}
211
212async fn register_snaptun_identity_handler(
213 State(identity_registry): State<Arc<dyn SnapTunIdentityRegistry>>,
214 snap_token: Extension<AnyClaims>,
215 ConnectInfo(_): ConnectInfo<std::net::SocketAddr>,
216 ConnectRpc(request): ConnectRpc<RegisterSnapTunIdentityRequest>,
217) -> Result<ConnectRpc<RegisterSnapTunIdentityResponse>, CrpcError> {
218 let now = SystemTime::now();
219 let lifetime = snap_token.0.exp_time().duration_since(now).map_err(|_| {
220 CrpcError::new(
221 CrpcErrorCode::InvalidArgument,
222 "expiration time is in the past".to_string(),
223 )
224 })?;
225
226 let initiator_identity = {
227 let key_bytes: [u8; 32] = request
228 .initiator_static_x25519
229 .as_slice()
230 .try_into()
231 .map_err(|_| {
232 CrpcError::new(
233 CrpcErrorCode::InvalidArgument,
234 "initiator identity is not 32 bytes".to_string(),
235 )
236 })?;
237 PublicKey::from(key_bytes)
238 };
239
240 let psk_share: Option<[u8; 32]> = if request.psk_share.as_slice() == [0u8; 32] {
241 None
242 } else {
243 Some(request.psk_share.as_slice().try_into().map_err(|_| {
244 CrpcError::new(
245 CrpcErrorCode::InvalidArgument,
246 "psk share is not 32 bytes".to_string(),
247 )
248 })?)
249 };
250
251 let key = snap_token.jti();
252 if !identity_registry
253 .register(
254 Instant::now(),
255 &key,
256 *initiator_identity.as_bytes(),
257 psk_share,
258 lifetime,
259 &snap_token,
260 )
261 .map_err(|err| CrpcError::new(CrpcErrorCode::InvalidArgument, err.to_string()))?
262 {
263 tracing::info!(key, "re-registered identity");
264 }
265 Ok(ConnectRpc(RegisterSnapTunIdentityResponse {
266 psk_share: [0u8; 32].to_vec(),
268 }))
269}