diff --git a/Cargo.lock b/Cargo.lock index 3f7f943f..6bc5c3a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,7 @@ dependencies = [ "alloy-rpc-types", "alloy-serde", "alloy-signer", + "alloy-signer-aws", "alloy-signer-local", "alloy-transport", "alloy-transport-http", @@ -298,7 +299,7 @@ checksum = "f614019a029c8fec14ae661aa7d4302e6e66bdbfb869dab40e78dcfba935fc97" dependencies = [ "alloy-primitives", "alloy-sol-types", - "http", + "http 1.3.1", "serde", "serde_json", "thiserror 2.0.16", @@ -587,6 +588,25 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "alloy-signer-aws" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5cbc38711b7e5ee7b53a7489e8e718a54c6b138d382cfb86e293a2ad5ac894d" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "aws-config", + "aws-sdk-kms", + "k256", + "spki", + "thiserror 2.0.16", + "tracing", +] + [[package]] name = "alloy-signer-local" version = "1.0.32" @@ -724,7 +744,7 @@ dependencies = [ "alloy-pubsub", "alloy-transport", "futures", - "http", + "http 1.3.1", "rustls", "serde_json", "tokio", @@ -1078,6 +1098,304 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-config" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04b37ddf8d2e9744a0b9c19ce0b78efe4795339a90b66b7bae77987092cd2e69" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 1.3.1", + "time", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01c9521fa01558f750d183c8c68c81b0155b9d193a4ba7f84c36bd1b6d04a06" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce527fb7e53ba9626fc47824f25e256250556c40d8f81d27dd92aa38239d632" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995d40070271994fb774137aa603c10e7d29c4567a9605c6b801dff199c3d221" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.87.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5871bec9a79a3e8d928c7788d654f135dde0e71d2dd98089388bab36b37ef607" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c35452ec3f001e1f2f6db107b6373f1f48f05ec63ba2c5c9fa91f07dad32af11" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.3.1", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "127fcfad33b7dfc531141fda7e1c402ac65f88aca5511a4d31e2e3d2cd01ce9c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "623254723e8dfd535f566ee7b2381645f8981da086b5c4aa26c0c41582bb1d2c" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2", + "http 1.3.1", + "hyper", + "hyper-rustls", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d1881b1ea6d313f9890710d65c158bdab6fb08c91ea825f74c1c8c357baf4cc" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bbe9d018d646b96c7be063dd07987849862b0e6d07c778aad7d93d1be6c1ef0" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d79fb68e3d7fe5d4833ea34dc87d2e97d26d3086cb3da660bb6b1f76d98680b6" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", +] + [[package]] name = "axum" version = "0.8.4" @@ -1087,8 +1405,8 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-util", @@ -1115,8 +1433,8 @@ checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -1173,6 +1491,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.8.0" @@ -1321,6 +1649,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "bzip2-sys" version = "0.1.13+1.0.8" @@ -1592,6 +1930,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -2562,7 +2910,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.3.1", "indexmap 2.11.4", "slab", "tokio", @@ -2645,6 +2993,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.3.1" @@ -2656,6 +3015,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -2663,7 +3033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.3.1", ] [[package]] @@ -2674,8 +3044,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", ] @@ -2702,8 +3072,8 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -2720,10 +3090,11 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.3.1", "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -2757,8 +3128,8 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "hyper", "ipnet", "libc", @@ -3527,10 +3898,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -3769,6 +4140,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" version = "0.9.109" @@ -3781,6 +4158,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -4325,6 +4708,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + [[package]] name = "regex-syntax" version = "0.8.6" @@ -4342,8 +4731,8 @@ dependencies = [ "encoding_rs", "futures-core", "h2", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "hyper", "hyper-rustls", @@ -4520,6 +4909,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + [[package]] name = "rustls-pki-types" version = "1.12.0" @@ -4673,7 +5074,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.4", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -5108,7 +5522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.4", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5485,8 +5899,8 @@ dependencies = [ "bitflags 2.9.4", "bytes", "futures-util", - "http", - "http-body", + "http 1.3.1", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower", @@ -5613,7 +6027,7 @@ checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.3.1", "httparse", "log", "rand 0.9.2", @@ -5708,6 +6122,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -5726,6 +6146,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -5785,6 +6215,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -5954,6 +6390,10 @@ name = "wcn_admin" version = "0.1.0" dependencies = [ "anyhow", + "aws-config", + "aws-sdk-kms", + "aws-smithy-async", + "aws-smithy-http-client", "clap", "derive_more", "futures", @@ -5995,6 +6435,7 @@ dependencies = [ "alloy", "anyhow", "arc-swap", + "aws-sdk-kms", "base64", "const-hex", "derive-where", @@ -6984,6 +7425,12 @@ dependencies = [ "time", ] +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "xxhash-rust" version = "0.8.15" diff --git a/Cargo.toml b/Cargo.toml index 6ea309a8..6349b2cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ envy = { version = "0.4", default-features = false } vergen-pretty = { version = "1.0", default-features = false, features = ["trace"] } vergen = { version = "9", default-features = false, features = ["build", "cargo", "rustc", "si"] } clap = { version = "4.5", features = ["derive", "env"] } +aws-sdk-kms = { version = "1.96", default-features = false } [workspace.lints.clippy] all = { level = "deny", priority = -1 } diff --git a/VERSION b/VERSION index 065f561f..0b4d2c6b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -260203.0 +260205.0 diff --git a/crates/admin_cli/Cargo.toml b/crates/admin_cli/Cargo.toml index 7ed5511d..c27e10a4 100644 --- a/crates/admin_cli/Cargo.toml +++ b/crates/admin_cli/Cargo.toml @@ -6,7 +6,7 @@ publish = false [dependencies] # Local -wcn_cluster = { workspace = true, features = ["evm"] } +wcn_cluster = { workspace = true, features = ["evm", "kms"] } # Derives derive_more = { workspace = true, features = ["as_ref"] } @@ -21,6 +21,10 @@ futures = { workspace = true } # Other clap = { workspace = true } libp2p-identity = { workspace = true } +aws-sdk-kms = { workspace = true } +aws-config = { version = "1.8", default-features = false } +aws-smithy-http-client = { version = "1.0", default-features = false, features = ["rustls-ring"] } +aws-smithy-async = { version = "1.0", default-features = false, features = ["rt-tokio"] } [lints] workspace = true diff --git a/crates/admin_cli/src/main.rs b/crates/admin_cli/src/main.rs index 951f5f04..7064b55c 100644 --- a/crates/admin_cli/src/main.rs +++ b/crates/admin_cli/src/main.rs @@ -1,9 +1,9 @@ use { anyhow::Context, - clap::{Args, Parser, Subcommand}, + clap::{ArgGroup, Args, Parser, Subcommand}, derive_more::AsRef, std::io::{self, Write as _}, - wcn_cluster::NodeOperator, + wcn_cluster::{NodeOperator, smart_contract::evm}, }; mod deploy; @@ -49,14 +49,20 @@ enum Command { } #[derive(Debug, Args)] +#[command(group( + ArgGroup::new("signer") + .required(true) + .multiple(false) + .args(["private_key", "kms_key_arn"]) +))] struct ClusterArgs { /// Private key of the WCN Cluster Smart-Contract owner - #[arg( - id = "PRIVATE_KEY", - long = "private-key", - env = "WCN_CLUSTER_SMART_CONTRACT_OWNER_PRIVATE_KEY" - )] - signer: wcn_cluster::smart_contract::evm::Signer, + #[arg(long, env = "WCN_CLUSTER_SMART_CONTRACT_OWNER_PRIVATE_KEY")] + private_key: Option, + + /// KMS key id of the WCN Cluster Smart-Contract owner + #[arg(long, env = "WCN_CLUSTER_SMART_CONTRACT_OWNER_KMS_KEY_ARN")] + kms_key_arn: Option, /// WCN Cluster Smart-Contract encryption key #[arg( @@ -82,8 +88,34 @@ impl ClusterArgs { encryption_key: self.encryption_key, }; + let signer = if let Some(pk) = self.private_key { + evm::Signer::try_from_private_key(&pk)? + } else if let Some(key_arn) = self.kms_key_arn { + use aws_smithy_http_client::tls; + + // Force `aws_sdk` to use `ring` instead of `aws-lc`, otherwise we get a runtime + // conflict in `rustls`. + let client = aws_smithy_http_client::Builder::new() + .tls_provider(tls::Provider::Rustls( + tls::rustls_provider::CryptoMode::Ring, + )) + .build_https(); + + let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .http_client(client) + .sleep_impl(aws_smithy_async::rt::sleep::TokioSleep::new()) + .load() + .await; + + let client = aws_sdk_kms::Client::new(&config); + evm::Signer::try_from_kms(client, key_arn).await? + } else { + // `clap` validates that exactly one required argument is present + unreachable!() + }; + let connector = - wcn_cluster::smart_contract::evm::RpcProvider::new(self.rpc_provider_url, self.signer) + wcn_cluster::smart_contract::evm::RpcProvider::new(self.rpc_provider_url, signer) .await .context("RpcProvider::new")?; diff --git a/crates/cluster/Cargo.toml b/crates/cluster/Cargo.toml index 3d24dbf0..6301f10c 100644 --- a/crates/cluster/Cargo.toml +++ b/crates/cluster/Cargo.toml @@ -8,6 +8,7 @@ publish = false default = ["evm"] testing = [] evm = ["dep:alloy"] +kms = ["dep:aws-sdk-kms", "alloy/signer-aws"] [lints] workspace = true @@ -67,6 +68,7 @@ alloy = { workspace = true, optional = true, features = [ "provider-ws", "rpc-types", ] } +aws-sdk-kms = { workspace = true, optional = true } [dev-dependencies] libp2p-identity = { workspace = true, features = ["rand"] } diff --git a/crates/cluster/src/smart_contract/evm.rs b/crates/cluster/src/smart_contract/evm.rs index 4db00023..14b5f2aa 100644 --- a/crates/cluster/src/smart_contract/evm.rs +++ b/crates/cluster/src/smart_contract/evm.rs @@ -1,5 +1,7 @@ //! EVM implementation of [`SmartContract`](super::SmartContract). +#[cfg(feature = "kms")] +use alloy::signers::aws::AwsSigner; use { super::*, crate::{migration, node_operator, smart_contract}, @@ -47,6 +49,8 @@ impl RpcProvider { pub async fn new(url: RpcUrl, signer: Signer) -> Result { let wallet: EthereumWallet = match &signer.kind { SignerKind::PrivateKey(key) => key.clone().into(), + #[cfg(feature = "kms")] + SignerKind::Kms(key) => key.clone().into(), }; let builder = ProviderBuilder::new().wallet(wallet); @@ -679,7 +683,7 @@ pub struct Signer { } impl FromStr for Signer { - type Err = InvalidPrivateKeyError; + type Err = InvalidSignerError; fn from_str(s: &str) -> Result { Self::try_from_private_key(s) @@ -687,9 +691,26 @@ impl FromStr for Signer { } impl Signer { - pub fn try_from_private_key(hex: &str) -> Result { + #[cfg(feature = "kms")] + pub async fn try_from_kms( + kms: aws_sdk_kms::Client, + key_id: String, + ) -> Result { + use alloy::{network::TxSigner, signers::Signature}; + + let aws_signer = AwsSigner::new(kms, key_id, None) + .await + .map_err(|err| InvalidSignerError(format!("KMS: {err:?}")))?; + + Ok(Self { + address: TxSigner::::address(&aws_signer).into(), + kind: SignerKind::Kms(aws_signer), + }) + } + + pub fn try_from_private_key(hex: &str) -> Result { let private_key = PrivateKeySigner::from_str(hex) - .map_err(|err| InvalidPrivateKeyError(format!("{err:?}")))?; + .map_err(|err| InvalidSignerError(format!("Private key: {err:?}")))?; Ok(Self { address: private_key.address().into(), @@ -706,8 +727,11 @@ impl Signer { #[derive(Clone)] enum SignerKind { PrivateKey(PrivateKeySigner), + + #[cfg(feature = "kms")] + Kms(AwsSigner), } #[derive(Debug, thiserror::Error)] -#[error("Invalid private key: {0:?}")] -pub struct InvalidPrivateKeyError(String); +#[error("Invalid signer: {0:?}")] +pub struct InvalidSignerError(String); diff --git a/env.sh b/env.sh index deecd25c..890dbb07 100755 --- a/env.sh +++ b/env.sh @@ -3,10 +3,19 @@ secrets=$(sops -d $1) smart_contract_address=$(echo $secrets | jq -r '.["smart_contract_address_unencrypted"]') smart_contract_encryption_key=$(echo $secrets | jq -r '.["smart_contract_encryption_key"]') ecdsa_private_key=$(echo $secrets | jq -r '.["ecdsa_private_key"]') +kms_key_arn=$(echo $secrets | jq -r '.["kms_key_arn_unencrypted"]') rpc_provider_url=$(echo $secrets | jq -r '.["rpc_provider_url"]') + +if [ "$ecdsa_private_key" != "null" ]; then + export WCN_CLUSTER_SMART_CONTRACT_OWNER_PRIVATE_KEY=$ecdsa_private_key +fi + +if [ "$kms_key_arn" != "null" ]; then + export WCN_CLUSTER_SMART_CONTRACT_OWNER_KMS_KEY_ARN=$kms_key_arn +fi + export WCN_CLUSTER_SMART_CONTRACT_ADDRESS=$smart_contract_address export WCN_CLUSTER_SMART_CONTRACT_ENCRYPTION_KEY=$smart_contract_encryption_key -export WCN_CLUSTER_SMART_CONTRACT_OWNER_PRIVATE_KEY=$ecdsa_private_key export WCN_NODE_OPERATOR_PRIVATE_KEY=$ecdsa_private_key export OPTIMISM_RPC_PROVIDER_URL=$rpc_provider_url diff --git a/infra/mainnet/main.tf b/infra/mainnet/main.tf index 9aee9347..878c527a 100644 --- a/infra/mainnet/main.tf +++ b/infra/mainnet/main.tf @@ -81,6 +81,34 @@ module "sops-encryption-key" { source = "../modules/sops-encryption-key" } +module "admin-key-eu" { + source = "../modules/admin-key" + providers = { + aws = aws.eu + } +} + +module "admin-key-us" { + source = "../modules/admin-key" + providers = { + aws = aws.us + } +} + +module "admin-key-ap" { + source = "../modules/admin-key" + providers = { + aws = aws.ap + } +} + +module "admin-key-sa" { + source = "../modules/admin-key" + providers = { + aws = aws.sa + } +} + resource "aws_route53_zone" "this" { name = "mainnet.walletconnect.network" } diff --git a/infra/modules/admin-key/main.tf b/infra/modules/admin-key/main.tf new file mode 100644 index 00000000..5a67573b --- /dev/null +++ b/infra/modules/admin-key/main.tf @@ -0,0 +1,52 @@ +data "aws_caller_identity" "current" {} + +resource "aws_kms_key" "this" { + description = "WCN Cluster Smart-Contract admin (owner) key" + key_usage = "SIGN_VERIFY" + customer_master_key_spec = "ECC_SECG_P256K1" + multi_region = true +} + +resource "aws_kms_key_policy" "this" { + key_id = aws_kms_key.this.id + + policy = jsonencode({ + Version = "2012-10-17" + Id = "key-default-1" + Statement = [ + { + Effect = "Allow" + Principal = { + AWS = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/TerraformCloud" + ] + }, + Action = "kms:*" + Resource = "*" + }, + { + Effect = "Allow" + Principal = { + AWS = "*" + }, + Action = [ + "kms:Sign", + "kms:Verify", + "kms:GetPublicKey", + "kms:DescribeKey" + ] + Resource = "*", + "Condition" : { + "ArnLike" : { + "aws:PrincipalArn" = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_Administrator_*" + } + } + } + ] + }) +} + +output "arn" { + value = aws_kms_key.this.arn +} diff --git a/infra/modules/sops-encryption-key/main.tf b/infra/modules/sops-encryption-key/main.tf index 4d40fd95..a9312eb4 100644 --- a/infra/modules/sops-encryption-key/main.tf +++ b/infra/modules/sops-encryption-key/main.tf @@ -39,7 +39,7 @@ resource "aws_kms_key_policy" "this" { Resource = "*", "Condition" : { "ArnLike" : { - "aws:PrincipalArn" = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_Read-Only_*" + "aws:PrincipalArn" = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_Administrator_*" } } } diff --git a/infra/testnet/main.tf b/infra/testnet/main.tf index 88f1344f..aeb73c2a 100644 --- a/infra/testnet/main.tf +++ b/infra/testnet/main.tf @@ -57,6 +57,13 @@ module "sops-encryption-key" { source = "../modules/sops-encryption-key" } +module "admin-key-eu" { + source = "../modules/admin-key" + providers = { + aws = aws.eu + } +} + resource "aws_route53_zone" "this" { name = "testnet.walletconnect.network" } diff --git a/infra/testnet/sops/admin.json b/infra/testnet/sops/admin.json index db36ce53..dd5f4c63 100644 --- a/infra/testnet/sops/admin.json +++ b/infra/testnet/sops/admin.json @@ -1,5 +1,5 @@ { - "ecdsa_private_key": "ENC[AES256_GCM,data:7CabaechT+0PbaFOaPPWUB04PJGLZWnp9k/bfE7Z4RRc5ONatrgRP8Ffv7lFXoWS0i3k9DaVBFewIXEUzS9FoM4s,iv:gXC7QyHk3GpU46CfYLimelH/3NzIUymBByCy9dIq3xw=,tag:YbBe5LateVcAJVI21mWCYg==,type:str]", + "kms_key_arn_unencrypted": "arn:aws:kms:eu-central-1:260477932319:key/mrk-2d757d57eea144a58aeb03f162793103", "smart_contract_address_unencrypted": "0x31551311408e4428b82e1acf042217a5446ff490", "smart_contract_encryption_key": "ENC[AES256_GCM,data:ILQVdF18cdDg7AyaFks4f1GBQ2B3GfzYBSqyz2s5ANcZly9v07yJkr7roFMX1vYfuFtKcWHl5fqQWcbEZ0o7LQ==,iv:MorAAGzt46ue4svn+t02AcecYSDyvLRWGZKR4Xog5qE=,tag:9QS+ZKIGdScqgaw5oceiWA==,type:str]", "rpc_provider_url": "ENC[AES256_GCM,data:6BFFka9TWP0gBWJtFtUoFnYawaQhGEPppXsmNvHWUESWVyy+7iXqH9Rk5YBeEavT482RsEVUyYZTXzIL8x0xZDLl4ltckYBa/wU8pvc4GgM+8egUQ/ue35T1AQ==,iv:PBcTvMBVnwOADi/5BaqF0gw2h/pOdBL6rx9njShFyT0=,tag:PwCRHWOD+WgElLW4F15IBQ==,type:str]", @@ -12,8 +12,8 @@ "aws_profile": "" } ], - "lastmodified": "2026-01-13T13:28:42Z", - "mac": "ENC[AES256_GCM,data:XHKy9XYUyO2JhtcZPs5XyrF57P88OJTSVuXRc5qJoVrwTxF2ktRgzXQmxrS0udO/6RSNjVDLmOw/5wyhOQTYGkDhAMil3D0F7JBNc/oU3mC1Y6zqMI+RSFLD08xDsXUGgQNPI/iMtOaXKrtKqim7naYrikTeJn3WPKcSWNwJIfs=,iv:RiiImAiWSLv9smXsfs/5fWE2ttiv1Bim0yFPxz1m088=,tag:Fy/okHzYD5GRbPwcqVoMgw==,type:str]", + "lastmodified": "2026-02-04T16:31:08Z", + "mac": "ENC[AES256_GCM,data:0O8RTOCdk/N1pd2FNV+eHpbc9zms6HPOhm64vcvTEzS525K6Wwu/3amlS251XaLCkIypjxiuJDsVzgDQY/xnWgFEQUO28Ge6uAVO23xWXAqgAM9DxHneceXXjE6N3vOTgjWUBrw60r5uqY5XzfJwpABJmWZ97j2X/JaBJJZ/Iws=,iv:DzQAzAp9T8ASmZ4olAKsxaiyLCy/Y5YKhtev+09obQI=,tag:e11p8y3dwVIMOpafr9W7Qg==,type:str]", "unencrypted_suffix": "_unencrypted", "version": "3.10.2" }