some work on porting client
This commit is contained in:
parent
eaf4cee4ee
commit
5cbab46d3b
|
@ -625,6 +625,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.0",
|
"base64 0.21.0",
|
||||||
"base64-serde",
|
"base64-serde",
|
||||||
|
"chrono",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|
|
@ -20,4 +20,10 @@ url = "2.3.1"
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
serde_json = "1.0.95"
|
serde_json = "1.0.95"
|
||||||
trifid-pki = { version = "0.1.6", path = "../trifid-pki" }
|
trifid-pki = { version = "0.1.6", path = "../trifid-pki" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
chrono = "0.4.24"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["blocking"]
|
||||||
|
blocking = ["reqwest/blocking"]
|
||||||
|
async = []
|
|
@ -1 +0,0 @@
|
||||||
//! Client structs to handle communication with the Defined Networking API.
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
//! Client structs to handle communication with the Defined Networking API. This is the blocking client API - if you want async instead, set no-default-features and enable the async feature instead.
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use chrono::Local;
|
||||||
|
use log::{debug, error};
|
||||||
|
use url::Url;
|
||||||
|
use trifid_pki::cert::serialize_ed25519_public;
|
||||||
|
use crate::credentials::{Credentials, ed25519_public_keys_from_pem};
|
||||||
|
use crate::crypto::new_keys;
|
||||||
|
use crate::message::{ENROLL_ENDPOINT, EnrollRequest, EnrollResponse};
|
||||||
|
|
||||||
|
/// A type alias to abstract return types
|
||||||
|
pub type NebulaConfig = Vec<u8>;
|
||||||
|
|
||||||
|
/// A type alias to abstract DH private keys
|
||||||
|
pub type DHPrivateKeyPEM = Vec<u8>;
|
||||||
|
|
||||||
|
/// A combination of persistent data and HTTP client used for communicating with the API.
|
||||||
|
pub struct Client {
|
||||||
|
http_client: reqwest::blocking::Client,
|
||||||
|
server_url: Url
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct containing organization metadata returned as a result of enrollment
|
||||||
|
pub struct EnrollMeta {
|
||||||
|
/// The server organization ID this node is now a member of
|
||||||
|
pub organization_id: String,
|
||||||
|
/// The server organization name this node is now a member of
|
||||||
|
pub organization_name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Create a new `Client` configured with the given User-Agent and API base.
|
||||||
|
/// # Errors
|
||||||
|
/// This function will return an error if the reqwest Client could not be created.
|
||||||
|
pub fn new(user_agent: String, api_base: Url) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let client = reqwest::blocking::Client::builder().user_agent(user_agent).build()?;
|
||||||
|
Ok(Self {
|
||||||
|
http_client: client,
|
||||||
|
server_url: api_base
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Issues an enrollment request against the REST API using the given enrollment code, passing along a
|
||||||
|
/// locally generated DH X25519 Nebula key to be signed by the CA, and an Ed25519 key for future API
|
||||||
|
/// authentication. On success it returns the Nebula config generated by the server, a Nebula private key PEM,
|
||||||
|
/// credentials to be used for future DN API requests, and an object containing organization information.
|
||||||
|
/// # Errors
|
||||||
|
/// This function will return an error in any of the following situations:
|
||||||
|
/// - the server_url is invalid
|
||||||
|
/// - the HTTP request fails
|
||||||
|
/// - the HTTP response is missing X-Request-ID
|
||||||
|
/// - X-Request-ID isn't valid UTF-8
|
||||||
|
/// - the server returns an error
|
||||||
|
/// - the server returns invalid JSON
|
||||||
|
/// - the `trusted_keys` field is invalid
|
||||||
|
pub fn enroll(&self, code: &str) -> Result<(NebulaConfig, DHPrivateKeyPEM, Credentials, EnrollMeta), Box<dyn Error>> {
|
||||||
|
debug!("making enrollment request to API {{server: {}, code: {}}}", self.server_url, code);
|
||||||
|
|
||||||
|
let (dh_pubkey_pem, dh_privkey_pem, ed_pubkey, ed_privkey) = new_keys();
|
||||||
|
|
||||||
|
let req_json = serde_json::to_string(&EnrollRequest {
|
||||||
|
code: code.to_string(),
|
||||||
|
dh_pubkey: dh_pubkey_pem,
|
||||||
|
ed_pubkey: serialize_ed25519_public(ed_pubkey.as_bytes()),
|
||||||
|
timestamp: Local::now().format("%Y-%m-%dT%H:%M:%S.%f%:z").to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let resp = self.http_client.post(self.server_url.join(ENROLL_ENDPOINT)?).body(req_json).send()?;
|
||||||
|
|
||||||
|
let req_id = resp.headers().get("X-Request-ID").ok_or("Response missing X-Request-ID")?.to_str()?;
|
||||||
|
debug!("enrollment request complete {{req_id: {}}}", req_id);
|
||||||
|
|
||||||
|
let resp: EnrollResponse = resp.json()?;
|
||||||
|
|
||||||
|
let r = match resp {
|
||||||
|
EnrollResponse::Success { data } => data,
|
||||||
|
EnrollResponse::Error { errors } => {
|
||||||
|
error!("unexpected error during enrollment: {}", errors[0].message);
|
||||||
|
return Err(errors[0].message.clone().into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let meta = EnrollMeta {
|
||||||
|
organization_id: r.organization.id,
|
||||||
|
organization_name: r.organization.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
let trusted_keys = ed25519_public_keys_from_pem(&r.trusted_keys)?;
|
||||||
|
|
||||||
|
let creds = Credentials {
|
||||||
|
host_id: r.host_id,
|
||||||
|
ed_privkey,
|
||||||
|
counter: r.counter,
|
||||||
|
trusted_keys,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((r.config, dh_privkey_pem, creds, meta))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -15,7 +15,18 @@
|
||||||
#![allow(clippy::too_many_lines)]
|
#![allow(clippy::too_many_lines)]
|
||||||
#![allow(clippy::module_name_repetitions)]
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
|
||||||
|
#[cfg(all(feature = "blocking", feature = "async"))]
|
||||||
|
compile_error!("Cannot compile with both blocking and async features at the same time. Please pick one or the other.");
|
||||||
|
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
|
||||||
|
#[cfg(feature = "blocking")]
|
||||||
|
#[path = "client_blocking.rs"]
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
|
||||||
|
#[cfg(feature = "async")]
|
||||||
|
#[path = "client_async.rs"]
|
||||||
|
pub mod client;
|
||||||
|
|
||||||
pub mod credentials;
|
pub mod credentials;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
|
@ -4,7 +4,7 @@ use base64_serde::base64_serde_type;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
/// The version 1 `DNClient` API endpoint
|
/// The version 1 `DNClient` API endpoint
|
||||||
const ENDPOINT_V1: &str = "/v1/dnclient";
|
pub const ENDPOINT_V1: &str = "/v1/dnclient";
|
||||||
|
|
||||||
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
|
base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ pub struct DoUpdateResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The REST enrollment endpoint
|
/// The REST enrollment endpoint
|
||||||
const ENROLL_ENDPOINT: &str = "/v2/enroll";
|
pub const ENROLL_ENDPOINT: &str = "/v2/enroll";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
/// `EnrollRequest` is issued to the `ENROLL_ENDPOINT` to enroll this `dnclient` with a dnapi organization
|
/// `EnrollRequest` is issued to the `ENROLL_ENDPOINT` to enroll this `dnclient` with a dnapi organization
|
||||||
|
|
Loading…
Reference in New Issue