dnapi_rs/
message.rs

1//! Models for interacting with the Defined Networking API.
2
3use base64_serde::base64_serde_type;
4use serde::{Deserialize, Serialize};
5
6/// The version 1 `DNClient` API endpoint
7pub const ENDPOINT_V1: &str = "/v1/dnclient";
8
9/// The `CheckForUpdate` message type
10pub const CHECK_FOR_UPDATE: &str = "CheckForUpdate";
11/// The `DoUpdate` message type
12pub const DO_UPDATE: &str = "DoUpdate";
13
14base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
15
16#[derive(Serialize, Deserialize, Debug)]
17/// `RequestV1` is the version 1 `DNClient` request message.
18pub struct RequestV1 {
19    /// Version is always 1
20    pub version: i32,
21    #[serde(rename = "hostID")]
22    /// The Host ID of this dnclient instance
23    pub host_id: String,
24    /// The counter last returned by the server
25    pub counter: u32,
26    /// A base64-encoded message. This must be previously base64-encoded, as the signature is signed over the base64-encoded data.
27    pub message: String,
28    #[serde(with = "Base64Standard")]
29    /// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key
30    pub signature: Vec<u8>,
31}
32
33#[derive(Serialize, Deserialize, Debug)]
34/// `RequestWrapper` wraps a `DNClient` request message. It consists of a
35/// type and value, with the type string indicating how to interpret the value blob.
36pub struct RequestWrapper {
37    #[serde(rename = "type")]
38    /// The type of the message. Used to determine how `value` is encoded
39    pub message_type: String,
40    /// A base64-encoded arbitrary message, the type of which is stated in `message_type`
41    #[serde(with = "b64_as")]
42    pub value: Vec<u8>,
43    /// The timestamp of when this message was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or:
44    /// <4-digit year>-<two-digit-month>-<two-digit-day>T<two-digit-hour, 24-hour>:<two-digit-minute>:<two-digit-second>.<nanoseconds, zero-padded><offset with semicolon>
45    /// For example:
46    /// `2023-03-29T09:56:42.380006369-04:00`
47    /// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4`
48    pub timestamp: String,
49}
50
51#[derive(Serialize, Deserialize, Debug)]
52/// `SignedResponseWrapper` contains a response message and a signature to validate inside `data`.
53pub struct SignedResponseWrapper {
54    /// The response data contained in this message
55    pub data: SignedResponse,
56}
57
58#[derive(Serialize, Deserialize, Debug)]
59/// `SignedResponse` contains a response message and a signature to validate.
60pub struct SignedResponse {
61    /// The API version - always 1
62    pub version: i32,
63    #[serde(with = "Base64Standard")]
64    /// The Base64-encoded message signed inside this message
65    pub message: Vec<u8>,
66    #[serde(with = "Base64Standard")]
67    /// The ed25519 signature over the `message`
68    pub signature: Vec<u8>,
69}
70
71#[derive(Serialize, Deserialize, Debug)]
72/// `CheckForUpdateResponseWrapper` contains a response to `CheckForUpdate` inside "data."
73pub struct CheckForUpdateResponseWrapper {
74    /// The response data contained in this message
75    pub data: CheckForUpdateResponse,
76}
77
78#[derive(Serialize, Deserialize, Debug)]
79/// `CheckForUpdateResponse` is the response generated for a `CheckForUpdate` request.
80pub struct CheckForUpdateResponse {
81    #[serde(rename = "updateAvailable")]
82    /// Set to true if a config update is available
83    pub update_available: bool,
84}
85
86#[derive(Serialize, Deserialize, Debug)]
87/// `DoUpdateRequest` is the request sent for a `DoUpdate` request.
88pub struct DoUpdateRequest {
89    #[serde(rename = "edPubkeyPEM")]
90    #[serde(with = "Base64Standard")]
91    /// The new ed25519 public key that should be used for future API requests
92    pub ed_pubkey_pem: Vec<u8>,
93    #[serde(rename = "dhPubkeyPEM")]
94    #[serde(with = "Base64Standard")]
95    /// The new ECDH public key that the Nebula certificate should be signed for
96    pub dh_pubkey_pem: Vec<u8>,
97    #[serde(with = "Base64Standard")]
98    /// A randomized value used to uniquely identify this request.
99    /// The original client uses a randomized, 16-byte value here, which dnapi-rs replicates
100    pub nonce: Vec<u8>,
101}
102
103#[derive(Serialize, Deserialize, Debug)]
104/// A server response to a `DoUpdateRequest`, with the updated config and key information
105pub struct DoUpdateResponse {
106    #[serde(with = "Base64Standard")]
107    /// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran
108    pub config: Vec<u8>,
109    /// 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
110    pub counter: u32,
111    #[serde(with = "Base64Standard")]
112    /// The same base64-encoded nonce that was sent in the `DoUpdateRequest`.
113    pub nonce: Vec<u8>,
114    #[serde(rename = "trustedKeys")]
115    #[serde(with = "Base64Standard")]
116    /// A new set of trusted ed25519 keys that can be used by the server to sign messages.
117    pub trusted_keys: Vec<u8>,
118}
119
120/// The REST enrollment endpoint
121pub const ENROLL_ENDPOINT: &str = "/v2/enroll";
122
123#[derive(Serialize, Deserialize, Debug)]
124/// `EnrollRequest` is issued to the `ENROLL_ENDPOINT` to enroll this `dnclient` with a dnapi organization
125pub struct EnrollRequest {
126    /// The enrollment code given by the API server.
127    pub code: String,
128    #[serde(rename = "dhPubkey")]
129    #[serde(with = "Base64Standard")]
130    /// The ECDH public-key that should be used to sign the Nebula certificate given to this node.
131    pub dh_pubkey: Vec<u8>,
132    #[serde(rename = "edPubkey")]
133    #[serde(with = "Base64Standard")]
134    /// The Ed25519 public-key that this node will use to sign messages sent to the API.
135    pub ed_pubkey: Vec<u8>,
136    /// The timestamp of when this request was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or:
137    /// <4-digit year>-<two-digit-month>-<two-digit-day>T<two-digit-hour, 24-hour>:<two-digit-minute>:<two-digit-second>.<nanoseconds, zero-padded><offset with semicolon>
138    /// For example:
139    /// `2023-03-29T09:56:42.380006369-04:00`
140    /// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4`
141    pub timestamp: String,
142}
143
144#[derive(Serialize, Deserialize, Debug)]
145#[serde(untagged)]
146/// The response to an `EnrollRequest`
147pub enum EnrollResponse {
148    /// A successful enrollment, with a `data` field pointing to an `EnrollResponseData`
149    Success {
150        /// The response data from this response
151        data: EnrollResponseData,
152    },
153    /// An unsuccessful enrollment, with an `errors` field pointing to an array of `APIError`s.
154    Error {
155        /// A list of `APIError`s that happened while trying to enroll. `APIErrors` is a type alias to `Vec<APIError>`
156        errors: APIErrors,
157    },
158}
159
160#[derive(Serialize, Deserialize, Debug)]
161/// The data included in an successful enrollment.
162pub struct EnrollResponseData {
163    #[serde(with = "Base64Standard")]
164    /// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran
165    pub config: Vec<u8>,
166    #[serde(rename = "hostID")]
167    /// The server-side Host ID that this node now has.
168    pub host_id: String,
169    /// 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
170    pub counter: u32,
171    #[serde(rename = "trustedKeys")]
172    #[serde(with = "Base64Standard")]
173    /// A new set of trusted ed25519 keys that can be used by the server to sign messages.
174    pub trusted_keys: Vec<u8>,
175    /// The organization data that this node is now a part of
176    pub organization: EnrollResponseDataOrg,
177}
178
179#[derive(Serialize, Deserialize, Debug)]
180/// The organization data that this node is now a part of
181pub struct EnrollResponseDataOrg {
182    /// The organization ID that this node is now a part of
183    pub id: String,
184    /// The name of the organization that this node is now a part of
185    pub name: String,
186}
187
188#[derive(Serialize, Deserialize, Debug)]
189/// `APIError` represents a single error returned in an API error response.
190pub struct APIError {
191    /// The error code
192    pub code: String,
193    /// The human-readable error message
194    pub message: String,
195    /// An optional path to where the error occured
196    pub path: Option<String>,
197}
198
199/// A type alias to a array of `APIErrors`. Just for parity with dnapi.
200pub type APIErrors = Vec<APIError>;
201
202mod b64_as {
203    use base64::Engine;
204    use serde::{Deserialize, Serialize};
205    use serde::{Deserializer, Serializer};
206
207    pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
208        let base64 = base64::engine::general_purpose::STANDARD.encode(v);
209        <String>::serialize(&base64, s)
210    }
211
212    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
213        let base64 = <Option<String>>::deserialize(d)?;
214        base64.map_or_else(
215            || Ok(vec![]),
216            |v| {
217                base64::engine::general_purpose::STANDARD
218                    .decode(v.as_bytes())
219                    .map_err(serde::de::Error::custom)
220            },
221        )
222    }
223}