[feat](trx-config): let secrets live outside the config file
Tokens and passphrases had exactly one representation: plain text in
trx-rs.toml. That is awkward for config-management tools, for a config kept in
a private repo, and for anything shared between machines.
Two alternatives:
- ${VAR} anywhere in a config string, expanded from the environment at load.
An unset variable is an error rather than an empty string — a silently blank
passphrase is how authentication gets disabled by accident.
- A *_file sibling for every credential: [listen.auth].tokens_file,
[[remotes]].auth.token_file, [frontends.http.auth].rx_passphrase_file and
.control_passphrase_file, [frontends.http_json.auth].tokens_file. Setting
both forms is an error rather than a guess about which wins.
Plus a nudge: a config file that holds credentials inline and is readable by
group or others gets a warning naming the chmod that fixes it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -128,7 +128,12 @@ fn check_config(loaded: &trx_config::ConfigLoad<ClientConfig>) -> DynResult<()>
|
||||
|
||||
let mut warnings: Vec<String> = loaded.unknown_keys.iter().map(|k| k.to_string()).collect();
|
||||
|
||||
let cfg = &loaded.config;
|
||||
let mut cfg = loaded.config.clone();
|
||||
let mut errors = Vec::new();
|
||||
if let Err(e) = cfg.resolve_secrets(loaded.path.as_deref()) {
|
||||
errors.push(e);
|
||||
}
|
||||
let cfg = &cfg;
|
||||
let remotes = cfg.resolved_remotes();
|
||||
if remotes.is_empty() {
|
||||
warnings.push(
|
||||
@@ -137,7 +142,7 @@ fn check_config(loaded: &trx_config::ConfigLoad<ClientConfig>) -> DynResult<()>
|
||||
);
|
||||
}
|
||||
|
||||
let mut errors = cfg.validate_all();
|
||||
errors.extend(cfg.validate_all());
|
||||
if !remotes.is_empty() {
|
||||
errors.extend(cfg.validate_resolved_all(&remotes));
|
||||
}
|
||||
@@ -220,6 +225,9 @@ async fn async_init() -> DynResult<AppState> {
|
||||
loaded.report_unknown_keys(cli.strict_config)?;
|
||||
|
||||
let mut cfg = loaded.config;
|
||||
// Secrets configured as *_file are read before validation, so everything
|
||||
// downstream sees resolved values.
|
||||
cfg.resolve_secrets(config_path.as_deref())?;
|
||||
cfg.validate()
|
||||
.map_err(|e| format!("Invalid client configuration: {}", e))?;
|
||||
|
||||
@@ -274,7 +282,10 @@ async fn async_init() -> DynResult<AppState> {
|
||||
name,
|
||||
url: url.clone(),
|
||||
rig_id,
|
||||
auth: config::RemoteAuthConfig { token },
|
||||
auth: config::RemoteAuthConfig {
|
||||
token,
|
||||
token_file: None,
|
||||
},
|
||||
poll_interval_ms,
|
||||
}]
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user