refactor(server::grpc::vault_gate): standard approach using / traits
This commit is contained in:
@@ -1,55 +1,16 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::transport::{Bi, Error as TransportError, Receiver, Sender};
|
||||||
proto::{
|
|
||||||
shared::VaultState as ProtoVaultState,
|
|
||||||
user_agent::{
|
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
|
||||||
vault::{
|
|
||||||
self as proto_vault,
|
|
||||||
bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult},
|
|
||||||
request::Payload as VaultRequestPayload,
|
|
||||||
response::Payload as VaultResponsePayload,
|
|
||||||
unseal::{
|
|
||||||
self as proto_unseal, UnsealResult as ProtoUnsealResult,
|
|
||||||
request::Payload as UnsealRequestPayload,
|
|
||||||
response::Payload as UnsealResponsePayload,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
transport::{Bi, Error as TransportError, Receiver, Sender},
|
|
||||||
};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use super::auth::AuthTransportAdapter;
|
use super::auth::AuthTransportAdapter;
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::vault::VaultState,
|
grpc::TryConvert,
|
||||||
peers::user_agent::vault_gate::{
|
peers::user_agent::vault_gate::{self as vault_gate},
|
||||||
self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload {
|
mod inbound;
|
||||||
UserAgentResponsePayload::Vault(proto_vault::Response {
|
mod outbound;
|
||||||
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(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuthTransportAdapter<'_> {}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Receiver<vault_gate::Inbound> for AuthTransportAdapter<'_> {
|
impl Receiver<vault_gate::Inbound> for AuthTransportAdapter<'_> {
|
||||||
@@ -79,87 +40,12 @@ impl Receiver<vault_gate::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let vault_req = match payload {
|
match payload.try_convert() {
|
||||||
UserAgentRequestPayload::Vault(req) => req,
|
Ok(inbound) => return Some(inbound),
|
||||||
_ => {
|
Err(status) => {
|
||||||
let _ = self
|
let _ = self.bi_mut().send(Err(status)).await;
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::permission_denied(
|
|
||||||
"Only vault operations are permitted before unsealing",
|
|
||||||
)))
|
|
||||||
.await;
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let Some(vault_payload) = vault_req.payload else {
|
|
||||||
let _ = self
|
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::invalid_argument(
|
|
||||||
"Missing vault request payload",
|
|
||||||
)))
|
|
||||||
.await;
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
match vault_payload {
|
|
||||||
VaultRequestPayload::QueryState(_) => {
|
|
||||||
return Some(vault_gate::Inbound::HandleVaultState);
|
|
||||||
}
|
|
||||||
VaultRequestPayload::Unseal(req) => {
|
|
||||||
let Some(unseal_payload) = req.payload else {
|
|
||||||
let _ = self
|
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::invalid_argument(
|
|
||||||
"Missing unseal request payload",
|
|
||||||
)))
|
|
||||||
.await;
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
match unseal_payload {
|
|
||||||
UnsealRequestPayload::Start(start) => {
|
|
||||||
let Ok(bytes) = <[u8; 32]>::try_from(start.client_pubkey) else {
|
|
||||||
let _ = self
|
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::invalid_argument(
|
|
||||||
"Invalid X25519 public key",
|
|
||||||
)))
|
|
||||||
.await;
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
return Some(vault_gate::Inbound::HandleHandshake(HandleHandshake {
|
|
||||||
client_pubkey: x25519_dalek::PublicKey::from(bytes),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
UnsealRequestPayload::EncryptedKey(key) => {
|
|
||||||
return Some(vault_gate::Inbound::HandleUnsealEncryptedKey(
|
|
||||||
HandleUnsealEncryptedKey {
|
|
||||||
nonce: key.nonce,
|
|
||||||
ciphertext: key.ciphertext,
|
|
||||||
associated_data: key.associated_data,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VaultRequestPayload::Bootstrap(req) => {
|
|
||||||
let Some(encrypted_key) = req.encrypted_key else {
|
|
||||||
let _ = self
|
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::invalid_argument(
|
|
||||||
"Missing bootstrap encrypted key",
|
|
||||||
)))
|
|
||||||
.await;
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
return Some(vault_gate::Inbound::HandleBootstrapEncryptedKey(
|
|
||||||
HandleBootstrapEncryptedKey {
|
|
||||||
nonce: encrypted_key.nonce,
|
|
||||||
ciphertext: encrypted_key.ciphertext,
|
|
||||||
associated_data: encrypted_key.associated_data,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,71 +68,10 @@ impl Sender<Result<vault_gate::Outbound, vault_gate::Error>> for AuthTransportAd
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let payload = match outbound {
|
match outbound.try_convert() {
|
||||||
vault_gate::Outbound::HandleVaultState(result) => match result {
|
Ok(payload) => self.send_response_payload(payload).await,
|
||||||
Ok(state) => {
|
Err(status) => self.bi_mut().send(Err(status)).await,
|
||||||
let state = match state {
|
|
||||||
VaultState::Unbootstrapped => ProtoVaultState::Unbootstrapped,
|
|
||||||
VaultState::Sealed => ProtoVaultState::Sealed,
|
|
||||||
VaultState::Unsealed => ProtoVaultState::Unsealed,
|
|
||||||
};
|
|
||||||
|
|
||||||
wrap_vault_response(VaultResponsePayload::State(state.into()))
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
|
||||||
warn!(?err, "vault state query failed");
|
|
||||||
return self
|
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::internal("Failed to query vault state")))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
vault_gate::Outbound::HandleHandshake(Ok(response)) => wrap_unseal_response(
|
|
||||||
UnsealResponsePayload::Start(proto_unseal::UnsealStartResponse {
|
|
||||||
server_pubkey: response.server_pubkey.as_bytes().to_vec(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
vault_gate::Outbound::HandleHandshake(Err(err)) => {
|
|
||||||
warn!(?err, "handshake failed");
|
|
||||||
return self
|
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::internal("Failed to start unseal flow")))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
vault_gate::Outbound::HandleUnsealEncryptedKey(result) => {
|
|
||||||
let proto_result = match result {
|
|
||||||
Ok(()) => ProtoUnsealResult::Success,
|
|
||||||
Err(vault_gate::Error::InvalidKey) => ProtoUnsealResult::InvalidKey,
|
|
||||||
Err(err) => {
|
|
||||||
warn!(?err, "unseal failed");
|
|
||||||
return self
|
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::internal("Failed to unseal vault")))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
wrap_unseal_response(UnsealResponsePayload::Result(proto_result.into()))
|
|
||||||
}
|
|
||||||
vault_gate::Outbound::HandleBootstrapEncryptedKey(result) => {
|
|
||||||
let proto_result = match result {
|
|
||||||
Ok(()) => ProtoBootstrapResult::Success,
|
|
||||||
Err(vault_gate::Error::InvalidKey) => ProtoBootstrapResult::InvalidKey,
|
|
||||||
Err(vault_gate::Error::AlreadyBootstrapped) => {
|
|
||||||
ProtoBootstrapResult::AlreadyBootstrapped
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(?err, "bootstrap failed");
|
|
||||||
return self
|
|
||||||
.bi_mut()
|
|
||||||
.send(Err(Status::internal("Failed to bootstrap vault")))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
wrap_bootstrap_response(proto_result)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.send_response_payload(payload).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
use arbiter_proto::proto::user_agent::{
|
||||||
|
user_agent_request::Payload as UserAgentRequestPayload,
|
||||||
|
vault::{
|
||||||
|
self as proto_vault,
|
||||||
|
bootstrap::{self as proto_bootstrap},
|
||||||
|
request::Payload as VaultRequestPayload,
|
||||||
|
unseal::{self as proto_unseal, request::Payload as UnsealRequestPayload},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tonic::Status;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
grpc::{Convert, TryConvert},
|
||||||
|
peers::user_agent::vault_gate::{
|
||||||
|
self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl TryConvert for UserAgentRequestPayload {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<vault_gate::Inbound, Status> {
|
||||||
|
match self {
|
||||||
|
UserAgentRequestPayload::Vault(req) => req.try_convert(),
|
||||||
|
_ => Err(Status::permission_denied(
|
||||||
|
"Only vault operations are permitted before unsealing",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryConvert for proto_vault::Request {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<vault_gate::Inbound, Status> {
|
||||||
|
self.payload
|
||||||
|
.ok_or_else(|| Status::invalid_argument("Missing vault request payload"))?
|
||||||
|
.try_convert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryConvert for VaultRequestPayload {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<vault_gate::Inbound, Status> {
|
||||||
|
match self {
|
||||||
|
VaultRequestPayload::QueryState(_) => Ok(vault_gate::Inbound::HandleVaultState),
|
||||||
|
VaultRequestPayload::Unseal(req) => req.try_convert(),
|
||||||
|
VaultRequestPayload::Bootstrap(req) => req.try_convert(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryConvert for proto_unseal::Request {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<vault_gate::Inbound, Status> {
|
||||||
|
self.payload
|
||||||
|
.ok_or_else(|| Status::invalid_argument("Missing unseal request payload"))?
|
||||||
|
.try_convert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryConvert for UnsealRequestPayload {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<vault_gate::Inbound, Status> {
|
||||||
|
match self {
|
||||||
|
UnsealRequestPayload::Start(start) => start.try_convert(),
|
||||||
|
UnsealRequestPayload::EncryptedKey(key) => Ok(key.convert()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryConvert for proto_unseal::UnsealStart {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<vault_gate::Inbound, Status> {
|
||||||
|
let bytes = <[u8; 32]>::try_from(self.client_pubkey)
|
||||||
|
.map_err(|_| Status::invalid_argument("Invalid X25519 public key"))?;
|
||||||
|
Ok(vault_gate::Inbound::HandleHandshake(HandleHandshake {
|
||||||
|
client_pubkey: x25519_dalek::PublicKey::from(bytes),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Convert for proto_unseal::UnsealEncryptedKey {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
|
||||||
|
fn convert(self) -> vault_gate::Inbound {
|
||||||
|
vault_gate::Inbound::HandleUnsealEncryptedKey(HandleUnsealEncryptedKey {
|
||||||
|
nonce: self.nonce,
|
||||||
|
ciphertext: self.ciphertext,
|
||||||
|
associated_data: self.associated_data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryConvert for proto_bootstrap::Request {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<vault_gate::Inbound, Status> {
|
||||||
|
self.encrypted_key
|
||||||
|
.ok_or_else(|| Status::invalid_argument("Missing bootstrap encrypted key"))?
|
||||||
|
.try_convert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryConvert for proto_bootstrap::BootstrapEncryptedKey {
|
||||||
|
type Output = vault_gate::Inbound;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<vault_gate::Inbound, Status> {
|
||||||
|
Ok(vault_gate::Inbound::HandleBootstrapEncryptedKey(
|
||||||
|
HandleBootstrapEncryptedKey {
|
||||||
|
nonce: self.nonce,
|
||||||
|
ciphertext: self.ciphertext,
|
||||||
|
associated_data: self.associated_data,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
use arbiter_proto::proto::{
|
||||||
|
shared::VaultState as ProtoVaultState,
|
||||||
|
user_agent::{
|
||||||
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
|
vault::{
|
||||||
|
self as proto_vault,
|
||||||
|
bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult},
|
||||||
|
response::Payload as VaultResponsePayload,
|
||||||
|
unseal::{
|
||||||
|
self as proto_unseal, UnsealResult as ProtoUnsealResult,
|
||||||
|
response::Payload as UnsealResponsePayload,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tonic::Status;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
actors::vault::VaultState,
|
||||||
|
grpc::{Convert, TryConvert},
|
||||||
|
peers::user_agent::vault_gate::{self as vault_gate},
|
||||||
|
};
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Convert for VaultState {
|
||||||
|
type Output = UserAgentResponsePayload;
|
||||||
|
|
||||||
|
fn convert(self) -> UserAgentResponsePayload {
|
||||||
|
let proto_state = match self {
|
||||||
|
VaultState::Unbootstrapped => ProtoVaultState::Unbootstrapped,
|
||||||
|
VaultState::Sealed => ProtoVaultState::Sealed,
|
||||||
|
VaultState::Unsealed => ProtoVaultState::Unsealed,
|
||||||
|
};
|
||||||
|
wrap_vault_response(VaultResponsePayload::State(proto_state.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Convert for vault_gate::HandshakeResponse {
|
||||||
|
type Output = UserAgentResponsePayload;
|
||||||
|
|
||||||
|
fn convert(self) -> UserAgentResponsePayload {
|
||||||
|
wrap_unseal_response(UnsealResponsePayload::Start(
|
||||||
|
proto_unseal::UnsealStartResponse {
|
||||||
|
server_pubkey: self.server_pubkey.as_bytes().to_vec(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryConvert for vault_gate::Outbound {
|
||||||
|
type Output = UserAgentResponsePayload;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn try_convert(self) -> Result<UserAgentResponsePayload, Status> {
|
||||||
|
match self {
|
||||||
|
vault_gate::Outbound::HandleVaultState(result) => result
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!(?err, "vault state query failed");
|
||||||
|
Status::internal("Failed to query vault state")
|
||||||
|
})
|
||||||
|
.map(VaultState::convert),
|
||||||
|
vault_gate::Outbound::HandleHandshake(result) => result
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!(?err, "handshake failed");
|
||||||
|
Status::internal("Failed to start unseal flow")
|
||||||
|
})
|
||||||
|
.map(vault_gate::HandshakeResponse::convert),
|
||||||
|
vault_gate::Outbound::HandleUnsealEncryptedKey(result) => {
|
||||||
|
let proto_result = match result {
|
||||||
|
Ok(()) => ProtoUnsealResult::Success,
|
||||||
|
Err(vault_gate::Error::InvalidKey) => ProtoUnsealResult::InvalidKey,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "unseal failed");
|
||||||
|
return Err(Status::internal("Failed to unseal vault"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(wrap_unseal_response(UnsealResponsePayload::Result(
|
||||||
|
proto_result.into(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
vault_gate::Outbound::HandleBootstrapEncryptedKey(result) => {
|
||||||
|
let proto_result = match result {
|
||||||
|
Ok(()) => ProtoBootstrapResult::Success,
|
||||||
|
Err(vault_gate::Error::InvalidKey) => ProtoBootstrapResult::InvalidKey,
|
||||||
|
Err(vault_gate::Error::AlreadyBootstrapped) => {
|
||||||
|
ProtoBootstrapResult::AlreadyBootstrapped
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "bootstrap failed");
|
||||||
|
return Err(Status::internal("Failed to bootstrap vault"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(wrap_bootstrap_response(proto_result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user