Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

replicate: use bearer token instead of did #111

Merged
merged 1 commit into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions crates/replicate/client/examples/example-client.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use clap::Parser;
use color_eyre::{eyre::WrapErr, Result};
use replicate_client::{instance::Instance, manager::Manager};
use replicate_common::{
data_model::State,
did::{AuthenticationAttestation, Did, DidPrivateKey},
};
use replicate_common::data_model::State;
use tracing::info;
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
use url::Url;
Expand All @@ -14,8 +11,9 @@ use url::Url;
pub struct Args {
#[clap(long)]
url: Url,
/// Optional bearer token to use for authenticating.
#[clap(long)]
username: String,
token: Option<String>,
}

#[tokio::main]
Expand All @@ -34,12 +32,7 @@ async fn main() -> Result<()> {

let args = Args::parse();

let did = Did(args.username);
let did_private_key = DidPrivateKey;

let auth_attest = AuthenticationAttestation::new(did, &did_private_key);

let mut manager = Manager::connect(args.url, &auth_attest)
let mut manager = Manager::connect(args.url, args.token.as_deref())
.await
.wrap_err("failed to connect to manager")?;
info!("Connected to manager!");
Expand All @@ -55,7 +48,7 @@ async fn main() -> Result<()> {
.wrap_err("failed to get instance url")?;
info!("Got instance {instance_id} at: {instance_url}");

let mut instance = Instance::connect(instance_url, auth_attest)
let mut instance = Instance::connect(instance_url, args.token.as_deref())
.await
.wrap_err("failed to connect to instance")?;
info!("Connected to instance!");
Expand Down
69 changes: 11 additions & 58 deletions crates/replicate/client/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,6 @@
//! disconnects and reconnects. The client must prove ownership of this unique
//! identifier.
//!
//! The most logical implementation strategy to accomplish this is to solve it the same
//! way that the nexus protocol does - every user is identified by a
//! [Decentralized Identifier][DID][^1], and they sign a message with the DID's associated
//! private key, proving that they are who they say they are. This is done via the
//! [`AuthenticationAttestation`] argument when connecting to the instance.
//!
//! # Entities
//! Each entity has state, and the instance has many entities. An entity is identified
//! with an id, which the server assigns in response to a request to spawn an entity
Expand Down Expand Up @@ -79,18 +73,14 @@
//! [did:web]: https://w3c-ccg.github.io/did-method-web/
//! [ABA]: https://en.wikipedia.org/wiki/ABA_problem

use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD};
use eyre::{bail, ensure, Result, WrapErr};
use futures::{SinkExt, StreamExt};
use replicate_common::{
data_model::{DataModel, Entity, LocalChanges, RemoteChanges, State},
did::AuthenticationAttestation,
use replicate_common::data_model::{
DataModel, Entity, LocalChanges, RemoteChanges, State,
};
use tracing::warn;
use url::Url;
use wtransport::{endpoint::ConnectOptions, ClientConfig, Endpoint};

use crate::CertHashDecodeErr;
use crate::{connect_to_url, Ascii};

use replicate_common::messages::instance::{Clientbound as Cb, Serverbound as Sb};
type RpcFramed = replicate_common::Framed<wtransport::stream::BiStream, Cb, Sb>;
Expand Down Expand Up @@ -118,13 +108,14 @@ impl Instance {
/// # Arguments
/// - `url`: Url of the manager api. For example, `https://foobar.com/my/manager`
/// or `192.168.1.1:1337/uwu/some_manager`.
/// - `auth_attest`: Used to provide to the server proof of our identity, based on
/// our DID.
pub async fn connect(
url: Url,
auth_attest: AuthenticationAttestation,
) -> Result<Self> {
let conn = connect_to_url(&url, auth_attest)
/// - `bearer_token`: optional, must be ascii otherwise we will panic.
pub async fn connect(url: Url, bearer_token: Option<&str>) -> Result<Self> {
let bearer_token = bearer_token.map(|s| {
// Technically, bearer tokens only permit a *subset* of ascii. But I
// didn't care enough to be that precise.
Ascii::try_from(s).expect("to be in-spec, bearer tokens must be ascii")
});
let conn = connect_to_url(&url, bearer_token)
.await
.wrap_err("failed to connect to server")?;

Expand Down Expand Up @@ -196,41 +187,3 @@ pub enum RecvState<'a> {
/// Sequence number for state messages
#[derive(Debug, Default)]
pub struct StateSeq;

async fn connect_to_url(
url: &Url,
auth_attest: AuthenticationAttestation,
) -> Result<wtransport::Connection> {
let cert_hash = if let Some(frag) = url.fragment() {
let cert_hash = BASE64_URL_SAFE_NO_PAD
.decode(frag)
.map_err(CertHashDecodeErr::from)?;
let len = cert_hash.len();
let cert_hash: [u8; 32] = cert_hash
.try_into()
.map_err(|_| CertHashDecodeErr::InvalidLen(len))?;
Some(cert_hash)
} else {
None
};

let cfg = ClientConfig::builder().with_bind_default();
let cfg = if let Some(_cert_hash) = cert_hash {
// TODO: Implement self signed certs properly:
// https://github.com/BiagioFesta/wtransport/issues/128
warn!(
"`serverCertificateHashes` is not yet supported, turning off \
cert validation."
);
cfg.with_no_cert_validation()
} else {
cfg.with_native_certs()
}
.build();

let client = Endpoint::client(cfg)?;
let opts = ConnectOptions::builder(url)
.add_header("Authorization", format!("Bearer {}", auth_attest))
.build();
Ok(client.connect(opts).await?)
}
63 changes: 63 additions & 0 deletions crates/replicate/client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine as _};
use eyre::Result;
use tracing::warn;
use url::Url;

pub use replicate_common as common;
use wtransport::{endpoint::ConnectOptions, ClientConfig, Endpoint};

pub mod instance;
pub mod manager;
Expand All @@ -11,3 +17,60 @@ pub enum CertHashDecodeErr {
#[error("expected length of 32, got length of {0}")]
InvalidLen(usize),
}

/// A string that has been validated to be ascii.
struct Ascii<'a>(&'a str);

impl<'a> TryFrom<&'a str> for Ascii<'a> {
type Error = ();

fn try_from(value: &'a str) -> std::prelude::v1::Result<Self, Self::Error> {
if value.is_ascii() {
Ok(Self(value))
} else {
Err(())
}
}
}

/// If there is a url fragment, it will be treated as a server certificate hash.
async fn connect_to_url(
url: &Url,
bearer_token: Option<Ascii<'_>>,
) -> Result<wtransport::Connection> {
let cert_hash = if let Some(frag) = url.fragment() {
let cert_hash = BASE64_URL_SAFE_NO_PAD
.decode(frag)
.map_err(CertHashDecodeErr::from)?;
let len = cert_hash.len();
let cert_hash: [u8; 32] = cert_hash
.try_into()
.map_err(|_| CertHashDecodeErr::InvalidLen(len))?;
Some(cert_hash)
} else {
None
};

let cfg = ClientConfig::builder().with_bind_default();
let cfg = if let Some(_cert_hash) = cert_hash {
// TODO: Implement self signed certs properly:
// https://github.com/BiagioFesta/wtransport/issues/128
warn!(
"`serverCertificateHashes` is not yet supported, turning off \
cert validation."
);
cfg.with_no_cert_validation()
} else {
cfg.with_native_certs()
}
.build();

let client = Endpoint::client(cfg)?;
let opts = ConnectOptions::builder(url);
let opts = if let Some(b) = bearer_token {
opts.add_header("Authorization", format!("Bearer {}", b.0))
} else {
opts
};
Ok(client.connect(opts.build()).await?)
}
53 changes: 10 additions & 43 deletions crates/replicate/client/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@

use std::fmt::Debug;

use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD};
use eyre::{bail, ensure, eyre, Context};
use futures::sink::SinkExt;
use futures::stream::StreamExt;
use replicate_common::{
did::AuthenticationAttestation,
messages::manager::{Clientbound as Cb, Serverbound as Sb},
InstanceId,
};
use tracing::warn;
use url::Url;
use wtransport::{endpoint::ConnectOptions, ClientConfig, Endpoint};

use crate::CertHashDecodeErr;
use crate::connect_to_url;
use crate::Ascii;

type Result<T> = eyre::Result<T>;
type Framed = replicate_common::Framed<wtransport::stream::BiStream, Cb, Sb>;
Expand All @@ -37,44 +34,14 @@ impl Manager {
/// # Arguments
/// - `url`: Url of the manager api. For example, `https://foobar.com/my/manager`
/// or `192.168.1.1:1337/uwu/some_manager`.
/// - `auth_attest`: Used to provide to the server proof of our identity, based on
/// our DID.
pub async fn connect(
url: Url,
auth_attest: &AuthenticationAttestation,
) -> Result<Self> {
let cert_hash = if let Some(frag) = url.fragment() {
let cert_hash = BASE64_URL_SAFE_NO_PAD
.decode(frag)
.map_err(CertHashDecodeErr::from)?;
let len = cert_hash.len();
let cert_hash: [u8; 32] = cert_hash
.try_into()
.map_err(|_| CertHashDecodeErr::InvalidLen(len))?;
Some(cert_hash)
} else {
None
};

let cfg = ClientConfig::builder().with_bind_default();
let cfg = if let Some(_cert_hash) = cert_hash {
warn!(
"`serverCertificateHashes` is not yet supported, turning off \
cert validation."
);
// TODO: Use the cert hash as the root cert instead of no validation
cfg.with_no_cert_validation()
} else {
cfg.with_native_certs()
}
.build();

let client = Endpoint::client(cfg)?;
let opts = ConnectOptions::builder(&url)
.add_header("Authorization", format!("Bearer {}", auth_attest))
.build();
let conn = client
.connect(opts)
/// - `bearer_token`: optional, must be ascii otherwise we will panic.
pub async fn connect(url: Url, bearer_token: Option<&str>) -> Result<Self> {
let bearer_token = bearer_token.map(|s| {
// Technically, bearer tokens only permit a *subset* of ascii. But I
// didn't care enough to be that precise.
Ascii::try_from(s).expect("to be in-spec, bearer tokens must be ascii")
});
let conn = connect_to_url(&url, bearer_token)
.await
.wrap_err("failed to connect to server")?;
let bi = wtransport::stream::BiStream::join(
Expand Down
88 changes: 0 additions & 88 deletions crates/replicate/common/src/did.rs

This file was deleted.

1 change: 0 additions & 1 deletion crates/replicate/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
pub mod data_model;
pub mod did;
mod framed;
pub mod messages;

Expand Down
Loading