trifid-api 0.3.0-alpha1 and some work on a tfweb rewrite #6

Merged
core merged 12 commits from api-and-web-rewrite into master 2023-12-28 05:44:29 +00:00
13 changed files with 456 additions and 126 deletions
Showing only changes of commit a5fb79288b - Show all commits

1
Cargo.lock generated
View File

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

View File

@ -34,4 +34,5 @@ chrono = "0.4"
dnapi-rs = { version = "0.2", path = "../dnapi-rs" }
nebula-config = { version = "0.1", path = "../nebula-config" }
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,
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 auth_token_expiry_seconds: u64,
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.
// Review carefully what you write here!
use std::collections::{Bound, HashMap};
use std::error::Error;
use std::net::{Ipv4Addr, SocketAddrV4};
use std::str::FromStr;
use std::time::{Duration, SystemTime};
use crate::crypt::sign_cert_with_ca;
use crate::models::{Host, HostKey, HostOverride, Network, Role, RoleFirewallRule, SigningCA};
use crate::schema::{
host_keys, host_overrides, hosts, networks, role_firewall_rules, roles, signing_cas,
};
use crate::AppState;
use actix_web::web::Data;
use diesel::{ExpressionMethods, QueryDsl, SelectableHelper};
use diesel_async::pooled_connection::bb8::RunError;
use diesel_async::RunQueryDsl;
use ipnet::Ipv4Net;
use nebula_config::{
NebulaConfig, NebulaConfigCipher, NebulaConfigFirewall, NebulaConfigFirewallRule,
NebulaConfigLighthouse, NebulaConfigListen, NebulaConfigPki, NebulaConfigPunchy,
NebulaConfigRelay, NebulaConfigTun,
};
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 nebula_config::{NebulaConfig, NebulaConfigCipher, NebulaConfigFirewall, NebulaConfigFirewallRule, NebulaConfigLighthouse, NebulaConfigListen, NebulaConfigPki, NebulaConfigPunchy, NebulaConfigRelay, NebulaConfigTun};
use trifid_pki::cert::{deserialize_nebula_certificate_from_pem, NebulaCertificate, NebulaCertificateDetails};
use trifid_pki::cert::{
deserialize_nebula_certificate_from_pem, NebulaCertificate, NebulaCertificateDetails,
};
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)]
pub enum ConfigGenError {
@ -32,44 +40,68 @@ pub enum ConfigGenError {
#[error("error parsing a signing CA: {0}")]
InvalidCACert(serde_json::Error),
#[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> {
let mut conn = state.pool.get().await.map_err(ConfigGenError::AcquireError)?;
pub async fn generate_config(
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 ca_string = String::new();
for ca in cas {
if ca.expires_at < SystemTime::now() {
let ca_cert: NebulaCertificate = 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)?);
let ca_cert: NebulaCertificate =
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));
}
}
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 {
details: NebulaCertificateDetails {
name: host.name.clone(),
ips: vec![
Ipv4Net::new(
Ipv4Addr::from_str(&host.ip_address).unwrap(),
Ipv4Net::from_str(&network.cidr).unwrap().prefix_len()
).unwrap()
],
ips: vec![Ipv4Net::new(
Ipv4Addr::from_str(&host.ip_address).unwrap(),
Ipv4Net::from_str(&network.cidr).unwrap().prefix_len(),
)
.unwrap()],
subnets: vec![],
groups: if let Some(role_id) = &host.role_id {
vec![format!("role:{}", role_id)]
} else { vec![] },
} else {
vec![]
},
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(),
is_ca: false,
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();
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![];
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 {
let cert = deserialize_nebula_certificate_from_pem(&blocked_key.client_cert).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 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_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)?;
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_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 {
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 {
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 {
@ -121,7 +190,10 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
hosts: if host.is_lighthouse {
vec![]
} 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(),
local_allow_list: Default::default(),
@ -132,52 +204,66 @@ 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) {
vec![]
} 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 {
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;
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.iter().map(|u| NebulaConfigFirewallRule {
port: if let Some((from, to)) = u.port_range {
let start_port = match from {
Bound::Included(u) => u,
Bound::Excluded(u) => u+1,
Bound::Unbounded => 0
};
let end_port = match from {
Bound::Included(u) => u,
Bound::Excluded(u) => u-1,
Bound::Unbounded => 65535
};
Some(format!("{}-{}", start_port, end_port))
} else {
Some("any".to_string())
},
proto: Some(u.protocol.clone()),
ca_name: None,
ca_sha: None,
host: if u.allowed_role_id.is_some() {
None
} else {
Some("any".to_string())
},
group: None,
groups: if u.allowed_role_id.is_some() {
Some(vec![format!("role:{}", u.allowed_role_id.clone().unwrap())])
} else {
None
},
cidr: None,
}).collect::<Vec<_>>())
inbound_firewall = Some(
firewall_rules
.iter()
.map(|u| NebulaConfigFirewallRule {
port: if let Some((from, to)) = u.port_range {
let start_port = match from {
Bound::Included(u) => u,
Bound::Excluded(u) => u + 1,
Bound::Unbounded => 0,
};
let end_port = match to {
Bound::Included(u) => u,
Bound::Excluded(u) => u - 1,
Bound::Unbounded => 65535,
};
Some(format!("{}-{}", start_port, end_port))
} else {
Some("any".to_string())
},
proto: Some(u.protocol.clone()),
ca_name: None,
ca_sha: None,
host: if u.allowed_role_id.is_some() {
None
} else {
Some("any".to_string())
},
group: None,
groups: if u.allowed_role_id.is_some() {
Some(vec![format!("role:{}", u.allowed_role_id.clone().unwrap())])
} else {
None
},
cidr: None,
})
.collect::<Vec<_>>(),
)
}
let config = NebulaConfig {
@ -214,18 +300,16 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
firewall: Some(NebulaConfigFirewall {
conntrack: None,
inbound: inbound_firewall,
outbound: Some(vec![
NebulaConfigFirewallRule {
port: Some("any".to_string()),
proto: Some("any".to_string()),
ca_name: None,
ca_sha: None,
host: Some("any".to_string()),
group: None,
groups: None,
cidr: None,
}
]),
outbound: Some(vec![NebulaConfigFirewallRule {
port: Some("any".to_string()),
proto: Some("any".to_string()),
ca_name: None,
ca_sha: None,
host: Some("any".to_string()),
group: None,
groups: None,
cidr: None,
}]),
}),
routines: 1,
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 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
@ -255,11 +344,11 @@ pub async fn generate_config(host: &Host, dh_pubkey: PublicKey, state: Data<AppS
current_val.as_mapping_mut().unwrap().insert(
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(),
);
}
let merged_config: NebulaConfig = serde_yaml::from_value(yaml_value).unwrap();
Ok(merged_config)
}
}

View File

@ -143,7 +143,7 @@ pub fn sign_cert_with_ca(
pub struct DnclientKeyLockbox {
pub info: Vec<u8>,
pub nonce: Vec<u8>,
pub key: Vec<u8>
pub key: Vec<u8>,
}
pub fn create_dnclient_ed_key(
@ -155,7 +155,7 @@ pub fn create_dnclient_ed_key(
&hex::decode(&config.tokens.data_encryption_key)
.map_err(|e| CryptographyError::InvalidKey(e))?,
)
.map_err(|_| CryptographyError::InvalidKeyLength)?;
.map_err(|_| CryptographyError::InvalidKeyLength)?;
let salt = XChaCha20Poly1305::generate_nonce(&mut OsRng);
@ -169,13 +169,16 @@ pub fn create_dnclient_ed_key(
aad: &aad,
},
)
.map_err(|e| CryptographyError::LockingError)?;
.map_err(|_| CryptographyError::LockingError)?;
Ok((DnclientKeyLockbox {
info: aad.to_vec(),
nonce: salt.as_slice().to_vec(),
key: lockbox,
}, key.verifying_key()))
Ok((
DnclientKeyLockbox {
info: aad.to_vec(),
nonce: salt.as_slice().to_vec(),
key: lockbox,
},
key.verifying_key(),
))
}
pub fn sign_dnclient_with_lockbox(
@ -187,7 +190,7 @@ pub fn sign_dnclient_with_lockbox(
&hex::decode(&config.tokens.data_encryption_key)
.map_err(|e| CryptographyError::InvalidKey(e))?,
)
.map_err(|_| CryptographyError::InvalidKeyLength)?;
.map_err(|_| CryptographyError::InvalidKeyLength)?;
let salt_u24: [u8; 24] = lockbox
.nonce
@ -212,7 +215,7 @@ pub fn sign_dnclient_with_lockbox(
.try_into()
.map_err(|_| CryptographyError::InvalidSigningKeyLength)?,
)
.map_err(|e| CryptographyError::SignatureError(e))?;
.map_err(|e| CryptographyError::SignatureError(e))?;
Ok(key.sign(bytes))
}
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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::schema::networks::dsl::networks;
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 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!(
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::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::{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(
req: Json<EnrollRequest>,
@ -40,13 +42,13 @@ pub async fn enroll_req(
if token.expires < SystemTime::now() {
err!(
StatusCode::BAD_REQUEST,
make_err!(
"ERR_INVALID_VALUE",
"does not exist (maybe it expired?)",
"magicLinkToken"
)
);
StatusCode::BAD_REQUEST,
make_err!(
"ERR_INVALID_VALUE",
"does not exist (maybe it expired?)",
"magicLinkToken"
)
);
}
// valid token
@ -74,7 +76,11 @@ pub async fn enroll_req(
// 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));
@ -103,12 +109,36 @@ pub async fn enroll_req(
.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 {
data: EnrollResponseData {
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>,
update_available -> Nullable<Bool>,
tags -> Array<Nullable<Text>>,
counter -> Nullable<Int4>,
}
}