Skip to content

Commit

Permalink
Cleanup and fix PASTEO token key management
Browse files Browse the repository at this point in the history
Docker compose had stopped working because the containers weren't being
properly populated with the keypair PEM files.

Plus the logic was... wrong. Overall.

  * Fix the key pair management stuff to properly distinguish between
  private and public keys
  * Remove --generate-keypairs code, and let the user generate it
  themselves using standard tools...
  * ... like openssl which is now used in the Dockerfile to generate the
   keypairs and propagate into the containers
  • Loading branch information
rdaum committed Dec 22, 2024
1 parent 7001d7f commit 3254a06
Show file tree
Hide file tree
Showing 19 changed files with 103 additions and 115 deletions.
18 changes: 8 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ lazy_static = "1.5"
num-traits = "0.2"
oneshot = { version = "0.1", default-features = false, features = ["std"] }
semver = "1.0.24"
strum = { version = "0.26", features = ["derive"] }
similar = "*"
similar-asserts = "*"
strum = { version = "0.26", features = ["derive"] }
ustr = "1.0"
uuid = { version = "1.11", features = ["v4"] }
xml-rs = "0.8.24"
Expand Down Expand Up @@ -141,8 +141,7 @@ test_each_file = "0.3"
unindent = "0.2"

# Auth/Auth
ed25519-dalek = { version = "2.1", features = ["zeroize", "pkcs8", "rand_core"] }
pem = "3.0"
ed25519-dalek = { version = "2.0", features = ["pkcs8", "pem", "signature"] }
rusty_paseto = { version = "0.7" }
signal-hook = "0.3"

Expand Down
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ FROM rust:1.81-bookworm
WORKDIR /moor
RUN apt update
RUN apt -y install clang-16 libclang-16-dev swig python3-dev cmake libc6
# Generate the keypair for signing PASETO tokens. Shared between hosts and the daemon.
RUN openssl genpkey -algorithm ed25519 -out moor-signing-key.pem
RUN openssl pkey -in moor-signing-key.pem -pubout -out moor-verifying-key.pem
EXPOSE 8080
COPY ./crates ./crates
COPY ./Cargo.toml ./Cargo.toml
Expand Down
3 changes: 0 additions & 3 deletions crates/daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,4 @@ uuid.workspace = true
zmq.workspace = true

# Auth/Auth
ed25519-dalek.workspace = true
pem.workspace = true
rand.workspace = true
rusty_paseto.workspace = true
16 changes: 4 additions & 12 deletions crates/daemon/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,27 +87,19 @@ pub struct Args {
#[arg(
long,
value_name = "public_key",
help = "file containing a pkcs8 ed25519 public key, used for authenticating client & host connections",
default_value = "public_key.pem"
help = "file containing the PEM encoded public key (shared with the daemon), used for authenticating client & host connections",
default_value = "moor-verifying-key.pem"
)]
pub public_key: PathBuf,

#[arg(
long,
value_name = "private_key",
help = "file containing a pkcs8 ed25519 private key, used for authenticating client & host connections",
default_value = "private_key.pem"
help = "file containing an openssh generated ed25519 format private key (shared with the daemon), used for authenticating client & host connections",
default_value = "moor-signing-key.pem"
)]
pub private_key: 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"
)]
pub generate_keypair: bool,

#[arg(
long,
value_name = "num-io-threads",
Expand Down
36 changes: 7 additions & 29 deletions crates/daemon/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,12 @@ use std::sync::Arc;
use crate::args::Args;
use crate::rpc_server::RpcServer;
use clap::Parser;
use ed25519_dalek::SigningKey;
use eyre::Report;
use moor_db::{Database, TxDB};
use moor_kernel::tasks::scheduler::Scheduler;
use moor_kernel::tasks::{NoopTasksDb, TasksDb};
use moor_kernel::textdump::textdump_load;
use pem::Pem;
use rand::rngs::OsRng;
use rpc_common::load_keypair;
use rusty_paseto::core::Key;
use tracing::{debug, info, warn};

