Test unsigned replicated ops are rejected
This commit is contained in:
parent
a2935328ce
commit
0ffc950dc6
4 changed files with 171 additions and 4 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1041,12 +1041,14 @@ name = "geth"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"geth-auth",
|
||||||
"geth-cas",
|
"geth-cas",
|
||||||
"geth-cli",
|
"geth-cli",
|
||||||
"geth-config",
|
"geth-config",
|
||||||
"geth-control",
|
"geth-control",
|
||||||
"geth-discovery",
|
"geth-discovery",
|
||||||
"geth-iroh",
|
"geth-iroh",
|
||||||
|
"geth-keychain",
|
||||||
"geth-node",
|
"geth-node",
|
||||||
"geth-ssh-identity",
|
"geth-ssh-identity",
|
||||||
"geth-store",
|
"geth-store",
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ tracing-subscriber.workspace = true
|
||||||
geth-cli = { path = "../geth-cli" }
|
geth-cli = { path = "../geth-cli" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
geth-auth = { path = "../geth-auth" }
|
||||||
geth-cas = { path = "../geth-cas" }
|
geth-cas = { path = "../geth-cas" }
|
||||||
geth-config = { path = "../geth-config" }
|
geth-config = { path = "../geth-config" }
|
||||||
geth-control = { path = "../geth-control" }
|
geth-control = { path = "../geth-control" }
|
||||||
geth-discovery = { path = "../geth-discovery" }
|
geth-discovery = { path = "../geth-discovery" }
|
||||||
geth-iroh = { path = "../geth-iroh" }
|
geth-iroh = { path = "../geth-iroh" }
|
||||||
|
geth-keychain = { path = "../geth-keychain" }
|
||||||
geth-node = { path = "../geth-node" }
|
geth-node = { path = "../geth-node" }
|
||||||
geth-ssh-identity = { path = "../geth-ssh-identity" }
|
geth-ssh-identity = { path = "../geth-ssh-identity" }
|
||||||
geth-store = { path = "../geth-store" }
|
geth-store = { path = "../geth-store" }
|
||||||
|
|
|
||||||
|
|
@ -630,6 +630,165 @@ fn denied_remote_operations_do_not_mutate_serving_node_state() {
|
||||||
assert!(denied_admin_stdout.contains("ssh admin shell denied"));
|
assert!(denied_admin_stdout.contains("ssh admin shell denied"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unsigned_keychain_and_auth_ops_are_rejected_during_peer_sync() {
|
||||||
|
let left_home = tempfile::tempdir().expect("left tempdir");
|
||||||
|
let right_home = tempfile::tempdir().expect("right tempdir");
|
||||||
|
if !unix_sockets_available(left_home.path()) || !unix_sockets_available(right_home.path()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert!(run_geth(left_home.path(), &["init"]).status.success());
|
||||||
|
assert!(run_geth(right_home.path(), &["init"]).status.success());
|
||||||
|
for home in [left_home.path(), right_home.path()] {
|
||||||
|
std::fs::write(
|
||||||
|
home.join("config.toml"),
|
||||||
|
"[iroh]\nrelay_mode = \"disabled\"\nlocal_discovery = false\n",
|
||||||
|
)
|
||||||
|
.expect("write config");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut left_daemon = spawn_daemon(left_home.path());
|
||||||
|
let mut right_daemon = spawn_daemon(right_home.path());
|
||||||
|
wait_for_socket(&left_home.path().join("run/geth.sock"));
|
||||||
|
wait_for_socket(&right_home.path().join("run/geth.sock"));
|
||||||
|
|
||||||
|
let left_card_path = left_home.path().join("left-peer-card.json");
|
||||||
|
let right_card_path = right_home.path().join("right-peer-card.json");
|
||||||
|
assert!(
|
||||||
|
run_geth(
|
||||||
|
left_home.path(),
|
||||||
|
&[
|
||||||
|
"peer",
|
||||||
|
"export",
|
||||||
|
"--out",
|
||||||
|
left_card_path.to_str().expect("left card path"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.status
|
||||||
|
.success()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
run_geth(
|
||||||
|
right_home.path(),
|
||||||
|
&[
|
||||||
|
"peer",
|
||||||
|
"export",
|
||||||
|
"--out",
|
||||||
|
right_card_path.to_str().expect("right card path"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.status
|
||||||
|
.success()
|
||||||
|
);
|
||||||
|
let right_card_json = std::fs::read_to_string(&right_card_path).expect("read right card");
|
||||||
|
let right_card: geth_discovery::PeerCard =
|
||||||
|
serde_json::from_str(&right_card_json).expect("decode right card");
|
||||||
|
assert!(
|
||||||
|
run_geth(
|
||||||
|
left_home.path(),
|
||||||
|
&[
|
||||||
|
"peer",
|
||||||
|
"import",
|
||||||
|
right_card_path.to_str().expect("right card path"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.status
|
||||||
|
.success()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
run_geth(
|
||||||
|
right_home.path(),
|
||||||
|
&[
|
||||||
|
"peer",
|
||||||
|
"import",
|
||||||
|
left_card_path.to_str().expect("left card path"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.status
|
||||||
|
.success()
|
||||||
|
);
|
||||||
|
|
||||||
|
let unsigned_keychain_op = geth_keychain::KeychainOp {
|
||||||
|
id: geth_types::AuthOpId::new("keychain-op:unsigned-user-add"),
|
||||||
|
created_at: geth_types::UnixMillis(10),
|
||||||
|
kind: geth_keychain::KeychainOpKind::UserAdd {
|
||||||
|
user: geth_types::UserId::new("user:unsigned"),
|
||||||
|
name: "Unsigned".to_owned(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let unsigned_auth_op = geth_auth::AuthOp {
|
||||||
|
id: geth_types::AuthOpId::new("auth-op:unsigned-grant"),
|
||||||
|
resource: geth_types::ResourceId::new("resource:ssh-proxy:local"),
|
||||||
|
created_at: geth_types::UnixMillis(11),
|
||||||
|
kind: geth_auth::AuthOpKind::GrantCreate {
|
||||||
|
grant_id: "grant:unsigned".to_owned(),
|
||||||
|
principal: geth_types::PrincipalId::new("node:unsigned"),
|
||||||
|
capabilities: vec![geth_types::Capability::new("ssh_proxy.connect")],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let right_paths = geth_config::GethPaths::from_home(right_home.path());
|
||||||
|
let right_store =
|
||||||
|
geth_store::Store::open(&right_paths.metadata_db()).expect("open right store");
|
||||||
|
right_store
|
||||||
|
.insert_keychain_op(&geth_store::StoredKeychainOp {
|
||||||
|
op_id: unsigned_keychain_op.id.to_string(),
|
||||||
|
op_json: serde_json::to_string(&unsigned_keychain_op).expect("encode keychain op"),
|
||||||
|
created_at_ms: unsigned_keychain_op.created_at.0,
|
||||||
|
})
|
||||||
|
.expect("insert unsigned keychain op");
|
||||||
|
right_store
|
||||||
|
.insert_auth_op(&geth_store::StoredAuthOp {
|
||||||
|
op_id: unsigned_auth_op.id.to_string(),
|
||||||
|
resource_id: unsigned_auth_op.resource.to_string(),
|
||||||
|
op_json: serde_json::to_string(&unsigned_auth_op).expect("encode auth op"),
|
||||||
|
created_at_ms: unsigned_auth_op.created_at.0,
|
||||||
|
})
|
||||||
|
.expect("insert unsigned auth op");
|
||||||
|
|
||||||
|
let keychain_sync = run_geth(
|
||||||
|
left_home.path(),
|
||||||
|
&["keychain", "sync", right_card.node_id.as_str()],
|
||||||
|
);
|
||||||
|
let auth_sync = run_geth(
|
||||||
|
left_home.path(),
|
||||||
|
&["auth", "sync", right_card.node_id.as_str()],
|
||||||
|
);
|
||||||
|
let _ = left_daemon.kill();
|
||||||
|
let _ = right_daemon.kill();
|
||||||
|
let _ = left_daemon.wait();
|
||||||
|
let _ = right_daemon.wait();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
keychain_sync.status.success(),
|
||||||
|
"keychain sync stderr: {}",
|
||||||
|
String::from_utf8_lossy(&keychain_sync.stderr)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
auth_sync.status.success(),
|
||||||
|
"auth sync stderr: {}",
|
||||||
|
String::from_utf8_lossy(&auth_sync.stderr)
|
||||||
|
);
|
||||||
|
assert!(String::from_utf8_lossy(&keychain_sync.stdout).contains("invalid_ops_rejected: 1"));
|
||||||
|
assert!(String::from_utf8_lossy(&auth_sync.stdout).contains("invalid_ops_rejected: 1"));
|
||||||
|
|
||||||
|
let left_paths = geth_config::GethPaths::from_home(left_home.path());
|
||||||
|
let left_store = geth_store::Store::open(&left_paths.metadata_db()).expect("open left store");
|
||||||
|
assert!(
|
||||||
|
!left_store
|
||||||
|
.list_keychain_ops()
|
||||||
|
.expect("list left keychain ops")
|
||||||
|
.iter()
|
||||||
|
.any(|op| op.op_id == unsigned_keychain_op.id.as_str())
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!left_store
|
||||||
|
.list_auth_ops()
|
||||||
|
.expect("list left auth ops")
|
||||||
|
.iter()
|
||||||
|
.any(|op| op.op_id == unsigned_auth_op.id.as_str())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn peer_card_export_import_and_list_are_candidate_only() {
|
fn peer_card_export_import_and_list_are_candidate_only() {
|
||||||
let source_home = tempfile::tempdir().expect("source tempdir");
|
let source_home = tempfile::tempdir().expect("source tempdir");
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ Implementation order:
|
||||||
- `[x]` Add initial two-daemon tests proving denied remote pubsub publish,
|
- `[x]` Add initial two-daemon tests proving denied remote pubsub publish,
|
||||||
remote pipe listen, and SSH admin shell requests do not mutate serving
|
remote pipe listen, and SSH admin shell requests do not mutate serving
|
||||||
node state.
|
node state.
|
||||||
- `[ ]` Add tests proving unsigned, invalidly signed, and conflicting
|
- `[x]` Add initial two-daemon tests proving unsigned replicated
|
||||||
replicated records do not mutate trust/resource state.
|
keychain/auth operations are rejected and not imported.
|
||||||
|
- `[ ]` Add tests proving invalidly signed and conflicting replicated
|
||||||
|
keychain/auth records do not mutate trust/resource state.
|
||||||
- `[ ]` Improve `auth explain` diagnostics enough for operators to
|
- `[ ]` Improve `auth explain` diagnostics enough for operators to
|
||||||
distinguish discovered-only peers, missing endpoint bindings, missing
|
distinguish discovered-only peers, missing endpoint bindings, missing
|
||||||
grants, matching grants, revocations, and bearer access.
|
grants, matching grants, revocations, and bearer access.
|
||||||
|
|
@ -67,8 +69,10 @@ Implementation order:
|
||||||
`geth sync status`.
|
`geth sync status`.
|
||||||
- `[x]` A two-daemon test proves approved keychain/auth state reaches the
|
- `[x]` A two-daemon test proves approved keychain/auth state reaches the
|
||||||
enrolled node without using the old one-off `node enroll sync` shortcut.
|
enrolled node without using the old one-off `node enroll sync` shortcut.
|
||||||
- `[ ]` Tests assert rejected/unsigned/conflicting replicated records do not
|
- `[x]` Tests assert unsigned replicated keychain/auth records do not mutate
|
||||||
mutate local trust or resource state.
|
local trust or resource state.
|
||||||
|
- `[ ]` Tests assert invalidly signed/conflicting replicated keychain/auth
|
||||||
|
records do not mutate local trust or resource state.
|
||||||
|
|
||||||
- `[~]` Remote authorization enforcement audit.
|
- `[~]` Remote authorization enforcement audit.
|
||||||
Acceptance criteria:
|
Acceptance criteria:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue