//! Models for interacting with the Defined Networking API. use base64_serde::base64_serde_type; use serde::{Deserialize, Serialize}; /// The version 1 `DNClient` API endpoint pub const ENDPOINT_V1: &str = "/v1/dnclient"; /// The `CheckForUpdate` message type pub const CHECK_FOR_UPDATE: &str = "CheckForUpdate"; /// The `DoUpdate` message type pub const DO_UPDATE: &str = "DoUpdate"; base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); #[derive(Serialize, Deserialize)] /// `RequestV1` is the version 1 `DNClient` request message. pub struct RequestV1 { /// Version is always 1 pub version: i32, #[serde(rename = "hostID")] /// The Host ID of this dnclient instance pub host_id: String, /// The counter last returned by the server pub counter: u32, /// A base64-encoded message. This must be previously base64-encoded, as the signature is signed over the base64-encoded data. pub message: String, #[serde(with = "Base64Standard")] /// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key pub signature: Vec, } #[derive(Serialize, Deserialize)] /// `RequestWrapper` wraps a `DNClient` request message. It consists of a /// type and value, with the type string indicating how to interpret the value blob. pub struct RequestWrapper { #[serde(rename = "type")] /// The type of the message. Used to determine how `value` is encoded pub message_type: String, /// A base64-encoded arbitrary message, the type of which is stated in `message_type` #[serde(with = "b64_as")] pub value: Vec, /// The timestamp of when this message was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or: /// <4-digit year>--T::. /// For example: /// `2023-03-29T09:56:42.380006369-04:00` /// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4` pub timestamp: String, } #[derive(Serialize, Deserialize)] /// `SignedResponseWrapper` contains a response message and a signature to validate inside `data`. pub struct SignedResponseWrapper { /// The response data contained in this message pub data: SignedResponse, } #[derive(Serialize, Deserialize)] /// `SignedResponse` contains a response message and a signature to validate. pub struct SignedResponse { /// The API version - always 1 pub version: i32, #[serde(with = "Base64Standard")] /// The Base64-encoded message signed inside this message pub message: Vec, #[serde(with = "Base64Standard")] /// The ed25519 signature over the `message` pub signature: Vec, } #[derive(Serialize, Deserialize)] /// `CheckForUpdateResponseWrapper` contains a response to `CheckForUpdate` inside "data." pub struct CheckForUpdateResponseWrapper { /// The response data contained in this message pub data: CheckForUpdateResponse, } #[derive(Serialize, Deserialize)] /// `CheckForUpdateResponse` is the response generated for a `CheckForUpdate` request. pub struct CheckForUpdateResponse { #[serde(rename = "updateAvailable")] /// Set to true if a config update is available pub update_available: bool, } #[derive(Serialize, Deserialize)] /// `DoUpdateRequest` is the request sent for a `DoUpdate` request. pub struct DoUpdateRequest { #[serde(rename = "edPubkeyPEM")] #[serde(with = "Base64Standard")] /// The new ed25519 public key that should be used for future API requests pub ed_pubkey_pem: Vec, #[serde(rename = "dhPubkeyPEM")] #[serde(with = "Base64Standard")] /// The new ECDH public key that the Nebula certificate should be signed for pub dh_pubkey_pem: Vec, #[serde(with = "Base64Standard")] /// A randomized value used to uniquely identify this request. /// The original client uses a randomized, 16-byte value here, which dnapi-rs replicates pub nonce: Vec, } #[derive(Serialize, Deserialize)] /// A server response to a `DoUpdateRequest`, with the updated config and key information pub struct DoUpdateResponse { #[serde(with = "Base64Standard")] /// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran pub config: Vec, /// The new config counter. It is unknown what the purpose of this is, but the original client keeps track of it and it is used later in the api pub counter: u32, #[serde(with = "Base64Standard")] /// The same base64-encoded nonce that was sent in the `DoUpdateRequest`. pub nonce: Vec, #[serde(rename = "trustedKeys")] #[serde(with = "Base64Standard")] /// A new set of trusted ed25519 keys that can be used by the server to sign messages. pub trusted_keys: Vec, } /// The REST enrollment endpoint pub const ENROLL_ENDPOINT: &str = "/v2/enroll"; #[derive(Serialize, Deserialize, Debug)] /// `EnrollRequest` is issued to the `ENROLL_ENDPOINT` to enroll this `dnclient` with a dnapi organization pub struct EnrollRequest { /// The enrollment code given by the API server. pub code: String, #[serde(rename = "dhPubkey")] #[serde(with = "Base64Standard")] /// The ECDH public-key that should be used to sign the Nebula certificate given to this node. pub dh_pubkey: Vec, #[serde(rename = "edPubkey")] #[serde(with = "Base64Standard")] /// The Ed25519 public-key that this node will use to sign messages sent to the API. pub ed_pubkey: Vec, /// The timestamp of when this request was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or: /// <4-digit year>--T::. /// For example: /// `2023-03-29T09:56:42.380006369-04:00` /// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4` pub timestamp: String, } #[derive(Serialize, Deserialize)] #[serde(untagged)] /// The response to an `EnrollRequest` pub enum EnrollResponse { /// A successful enrollment, with a `data` field pointing to an `EnrollResponseData` Success { /// The response data from this response data: EnrollResponseData, }, /// An unsuccessful enrollment, with an `errors` field pointing to an array of `APIError`s. Error { /// A list of `APIError`s that happened while trying to enroll. `APIErrors` is a type alias to `Vec` errors: APIErrors, }, } #[derive(Serialize, Deserialize)] /// The data included in an successful enrollment. pub struct EnrollResponseData { #[serde(with = "Base64Standard")] /// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran pub config: Vec, #[serde(rename = "hostID")] /// The server-side Host ID that this node now has. pub host_id: String, /// The new config counter. It is unknown what the purpose of this is, but the original client keeps track of it and it is used later in the api pub counter: u32, #[serde(rename = "trustedKeys")] #[serde(with = "Base64Standard")] /// A new set of trusted ed25519 keys that can be used by the server to sign messages. pub trusted_keys: Vec, /// The organization data that this node is now a part of pub organization: EnrollResponseDataOrg, } #[derive(Serialize, Deserialize)] /// The organization data that this node is now a part of pub struct EnrollResponseDataOrg { /// The organization ID that this node is now a part of pub id: String, /// The name of the organization that this node is now a part of pub name: String, } #[derive(Serialize, Deserialize)] /// `APIError` represents a single error returned in an API error response. pub struct APIError { /// The error code pub code: String, /// The human-readable error message pub message: String, /// An optional path to where the error occured pub path: Option, } /// A type alias to a array of `APIErrors`. Just for parity with dnapi. pub type APIErrors = Vec; mod b64_as { use base64::Engine; use serde::{Deserialize, Serialize}; use serde::{Deserializer, Serializer}; pub fn serialize(v: &Vec, s: S) -> Result { let base64 = base64::engine::general_purpose::STANDARD.encode(v); ::serialize(&base64, s) } pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { let base64 = >::deserialize(d)?; base64.map_or_else( || Ok(vec![]), |v| { base64::engine::general_purpose::STANDARD .decode(v.as_bytes()) .map_err(serde::de::Error::custom) }, ) } }