1use crate::token::{verify as verify_token, TokenBuilder};
9use crate::vault::{Vault, VaultError};
10use std::net::SocketAddr;
11use std::sync::Arc;
12use tonic::{transport::Server, Request, Response, Status};
13use zeroize::Zeroizing;
14
15pub mod proto {
16 tonic::include_proto!("agentid.v1");
17}
18
19use proto::agent_id_service_server::{AgentIdService, AgentIdServiceServer};
20use proto::*;
21
22pub struct AgentIdImpl {
23 vault: Arc<Vault>,
24 password: Arc<Zeroizing<String>>,
25}
26
27impl AgentIdImpl {
28 pub fn new(vault: Vault, password: String) -> Self {
29 Self {
30 vault: Arc::new(vault),
31 password: Arc::new(Zeroizing::new(password)),
32 }
33 }
34}
35
36#[tonic::async_trait]
37impl AgentIdService for AgentIdImpl {
38 async fn mint_token(
39 &self,
40 req: Request<MintTokenRequest>,
41 ) -> Result<Response<MintTokenResponse>, Status> {
42 let r = req.into_inner();
43 let identity = self
44 .vault
45 .load(&r.fingerprint, self.password.as_str())
46 .map_err(map_vault_err)?;
47 let token = TokenBuilder::new(&identity)
48 .scopes(r.scopes)
49 .ttl_seconds(if r.ttl_seconds == 0 { 900 } else { r.ttl_seconds })
50 .max_calls(r.max_calls)
51 .build()
52 .map_err(|e| Status::invalid_argument(format!("mint failed: {e}")))?;
53 Ok(Response::new(MintTokenResponse {
54 token,
55 fingerprint: identity.fingerprint(),
56 }))
57 }
58
59 async fn verify_token(
60 &self,
61 req: Request<VerifyTokenRequest>,
62 ) -> Result<Response<VerifyTokenResponse>, Status> {
63 let r = req.into_inner();
64 let expected_pk = match r.expected_pubkey.len() {
65 0 => None,
66 32 => {
67 let mut a = [0u8; 32];
68 a.copy_from_slice(&r.expected_pubkey);
69 Some(a)
70 }
71 n => return Err(Status::invalid_argument(format!("expected_pubkey must be 32 bytes, got {n}"))),
72 };
73 match verify_token(&r.token, expected_pk.as_ref()) {
74 Ok(claims) => Ok(Response::new(VerifyTokenResponse {
75 valid: true,
76 error: String::new(),
77 name: claims.name.clone(),
78 project: claims.project.clone(),
79 scopes: claims.scopes.clone(),
80 issued_at: claims.issued_at,
81 expires_at: claims.expires_at,
82 max_calls: claims.max_calls,
83 issuer: claims.issuer.to_vec(),
84 fingerprint: claims.fingerprint(),
85 })),
86 Err(e) => Ok(Response::new(VerifyTokenResponse {
87 valid: false,
88 error: format!("{e}"),
89 ..Default::default()
90 })),
91 }
92 }
93
94 async fn list_identities(
95 &self,
96 _req: Request<ListIdentitiesRequest>,
97 ) -> Result<Response<ListIdentitiesResponse>, Status> {
98 let entries = self.vault.list().map_err(map_vault_err)?;
99 let identities = entries
100 .into_iter()
101 .map(|e| Identity {
102 name: e.name,
103 project: e.project,
104 fingerprint: e.fingerprint,
105 public_key: e.public_key,
106 created_at: e.created_at,
107 })
108 .collect();
109 Ok(Response::new(ListIdentitiesResponse { identities }))
110 }
111
112 async fn health(
113 &self,
114 _req: Request<HealthRequest>,
115 ) -> Result<Response<HealthResponse>, Status> {
116 Ok(Response::new(HealthResponse {
117 status: "ok".into(),
118 version: crate::VERSION.into(),
119 }))
120 }
121}
122
123fn map_vault_err(e: VaultError) -> Status {
124 match e {
125 VaultError::NotFound(s) => Status::not_found(s),
126 VaultError::DecryptionFailed => Status::permission_denied("vault password incorrect"),
127 VaultError::NotInitialized(p) => {
128 Status::failed_precondition(format!("vault not initialized at {}", p.display()))
129 }
130 e => Status::internal(format!("{e}")),
131 }
132}
133
134pub async fn serve(
137 addr: SocketAddr,
138 vault: Vault,
139 password: String,
140) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
141 let svc = AgentIdImpl::new(vault, password);
142 eprintln!(
143 "agentid-server v{} listening on {addr}",
144 crate::VERSION
145 );
146 Server::builder()
147 .add_service(AgentIdServiceServer::new(svc))
148 .serve(addr)
149 .await?;
150 Ok(())
151}