Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for AWS KMS #1599

Merged
merged 37 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bb328f1
add kms support
Salka1988 Feb 12, 2025
95aaa27
fund kms wallet
Salka1988 Feb 12, 2025
db5dd0a
add tests
Salka1988 Feb 12, 2025
5b77e23
fmt
Salka1988 Feb 12, 2025
7f5939d
refactor
Salka1988 Feb 12, 2025
1a83c2f
addd google kms client
Salka1988 Feb 13, 2025
ec7b012
update
Salka1988 Feb 24, 2025
52d0e3d
update
Salka1988 Feb 24, 2025
4f2bf78
Merge branch 'master' into feat/aws_google_kms_support
Salka1988 Feb 24, 2025
6950675
update
Salka1988 Feb 24, 2025
a4ca73e
update
Salka1988 Feb 24, 2025
d97edf4
update
Salka1988 Feb 24, 2025
141178b
update
Salka1988 Feb 24, 2025
71cc965
update
Salka1988 Feb 24, 2025
1715c37
update
Salka1988 Feb 24, 2025
91a9870
update
Salka1988 Feb 24, 2025
da5f359
update
Salka1988 Feb 24, 2025
e61d479
Merge branch 'master' into feat/aws_google_kms_support
Salka1988 Feb 26, 2025
911cb1d
update
Salka1988 Feb 26, 2025
e9cab4e
update
Salka1988 Feb 27, 2025
5b35ef6
update
Salka1988 Feb 27, 2025
268a429
add fix
Salka1988 Feb 27, 2025
0d177fd
add fix
Salka1988 Feb 27, 2025
162ddec
Update e2e/Cargo.toml
Salka1988 Feb 27, 2025
a31ba56
Update e2e/Cargo.toml
Salka1988 Feb 27, 2025
4943694
Update e2e/tests/aws.rs
Salka1988 Feb 27, 2025
9f15169
Update packages/fuels-accounts/Cargo.toml
Salka1988 Feb 27, 2025
da68a3b
Update e2e/Cargo.toml
Salka1988 Feb 27, 2025
4b6e5ce
Update e2e/tests/aws.rs
Salka1988 Feb 27, 2025
dc41cb2
add
Salka1988 Feb 27, 2025
07b2185
Merge branch 'feat/aws_google_kms_support' of github.com:FuelLabs/fue…
Salka1988 Feb 27, 2025
d40dbe5
fix
Salka1988 Feb 27, 2025
0b8a075
fix
Salka1988 Feb 28, 2025
5282904
fix
Salka1988 Feb 28, 2025
6bab0fd
fix
Salka1988 Feb 28, 2025
57da2fc
fix
Salka1988 Mar 3, 2025
a8c86cc
fix
Salka1988 Mar 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ jobs:

- name: Check for typos
if: ${{ matrix.command == 'check_typos' }}
uses: crate-ci/typos@v1.20.3
uses: crate-ci/typos@v1.29.5

publish:
needs:
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ octocrab = { version = "0.43", default-features = false }
dotenv = { version = "0.15", default-features = false }
toml = { version = "0.8", default-features = false }
mockall = { version = "0.13", default-features = false }
aws-config = { version = "1", default-features = false }
aws-sdk-kms = { version = "1", default-features = false }
testcontainers = { version = "0.23", default-features = false }
k256 = { version = "0.13", default-features = false }

# Dependencies from the `fuel-core` repository:
fuel-core = { version = "0.41.7", default-features = false, features = [
Expand Down
2 changes: 1 addition & 1 deletion _typos.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[files]
extend-exclude = ["packages/fuels-accounts/src/schema/schema.sdl"]
extend-exclude = ["packages/fuels-accounts/src/schema/schema.sdl"]
3 changes: 3 additions & 0 deletions docs/spell-check-custom-words.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
KMS
kms
Kms
ABI
ABIs
ASM
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Managing wallets](./wallets/index.md)
- [Creating a wallet from a private key](./wallets/private-keys.md)
- [Creating a wallet from mnemonic phrases](./wallets/mnemonic-wallet.md)
- [KMS wallets](./wallets/kms-wallets.md)
- [Wallet Access](./wallets/access.md)
- [Encrypting and storing wallets](./wallets/encrypting-and-storing.md)
- [Checking balances and coins](./wallets/checking-balances-and-coins.md)
Expand Down
7 changes: 7 additions & 0 deletions docs/src/wallets/kms-wallets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Using AWS KMS Wallets

AWS Key Management Service (KMS) provides a secure way to manage cryptographic keys for your Fuel wallets. Rather than storing private keys locally, AWS KMS wallets use AWS's secure infrastructure to handle key storage and signing operations.

```rust,ignore
{{#include ../../../e2e/tests/aws.rs:use_kms_wallet}}
```
8 changes: 6 additions & 2 deletions e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ chrono = { workspace = true }
fuel-asm = { workspace = true }
# TODO: [issue](https://github.com/FuelLabs/fuels-rs/issues/1375) needs to be removed, `ScriptTransaction` and `CreateTransaction` in `fuels` use `fuel_tx::Input` but don't reexport or convert it into a `fuels` owned type
fuel-tx = { workspace = true }
fuels = { workspace = true }
# used in test assertions
tai64 = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["test-util"] }

