secrets: add local key store

This commit is contained in:
2026-06-01 07:07:39 +09:00
parent 6e5ed683d6
commit cc2c9a2973
25 changed files with 1197 additions and 151 deletions
+5 -8
View File
@@ -331,7 +331,7 @@ impl crate::WebSearchConfig {
Self {
enabled: upper.enabled.or(self.enabled),
provider: upper.provider.or(self.provider),
api_key_env: upper.api_key_env.or(self.api_key_env),
api_key_secret: upper.api_key_secret.or(self.api_key_secret),
timeout_secs: upper.timeout_secs.or(self.timeout_secs),
base_url: upper.base_url.or(self.base_url),
country: upper.country.or(self.country),
@@ -517,7 +517,7 @@ fn ensure_absolute(field: &'static str, path: &Path) -> Result<(), ResolveError>
}
}
/// `AuthRef::ApiKey { file, .. }` が相対パスのとき `base` を前置する。
/// `AuthRef::ApiKey { file }` が相対パスのとき `base` を前置する。
fn resolve_auth_file(auth: &mut Option<AuthRef>, base: &Path) {
if let Some(AuthRef::ApiKey { file: Some(p), .. }) = auth.as_mut() {
*p = join_if_relative(base, p);
@@ -692,10 +692,7 @@ mod tests {
}
fn api_key_file_auth(path: PathBuf) -> AuthRef {
AuthRef::ApiKey {
env: None,
file: Some(path),
}
AuthRef::ApiKey { file: Some(path) }
}
fn minimal_valid() -> PodManifestConfig {
@@ -1089,7 +1086,7 @@ mod tests {
}),
web: Some(WebConfig {
search: Some(crate::WebSearchConfig {
api_key_env: Some("LOWER_BRAVE_KEY".into()),
api_key_secret: Some("web/brave/lower".into()),
timeout_secs: Some(12),
..Default::default()
}),
@@ -1118,7 +1115,7 @@ mod tests {
assert_eq!(c.prune_protected_tokens, Some(5_000));
let search = merged.web.unwrap().search.unwrap();
assert_eq!(search.timeout_secs, Some(3));
assert_eq!(search.api_key_env.as_deref(), Some("LOWER_BRAVE_KEY"));
assert_eq!(search.api_key_secret.as_deref(), Some("web/brave/lower"));
}
#[test]
+4 -4
View File
@@ -122,10 +122,10 @@ pub struct WebSearchConfig {
pub enabled: Option<bool>,
#[serde(default)]
pub provider: Option<WebSearchProvider>,
/// Environment variable that stores the provider API key. Raw secrets do
/// not belong in manifest files.
/// Local secret-store id for the provider API key. Raw secrets do not
/// belong in manifest files.
#[serde(default)]
pub api_key_env: Option<String>,
pub api_key_secret: Option<String>,
/// Request timeout in seconds. Tool implementation applies a safe default
/// when this is omitted.
#[serde(default)]
@@ -651,7 +651,7 @@ permission = "write"
#[test]
fn parse_web_config() {
let toml = format!(
"{}\n[web]\nenabled = true\n\n[web.search]\nprovider = \"brave\"\napi_key_env = \"BRAVE_SEARCH_API_KEY\"\ntimeout_secs = 12\n\n[web.fetch]\ntimeout_secs = 7\nredirect_limit = 3\nmax_response_bytes = 12345\nmax_output_bytes = 2048\n",
"{}\n[web]\nenabled = true\n\n[web.search]\nprovider = \"brave\"\napi_key_secret = \"web/brave/default\"\ntimeout_secs = 12\n\n[web.fetch]\ntimeout_secs = 7\nredirect_limit = 3\nmax_response_bytes = 12345\nmax_output_bytes = 2048\n",
MINIMAL_REQUIRED
);
let manifest = PodManifest::from_toml(&toml).unwrap();
+6 -20
View File
@@ -97,7 +97,7 @@ pub enum SchemeKind {
/// 認証の参照。
///
/// 実際のトークン値の解決(env / file 読取、OAuth refresh 等)は
/// 実際のトークン値の解決(local secret store / file 読取、OAuth refresh 等)は
/// `crates/provider` で行う。ここはあくまで「どこから取るか」の宣言。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(tag = "kind", rename_all = "snake_case")]
@@ -105,11 +105,10 @@ pub enum AuthRef {
/// 認証不要(ローカル Ollama 等)
#[default]
None,
/// API key。env / file のいずれか(両方指定された場合は env が優先)
/// API key file reference. Prefer [`AuthRef::SecretRef`] for normal
/// provider credentials; this remains an explicit file source for low-level
/// manifests and tests.
ApiKey {
/// 環境変数名。未指定のときは scheme ごとの既定(`INSOMNIA_API_KEY_*`
#[serde(default)]
env: Option<String>,
/// key を書き込んだファイル(絶対パス)
#[serde(default)]
file: Option<PathBuf>,
@@ -117,8 +116,8 @@ pub enum AuthRef {
/// ChatGPT OAuth`~/.codex/auth.json`)。実装は `llm-auth-codex-oauth` チケット
#[serde(rename = "codex_oauth")]
CodexOAuth,
/// Typed secret-store reference. The profile resolver preserves this
/// reference verbatim; secret-store lookup/decryption is intentionally a
/// Typed local secret-store reference. The profile resolver preserves this
/// reference verbatim; secret-store lookup/deobfuscation is intentionally a
/// later consumer-boundary concern.
#[serde(rename = "secret_ref")]
SecretRef {
@@ -126,16 +125,3 @@ pub enum AuthRef {
ref_: String,
},
}
impl SchemeKind {
/// 既定の環境変数名(`INSOMNIA_API_KEY_*`)。
///
/// `AuthRef::ApiKey { env: None, .. }` の env 未指定時に使う。
pub fn default_env_var(self) -> &'static str {
match self {
Self::Anthropic => "INSOMNIA_API_KEY_ANTHROPIC",
Self::OpenaiChat | Self::OpenaiResponses => "INSOMNIA_API_KEY_OPENAI",
Self::Gemini => "INSOMNIA_API_KEY_GEMINI",
}
}
}
+1 -3
View File
@@ -1038,9 +1038,7 @@ fn reject_absolute_auth_file(
auth: &Option<AuthRef>,
field: &'static str,
) -> Result<(), ProfileError> {
if let Some(AuthRef::ApiKey {
file: Some(file), ..
}) = auth
if let Some(AuthRef::ApiKey { file: Some(file) }) = auth
&& file.is_absolute()
{
return Err(ProfileError::InvalidProfile(format!(