Preserve public trust-anchor parent permissions
enroll_service_identity_at and export_service_identity_trust_anchor
both routed through prepare_parent, which chmods the target directory
to 0700 on unix. That is correct for the private enrollment path but
wrong for exporting a public trust anchor: Idunn's provisioning and
control-plane code export the public anchor into a directory another
service already owns and reads from (Idunn/src/provisioning.rs,
Idunn/src/control_plane.rs), and forcibly narrowing that directory's
permissions on every export would lock out the legitimate reader
instead of protecting anything.
Split the helper: ensure_parent only creates the directory, and the
renamed prepare_private_parent (used solely by enrollment) also locks
it down. Ported from GameCult/cultnet-rs commit 69f2498 ("Preserve
public anchor parent permissions"), which fixed the same bug before
that standalone repo was superseded by this package.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
e9e9c60b0f
commit
e9b91c1f50
1 changed files with 42 additions and 3 deletions
|
|
@ -229,7 +229,7 @@ pub fn enroll_service_identity_at<P: ServiceIdentityProfile>(
|
|||
path.display()
|
||||
);
|
||||
}
|
||||
prepare_parent(path)?;
|
||||
prepare_private_parent(path)?;
|
||||
let mut seed = Zeroizing::new([0u8; 32]);
|
||||
rand::rng().fill_bytes(seed.as_mut());
|
||||
let signing_key = SigningKey::from_bytes(&*seed);
|
||||
|
|
@ -338,7 +338,7 @@ pub fn export_service_identity_trust_anchor<P: ServiceIdentityProfile>(
|
|||
path: &Path,
|
||||
) -> Result<ServiceIdentityTrustAnchor> {
|
||||
let anchor = signer.trust_anchor()?;
|
||||
prepare_parent(path)?;
|
||||
ensure_parent(path)?;
|
||||
let envelope = CultCacheEnvelope {
|
||||
key: P::TRUST_ANCHOR_KEY.into(),
|
||||
r#type: P::TRUST_ANCHOR_TYPE.into(),
|
||||
|
|
@ -470,14 +470,27 @@ fn private_envelope<P: ServiceIdentityProfile>(
|
|||
})
|
||||
}
|
||||
|
||||
fn prepare_parent(path: &Path) -> Result<()> {
|
||||
fn ensure_parent(path: &Path) -> Result<()> {
|
||||
let parent = path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("service identity path has no parent"))?;
|
||||
std::fs::create_dir_all(parent)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Like [`ensure_parent`], but also locks the directory down to owner-only
|
||||
/// access. Reserved for paths that hold private key material: a public trust
|
||||
/// anchor is routinely exported into a directory another service already
|
||||
/// owns and reads from, and forcing that directory to `0700` would lock the
|
||||
/// legitimate reader out rather than protect anything.
|
||||
fn prepare_private_parent(path: &Path) -> Result<()> {
|
||||
ensure_parent(path)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let parent = path
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("service identity path has no parent"))?;
|
||||
std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700))?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -830,6 +843,32 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exporting_a_public_trust_anchor_does_not_narrow_its_parent_permissions() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let signer =
|
||||
enroll_service_identity_at::<IdunnServiceIdentity>(&temp.path().join("private.ccmp"))?;
|
||||
let public_parent = temp.path().join("public");
|
||||
std::fs::create_dir(&public_parent)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&public_parent, std::fs::Permissions::from_mode(0o750))?;
|
||||
}
|
||||
let output = public_parent.join("public.ccmp");
|
||||
export_service_identity_trust_anchor(&signer, &output)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
assert_eq!(
|
||||
std::fs::metadata(&public_parent)?.permissions().mode() & 0o777,
|
||||
0o750,
|
||||
"exporting a public trust anchor must not reassign its parent directory's permissions"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exported_anchor_is_public_only_immutable_and_rejects_identity_substitution() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue