Skip to main content

rig_harness/
secrets.rs

1//! Secrets a plugin needs before any front runs, such as the passphrase of
2//! an encrypted session store, which opens before the session is restored.
3//!
4//! A plugin asks for one in `build` with `app.add_secret(..)`
5//! ([`AppCliExt`](crate::host::cli::AppCliExt)) and reads it from
6//! [`Secrets`], there from `build` on. The launcher learns what the build
7//! asks for when it describes it ([`describe`](crate::describe)) and,
8//! when stdin is a terminal, whatever the front, asks the user for each
9//! one the environment does not hold, once per launcher, whatever the
10//! restarts. A secret reaches the agent's process alone, as the variable
11//! [`rig_harness_protocol::secret_variable`] of its name
12//! (`RIG_SECRET_CRYPT`); a piped run gets only what the environment
13//! has. A secret not given is `None`, and the plugin decides what then.
14//!
15//! No process rig starts gets a secret: not cargo or any other launcher
16//! child, not `/reload`'s build, not a command started through
17//! [`Processes`](crate::services::Processes) (the shell and every tool), not
18//! the terminal view's editor. A plugin that starts a process with a bare
19//! `std::process::Command` passes its secrets on: start processes with
20//! `Processes`. What this protects from: tools, the model's commands,
21//! build scripts and proc macros, and helper programs. What it does not:
22//! the same user reading the agent's environment (`/proc/<pid>/environ`),
23//! and plugin code, which is trusted as all Bevy plugin code is.
24//!
25//! ```
26//! use rig_harness::prelude::*;
27//!
28//! #[derive(Default)]
29//! struct CryptPlugin;
30//!
31//! impl Plugin for CryptPlugin {
32//!     fn build(&self, app: &mut App) {
33//!         app.add_secret(Secret::new("crypt", "Passphrase for this machine's sessions"));
34//!         let passphrase = app
35//!             .world()
36//!             .get_resource::<Secrets>()
37//!             .and_then(|secrets| secrets.get("crypt"))
38//!             .map(str::to_owned);
39//!         // Open the session store with it, or refuse to start without.
40//!         # drop(passphrase);
41//!     }
42//! }
43//! ```
44
45use std::collections::BTreeMap;
46use std::fmt;
47
48use bevy_ecs::prelude::*;
49use rig_harness_protocol::{env, secret_variable};
50
51/// A secret a plugin asks for: its name, which names its variable, and
52/// what the launcher asks.
53#[derive(Component, Clone, Debug, PartialEq, Eq)]
54pub struct Secret {
55    /// Its name: lowercase letters, digits and dashes.
56    pub name: &'static str,
57    /// What to ask for, such as `Passphrase for this machine's sessions`.
58    pub help: &'static str,
59}
60
61impl Secret {
62    /// The secret `name`, asked for as `help`.
63    pub fn new(name: &'static str, help: &'static str) -> Self {
64        Self { name, help }
65    }
66}
67
68/// The secrets this agent was given. The harness takes them from the
69/// process's environment while it is built, unless the app inserted its
70/// own, as a test does: `Secrets::from_iter([("crypt", "passphrase")])`.
71/// Its `Debug` shows names only.
72#[derive(Resource, Clone, Default)]
73pub struct Secrets(BTreeMap<String, String>);
74
75impl Secrets {
76    /// Every secret in this process's environment.
77    pub fn from_env() -> Self {
78        Self(
79            std::env::vars_os()
80                .filter_map(|(name, value)| {
81                    Some((name.into_string().ok()?, value.into_string().ok()?))
82                })
83                .filter(|(name, _)| name.starts_with(env::SECRET_PREFIX))
84                .collect(),
85        )
86    }
87
88    /// The secret `name`, if given.
89    pub fn get(&self, name: &str) -> Option<&str> {
90        self.0.get(&secret_variable(name)).map(String::as_str)
91    }
92}
93
94/// Secrets by name, such as a test's.
95impl<N: AsRef<str>, V: Into<String>> FromIterator<(N, V)> for Secrets {
96    fn from_iter<I: IntoIterator<Item = (N, V)>>(secrets: I) -> Self {
97        Self(
98            secrets
99                .into_iter()
100                .map(|(name, value)| (secret_variable(name.as_ref()), value.into()))
101                .collect(),
102        )
103    }
104}
105
106impl fmt::Debug for Secrets {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        f.debug_set().entries(self.0.keys()).finish()
109    }
110}