Add static keychain publication workflow
This commit is contained in:
parent
b6ffcde54c
commit
cfd41522d1
11 changed files with 1852 additions and 46 deletions
|
|
@ -2627,9 +2627,11 @@ fn keychain_admin_sigchain_exports_allowed_signers_and_verifies() {
|
|||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let response =
|
||||
geth_node::handle_request(&node, geth_control::ControlRequest::KeychainAllowedSigners)
|
||||
.expect("allowed signers");
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainAllowedSigners { out: None },
|
||||
)
|
||||
.expect("allowed signers");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainAllowedSigners {
|
||||
entries,
|
||||
|
|
@ -2642,6 +2644,278 @@ fn keychain_admin_sigchain_exports_allowed_signers_and_verifies() {
|
|||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let allowed_signers_path = home.path().join("allowed_signers");
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainAllowedSigners {
|
||||
out: Some(allowed_signers_path.clone()),
|
||||
},
|
||||
)
|
||||
.expect("allowed signers file");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainAllowedSigners { out, .. } => {
|
||||
assert_eq!(out.as_deref(), Some(allowed_signers_path.as_path()));
|
||||
let allowed_signers_file =
|
||||
std::fs::read_to_string(&allowed_signers_path).expect("read allowed_signers");
|
||||
assert!(allowed_signers_file.contains("second-admin ssh-ed25519 "));
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let authorized_keys_path = home.path().join("authorized_keys");
|
||||
std::fs::write(
|
||||
&authorized_keys_path,
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGexample external-login-key\n",
|
||||
)
|
||||
.expect("write authorized_keys snapshot");
|
||||
let authorized_keys_signature_path = home.path().join("authorized_keys.sig");
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainSignFile {
|
||||
input: authorized_keys_path.clone(),
|
||||
out: Some(authorized_keys_signature_path.clone()),
|
||||
namespace: None,
|
||||
signing_key_path: Some(admin_key_path.clone()),
|
||||
admin_key_path: Some(admin_key_path.with_extension("pub")),
|
||||
},
|
||||
)
|
||||
.expect("sign authorized_keys snapshot");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainFileSigned {
|
||||
input,
|
||||
out,
|
||||
namespace,
|
||||
signer,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(input, authorized_keys_path);
|
||||
assert_eq!(
|
||||
out.as_deref(),
|
||||
Some(authorized_keys_signature_path.as_path())
|
||||
);
|
||||
assert_eq!(namespace, geth_keychain::AUTHORIZED_KEYS_NAMESPACE);
|
||||
assert!(!signer.is_empty());
|
||||
assert!(authorized_keys_signature_path.exists());
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainVerifyFile {
|
||||
input: authorized_keys_path.clone(),
|
||||
signature: authorized_keys_signature_path.clone(),
|
||||
namespace: None,
|
||||
allowed_signers_path: Some(allowed_signers_path.clone()),
|
||||
principal: None,
|
||||
},
|
||||
)
|
||||
.expect("verify authorized_keys snapshot");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainFileVerified {
|
||||
verified,
|
||||
principal,
|
||||
..
|
||||
} => {
|
||||
assert!(verified);
|
||||
assert_eq!(principal.as_deref(), Some("admin"));
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let sigchain_path = home.path().join("keychain.sigchain.jsonl");
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainSigchainExport {
|
||||
out: Some(sigchain_path.clone()),
|
||||
},
|
||||
)
|
||||
.expect("sigchain export");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainSigchainExported { entries, out, .. } => {
|
||||
assert_eq!(out.as_deref(), Some(sigchain_path.as_path()));
|
||||
assert!(entries.len() >= 3);
|
||||
let jsonl = std::fs::read_to_string(&sigchain_path).expect("read sigchain");
|
||||
let decoded = geth_keychain::decode_sigchain_jsonl(&jsonl).expect("decode sigchain");
|
||||
assert_eq!(decoded, entries);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainVerifySigchain {
|
||||
input: sigchain_path.clone(),
|
||||
},
|
||||
)
|
||||
.expect("verify sigchain file");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainSigchainFileVerified { report, .. } => {
|
||||
assert_eq!(report.rejected_ops, 0);
|
||||
assert_eq!(report.active_admin_keys, 2);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let bundle_dir = home.path().join("public-bundle");
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainPublishBundle {
|
||||
out: bundle_dir.clone(),
|
||||
base_url: None,
|
||||
signing_key_path: admin_key_path.clone(),
|
||||
admin_key_path: Some(admin_key_path.with_extension("pub")),
|
||||
snapshots: vec![format!(
|
||||
"authorized_keys={}",
|
||||
authorized_keys_path.display()
|
||||
)],
|
||||
},
|
||||
)
|
||||
.expect("publish bundle");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainBundlePublished {
|
||||
base_url,
|
||||
allowed_signers_path,
|
||||
sigchain_path: bundle_sigchain_path,
|
||||
checkpoint_path,
|
||||
checkpoint_signature_path,
|
||||
checkpoint,
|
||||
snapshots,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(base_url, geth_keychain::DEFAULT_SSH_SIGCHAIN_DISCOVERY_URL);
|
||||
assert!(allowed_signers_path.exists());
|
||||
assert!(bundle_sigchain_path.exists());
|
||||
assert!(checkpoint_path.exists());
|
||||
assert!(checkpoint_signature_path.exists());
|
||||
assert_eq!(
|
||||
checkpoint.base_url,
|
||||
geth_keychain::DEFAULT_SSH_SIGCHAIN_DISCOVERY_URL
|
||||
);
|
||||
assert_eq!(snapshots.len(), 1);
|
||||
assert_eq!(snapshots[0].name, "authorized_keys");
|
||||
assert!(snapshots[0].path.exists());
|
||||
assert!(snapshots[0].signature_path.exists());
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainVerifyCheckpoint {
|
||||
checkpoint: bundle_dir.join("geth.sigchain.checkpoint.json"),
|
||||
signature: bundle_dir.join("geth.sigchain.checkpoint.json.sig"),
|
||||
sigchain: bundle_dir.join("geth.sigchain.jsonl"),
|
||||
allowed_signers: bundle_dir.join("allowed_signers"),
|
||||
base_url: Some(geth_keychain::DEFAULT_SSH_SIGCHAIN_DISCOVERY_URL.to_owned()),
|
||||
principal: None,
|
||||
},
|
||||
)
|
||||
.expect("verify checkpoint");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainCheckpointVerified {
|
||||
verified,
|
||||
principal,
|
||||
..
|
||||
} => {
|
||||
assert!(verified);
|
||||
assert_eq!(principal.as_deref(), Some("admin"));
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let fetched_home = tempfile::tempdir().expect("fetch tempdir");
|
||||
let fetched_paths = geth_config::GethPaths::from_home(fetched_home.path());
|
||||
let fetched_node = geth_node::init_node(&fetched_paths).expect("init fetched node");
|
||||
let response = geth_node::handle_request(
|
||||
&fetched_node,
|
||||
geth_control::ControlRequest::KeychainFetch {
|
||||
url: format!("file://{}", bundle_dir.display()),
|
||||
out: Some(fetched_home.path().join("bundle")),
|
||||
import: true,
|
||||
},
|
||||
)
|
||||
.expect("fetch bundle");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainFetched {
|
||||
imported: Some(imported),
|
||||
checkpoint,
|
||||
..
|
||||
} => {
|
||||
assert!(imported.ops_imported >= 3);
|
||||
assert!(imported.signatures_imported >= 3);
|
||||
assert_eq!(imported.invalid_ops_rejected, 0);
|
||||
assert_eq!(
|
||||
checkpoint.base_url,
|
||||
geth_keychain::DEFAULT_SSH_SIGCHAIN_DISCOVERY_URL
|
||||
);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let jsonl = std::fs::read_to_string(&sigchain_path).expect("read sigchain for explain");
|
||||
let decoded =
|
||||
geth_keychain::decode_sigchain_jsonl(&jsonl).expect("decode sigchain for explain");
|
||||
let explain_op_id = decoded.last().expect("last sigchain op").op.id.to_string();
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainExplain {
|
||||
op_id: explain_op_id.clone(),
|
||||
},
|
||||
)
|
||||
.expect("explain op");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainExplained { subject, lines } => {
|
||||
assert_eq!(subject, explain_op_id);
|
||||
assert!(lines.iter().any(|line| line.contains("accepted_by_replay")));
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let admin_public_key =
|
||||
std::fs::read_to_string(admin_key_path.with_extension("pub")).expect("admin pub");
|
||||
let admin_key = geth_keychain::admin_key_fingerprint(&admin_public_key);
|
||||
let response = geth_node::handle_request(
|
||||
&node,
|
||||
geth_control::ControlRequest::KeychainExplainSigner { key: admin_key },
|
||||
)
|
||||
.expect("explain signer");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainExplained { lines, .. } => {
|
||||
assert!(lines.iter().any(|line| line == "active_admin_signer: true"));
|
||||
assert!(
|
||||
lines
|
||||
.iter()
|
||||
.any(|line| line.starts_with("signed_operations:"))
|
||||
);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let import_home = tempfile::tempdir().expect("import tempdir");
|
||||
let import_paths = geth_config::GethPaths::from_home(import_home.path());
|
||||
let import_node = geth_node::init_node(&import_paths).expect("init import node");
|
||||
let response = geth_node::handle_request(
|
||||
&import_node,
|
||||
geth_control::ControlRequest::KeychainImportSigchain {
|
||||
input: sigchain_path.clone(),
|
||||
},
|
||||
)
|
||||
.expect("import sigchain file");
|
||||
match response {
|
||||
geth_control::ControlResponse::KeychainSigchainImported {
|
||||
ops_imported,
|
||||
signatures_imported,
|
||||
invalid_ops_rejected,
|
||||
..
|
||||
} => {
|
||||
assert!(ops_imported >= 3);
|
||||
assert!(signatures_imported >= 3);
|
||||
assert_eq!(invalid_ops_rejected, 0);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
|
||||
let response = geth_node::handle_request(&node, geth_control::ControlRequest::KeychainVerify)
|
||||
.expect("verify sigchain");
|
||||
match response {
|
||||
|
|
|
|||
Loading…
Reference in a new issue