feat(daemon): policy apply via pam-auth-update + lockout simulator (Phase 4+5)
Lands Lane 1 of the Phase 4+5+8+15+16 parallel cycle. Phase 4 + Phase 5 must
land together because both modify the SetPolicy code path.
- daemon/src/lockout.rs — pure simulate(new_policy, registry) -> Vec<Violation>.
Iterates Required stacks, flags users with no enrolled credential of any
required method. 5 unit tests cover: optional-mode skipped, required-with-
unenrolled flagged, any-method-satisfies, empty registry, multi-stack.
- daemon/src/policy_apply.rs — PolicyApplier renders the pam-configs profile
(Default: yes when any stack requires fido2; pam_u2f.so + pam_authforge_pending
when fido2 required, only pam_authforge_pending otherwise) and runs
pam-auth-update --package. Stash-and-restore on failure: prior profile
contents are restored and pam-auth-update re-run, so a failed apply leaves
the system in its previous PAM state. 4 unit tests including a real-process
rollback test against a failing /bin/sh shim.
- daemon/src/state.rs — AppState::set_policy(p, force) returns
PolicyApplyResult. Always runs the simulator first; if violations and !force,
returns { applied: false, violations } without writing. Otherwise persists
via PolicyStore::save and invokes PolicyApplier::apply. StorageConfig grows
pam_profile_path + pam_auth_update fields (env-var driven, tests inject a
no-op /bin/sh shim into a tempdir).
- daemon/src/dbus.rs — SetPolicy signature is now (Policy, bool) -> Result.
Wire-breaking pre-alpha; CLI updated in this commit.
- cli/src/{bus,commands}.rs — set_policy takes force flag. policy set runs
with force=false and surfaces violations as a non-zero exit + stderr list
pointing the user at policy apply --force-i-know-what-im-doing. policy
apply now actually invokes pam-auth-update via the daemon.
Test count: 42 daemon (was 33; adds 5 lockout + 4 policy_apply). 13 common.
5 cli. cargo clippy --workspace --all-targets -D warnings clean.
Plan deviation: PolicyApplier::from_env() became PolicyApplier::new(profile_path,
pam_auth_update) with the env defaults moved into StorageConfig::from_env_or_defaults.
Cleaner: state owns one source of truth for env-driven path config.
This commit is contained in:
@@ -100,17 +100,13 @@ impl AuthForge {
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_policy(&self, p: Policy) -> zbus::fdo::Result<PolicyApplyResult> {
|
||||
async fn set_policy(&self, p: Policy, force: bool) -> zbus::fdo::Result<PolicyApplyResult> {
|
||||
self.authz("io.dangerousthings.AuthForge.set-policy")
|
||||
.await?;
|
||||
self.state
|
||||
.set_policy(p)
|
||||
.set_policy(p, force)
|
||||
.await
|
||||
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?;
|
||||
Ok(PolicyApplyResult {
|
||||
applied: true,
|
||||
violations: vec![],
|
||||
})
|
||||
.map_err(|e| zbus::fdo::Error::Failed(e.to_string()))
|
||||
}
|
||||
|
||||
async fn set_pending_flag(&self, user: String, flag: PendingFlag) -> zbus::fdo::Result<()> {
|
||||
@@ -214,17 +210,24 @@ central_path = "{}"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let state = Arc::new(
|
||||
let state = Arc::new({
|
||||
let shim = tmp.path().join("fake-pau.sh");
|
||||
std::fs::write(&shim, "#!/bin/sh\nexit 0\n").unwrap();
|
||||
let mut perms = std::fs::metadata(&shim).unwrap().permissions();
|
||||
std::os::unix::fs::PermissionsExt::set_mode(&mut perms, 0o755);
|
||||
std::fs::set_permissions(&shim, perms).unwrap();
|
||||
AppState::open(
|
||||
crate::state::StorageConfig {
|
||||
policy_dir,
|
||||
pending_dir: tmp.path().join("pending"),
|
||||
userdb_path: tmp.path().join("users.db"),
|
||||
pam_profile_path: tmp.path().join("authforge-pamconf"),
|
||||
pam_auth_update: shim,
|
||||
},
|
||||
Arc::new(crate::fido::mock::MockAuthenticator::with_one_yubikey()),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
.unwrap()
|
||||
});
|
||||
let auth = AuthForge {
|
||||
state: state.clone(),
|
||||
polkit: Arc::new(Polkit::permissive()),
|
||||
@@ -333,7 +336,7 @@ central_path = "{}"
|
||||
stacks: stacks.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let r: PolicyApplyResult = p.call("SetPolicy", &(pol,)).await.unwrap();
|
||||
let r: PolicyApplyResult = p.call("SetPolicy", &(pol, true)).await.unwrap();
|
||||
assert!(r.applied);
|
||||
// p2p_pair seeds 00-test.conf with [storage]; SetPolicy writes 50-local.conf.
|
||||
// The merged read shows our new stack alongside the seeded storage block.
|
||||
|
||||
Reference in New Issue
Block a user