Skip to main content

KeyStore

Struct KeyStore 

Source
pub struct KeyStore { /* private fields */ }
Expand description

KeyStore:热可替换的 keys 集合

Implementations§

Source§

impl KeyStore

Source

pub fn empty() -> Self

空 store(没有 keys 文件时)

Source

pub fn load(path: impl Into<PathBuf>) -> Result<Self, KeyStoreError>

从文件加载

Source

pub fn reload(&self) -> Result<(), KeyStoreError>

SIGHUP 热重载:用同一路径重新读文件

Source

pub fn expand_allowed_card_nums<R, FU, FA>( &self, resolver: R, unresolved_callback: FU, ambiguous_callback: FA, ) -> (usize, usize, usize)
where R: Fn(&str) -> Vec<u64>, FU: FnMut(&str, &str), FA: FnMut(&str, &str, &[u64]),

v1.4.103 (B10): 把每条 key 的 allowed_card_nums (string format) 通过 resolver 解析成 acc_id, 合并allowed_acc_ids (in-memory only, 不写回 keys.json — 文件源不变, 重载后再 expand).

resolver(card_num) -> Vec<u64> 由 caller 提供 (典型 closure 持 Arc<TrdCache>find_acc_ids_by_card_num).

行为:

  • resolver 返 1 个 acc_id → 加入 allowed_acc_ids (resolved)
  • 返 0 个 → 通过 unresolved_callback 通知 caller (e.g. log warn)
  • 返 ≥ 2 个 → 通过 ambiguous_callback 通知 caller (loud, skip 该条)

(resolved_count, unresolved_count, ambiguous_count).

典型调用 (daemon 启动 GetAccList 成功后):

let cache_clone = trd_cache.clone();
key_store.expand_allowed_card_nums(
    |cn: &str| cache_clone.find_acc_ids_by_card_num(cn),
    |key_id, cn| tracing::warn!(key_id, card_num=cn, "card_num not found"),
    |key_id, cn, candidates| tracing::warn!(key_id, card_num=cn, ?candidates, "ambiguous card_num"),
);
Source

pub fn verify(&self, plaintext: &str) -> Option<Arc<KeyRecord>>

明文校验:遍历所有未过期 key,匹配则返回 KeyRecord 快照

如果 key 设置了 allowed_machines 且本机不在白名单,会打 warn 日志并视为未匹配。 这样做法的代价:攻击者可以通过“能不能过“侧信道区分 key 是否存在 — 我们接受, 因为 plaintext 空间是 256 bit 随机 hex,侧信道没意义。

Source

pub fn is_configured(&self) -> bool

是否已加载 keys 文件(非 empty)

Source

pub fn path(&self) -> Option<&Path>

Source

pub fn len(&self) -> usize

Source

pub fn is_empty(&self) -> bool

Source

pub fn has_any_card_num_restrictions(&self) -> bool

v1.4.105 eli #4 fix: 当前 KeyStore 是否有任意 key 配置了 allowed_card_nums 限制. 用于 standalone MCP / gRPC / 任何不持 TrdCache 的 keystore consumer 在启动时判断:

  • false → 没有 card_num 限制, 跳过 daemon GetAccList + expand 全流程 (避免无意义的 daemon 请求)
  • true → 必须连 daemon, 调 GetAccList, 通过 Self::expand_allowed_card_nums 把 card_num resolve 成 acc_id; 否则 fail-closed sentinel {0} 会让所有真账户 reject (eli BUG v1.4.104-002: standalone MCP 漏调 expand 导致 0757 配置的 key 全 reject).

注意: 本方法只检查 raw allowed_card_nums 是否非空 — load_file 阶段 注入的 sentinel allowed_acc_ids = {0} 算“已 expand“; 只有 caller 真跑过 Self::expand_allowed_card_nums 后才会用 resolved acc_ids 覆盖 sentinel.

Source

pub fn get_by_id(&self, id: &str) -> Option<Arc<KeyRecord>>

按 id 查询当前快照中的 key(不做 expiry / machine 校验,调用方自己做)

典型用法:MCP 在启动时 verify(plaintext) 拿到 id,后续每个请求用 get_by_id 取最新记录,这样 SIGHUP 重载 keys.json 后 scope / 限额 / expires_at 的变更能立刻生效(不用重启进程)。

返回 None 表示 id 在当前文件里不存在(被 remove_key 删掉了),调用方应 视为“key 已吊销“直接拒绝。

注意:此方法不做 machine binding 校验。对于跨 SIGHUP 的 per-msg / per-tool 复检场景应改用 Self::get_by_id_for_current_machine,确保 SIGHUP 后新加的 allowed_machines 限制立即生效(避免 startup 验过 → SIGHUP 收紧 → 仍按老 record 放行的语义漂移)。

Source

pub fn get_by_id_for_current_machine(&self, id: &str) -> Option<Arc<KeyRecord>>

按 id 查询当前快照中的 key + 立即校验本机 machine binding。

统一生命周期入口: 任何 surface (WS / MCP / REST / gRPC) 在 已 verify-once → 跨 SIGHUP 复检场景下应使用此方法替代裸 Self::get_by_id, 避免如下漂移:

  • startup 时 verify(plaintext) 检查 machine binding ✅
  • SIGHUP reload 把该 key 的 allowed_machines 收紧(移除本机指纹)
  • 后续 per-msg / per-tool 仅调 get_by_id绕过 machine binding → silent unrestricted (反模式 D / pitfall #45 silent-success 同模式)

行为:

  • id 不存在 → None(key 已被 remove_key 吊销,caller 视为吊销拒绝)
  • id 存在 + machine 校验通过 → Some(rec)
  • id 存在 + machine 校验失败 → None + warn log(与 verify 同语义)

不做 expiry 校验 —— pipeline.rs Step 1.5 / caller 自己做(与 get_by_id 行为对齐,仅差 machine 一层)。

Source

pub fn ids(&self) -> Vec<String>

导出当前所有 keys 的 id(用于调试 / 审计)

Trait Implementations§

Source§

impl Debug for KeyStore

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more