Skip to content

Commit

Permalink
Phase 1 of auth/auth work
Browse files Browse the repository at this point in the history
All RPC requests to the daemon now have attached Client and Auth
PASETO tokens. The former is for validating that the RPC connection is
who it says it is. The latter is for validating that the user is who
they say they are.

It is now possible to get an auth token from logging in through
`/ws/auth` endpoint. Though there's nothing yet in place to use it.

The idea is that websocket connections (and HTTP generally as well)
will work this way:

1) auth and get a PASETO token for the player that is auth'd to.

2) subsequent calls to the system -- such as a websocket attach, or a
request to retrieve a property or verb, etc.) will attach the auth
token in a `X-Moor-Auth-Token` header on each request.

TODO:

  * validate the player inside the Scheduler/Task layer before
    launching a task (and return E_PERM etc.)

  * add a websocket connect that uses the authorization header to skip
    login, and remove the Basic-Auth (which can't work from a browser
    now anyways)

(#30)
  • Loading branch information
rdaum committed Nov 1, 2023
1 parent 34cd774 commit 81b9abe
Show file tree
Hide file tree
Showing 16 changed files with 790 additions and 223 deletions.
376 changes: 238 additions & 138 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ yoke-derive = "0.7.2"
pwhash = "1.0.0" # For MOO's hokey "crypt" function, which is unix's crypt(3) basically
md5 = "0.7.0" # For MOO's "string_hash"
rand = "0.8.5"
onig = "6.4.0"
onig = { version = "6.4.0", default-features = false }
chrono-tz = "0.8.3"
iana-time-zone = "0.1.57"

Expand Down Expand Up @@ -103,3 +103,7 @@ tempfile = "3.8.0"
pretty_assertions = "1.4.0"
test-case = "3.2.1"
unindent = "0.2.3"

# Auth/Auth
paseto = "2.0.2"
ring = { version = "*", features = ["std"] }
3 changes: 1 addition & 2 deletions crates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ Binaries:
Libraries:
* `values` - crate that implements the core MOO discriminated union (`Var`) value type,
plus all associated types and traits.
* `db` - implementation of the `WorldState` object database trait w/ a (for now) RocksDB backend, along with mock/testing
database implementations.
* `db` - implementation of the `WorldState` object database trait w/ a custom MVCC in-memory database.
* `compiler` - the MOO language grammar, parser, AST, and codegen, as well as the decompiler & unparser
* `kernel` - the kernel of the MOO driver: virtual machine, task scheduler, implementations of all builtin
functions
Expand Down
3 changes: 2 additions & 1 deletion crates/console-host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ tracing.workspace = true
tracing-subscriber.workspace = true
tracing-chrome.workspace = true

## ZMQ
## ZMQ / RPC
tmq.workspace = true
uuid.workspace = true
itertools.workspace = true
paseto.workspace = true

## For console
rustyline.workspace = true
75 changes: 58 additions & 17 deletions crates/console-host/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use uuid::Uuid;
use rpc_common::pubsub_client::{broadcast_recv, narrative_recv};
use rpc_common::rpc_client::RpcSendClient;
use rpc_common::{
BroadcastEvent, ConnectionEvent, RpcRequest, RpcResponse, RpcResult, BROADCAST_TOPIC,
AuthToken, BroadcastEvent, ClientToken, ConnectionEvent, RpcRequest, RpcResponse, RpcResult,
BROADCAST_TOPIC,
};

#[derive(Parser, Debug)]
Expand Down Expand Up @@ -60,15 +61,15 @@ struct Args {
async fn establish_connection(
client_id: Uuid,
rpc_client: &mut RpcSendClient,
) -> Result<Objid, anyhow::Error> {
) -> Result<(ClientToken, Objid), anyhow::Error> {
match rpc_client
.make_rpc_call(
client_id,
RpcRequest::ConnectionEstablish("console".to_string()),
)
.await
{
Ok(RpcResult::Success(RpcResponse::NewConnection(conn_id))) => Ok(conn_id),
Ok(RpcResult::Success(RpcResponse::NewConnection(token, conn_id))) => Ok((token, conn_id)),
Ok(RpcResult::Success(other)) => {
error!("Unexpected response: {:?}", other);
Err(Error::msg("Unexpected response"))
Expand All @@ -85,26 +86,34 @@ async fn establish_connection(
}

async fn perform_auth(
token: ClientToken,
client_id: Uuid,
rpc_client: &mut RpcSendClient,
username: &str,
password: &str,
) -> Result<Objid, Error> {
) -> Result<(AuthToken, Objid), Error> {
// Need to first authenticate with the server.
match rpc_client
.make_rpc_call(
client_id,
RpcRequest::LoginCommand(vec![
"connect".to_string(),
username.to_string(),
password.to_string(),
]),
RpcRequest::LoginCommand(
token,
vec![
"connect".to_string(),
username.to_string(),
password.to_string(),
],
),
)
.await
{
Ok(RpcResult::Success(RpcResponse::LoginResult(Some((connect_type, player))))) => {
Ok(RpcResult::Success(RpcResponse::LoginResult(Some((
auth_token,
connect_type,
player,
))))) => {
info!("Authenticated as {:?} with id {:?}", connect_type, player);
Ok(player)
Ok((auth_token, player))
}
Ok(RpcResult::Success(RpcResponse::LoginResult(None))) => {
error!("Authentication failed");
Expand All @@ -126,6 +135,8 @@ async fn perform_auth(
}

async fn handle_console_line(
client_token: ClientToken,
auth_token: AuthToken,
client_id: Uuid,
line: &str,
rpc_client: &mut RpcSendClient,
Expand All @@ -143,7 +154,12 @@ async fn handle_console_line(
match rpc_client
.make_rpc_call(
client_id,
RpcRequest::RequestedInput(input_request_id.as_u128(), line.to_string()),
RpcRequest::RequestedInput(
client_token.clone(),
auth_token.clone(),
input_request_id.as_u128(),
line.to_string(),
),
)
.await
{
Expand All @@ -164,7 +180,10 @@ async fn handle_console_line(
}

match rpc_client
.make_rpc_call(client_id, RpcRequest::Command(line.to_string()))
.make_rpc_call(
client_id,
RpcRequest::Command(client_token.clone(), auth_token.clone(), line.to_string()),
)
.await
{
Ok(RpcResult::Success(RpcResponse::CommandSubmitted(_))) => {
Expand Down Expand Up @@ -201,11 +220,18 @@ async fn console_loop(

let mut rpc_client = RpcSendClient::new(rcp_request_sock);

let conn_obj_id = establish_connection(client_id, &mut rpc_client).await?;
let (client_token, conn_obj_id) = establish_connection(client_id, &mut rpc_client).await?;
debug!("Transitional connection ID before auth: {:?}", conn_obj_id);

// Now authenticate with the server.
let player = perform_auth(client_id, &mut rpc_client, username, password).await?;
let (auth_token, player) = perform_auth(
client_token.clone(),
client_id,
&mut rpc_client,
username,
password,
)
.await?;

info!("Authenticated as {:?} / {}", username, player);

Expand Down Expand Up @@ -256,12 +282,17 @@ async fn console_loop(
.connect(rpc_server)
.expect("Unable to bind RPC server for connection");
let mut broadcast_rpc_client = RpcSendClient::new(broadcast_rcp_request_sock);

let broadcast_client_token = client_token.clone();
let broadcast_loop = tokio::spawn(async move {
loop {
match broadcast_recv(&mut broadcast_subscriber).await {
Ok(BroadcastEvent::PingPong(_)) => {
if let Err(e) = broadcast_rpc_client
.make_rpc_call(client_id, RpcRequest::Pong(SystemTime::now()))
.make_rpc_call(
client_id,
RpcRequest::Pong(broadcast_client_token.clone(), SystemTime::now()),
)
.await
{
error!("Error sending pong: {:?}", e);
Expand All @@ -276,6 +307,8 @@ async fn console_loop(
}
});

let edit_client_token = client_token.clone();
let edit_auth_token = auth_token.clone();
let edit_loop = tokio::spawn(async move {
let mut rl = DefaultEditor::new().unwrap();
loop {
Expand All @@ -293,7 +326,15 @@ async fn console_loop(
Ok(line) => {
rl.add_history_entry(line.clone())
.expect("Could not add history");
handle_console_line(client_id, &line, &mut rpc_client, input_request_id).await;
handle_console_line(
edit_client_token.clone(),
edit_auth_token.clone(),
client_id,
&line,
&mut rpc_client,
input_request_id,
)
.await;
}
Err(ReadlineError::Eof) => {
println!("<EOF>");
Expand Down
3 changes: 3 additions & 0 deletions crates/daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ serde_json.workspace = true
serde.workspace = true
chrono.workspace = true

# Auth/Auth
paseto.workspace = true
ring.workspace = true
50 changes: 50 additions & 0 deletions crates/daemon/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use tmq::Multipart;
use tokio::select;
use tokio::signal::unix::{signal, SignalKind};
use tracing::info;
use {ring::rand::SystemRandom, ring::signature::Ed25519KeyPair};

use moor_db::{DatabaseBuilder, DatabaseType};
use moor_kernel::tasks::scheduler::Scheduler;
Expand Down Expand Up @@ -75,6 +76,22 @@ struct Args {
default_value = "tcp://0.0.0.0:7898"
)]
narrative_listen: String,

#[arg(
long,
value_name = "keypair",
help = "file containing a pkcs8 ed25519, used for authenticating client connections",
default_value = "keypair.pkcs8"
)]
keypair: PathBuf,

#[arg(
long,
value_name = "generate-keypair",
help = "Generate a new keypair and save it to the keypair files, if they don't exist already",
default_value = "false"
)]
generate_keypair: bool,
}

pub(crate) fn make_response(result: Result<RpcResponse, RpcRequestError>) -> Multipart {
Expand Down Expand Up @@ -111,6 +128,38 @@ async fn main() {
.install()
.expect("failed to install Prometheus recorder");

// Check the public/private keypair file to see if it exists. If it does, parse it and establish
// the PASETO keypair from it...
let keypair = if args.keypair.exists() {
let keypair_bytes = std::fs::read(args.keypair).expect("Unable to read keypair file");
let keypair = Ed25519KeyPair::from_pkcs8(keypair_bytes.as_ref())
.expect("Unable to parse keypair file");
keypair
} else {
// Otherwise, check to see if --generate-keypair was passed. If it was, generate a new
// keypair and save it to the file; otherwise, error out.

if args.generate_keypair {
let sys_rand = SystemRandom::new();
let key_pkcs8 =
Ed25519KeyPair::generate_pkcs8(&sys_rand).expect("Failed to generate pkcs8 key!");
let keypair =
Ed25519KeyPair::from_pkcs8(key_pkcs8.as_ref()).expect("Failed to parse keypair");
let pkcs8_keypair_bytes: &[u8] = key_pkcs8.as_ref();

// Now write it out...
std::fs::write(args.keypair, pkcs8_keypair_bytes)
.expect("Unable to write keypair file");

keypair
// Write
} else {
panic!(
"Public/private keypair files do not exist, and --generate-keypair was not passed"
);
}
};

info!("Daemon starting...");
let db_source_builder = DatabaseBuilder::new()
.with_db_type(args.db_type)
Expand Down Expand Up @@ -157,6 +206,7 @@ async fn main() {
let scheduler_loop = tokio::spawn(async move { loop_scheduler.run().await });

let zmq_server_loop = zmq_loop(
keypair,
args.connections_file,
state_source,
scheduler.clone(),
Expand Down
Loading

0 comments on commit 81b9abe

Please sign in to comment.