dnclient endpoint speedrun: 14 minutes

This commit is contained in:
core 2023-12-27 00:03:40 -05:00
parent d8888e8f6e
commit a5fb79288b
Signed by: core
GPG Key ID: FDBF740DADDCEECF
13 changed files with 456 additions and 126 deletions

1
Cargo.lock generated
View File

@ -3087,6 +3087,7 @@ version = "0.3.0-alpha1"
dependencies = [ dependencies = [
"actix-cors", "actix-cors",
"actix-web", "actix-web",
"base64 0.21.5",
"bb8", "bb8",
"chacha20poly1305", "chacha20poly1305",
"chrono", "chrono",

View File

@ -35,3 +35,4 @@ dnapi-rs = { version = "0.2", path = "../dnapi-rs" }
nebula-config = { version = "0.1", path = "../nebula-config" } nebula-config = { version = "0.1", path = "../nebula-config" }
ipnet = "2.9" ipnet = "2.9"
serde_yaml = "0.9" serde_yaml = "0.9"
base64 = "0.21"

View File

@ -17,5 +17,7 @@ CREATE TABLE hosts (
platform VARCHAR NULL, platform VARCHAR NULL,
update_available BOOLEAN NULL, update_available BOOLEAN NULL,
tags text[] NOT NULL tags text[] NOT NULL,
counter INT NULL
); );

View File

@ -44,5 +44,5 @@ pub struct ConfigTokens {
pub session_token_expiry_seconds: u64, pub session_token_expiry_seconds: u64,
pub auth_token_expiry_seconds: u64, pub auth_token_expiry_seconds: u64,
pub data_encryption_key: String, pub data_encryption_key: String,
pub cert_expiry_time_seconds: u64 pub cert_expiry_time_seconds: u64,
} }

View File