[build-dependencies]
anyhow = { workspace = true, features = ["std"] }
Expand All @@ -32,6 +30,12 @@ reqwest = { workspace = true, features = ["blocking", "default-tls"] }
semver = { workspace = true }
tar = { workspace = true }

[dependencies]
anyhow = { workspace = true }
fuels = { workspace = true, features = ["kms-signer", "test-helpers"] }
testcontainers = { workspace = true }
tokio = { workspace = true, features = ["test-util"] }

[features]
default = ["fuels/default", "coin-cache"]
fuel-core-lib = ["fuels/fuel-core-lib"]
Expand Down
167 changes: 167 additions & 0 deletions e2e/src/aws_kms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use fuels::accounts::kms::{
aws_config::{defaults, BehaviorVersion, Region},
aws_sdk_kms::{
config::Credentials,
types::{KeySpec, KeyUsageType},
Client,
},
KmsKey,
};
use fuels::prelude::Error;
use fuels::types::errors::Context;
use fuels::types::errors::Result;
use testcontainers::{core::ContainerPort, runners::AsyncRunner};
use tokio::io::AsyncBufReadExt;

#[derive(Default)]
pub struct AwsKms {
show_logs: bool,
}

struct AwsKmsImage;

impl testcontainers::Image for AwsKmsImage {
fn name(&self) -> &str {
"localstack/localstack"
}

fn tag(&self) -> &str {
"latest"
}

fn ready_conditions(&self) -> Vec<testcontainers::core::WaitFor> {
vec![testcontainers::core::WaitFor::message_on_stdout("Ready.")]
}

fn expose_ports(&self) -> &[ContainerPort] {
&[ContainerPort::Tcp(4566)]
}
}

impl AwsKms {
pub fn with_show_logs(mut self, show_logs: bool) -> Self {
self.show_logs = show_logs;
self
}

pub async fn start(self) -> Result<AwsKmsProcess> {
let container = AwsKmsImage
.start()
.await
.map_err(|e| Error::Other(e.to_string()))
.with_context(|| "Failed to start KMS container")?;

if self.show_logs {
spawn_log_printer(&container);
}

let port = container
.get_host_port_ipv4(4566)
.await
.map_err(|e| Error::Other(e.to_string()))?;
let url = format!("http://localhost:{}", port);

let credentials = Credentials::new("test", "test", None, None, "Static Test Credentials");
let region = Region::new("us-east-1");

let config = defaults(BehaviorVersion::latest())
.credentials_provider(credentials)
.endpoint_url(url.clone())
.region(region)
.load()
.await;

let client = Client::new(&config);

Ok(AwsKmsProcess {
_container: container,
client,
url,
})
}
}

fn spawn_log_printer(container: &testcontainers::ContainerAsync<AwsKmsImage>) {
let stderr = container.stderr(true);
let stdout = container.stdout(true);
tokio::spawn(async move {
let mut stderr_lines = stderr.lines();
let mut stdout_lines = stdout.lines();

let mut other_stream_closed = false;
loop {
tokio::select! {
stderr_result = stderr_lines.next_line() => {
match stderr_result {
Ok(Some(line)) => eprintln!("KMS (stderr): {}", line),
Ok(None) if other_stream_closed => break,
Ok(None) => other_stream_closed = true,
Err(e) => {
eprintln!("KMS: Error reading from stderr: {:?}", e);
break;
}
}
}
stdout_result = stdout_lines.next_line() => {
match stdout_result {
Ok(Some(line)) => eprintln!("KMS (stdout): {}", line),
Ok(None) if other_stream_closed => break,
Ok(None) => other_stream_closed = true,
Err(e) => {
eprintln!("KMS: Error reading from stdout: {:?}", e);
break;
}
}
}
}
}

Ok::<(), std::io::Error>(())
});
}

pub struct AwsKmsProcess {
_container: testcontainers::ContainerAsync<AwsKmsImage>,
client: Client,
url: String,
}