mod connections;
Expand Down Expand Up @@ -69,33 +65,14 @@ fn main() -> Result<(), Report> {

// Check the public/private keypair file to see if it exists. If it does, parse it and establish
// the keypair from it...
let keypair = if args.public_key.exists() && args.private_key.exists() {
let (private_key, public_key) = if args.public_key.exists() && args.private_key.exists() {
load_keypair(&args.public_key, &args.private_key)
.expect("Unable to load keypair from public and private key files")
} 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 mut csprng = OsRng;
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
let keypair: Key<64> = Key::from(signing_key.to_keypair_bytes());

let privkey_pem = Pem::new("PRIVATE KEY", signing_key.to_bytes());
let pubkey_pem = Pem::new("PUBLIC KEY", signing_key.verifying_key().to_bytes());

// And write to the files...
std::fs::write(args.private_key.clone(), pem::encode(&privkey_pem))
.expect("Unable to write private key");
std::fs::write(args.public_key.clone(), pem::encode(&pubkey_pem))
.expect("Unable to write public key");

keypair
// Write
} else {
panic!(
"Public/private keypair files do not exist, and --generate-keypair was not passed"
);
}
panic!(
"Public ({:?}) and/or private ({:?}) key files must exist",
args.public_key, args.private_key
);
};

let config = args
Expand Down Expand Up @@ -164,7 +141,8 @@ fn main() -> Result<(), Report> {
.set_io_threads(args.num_io_threads)
.expect("Failed to set number of IO threads");
let rpc_server = Arc::new(RpcServer::new(
keypair,
public_key,
private_key,
args.connections_file,
zmq_ctx.clone(),
args.events_listen.as_str(),
Expand Down
25 changes: 14 additions & 11 deletions crates/daemon/src/rpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ use zmq::{Socket, SocketType};

pub struct RpcServer {
zmq_context: zmq::Context,
keypair: Key<64>,
public_key: Key<32>,
private_key: Key<64>,
pub(crate) events_publish: Arc<Mutex<Socket>>,
connections: Arc<dyn ConnectionsDB + Send + Sync>,
task_handles: Mutex<HashMap<TaskId, (Uuid, TaskHandle)>>,
Expand Down Expand Up @@ -95,7 +96,8 @@ pub(crate) fn pack_host_response(result: Result<DaemonToHostReply, RpcMessageErr

impl RpcServer {
pub fn new(
keypair: Key<64>,
public_key: Key<32>,
private_key: Key<64>,
connections_db_path: PathBuf,
zmq_context: zmq::Context,
narrative_endpoint: &str,
Expand All @@ -121,7 +123,8 @@ impl RpcServer {
);
let kill_switch = Arc::new(AtomicBool::new(false));
Self {
keypair,
public_key,
private_key,
connections,
events_publish: Arc::new(Mutex::new(publish)),
zmq_context,
Expand Down Expand Up @@ -1235,7 +1238,7 @@ impl RpcServer {
/// validate the client connection to the daemon for future requests.
fn make_client_token(&self, client_id: Uuid) -> ClientToken {
let privkey: PasetoAsymmetricPrivateKey<V4, Public> =
PasetoAsymmetricPrivateKey::from(self.keypair.as_ref());
PasetoAsymmetricPrivateKey::from(self.private_key.as_ref());
let token = Paseto::<V4, Public>::default()
.set_footer(Footer::from(MOOR_SESSION_TOKEN_FOOTER))
.set_payload(Payload::from(
Expand All @@ -1256,7 +1259,7 @@ impl RpcServer {
/// Construct a PASETO token for this player login. This token is used to provide credentials
/// for requests, to allow reconnection with a different client_id.
fn make_auth_token(&self, oid: &Obj) -> AuthToken {
let privkey = PasetoAsymmetricPrivateKey::from(self.keypair.as_ref());
let privkey = PasetoAsymmetricPrivateKey::from(self.private_key.as_ref());
let token = Paseto::<V4, Public>::default()
.set_footer(Footer::from(MOOR_AUTH_TOKEN_FOOTER))
.set_payload(Payload::from(
Expand Down Expand Up @@ -1284,8 +1287,8 @@ impl RpcServer {
}
}
}
let key: Key<32> = Key::from(&self.keypair[32..]);
let pk: PasetoAsymmetricPublicKey<V4, Public> = PasetoAsymmetricPublicKey::from(&key);
let pk: PasetoAsymmetricPublicKey<V4, Public> =
PasetoAsymmetricPublicKey::from(&self.public_key);
let host_type = Paseto::<V4, Public>::try_verify(
token.0.as_str(),
&pk,
Expand Down Expand Up @@ -1325,8 +1328,8 @@ impl RpcServer {
}
}

let key: Key<32> = Key::from(&self.keypair[32..]);
let pk: PasetoAsymmetricPublicKey<V4, Public> = PasetoAsymmetricPublicKey::from(&key);
let pk: PasetoAsymmetricPublicKey<V4, Public> =
PasetoAsymmetricPublicKey::from(&self.public_key);
let verified_token = Paseto::<V4, Public>::try_verify(
token.0.as_str(),
&pk,
Expand Down Expand Up @@ -1391,8 +1394,8 @@ impl RpcServer {
}
}
}
let key: Key<32> = Key::from(&self.keypair[32..]);
let pk: PasetoAsymmetricPublicKey<V4, Public> = PasetoAsymmetricPublicKey::from(&key);
let pk: PasetoAsymmetricPublicKey<V4, Public> =
PasetoAsymmetricPublicKey::from(&self.public_key);
let verified_token = Paseto::<V4, Public>::try_verify(
token.0.as_str(),
&pk,
Expand Down
4 changes: 2 additions & 2 deletions crates/kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ moor-db = { path = "../db" }
criterion.workspace = true
eyre.workspace = true
pretty_assertions.workspace = true
test-case.workspace = true
test_each_file.workspace = true
similar.workspace = true
similar-asserts.workspace = true
test-case.workspace = true
test_each_file.workspace = true
tracing.workspace = true

[[test]]
Expand Down
5 changes: 3 additions & 2 deletions crates/rpc/rpc-async-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ pub mod pubsub_client;
pub mod rpc_client;

/// Construct a PASETO token for this host, to authenticate the host itself to the daemon.
pub fn make_host_token(keypair: &Key<64>, host_type: HostType) -> HostToken {
let privkey: PasetoAsymmetricPrivateKey<V4, Public> = PasetoAsymmetricPrivateKey::from(keypair);
pub fn make_host_token(private_key: &Key<64>, host_type: HostType) -> HostToken {
let privkey: PasetoAsymmetricPrivateKey<V4, Public> =
PasetoAsymmetricPrivateKey::from(private_key.as_ref());
let token = Paseto::<V4, Public>::default()
.set_footer(Footer::from(MOOR_HOST_TOKEN_FOOTER))
.set_payload(Payload::from(host_type.id_str()))
Expand Down
2 changes: 1 addition & 1 deletion crates/rpc/rpc-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ clap_derive.workspace = true
thiserror.workspace = true

# Auth/Auth
pem.workspace = true
ed25519-dalek.workspace = true
rusty_paseto.workspace = true
8 changes: 4 additions & 4 deletions crates/rpc/rpc-common/src/client_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ pub struct RpcClientArgs {
#[arg(
long,
value_name = "public_key",
help = "file containing the pkcs8 ed25519 public key (shared with the daemon), used for authenticating client & host connections",
default_value = "public_key.pem"
help = "file containing the PEM encoded public key (shared with the daemon), used for authenticating client & host connections",
default_value = "moor-verifying-key.pem"
)]
pub public_key: PathBuf,

#[arg(
long,
value_name = "private_key",
help = "file containing a pkcs8 ed25519 private key (shared with the daemon), used for authenticating client & host connections",
default_value = "private_key.pem"
help = "file containing an openssh generated ed25519 format private key (shared with the daemon), used for authenticating client & host connections",
default_value = "moor-signing-key.pem"
)]
pub private_key: PathBuf,
}
Loading

0 comments on commit 3254a06

Please sign in to comment.