@ -3,25 +3,33 @@
// and the entirety of trifid-pki, as it deals with CA private keys. // and the entirety of trifid-pki, as it deals with CA private keys.
// Review carefully what you write here! // Review carefully what you write here!
use std::collections::{Bound, HashMap}; use crate::crypt::sign_cert_with_ca;
use std::error::Error; use crate::models::{Host, HostKey, HostOverride, Network, Role, RoleFirewallRule, SigningCA};
use std::net::{Ipv4Addr, SocketAddrV4}; use crate::schema::{
use std::str::FromStr; host_keys, host_overrides, hosts, networks, role_firewall_rules, roles, signing_cas,
use std::time::{Duration, SystemTime}; };
use crate::AppState;
use actix_web::web::Data; use actix_web::web::Data;
use diesel::{ExpressionMethods, QueryDsl, SelectableHelper}; use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
use diesel_async::pooled_connection::bb8::RunError; use diesel_async::pooled_connection::bb8::RunError;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use ipnet::Ipv4Net; use ipnet::Ipv4Net;
use nebula_config::{
NebulaConfig, NebulaConfigCipher, NebulaConfigFirewall, NebulaConfigFirewallRule,
NebulaConfigLighthouse, NebulaConfigListen, NebulaConfigPki, NebulaConfigPunchy,
NebulaConfigRelay, NebulaConfigTun,
};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
use std::collections::{Bound, HashMap};
use std::error::Error;
use std::net::{Ipv4Addr, SocketAddrV4};
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use thiserror::Error; use thiserror::Error;
use nebula_config::{NebulaConfig, NebulaConfigCipher, NebulaConfigFirewall, NebulaConfigFirewallRule, NebulaConfigLighthouse, NebulaConfigListen, NebulaConfigPki, NebulaConfigPunchy, NebulaConfigRelay, NebulaConfigTun}; use trifid_pki::cert::{
use trifid_pki::cert::{deserialize_nebula_certificate_from_pem, NebulaCertificate, NebulaCertificateDetails}; deserialize_nebula_certificate_from_pem, NebulaCertificate, NebulaCertificateDetails,
};
use trifid_pki::x25519_dalek::PublicKey; use trifid_pki::x25519_dalek::PublicKey;
use crate::AppState;
use crate::crypt::sign_cert_with_ca;
use crate::models::{Host, Network, SigningCA, HostKey, Role, RoleFirewallRule, HostOverride};
use crate::schema::{host_keys, hosts, networks, role_firewall_rules, roles, signing_cas, host_overrides};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ConfigGenError { pub enum ConfigGenError {
@ -32,44 +40,68 @@ pub enum ConfigGenError {
#[error("error parsing a signing CA: {0}")] #[error("error parsing a signing CA: {0}")]
InvalidCACert(serde_json::Error), InvalidCACert(serde_json::Error),
#[error("an error occured: {0}")] #[error("an error occured: {0}")]
GenericError(Box<dyn Error>) GenericError(Box<dyn Error>),
} }
pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppState>) -> Result<NebulaConfig, ConfigGenError> { pub async fn generate_config(
let mut conn = state.pool.get().await.map_err(ConfigGenError::AcquireError)?; host: &Host,
dh_pubkey: PublicKey,
state: Data<AppState>,
) -> Result<NebulaConfig, ConfigGenError> {
let mut conn = state
.pool
.get()
.await
.map_err(ConfigGenError::AcquireError)?;
let cas = signing_cas::dsl::signing_cas.filter(signing_cas::organization_id.eq(&host.organization_id)).select(SigningCA::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?; let cas = signing_cas::dsl::signing_cas
.filter(signing_cas::organization_id.eq(&host.organization_id))
.select(SigningCA::as_select())
.load(&mut conn)
.await
.map_err(ConfigGenError::DbError)?;
let mut good_cas = vec![]; let mut good_cas = vec![];
let mut ca_string = String::new(); let mut ca_string = String::new();
for ca in cas { for ca in cas {
if ca.expires_at < SystemTime::now() { if ca.expires_at < SystemTime::now() {
let ca_cert: NebulaCertificate = serde_json::from_value(ca.cert.clone()).map_err(ConfigGenError::InvalidCACert)?; let ca_cert: NebulaCertificate =
ca_string += &String::from_utf8_lossy(&ca_cert.serialize_to_pem().map_err(ConfigGenError::GenericError)?); serde_json::from_value(ca.cert.clone()).map_err(ConfigGenError::InvalidCACert)?;
ca_string += &String::from_utf8_lossy(
&ca_cert
.serialize_to_pem()
.map_err(ConfigGenError::GenericError)?,
);
good_cas.push((ca, ca_cert)); good_cas.push((ca, ca_cert));
} }
} }
let (signing_ca, ca_cert) = &good_cas[0]; let (signing_ca, ca_cert) = &good_cas[0];
let network = networks::dsl::networks.find(&host.network_id).first::<Network>(&mut conn).await.map_err(ConfigGenError::DbError)?; let network = networks::dsl::networks
.find(&host.network_id)
.first::<Network>(&mut conn)
.await
.map_err(ConfigGenError::DbError)?;
let mut cert = NebulaCertificate { let mut cert = NebulaCertificate {
details: NebulaCertificateDetails { details: NebulaCertificateDetails {
name: host.name.clone(), name: host.name.clone(),
ips: vec![ ips: vec![Ipv4Net::new(
Ipv4Net::new(
Ipv4Addr::from_str(&host.ip_address).unwrap(), Ipv4Addr::from_str(&host.ip_address).unwrap(),
Ipv4Net::from_str(&network.cidr).unwrap().prefix_len() Ipv4Net::from_str(&network.cidr).unwrap().prefix_len(),
).unwrap() )
], .unwrap()],
subnets: vec![], subnets: vec![],
groups: if let Some(role_id) = &host.role_id { groups: if let Some(role_id) = &host.role_id {
vec![format!("role:{}", role_id)] vec![format!("role:{}", role_id)]
} else { vec![] }, } else {
vec![]
},
not_before: SystemTime::now() - Duration::from_secs(3600), not_before: SystemTime::now() - Duration::from_secs(3600),
not_after: SystemTime::now() + Duration::from_secs(state.config.tokens.cert_expiry_time_seconds), not_after: SystemTime::now()
+ Duration::from_secs(state.config.tokens.cert_expiry_time_seconds),
public_key: *dh_pubkey.as_bytes(), public_key: *dh_pubkey.as_bytes(),
is_ca: false, is_ca: false,
issuer: ca_cert.sha256sum().unwrap(), issuer: ca_cert.sha256sum().unwrap(),
@ -79,12 +111,23 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
sign_cert_with_ca(signing_ca, &mut cert, &state.config).unwrap(); sign_cert_with_ca(signing_ca, &mut cert, &state.config).unwrap();
let all_blocked_hosts = hosts::dsl::hosts.filter(hosts::network_id.eq(&host.network_id)).filter(hosts::is_blocked.eq(true)).select(Host::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?; let all_blocked_hosts = hosts::dsl::hosts
.filter(hosts::network_id.eq(&host.network_id))
.filter(hosts::is_blocked.eq(true))
.select(Host::as_select())
.load(&mut conn)
.await
.map_err(ConfigGenError::DbError)?;
let mut all_blocked_fingerprints = vec![]; let mut all_blocked_fingerprints = vec![];
for blocked_host in all_blocked_hosts { for blocked_host in all_blocked_hosts {
let hosts_blocked_key_entries = host_keys::dsl::host_keys.filter(host_keys::host_id.eq(&blocked_host.id)).select(HostKey::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?; let hosts_blocked_key_entries = host_keys::dsl::host_keys
.filter(host_keys::host_id.eq(&blocked_host.id))
.select(HostKey::as_select())
.load(&mut conn)
.await
.map_err(ConfigGenError::DbError)?;
for blocked_key in hosts_blocked_key_entries { for blocked_key in hosts_blocked_key_entries {
let cert = deserialize_nebula_certificate_from_pem(&blocked_key.client_cert).unwrap(); let cert = deserialize_nebula_certificate_from_pem(&blocked_key.client_cert).unwrap();
let fingerprint = cert.sha256sum().unwrap(); let fingerprint = cert.sha256sum().unwrap();
@ -102,15 +145,41 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
let mut static_host_map = HashMap::new(); let mut static_host_map = HashMap::new();
let all_lighthouses = hosts::dsl::hosts.filter(hosts::network_id.eq(&host.network_id)).filter(hosts::is_lighthouse.eq(true)).select(Host::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?; let all_lighthouses = hosts::dsl::hosts
let all_relays = hosts::dsl::hosts.filter(hosts::network_id.eq(&host.network_id)).filter(hosts::is_relay.eq(true)).select(Host::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?; .filter(hosts::network_id.eq(&host.network_id))
.filter(hosts::is_lighthouse.eq(true))
.select(Host::as_select())
.load(&mut conn)
.await
.map_err(ConfigGenError::DbError)?;
let all_relays = hosts::dsl::hosts
.filter(hosts::network_id.eq(&host.network_id))
.filter(hosts::is_relay.eq(true))
.select(Host::as_select())
.load(&mut conn)
.await
.map_err(ConfigGenError::DbError)?;
for lighthouse in &all_lighthouses { for lighthouse in &all_lighthouses {
static_host_map.insert(Ipv4Addr::from_str(&lighthouse.ip_address).unwrap(), lighthouse.static_addresses.iter().map(|u| SocketAddrV4::from_str(&u.clone().unwrap()).unwrap()).collect::<Vec<_>>()); static_host_map.insert(
Ipv4Addr::from_str(&lighthouse.ip_address).unwrap(),
lighthouse
.static_addresses
.iter()
.map(|u| SocketAddrV4::from_str(&u.clone().unwrap()).unwrap())
.collect::<Vec<_>>(),
);
} }
for relay in &all_relays { for relay in &all_relays {
static_host_map.insert(Ipv4Addr::from_str(&relay.ip_address).unwrap(), relay.static_addresses.iter().map(|u| SocketAddrV4::from_str(&u.clone().unwrap()).unwrap()).collect::<Vec<_>>()); static_host_map.insert(
Ipv4Addr::from_str(&relay.ip_address).unwrap(),
relay
.static_addresses
.iter()
.map(|u| SocketAddrV4::from_str(&u.clone().unwrap()).unwrap())
.collect::<Vec<_>>(),
);
} }
let lighthouse = Some(NebulaConfigLighthouse { let lighthouse = Some(NebulaConfigLighthouse {
@ -121,7 +190,10 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
hosts: if host.is_lighthouse { hosts: if host.is_lighthouse {
vec![] vec![]
} else { } else {
all_lighthouses.iter().map(|u| Ipv4Addr::from_str(&u.ip_address).unwrap()).collect::<Vec<_>>() all_lighthouses
.iter()
.map(|u| Ipv4Addr::from_str(&u.ip_address).unwrap())
.collect::<Vec<_>>()
}, },
remote_allow_list: Default::default(), remote_allow_list: Default::default(),
local_allow_list: Default::default(), local_allow_list: Default::default(),
@ -132,31 +204,43 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
relays: if host.is_relay || (host.is_lighthouse && network.lighthouses_as_relays) { relays: if host.is_relay || (host.is_lighthouse && network.lighthouses_as_relays) {
vec![] vec![]
} else if network.lighthouses_as_relays { } else if network.lighthouses_as_relays {
all_lighthouses.iter().map(|u| Ipv4Addr::from_str(&u.ip_address).unwrap()).collect::<Vec<_>>() all_lighthouses
.iter()
.map(|u| Ipv4Addr::from_str(&u.ip_address).unwrap())
.collect::<Vec<_>>()
} else { } else {
all_relays.iter().map(|u| Ipv4Addr::from_str(&u.ip_address).unwrap()).collect::<Vec<_>>() all_relays
.iter()
.map(|u| Ipv4Addr::from_str(&u.ip_address).unwrap())
.collect::<Vec<_>>()
}, },
use_relays: true use_relays: true,
}); });
let mut inbound_firewall = None; let mut inbound_firewall = None;
if let Some(role_id) = &host.role_id { if let Some(role_id) = &host.role_id {
let role = roles::dsl::roles.find(role_id).first::<Role>(&mut conn).await.map_err(ConfigGenError::DbError)?; let firewall_rules = role_firewall_rules::dsl::role_firewall_rules
.filter(role_firewall_rules::role_id.eq(role_id))
.select(RoleFirewallRule::as_select())
.load(&mut conn)
.await
.map_err(ConfigGenError::DbError)?;
let firewall_rules = role_firewall_rules::dsl::role_firewall_rules.filter(role_firewall_rules::role_id.eq(role_id)).select(RoleFirewallRule::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?; inbound_firewall = Some(
firewall_rules
inbound_firewall = Some(firewall_rules.iter().map(|u| NebulaConfigFirewallRule { .iter()
.map(|u| NebulaConfigFirewallRule {
port: if let Some((from, to)) = u.port_range { port: if let Some((from, to)) = u.port_range {
let start_port = match from { let start_port = match from {
Bound::Included(u) => u, Bound::Included(u) => u,
Bound::Excluded(u) => u + 1, Bound::Excluded(u) => u + 1,
Bound::Unbounded => 0 Bound::Unbounded => 0,
}; };
let end_port = match from { let end_port = match to {
Bound::Included(u) => u, Bound::Included(u) => u,
Bound::Excluded(u) => u - 1, Bound::Excluded(u) => u - 1,
Bound::Unbounded => 65535 Bound::Unbounded => 65535,
}; };
Some(format!("{}-{}", start_port, end_port)) Some(format!("{}-{}", start_port, end_port))
} else { } else {
@ -177,7 +261,9 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
None None
}, },
cidr: None, cidr: None,
}).collect::<Vec<_>>()) })
.collect::<Vec<_>>(),
)
} }
let config = NebulaConfig { let config = NebulaConfig {
@ -214,8 +300,7 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
firewall: Some(NebulaConfigFirewall { firewall: Some(NebulaConfigFirewall {
conntrack: None, conntrack: None,
inbound: inbound_firewall, inbound: inbound_firewall,
outbound: Some(vec![ outbound: Some(vec![NebulaConfigFirewallRule {
NebulaConfigFirewallRule {
port: Some("any".to_string()), port: Some("any".to_string()),
proto: Some("any".to_string()), proto: Some("any".to_string()),
ca_name: None, ca_name: None,
@ -224,8 +309,7 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
group: None, group: None,
groups: None, groups: None,
cidr: None, cidr: None,
} }]),
]),
}), }),
routines: 1, routines: 1,
stats: None, stats: None,
@ -234,7 +318,12 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
let mut yaml_value = serde_yaml::to_value(&config).unwrap(); let mut yaml_value = serde_yaml::to_value(&config).unwrap();
let all_overrides = host_overrides::dsl::host_overrides.filter(host_overrides::host_id.eq(&host.id)).select(HostOverride::as_select()).load(&mut conn).await.map_err(ConfigGenError::DbError)?; let all_overrides = host_overrides::dsl::host_overrides
.filter(host_overrides::host_id.eq(&host.id))
.select(HostOverride::as_select())
.load(&mut conn)
.await
.map_err(ConfigGenError::DbError)?;
// Cursed value overrides // Cursed value overrides
@ -255,7 +344,7 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
current_val.as_mapping_mut().unwrap().insert( current_val.as_mapping_mut().unwrap().insert(
Value::String(key_split[key_split.len() - 1].to_string()), Value::String(key_split[key_split.len() - 1].to_string()),
serde_yaml::from_str(h_override.value)?, serde_yaml::from_str(&h_override.value).unwrap(),
); );
} }

View File

@ -143,7 +143,7 @@ pub fn sign_cert_with_ca(
pub struct DnclientKeyLockbox { pub struct DnclientKeyLockbox {
pub info: Vec<u8>, pub info: Vec<u8>,
pub nonce: Vec<u8>, pub nonce: Vec<u8>,
pub key: Vec<u8> pub key: Vec<u8>,
} }
pub fn create_dnclient_ed_key( pub fn create_dnclient_ed_key(
@ -169,13 +169,16 @@ pub fn create_dnclient_ed_key(
aad: &aad, aad: &aad,
}, },
) )
.map_err(|e| CryptographyError::LockingError)?; .map_err(|_| CryptographyError::LockingError)?;
Ok((DnclientKeyLockbox { Ok((
DnclientKeyLockbox {
info: aad.to_vec(), info: aad.to_vec(),
nonce: salt.as_slice().to_vec(), nonce: salt.as_slice().to_vec(),
key: lockbox, key: lockbox,
}, key.verifying_key())) },
key.verifying_key(),
))
} }
pub fn sign_dnclient_with_lockbox( pub fn sign_dnclient_with_lockbox(

View File

@ -26,8 +26,8 @@ pub mod auth;
pub mod email; pub mod email;
#[macro_use] #[macro_use]
pub mod macros; pub mod macros;
pub mod crypt;
mod config_generator; mod config_generator;
pub mod crypt;
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {

View File

@ -290,6 +290,7 @@ pub struct Host {
pub platform: Option<String>, pub platform: Option<String>,
pub update_available: Option<bool>, pub update_available: Option<bool>,
pub tags: Vec<Option<String>>, pub tags: Vec<Option<String>>,
pub counter: Option<i32>,
} }
#[derive( #[derive(
@ -311,7 +312,7 @@ pub struct HostOverride {
pub id: String, pub id: String,
pub host_id: String, pub host_id: String,
pub key: String, pub key: String,
pub value: Value, pub value: String,
} }
#[derive( #[derive(
@ -340,7 +341,7 @@ pub struct HostKey {
pub salt: Vec<u8>, pub salt: Vec<u8>,
pub info: Vec<u8>, pub info: Vec<u8>,
pub server_ed_priv: Vec<u8> pub server_ed_priv: Vec<u8>,
} }
#[derive( #[derive(

View File

@ -0,0 +1,201 @@
use crate::config_generator::generate_config;
use crate::crypt::{create_dnclient_ed_key, sign_dnclient_with_lockbox};
use crate::models::{Host, HostKey};
use crate::response::JsonAPIResponse;
use crate::schema::{host_keys, hosts};
use crate::{randid, AppState};
use actix_web::http::StatusCode;
use actix_web::web::{Data, Json};
use base64::Engine;
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use dnapi_rs::message::{
CheckForUpdateResponse, CheckForUpdateResponseWrapper, DoUpdateRequest, DoUpdateResponse,
RequestV1, RequestWrapper, SignedResponseWrapper, SignedResponse
};
use log::warn;
use serde::Serialize;
use std::time::SystemTime;
use trifid_pki::cert::{
deserialize_ed25519_public, deserialize_x25519_public, serialize_ed25519_public,
};
use trifid_pki::ed25519_dalek::{Signature, Verifier, VerifyingKey};
use trifid_pki::x25519_dalek::PublicKey;
#[derive(Serialize, Debug)]
pub enum DnclientResponse {
CheckForUpdateResp(CheckForUpdateResponseWrapper),
DoUpdateResp(SignedResponseWrapper),
}
pub async fn dnclient_req(
req: Json<RequestV1>,
state: Data<AppState>,
) -> JsonAPIResponse<DnclientResponse> {
if req.version != 1 {
err!(
StatusCode::BAD_REQUEST,
make_err!(
"ERR_INVALID_DNCLIENT_VERSION",
"unsupported dnclient api version",
"version"
)
);
}
let mut conn = handle_error!(state.pool.get().await);
// get key for this host and counter
let maybe_key: Option<HostKey> = handle_error!(host_keys::dsl::host_keys
.filter(host_keys::host_id.eq(&req.host_id))
.filter(host_keys::counter.eq(req.counter as i32))
.first::<HostKey>(&mut conn)
.await
.optional());
let key = match maybe_key {
Some(k) => k,
None => {
err!(
StatusCode::UNAUTHORIZED,
make_err!(
"ERR_UNAUTHORIZED",
"no key for host/counter pair in keystore"
)
)
}
};
let signature = handle_error!(Signature::from_slice(&req.signature));
let key = handle_error!(VerifyingKey::from_bytes(
&key.client_ed_pub.try_into().unwrap()
));
if key.verify(req.message.as_bytes(), &signature).is_err() {
warn!("! invalid signature from {}", req.host_id);
err!(
StatusCode::UNAUTHORIZED,
make_err!("ERR_UNAUTHORIZED", "unauthorized")
)
}
// Sig OK
let msg_raw = handle_error!(base64::engine::general_purpose::STANDARD.decode(&req.message));
let req_w: RequestWrapper = handle_error!(serde_json::from_slice(&msg_raw));
let host = handle_error!(
hosts::table
.find(&req.host_id)
.first::<Host>(&mut conn)
.await
);
handle_error!(
diesel::update(&host)
.set(hosts::dsl::last_seen_at.eq(SystemTime::now()))
.execute(&mut conn)
.await
);
match req_w.message_type.as_str() {
"CheckForUpdate" => {
ok!(DnclientResponse::CheckForUpdateResp(
CheckForUpdateResponseWrapper {
data: CheckForUpdateResponse {
update_available: host.update_available.unwrap()
|| req.counter < host.counter.unwrap() as u32
}
}
))
}
"DoUpdate" => {
if !host.update_available.unwrap_or(false) {
err!(
StatusCode::BAD_REQUEST,
make_err!("ERR_NO_CONFIG_AVAIL", "no new configuration available")
);
}
let do_update_req: DoUpdateRequest =
handle_error!(serde_json::from_slice(&req_w.value));
let new_dh_pub_bytes =
handle_error!(deserialize_x25519_public(&do_update_req.dh_pubkey_pem));
let new_dh_pub_static: [u8; 32] = new_dh_pub_bytes.clone().try_into().unwrap();
let new_dh_pub = PublicKey::from(new_dh_pub_static);
let new_config = handle_error!(generate_config(&host, new_dh_pub, state.clone()).await);
let new_ed_key =
handle_error!(deserialize_ed25519_public(&do_update_req.ed_pubkey_pem));
let new_counter = host.counter.unwrap() + 1;
let (key_lockbox, trusted_key) = handle_error!(create_dnclient_ed_key(&state.config));
let new_key = HostKey {
id: randid!(id "hostkey"),
host_id: host.id.clone(),
counter: new_counter,
client_ed_pub: new_ed_key,
client_dh_pub: new_dh_pub_bytes,
client_cert: new_config.pki.cert.as_bytes().to_vec(),
salt: key_lockbox.nonce.clone(),
info: key_lockbox.info.clone(),
server_ed_priv: key_lockbox.key.clone(),
};
handle_error!(
diesel::insert_into(host_keys::table)
.values(&new_key)
.execute(&mut conn)
.await
);
handle_error!(
diesel::update(&host)
.set((
hosts::dsl::last_seen_at.eq(SystemTime::now()),
hosts::dsl::update_available.eq(Some(false)),
hosts::dsl::counter.eq(Some(new_counter))
))
.execute(&mut conn)
.await
);
let config_str = handle_error!(serde_yaml::to_string(&new_config));
let msg = DoUpdateResponse {
config: config_str.as_bytes().to_vec(),
counter: new_counter as u32,
nonce: do_update_req.nonce,
trusted_keys: serialize_ed25519_public(trusted_key.as_bytes()),
};
let msg_bytes = handle_error!(serde_json::to_vec(&msg));
let resp = SignedResponse {
version: 1,
message: msg_bytes.clone(),
signature: sign_dnclient_with_lockbox(&key_lockbox, &msg_bytes, &state.config)
.unwrap().to_vec(),
};
let resp_w = SignedResponseWrapper { data: resp };
ok!(DnclientResponse::DoUpdateResp(resp_w));
}
_ => {
err!(
StatusCode::BAD_REQUEST,
make_err!(
"ERR_INVALID_DNCLIENT_REQ_TYPE",
"unsupported dnclient request type",
"message_type"
)
);
}
}
}

View File

@ -1,4 +1,5 @@
pub mod auth; pub mod auth;
pub mod dnclient;
pub mod networks; pub mod networks;
pub mod signup; pub mod signup;
pub mod totp_authenticators; pub mod totp_authenticators;

View File

@ -1,5 +1,5 @@
use crate::crypt::create_signing_ca; use crate::crypt::create_signing_ca;
use crate::models::{Network, NetworkNormalized, Organization, SigningCA, User}; use crate::models::{Network, NetworkNormalized, Organization, User};
use crate::response::JsonAPIResponse; use crate::response::JsonAPIResponse;
use crate::schema::networks::dsl::networks; use crate::schema::networks::dsl::networks;
use crate::schema::organizations::dsl::organizations; use crate::schema::organizations::dsl::organizations;
@ -33,7 +33,7 @@ pub async fn create_network_req(
let mut conn = handle_error!(state.pool.get().await); let mut conn = handle_error!(state.pool.get().await);
let auth_info = auth!(req_info, conn); let auth_info = auth!(req_info, conn);
let (session_token, auth_token) = enforce!(sess auth auth_info); let (session_token, _) = enforce!(sess auth auth_info);
let user = handle_error!( let user = handle_error!(
users::table users::table

View File

@ -1,17 +1,19 @@
use std::time::SystemTime;
use crate::response::JsonAPIResponse;
use crate::{AppState, randid};
use actix_web::web::{Data, Json};
use actix_web::http::StatusCode;
use dnapi_rs::message::{EnrollRequest, EnrollResponse, EnrollResponseData};
use crate::models::{EnrollmentCode, Host, HostKey};
use crate::schema::{enrollment_codes, hosts};
use diesel::{QueryDsl, OptionalExtension, ExpressionMethods};
use diesel_async::RunQueryDsl;
use trifid_pki::x25519_dalek::PublicKey;
use crate::config_generator::generate_config; use crate::config_generator::generate_config;
use crate::crypt::create_dnclient_ed_key; use crate::crypt::create_dnclient_ed_key;
use crate::models::Organization;
use crate::models::{EnrollmentCode, Host, HostKey};
use crate::response::JsonAPIResponse;
use crate::schema::host_keys; use crate::schema::host_keys;
use crate::schema::{enrollment_codes, hosts, organizations};
use crate::{randid, AppState};
use actix_web::http::StatusCode;
use actix_web::web::{Data, Json};
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl};
use diesel_async::RunQueryDsl;
use dnapi_rs::message::{EnrollRequest, EnrollResponse, EnrollResponseData, EnrollResponseDataOrg};
use std::time::SystemTime;
use trifid_pki::cert::serialize_ed25519_public;
use trifid_pki::x25519_dalek::PublicKey;
pub async fn enroll_req( pub async fn enroll_req(
req: Json<EnrollRequest>, req: Json<EnrollRequest>,
@ -74,7 +76,11 @@ pub async fn enroll_req(
// reset the host's key entries // reset the host's key entries
handle_error!(diesel::delete(host_keys::dsl::host_keys.filter(host_keys::host_id.eq(&host.id))).execute(&mut conn).await); handle_error!(
diesel::delete(host_keys::dsl::host_keys.filter(host_keys::host_id.eq(&host.id)))
.execute(&mut conn)
.await
);
let (key_lockbox, trusted_key) = handle_error!(create_dnclient_ed_key(&state.config)); let (key_lockbox, trusted_key) = handle_error!(create_dnclient_ed_key(&state.config));
@ -103,12 +109,36 @@ pub async fn enroll_req(
.await .await
); );
let config_bytes = serde_yaml::to_string(&config).unwrap().as_bytes(); let org = handle_error!(
organizations::dsl::organizations
.find(host.organization_id.clone())
.first::<Organization>(&mut conn)
.await
);
let config_bytes = serde_yaml::to_string(&config).unwrap();
handle_error!(
diesel::update(&host)
.set((
hosts::dsl::last_seen_at.eq(SystemTime::now()),
hosts::dsl::update_available.eq(Some(false)),
hosts::dsl::counter.eq(Some(1))
))
.execute(&mut conn)
.await
);
ok!(EnrollResponse::Success { ok!(EnrollResponse::Success {
data: EnrollResponseData { data: EnrollResponseData {
host_id: host.id.clone(), host_id: host.id.clone(),
config: config_bytes.to_vec() config: config_bytes.as_bytes().to_vec(),
counter: 1,
trusted_keys: serialize_ed25519_public(trusted_key.as_bytes()),
organization: EnrollResponseDataOrg {
id: org.id,
name: org.name
}
} }
}) })
} }

View File

@ -58,6 +58,7 @@ diesel::table! {
platform -> Nullable<Varchar>, platform -> Nullable<Varchar>,
update_available -> Nullable<Bool>, update_available -> Nullable<Bool>,
tags -> Array<Nullable<Text>>, tags -> Array<Nullable<Text>>,
counter -> Nullable<Int4>,
} }
} }