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

[DO NOT MERGE YET] Develop -> Main #155

Merged
merged 4 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 9 additions & 13 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,18 @@ jobs:
name: Test
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
repository: "near/pagoda-relayer-rs-fastauth"
ref: "mpc/entrypoint"
path: "relayer"
ssh-key: ${{ secrets.RELAYER_DEPLOY_SSH_KEY }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Relayer Docker Image
run: |
docker pull ghcr.io/near/pagoda-relayer-rs-fastauth
docker tag ghcr.io/near/pagoda-relayer-rs-fastauth pagoda-relayer-rs-fastauth
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Docker image
uses: docker/build-push-action@v4
with:
context: relayer
tags: pagoda-relayer-rs-fastauth
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
Expand Down
14 changes: 12 additions & 2 deletions DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Make sure that:
* You have a GCP Project (its ID will be referred to as `GCP_PROJECT_ID` below, should look something like `pagoda-discovery-platform-dev`)
* `GCP_PROJECT_ID` has the following services enabled:
* `Artifact Registry`
* `Cloud Run`
* `Cloud Run Admin API` (can be enabled by trying to create a Cloud Run instance, no need to proceed with creation after you pressed the `CREATE SERVICE` button)
* `Datastore` (should also be initialized with the default database)
* `Secret Manager`
* You have a service account dedicated to mpc-recovery (will be referred to as `GCP_SERVICE_ACCOUNT` below, should look something like `mpc-recovery@pagoda-discovery-platform-dev.iam.gserviceaccount.com`).
Expand All @@ -26,6 +26,16 @@ Make sure that:
4. Press `ADD KEY` and then `Create new key`.
5. Choose `JSON` and press `CREATE`.
6. Save the keys somewhere to your filesystem, we will refer to its location as `GCP_SERVICE_ACCOUNT_KEY_PATH`.

## Requirements

⚠️ **Warning: You must use an x86 machine, M1 will not work**

You need Rust 1.68 or later. Update your `rustc` by running:

```
$ rustup install stable
```

## Configuration

Expand Down Expand Up @@ -89,7 +99,7 @@ $ gcloud run deploy <GCP_CLOUD_RUN_SERVICE> \
--memory=2Gi \
--min-instances=1 \
--max-instances=1 \
--set-env-vars=MPC_RECOVERY_NODE_ID=<MPC_NODE_ID>,MPC_RECOVERY_WEB_PORT=3000,RUST_LOG=mpc_recovery=debug,MPC_RECOVERY_GCP_PROJECT_ID=<GCP_PROJECT_ID> \
--set-env-vars=MPC_RECOVERY_NODE_ID=<MPC_NODE_ID>,MPC_RECOVERY_GCP_PROJECT_ID=<GCP_PROJECT_ID>,MPC_RECOVERY_WEB_PORT=3000,RUST_LOG=mpc_recovery=debug,PAGODA_FIREBASE_AUDIENCE_ID=near-fastauth-prod \
--set-secrets=MPC_RECOVERY_SK_SHARE=<GCP_SM_KEY_NAME>:latest,MPC_RECOVERY_CIPHER_KEY=<GCP_SM_CIPHER_NAME>:latest \
--no-cpu-throttling \
--region=<GCP_REGION> \
Expand Down
57 changes: 33 additions & 24 deletions integration-tests/tests/mpc/negative.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{account, check, key, token, with_nodes};
use hyper::StatusCode;
use mpc_recovery::msg::{AddKeyRequest, AddKeyResponse, NewAccountRequest, NewAccountResponse};
use mpc_recovery::{
msg::{AddKeyRequest, AddKeyResponse, NewAccountRequest, NewAccountResponse},
transaction::CreateAccountOptions,
};
use std::time::Duration;

#[tokio::test]
Expand All @@ -11,12 +14,18 @@ async fn test_invalid_token() -> anyhow::Result<()> {
let user_public_key = key::random();
let oidc_token = token::valid_random();

let create_account_options = CreateAccountOptions {
full_access_keys: Some(vec![user_public_key.clone().parse().unwrap()]),
limited_access_keys: None,
contract_bytes: None,
};

let (status_code, new_acc_response) = ctx
.leader_node
.new_account(NewAccountRequest {
near_account_id: account_id.to_string(),
create_account_options: create_account_options.clone(),
oidc_token: token::invalid(),
public_key: user_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::UNAUTHORIZED);
Expand All @@ -27,16 +36,16 @@ async fn test_invalid_token() -> anyhow::Result<()> {
.leader_node
.new_account(NewAccountRequest {
near_account_id: account_id.to_string(),
create_account_options,
oidc_token: oidc_token.clone(),
public_key: user_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::OK);
assert!(matches!(new_acc_response, NewAccountResponse::Ok {
user_public_key: user_pk,
create_account_options: _,
user_recovery_public_key: _,
near_account_id: acc_id,
} if user_pk == user_public_key && acc_id == account_id.to_string()
} if acc_id == account_id.to_string()
));

tokio::time::sleep(Duration::from_millis(2000)).await;
Expand Down Expand Up @@ -93,12 +102,18 @@ async fn test_malformed_account_id() -> anyhow::Result<()> {
let user_public_key = key::random();
let oidc_token = token::valid_random();

let create_account_options = CreateAccountOptions {
full_access_keys: Some(vec![user_public_key.clone().parse().unwrap()]),
limited_access_keys: None,
contract_bytes: None,
};

let (status_code, new_acc_response) = ctx
.leader_node
.new_account(NewAccountRequest {
near_account_id: malformed_account_id.to_string(),
create_account_options: create_account_options.clone(),
oidc_token: oidc_token.clone(),
public_key: user_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::BAD_REQUEST);
Expand All @@ -111,16 +126,16 @@ async fn test_malformed_account_id() -> anyhow::Result<()> {
.leader_node
.new_account(NewAccountRequest {
near_account_id: account_id.to_string(),
create_account_options: create_account_options.clone(),
oidc_token: oidc_token.clone(),
public_key: user_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::OK);
assert!(matches!(new_acc_response, NewAccountResponse::Ok {
user_public_key: user_pk,
create_account_options: _,
user_recovery_public_key: _,
near_account_id: acc_id,
} if user_pk == user_public_key && acc_id == account_id.to_string()
} if acc_id == account_id.to_string()
));

tokio::time::sleep(Duration::from_millis(2000)).await;
Expand Down Expand Up @@ -175,35 +190,29 @@ async fn test_malformed_public_key() -> anyhow::Result<()> {
let account_id = account::random(ctx.worker)?;
let malformed_public_key = key::malformed();
let oidc_token = token::valid_random();

let (status_code, new_acc_response) = ctx
.leader_node
.new_account(NewAccountRequest {
near_account_id: account_id.to_string(),
oidc_token: oidc_token.clone(),
public_key: malformed_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::BAD_REQUEST);
assert!(matches!(new_acc_response, NewAccountResponse::Err { .. }));

let user_public_key = key::random();

let create_account_options = CreateAccountOptions {
full_access_keys: Some(vec![user_public_key.clone().parse().unwrap()]),
limited_access_keys: None,
contract_bytes: None,
};

// Check that the service is still available
let (status_code, new_acc_response) = ctx
.leader_node
.new_account(NewAccountRequest {
near_account_id: account_id.to_string(),
create_account_options,
oidc_token: oidc_token.clone(),
public_key: user_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::OK);
assert!(matches!(new_acc_response, NewAccountResponse::Ok {
user_public_key: user_pk,
create_account_options: _,
user_recovery_public_key: _,
near_account_id: acc_id,
} if user_pk == user_public_key && acc_id == account_id.to_string()
} if acc_id == account_id.to_string()
));

tokio::time::sleep(Duration::from_millis(2000)).await;
Expand Down
91 changes: 80 additions & 11 deletions integration-tests/tests/mpc/positive.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::{account, check, key, token, with_nodes};
use anyhow::anyhow;
use ed25519_dalek::Verifier;
use hyper::StatusCode;
use mpc_recovery::{
msg::{AddKeyRequest, AddKeyResponse, NewAccountRequest, NewAccountResponse},
oauth::get_test_claims,
transaction::{call, sign, to_dalek_combined_public_key},
transaction::{
call, sign, to_dalek_combined_public_key, CreateAccountOptions, LimitedAccessKey,
},
};
use rand::{distributions::Alphanumeric, Rng};
use std::time::Duration;
use workspaces::types::AccessKeyPermission;

#[tokio::test]
async fn test_trio() -> anyhow::Result<()> {
Expand Down Expand Up @@ -56,21 +60,27 @@ async fn test_basic_action() -> anyhow::Result<()> {
let user_public_key = key::random();
let oidc_token = token::valid_random();

let create_account_options = CreateAccountOptions {
full_access_keys: Some(vec![user_public_key.clone().parse().unwrap()]),
limited_access_keys: None,
contract_bytes: None,
};

// Create account
let (status_code, new_acc_response) = ctx
.leader_node
.new_account(NewAccountRequest {
near_account_id: account_id.to_string(),
create_account_options,
oidc_token: oidc_token.clone(),
public_key: user_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::OK);
assert!(matches!(new_acc_response, NewAccountResponse::Ok {
user_public_key: user_pk,
create_account_options: _,
user_recovery_public_key: _,
near_account_id: acc_id,
} if user_pk == user_public_key && acc_id == account_id.to_string()
} if acc_id == account_id.to_string()
));

tokio::time::sleep(Duration::from_millis(2000)).await;
Expand Down Expand Up @@ -127,50 +137,109 @@ async fn test_random_recovery_keys() -> anyhow::Result<()> {
with_nodes(4, |ctx| {
Box::pin(async move {
let account_id = account::random(ctx.worker)?;
let user_public_key = key::random();
let user_full_access_key = key::random();

let user_limited_access_key = LimitedAccessKey {
public_key: key::random().parse().unwrap(),
allowance: "100".to_string(),
receiver_id: account::random(ctx.worker)?.to_string().parse().unwrap(), // TODO: type issues here
method_names: "method_names".to_string(),
};

let create_account_options = CreateAccountOptions {
full_access_keys: Some(vec![user_full_access_key.clone().parse().unwrap()]),
limited_access_keys: Some(vec![user_limited_access_key.clone()]),
contract_bytes: None,
};

let (status_code, _) = ctx
.leader_node
.new_account(NewAccountRequest {
near_account_id: account_id.to_string(),
create_account_options,
oidc_token: token::valid_random(),
public_key: user_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::OK);

tokio::time::sleep(Duration::from_millis(2000)).await;

let access_keys = ctx.worker.view_access_keys(&account_id).await?;
let recovery_access_key1 = access_keys

let recovery_full_access_key1 = access_keys
.clone()
.into_iter()
.find(|ak| ak.public_key.to_string() != user_public_key)
.find(|ak| {
ak.public_key.to_string() != user_full_access_key
&& ak.public_key.to_string()
!= user_limited_access_key.public_key.to_string()
})
.ok_or_else(|| anyhow::anyhow!("missing recovery access key"))?;

match recovery_full_access_key1.access_key.permission {
AccessKeyPermission::FullAccess => (),
AccessKeyPermission::FunctionCall(_) => {
return Err(anyhow!(
"Got a limited access key when we expected a full access key"
))
}
};

let la_key = access_keys
.into_iter()
.find(|ak| {
ak.public_key.to_string() == user_limited_access_key.public_key.to_string()
})
.ok_or_else(|| anyhow::anyhow!("missing limited access key"))?;

match la_key.access_key.permission {
AccessKeyPermission::FullAccess => {
return Err(anyhow!(
"Got a full access key when we expected a limited access key"
))
}
AccessKeyPermission::FunctionCall(fc) => {
assert_eq!(
fc.receiver_id,
user_limited_access_key.receiver_id.to_string()
);
assert_eq!(
fc.method_names.first().unwrap(),
&user_limited_access_key.method_names.to_string()
);
}
};

// Generate another user
let account_id = account::random(ctx.worker)?;
let user_public_key = key::random();

let create_account_options = CreateAccountOptions {
full_access_keys: Some(vec![user_public_key.clone().parse().unwrap()]),
limited_access_keys: None,
contract_bytes: None,
};

let (status_code, _) = ctx
.leader_node
.new_account(NewAccountRequest {
near_account_id: account_id.to_string(),
create_account_options,
oidc_token: token::valid_random(),
public_key: user_public_key.clone(),
})
.await?;
assert_eq!(status_code, StatusCode::OK);

tokio::time::sleep(Duration::from_millis(2000)).await;

let access_keys = ctx.worker.view_access_keys(&account_id).await?;
let recovery_access_key2 = access_keys
let recovery_full_access_key2 = access_keys
.into_iter()
.find(|ak| ak.public_key.to_string() != user_public_key)
.ok_or_else(|| anyhow::anyhow!("missing recovery access key"))?;

assert_ne!(
recovery_access_key1.public_key, recovery_access_key2.public_key,
recovery_full_access_key1.public_key, recovery_full_access_key2.public_key,
"MPC recovery should generate random recovery keys for each user"
);

Expand Down
Loading