mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-19 05:07:20 +02:00
Lot 6 : clients Rust du chiffrement et de l'exécution des outils
Sceller / ouvrir un blob par le Go (mémoire verrouillée : erreur dédiée, rien d'écrit en clair) et faire exécuter un appel d'outil par sa boucle — les deux ponts dont la conversation Rust aura besoin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c38b1ac43e
commit
461ef8030d
3 files changed
+121
No files matched your search
@@ -86,6 +86,46 @@ impl Store {
|
|||||||
serde_json::from_slice(&bytes).map_err(|e| e.to_string())
|
serde_json::from_slice(&bytes).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scelle un blob avec la clé de la mémoire (gardée par le Go). Clair
|
||||||
|
/// rendu tel quel si le chiffrement est coupé.
|
||||||
|
pub async fn seal(&self, plain: Vec<u8>) -> Result<Vec<u8>, CryptError> {
|
||||||
|
self.crypt("/internal/v1/crypt/seal", plain).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ouvre un blob (un contenu non chiffré passe tel quel).
|
||||||
|
pub async fn open(&self, raw: Vec<u8>) -> Result<Vec<u8>, CryptError> {
|
||||||
|
self.crypt("/internal/v1/crypt/open", raw).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn crypt(&self, path: &str, body: Vec<u8>) -> Result<Vec<u8>, CryptError> {
|
||||||
|
match self.call(Method::POST, path, body).await {
|
||||||
|
Ok((StatusCode::OK, b)) => Ok(b.to_vec()),
|
||||||
|
Ok((StatusCode::LOCKED, _)) => Err(CryptError::Locked),
|
||||||
|
Ok((s, b)) => Err(CryptError::Other(format!(
|
||||||
|
"{path} : {s} {}",
|
||||||
|
String::from_utf8_lossy(&b)
|
||||||
|
))),
|
||||||
|
Err(e) => Err(CryptError::Other(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exécute un appel d'outil par le Go (`/internal/v1/tool/exec`) : même
|
||||||
|
/// fonction que sa boucle de chat. `caps` : objet JSON des capacités du
|
||||||
|
/// tour (champs Go : Agent, Internet, Mem, Code, Role, ComputerUse).
|
||||||
|
pub async fn tool_exec(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
args: &serde_json::Value,
|
||||||
|
caps: &serde_json::Value,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
self.internal_json(
|
||||||
|
Method::POST,
|
||||||
|
"/internal/v1/tool/exec",
|
||||||
|
Some(&serde_json::json!({ "name": name, "args": args, "caps": caps })),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Valeur brute ; `Ok(None)` = absente, `Err` = pas pu lire (à distinguer
|
/// Valeur brute ; `Ok(None)` = absente, `Err` = pas pu lire (à distinguer
|
||||||
/// pour les secrets : comme `getBytesErr`).
|
/// pour les secrets : comme `getBytesErr`).
|
||||||
pub async fn get(&self, bucket: &str, key: &str) -> Result<Option<Vec<u8>>, String> {
|
pub async fn get(&self, bucket: &str, key: &str) -> Result<Option<Vec<u8>>, String> {
|
||||||
@@ -136,6 +176,14 @@ impl Store {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Échec de chiffrement / déchiffrement.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum CryptError {
|
||||||
|
/// Mémoire verrouillée : attendre le déverrouillage, ne rien écrire.
|
||||||
|
Locked,
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
/// Encodage d'un paramètre de requête (RFC 3986, caractères non réservés
|
/// Encodage d'un paramètre de requête (RFC 3986, caractères non réservés
|
||||||
/// conservés).
|
/// conservés).
|
||||||
pub fn enc(s: &str) -> String {
|
pub fn enc(s: &str) -> String {
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ pub struct FakeGo {
|
|||||||
pub side_live: Mutex<bool>,
|
pub side_live: Mutex<bool>,
|
||||||
pub refuse: Mutex<Option<serde_json::Value>>,
|
pub refuse: Mutex<Option<serde_json::Value>>,
|
||||||
pub oai_log: Mutex<Vec<String>>,
|
pub oai_log: Mutex<Vec<String>>,
|
||||||
|
/// Mémoire verrouillée : seal/open répondent 423.
|
||||||
|
pub locked: Mutex<bool>,
|
||||||
|
/// Adresse d'écoute du faux Go (pour un client `Store` direct).
|
||||||
|
pub backend: Mutex<Option<SocketAddr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FakeGo {
|
impl FakeGo {
|
||||||
@@ -101,6 +105,29 @@ async fn handle(st: Arc<FakeGo>, req: Request<Incoming>) -> Response<Full<Bytes>
|
|||||||
let key = st.get("state", "web_key").unwrap_or_default();
|
let key = st.get("state", "web_key").unwrap_or_default();
|
||||||
resp(200, format!(r#"{{"web_key":"{key}","version":"0.15.0"}}"#))
|
resp(200, format!(r#"{{"web_key":"{key}","version":"0.15.0"}}"#))
|
||||||
}
|
}
|
||||||
|
"/internal/v1/crypt/seal" | "/internal/v1/crypt/open" => {
|
||||||
|
if *st.locked.lock().unwrap() {
|
||||||
|
return resp(423, r#"{"error":"mémoire verrouillée"}"#);
|
||||||
|
}
|
||||||
|
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||||
|
if path.ends_with("seal") {
|
||||||
|
let mut out = b"SCELLE:".to_vec();
|
||||||
|
out.extend_from_slice(&body);
|
||||||
|
resp(200, out)
|
||||||
|
} else {
|
||||||
|
resp(200, body.strip_prefix(b"SCELLE:").unwrap_or(&body).to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"/internal/v1/tool/exec" => {
|
||||||
|
let body = req.into_body().collect().await.unwrap().to_bytes();
|
||||||
|
let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||||
|
let out = serde_json::json!({
|
||||||
|
"label": v["args"]["command"],
|
||||||
|
"result": format!("exec:{}", v["name"].as_str().unwrap()),
|
||||||
|
"caps_agent": v["caps"]["Agent"],
|
||||||
|
});
|
||||||
|
resp(200, out.to_string())
|
||||||
|
}
|
||||||
"/internal/v1/oai/route" => {
|
"/internal/v1/oai/route" => {
|
||||||
let key = st.get("state", "api_key").unwrap_or_default();
|
let key = st.get("state", "api_key").unwrap_or_default();
|
||||||
let v = serde_json::json!({
|
let v = serde_json::json!({
|
||||||
@@ -188,6 +215,7 @@ pub async fn start(home: std::path::PathBuf) -> (SocketAddr, Arc<FakeGo>, onesho
|
|||||||
let st = Arc::new(FakeGo::default());
|
let st = Arc::new(FakeGo::default());
|
||||||
let l = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
let l = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
let backend = l.local_addr().unwrap();
|
let backend = l.local_addr().unwrap();
|
||||||
|
*st.backend.lock().unwrap() = Some(backend);
|
||||||
let st2 = st.clone();
|
let st2 = st.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Ok((s, _)) = l.accept().await {
|
while let Ok((s, _)) = l.accept().await {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//! Lot 6 : clients Rust des API internes du chat (chiffrement, outils).
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::fakego;
|
||||||
|
use loki_rs::store::{CryptError, Store};
|
||||||
|
|
||||||
|
async fn store() -> (
|
||||||
|
Store,
|
||||||
|
std::sync::Arc<fakego::FakeGo>,
|
||||||
|
tokio::sync::oneshot::Sender<()>,
|
||||||
|
tempfile::TempDir,
|
||||||
|
) {
|
||||||
|
let t = tempfile::tempdir().unwrap();
|
||||||
|
let (_f, go, stop) = fakego::start(t.path().into()).await;
|
||||||
|
let backend = go.backend.lock().unwrap().unwrap();
|
||||||
|
(Store::new(backend, fakego::JETON.into()), go, stop, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sceller_ouvrir_et_verrou() {
|
||||||
|
let (st, go, _stop, _t) = store().await;
|
||||||
|
let sealed = st.seal(b"bonjour".to_vec()).await.unwrap();
|
||||||
|
assert_eq!(sealed, b"SCELLE:bonjour");
|
||||||
|
assert_eq!(st.open(sealed).await.unwrap(), b"bonjour");
|
||||||
|
*go.locked.lock().unwrap() = true;
|
||||||
|
assert_eq!(st.seal(b"x".to_vec()).await, Err(CryptError::Locked));
|
||||||
|
assert_eq!(st.open(b"x".to_vec()).await, Err(CryptError::Locked));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn outil_execute_par_le_go() {
|
||||||
|
let (st, _go, _stop, _t) = store().await;
|
||||||
|
let v = st
|
||||||
|
.tool_exec(
|
||||||
|
"bash",
|
||||||
|
&serde_json::json!({ "command": "ls" }),
|
||||||
|
&serde_json::json!({ "Agent": true }),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(v["result"], "exec:bash");
|
||||||
|
assert_eq!(v["label"], "ls");
|
||||||
|
assert_eq!(v["caps_agent"], true);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user