[verified] fix(hs): redact onion in publish logs; cache 0700
All checks were successful
ci / test (pull_request) Successful in 3m0s
All checks were successful
ci / test (pull_request) Successful in 3m0s
wait_until_published now logs HsId via safelog, not the locator. Store and client_config mkdir Arti cache/state 0700. Arti dangerously_trust_everyone stays on storage only.
This commit is contained in:
parent
bfd28fce6b
commit
424002c0f5
6 changed files with 56 additions and 8 deletions
24
src/hs.rs
24
src/hs.rs
|
|
@ -1,5 +1,6 @@
|
||||||
//! In-process Arti onion-service helpers (no C-tor).
|
//! In-process Arti onion-service helpers (no C-tor).
|
||||||
|
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
|
@ -8,7 +9,7 @@ use arti_client::{TorClient, TorClientConfig};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use safelog::DisplayRedacted;
|
use safelog::DisplayRedacted;
|
||||||
use tor_hsservice::status::State;
|
use tor_hsservice::status::State;
|
||||||
use tor_hsservice::{HsNickname, OnionServiceConfig, RunningOnionService};
|
use tor_hsservice::{HsId, HsNickname, OnionServiceConfig, RunningOnionService};
|
||||||
use tor_rtcompat::PreferredRuntime;
|
use tor_rtcompat::PreferredRuntime;
|
||||||
|
|
||||||
pub const HS_PORT: u16 = 80;
|
pub const HS_PORT: u16 = 80;
|
||||||
|
|
@ -28,9 +29,24 @@ const PROBE_TIMEOUT: Duration = Duration::from_secs(12);
|
||||||
|
|
||||||
pub type Client = Arc<TorClient<PreferredRuntime>>;
|
pub type Client = Arc<TorClient<PreferredRuntime>>;
|
||||||
|
|
||||||
|
fn mkdir_700(path: &std::path::Path) {
|
||||||
|
std::fs::create_dir_all(path).expect("mkdir");
|
||||||
|
let mut perms = std::fs::metadata(path).expect("metadata").permissions();
|
||||||
|
perms.set_mode(0o700);
|
||||||
|
std::fs::set_permissions(path, perms).expect("chmod 0700");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Status/probe log label: safelog-redacted v3 onion, never the locator.
|
||||||
|
pub fn log_label(onion: &str) -> String {
|
||||||
|
match onion.parse::<HsId>() {
|
||||||
|
Ok(id) => id.display_redacted().to_string(),
|
||||||
|
Err(_) => safelog::sensitive(onion).to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn client_config(state_dir: &std::path::Path, cache_dir: &std::path::Path) -> TorClientConfig {
|
pub fn client_config(state_dir: &std::path::Path, cache_dir: &std::path::Path) -> TorClientConfig {
|
||||||
std::fs::create_dir_all(state_dir).expect("state dir");
|
mkdir_700(state_dir);
|
||||||
std::fs::create_dir_all(cache_dir).expect("cache dir");
|
mkdir_700(cache_dir);
|
||||||
let mut builder = TorClientConfigBuilder::from_directories(state_dir, cache_dir);
|
let mut builder = TorClientConfigBuilder::from_directories(state_dir, cache_dir);
|
||||||
builder.storage().permissions().dangerously_trust_everyone();
|
builder.storage().permissions().dangerously_trust_everyone();
|
||||||
builder
|
builder
|
||||||
|
|
@ -84,8 +100,8 @@ pub async fn wait_until_published(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
svc: &RunningOnionService,
|
svc: &RunningOnionService,
|
||||||
onion: &str,
|
onion: &str,
|
||||||
label: &str,
|
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
let label = log_label(onion);
|
||||||
let deadline = Instant::now() + PUBLISH_WAIT;
|
let deadline = Instant::now() + PUBLISH_WAIT;
|
||||||
let mut events = svc.status_events();
|
let mut events = svc.status_events();
|
||||||
let mut next_probe = Instant::now() + PROBE_EVERY;
|
let mut next_probe = Instant::now() + PROBE_EVERY;
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ impl Node {
|
||||||
_svc: Arc::clone(&svc),
|
_svc: Arc::clone(&svc),
|
||||||
rend,
|
rend,
|
||||||
});
|
});
|
||||||
hs::wait_until_published(&node.client, &svc, &onion, &onion).await?;
|
hs::wait_until_published(&node.client, &svc, &onion).await?;
|
||||||
node.store
|
node.store
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
|
|
@ -444,7 +444,7 @@ impl Node {
|
||||||
_svc: Arc::clone(&svc),
|
_svc: Arc::clone(&svc),
|
||||||
rend,
|
rend,
|
||||||
});
|
});
|
||||||
hs::wait_until_published(&self.client, &svc, &onion, &onion).await?;
|
hs::wait_until_published(&self.client, &svc, &onion).await?;
|
||||||
{
|
{
|
||||||
let store = self.store.lock().map_err(|e| e.to_string())?;
|
let store = self.store.lock().map_err(|e| e.to_string())?;
|
||||||
store.set_onion(&onion).map_err(|e| e.to_string())?;
|
store.set_onion(&onion).map_err(|e| e.to_string())?;
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ impl Store {
|
||||||
}
|
}
|
||||||
mkdir_700(home)?;
|
mkdir_700(home)?;
|
||||||
mkdir_700(&home.join("arti"))?;
|
mkdir_700(&home.join("arti"))?;
|
||||||
|
mkdir_700(&home.join("cache"))?;
|
||||||
let db_path = home.join("onionwire.db");
|
let db_path = home.join("onionwire.db");
|
||||||
let conn = Connection::open(&db_path)?;
|
let conn = Connection::open(&db_path)?;
|
||||||
let journal: String = conn.query_row("PRAGMA journal_mode = WAL", [], |row| row.get(0))?;
|
let journal: String = conn.query_row("PRAGMA journal_mode = WAL", [], |row| row.get(0))?;
|
||||||
|
|
|
||||||
28
tests/hs.rs
28
tests/hs.rs
|
|
@ -1,5 +1,6 @@
|
||||||
//! HS publish wait and CBT floor — 180s fail-closed cuts a working HsDir upload.
|
//! HS publish wait and CBT floor — 180s fail-closed cuts a working HsDir upload.
|
||||||
|
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use onionwire::hs;
|
use onionwire::hs;
|
||||||
|
|
@ -67,3 +68,30 @@ fn broken_or_shutdown_never_ready() {
|
||||||
assert!(!hs::hs_is_ready(State::Broken, true));
|
assert!(!hs::hs_is_ready(State::Broken, true));
|
||||||
assert!(!hs::hs_is_ready(State::Shutdown, true));
|
assert!(!hs::hs_is_ready(State::Shutdown, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hs_log_label_is_not_the_full_v3_onion() {
|
||||||
|
// Public v3 address; checksum is valid so HsId::from_str works.
|
||||||
|
let onion = "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion";
|
||||||
|
let label = hs::log_label(onion);
|
||||||
|
assert_ne!(label, onion, "status/probe logs must not use the locator");
|
||||||
|
assert!(
|
||||||
|
!label.contains("facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd"),
|
||||||
|
"log label leaked the onion body: {label}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
label.contains('…') || label.contains("[scrubbed]"),
|
||||||
|
"expected safelog redaction, got {label}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_config_mkdirs_state_and_cache_0700() {
|
||||||
|
let root = tempfile::tempdir().expect("tempdir");
|
||||||
|
let state = root.path().join("arti");
|
||||||
|
let cache = root.path().join("cache");
|
||||||
|
let _cfg = hs::client_config(&state, &cache);
|
||||||
|
let mode = |p: &std::path::Path| std::fs::metadata(p).unwrap().permissions().mode() & 0o777;
|
||||||
|
assert_eq!(mode(&state), 0o700);
|
||||||
|
assert_eq!(mode(&cache), 0o700);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,14 @@ fn first_run_creates_0700_dirs_and_self_row() {
|
||||||
let store = Store::open().expect("open");
|
let store = Store::open().expect("open");
|
||||||
|
|
||||||
let arti = home.path().join("arti");
|
let arti = home.path().join("arti");
|
||||||
|
let cache = home.path().join("cache");
|
||||||
let db = home.path().join("onionwire.db");
|
let db = home.path().join("onionwire.db");
|
||||||
assert!(arti.is_dir(), "arti dir");
|
assert!(arti.is_dir(), "arti dir");
|
||||||
|
assert!(cache.is_dir(), "cache dir");
|
||||||
assert!(db.is_file(), "onionwire.db");
|
assert!(db.is_file(), "onionwire.db");
|
||||||
assert_eq!(mode(home.path()), 0o700);
|
assert_eq!(mode(home.path()), 0o700);
|
||||||
assert_eq!(mode(&arti), 0o700);
|
assert_eq!(mode(&arti), 0o700);
|
||||||
|
assert_eq!(mode(&cache), 0o700);
|
||||||
|
|
||||||
let me = store.self_identity().expect("self");
|
let me = store.self_identity().expect("self");
|
||||||
assert_eq!(me.identity_pk.len(), 32);
|
assert_eq!(me.identity_pk.len(), 32);
|
||||||
|
|
|
||||||
|
|
@ -124,10 +124,10 @@ async fn two_node_byte_pipe_restart_and_dormant() {
|
||||||
eprintln!("bob onion={bob_onion}");
|
eprintln!("bob onion={bob_onion}");
|
||||||
assert_ne!(alice_onion, bob_onion, "separate HS identities");
|
assert_ne!(alice_onion, bob_onion, "separate HS identities");
|
||||||
|
|
||||||
hs::wait_until_published(&alice, &alice_svc, &alice_onion, "alice")
|
hs::wait_until_published(&alice, &alice_svc, &alice_onion)
|
||||||
.await
|
.await
|
||||||
.expect("alice publish");
|
.expect("alice publish");
|
||||||
hs::wait_until_published(&bob, &bob_svc, &bob_onion, "bob")
|
hs::wait_until_published(&bob, &bob_svc, &bob_onion)
|
||||||
.await
|
.await
|
||||||
.expect("bob publish");
|
.expect("bob publish");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue