warg_server/api/v1/
monitor.rs1use super::{Json, RegistryHeader};
2use crate::datastore::DataStoreError;
3use crate::services::CoreService;
4use axum::http::StatusCode;
5use axum::{debug_handler, extract::State, response::IntoResponse, routing::post, Router};
6use warg_api::v1::monitor::{CheckpointVerificationResponse, MonitorError, VerificationState};
7use warg_crypto::hash::Sha256;
8use warg_protocol::registry::{LogId, TimestampedCheckpoint};
9use warg_protocol::SerdeEnvelope;
10
11#[derive(Clone)]
12pub struct Config {
13 core_service: CoreService,
14}
15
16impl Config {
17 pub fn new(core_service: CoreService) -> Self {
18 Self { core_service }
19 }
20
21 pub fn into_router(self) -> Router {
22 Router::new()
23 .route("/checkpoint", post(verify_checkpoint))
24 .with_state(self)
25 }
26}
27
28struct MonitorApiError(MonitorError);
29
30impl From<DataStoreError> for MonitorApiError {
31 fn from(e: DataStoreError) -> Self {
32 tracing::error!("unexpected data store error: {e}");
33
34 Self(MonitorError::Message {
35 status: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
36 message: "an error occurred while processing the request".into(),
37 })
38 }
39}
40
41impl IntoResponse for MonitorApiError {
42 fn into_response(self) -> axum::response::Response {
43 (StatusCode::from_u16(self.0.status()).unwrap(), Json(self.0)).into_response()
44 }
45}
46
47#[debug_handler]
65async fn verify_checkpoint(
66 State(config): State<Config>,
67 RegistryHeader(_registry_header): RegistryHeader,
68 Json(body): Json<SerdeEnvelope<TimestampedCheckpoint>>,
69) -> Result<Json<CheckpointVerificationResponse>, MonitorApiError> {
70 let (checkpoint_verification, signature_verification) =
72 try_verify_exact_match(&config.core_service, &body).await;
73
74 let signature_verification = if signature_verification == VerificationState::Unverified {
76 match config
77 .core_service
78 .store()
79 .verify_timestamped_checkpoint_signature(&LogId::operator_log::<Sha256>(), &body)
80 .await
81 {
82 Ok(_) => VerificationState::Verified,
83 Err(error) => match error {
84 DataStoreError::UnknownKey(_)
85 | DataStoreError::SignatureVerificationFailed(_)
86 | DataStoreError::KeyUnauthorized(_) => VerificationState::Invalid,
87 _ => return Err(MonitorApiError::from(error)),
88 },
89 }
90 } else {
91 signature_verification
92 };
93
94 Ok(Json(CheckpointVerificationResponse {
95 checkpoint: checkpoint_verification,
96 signature: signature_verification,
97 retry_after: None,
98 }))
99}
100
101async fn try_verify_exact_match(
104 core_service: &CoreService,
105 checkpoint_envelope: &SerdeEnvelope<TimestampedCheckpoint>,
106) -> (VerificationState, VerificationState) {
107 let checkpoint = &checkpoint_envelope.as_ref().checkpoint;
108
109 let found = core_service
111 .store()
112 .get_checkpoint(checkpoint.log_length)
113 .await;
114
115 if let Ok(found_checkpoint_envelope) = found {
116 let found_checkpoint = &found_checkpoint_envelope.as_ref().checkpoint;
117 let log_matches = found_checkpoint.log_root == checkpoint.log_root;
119 let map_matches = found_checkpoint.map_root == checkpoint.map_root;
120
121 let checkpoint_verification = if log_matches && map_matches {
124 VerificationState::Verified
125 } else {
126 VerificationState::Invalid
127 };
128
129 let signature_matches =
131 found_checkpoint_envelope.signature() == checkpoint_envelope.signature();
132 let key_id_matches = found_checkpoint_envelope.key_id() == checkpoint_envelope.key_id();
133
134 let signature_verification = if signature_matches && key_id_matches {
137 VerificationState::Verified
138 } else {
139 VerificationState::Unverified
140 };
141
142 (checkpoint_verification, signature_verification)
143 } else {
144 (VerificationState::Invalid, VerificationState::Unverified)
145 }
146}