impl AwsKmsProcess {
pub async fn create_key(&self) -> anyhow::Result<KmsTestKey> {
let response = self
.client
.create_key()
.key_usage(KeyUsageType::SignVerify)
.key_spec(KeySpec::EccSecgP256K1)
.send()
.await?;

let id = response
.key_metadata
.and_then(|metadata| metadata.arn)
.ok_or_else(|| anyhow::anyhow!("key arn missing from response"))?;

let kms_key = KmsKey::new(id.clone(), &self.client).await?;

Ok(KmsTestKey {
id,
kms_key,
url: self.url.clone(),
})
}

pub fn client(&self) -> &Client {
&self.client
}

pub fn url(&self) -> &str {
&self.url
}
}

#[derive(Debug, Clone)]
pub struct KmsTestKey {
pub id: String,
pub kms_key: KmsKey,
pub url: String,
}
6 changes: 6 additions & 0 deletions e2e/src/e2e_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use crate::aws_kms::{AwsKms, AwsKmsProcess};
use fuels::types::errors::Result;

pub async fn start_aws_kms(logs: bool) -> Result<AwsKmsProcess> {
AwsKms::default().with_show_logs(logs).start().await
}
2 changes: 2 additions & 0 deletions e2e/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod aws_kms;
pub mod e2e_helpers;
68 changes: 68 additions & 0 deletions e2e/tests/aws.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#[cfg(test)]
mod tests {
use anyhow::Result;
use e2e::e2e_helpers::start_aws_kms;
use fuels::accounts::kms::AwsWallet;
use fuels::accounts::{Account, ViewOnlyAccount};
use fuels::prelude::{
launch_provider_and_get_wallet, AssetId, Contract, LoadConfiguration, TxPolicies,
};
use fuels::types::errors::Context;

#[tokio::test(flavor = "multi_thread")]
async fn fund_aws_wallet() -> Result<()> {
let kms = start_aws_kms(false).await?;
let wallet = launch_provider_and_get_wallet().await?;

let amount = 500000000;
let key = kms.create_key().await?;
let address = key.kms_key.address().clone();

wallet
.transfer(&address, amount, AssetId::zeroed(), TxPolicies::default())
.await
.context("Failed to transfer funds")?;

let your_kms_key_id = key.id;
let provider = wallet.provider().expect("No provider found").clone();

// ANCHOR: use_kms_wallet
let wallet = AwsWallet::with_kms_key(your_kms_key_id, kms.client(), Some(provider)).await?;
// ANCHOR_END: use_kms_wallet

let total_base_balance = wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(total_base_balance, amount);
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn deploy_contract() -> Result<()> {
let kms = start_aws_kms(false).await?;

let wallet = launch_provider_and_get_wallet().await?;

let amount = 500000000;
let key = kms.create_key().await?;
let address = key.kms_key.address().clone();

wallet
.transfer(&address, amount, AssetId::zeroed(), TxPolicies::default())
.await
.context("Failed to transfer funds")?;

let your_kms_key_id = key.id;
let provider = wallet.provider().expect("No provider found").clone();

let aws_wallet =
&AwsWallet::with_kms_key(your_kms_key_id, kms.client(), Some(provider)).await?;

Contract::load_from(
"../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(aws_wallet, TxPolicies::default())
.await?;

Ok(())
}
}
8 changes: 7 additions & 1 deletion packages/fuels-accounts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ description = "Fuel Rust SDK accounts."

[dependencies]
async-trait = { workspace = true, default-features = false }
aws-config = { workspace = true, features = [
"behavior-version-latest",
], optional = true }
aws-sdk-kms = { workspace = true, features = ["default"], optional = true }
chrono = { workspace = true }
cynic = { workspace = true, optional = true }
elliptic-curve = { workspace = true, default-features = false }
Expand All @@ -22,6 +26,7 @@ fuel-tx = { workspace = true }
fuel-types = { workspace = true, features = ["random"] }
fuels-core = { workspace = true, default-features = false }
itertools = { workspace = true }
k256 = { workspace = true, features = ["ecdsa-core"] }
rand = { workspace = true, default-features = false }
semver = { workspace = true }
tai64 = { workspace = true, features = ["serde"] }
Expand All @@ -30,8 +35,8 @@ tokio = { workspace = true, features = ["full"], optional = true }
zeroize = { workspace = true, features = ["derive"] }

[dev-dependencies]
mockall = { workspace = true, default-features = false }
fuel-tx = { workspace = true, features = ["test-helpers", "random"] }
mockall = { workspace = true, default-features = false }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["test-util"] }

Expand All @@ -46,3 +51,4 @@ std = [
"dep:cynic",
]
test-helpers = []
kms-signer = ["dep:aws-sdk-kms", "dep:aws-config"]
3 changes: 3 additions & 0 deletions packages/fuels-accounts/src/kms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod aws;

pub use aws::*;
5 changes: 5 additions & 0 deletions packages/fuels-accounts/src/kms/aws.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod wallet;
pub use wallet::*;

pub use aws_config;
pub use aws_sdk_kms;
Loading
Loading