1use crate::state::AppState;
6use axum::{
7 extract::{Path, Query, State},
8 http::StatusCode,
9 response::IntoResponse,
10 Json,
11};
12use serde::{Deserialize, Serialize};
13
14
15#[derive(Deserialize)]
20pub struct UnsealRequest {
21 pub passphrase: String,
22}
23
24#[derive(Deserialize)]
25pub struct SetSecretRequest {
26 pub value: String,
27}
28
29#[derive(Deserialize)]
30pub struct TransitEncryptRequest {
31 pub key_name: String,
32 pub plaintext: String, }
34
35#[derive(Deserialize)]
36pub struct TransitDecryptRequest {
37 pub key_name: String,
38 pub ciphertext: String, }
40
41#[derive(Deserialize)]
42pub struct CreateTransitKeyRequest {
43 pub name: String,
44}
45
46#[derive(Deserialize, Default)]
47pub struct ListQuery {
48 pub prefix: Option<String>,
49 pub limit: Option<usize>,
50}
51
52#[derive(Serialize)]
53pub struct VaultStatusResponse {
54 pub sealed: bool,
55 pub secret_count: usize,
56 pub transit_key_count: usize,
57 pub uptime_secs: Option<u64>,
58}
59
60pub async fn vault_status(State(state): State<AppState>) -> impl IntoResponse {
66 let status = state.vault.status();
67 Json(serde_json::json!({
68 "sealed": status.sealed,
69 "secret_count": status.secret_count,
70 "transit_key_count": status.transit_key_count,
71 "uptime_secs": status.uptime_secs,
72 }))
73}
74
75pub async fn vault_unseal(
77 State(state): State<AppState>,
78 Json(req): Json<UnsealRequest>,
79) -> impl IntoResponse {
80 match state.vault.unseal(&req.passphrase) {
81 Ok(()) => (
82 StatusCode::OK,
83 Json(serde_json::json!({"status": "unsealed"})),
84 ),
85 Err(e) => (
86 StatusCode::FORBIDDEN,
87 Json(serde_json::json!({"error": e.to_string()})),
88 ),
89 }
90}
91
92pub async fn vault_seal(State(state): State<AppState>) -> impl IntoResponse {
94 match state.vault.seal() {
95 Ok(()) => (
96 StatusCode::OK,
97 Json(serde_json::json!({"status": "sealed"})),
98 ),
99 Err(e) => (
100 StatusCode::INTERNAL_SERVER_ERROR,
101 Json(serde_json::json!({"error": e.to_string()})),
102 ),
103 }
104}
105
106pub async fn list_secrets(
108 State(state): State<AppState>,
109 Query(params): Query<ListQuery>,
110) -> impl IntoResponse {
111 let prefix = params.prefix.as_deref().unwrap_or("");
112 match state.vault.list(prefix, "api") {
113 Ok(keys) => (StatusCode::OK, Json(serde_json::json!({"keys": keys}))),
114 Err(e) => (
115 StatusCode::INTERNAL_SERVER_ERROR,
116 Json(serde_json::json!({"error": e.to_string()})),
117 ),
118 }
119}
120
121pub async fn get_secret(
123 State(state): State<AppState>,
124 Path(key): Path<String>,
125) -> impl IntoResponse {
126 match state.vault.get(&key, "api") {
127 Ok(value) => (
128 StatusCode::OK,
129 Json(serde_json::json!({"key": key, "value": value})),
130 ),
131 Err(e) => (
132 StatusCode::NOT_FOUND,
133 Json(serde_json::json!({"error": e.to_string()})),
134 ),
135 }
136}
137
138pub async fn set_secret(
140 State(state): State<AppState>,
141 Path(key): Path<String>,
142 Json(req): Json<SetSecretRequest>,
143) -> impl IntoResponse {
144 match state.vault.set(&key, &req.value, "api") {
145 Ok(()) => (
146 StatusCode::OK,
147 Json(serde_json::json!({"status": "ok", "key": key})),
148 ),
149 Err(e) => (
150 StatusCode::INTERNAL_SERVER_ERROR,
151 Json(serde_json::json!({"error": e.to_string()})),
152 ),
153 }
154}
155
156pub async fn delete_secret(
158 State(state): State<AppState>,
159 Path(key): Path<String>,
160) -> impl IntoResponse {
161 match state.vault.delete(&key, "api") {
162 Ok(()) => (
163 StatusCode::OK,
164 Json(serde_json::json!({"status": "deleted", "key": key})),
165 ),
166 Err(e) => (
167 StatusCode::NOT_FOUND,
168 Json(serde_json::json!({"error": e.to_string()})),
169 ),
170 }
171}
172
173pub async fn transit_encrypt(
175 State(state): State<AppState>,
176 Json(req): Json<TransitEncryptRequest>,
177) -> impl IntoResponse {
178 match state
179 .vault
180 .transit_encrypt(&req.key_name, req.plaintext.as_bytes())
181 {
182 Ok(ciphertext) => {
183 let encoded = data_encoding_hex(&ciphertext);
184 (
185 StatusCode::OK,
186 Json(serde_json::json!({"ciphertext": encoded})),
187 )
188 }
189 Err(e) => (
190 StatusCode::INTERNAL_SERVER_ERROR,
191 Json(serde_json::json!({"error": e.to_string()})),
192 ),
193 }
194}
195
196pub async fn transit_decrypt(
198 State(state): State<AppState>,
199 Json(req): Json<TransitDecryptRequest>,
200) -> impl IntoResponse {
201 match hex_decode(&req.ciphertext) {
202 Ok(ciphertext) => match state.vault.transit_decrypt(&req.key_name, &ciphertext) {
203 Ok(plaintext) => {
204 let text = String::from_utf8_lossy(&plaintext).to_string();
205 (StatusCode::OK, Json(serde_json::json!({"plaintext": text})))
206 }
207 Err(e) => (
208 StatusCode::INTERNAL_SERVER_ERROR,
209 Json(serde_json::json!({"error": e.to_string()})),
210 ),
211 },
212 Err(e) => (
213 StatusCode::BAD_REQUEST,
214 Json(serde_json::json!({"error": format!("Invalid hex: {}", e)})),
215 ),
216 }
217}
218
219pub async fn create_transit_key(
221 State(state): State<AppState>,
222 Json(req): Json<CreateTransitKeyRequest>,
223) -> impl IntoResponse {
224 match state.vault.transit_create_key(&req.name) {
225 Ok(()) => (
226 StatusCode::CREATED,
227 Json(serde_json::json!({"status": "created", "key_name": req.name})),
228 ),
229 Err(e) => (
230 StatusCode::INTERNAL_SERVER_ERROR,
231 Json(serde_json::json!({"error": e.to_string()})),
232 ),
233 }
234}
235
236pub async fn list_transit_keys(State(state): State<AppState>) -> impl IntoResponse {
238 let keys = state.vault.transit_list_keys();
239 Json(serde_json::json!({"keys": keys}))
240}
241
242pub async fn vault_audit(
244 State(state): State<AppState>,
245 Query(params): Query<ListQuery>,
246) -> impl IntoResponse {
247 let limit = params.limit.unwrap_or(100);
248 let entries = state.vault.audit_entries(limit);
249 Json(serde_json::json!({"entries": entries}))
250}
251
252fn data_encoding_hex(data: &[u8]) -> String {
257 data.iter().map(|b| format!("{:02x}", b)).collect()
258}
259
260fn hex_decode(hex: &str) -> Result<Vec<u8>, String> {
261 if hex.len() % 2 != 0 {
262 return Err("odd length".to_string());
263 }
264 (0..hex.len())
265 .step_by(2)
266 .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(|e| e.to_string()))
267 .collect()
268}