feat(credentials): manage declarative credentials through TOML (#2515)

* feat(credentials): manage declarative credentials through TOML

Make managed credentials part of the canonical TOML configuration and
load them before peers can authenticate.

Reuse ConfigRpc hot patches to durably replace the configured credential
set without restarting the instance. Serialize credential mutations so
base, managed, and ephemeral keys cannot race into conflicts.

Remove the managed overlay file format, digest protocol, capability
negotiation, force reconciliation, and database CAS machinery. Redact
credential secrets from debug output and management events. Write
credential-bearing files atomically with private permissions.

* fix(core): release JoinSet reapers with their owners

Pass weak task-set references into background reapers so they cannot
retain the JoinSet they are meant to collect. This lets stale smoltcp
bridge tasks terminate when an IPv4 generation is replaced.

Add ownership and TCP generation-replacement regressions covering the
production port-forward failure.
This commit is contained in:
KKRainbow authored and GitHub committed 2026-08-22 16:30:51 +08:00
1 parent 8794e12a26
commit 3fe427bc99
44 files changed
+1600 -259

No files matched your search

+42 -2
View File
@@ -1,4 +1,6 @@
use std::path::Path;
use std::{io::Write as _, path::Path};
use atomic_write_file::{AtomicWriteFile, OpenOptions};
use easytier_core::management::{ConfigFileControl, ConfigFilePermission, ConfigFileStorage};
@@ -31,7 +33,20 @@ impl ConfigFileStorage for NativeConfigFileStorage {
}
async fn write(&self, path: &Path, contents: &[u8]) -> anyhow::Result<()> {
tokio::fs::write(path, contents).await?;
let path = path.to_owned();
let contents = contents.to_owned();
tokio::task::spawn_blocking(move || {
let mut options = OpenOptions::new();
#[cfg(unix)]
{
atomic_write_file::unix::OpenOptionsExt::preserve_mode(&mut options, false);
std::os::unix::fs::OpenOptionsExt::mode(&mut options, 0o600);
}
let mut file: AtomicWriteFile = options.open(path)?;
file.write_all(&contents)?;
file.commit()
})
.await??;
Ok(())
}
@@ -40,3 +55,28 @@ impl ConfigFileStorage for NativeConfigFileStorage {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn config_write_is_atomic_and_private() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("instance.toml");
let storage = NativeConfigFileStorage;
storage.write(&path, b"first").await.unwrap();
storage.write(&path, b"second").await.unwrap();
assert_eq!(std::fs::read_to_string(&path).unwrap(), "second");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
assert_eq!(
std::fs::metadata(path).unwrap().permissions().mode() & 0o777,
0o600
);
}
}
}
+1 -1
View File
@@ -95,7 +95,7 @@ impl TestConfigPatcher {
&self,
patch: crate::proto::api::config::InstanceConfigPatch,
) -> anyhow::Result<()> {
easytier_core::management::apply_config_patch(&self.core, patch).await
easytier_core::management::apply_config_patch(&self.core, patch, None).await
}
}