dnclient endpoint speedrun: 14 minutes
This commit is contained in:
parent
d8888e8f6e
commit
a5fb79288b
|
@ -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",
|
||||||
|
|
|
@ -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"
|
|
@ -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
|
||||||
);
|
);
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,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) {
|
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()
|
||||||
port: if let Some((from, to)) = u.port_range {
|
.map(|u| NebulaConfigFirewallRule {
|
||||||
let start_port = match from {
|
port: if let Some((from, to)) = u.port_range {
|
||||||
Bound::Included(u) => u,
|
let start_port = match from {
|
||||||
Bound::Excluded(u) => u+1,
|
Bound::Included(u) => u,
|
||||||
Bound::Unbounded => 0
|
Bound::Excluded(u) => u + 1,
|
||||||
};
|
Bound::Unbounded => 0,
|
||||||
let end_port = match from {
|
};
|
||||||
Bound::Included(u) => u,
|
let end_port = match to {
|
||||||
Bound::Excluded(u) => u-1,
|
Bound::Included(u) => u,
|
||||||
Bound::Unbounded => 65535
|
Bound::Excluded(u) => u - 1,
|
||||||
};
|
Bound::Unbounded => 65535,
|
||||||
Some(format!("{}-{}", start_port, end_port))
|
};
|
||||||
} else {
|
Some(format!("{}-{}", start_port, end_port))
|
||||||
Some("any".to_string())
|
} else {
|
||||||
},
|
Some("any".to_string())
|
||||||
proto: Some(u.protocol.clone()),
|
},
|
||||||
ca_name: None,
|
proto: Some(u.protocol.clone()),
|
||||||
ca_sha: None,
|
ca_name: None,
|
||||||
host: if u.allowed_role_id.is_some() {
|
ca_sha: None,
|
||||||
None
|
host: if u.allowed_role_id.is_some() {
|
||||||
} else {
|
None
|
||||||
Some("any".to_string())
|
} else {
|
||||||
},
|
Some("any".to_string())
|
||||||
group: None,
|
},
|
||||||
groups: if u.allowed_role_id.is_some() {
|
group: None,
|
||||||
Some(vec![format!("role:{}", u.allowed_role_id.clone().unwrap())])
|
groups: if u.allowed_role_id.is_some() {
|
||||||
} else {
|
Some(vec![format!("role:{}", u.allowed_role_id.clone().unwrap())])
|
||||||
None
|
} else {
|
||||||
},
|
None
|
||||||
cidr: None,
|
},
|
||||||
}).collect::<Vec<_>>())
|
cidr: None,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = NebulaConfig {
|
let config = NebulaConfig {
|
||||||
|
@ -214,18 +300,16 @@ 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,
|
ca_sha: None,
|
||||||
ca_sha: None,
|
host: Some("any".to_string()),
|
||||||
host: Some("any".to_string()),
|
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(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
@ -155,7 +155,7 @@ pub fn create_dnclient_ed_key(
|
||||||
&hex::decode(&config.tokens.data_encryption_key)
|
&hex::decode(&config.tokens.data_encryption_key)
|
||||||
.map_err(|e| CryptographyError::InvalidKey(e))?,
|
.map_err(|e| CryptographyError::InvalidKey(e))?,
|
||||||
)
|
)
|
||||||
.map_err(|_| CryptographyError::InvalidKeyLength)?;
|
.map_err(|_| CryptographyError::InvalidKeyLength)?;
|
||||||
|
|
||||||
let salt = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
let salt = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||||
|
|
||||||
|
@ -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((
|
||||||
info: aad.to_vec(),
|
DnclientKeyLockbox {
|
||||||
nonce: salt.as_slice().to_vec(),
|
info: aad.to_vec(),
|
||||||
key: lockbox,
|
nonce: salt.as_slice().to_vec(),
|
||||||
}, key.verifying_key()))
|
key: lockbox,
|
||||||
|
},
|
||||||
|
key.verifying_key(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign_dnclient_with_lockbox(
|
pub fn sign_dnclient_with_lockbox(
|
||||||
|
@ -187,7 +190,7 @@ pub fn sign_dnclient_with_lockbox(
|
||||||
&hex::decode(&config.tokens.data_encryption_key)
|
&hex::decode(&config.tokens.data_encryption_key)
|
||||||
.map_err(|e| CryptographyError::InvalidKey(e))?,
|
.map_err(|e| CryptographyError::InvalidKey(e))?,
|
||||||
)
|
)
|
||||||
.map_err(|_| CryptographyError::InvalidKeyLength)?;
|
.map_err(|_| CryptographyError::InvalidKeyLength)?;
|
||||||
|
|
||||||
let salt_u24: [u8; 24] = lockbox
|
let salt_u24: [u8; 24] = lockbox
|
||||||
.nonce
|
.nonce
|
||||||
|
@ -212,7 +215,7 @@ pub fn sign_dnclient_with_lockbox(
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| CryptographyError::InvalidSigningKeyLength)?,
|
.map_err(|_| CryptographyError::InvalidSigningKeyLength)?,
|
||||||
)
|
)
|
||||||
.map_err(|e| CryptographyError::SignatureError(e))?;
|
.map_err(|e| CryptographyError::SignatureError(e))?;
|
||||||
|
|
||||||
Ok(key.sign(bytes))
|
Ok(key.sign(bytes))
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>,
|
||||||
|
@ -40,13 +42,13 @@ pub async fn enroll_req(
|
||||||
|
|
||||||
if token.expires < SystemTime::now() {
|
if token.expires < SystemTime::now() {
|
||||||
err!(
|
err!(
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
make_err!(
|
make_err!(
|
||||||
"ERR_INVALID_VALUE",
|
"ERR_INVALID_VALUE",
|
||||||
"does not exist (maybe it expired?)",
|
"does not exist (maybe it expired?)",
|
||||||
"magicLinkToken"
|
"magicLinkToken"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// valid token
|
// valid token
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue