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}