Compare commits
4 Commits
win-servic
...
366a58f5eb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
366a58f5eb | ||
|
|
5141ac4f55 | ||
|
|
f6f4f81acb | ||
|
|
805d5691d6 |
62
mise.lock
62
mise.lock
@@ -8,18 +8,10 @@ backend = "aqua:ast-grep/ast-grep"
|
|||||||
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-x64"]
|
[tools.ast-grep."platforms.linux-x64"]
|
||||||
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.macos-arm64"]
|
[tools.ast-grep."platforms.macos-arm64"]
|
||||||
checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620"
|
checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"
|
||||||
@@ -40,6 +32,10 @@ backend = "cargo:cargo-audit"
|
|||||||
version = "0.13.9"
|
version = "0.13.9"
|
||||||
backend = "cargo:cargo-edit"
|
backend = "cargo:cargo-edit"
|
||||||
|
|
||||||
|
[[tools."cargo:cargo-features"]]
|
||||||
|
version = "1.0.0"
|
||||||
|
backend = "cargo:cargo-features"
|
||||||
|
|
||||||
[[tools."cargo:cargo-features-manager"]]
|
[[tools."cargo:cargo-features-manager"]]
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
backend = "cargo:cargo-features-manager"
|
backend = "cargo:cargo-features-manager"
|
||||||
@@ -53,13 +49,21 @@ version = "0.9.126"
|
|||||||
backend = "cargo:cargo-nextest"
|
backend = "cargo:cargo-nextest"
|
||||||
|
|
||||||
[[tools."cargo:cargo-shear"]]
|
[[tools."cargo:cargo-shear"]]
|
||||||
version = "1.11.2"
|
version = "1.9.1"
|
||||||
backend = "cargo:cargo-shear"
|
backend = "cargo:cargo-shear"
|
||||||
|
|
||||||
[[tools."cargo:cargo-vet"]]
|
[[tools."cargo:cargo-vet"]]
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
backend = "cargo:cargo-vet"
|
backend = "cargo:cargo-vet"
|
||||||
|
|
||||||
|
[[tools."cargo:diesel-cli"]]
|
||||||
|
version = "2.3.6"
|
||||||
|
backend = "cargo:diesel-cli"
|
||||||
|
|
||||||
|
[tools."cargo:diesel-cli".options]
|
||||||
|
default-features = "false"
|
||||||
|
features = "sqlite,sqlite-bundled"
|
||||||
|
|
||||||
[[tools."cargo:diesel_cli"]]
|
[[tools."cargo:diesel_cli"]]
|
||||||
version = "2.3.6"
|
version = "2.3.6"
|
||||||
backend = "cargo:diesel_cli"
|
backend = "cargo:diesel_cli"
|
||||||
@@ -68,6 +72,10 @@ backend = "cargo:diesel_cli"
|
|||||||
default-features = "false"
|
default-features = "false"
|
||||||
features = "sqlite,sqlite-bundled"
|
features = "sqlite,sqlite-bundled"
|
||||||
|
|
||||||
|
[[tools."cargo:rinf_cli"]]
|
||||||
|
version = "8.9.1"
|
||||||
|
backend = "cargo:rinf_cli"
|
||||||
|
|
||||||
[[tools.flutter]]
|
[[tools.flutter]]
|
||||||
version = "3.38.9-stable"
|
version = "3.38.9-stable"
|
||||||
backend = "asdf:flutter"
|
backend = "asdf:flutter"
|
||||||
@@ -80,18 +88,10 @@ backend = "aqua:protocolbuffers/protobuf/protoc"
|
|||||||
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
||||||
|
|
||||||
[tools.protoc."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.linux-x64"]
|
[tools.protoc."platforms.linux-x64"]
|
||||||
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
||||||
|
|
||||||
[tools.protoc."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.macos-arm64"]
|
[tools.protoc."platforms.macos-arm64"]
|
||||||
checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed"
|
checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed"
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip"
|
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip"
|
||||||
@@ -109,32 +109,24 @@ version = "3.14.3"
|
|||||||
backend = "core:python"
|
backend = "core:python"
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64"]
|
[tools.python."platforms.linux-arm64"]
|
||||||
checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be"
|
checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64"]
|
[tools.python."platforms.linux-x64"]
|
||||||
checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39"
|
checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
|
|
||||||
[tools.python."platforms.macos-arm64"]
|
[tools.python."platforms.macos-arm64"]
|
||||||
checksum = "sha256:c43aecde4a663aebff99b9b83da0efec506479f1c3f98331442f33d2c43501f9"
|
checksum = "sha256:4703cdf18b26798fde7b49b6b66149674c25f97127be6a10dbcf29309bdcdcdb"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
||||||
|
|
||||||
[tools.python."platforms.macos-x64"]
|
[tools.python."platforms.macos-x64"]
|
||||||
checksum = "sha256:9ab41dbc2f100a2a45d1833b9c11165f51051c558b5213eda9a9731d5948a0c0"
|
checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
||||||
|
|
||||||
[tools.python."platforms.windows-x64"]
|
[tools.python."platforms.windows-x64"]
|
||||||
checksum = "sha256:bbe19034b35b0267176a7442575ae7dc6343480fd4d35598cb7700173d431e09"
|
checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
||||||
|
|
||||||
[[tools.rust]]
|
[[tools.rust]]
|
||||||
version = "1.93.0"
|
version = "1.93.0"
|
||||||
|
|||||||
@@ -2,63 +2,24 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package arbiter.client;
|
package arbiter.client;
|
||||||
|
|
||||||
import "evm.proto";
|
import "client/auth.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "client/evm.proto";
|
||||||
|
import "client/vault.proto";
|
||||||
message ClientInfo {
|
|
||||||
string name = 1;
|
|
||||||
optional string description = 2;
|
|
||||||
optional string version = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
ClientInfo client_info = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallenge {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
int32 nonce = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallengeSolution {
|
|
||||||
bytes signature = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AuthResult {
|
|
||||||
AUTH_RESULT_UNSPECIFIED = 0;
|
|
||||||
AUTH_RESULT_SUCCESS = 1;
|
|
||||||
AUTH_RESULT_INVALID_KEY = 2;
|
|
||||||
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
|
||||||
AUTH_RESULT_APPROVAL_DENIED = 4;
|
|
||||||
AUTH_RESULT_NO_USER_AGENTS_ONLINE = 5;
|
|
||||||
AUTH_RESULT_INTERNAL = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VaultState {
|
|
||||||
VAULT_STATE_UNSPECIFIED = 0;
|
|
||||||
VAULT_STATE_UNBOOTSTRAPPED = 1;
|
|
||||||
VAULT_STATE_SEALED = 2;
|
|
||||||
VAULT_STATE_UNSEALED = 3;
|
|
||||||
VAULT_STATE_ERROR = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ClientRequest {
|
message ClientRequest {
|
||||||
int32 request_id = 4;
|
int32 request_id = 4;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallengeRequest auth_challenge_request = 1;
|
auth.Request auth = 1;
|
||||||
AuthChallengeSolution auth_challenge_solution = 2;
|
vault.Request vault = 2;
|
||||||
google.protobuf.Empty query_vault_state = 3;
|
evm.Request evm = 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClientResponse {
|
message ClientResponse {
|
||||||
optional int32 request_id = 7;
|
optional int32 request_id = 7;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallenge auth_challenge = 1;
|
auth.Response auth = 1;
|
||||||
AuthResult auth_result = 2;
|
vault.Response vault = 2;
|
||||||
arbiter.evm.EvmSignTransactionResponse evm_sign_transaction = 3;
|
evm.Response evm = 3;
|
||||||
arbiter.evm.EvmAnalyzeTransactionResponse evm_analyze_transaction = 4;
|
|
||||||
VaultState vault_state = 6;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
protobufs/client/auth.proto
Normal file
43
protobufs/client/auth.proto
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.client.auth;
|
||||||
|
|
||||||
|
import "shared/client.proto";
|
||||||
|
|
||||||
|
message AuthChallengeRequest {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
arbiter.shared.ClientInfo client_info = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthChallenge {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
int32 nonce = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthChallengeSolution {
|
||||||
|
bytes signature = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuthResult {
|
||||||
|
AUTH_RESULT_UNSPECIFIED = 0;
|
||||||
|
AUTH_RESULT_SUCCESS = 1;
|
||||||
|
AUTH_RESULT_INVALID_KEY = 2;
|
||||||
|
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
||||||
|
AUTH_RESULT_APPROVAL_DENIED = 4;
|
||||||
|
AUTH_RESULT_NO_USER_AGENTS_ONLINE = 5;
|
||||||
|
AUTH_RESULT_INTERNAL = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof payload {
|
||||||
|
AuthChallengeRequest challenge_request = 1;
|
||||||
|
AuthChallengeSolution challenge_solution = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof payload {
|
||||||
|
AuthChallenge challenge = 1;
|
||||||
|
AuthResult result = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
protobufs/client/evm.proto
Normal file
19
protobufs/client/evm.proto
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.client.evm;
|
||||||
|
|
||||||
|
import "evm.proto";
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof payload {
|
||||||
|
arbiter.evm.EvmSignTransactionRequest sign_transaction = 1;
|
||||||
|
arbiter.evm.EvmAnalyzeTransactionRequest analyze_transaction = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof payload {
|
||||||
|
arbiter.evm.EvmSignTransactionResponse sign_transaction = 1;
|
||||||
|
arbiter.evm.EvmAnalyzeTransactionResponse analyze_transaction = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
protobufs/client/vault.proto
Normal file
18
protobufs/client/vault.proto
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.client.vault;
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "shared/vault.proto";
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof payload {
|
||||||
|
google.protobuf.Empty query_state = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof payload {
|
||||||
|
arbiter.shared.VaultState state = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ package arbiter.evm;
|
|||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "shared/evm.proto";
|
||||||
|
|
||||||
enum EvmError {
|
enum EvmError {
|
||||||
EVM_ERROR_UNSPECIFIED = 0;
|
EVM_ERROR_UNSPECIFIED = 0;
|
||||||
@@ -74,70 +75,6 @@ message SpecificGrant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message EtherTransferMeaning {
|
|
||||||
bytes to = 1; // 20-byte Ethereum address
|
|
||||||
bytes value = 2; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
message TokenInfo {
|
|
||||||
string symbol = 1;
|
|
||||||
bytes address = 2; // 20-byte Ethereum address
|
|
||||||
uint64 chain_id = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror of token_transfers::Meaning
|
|
||||||
message TokenTransferMeaning {
|
|
||||||
TokenInfo token = 1;
|
|
||||||
bytes to = 2; // 20-byte Ethereum address
|
|
||||||
bytes value = 3; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mirror of policies::SpecificMeaning
|
|
||||||
message SpecificMeaning {
|
|
||||||
oneof meaning {
|
|
||||||
EtherTransferMeaning ether_transfer = 1;
|
|
||||||
TokenTransferMeaning token_transfer = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Eval error types ---
|
|
||||||
message GasLimitExceededViolation {
|
|
||||||
optional bytes max_gas_fee_per_gas = 1; // U256 as big-endian bytes
|
|
||||||
optional bytes max_priority_fee_per_gas = 2; // U256 as big-endian bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
message EvalViolation {
|
|
||||||
oneof kind {
|
|
||||||
bytes invalid_target = 1; // 20-byte Ethereum address
|
|
||||||
GasLimitExceededViolation gas_limit_exceeded = 2;
|
|
||||||
google.protobuf.Empty rate_limit_exceeded = 3;
|
|
||||||
google.protobuf.Empty volumetric_limit_exceeded = 4;
|
|
||||||
google.protobuf.Empty invalid_time = 5;
|
|
||||||
google.protobuf.Empty invalid_transaction_type = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction was classified but no grant covers it
|
|
||||||
message NoMatchingGrantError {
|
|
||||||
SpecificMeaning meaning = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction was classified and a grant was found, but constraints were violated
|
|
||||||
message PolicyViolationsError {
|
|
||||||
SpecificMeaning meaning = 1;
|
|
||||||
repeated EvalViolation violations = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// top-level error returned when transaction evaluation fails
|
|
||||||
message TransactionEvalError {
|
|
||||||
oneof kind {
|
|
||||||
google.protobuf.Empty contract_creation_not_supported = 1;
|
|
||||||
google.protobuf.Empty unsupported_transaction_type = 2;
|
|
||||||
NoMatchingGrantError no_matching_grant = 3;
|
|
||||||
PolicyViolationsError policy_violations = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- UserAgent grant management ---
|
// --- UserAgent grant management ---
|
||||||
message EvmGrantCreateRequest {
|
message EvmGrantCreateRequest {
|
||||||
SharedSettings shared = 1;
|
SharedSettings shared = 1;
|
||||||
@@ -197,7 +134,7 @@ message EvmSignTransactionRequest {
|
|||||||
message EvmSignTransactionResponse {
|
message EvmSignTransactionResponse {
|
||||||
oneof result {
|
oneof result {
|
||||||
bytes signature = 1; // 65-byte signature: r[32] || s[32] || v[1]
|
bytes signature = 1; // 65-byte signature: r[32] || s[32] || v[1]
|
||||||
TransactionEvalError eval_error = 2;
|
arbiter.shared.evm.TransactionEvalError eval_error = 2;
|
||||||
EvmError error = 3;
|
EvmError error = 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,8 +146,8 @@ message EvmAnalyzeTransactionRequest {
|
|||||||
|
|
||||||
message EvmAnalyzeTransactionResponse {
|
message EvmAnalyzeTransactionResponse {
|
||||||
oneof result {
|
oneof result {
|
||||||
SpecificMeaning meaning = 1;
|
arbiter.shared.evm.SpecificMeaning meaning = 1;
|
||||||
TransactionEvalError eval_error = 2;
|
arbiter.shared.evm.TransactionEvalError eval_error = 2;
|
||||||
EvmError error = 3;
|
EvmError error = 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
protobufs/shared/client.proto
Normal file
9
protobufs/shared/client.proto
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.shared;
|
||||||
|
|
||||||
|
message ClientInfo {
|
||||||
|
string name = 1;
|
||||||
|
optional string description = 2;
|
||||||
|
optional string version = 3;
|
||||||
|
}
|
||||||
68
protobufs/shared/evm.proto
Normal file
68
protobufs/shared/evm.proto
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.shared.evm;
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
message EtherTransferMeaning {
|
||||||
|
bytes to = 1; // 20-byte Ethereum address
|
||||||
|
bytes value = 2; // U256 as big-endian bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
message TokenInfo {
|
||||||
|
string symbol = 1;
|
||||||
|
bytes address = 2; // 20-byte Ethereum address
|
||||||
|
uint64 chain_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirror of token_transfers::Meaning
|
||||||
|
message TokenTransferMeaning {
|
||||||
|
TokenInfo token = 1;
|
||||||
|
bytes to = 2; // 20-byte Ethereum address
|
||||||
|
bytes value = 3; // U256 as big-endian bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirror of policies::SpecificMeaning
|
||||||
|
message SpecificMeaning {
|
||||||
|
oneof meaning {
|
||||||
|
EtherTransferMeaning ether_transfer = 1;
|
||||||
|
TokenTransferMeaning token_transfer = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message GasLimitExceededViolation {
|
||||||
|
optional bytes max_gas_fee_per_gas = 1; // U256 as big-endian bytes
|
||||||
|
optional bytes max_priority_fee_per_gas = 2; // U256 as big-endian bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
message EvalViolation {
|
||||||
|
oneof kind {
|
||||||
|
bytes invalid_target = 1; // 20-byte Ethereum address
|
||||||
|
GasLimitExceededViolation gas_limit_exceeded = 2;
|
||||||
|
google.protobuf.Empty rate_limit_exceeded = 3;
|
||||||
|
google.protobuf.Empty volumetric_limit_exceeded = 4;
|
||||||
|
google.protobuf.Empty invalid_time = 5;
|
||||||
|
google.protobuf.Empty invalid_transaction_type = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction was classified but no grant covers it
|
||||||
|
message NoMatchingGrantError {
|
||||||
|
SpecificMeaning meaning = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction was classified and a grant was found, but constraints were violated
|
||||||
|
message PolicyViolationsError {
|
||||||
|
SpecificMeaning meaning = 1;
|
||||||
|
repeated EvalViolation violations = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// top-level error returned when transaction evaluation fails
|
||||||
|
message TransactionEvalError {
|
||||||
|
oneof kind {
|
||||||
|
google.protobuf.Empty contract_creation_not_supported = 1;
|
||||||
|
google.protobuf.Empty unsupported_transaction_type = 2;
|
||||||
|
NoMatchingGrantError no_matching_grant = 3;
|
||||||
|
PolicyViolationsError policy_violations = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
protobufs/shared/vault.proto
Normal file
11
protobufs/shared/vault.proto
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.shared;
|
||||||
|
|
||||||
|
enum VaultState {
|
||||||
|
VAULT_STATE_UNSPECIFIED = 0;
|
||||||
|
VAULT_STATE_UNBOOTSTRAPPED = 1;
|
||||||
|
VAULT_STATE_SEALED = 2;
|
||||||
|
VAULT_STATE_UNSEALED = 3;
|
||||||
|
VAULT_STATE_ERROR = 4;
|
||||||
|
}
|
||||||
@@ -2,198 +2,27 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package arbiter.user_agent;
|
package arbiter.user_agent;
|
||||||
|
|
||||||
import "client.proto";
|
import "user_agent/auth.proto";
|
||||||
import "evm.proto";
|
import "user_agent/evm.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "user_agent/sdk_client.proto";
|
||||||
|
import "user_agent/vault/vault.proto";
|
||||||
enum KeyType {
|
|
||||||
KEY_TYPE_UNSPECIFIED = 0;
|
|
||||||
KEY_TYPE_ED25519 = 1;
|
|
||||||
KEY_TYPE_ECDSA_SECP256K1 = 2;
|
|
||||||
KEY_TYPE_RSA = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- SDK client management ---
|
|
||||||
|
|
||||||
enum SdkClientError {
|
|
||||||
SDK_CLIENT_ERROR_UNSPECIFIED = 0;
|
|
||||||
SDK_CLIENT_ERROR_ALREADY_EXISTS = 1;
|
|
||||||
SDK_CLIENT_ERROR_NOT_FOUND = 2;
|
|
||||||
SDK_CLIENT_ERROR_HAS_RELATED_DATA = 3; // hard-delete blocked by FK (client has grants or transaction logs)
|
|
||||||
SDK_CLIENT_ERROR_INTERNAL = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientRevokeRequest {
|
|
||||||
int32 client_id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientEntry {
|
|
||||||
int32 id = 1;
|
|
||||||
bytes pubkey = 2;
|
|
||||||
arbiter.client.ClientInfo info = 3;
|
|
||||||
int32 created_at = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientList {
|
|
||||||
repeated SdkClientEntry clients = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientRevokeResponse {
|
|
||||||
oneof result {
|
|
||||||
google.protobuf.Empty ok = 1;
|
|
||||||
SdkClientError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientListResponse {
|
|
||||||
oneof result {
|
|
||||||
SdkClientList clients = 1;
|
|
||||||
SdkClientError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
optional string bootstrap_token = 2;
|
|
||||||
KeyType key_type = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallenge {
|
|
||||||
int32 nonce = 2;
|
|
||||||
reserved 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AuthChallengeSolution {
|
|
||||||
bytes signature = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum AuthResult {
|
|
||||||
AUTH_RESULT_UNSPECIFIED = 0;
|
|
||||||
AUTH_RESULT_SUCCESS = 1;
|
|
||||||
AUTH_RESULT_INVALID_KEY = 2;
|
|
||||||
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
|
||||||
AUTH_RESULT_BOOTSTRAP_REQUIRED = 4;
|
|
||||||
AUTH_RESULT_TOKEN_INVALID = 5;
|
|
||||||
AUTH_RESULT_INTERNAL = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UnsealStart {
|
|
||||||
bytes client_pubkey = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UnsealStartResponse {
|
|
||||||
bytes server_pubkey = 1;
|
|
||||||
}
|
|
||||||
message UnsealEncryptedKey {
|
|
||||||
bytes nonce = 1;
|
|
||||||
bytes ciphertext = 2;
|
|
||||||
bytes associated_data = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BootstrapEncryptedKey {
|
|
||||||
bytes nonce = 1;
|
|
||||||
bytes ciphertext = 2;
|
|
||||||
bytes associated_data = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UnsealResult {
|
|
||||||
UNSEAL_RESULT_UNSPECIFIED = 0;
|
|
||||||
UNSEAL_RESULT_SUCCESS = 1;
|
|
||||||
UNSEAL_RESULT_INVALID_KEY = 2;
|
|
||||||
UNSEAL_RESULT_UNBOOTSTRAPPED = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum BootstrapResult {
|
|
||||||
BOOTSTRAP_RESULT_UNSPECIFIED = 0;
|
|
||||||
BOOTSTRAP_RESULT_SUCCESS = 1;
|
|
||||||
BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED = 2;
|
|
||||||
BOOTSTRAP_RESULT_INVALID_KEY = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VaultState {
|
|
||||||
VAULT_STATE_UNSPECIFIED = 0;
|
|
||||||
VAULT_STATE_UNBOOTSTRAPPED = 1;
|
|
||||||
VAULT_STATE_SEALED = 2;
|
|
||||||
VAULT_STATE_UNSEALED = 3;
|
|
||||||
VAULT_STATE_ERROR = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientConnectionRequest {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
arbiter.client.ClientInfo info = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientConnectionResponse {
|
|
||||||
bool approved = 1;
|
|
||||||
bytes pubkey = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientConnectionCancel {
|
|
||||||
bytes pubkey = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message WalletAccess {
|
|
||||||
int32 wallet_id = 1;
|
|
||||||
int32 sdk_client_id = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientWalletAccess {
|
|
||||||
int32 id = 1;
|
|
||||||
WalletAccess access = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientGrantWalletAccess {
|
|
||||||
repeated WalletAccess accesses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SdkClientRevokeWalletAccess {
|
|
||||||
repeated int32 accesses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListWalletAccessResponse {
|
|
||||||
repeated SdkClientWalletAccess accesses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UserAgentRequest {
|
message UserAgentRequest {
|
||||||
int32 id = 16;
|
int32 id = 16;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallengeRequest auth_challenge_request = 1;
|
auth.Request auth = 1;
|
||||||
AuthChallengeSolution auth_challenge_solution = 2;
|
vault.Request vault = 2;
|
||||||
UnsealStart unseal_start = 3;
|
evm.Request evm = 3;
|
||||||
UnsealEncryptedKey unseal_encrypted_key = 4;
|
sdk_client.Request sdk_client = 4;
|
||||||
google.protobuf.Empty query_vault_state = 5;
|
|
||||||
google.protobuf.Empty evm_wallet_create = 6;
|
|
||||||
google.protobuf.Empty evm_wallet_list = 7;
|
|
||||||
arbiter.evm.EvmGrantCreateRequest evm_grant_create = 8;
|
|
||||||
arbiter.evm.EvmGrantDeleteRequest evm_grant_delete = 9;
|
|
||||||
arbiter.evm.EvmGrantListRequest evm_grant_list = 10;
|
|
||||||
SdkClientConnectionResponse sdk_client_connection_response = 11;
|
|
||||||
SdkClientRevokeRequest sdk_client_revoke = 12;
|
|
||||||
google.protobuf.Empty sdk_client_list = 13;
|
|
||||||
BootstrapEncryptedKey bootstrap_encrypted_key = 14;
|
|
||||||
SdkClientGrantWalletAccess grant_wallet_access = 15;
|
|
||||||
SdkClientRevokeWalletAccess revoke_wallet_access = 17;
|
|
||||||
google.protobuf.Empty list_wallet_access = 18;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserAgentResponse {
|
message UserAgentResponse {
|
||||||
optional int32 id = 16;
|
optional int32 id = 16;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallenge auth_challenge = 1;
|
auth.Response auth = 1;
|
||||||
AuthResult auth_result = 2;
|
vault.Response vault = 2;
|
||||||
UnsealStartResponse unseal_start_response = 3;
|
evm.Response evm = 3;
|
||||||
UnsealResult unseal_result = 4;
|
sdk_client.Response sdk_client = 4;
|
||||||
VaultState vault_state = 5;
|
|
||||||
arbiter.evm.WalletCreateResponse evm_wallet_create = 6;
|
|
||||||
arbiter.evm.WalletListResponse evm_wallet_list = 7;
|
|
||||||
arbiter.evm.EvmGrantCreateResponse evm_grant_create = 8;
|
|
||||||
arbiter.evm.EvmGrantDeleteResponse evm_grant_delete = 9;
|
|
||||||
arbiter.evm.EvmGrantListResponse evm_grant_list = 10;
|
|
||||||
SdkClientConnectionRequest sdk_client_connection_request = 11;
|
|
||||||
SdkClientConnectionCancel sdk_client_connection_cancel = 12;
|
|
||||||
SdkClientRevokeResponse sdk_client_revoke_response = 13;
|
|
||||||
SdkClientListResponse sdk_client_list_response = 14;
|
|
||||||
BootstrapResult bootstrap_result = 15;
|
|
||||||
ListWalletAccessResponse list_wallet_access_response = 17;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
protobufs/user_agent/auth.proto
Normal file
48
protobufs/user_agent/auth.proto
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.user_agent.auth;
|
||||||
|
|
||||||
|
enum KeyType {
|
||||||
|
KEY_TYPE_UNSPECIFIED = 0;
|
||||||
|
KEY_TYPE_ED25519 = 1;
|
||||||
|
KEY_TYPE_ECDSA_SECP256K1 = 2;
|
||||||
|
KEY_TYPE_RSA = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthChallengeRequest {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
optional string bootstrap_token = 2;
|
||||||
|
KeyType key_type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthChallenge {
|
||||||
|
int32 nonce = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AuthChallengeSolution {
|
||||||
|
bytes signature = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuthResult {
|
||||||
|
AUTH_RESULT_UNSPECIFIED = 0;
|
||||||
|
AUTH_RESULT_SUCCESS = 1;
|
||||||
|
AUTH_RESULT_INVALID_KEY = 2;
|
||||||
|
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
||||||
|
AUTH_RESULT_BOOTSTRAP_REQUIRED = 4;
|
||||||
|
AUTH_RESULT_TOKEN_INVALID = 5;
|
||||||
|
AUTH_RESULT_INTERNAL = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof payload {
|
||||||
|
AuthChallengeRequest challenge_request = 1;
|
||||||
|
AuthChallengeSolution challenge_solution = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof payload {
|
||||||
|
AuthChallenge challenge = 1;
|
||||||
|
AuthResult result = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
protobufs/user_agent/evm.proto
Normal file
26
protobufs/user_agent/evm.proto
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.user_agent.evm;
|
||||||
|
|
||||||
|
import "evm.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof payload {
|
||||||
|
google.protobuf.Empty wallet_create = 1;
|
||||||
|
google.protobuf.Empty wallet_list = 2;
|
||||||
|
arbiter.evm.EvmGrantCreateRequest grant_create = 3;
|
||||||
|
arbiter.evm.EvmGrantDeleteRequest grant_delete = 4;
|
||||||
|
arbiter.evm.EvmGrantListRequest grant_list = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof payload {
|
||||||
|
arbiter.evm.WalletCreateResponse wallet_create = 1;
|
||||||
|
arbiter.evm.WalletListResponse wallet_list = 2;
|
||||||
|
arbiter.evm.EvmGrantCreateResponse grant_create = 3;
|
||||||
|
arbiter.evm.EvmGrantDeleteResponse grant_delete = 4;
|
||||||
|
arbiter.evm.EvmGrantListResponse grant_list = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
protobufs/user_agent/sdk_client.proto
Normal file
100
protobufs/user_agent/sdk_client.proto
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.user_agent.sdk_client;
|
||||||
|
|
||||||
|
import "shared/client.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
enum Error {
|
||||||
|
ERROR_UNSPECIFIED = 0;
|
||||||
|
ERROR_ALREADY_EXISTS = 1;
|
||||||
|
ERROR_NOT_FOUND = 2;
|
||||||
|
ERROR_HAS_RELATED_DATA = 3; // hard-delete blocked by FK (client has grants or transaction logs)
|
||||||
|
ERROR_INTERNAL = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RevokeRequest {
|
||||||
|
int32 client_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Entry {
|
||||||
|
int32 id = 1;
|
||||||
|
bytes pubkey = 2;
|
||||||
|
arbiter.shared.ClientInfo info = 3;
|
||||||
|
int32 created_at = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message List {
|
||||||
|
repeated Entry clients = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RevokeResponse {
|
||||||
|
oneof result {
|
||||||
|
google.protobuf.Empty ok = 1;
|
||||||
|
Error error = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListResponse {
|
||||||
|
oneof result {
|
||||||
|
List clients = 1;
|
||||||
|
Error error = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionRequest {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
arbiter.shared.ClientInfo info = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionResponse {
|
||||||
|
bool approved = 1;
|
||||||
|
bytes pubkey = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionCancel {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WalletAccess {
|
||||||
|
int32 wallet_id = 1;
|
||||||
|
int32 sdk_client_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WalletAccessEntry {
|
||||||
|
int32 id = 1;
|
||||||
|
WalletAccess access = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GrantWalletAccess {
|
||||||
|
repeated WalletAccess accesses = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RevokeWalletAccess {
|
||||||
|
repeated int32 accesses = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListWalletAccessResponse {
|
||||||
|
repeated WalletAccessEntry accesses = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof payload {
|
||||||
|
ConnectionResponse connection_response = 1;
|
||||||
|
RevokeRequest revoke = 2;
|
||||||
|
google.protobuf.Empty list = 3;
|
||||||
|
GrantWalletAccess grant_wallet_access = 4;
|
||||||
|
RevokeWalletAccess revoke_wallet_access = 5;
|
||||||
|
google.protobuf.Empty list_wallet_access = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof payload {
|
||||||
|
ConnectionRequest connection_request = 1;
|
||||||
|
ConnectionCancel connection_cancel = 2;
|
||||||
|
RevokeResponse revoke = 3;
|
||||||
|
ListResponse list = 4;
|
||||||
|
ListWalletAccessResponse list_wallet_access = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
protobufs/user_agent/vault/bootstrap.proto
Normal file
24
protobufs/user_agent/vault/bootstrap.proto
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.user_agent.vault.bootstrap;
|
||||||
|
|
||||||
|
message BootstrapEncryptedKey {
|
||||||
|
bytes nonce = 1;
|
||||||
|
bytes ciphertext = 2;
|
||||||
|
bytes associated_data = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BootstrapResult {
|
||||||
|
BOOTSTRAP_RESULT_UNSPECIFIED = 0;
|
||||||
|
BOOTSTRAP_RESULT_SUCCESS = 1;
|
||||||
|
BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED = 2;
|
||||||
|
BOOTSTRAP_RESULT_INVALID_KEY = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
BootstrapEncryptedKey encrypted_key = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
BootstrapResult result = 1;
|
||||||
|
}
|
||||||
37
protobufs/user_agent/vault/unseal.proto
Normal file
37
protobufs/user_agent/vault/unseal.proto
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.user_agent.vault.unseal;
|
||||||
|
|
||||||
|
message UnsealStart {
|
||||||
|
bytes client_pubkey = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UnsealStartResponse {
|
||||||
|
bytes server_pubkey = 1;
|
||||||
|
}
|
||||||
|
message UnsealEncryptedKey {
|
||||||
|
bytes nonce = 1;
|
||||||
|
bytes ciphertext = 2;
|
||||||
|
bytes associated_data = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UnsealResult {
|
||||||
|
UNSEAL_RESULT_UNSPECIFIED = 0;
|
||||||
|
UNSEAL_RESULT_SUCCESS = 1;
|
||||||
|
UNSEAL_RESULT_INVALID_KEY = 2;
|
||||||
|
UNSEAL_RESULT_UNBOOTSTRAPPED = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof payload {
|
||||||
|
UnsealStart start = 1;
|
||||||
|
UnsealEncryptedKey encrypted_key = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof payload {
|
||||||
|
UnsealStartResponse start = 1;
|
||||||
|
UnsealResult result = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
protobufs/user_agent/vault/vault.proto
Normal file
24
protobufs/user_agent/vault/vault.proto
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.user_agent.vault;
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "shared/vault.proto";
|
||||||
|
import "user_agent/vault/bootstrap.proto";
|
||||||
|
import "user_agent/vault/unseal.proto";
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof payload {
|
||||||
|
google.protobuf.Empty query_state = 1;
|
||||||
|
unseal.Request unseal = 2;
|
||||||
|
bootstrap.Request bootstrap = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Response {
|
||||||
|
oneof payload {
|
||||||
|
arbiter.shared.VaultState state = 1;
|
||||||
|
unseal.Response unseal = 2;
|
||||||
|
bootstrap.Response bootstrap = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
143
server/Cargo.lock
generated
143
server/Cargo.lock
generated
@@ -669,56 +669,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is_terminal_polyfill",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"once_cell_polyfill",
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.102"
|
||||||
@@ -780,7 +730,6 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-async",
|
"diesel-async",
|
||||||
@@ -812,7 +761,6 @@ dependencies = [
|
|||||||
"tonic",
|
"tonic",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"windows-service",
|
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -1485,46 +1433,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "4.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
|
||||||
dependencies = [
|
|
||||||
"clap_builder",
|
|
||||||
"clap_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_builder"
|
|
||||||
version = "4.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"clap_lex",
|
|
||||||
"strsim",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_derive"
|
|
||||||
version = "4.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
|
||||||
dependencies = [
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.117",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_lex"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.57"
|
version = "0.1.57"
|
||||||
@@ -1534,12 +1442,6 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.15.11"
|
version = "0.15.11"
|
||||||
@@ -2149,7 +2051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2951,12 +2853,6 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_terminal_polyfill"
|
|
||||||
version = "1.70.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@@ -3290,7 +3186,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3424,12 +3320,6 @@ version = "1.21.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell_polyfill"
|
|
||||||
version = "1.70.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -4395,7 +4285,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4811,7 +4701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5005,7 +4895,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5579,12 +5469,6 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.22.0"
|
version = "1.22.0"
|
||||||
@@ -5791,12 +5675,6 @@ dependencies = [
|
|||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "widestring"
|
|
||||||
version = "1.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -5869,17 +5747,6 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-service"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"widestring",
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-strings"
|
name = "windows-strings"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
ClientMetadata, format_challenge,
|
ClientMetadata, format_challenge,
|
||||||
proto::client::{
|
proto::{
|
||||||
AuthChallengeRequest, AuthChallengeSolution, AuthResult, ClientInfo as ProtoClientInfo,
|
client::{
|
||||||
ClientRequest, client_request::Payload as ClientRequestPayload,
|
ClientRequest,
|
||||||
|
auth::{
|
||||||
|
self as proto_auth, AuthChallenge, AuthChallengeRequest, AuthChallengeSolution,
|
||||||
|
AuthResult, request::Payload as AuthRequestPayload,
|
||||||
|
response::Payload as AuthResponsePayload,
|
||||||
|
},
|
||||||
|
client_request::Payload as ClientRequestPayload,
|
||||||
client_response::Payload as ClientResponsePayload,
|
client_response::Payload as ClientResponsePayload,
|
||||||
},
|
},
|
||||||
|
shared::ClientInfo as ProtoClientInfo,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use ed25519_dalek::Signer as _;
|
use ed25519_dalek::Signer as _;
|
||||||
|
|
||||||
@@ -51,16 +59,16 @@ async fn send_auth_challenge_request(
|
|||||||
transport
|
transport
|
||||||
.send(ClientRequest {
|
.send(ClientRequest {
|
||||||
request_id: next_request_id(),
|
request_id: next_request_id(),
|
||||||
payload: Some(ClientRequestPayload::AuthChallengeRequest(
|
payload: Some(ClientRequestPayload::Auth(proto_auth::Request {
|
||||||
AuthChallengeRequest {
|
payload: Some(AuthRequestPayload::ChallengeRequest(AuthChallengeRequest {
|
||||||
pubkey: key.verifying_key().to_bytes().to_vec(),
|
pubkey: key.verifying_key().to_bytes().to_vec(),
|
||||||
client_info: Some(ProtoClientInfo {
|
client_info: Some(ProtoClientInfo {
|
||||||
name: metadata.name,
|
name: metadata.name,
|
||||||
description: metadata.description,
|
description: metadata.description,
|
||||||
version: metadata.version,
|
version: metadata.version,
|
||||||
}),
|
}),
|
||||||
},
|
})),
|
||||||
)),
|
})),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| AuthError::UnexpectedAuthResponse)
|
.map_err(|_| AuthError::UnexpectedAuthResponse)
|
||||||
@@ -68,7 +76,7 @@ async fn send_auth_challenge_request(
|
|||||||
|
|
||||||
async fn receive_auth_challenge(
|
async fn receive_auth_challenge(
|
||||||
transport: &mut ClientTransport,
|
transport: &mut ClientTransport,
|
||||||
) -> std::result::Result<arbiter_proto::proto::client::AuthChallenge, AuthError> {
|
) -> std::result::Result<AuthChallenge, AuthError> {
|
||||||
let response = transport
|
let response = transport
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
@@ -76,8 +84,11 @@ async fn receive_auth_challenge(
|
|||||||
|
|
||||||
let payload = response.payload.ok_or(AuthError::MissingAuthChallenge)?;
|
let payload = response.payload.ok_or(AuthError::MissingAuthChallenge)?;
|
||||||
match payload {
|
match payload {
|
||||||
ClientResponsePayload::AuthChallenge(challenge) => Ok(challenge),
|
ClientResponsePayload::Auth(response) => match response.payload {
|
||||||
ClientResponsePayload::AuthResult(result) => Err(map_auth_result(result)),
|
Some(AuthResponsePayload::Challenge(challenge)) => Ok(challenge),
|
||||||
|
Some(AuthResponsePayload::Result(result)) => Err(map_auth_result(result)),
|
||||||
|
None => Err(AuthError::MissingAuthChallenge),
|
||||||
|
},
|
||||||
_ => Err(AuthError::UnexpectedAuthResponse),
|
_ => Err(AuthError::UnexpectedAuthResponse),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +96,7 @@ async fn receive_auth_challenge(
|
|||||||
async fn send_auth_challenge_solution(
|
async fn send_auth_challenge_solution(
|
||||||
transport: &mut ClientTransport,
|
transport: &mut ClientTransport,
|
||||||
key: &ed25519_dalek::SigningKey,
|
key: &ed25519_dalek::SigningKey,
|
||||||
challenge: arbiter_proto::proto::client::AuthChallenge,
|
challenge: AuthChallenge,
|
||||||
) -> std::result::Result<(), AuthError> {
|
) -> std::result::Result<(), AuthError> {
|
||||||
let challenge_payload = format_challenge(challenge.nonce, &challenge.pubkey);
|
let challenge_payload = format_challenge(challenge.nonce, &challenge.pubkey);
|
||||||
let signature = key.sign(&challenge_payload).to_bytes().to_vec();
|
let signature = key.sign(&challenge_payload).to_bytes().to_vec();
|
||||||
@@ -93,9 +104,11 @@ async fn send_auth_challenge_solution(
|
|||||||
transport
|
transport
|
||||||
.send(ClientRequest {
|
.send(ClientRequest {
|
||||||
request_id: next_request_id(),
|
request_id: next_request_id(),
|
||||||
payload: Some(ClientRequestPayload::AuthChallengeSolution(
|
payload: Some(ClientRequestPayload::Auth(proto_auth::Request {
|
||||||
|
payload: Some(AuthRequestPayload::ChallengeSolution(
|
||||||
AuthChallengeSolution { signature },
|
AuthChallengeSolution { signature },
|
||||||
)),
|
)),
|
||||||
|
})),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| AuthError::UnexpectedAuthResponse)
|
.map_err(|_| AuthError::UnexpectedAuthResponse)
|
||||||
@@ -113,12 +126,15 @@ async fn receive_auth_confirmation(
|
|||||||
.payload
|
.payload
|
||||||
.ok_or(AuthError::UnexpectedAuthResponse)?;
|
.ok_or(AuthError::UnexpectedAuthResponse)?;
|
||||||
match payload {
|
match payload {
|
||||||
ClientResponsePayload::AuthResult(result)
|
ClientResponsePayload::Auth(response) => match response.payload {
|
||||||
|
Some(AuthResponsePayload::Result(result))
|
||||||
if AuthResult::try_from(result).ok() == Some(AuthResult::Success) =>
|
if AuthResult::try_from(result).ok() == Some(AuthResult::Success) =>
|
||||||
{
|
{
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
ClientResponsePayload::AuthResult(result) => Err(map_auth_result(result)),
|
Some(AuthResponsePayload::Result(result)) => Err(map_auth_result(result)),
|
||||||
|
_ => Err(AuthError::UnexpectedAuthResponse),
|
||||||
|
},
|
||||||
_ => Err(AuthError::UnexpectedAuthResponse),
|
_ => Err(AuthError::UnexpectedAuthResponse),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,60 @@ pub mod transport;
|
|||||||
pub mod url;
|
pub mod url;
|
||||||
|
|
||||||
use base64::{Engine, prelude::BASE64_STANDARD};
|
use base64::{Engine, prelude::BASE64_STANDARD};
|
||||||
use std::{
|
|
||||||
path::PathBuf,
|
|
||||||
sync::{LazyLock, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod proto {
|
pub mod proto {
|
||||||
tonic::include_proto!("arbiter");
|
tonic::include_proto!("arbiter");
|
||||||
|
|
||||||
|
pub mod shared {
|
||||||
|
tonic::include_proto!("arbiter.shared");
|
||||||
|
|
||||||
|
pub mod evm {
|
||||||
|
tonic::include_proto!("arbiter.shared.evm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub mod user_agent {
|
pub mod user_agent {
|
||||||
tonic::include_proto!("arbiter.user_agent");
|
tonic::include_proto!("arbiter.user_agent");
|
||||||
|
|
||||||
|
pub mod auth {
|
||||||
|
tonic::include_proto!("arbiter.user_agent.auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod evm {
|
||||||
|
tonic::include_proto!("arbiter.user_agent.evm");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod sdk_client {
|
||||||
|
tonic::include_proto!("arbiter.user_agent.sdk_client");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod vault {
|
||||||
|
tonic::include_proto!("arbiter.user_agent.vault");
|
||||||
|
|
||||||
|
pub mod bootstrap {
|
||||||
|
tonic::include_proto!("arbiter.user_agent.vault.bootstrap");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod unseal {
|
||||||
|
tonic::include_proto!("arbiter.user_agent.vault.unseal");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod client {
|
pub mod client {
|
||||||
tonic::include_proto!("arbiter.client");
|
tonic::include_proto!("arbiter.client");
|
||||||
|
|
||||||
|
pub mod auth {
|
||||||
|
tonic::include_proto!("arbiter.client.auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod evm {
|
||||||
|
tonic::include_proto!("arbiter.client.evm");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod vault {
|
||||||
|
tonic::include_proto!("arbiter.client.vault");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod evm {
|
pub mod evm {
|
||||||
@@ -31,27 +71,8 @@ pub struct ClientMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub static BOOTSTRAP_PATH: &str = "bootstrap_token";
|
pub static BOOTSTRAP_PATH: &str = "bootstrap_token";
|
||||||
pub const DEFAULT_SERVER_PORT: u16 = 50051;
|
|
||||||
static HOME_OVERRIDE: LazyLock<RwLock<Option<PathBuf>>> = LazyLock::new(|| RwLock::new(None));
|
|
||||||
|
|
||||||
pub fn set_home_path_override(path: Option<PathBuf>) -> Result<(), std::io::Error> {
|
|
||||||
let mut lock = HOME_OVERRIDE
|
|
||||||
.write()
|
|
||||||
.map_err(|_| std::io::Error::other("home path override lock poisoned"))?;
|
|
||||||
*lock = path;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn home_path() -> Result<std::path::PathBuf, std::io::Error> {
|
pub fn home_path() -> Result<std::path::PathBuf, std::io::Error> {
|
||||||
if let Some(path) = HOME_OVERRIDE
|
|
||||||
.read()
|
|
||||||
.map_err(|_| std::io::Error::other("home path override lock poisoned"))?
|
|
||||||
.clone()
|
|
||||||
{
|
|
||||||
std::fs::create_dir_all(&path)?;
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
static ARBITER_HOME: &str = ".arbiter";
|
static ARBITER_HOME: &str = ".arbiter";
|
||||||
let home_dir = std::env::home_dir().ok_or(std::io::Error::new(
|
let home_dir = std::env::home_dir().ok_or(std::io::Error::new(
|
||||||
std::io::ErrorKind::PermissionDenied,
|
std::io::ErrorKind::PermissionDenied,
|
||||||
|
|||||||
@@ -53,11 +53,7 @@ spki.workspace = true
|
|||||||
alloy.workspace = true
|
alloy.workspace = true
|
||||||
prost-types.workspace = true
|
prost-types.workspace = true
|
||||||
arbiter-tokens-registry.path = "../arbiter-tokens-registry"
|
arbiter-tokens-registry.path = "../arbiter-tokens-registry"
|
||||||
clap = { version = "4.6", features = ["derive"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.46.3"
|
insta = "1.46.3"
|
||||||
test-log = { version = "0.2", default-features = false, features = ["trace"] }
|
test-log = { version = "0.2", default-features = false, features = ["trace"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
|
||||||
windows-service = "0.8"
|
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
use std::{
|
|
||||||
net::{Ipv4Addr, SocketAddr, SocketAddrV4},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clap::{Args, Parser, Subcommand};
|
|
||||||
|
|
||||||
const DEFAULT_LISTEN_ADDR: SocketAddr = SocketAddr::V4(SocketAddrV4::new(
|
|
||||||
Ipv4Addr::LOCALHOST,
|
|
||||||
arbiter_proto::DEFAULT_SERVER_PORT,
|
|
||||||
));
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
#[command(name = "arbiter-server")]
|
|
||||||
#[command(about = "Arbiter gRPC server")]
|
|
||||||
pub struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
pub command: Option<Command>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
|
||||||
pub enum Command {
|
|
||||||
/// Run server in foreground mode.
|
|
||||||
Run(RunArgs),
|
|
||||||
/// Manage service lifecycle.
|
|
||||||
Service {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: ServiceCommand,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Args)]
|
|
||||||
pub struct RunArgs {
|
|
||||||
#[arg(long, default_value_t = DEFAULT_LISTEN_ADDR)]
|
|
||||||
pub listen_addr: SocketAddr,
|
|
||||||
#[arg(long)]
|
|
||||||
pub data_dir: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RunArgs {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
listen_addr: DEFAULT_LISTEN_ADDR,
|
|
||||||
data_dir: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
|
||||||
pub enum ServiceCommand {
|
|
||||||
/// Install Windows service in Service Control Manager.
|
|
||||||
Install(ServiceInstallArgs),
|
|
||||||
/// Internal service entrypoint. SCM only.
|
|
||||||
#[command(hide = true)]
|
|
||||||
Run(ServiceRunArgs),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Args)]
|
|
||||||
pub struct ServiceInstallArgs {
|
|
||||||
#[arg(long)]
|
|
||||||
pub start: bool,
|
|
||||||
#[arg(long)]
|
|
||||||
pub data_dir: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Args)]
|
|
||||||
pub struct ServiceRunArgs {
|
|
||||||
#[arg(long, default_value_t = DEFAULT_LISTEN_ADDR)]
|
|
||||||
pub listen_addr: SocketAddr,
|
|
||||||
#[arg(long)]
|
|
||||||
pub data_dir: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::client::{
|
proto::{
|
||||||
ClientRequest, ClientResponse, VaultState as ProtoVaultState,
|
client::{
|
||||||
|
ClientRequest, ClientResponse,
|
||||||
client_request::Payload as ClientRequestPayload,
|
client_request::Payload as ClientRequestPayload,
|
||||||
client_response::Payload as ClientResponsePayload,
|
client_response::Payload as ClientResponsePayload,
|
||||||
|
vault::{self as proto_vault, request::Payload as VaultRequestPayload, response::Payload as VaultResponsePayload},
|
||||||
|
},
|
||||||
|
shared::VaultState as ProtoVaultState,
|
||||||
},
|
},
|
||||||
transport::{Receiver, Sender, grpc::GrpcBi},
|
transport::{Receiver, Sender, grpc::GrpcBi},
|
||||||
};
|
};
|
||||||
@@ -79,7 +83,24 @@ async fn dispatch_inner(
|
|||||||
payload: ClientRequestPayload,
|
payload: ClientRequestPayload,
|
||||||
) -> Result<ClientResponsePayload, Status> {
|
) -> Result<ClientResponsePayload, Status> {
|
||||||
match payload {
|
match payload {
|
||||||
ClientRequestPayload::QueryVaultState(_) => {
|
ClientRequestPayload::Vault(req) => dispatch_vault_request(actor, req).await,
|
||||||
|
payload => {
|
||||||
|
warn!(?payload, "Unsupported post-auth client request");
|
||||||
|
Err(Status::invalid_argument("Unsupported client request"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn dispatch_vault_request(
|
||||||
|
actor: &ActorRef<ClientSession>,
|
||||||
|
req: proto_vault::Request,
|
||||||
|
) -> Result<ClientResponsePayload, Status> {
|
||||||
|
let Some(payload) = req.payload else {
|
||||||
|
return Err(Status::invalid_argument("Missing client vault request payload"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match payload {
|
||||||
|
VaultRequestPayload::QueryState(_) => {
|
||||||
let state = match actor.ask(HandleQueryVaultState {}).await {
|
let state = match actor.ask(HandleQueryVaultState {}).await {
|
||||||
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
||||||
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed,
|
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed,
|
||||||
@@ -90,11 +111,9 @@ async fn dispatch_inner(
|
|||||||
ProtoVaultState::Error
|
ProtoVaultState::Error
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(ClientResponsePayload::VaultState(state.into()))
|
Ok(ClientResponsePayload::Vault(proto_vault::Response {
|
||||||
}
|
payload: Some(VaultResponsePayload::State(state.into())),
|
||||||
payload => {
|
}))
|
||||||
warn!(?payload, "Unsupported post-auth client request");
|
|
||||||
Err(Status::invalid_argument("Unsupported client request"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
ClientMetadata, proto::client::{
|
ClientMetadata,
|
||||||
AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest,
|
proto::{
|
||||||
|
client::{
|
||||||
|
ClientRequest, ClientResponse,
|
||||||
|
auth::{
|
||||||
|
self as proto_auth, AuthChallenge as ProtoAuthChallenge,
|
||||||
|
AuthChallengeRequest as ProtoAuthChallengeRequest,
|
||||||
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
||||||
ClientInfo as ProtoClientInfo, ClientRequest, ClientResponse,
|
request::Payload as AuthRequestPayload, response::Payload as AuthResponsePayload,
|
||||||
|
},
|
||||||
client_request::Payload as ClientRequestPayload,
|
client_request::Payload as ClientRequestPayload,
|
||||||
client_response::Payload as ClientResponsePayload,
|
client_response::Payload as ClientResponsePayload,
|
||||||
}, transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi}
|
},
|
||||||
|
shared::ClientInfo as ProtoClientInfo,
|
||||||
|
},
|
||||||
|
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi}
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
@@ -32,22 +41,20 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response_to_proto(response: auth::Outbound) -> ClientResponsePayload {
|
fn response_to_proto(response: auth::Outbound) -> AuthResponsePayload {
|
||||||
match response {
|
match response {
|
||||||
auth::Outbound::AuthChallenge { pubkey, nonce } => {
|
auth::Outbound::AuthChallenge { pubkey, nonce } => {
|
||||||
ClientResponsePayload::AuthChallenge(ProtoAuthChallenge {
|
AuthResponsePayload::Challenge(ProtoAuthChallenge {
|
||||||
pubkey: pubkey.to_bytes().to_vec(),
|
pubkey: pubkey.to_bytes().to_vec(),
|
||||||
nonce,
|
nonce,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
auth::Outbound::AuthSuccess => {
|
auth::Outbound::AuthSuccess => AuthResponsePayload::Result(ProtoAuthResult::Success.into()),
|
||||||
ClientResponsePayload::AuthResult(ProtoAuthResult::Success.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_to_proto(error: auth::Error) -> ClientResponsePayload {
|
fn error_to_proto(error: auth::Error) -> AuthResponsePayload {
|
||||||
ClientResponsePayload::AuthResult(
|
AuthResponsePayload::Result(
|
||||||
match error {
|
match error {
|
||||||
auth::Error::InvalidChallengeSolution => ProtoAuthResult::InvalidSignature,
|
auth::Error::InvalidChallengeSolution => ProtoAuthResult::InvalidSignature,
|
||||||
auth::Error::ApproveError(auth::ApproveError::Denied) => {
|
auth::Error::ApproveError(auth::ApproveError::Denied) => {
|
||||||
@@ -67,18 +74,20 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
|
|
||||||
async fn send_client_response(
|
async fn send_client_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
payload: ClientResponsePayload,
|
payload: AuthResponsePayload,
|
||||||
) -> Result<(), TransportError> {
|
) -> Result<(), TransportError> {
|
||||||
self.bi
|
self.bi
|
||||||
.send(Ok(ClientResponse {
|
.send(Ok(ClientResponse {
|
||||||
request_id: Some(self.request_tracker.current_request_id()),
|
request_id: Some(self.request_tracker.current_request_id()),
|
||||||
|
payload: Some(ClientResponsePayload::Auth(proto_auth::Response {
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
|
})),
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_auth_result(&mut self, result: ProtoAuthResult) -> Result<(), TransportError> {
|
async fn send_auth_result(&mut self, result: ProtoAuthResult) -> Result<(), TransportError> {
|
||||||
self.send_client_response(ClientResponsePayload::AuthResult(result.into()))
|
self.send_client_response(AuthResponsePayload::Result(result.into()))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,9 +126,25 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let payload = request.payload?;
|
let payload = request.payload?;
|
||||||
|
let ClientRequestPayload::Auth(auth_request) = payload else {
|
||||||
|
let _ = self
|
||||||
|
.bi
|
||||||
|
.send(Err(Status::invalid_argument(
|
||||||
|
"Unsupported client auth request",
|
||||||
|
)))
|
||||||
|
.await;
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Some(payload) = auth_request.payload else {
|
||||||
|
let _ = self
|
||||||
|
.bi
|
||||||
|
.send(Err(Status::invalid_argument("Missing client auth request payload")))
|
||||||
|
.await;
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
match payload {
|
match payload {
|
||||||
ClientRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest {
|
AuthRequestPayload::ChallengeRequest(ProtoAuthChallengeRequest {
|
||||||
pubkey,
|
pubkey,
|
||||||
client_info,
|
client_info,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -143,7 +168,7 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
metadata: client_metadata_from_proto(client_info),
|
metadata: client_metadata_from_proto(client_info),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ClientRequestPayload::AuthChallengeSolution(ProtoAuthChallengeSolution {
|
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution {
|
||||||
signature,
|
signature,
|
||||||
}) => {
|
}) => {
|
||||||
let Ok(signature) = ed25519_dalek::Signature::try_from(signature.as_slice()) else {
|
let Ok(signature) = ed25519_dalek::Signature::try_from(signature.as_slice()) else {
|
||||||
@@ -154,15 +179,6 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
};
|
};
|
||||||
Some(auth::Inbound::AuthChallengeSolution { signature })
|
Some(auth::Inbound::AuthChallengeSolution { signature })
|
||||||
}
|
}
|
||||||
_ => {
|
|
||||||
let _ = self
|
|
||||||
.bi
|
|
||||||
.send(Err(Status::invalid_argument(
|
|
||||||
"Unsupported client auth request",
|
|
||||||
)))
|
|
||||||
.await;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,8 @@ use tokio::sync::mpsc;
|
|||||||
|
|
||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::{
|
proto::{
|
||||||
client::ClientInfo as ProtoClientMetadata,
|
|
||||||
evm::{
|
|
||||||
EvmError as ProtoEvmError, EvmGrantCreateRequest, EvmGrantCreateResponse,
|
|
||||||
EvmGrantDeleteRequest, EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse,
|
|
||||||
GrantEntry, WalletCreateResponse, WalletEntry, WalletList, WalletListResponse,
|
|
||||||
evm_grant_create_response::Result as EvmGrantCreateResult,
|
|
||||||
evm_grant_delete_response::Result as EvmGrantDeleteResult,
|
|
||||||
evm_grant_list_response::Result as EvmGrantListResult,
|
|
||||||
wallet_create_response::Result as WalletCreateResult,
|
|
||||||
wallet_list_response::Result as WalletListResult,
|
|
||||||
},
|
|
||||||
user_agent::{
|
user_agent::{
|
||||||
BootstrapEncryptedKey as ProtoBootstrapEncryptedKey,
|
UserAgentRequest, UserAgentResponse,
|
||||||
BootstrapResult as ProtoBootstrapResult, ListWalletAccessResponse,
|
|
||||||
SdkClientConnectionCancel as ProtoSdkClientConnectionCancel,
|
|
||||||
SdkClientConnectionRequest as ProtoSdkClientConnectionRequest,
|
|
||||||
SdkClientEntry as ProtoSdkClientEntry, SdkClientError as ProtoSdkClientError,
|
|
||||||
SdkClientGrantWalletAccess, SdkClientList as ProtoSdkClientList,
|
|
||||||
SdkClientListResponse as ProtoSdkClientListResponse, SdkClientRevokeWalletAccess,
|
|
||||||
SdkClientWalletAccess, UnsealEncryptedKey as ProtoUnsealEncryptedKey,
|
|
||||||
UnsealResult as ProtoUnsealResult, UnsealStart, UserAgentRequest, UserAgentResponse,
|
|
||||||
VaultState as ProtoVaultState,
|
|
||||||
sdk_client_list_response::Result as ProtoSdkClientListResult,
|
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
user_agent_request::Payload as UserAgentRequestPayload,
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
},
|
},
|
||||||
@@ -32,33 +11,20 @@ use arbiter_proto::{
|
|||||||
transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use kameo::{
|
use kameo::actor::{ActorRef, Spawn as _};
|
||||||
actor::{ActorRef, Spawn as _},
|
|
||||||
error::SendError,
|
|
||||||
};
|
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession},
|
||||||
keyholder::KeyHolderState,
|
grpc::request_tracker::RequestTracker,
|
||||||
user_agent::{
|
|
||||||
OutOfBand, UserAgentConnection, UserAgentSession,
|
|
||||||
session::connection::{
|
|
||||||
BootstrapError, HandleBootstrapEncryptedKey, HandleEvmWalletCreate,
|
|
||||||
HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete,
|
|
||||||
HandleGrantEvmWalletAccess, HandleGrantList, HandleListWalletAccess,
|
|
||||||
HandleNewClientApprove, HandleQueryVaultState, HandleRevokeEvmWalletAccess,
|
|
||||||
HandleSdkClientList, HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
db::models::{CoreEvmWalletAccess, NewEvmWalletAccess},
|
|
||||||
grpc::{Convert, TryConvert, request_tracker::RequestTracker},
|
|
||||||
};
|
};
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod evm;
|
||||||
mod inbound;
|
mod inbound;
|
||||||
mod outbound;
|
mod outbound;
|
||||||
|
mod sdk_client;
|
||||||
|
mod vault;
|
||||||
|
|
||||||
pub struct OutOfBandAdapter(mpsc::Sender<OutOfBand>);
|
pub struct OutOfBandAdapter(mpsc::Sender<OutOfBand>);
|
||||||
|
|
||||||
@@ -86,23 +52,7 @@ async fn dispatch_loop(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let payload = match oob {
|
let payload = sdk_client::out_of_band_payload(oob);
|
||||||
OutOfBand::ClientConnectionRequest { profile } => {
|
|
||||||
UserAgentResponsePayload::SdkClientConnectionRequest(ProtoSdkClientConnectionRequest {
|
|
||||||
pubkey: profile.pubkey.to_bytes().to_vec(),
|
|
||||||
info: Some(ProtoClientMetadata {
|
|
||||||
name: profile.metadata.name,
|
|
||||||
description: profile.metadata.description,
|
|
||||||
version: profile.metadata.version,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
OutOfBand::ClientConnectionCancel { pubkey } => {
|
|
||||||
UserAgentResponsePayload::SdkClientConnectionCancel(ProtoSdkClientConnectionCancel {
|
|
||||||
pubkey: pubkey.to_bytes().to_vec(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if bi.send(Ok(UserAgentResponse { id: None, payload: Some(payload) })).await.is_err() {
|
if bi.send(Ok(UserAgentResponse { id: None, payload: Some(payload) })).await.is_err() {
|
||||||
return;
|
return;
|
||||||
@@ -158,285 +108,15 @@ async fn dispatch_inner(
|
|||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<UserAgentSession>,
|
||||||
payload: UserAgentRequestPayload,
|
payload: UserAgentRequestPayload,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
let response = match payload {
|
match payload {
|
||||||
UserAgentRequestPayload::UnsealStart(UnsealStart { client_pubkey }) => {
|
UserAgentRequestPayload::Vault(req) => vault::dispatch(actor, req).await,
|
||||||
let client_pubkey = <[u8; 32]>::try_from(client_pubkey)
|
UserAgentRequestPayload::Evm(req) => evm::dispatch(actor, req).await,
|
||||||
.map(x25519_dalek::PublicKey::from)
|
UserAgentRequestPayload::SdkClient(req) => sdk_client::dispatch(actor, req).await,
|
||||||
.map_err(|_| Status::invalid_argument("Invalid X25519 public key"))?;
|
UserAgentRequestPayload::Auth(..) => {
|
||||||
|
warn!("Unsupported post-auth user agent auth request");
|
||||||
let response = actor
|
Err(Status::invalid_argument("Unsupported user-agent request"))
|
||||||
.ask(HandleUnsealRequest { client_pubkey })
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!(error = ?err, "Failed to handle unseal start request");
|
|
||||||
Status::internal("Failed to start unseal flow")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
UserAgentResponsePayload::UnsealStartResponse(
|
|
||||||
arbiter_proto::proto::user_agent::UnsealStartResponse {
|
|
||||||
server_pubkey: response.server_pubkey.as_bytes().to_vec(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::UnsealEncryptedKey(ProtoUnsealEncryptedKey {
|
|
||||||
nonce,
|
|
||||||
ciphertext,
|
|
||||||
associated_data,
|
|
||||||
}) => {
|
|
||||||
let result = match actor
|
|
||||||
.ask(HandleUnsealEncryptedKey {
|
|
||||||
nonce,
|
|
||||||
ciphertext,
|
|
||||||
associated_data,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(()) => ProtoUnsealResult::Success,
|
|
||||||
Err(SendError::HandlerError(UnsealError::InvalidKey)) => {
|
|
||||||
ProtoUnsealResult::InvalidKey
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to handle unseal request");
|
|
||||||
return Err(Status::internal("Failed to unseal vault"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::UnsealResult(result.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::BootstrapEncryptedKey(ProtoBootstrapEncryptedKey {
|
|
||||||
nonce,
|
|
||||||
ciphertext,
|
|
||||||
associated_data,
|
|
||||||
}) => {
|
|
||||||
let result = match actor
|
|
||||||
.ask(HandleBootstrapEncryptedKey {
|
|
||||||
nonce,
|
|
||||||
ciphertext,
|
|
||||||
associated_data,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(()) => ProtoBootstrapResult::Success,
|
|
||||||
Err(SendError::HandlerError(BootstrapError::InvalidKey)) => {
|
|
||||||
ProtoBootstrapResult::InvalidKey
|
|
||||||
}
|
|
||||||
Err(SendError::HandlerError(BootstrapError::AlreadyBootstrapped)) => {
|
|
||||||
ProtoBootstrapResult::AlreadyBootstrapped
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to handle bootstrap request");
|
|
||||||
return Err(Status::internal("Failed to bootstrap vault"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::BootstrapResult(result.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::QueryVaultState(_) => {
|
|
||||||
let state = match actor.ask(HandleQueryVaultState {}).await {
|
|
||||||
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
|
||||||
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed,
|
|
||||||
Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed,
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to query vault state");
|
|
||||||
ProtoVaultState::Error
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::VaultState(state.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::EvmWalletCreate(_) => {
|
|
||||||
let result = match actor.ask(HandleEvmWalletCreate {}).await {
|
|
||||||
Ok((wallet_id, address)) => WalletCreateResult::Wallet(WalletEntry {
|
|
||||||
id: wallet_id,
|
|
||||||
address: address.to_vec(),
|
|
||||||
}),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to create EVM wallet");
|
|
||||||
WalletCreateResult::Error(ProtoEvmError::Internal.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::EvmWalletCreate(WalletCreateResponse {
|
|
||||||
result: Some(result),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::EvmWalletList(_) => {
|
|
||||||
let result = match actor.ask(HandleEvmWalletList {}).await {
|
|
||||||
Ok(wallets) => WalletListResult::Wallets(WalletList {
|
|
||||||
wallets: wallets
|
|
||||||
.into_iter()
|
|
||||||
.map(|(id, address)| WalletEntry {
|
|
||||||
address: address.to_vec(),
|
|
||||||
id,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to list EVM wallets");
|
|
||||||
WalletListResult::Error(ProtoEvmError::Internal.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::EvmWalletList(WalletListResponse {
|
|
||||||
result: Some(result),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::EvmGrantList(_) => {
|
|
||||||
let result = match actor.ask(HandleGrantList {}).await {
|
|
||||||
Ok(grants) => EvmGrantListResult::Grants(EvmGrantList {
|
|
||||||
grants: grants
|
|
||||||
.into_iter()
|
|
||||||
.map(|grant| GrantEntry {
|
|
||||||
id: grant.id,
|
|
||||||
wallet_access_id: grant.shared.wallet_access_id,
|
|
||||||
shared: Some(grant.shared.convert()),
|
|
||||||
specific: Some(grant.settings.convert()),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to list EVM grants");
|
|
||||||
EvmGrantListResult::Error(ProtoEvmError::Internal.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::EvmGrantList(EvmGrantListResponse {
|
|
||||||
result: Some(result),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::EvmGrantCreate(EvmGrantCreateRequest { shared, specific }) => {
|
|
||||||
let basic = shared
|
|
||||||
.ok_or_else(|| Status::invalid_argument("Missing shared grant settings"))?
|
|
||||||
.try_convert()?;
|
|
||||||
let grant = specific
|
|
||||||
.ok_or_else(|| Status::invalid_argument("Missing specific grant settings"))?
|
|
||||||
.try_convert()?;
|
|
||||||
|
|
||||||
let result = match actor.ask(HandleGrantCreate { basic, grant }).await {
|
|
||||||
Ok(grant_id) => EvmGrantCreateResult::GrantId(grant_id),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to create EVM grant");
|
|
||||||
EvmGrantCreateResult::Error(ProtoEvmError::Internal.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::EvmGrantCreate(EvmGrantCreateResponse {
|
|
||||||
result: Some(result),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::EvmGrantDelete(EvmGrantDeleteRequest { grant_id }) => {
|
|
||||||
let result = match actor.ask(HandleGrantDelete { grant_id }).await {
|
|
||||||
Ok(()) => EvmGrantDeleteResult::Ok(()),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to delete EVM grant");
|
|
||||||
EvmGrantDeleteResult::Error(ProtoEvmError::Internal.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::EvmGrantDelete(EvmGrantDeleteResponse {
|
|
||||||
result: Some(result),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::SdkClientConnectionResponse(resp) => {
|
|
||||||
let pubkey_bytes = <[u8; 32]>::try_from(resp.pubkey)
|
|
||||||
.map_err(|_| Status::invalid_argument("Invalid Ed25519 public key length"))?;
|
|
||||||
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(&pubkey_bytes)
|
|
||||||
.map_err(|_| Status::invalid_argument("Invalid Ed25519 public key"))?;
|
|
||||||
|
|
||||||
actor
|
|
||||||
.ask(HandleNewClientApprove {
|
|
||||||
approved: resp.approved,
|
|
||||||
pubkey,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.map_err(|err| {
|
|
||||||
warn!(?err, "Failed to process client connection response");
|
|
||||||
Status::internal("Failed to process response")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::SdkClientRevoke(_) => todo!(),
|
|
||||||
|
|
||||||
UserAgentRequestPayload::SdkClientList(_) => {
|
|
||||||
let result = match actor.ask(HandleSdkClientList {}).await {
|
|
||||||
Ok(clients) => ProtoSdkClientListResult::Clients(ProtoSdkClientList {
|
|
||||||
clients: clients
|
|
||||||
.into_iter()
|
|
||||||
.map(|(client, metadata)| ProtoSdkClientEntry {
|
|
||||||
id: client.id,
|
|
||||||
pubkey: client.public_key,
|
|
||||||
info: Some(ProtoClientMetadata {
|
|
||||||
name: metadata.name,
|
|
||||||
description: metadata.description,
|
|
||||||
version: metadata.version,
|
|
||||||
}),
|
|
||||||
created_at: client.created_at.0.timestamp() as i32,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to list SDK clients");
|
|
||||||
ProtoSdkClientListResult::Error(ProtoSdkClientError::Internal.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::SdkClientListResponse(ProtoSdkClientListResponse {
|
|
||||||
result: Some(result),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::GrantWalletAccess(SdkClientGrantWalletAccess { accesses }) => {
|
|
||||||
let entries: Vec<NewEvmWalletAccess> =
|
|
||||||
accesses.into_iter().map(|a| a.convert()).collect();
|
|
||||||
|
|
||||||
match actor.ask(HandleGrantEvmWalletAccess { entries }).await {
|
|
||||||
Ok(()) => {
|
|
||||||
info!("Successfully granted wallet access");
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to grant wallet access");
|
|
||||||
return Err(Status::internal("Failed to grant wallet access"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::RevokeWalletAccess(SdkClientRevokeWalletAccess { accesses }) => {
|
|
||||||
match actor.ask(HandleRevokeEvmWalletAccess { entries: accesses }).await {
|
|
||||||
Ok(()) => {
|
|
||||||
info!("Successfully revoked wallet access");
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to revoke wallet access");
|
|
||||||
return Err(Status::internal("Failed to revoke wallet access"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::ListWalletAccess(_) => {
|
|
||||||
let result = match actor.ask(HandleListWalletAccess {}).await {
|
|
||||||
Ok(accesses) => ListWalletAccessResponse {
|
|
||||||
accesses: accesses.into_iter().map(|a| a.convert()).collect(),
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to list wallet access");
|
|
||||||
return Err(Status::internal("Failed to list wallet access"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
UserAgentResponsePayload::ListWalletAccessResponse(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAgentRequestPayload::AuthChallengeRequest(..)
|
|
||||||
| UserAgentRequestPayload::AuthChallengeSolution(..) => {
|
|
||||||
warn!(?payload, "Unsupported post-auth user agent request");
|
|
||||||
return Err(Status::invalid_argument("Unsupported user-agent request"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(response))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(
|
pub async fn start(
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::user_agent::{
|
proto::user_agent::{
|
||||||
AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest,
|
UserAgentRequest, UserAgentResponse, auth::{
|
||||||
|
self as proto_auth, AuthChallenge as ProtoAuthChallenge,
|
||||||
|
AuthChallengeRequest as ProtoAuthChallengeRequest,
|
||||||
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
||||||
KeyType as ProtoKeyType, UserAgentRequest, UserAgentResponse,
|
KeyType as ProtoKeyType, request::Payload as AuthRequestPayload,
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
response::Payload as AuthResponsePayload,
|
||||||
|
}, user_agent_request::Payload as UserAgentRequestPayload,
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
},
|
},
|
||||||
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
@@ -36,12 +39,14 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
|
|
||||||
async fn send_user_agent_response(
|
async fn send_user_agent_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
payload: UserAgentResponsePayload,
|
payload: AuthResponsePayload,
|
||||||
) -> Result<(), TransportError> {
|
) -> Result<(), TransportError> {
|
||||||
self.bi
|
self.bi
|
||||||
.send(Ok(UserAgentResponse {
|
.send(Ok(UserAgentResponse {
|
||||||
id: Some(self.request_tracker.current_request_id()),
|
id: Some(self.request_tracker.current_request_id()),
|
||||||
|
payload: Some(UserAgentResponsePayload::Auth(proto_auth::Response {
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
|
})),
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -56,19 +61,17 @@ impl Sender<Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {
|
|||||||
use auth::{Error, Outbound};
|
use auth::{Error, Outbound};
|
||||||
let payload = match item {
|
let payload = match item {
|
||||||
Ok(Outbound::AuthChallenge { nonce }) => {
|
Ok(Outbound::AuthChallenge { nonce }) => {
|
||||||
UserAgentResponsePayload::AuthChallenge(ProtoAuthChallenge { nonce })
|
AuthResponsePayload::Challenge(ProtoAuthChallenge { nonce })
|
||||||
}
|
|
||||||
Ok(Outbound::AuthSuccess) => {
|
|
||||||
UserAgentResponsePayload::AuthResult(ProtoAuthResult::Success.into())
|
|
||||||
}
|
}
|
||||||
|
Ok(Outbound::AuthSuccess) => AuthResponsePayload::Result(ProtoAuthResult::Success.into()),
|
||||||
Err(Error::UnregisteredPublicKey) => {
|
Err(Error::UnregisteredPublicKey) => {
|
||||||
UserAgentResponsePayload::AuthResult(ProtoAuthResult::InvalidKey.into())
|
AuthResponsePayload::Result(ProtoAuthResult::InvalidKey.into())
|
||||||
}
|
}
|
||||||
Err(Error::InvalidChallengeSolution) => {
|
Err(Error::InvalidChallengeSolution) => {
|
||||||
UserAgentResponsePayload::AuthResult(ProtoAuthResult::InvalidSignature.into())
|
AuthResponsePayload::Result(ProtoAuthResult::InvalidSignature.into())
|
||||||
}
|
}
|
||||||
Err(Error::InvalidBootstrapToken) => {
|
Err(Error::InvalidBootstrapToken) => {
|
||||||
UserAgentResponsePayload::AuthResult(ProtoAuthResult::TokenInvalid.into())
|
AuthResponsePayload::Result(ProtoAuthResult::TokenInvalid.into())
|
||||||
}
|
}
|
||||||
Err(Error::Internal { details }) => {
|
Err(Error::Internal { details }) => {
|
||||||
return self.bi.send(Err(Status::internal(details))).await;
|
return self.bi.send(Err(Status::internal(details))).await;
|
||||||
@@ -112,8 +115,26 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let UserAgentRequestPayload::Auth(auth_request) = payload else {
|
||||||
|
let _ = self
|
||||||
|
.bi
|
||||||
|
.send(Err(Status::invalid_argument(
|
||||||
|
"Unsupported user-agent auth request",
|
||||||
|
)))
|
||||||
|
.await;
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(payload) = auth_request.payload else {
|
||||||
|
warn!(
|
||||||
|
event = "received auth request with empty payload",
|
||||||
|
"grpc.useragent.auth_adapter"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
match payload {
|
match payload {
|
||||||
UserAgentRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest {
|
AuthRequestPayload::ChallengeRequest(ProtoAuthChallengeRequest {
|
||||||
pubkey,
|
pubkey,
|
||||||
bootstrap_token,
|
bootstrap_token,
|
||||||
key_type,
|
key_type,
|
||||||
@@ -150,18 +171,9 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
bootstrap_token,
|
bootstrap_token,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
UserAgentRequestPayload::AuthChallengeSolution(ProtoAuthChallengeSolution {
|
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution {
|
||||||
signature,
|
signature,
|
||||||
}) => Some(auth::Inbound::AuthChallengeSolution { signature }),
|
}) => Some(auth::Inbound::AuthChallengeSolution { signature }),
|
||||||
_ => {
|
|
||||||
let _ = self
|
|
||||||
.bi
|
|
||||||
.send(Err(Status::invalid_argument(
|
|
||||||
"Unsupported user-agent auth request",
|
|
||||||
)))
|
|
||||||
.await;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
170
server/crates/arbiter-server/src/grpc/user_agent/evm.rs
Normal file
170
server/crates/arbiter-server/src/grpc/user_agent/evm.rs
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
use arbiter_proto::proto::{
|
||||||
|
evm::{
|
||||||
|
EvmError as ProtoEvmError, EvmGrantCreateRequest, EvmGrantCreateResponse,
|
||||||
|
EvmGrantDeleteRequest, EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse,
|
||||||
|
GrantEntry, WalletCreateResponse, WalletEntry, WalletList, WalletListResponse,
|
||||||
|
evm_grant_create_response::Result as EvmGrantCreateResult,
|
||||||
|
evm_grant_delete_response::Result as EvmGrantDeleteResult,
|
||||||
|
evm_grant_list_response::Result as EvmGrantListResult,
|
||||||
|
wallet_create_response::Result as WalletCreateResult,
|
||||||
|
wallet_list_response::Result as WalletListResult,
|
||||||
|
},
|
||||||
|
user_agent::{
|
||||||
|
evm::{self as proto_evm, request::Payload as EvmRequestPayload, response::Payload as EvmResponsePayload},
|
||||||
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use kameo::actor::ActorRef;
|
||||||
|
use tonic::Status;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
actors::user_agent::{
|
||||||
|
UserAgentSession,
|
||||||
|
session::connection::{
|
||||||
|
HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete,
|
||||||
|
HandleGrantList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grpc::{Convert, TryConvert},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn wrap_evm_response(payload: EvmResponsePayload) -> UserAgentResponsePayload {
|
||||||
|
UserAgentResponsePayload::Evm(proto_evm::Response {
|
||||||
|
payload: Some(payload),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn dispatch(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: proto_evm::Request,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let Some(payload) = req.payload else {
|
||||||
|
return Err(Status::invalid_argument("Missing EVM request payload"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match payload {
|
||||||
|
EvmRequestPayload::WalletCreate(_) => handle_wallet_create(actor).await,
|
||||||
|
EvmRequestPayload::WalletList(_) => handle_wallet_list(actor).await,
|
||||||
|
EvmRequestPayload::GrantCreate(req) => handle_grant_create(actor, req).await,
|
||||||
|
EvmRequestPayload::GrantDelete(req) => handle_grant_delete(actor, req).await,
|
||||||
|
EvmRequestPayload::GrantList(_) => handle_grant_list(actor).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_wallet_create(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let result = match actor.ask(HandleEvmWalletCreate {}).await {
|
||||||
|
Ok((wallet_id, address)) => WalletCreateResult::Wallet(WalletEntry {
|
||||||
|
id: wallet_id,
|
||||||
|
address: address.to_vec(),
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to create EVM wallet");
|
||||||
|
WalletCreateResult::Error(ProtoEvmError::Internal.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_evm_response(EvmResponsePayload::WalletCreate(
|
||||||
|
WalletCreateResponse {
|
||||||
|
result: Some(result),
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_wallet_list(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let result = match actor.ask(HandleEvmWalletList {}).await {
|
||||||
|
Ok(wallets) => WalletListResult::Wallets(WalletList {
|
||||||
|
wallets: wallets
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, address)| WalletEntry {
|
||||||
|
address: address.to_vec(),
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to list EVM wallets");
|
||||||
|
WalletListResult::Error(ProtoEvmError::Internal.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_evm_response(EvmResponsePayload::WalletList(
|
||||||
|
WalletListResponse {
|
||||||
|
result: Some(result),
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_grant_list(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let result = match actor.ask(HandleGrantList {}).await {
|
||||||
|
Ok(grants) => EvmGrantListResult::Grants(EvmGrantList {
|
||||||
|
grants: grants
|
||||||
|
.into_iter()
|
||||||
|
.map(|grant| GrantEntry {
|
||||||
|
id: grant.id,
|
||||||
|
wallet_access_id: grant.shared.wallet_access_id,
|
||||||
|
shared: Some(grant.shared.convert()),
|
||||||
|
specific: Some(grant.settings.convert()),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to list EVM grants");
|
||||||
|
EvmGrantListResult::Error(ProtoEvmError::Internal.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_evm_response(EvmResponsePayload::GrantList(
|
||||||
|
EvmGrantListResponse {
|
||||||
|
result: Some(result),
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_grant_create(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: EvmGrantCreateRequest,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let basic = req
|
||||||
|
.shared
|
||||||
|
.ok_or_else(|| Status::invalid_argument("Missing shared grant settings"))?
|
||||||
|
.try_convert()?;
|
||||||
|
let grant = req
|
||||||
|
.specific
|
||||||
|
.ok_or_else(|| Status::invalid_argument("Missing specific grant settings"))?
|
||||||
|
.try_convert()?;
|
||||||
|
|
||||||
|
let result = match actor.ask(HandleGrantCreate { basic, grant }).await {
|
||||||
|
Ok(grant_id) => EvmGrantCreateResult::GrantId(grant_id),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to create EVM grant");
|
||||||
|
EvmGrantCreateResult::Error(ProtoEvmError::Internal.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_evm_response(EvmResponsePayload::GrantCreate(
|
||||||
|
EvmGrantCreateResponse {
|
||||||
|
result: Some(result),
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_grant_delete(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: EvmGrantDeleteRequest,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let result = match actor.ask(HandleGrantDelete { grant_id: req.grant_id }).await {
|
||||||
|
Ok(()) => EvmGrantDeleteResult::Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to delete EVM grant");
|
||||||
|
EvmGrantDeleteResult::Error(ProtoEvmError::Internal.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_evm_response(EvmResponsePayload::GrantDelete(
|
||||||
|
EvmGrantDeleteResponse {
|
||||||
|
result: Some(result),
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
}
|
||||||
@@ -5,12 +5,14 @@ use arbiter_proto::proto::evm::{
|
|||||||
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
||||||
specific_grant::Grant as ProtoSpecificGrantType,
|
specific_grant::Grant as ProtoSpecificGrantType,
|
||||||
};
|
};
|
||||||
use arbiter_proto::proto::user_agent::{SdkClientWalletAccess, WalletAccess};
|
use arbiter_proto::proto::user_agent::sdk_client::{
|
||||||
|
WalletAccess, WalletAccessEntry as SdkClientWalletAccess,
|
||||||
|
};
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
use prost_types::Timestamp as ProtoTimestamp;
|
use prost_types::Timestamp as ProtoTimestamp;
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
|
|
||||||
use crate::db::models::{CoreEvmWalletAccess, NewEvmWallet, NewEvmWalletAccess};
|
use crate::db::models::{CoreEvmWalletAccess, NewEvmWalletAccess};
|
||||||
use crate::grpc::Convert;
|
use crate::grpc::Convert;
|
||||||
use crate::{
|
use crate::{
|
||||||
evm::policies::{
|
evm::policies::{
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ use arbiter_proto::proto::{
|
|||||||
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
||||||
specific_grant::Grant as ProtoSpecificGrantType,
|
specific_grant::Grant as ProtoSpecificGrantType,
|
||||||
},
|
},
|
||||||
user_agent::{SdkClientWalletAccess as ProtoSdkClientWalletAccess, WalletAccess},
|
user_agent::sdk_client::{
|
||||||
|
WalletAccess, WalletAccessEntry as ProtoSdkClientWalletAccess,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use prost_types::Timestamp as ProtoTimestamp;
|
use prost_types::Timestamp as ProtoTimestamp;
|
||||||
|
|||||||
190
server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs
Normal file
190
server/crates/arbiter-server/src/grpc/user_agent/sdk_client.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
use arbiter_proto::proto::{
|
||||||
|
user_agent::{
|
||||||
|
sdk_client::{
|
||||||
|
self as proto_sdk_client, ConnectionCancel as ProtoSdkClientConnectionCancel,
|
||||||
|
ConnectionRequest as ProtoSdkClientConnectionRequest,
|
||||||
|
ConnectionResponse as ProtoSdkClientConnectionResponse, Entry as ProtoSdkClientEntry,
|
||||||
|
Error as ProtoSdkClientError, GrantWalletAccess as ProtoSdkClientGrantWalletAccess,
|
||||||
|
List as ProtoSdkClientList, ListResponse as ProtoSdkClientListResponse,
|
||||||
|
ListWalletAccessResponse, RevokeWalletAccess as ProtoSdkClientRevokeWalletAccess,
|
||||||
|
list_response::Result as ProtoSdkClientListResult,
|
||||||
|
request::Payload as SdkClientRequestPayload,
|
||||||
|
response::Payload as SdkClientResponsePayload,
|
||||||
|
},
|
||||||
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
|
},
|
||||||
|
shared::ClientInfo as ProtoClientMetadata,
|
||||||
|
};
|
||||||
|
use kameo::actor::ActorRef;
|
||||||
|
use tonic::Status;
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
actors::user_agent::{
|
||||||
|
OutOfBand, UserAgentSession,
|
||||||
|
session::connection::{
|
||||||
|
HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove,
|
||||||
|
HandleRevokeEvmWalletAccess, HandleSdkClientList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
db::models::NewEvmWalletAccess,
|
||||||
|
grpc::Convert,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn wrap_sdk_client_response(payload: SdkClientResponsePayload) -> UserAgentResponsePayload {
|
||||||
|
UserAgentResponsePayload::SdkClient(proto_sdk_client::Response {
|
||||||
|
payload: Some(payload),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn out_of_band_payload(oob: OutOfBand) -> UserAgentResponsePayload {
|
||||||
|
match oob {
|
||||||
|
OutOfBand::ClientConnectionRequest { profile } => wrap_sdk_client_response(
|
||||||
|
SdkClientResponsePayload::ConnectionRequest(ProtoSdkClientConnectionRequest {
|
||||||
|
pubkey: profile.pubkey.to_bytes().to_vec(),
|
||||||
|
info: Some(ProtoClientMetadata {
|
||||||
|
name: profile.metadata.name,
|
||||||
|
description: profile.metadata.description,
|
||||||
|
version: profile.metadata.version,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
OutOfBand::ClientConnectionCancel { pubkey } => wrap_sdk_client_response(
|
||||||
|
SdkClientResponsePayload::ConnectionCancel(ProtoSdkClientConnectionCancel {
|
||||||
|
pubkey: pubkey.to_bytes().to_vec(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn dispatch(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: proto_sdk_client::Request,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let Some(payload) = req.payload else {
|
||||||
|
return Err(Status::invalid_argument("Missing SDK client request payload"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match payload {
|
||||||
|
SdkClientRequestPayload::ConnectionResponse(resp) => {
|
||||||
|
handle_connection_response(actor, resp).await
|
||||||
|
}
|
||||||
|
SdkClientRequestPayload::Revoke(_) => {
|
||||||
|
Err(Status::unimplemented("SdkClientRevoke is not yet implemented"))
|
||||||
|
}
|
||||||
|
SdkClientRequestPayload::List(_) => handle_list(actor).await,
|
||||||
|
SdkClientRequestPayload::GrantWalletAccess(req) => handle_grant_wallet_access(actor, req).await,
|
||||||
|
SdkClientRequestPayload::RevokeWalletAccess(req) => {
|
||||||
|
handle_revoke_wallet_access(actor, req).await
|
||||||
|
}
|
||||||
|
SdkClientRequestPayload::ListWalletAccess(_) => handle_list_wallet_access(actor).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_connection_response(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
resp: ProtoSdkClientConnectionResponse,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let pubkey_bytes = <[u8; 32]>::try_from(resp.pubkey)
|
||||||
|
.map_err(|_| Status::invalid_argument("Invalid Ed25519 public key length"))?;
|
||||||
|
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(&pubkey_bytes)
|
||||||
|
.map_err(|_| Status::invalid_argument("Invalid Ed25519 public key"))?;
|
||||||
|
|
||||||
|
actor
|
||||||
|
.ask(HandleNewClientApprove {
|
||||||
|
approved: resp.approved,
|
||||||
|
pubkey,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!(?err, "Failed to process client connection response");
|
||||||
|
Status::internal("Failed to process response")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_list(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let result = match actor.ask(HandleSdkClientList {}).await {
|
||||||
|
Ok(clients) => ProtoSdkClientListResult::Clients(ProtoSdkClientList {
|
||||||
|
clients: clients
|
||||||
|
.into_iter()
|
||||||
|
.map(|(client, metadata)| ProtoSdkClientEntry {
|
||||||
|
id: client.id,
|
||||||
|
pubkey: client.public_key,
|
||||||
|
info: Some(ProtoClientMetadata {
|
||||||
|
name: metadata.name,
|
||||||
|
description: metadata.description,
|
||||||
|
version: metadata.version,
|
||||||
|
}),
|
||||||
|
created_at: client.created_at.0.timestamp() as i32,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to list SDK clients");
|
||||||
|
ProtoSdkClientListResult::Error(ProtoSdkClientError::Internal.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_sdk_client_response(SdkClientResponsePayload::List(
|
||||||
|
ProtoSdkClientListResponse {
|
||||||
|
result: Some(result),
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_grant_wallet_access(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: ProtoSdkClientGrantWalletAccess,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let entries: Vec<NewEvmWalletAccess> = req.accesses.into_iter().map(|a| a.convert()).collect();
|
||||||
|
match actor.ask(HandleGrantEvmWalletAccess { entries }).await {
|
||||||
|
Ok(()) => {
|
||||||
|
info!("Successfully granted wallet access");
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to grant wallet access");
|
||||||
|
Err(Status::internal("Failed to grant wallet access"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_revoke_wallet_access(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: ProtoSdkClientRevokeWalletAccess,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
match actor
|
||||||
|
.ask(HandleRevokeEvmWalletAccess {
|
||||||
|
entries: req.accesses,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
info!("Successfully revoked wallet access");
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to revoke wallet access");
|
||||||
|
Err(Status::internal("Failed to revoke wallet access"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_list_wallet_access(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
match actor.ask(HandleListWalletAccess {}).await {
|
||||||
|
Ok(accesses) => Ok(Some(wrap_sdk_client_response(
|
||||||
|
SdkClientResponsePayload::ListWalletAccess(ListWalletAccessResponse {
|
||||||
|
accesses: accesses.into_iter().map(|a| a.convert()).collect(),
|
||||||
|
}),
|
||||||
|
))),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to list wallet access");
|
||||||
|
Err(Status::internal("Failed to list wallet access"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
181
server/crates/arbiter-server/src/grpc/user_agent/vault.rs
Normal file
181
server/crates/arbiter-server/src/grpc/user_agent/vault.rs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
use arbiter_proto::proto::user_agent::{
|
||||||
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
|
vault::{
|
||||||
|
self as proto_vault,
|
||||||
|
bootstrap::{
|
||||||
|
self as proto_bootstrap, BootstrapEncryptedKey as ProtoBootstrapEncryptedKey,
|
||||||
|
BootstrapResult as ProtoBootstrapResult,
|
||||||
|
},
|
||||||
|
request::Payload as VaultRequestPayload,
|
||||||
|
response::Payload as VaultResponsePayload,
|
||||||
|
unseal::{
|
||||||
|
self as proto_unseal, UnsealEncryptedKey as ProtoUnsealEncryptedKey,
|
||||||
|
UnsealResult as ProtoUnsealResult, UnsealStart,
|
||||||
|
request::Payload as UnsealRequestPayload,
|
||||||
|
response::Payload as UnsealResponsePayload,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use arbiter_proto::proto::shared::VaultState as ProtoVaultState;
|
||||||
|
use kameo::{actor::ActorRef, error::SendError};
|
||||||
|
use tonic::Status;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
actors::{
|
||||||
|
keyholder::KeyHolderState,
|
||||||
|
user_agent::{
|
||||||
|
UserAgentSession,
|
||||||
|
session::connection::{
|
||||||
|
BootstrapError, HandleBootstrapEncryptedKey, HandleQueryVaultState,
|
||||||
|
HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload {
|
||||||
|
UserAgentResponsePayload::Vault(proto_vault::Response {
|
||||||
|
payload: Some(payload),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_unseal_response(payload: UnsealResponsePayload) -> UserAgentResponsePayload {
|
||||||
|
wrap_vault_response(VaultResponsePayload::Unseal(proto_unseal::Response {
|
||||||
|
payload: Some(payload),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePayload {
|
||||||
|
wrap_vault_response(VaultResponsePayload::Bootstrap(proto_bootstrap::Response {
|
||||||
|
result: result.into(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn dispatch(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: proto_vault::Request,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let Some(payload) = req.payload else {
|
||||||
|
return Err(Status::invalid_argument("Missing vault request payload"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match payload {
|
||||||
|
VaultRequestPayload::QueryState(_) => handle_query_vault_state(actor).await,
|
||||||
|
VaultRequestPayload::Unseal(req) => dispatch_unseal_request(actor, req).await,
|
||||||
|
VaultRequestPayload::Bootstrap(req) => handle_bootstrap_request(actor, req).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn dispatch_unseal_request(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: proto_unseal::Request,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let Some(payload) = req.payload else {
|
||||||
|
return Err(Status::invalid_argument("Missing unseal request payload"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match payload {
|
||||||
|
UnsealRequestPayload::Start(req) => handle_unseal_start(actor, req).await,
|
||||||
|
UnsealRequestPayload::EncryptedKey(req) => handle_unseal_encrypted_key(actor, req).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_unseal_start(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: UnsealStart,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let client_pubkey = <[u8; 32]>::try_from(req.client_pubkey)
|
||||||
|
.map(x25519_dalek::PublicKey::from)
|
||||||
|
.map_err(|_| Status::invalid_argument("Invalid X25519 public key"))?;
|
||||||
|
|
||||||
|
let response = actor
|
||||||
|
.ask(HandleUnsealRequest { client_pubkey })
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!(error = ?err, "Failed to handle unseal start request");
|
||||||
|
Status::internal("Failed to start unseal flow")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Some(wrap_unseal_response(UnsealResponsePayload::Start(
|
||||||
|
proto_unseal::UnsealStartResponse {
|
||||||
|
server_pubkey: response.server_pubkey.as_bytes().to_vec(),
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_unseal_encrypted_key(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: ProtoUnsealEncryptedKey,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let result = match actor
|
||||||
|
.ask(HandleUnsealEncryptedKey {
|
||||||
|
nonce: req.nonce,
|
||||||
|
ciphertext: req.ciphertext,
|
||||||
|
associated_data: req.associated_data,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => ProtoUnsealResult::Success,
|
||||||
|
Err(SendError::HandlerError(UnsealError::InvalidKey)) => ProtoUnsealResult::InvalidKey,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to handle unseal request");
|
||||||
|
return Err(Status::internal("Failed to unseal vault"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_unseal_response(UnsealResponsePayload::Result(
|
||||||
|
result.into(),
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_bootstrap_request(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: proto_bootstrap::Request,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let encrypted_key = req
|
||||||
|
.encrypted_key
|
||||||
|
.ok_or_else(|| Status::invalid_argument("Missing bootstrap encrypted key"))?;
|
||||||
|
handle_bootstrap_encrypted_key(actor, encrypted_key).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_bootstrap_encrypted_key(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
req: ProtoBootstrapEncryptedKey,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let result = match actor
|
||||||
|
.ask(HandleBootstrapEncryptedKey {
|
||||||
|
nonce: req.nonce,
|
||||||
|
ciphertext: req.ciphertext,
|
||||||
|
associated_data: req.associated_data,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => ProtoBootstrapResult::Success,
|
||||||
|
Err(SendError::HandlerError(BootstrapError::InvalidKey)) => ProtoBootstrapResult::InvalidKey,
|
||||||
|
Err(SendError::HandlerError(BootstrapError::AlreadyBootstrapped)) => {
|
||||||
|
ProtoBootstrapResult::AlreadyBootstrapped
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to handle bootstrap request");
|
||||||
|
return Err(Status::internal("Failed to bootstrap vault"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_bootstrap_response(result)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_query_vault_state(
|
||||||
|
actor: &ActorRef<UserAgentSession>,
|
||||||
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
|
let state = match actor.ask(HandleQueryVaultState {}).await {
|
||||||
|
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
||||||
|
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed,
|
||||||
|
Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to query vault state");
|
||||||
|
ProtoVaultState::Error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some(wrap_vault_response(VaultResponsePayload::State(
|
||||||
|
state.into(),
|
||||||
|
))))
|
||||||
|
}
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
use crate::context::ServerContext;
|
||||||
use std::{net::SocketAddr, path::PathBuf};
|
|
||||||
|
|
||||||
use arbiter_proto::{proto::arbiter_service_server::ArbiterServiceServer, url::ArbiterUrl};
|
|
||||||
use miette::miette;
|
|
||||||
use tonic::transport::{Identity, ServerTlsConfig};
|
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
use crate::{actors::bootstrap::GetToken, context::ServerContext};
|
|
||||||
|
|
||||||
pub mod actors;
|
pub mod actors;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
@@ -26,64 +18,3 @@ impl Server {
|
|||||||
Self { context }
|
Self { context }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RunConfig {
|
|
||||||
pub addr: SocketAddr,
|
|
||||||
pub data_dir: Option<PathBuf>,
|
|
||||||
pub log_arbiter_url: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RunConfig {
|
|
||||||
pub fn new(addr: SocketAddr, data_dir: Option<PathBuf>) -> Self {
|
|
||||||
Self {
|
|
||||||
addr,
|
|
||||||
data_dir,
|
|
||||||
log_arbiter_url: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run_server_until_shutdown<F>(config: RunConfig, shutdown: F) -> miette::Result<()>
|
|
||||||
where
|
|
||||||
F: Future<Output = ()> + Send + 'static,
|
|
||||||
{
|
|
||||||
arbiter_proto::set_home_path_override(config.data_dir.clone())
|
|
||||||
.map_err(|err| miette!("failed to set home path override: {err}"))?;
|
|
||||||
|
|
||||||
let db = db::create_pool(None).await?;
|
|
||||||
info!(addr = %config.addr, "Database ready");
|
|
||||||
|
|
||||||
let context = ServerContext::new(db).await?;
|
|
||||||
info!(addr = %config.addr, "Server context ready");
|
|
||||||
|
|
||||||
if config.log_arbiter_url {
|
|
||||||
let url = ArbiterUrl {
|
|
||||||
host: config.addr.ip().to_string(),
|
|
||||||
port: config.addr.port(),
|
|
||||||
ca_cert: context.tls.ca_cert().clone().into_owned(),
|
|
||||||
bootstrap_token: context
|
|
||||||
.actors
|
|
||||||
.bootstrapper
|
|
||||||
.ask(GetToken)
|
|
||||||
.await
|
|
||||||
.map_err(|err| miette!("failed to get bootstrap token from actor: {err}"))?,
|
|
||||||
};
|
|
||||||
info!(%url, "Server URL");
|
|
||||||
}
|
|
||||||
|
|
||||||
let tls = ServerTlsConfig::new().identity(Identity::from_pem(
|
|
||||||
context.tls.cert_pem(),
|
|
||||||
context.tls.key_pem(),
|
|
||||||
));
|
|
||||||
|
|
||||||
tonic::transport::Server::builder()
|
|
||||||
.tls_config(tls)
|
|
||||||
.map_err(|err| miette!("Failed to setup TLS: {err}"))?
|
|
||||||
.add_service(ArbiterServiceServer::new(Server::new(context)))
|
|
||||||
.serve_with_shutdown(config.addr, shutdown)
|
|
||||||
.await
|
|
||||||
.map_err(|e| miette!("gRPC server error: {e}"))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,42 +1,56 @@
|
|||||||
mod cli;
|
use std::net::SocketAddr;
|
||||||
mod service;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use arbiter_proto::{proto::arbiter_service_server::ArbiterServiceServer, url::ArbiterUrl};
|
||||||
use cli::{Cli, Command, RunArgs, ServiceCommand};
|
use arbiter_server::{Server, actors::bootstrap::GetToken, context::ServerContext, db};
|
||||||
|
use miette::miette;
|
||||||
use rustls::crypto::aws_lc_rs;
|
use rustls::crypto::aws_lc_rs;
|
||||||
|
use tonic::transport::{Identity, ServerTlsConfig};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
const PORT: u16 = 50051;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> miette::Result<()> {
|
async fn main() -> miette::Result<()> {
|
||||||
aws_lc_rs::default_provider().install_default().unwrap();
|
aws_lc_rs::default_provider().install_default().unwrap();
|
||||||
init_logging();
|
|
||||||
|
|
||||||
let cli = Cli::parse();
|
tracing_subscriber::fmt()
|
||||||
|
|
||||||
match cli.command {
|
|
||||||
None => run_foreground(RunArgs::default()).await,
|
|
||||||
Some(Command::Run(args)) => run_foreground(args).await,
|
|
||||||
Some(Command::Service { command }) => match command {
|
|
||||||
ServiceCommand::Install(args) => service::install_service(args),
|
|
||||||
ServiceCommand::Run(args) => service::run_service_dispatcher(args),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_foreground(args: RunArgs) -> miette::Result<()> {
|
|
||||||
info!(addr = %args.listen_addr, "Starting arbiter server");
|
|
||||||
arbiter_server::run_server_until_shutdown(
|
|
||||||
arbiter_server::RunConfig::new(args.listen_addr, args.data_dir),
|
|
||||||
std::future::pending::<()>(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_logging() {
|
|
||||||
let _ = tracing_subscriber::fmt()
|
|
||||||
.with_env_filter(
|
.with_env_filter(
|
||||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||||
)
|
)
|
||||||
.try_init();
|
.init();
|
||||||
|
|
||||||
|
info!("Starting arbiter server");
|
||||||
|
|
||||||
|
let db = db::create_pool(None).await?;
|
||||||
|
info!("Database ready");
|
||||||
|
|
||||||
|
let context = ServerContext::new(db).await?;
|
||||||
|
|
||||||
|
let addr: SocketAddr = format!("127.0.0.1:{PORT}").parse().expect("valid address");
|
||||||
|
info!(%addr, "Starting gRPC server");
|
||||||
|
|
||||||
|
let url = ArbiterUrl {
|
||||||
|
host: addr.ip().to_string(),
|
||||||
|
port: addr.port(),
|
||||||
|
ca_cert: context.tls.ca_cert().clone().into_owned(),
|
||||||
|
bootstrap_token: context.actors.bootstrapper.ask(GetToken).await.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(%url, "Server URL");
|
||||||
|
|
||||||
|
let tls = ServerTlsConfig::new().identity(Identity::from_pem(
|
||||||
|
context.tls.cert_pem(),
|
||||||
|
context.tls.key_pem(),
|
||||||
|
));
|
||||||
|
|
||||||
|
tonic::transport::Server::builder()
|
||||||
|
.tls_config(tls)
|
||||||
|
.map_err(|err| miette!("Faild to setup TLS: {err}"))?
|
||||||
|
.add_service(ArbiterServiceServer::new(Server::new(context)))
|
||||||
|
.serve(addr)
|
||||||
|
.await
|
||||||
|
.map_err(|e| miette::miette!("gRPC server error: {e}"))?;
|
||||||
|
|
||||||
|
unreachable!("gRPC server should run indefinitely");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
#[cfg(windows)]
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub use windows::{install_service, run_service_dispatcher};
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub fn install_service(_: crate::cli::ServiceInstallArgs) -> miette::Result<()> {
|
|
||||||
Err(miette::miette!(
|
|
||||||
"service install is currently supported only on Windows"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
pub fn run_service_dispatcher(_: crate::cli::ServiceRunArgs) -> miette::Result<()> {
|
|
||||||
Err(miette::miette!(
|
|
||||||
"service run entrypoint is currently supported only on Windows"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
use std::{
|
|
||||||
ffi::OsString,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::Command,
|
|
||||||
sync::mpsc,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use miette::{Context as _, IntoDiagnostic as _, miette};
|
|
||||||
use windows_service::{
|
|
||||||
define_windows_service,
|
|
||||||
service::{
|
|
||||||
ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode,
|
|
||||||
ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType,
|
|
||||||
},
|
|
||||||
service_control_handler::{self, ServiceControlHandlerResult},
|
|
||||||
service_dispatcher,
|
|
||||||
service_manager::{ServiceManager, ServiceManagerAccess},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::cli::{ServiceInstallArgs, ServiceRunArgs};
|
|
||||||
use arbiter_server::{RunConfig, run_server_until_shutdown};
|
|
||||||
|
|
||||||
const SERVICE_NAME: &str = "ArbiterServer";
|
|
||||||
const SERVICE_DISPLAY_NAME: &str = "Arbiter Server";
|
|
||||||
|
|
||||||
pub fn default_service_data_dir() -> PathBuf {
|
|
||||||
let base = std::env::var_os("PROGRAMDATA")
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"));
|
|
||||||
base.join("Arbiter")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn install_service(args: ServiceInstallArgs) -> miette::Result<()> {
|
|
||||||
ensure_admin_rights()?;
|
|
||||||
|
|
||||||
let executable = std::env::current_exe().into_diagnostic()?;
|
|
||||||
let data_dir = args.data_dir.unwrap_or_else(default_service_data_dir);
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&data_dir)
|
|
||||||
.into_diagnostic()
|
|
||||||
.with_context(|| format!("failed to create service data dir: {}", data_dir.display()))?;
|
|
||||||
ensure_token_acl_contract(&data_dir)?;
|
|
||||||
|
|
||||||
let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE;
|
|
||||||
let manager = ServiceManager::local_computer(None::<&str>, manager_access)
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("failed to open Service Control Manager")?;
|
|
||||||
|
|
||||||
let launch_arguments = vec![
|
|
||||||
OsString::from("service"),
|
|
||||||
OsString::from("run"),
|
|
||||||
OsString::from("--data-dir"),
|
|
||||||
data_dir.as_os_str().to_os_string(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let service_info = ServiceInfo {
|
|
||||||
name: OsString::from(SERVICE_NAME),
|
|
||||||
display_name: OsString::from(SERVICE_DISPLAY_NAME),
|
|
||||||
service_type: ServiceType::OWN_PROCESS,
|
|
||||||
start_type: ServiceStartType::AutoStart,
|
|
||||||
error_control: ServiceErrorControl::Normal,
|
|
||||||
executable_path: executable,
|
|
||||||
launch_arguments,
|
|
||||||
dependencies: vec![],
|
|
||||||
account_name: Some(OsString::from(r"NT AUTHORITY\LocalService")),
|
|
||||||
account_password: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let service = manager
|
|
||||||
.create_service(
|
|
||||||
&service_info,
|
|
||||||
ServiceAccess::QUERY_STATUS | ServiceAccess::START,
|
|
||||||
)
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("failed to create Windows service in SCM")?;
|
|
||||||
|
|
||||||
if args.start {
|
|
||||||
service
|
|
||||||
.start::<&str>(&[])
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("service created but failed to start")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_service_dispatcher(args: ServiceRunArgs) -> miette::Result<()> {
|
|
||||||
SERVICE_RUN_ARGS
|
|
||||||
.set(args)
|
|
||||||
.map_err(|_| miette!("service runtime args are already initialized"))?;
|
|
||||||
|
|
||||||
service_dispatcher::start(SERVICE_NAME, ffi_service_main)
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("failed to start service dispatcher")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
define_windows_service!(ffi_service_main, service_main);
|
|
||||||
|
|
||||||
static SERVICE_RUN_ARGS: std::sync::OnceLock<ServiceRunArgs> = std::sync::OnceLock::new();
|
|
||||||
|
|
||||||
fn service_main(_arguments: Vec<OsString>) {
|
|
||||||
if let Err(error) = run_service_main() {
|
|
||||||
tracing::error!(error = ?error, "Windows service main failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_service_main() -> miette::Result<()> {
|
|
||||||
let args = SERVICE_RUN_ARGS
|
|
||||||
.get()
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| miette!("service run args are missing"))?;
|
|
||||||
|
|
||||||
let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>();
|
|
||||||
|
|
||||||
let status_handle =
|
|
||||||
service_control_handler::register(SERVICE_NAME, move |control| match control {
|
|
||||||
ServiceControl::Stop => {
|
|
||||||
let _ = shutdown_tx.send(());
|
|
||||||
ServiceControlHandlerResult::NoError
|
|
||||||
}
|
|
||||||
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
|
||||||
_ => ServiceControlHandlerResult::NotImplemented,
|
|
||||||
})
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("failed to register service control handler")?;
|
|
||||||
|
|
||||||
set_status(
|
|
||||||
&status_handle,
|
|
||||||
ServiceState::StartPending,
|
|
||||||
ServiceControlAccept::empty(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("failed to build tokio runtime for service")?;
|
|
||||||
|
|
||||||
set_status(
|
|
||||||
&status_handle,
|
|
||||||
ServiceState::Running,
|
|
||||||
ServiceControlAccept::STOP,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let data_dir = args.data_dir.unwrap_or_else(default_service_data_dir);
|
|
||||||
let config = RunConfig {
|
|
||||||
addr: args.listen_addr,
|
|
||||||
data_dir: Some(data_dir),
|
|
||||||
log_arbiter_url: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = runtime.block_on(run_server_until_shutdown(config, async move {
|
|
||||||
let _ = tokio::task::spawn_blocking(move || shutdown_rx.recv()).await;
|
|
||||||
}));
|
|
||||||
|
|
||||||
set_status(
|
|
||||||
&status_handle,
|
|
||||||
ServiceState::Stopped,
|
|
||||||
ServiceControlAccept::empty(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_status(
|
|
||||||
status_handle: &service_control_handler::ServiceStatusHandle,
|
|
||||||
current_state: ServiceState,
|
|
||||||
controls_accepted: ServiceControlAccept,
|
|
||||||
) -> miette::Result<()> {
|
|
||||||
status_handle
|
|
||||||
.set_service_status(ServiceStatus {
|
|
||||||
service_type: ServiceType::OWN_PROCESS,
|
|
||||||
current_state,
|
|
||||||
controls_accepted,
|
|
||||||
exit_code: ServiceExitCode::Win32(0),
|
|
||||||
checkpoint: 0,
|
|
||||||
wait_hint: Duration::from_secs(10),
|
|
||||||
process_id: None,
|
|
||||||
})
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("failed to update service state")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_admin_rights() -> miette::Result<()> {
|
|
||||||
let status = Command::new("net")
|
|
||||||
.arg("session")
|
|
||||||
.status()
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("failed to check administrator rights")?;
|
|
||||||
|
|
||||||
if status.success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(miette!(
|
|
||||||
"administrator privileges are required to install Windows service"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_token_acl_contract(data_dir: &Path) -> miette::Result<()> {
|
|
||||||
// IMPORTANT: Keep this ACL setup explicit.
|
|
||||||
// The service account needs write access, while the interactive user only needs read access
|
|
||||||
// to the bootstrap token and service data directory.
|
|
||||||
let target = data_dir.as_os_str();
|
|
||||||
|
|
||||||
let status = Command::new("icacls")
|
|
||||||
.arg(target)
|
|
||||||
.arg("/grant")
|
|
||||||
.arg("*S-1-5-19:(OI)(CI)M")
|
|
||||||
.arg("/grant")
|
|
||||||
.arg("*S-1-5-32-545:(OI)(CI)RX")
|
|
||||||
.arg("/T")
|
|
||||||
.arg("/C")
|
|
||||||
.status()
|
|
||||||
.into_diagnostic()
|
|
||||||
.wrap_err("failed to apply ACLs for service data directory")?;
|
|
||||||
|
|
||||||
if status.success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(miette!(
|
|
||||||
"failed to ensure ACL contract for service data directory"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- biometric_signature (10.2.0):
|
- biometric_signature (11.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- cryptography_flutter (0.0.1):
|
- cryptography_flutter (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -35,7 +35,7 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
biometric_signature: 7ef6a703fcc2c3b0e3d937a8560507b1ba9d3414
|
biometric_signature: bae0597fffbc51252959e78b56a2f5afb8d4e1f5
|
||||||
cryptography_flutter: be2b3e0e31603521b6a1c2bea232a88a2488a91c
|
cryptography_flutter: be2b3e0e31603521b6a1c2bea232a88a2488a91c
|
||||||
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||||
|
|||||||
Reference in New Issue
Block a user