mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-06-08 05:23:44 +02:00
A bunch of reorg to partition off V1 stuff for future convenience, and more controller work.
This commit is contained in:
parent
0239991c0d
commit
8a50427833
18 changed files with 273 additions and 220 deletions
|
@ -18,7 +18,3 @@ serde_json = { version = "^1", features = ["std"], default-features = false }
|
|||
serde_yaml = "^0"
|
||||
clap = { version = "^3", features = ["std", "suggestions"], default-features = false }
|
||||
notify = { version = "^5", features = ["macos_fsevent"], default-features = false }
|
||||
|
||||
[target."cfg(not(windows))".dependencies]
|
||||
signal-hook = "^0"
|
||||
libc = "^0"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||
|
||||
|
@ -8,7 +9,9 @@ use tokio::time::{Duration, Instant};
|
|||
use zerotier_network_hypervisor::protocol;
|
||||
use zerotier_network_hypervisor::protocol::{PacketBuffer, DEFAULT_MULTICAST_LIMIT, ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU};
|
||||
use zerotier_network_hypervisor::vl1::{HostSystem, Identity, InnerProtocol, Node, PacketHandlerResult, Path, PathFilter, Peer};
|
||||
use zerotier_network_hypervisor::vl2::{CertificateOfMembership, CertificateOfOwnership, NetworkConfig, NetworkId, Tag};
|
||||
use zerotier_network_hypervisor::vl2;
|
||||
use zerotier_network_hypervisor::vl2::networkconfig::*;
|
||||
use zerotier_network_hypervisor::vl2::NetworkId;
|
||||
use zerotier_utils::blob::Blob;
|
||||
use zerotier_utils::buffer::OutOfBoundsError;
|
||||
use zerotier_utils::dictionary::Dictionary;
|
||||
|
@ -37,6 +40,8 @@ pub struct Controller {
|
|||
|
||||
impl Controller {
|
||||
/// Start an inner protocol handler answer ZeroTier VL2 network controller queries.
|
||||
///
|
||||
/// The start() method must be called once the service this will run within is also created.
|
||||
pub async fn new(database: Arc<dyn Database>, runtime: tokio::runtime::Handle) -> Result<Arc<Self>, Box<dyn Error>> {
|
||||
if let Some(local_identity) = database.load_node_identity() {
|
||||
assert!(local_identity.secret.is_some());
|
||||
|
@ -56,13 +61,13 @@ impl Controller {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the service and HostSystem implementation for this controller.
|
||||
/// Set the service and HostSystem implementation for this controller and start daemons.
|
||||
///
|
||||
/// This must be called once the service that uses this handler is up or the controller
|
||||
/// won't actually do anything. The reference the handler holds is weak to prevent
|
||||
/// a circular reference, so if the VL1Service is dropped this must be called again to
|
||||
/// tell the controller handler about a new instance.
|
||||
pub async fn set_service(&self, service: &Arc<VL1Service<dyn Database, Self, Self>>) {
|
||||
pub async fn start(&self, service: &Arc<VL1Service<dyn Database, Self, Self>>) {
|
||||
*self.service.write().unwrap() = Arc::downgrade(service);
|
||||
|
||||
if let Some(cw) = self.database.changes().await.map(|mut ch| {
|
||||
|
@ -137,10 +142,24 @@ impl Controller {
|
|||
}
|
||||
|
||||
/// Called when the DB informs us of a change.
|
||||
async fn handle_change_notification(self: Arc<Self>, _change: Change) {}
|
||||
async fn handle_change_notification(self: Arc<Self>, change: Change) {
|
||||
match change {
|
||||
Change::MemberAuthorized(_, _) => {}
|
||||
Change::MemberDeauthorized(network_id, node_id) => {
|
||||
if let Ok(Some(member)) = self.database.get_member(network_id, node_id).await {
|
||||
if !member.authorized() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to create a network configuration and return the result.
|
||||
///
|
||||
/// This is the central function of the controller that looks up members, checks their
|
||||
/// permissions, and generates a network config and other credentials (or not).
|
||||
///
|
||||
/// An error is only returned if a database or other unusual error occurs. Otherwise a rejection
|
||||
/// reason is returned with None or an acceptance reason with a network configuration is returned.
|
||||
async fn get_network_config(
|
||||
|
@ -151,27 +170,37 @@ impl Controller {
|
|||
) -> Result<(AuthorizationResult, Option<NetworkConfig>), Box<dyn Error + Send + Sync>> {
|
||||
let network = self.database.get_network(network_id).await?;
|
||||
if network.is_none() {
|
||||
// TODO: send error
|
||||
return Ok((AuthorizationResult::Rejected, None));
|
||||
}
|
||||
let network = network.unwrap();
|
||||
|
||||
let mut member = self.database.get_member(network_id, source_identity.address).await?;
|
||||
let mut member_changed = false;
|
||||
let legacy_v1 = source_identity.p384.is_none();
|
||||
|
||||
// If we have a member object and a pinned identity, check to make sure it matches.
|
||||
if let Some(member) = member.as_ref() {
|
||||
// If we have a member object and a pinned identity, check to make sure it matches. Also accept
|
||||
// upgraded identities to replace old versions if they are properly formed and inherit.
|
||||
if let Some(member) = member.as_mut() {
|
||||
if let Some(pinned_identity) = member.identity.as_ref() {
|
||||
if !pinned_identity.eq(&source_identity) {
|
||||
return Ok((AuthorizationResult::RejectedIdentityMismatch, None));
|
||||
} else if source_identity.is_upgraded_from(pinned_identity) {
|
||||
let _ = member.identity.replace(source_identity.clone_without_secret());
|
||||
member_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is the final verdict after everything has been checked.
|
||||
let mut authorization_result = AuthorizationResult::Rejected;
|
||||
let mut authorized = member.as_ref().map_or(false, |m| m.authorized());
|
||||
if !authorized {
|
||||
|
||||
// This is the main "authorized" flag on the member record. If it is true then
|
||||
// the member is allowed, but with the caveat that SSO must be checked if it's
|
||||
// enabled on the network. If this is false then the member is rejected unless
|
||||
// authorized by a token or unless it's a public network.
|
||||
let mut member_authorized = member.as_ref().map_or(false, |m| m.authorized());
|
||||
|
||||
// If the member isn't authorized, check to see if it should be auto-authorized.
|
||||
if !member_authorized {
|
||||
if member.is_none() {
|
||||
if network.learn_members.unwrap_or(true) {
|
||||
let _ = member.insert(Member::new_with_identity(source_identity.clone(), network_id));
|
||||
|
@ -181,33 +210,38 @@ impl Controller {
|
|||
}
|
||||
}
|
||||
|
||||
if !network.private {
|
||||
if network.private {
|
||||
// TODO: check token authorization
|
||||
} else {
|
||||
authorization_result = AuthorizationResult::ApprovedOnPublicNetwork;
|
||||
authorized = true;
|
||||
member.as_mut().unwrap().last_authorized_time = Some(now);
|
||||
member_authorized = true;
|
||||
member_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut member = member.unwrap();
|
||||
|
||||
let nc: Option<NetworkConfig> = if authorized {
|
||||
// ====================================================================================
|
||||
// Authorized requests are handled here
|
||||
// ====================================================================================
|
||||
// If the member is authorized set the final verdict to reflect this unless SSO (third party auth)
|
||||
// is enabled on the network and disagrees. Skip if the verdict is already one of the approved
|
||||
// values, which would indicate auth-authorization above.
|
||||
if member_authorized {
|
||||
if !authorization_result.approved() {
|
||||
// TODO: check SSO if enabled on network!
|
||||
authorization_result = AuthorizationResult::Approved;
|
||||
}
|
||||
} else {
|
||||
// This should not be able to be in approved state if member_authorized is still false.
|
||||
assert!(!authorization_result.approved());
|
||||
}
|
||||
|
||||
// TODO: check SSO
|
||||
let nc: Option<NetworkConfig> = if authorization_result.approved() {
|
||||
// We should not be able to make it here if this is still false.
|
||||
assert!(member_authorized);
|
||||
|
||||
// Figure out time bounds for the certificate to generate.
|
||||
// Figure out TTL for credentials (time window in V1).
|
||||
let credential_ttl = network.credential_ttl.unwrap_or(CREDENTIAL_WINDOW_SIZE_DEFAULT);
|
||||
|
||||
// Get a list of all network members that were deauthorized but are still within the time window.
|
||||
// These will be issued revocations to remind the node not to speak to them until they fall off.
|
||||
let deauthed_members_still_in_window = self
|
||||
.database
|
||||
.list_members_deauthorized_after(network.id, now - credential_ttl)
|
||||
.await;
|
||||
|
||||
// Check and if necessary auto-assign static IPs for this member.
|
||||
member_changed |= network.check_zt_ip_assignments(self.database.as_ref(), &mut member).await;
|
||||
|
||||
|
@ -225,35 +259,52 @@ impl Controller {
|
|||
nc.rules = network.rules;
|
||||
nc.dns = network.dns;
|
||||
|
||||
nc.certificate_of_membership =
|
||||
CertificateOfMembership::new(&self.local_identity, network_id, &source_identity, now, credential_ttl, legacy_v1);
|
||||
if nc.certificate_of_membership.is_none() {
|
||||
return Ok((AuthorizationResult::RejectedDueToError, None));
|
||||
}
|
||||
if network.min_supported_version.unwrap_or(0) < (protocol::PROTOCOL_VERSION_V2 as u32) {
|
||||
// Get a list of all network members that were deauthorized but are still within the time window.
|
||||
// These will be issued revocations to remind the node not to speak to them until they fall off.
|
||||
let deauthed_members_still_in_window = self
|
||||
.database
|
||||
.list_members_deauthorized_after(network.id, now - credential_ttl)
|
||||
.await;
|
||||
|
||||
let mut coo = CertificateOfOwnership::new(network_id, now, source_identity.address, legacy_v1);
|
||||
if let Some(com) =
|
||||
vl2::v1::CertificateOfMembership::new(&self.local_identity, network_id, &source_identity, now, credential_ttl)
|
||||
{
|
||||
let mut v1cred = V1Credentials {
|
||||
certificate_of_membership: com,
|
||||
certificates_of_ownership: Vec::new(),
|
||||
tags: HashMap::new(),
|
||||
};
|
||||
|
||||
let mut coo = vl2::v1::CertificateOfOwnership::new(network_id, now, source_identity.address);
|
||||
for ip in nc.static_ips.iter() {
|
||||
coo.add_ip(ip);
|
||||
}
|
||||
if !coo.sign(&self.local_identity, &source_identity) {
|
||||
return Ok((AuthorizationResult::RejectedDueToError, None));
|
||||
}
|
||||
nc.certificates_of_ownership.push(coo);
|
||||
v1cred.certificates_of_ownership.push(coo);
|
||||
|
||||
for (id, value) in member.tags.iter() {
|
||||
let tag = Tag::new(*id, *value, &self.local_identity, network_id, &source_identity, now, legacy_v1);
|
||||
let tag = vl2::v1::Tag::new(*id, *value, &self.local_identity, network_id, &source_identity, now);
|
||||
if tag.is_none() {
|
||||
return Ok((AuthorizationResult::RejectedDueToError, None));
|
||||
}
|
||||
let _ = nc.tags.insert(*id, tag.unwrap());
|
||||
let _ = v1cred.tags.insert(*id, tag.unwrap());
|
||||
}
|
||||
|
||||
// TODO: node info, which isn't supported in v1 so not needed yet
|
||||
nc.v1_credentials = Some(v1cred);
|
||||
} else {
|
||||
return Ok((AuthorizationResult::RejectedDueToError, None));
|
||||
}
|
||||
} else {
|
||||
// TODO: create V2 type credential for V2-only networks
|
||||
// TODO: populate node info for V2 networks
|
||||
}
|
||||
|
||||
// TODO: revocations!
|
||||
|
||||
Some(nc)
|
||||
// ====================================================================================
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -409,6 +460,7 @@ impl InnerProtocol for Controller {
|
|||
}
|
||||
|
||||
fn should_respond_to(&self, _: &Identity) -> bool {
|
||||
// Controllers respond to anyone.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ use crate::model::*;
|
|||
/// Database change relevant to the controller and that was NOT initiated by the controller.
|
||||
#[derive(Clone)]
|
||||
pub enum Change {
|
||||
NetworkDeleted(NetworkId),
|
||||
MemberAuthorized(NetworkId, Address),
|
||||
MemberDeauthorized(NetworkId, Address),
|
||||
}
|
||||
|
|
|
@ -86,13 +86,13 @@ impl FileDatabase {
|
|||
}
|
||||
|
||||
fn network_path(&self, network_id: NetworkId) -> PathBuf {
|
||||
self.base_path.join(format!("n{:06x}", network_id.network_no())).join("config.yaml")
|
||||
self.base_path.join(format!("N{:06x}", network_id.network_no())).join("config.yaml")
|
||||
}
|
||||
|
||||
fn member_path(&self, network_id: NetworkId, member_id: Address) -> PathBuf {
|
||||
self.base_path
|
||||
.join(format!("n{:06x}", network_id.network_no()))
|
||||
.join(format!("m{}.yaml", member_id.to_string()))
|
||||
.join(format!("N{:06x}", network_id.network_no()))
|
||||
.join(format!("M{}.yaml", member_id.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ impl Database for FileDatabase {
|
|||
let osname = ent.file_name();
|
||||
let name = osname.to_string_lossy();
|
||||
if name.len() == (zerotier_network_hypervisor::protocol::ADDRESS_SIZE_STRING + 6)
|
||||
&& name.starts_with("m")
|
||||
&& name.starts_with("M")
|
||||
&& name.ends_with(".yaml")
|
||||
{
|
||||
if let Ok(member_address) = u64::from_str_radix(&name[1..11], 16) {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::{Arg, Command};
|
||||
|
||||
|
@ -34,27 +32,9 @@ async fn run(database: Arc<dyn Database>, runtime: &Runtime) -> i32 {
|
|||
let svc = svc.unwrap();
|
||||
svc.node().init_default_roots();
|
||||
|
||||
handler.set_service(&svc).await;
|
||||
|
||||
// Wait for kill signal on Unix-like platforms.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let term = Arc::new(AtomicBool::new(false));
|
||||
let _ = signal_hook::flag::register(libc::SIGINT, term.clone());
|
||||
let _ = signal_hook::flag::register(libc::SIGTERM, term.clone());
|
||||
let _ = signal_hook::flag::register(libc::SIGQUIT, term.clone());
|
||||
while !term.load(Ordering::Relaxed) {
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// TODO: if anyone wants to use this on Windows you'll need to make it a service or wait
|
||||
// for a stop signal or soemthing here.
|
||||
todo!();
|
||||
}
|
||||
handler.start(&svc).await;
|
||||
|
||||
zerotier_utils::wait_for_process_abort();
|
||||
println!("Terminate signal received, shutting down...");
|
||||
exitcode::OK
|
||||
} else {
|
||||
|
@ -84,7 +64,7 @@ fn main() {
|
|||
.takes_value(true)
|
||||
.forbid_empty_values(true)
|
||||
.value_name("path")
|
||||
.help(Some("Connect to postgres with parameters in JSON file"))
|
||||
.help(Some("Connect to postgres with parameters in YAML file"))
|
||||
.required_unless_present_any(&REQUIRE_ONE_OF_ARGS),
|
||||
)
|
||||
.version(format!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION).as_str())
|
||||
|
|
|
@ -11,7 +11,8 @@ use std::collections::HashMap;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use zerotier_network_hypervisor::vl1::{Address, Endpoint};
|
||||
use zerotier_network_hypervisor::vl2::{NetworkConfig, NetworkId};
|
||||
use zerotier_network_hypervisor::vl2::networkconfig::NetworkConfig;
|
||||
use zerotier_network_hypervisor::vl2::NetworkId;
|
||||
use zerotier_utils::blob::Blob;
|
||||
|
||||
/// A complete network with all member configuration information for import/export or blob storage.
|
||||
|
@ -80,6 +81,14 @@ impl AuthorizationResult {
|
|||
Self::ApprovedOnPublicNetwork => "ap",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this result is one of the 'approved' result types.
|
||||
pub fn approved(&self) -> bool {
|
||||
match self {
|
||||
Self::Approved | Self::ApprovedViaSSO | Self::ApprovedViaToken | Self::ApprovedOnPublicNetwork => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for AuthorizationResult {
|
||||
|
|
|
@ -7,7 +7,8 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use zerotier_network_hypervisor::vl1::InetAddress;
|
||||
use zerotier_network_hypervisor::vl2::networkconfig::IpRoute;
|
||||
use zerotier_network_hypervisor::vl2::{NetworkId, Rule};
|
||||
use zerotier_network_hypervisor::vl2::rule::Rule;
|
||||
use zerotier_network_hypervisor::vl2::NetworkId;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::{Member, ObjectType};
|
||||
|
@ -102,6 +103,11 @@ pub struct Network {
|
|||
#[serde(rename = "credentialTtl")]
|
||||
pub credential_ttl: Option<i64>,
|
||||
|
||||
/// Minimum supported ZeroTier protocol version for this network (default: undefined, up to members)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "minSupportedVersion")]
|
||||
pub min_supported_version: Option<u32>,
|
||||
|
||||
/// MTU inside the virtual network, default of 2800 is used if not set.
|
||||
pub mtu: Option<u16>,
|
||||
|
||||
|
|
|
@ -50,6 +50,9 @@ pub const PROTOCOL_VERSION: u8 = 20;
|
|||
/// We could probably push it back to 8 or 9 with some added support for sending Salsa/Poly packets.
|
||||
pub const PROTOCOL_VERSION_MIN: u8 = 11;
|
||||
|
||||
/// V2 is this protocol version or higher.
|
||||
pub const PROTOCOL_VERSION_V2: u8 = 20;
|
||||
|
||||
/// Size of a pooled packet buffer.
|
||||
pub const PACKET_BUFFER_SIZE: usize = 16384;
|
||||
|
||||
|
@ -565,6 +568,10 @@ pub(crate) const PEER_EXPIRATION_TIME: i64 = (PEER_HELLO_INTERVAL_MAX * 2) + 100
|
|||
/// Proof of work difficulty (threshold) for identity generation.
|
||||
pub(crate) const IDENTITY_POW_THRESHOLD: u8 = 17;
|
||||
|
||||
/// Maximum number of key/value pairs in a single Tag credential.
|
||||
/// (This is for V2 only. In V1 tag credentials can have only one pair.)
|
||||
pub(crate) const MAX_TAG_KEY_VALUE_PAIRS: usize = 128;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::mem::size_of;
|
||||
|
|
|
@ -157,6 +157,7 @@ impl Identity {
|
|||
+ P384_ECDSA_SIGNATURE_SIZE;
|
||||
|
||||
pub const FINGERPRINT_SIZE: usize = IDENTITY_FINGERPRINT_SIZE;
|
||||
pub const MAX_SIGNATURE_SIZE: usize = IDENTITY_MAX_SIGNATURE_SIZE;
|
||||
|
||||
const ALGORITHM_X25519: u8 = 0x01;
|
||||
const ALGORITHM_EC_NIST_P384: u8 = 0x02;
|
||||
|
@ -320,6 +321,15 @@ impl Identity {
|
|||
return digest[0] < IDENTITY_POW_THRESHOLD && Address::from_bytes(&digest[59..64]).map_or(false, |a| a == self.address);
|
||||
}
|
||||
|
||||
/// Returns true if this identity was upgraded from another older version.
|
||||
pub fn is_upgraded_from(&self, other: &Identity) -> bool {
|
||||
self.address == other.address
|
||||
&& self.x25519 == other.x25519
|
||||
&& self.ed25519 == other.ed25519
|
||||
&& self.p384.is_some()
|
||||
&& other.p384.is_none()
|
||||
}
|
||||
|
||||
/// Perform ECDH key agreement, returning a shared secret or None on error.
|
||||
///
|
||||
/// An error can occur if this identity does not hold its secret portion or if either key is invalid.
|
||||
|
|
|
@ -1,20 +1,13 @@
|
|||
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
||||
|
||||
mod certificateofmembership;
|
||||
mod certificateofownership;
|
||||
mod multicastgroup;
|
||||
mod networkid;
|
||||
mod rule;
|
||||
mod switch;
|
||||
mod tag;
|
||||
|
||||
pub mod networkconfig;
|
||||
pub mod rule;
|
||||
pub mod v1;
|
||||
|
||||
pub use certificateofmembership::CertificateOfMembership;
|
||||
pub use certificateofownership::CertificateOfOwnership;
|
||||
pub use multicastgroup::MulticastGroup;
|
||||
pub use networkconfig::NetworkConfig;
|
||||
pub use networkid::NetworkId;
|
||||
pub use rule::Rule;
|
||||
pub use switch::{Switch, SwitchInterface};
|
||||
pub use tag::Tag;
|
||||
|
|
|
@ -7,10 +7,8 @@ use std::str::FromStr;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::vl1::{Address, Identity, InetAddress};
|
||||
use crate::vl2::certificateofmembership::CertificateOfMembership;
|
||||
use crate::vl2::certificateofownership::CertificateOfOwnership;
|
||||
use crate::vl2::rule::Rule;
|
||||
use crate::vl2::tag::Tag;
|
||||
use crate::vl2::v1::{CertificateOfMembership, CertificateOfOwnership, Tag};
|
||||
use crate::vl2::NetworkId;
|
||||
|
||||
use zerotier_utils::buffer::Buffer;
|
||||
|
@ -18,6 +16,19 @@ use zerotier_utils::dictionary::Dictionary;
|
|||
use zerotier_utils::error::InvalidParameterError;
|
||||
use zerotier_utils::marshalable::Marshalable;
|
||||
|
||||
/// Credentials that must be sent to V1 nodes to allow access.
|
||||
///
|
||||
/// These are also handed out to V2 nodes to use when communicating with V1 nodes on
|
||||
/// networks that support older protocol versions.
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct V1Credentials {
|
||||
pub certificate_of_membership: CertificateOfMembership,
|
||||
pub certificates_of_ownership: Vec<CertificateOfOwnership>,
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||
#[serde(default)]
|
||||
pub tags: HashMap<u32, Tag>,
|
||||
}
|
||||
|
||||
/// Network configuration object sent to nodes by network controllers.
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct NetworkConfig {
|
||||
|
@ -51,13 +62,9 @@ pub struct NetworkConfig {
|
|||
#[serde(default)]
|
||||
pub dns: HashMap<String, HashSet<InetAddress>>,
|
||||
|
||||
pub certificate_of_membership: Option<CertificateOfMembership>, // considered invalid if None
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub certificates_of_ownership: Vec<CertificateOfOwnership>,
|
||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||
#[serde(default)]
|
||||
pub tags: HashMap<u32, Tag>,
|
||||
pub v1_credentials: Option<V1Credentials>,
|
||||
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
#[serde(default)]
|
||||
|
@ -91,9 +98,7 @@ impl NetworkConfig {
|
|||
static_ips: HashSet::new(),
|
||||
rules: Vec::new(),
|
||||
dns: HashMap::new(),
|
||||
certificate_of_membership: None,
|
||||
certificates_of_ownership: Vec::new(),
|
||||
tags: HashMap::new(),
|
||||
v1_credentials: None,
|
||||
banned: HashSet::new(),
|
||||
node_info: HashMap::new(),
|
||||
central_url: String::new(),
|
||||
|
@ -171,26 +176,28 @@ impl NetworkConfig {
|
|||
d.set_bytes(proto_v1_field_name::network_config::DNS, dns_bin);
|
||||
}
|
||||
|
||||
if let Some(v1cred) = self.v1_credentials.as_ref() {
|
||||
d.set_bytes(
|
||||
proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP,
|
||||
self.certificate_of_membership.as_ref()?.to_bytes()?,
|
||||
v1cred.certificate_of_membership.to_bytes()?,
|
||||
);
|
||||
|
||||
if !self.certificates_of_ownership.is_empty() {
|
||||
let mut certs = Vec::with_capacity(self.certificates_of_ownership.len() * 256);
|
||||
for c in self.certificates_of_ownership.iter() {
|
||||
let _ = certs.write_all(c.v1_proto_to_bytes(controller_identity.address)?.as_slice());
|
||||
if !v1cred.certificates_of_ownership.is_empty() {
|
||||
let mut certs = Vec::with_capacity(v1cred.certificates_of_ownership.len() * 256);
|
||||
for c in v1cred.certificates_of_ownership.iter() {
|
||||
let _ = certs.write_all(c.to_bytes(controller_identity.address)?.as_slice());
|
||||
}
|
||||
d.set_bytes(proto_v1_field_name::network_config::CERTIFICATES_OF_OWNERSHIP, certs);
|
||||
}
|
||||
|
||||
if !self.tags.is_empty() {
|
||||
let mut certs = Vec::with_capacity(self.tags.len() * 256);
|
||||
for (_, t) in self.tags.iter() {
|
||||
let _ = certs.write_all(t.v1_proto_to_bytes(controller_identity.address)?.as_slice());
|
||||
if !v1cred.tags.is_empty() {
|
||||
let mut certs = Vec::with_capacity(v1cred.tags.len() * 256);
|
||||
for (_, t) in v1cred.tags.iter() {
|
||||
let _ = certs.write_all(t.to_bytes(controller_identity.address)?.as_slice());
|
||||
}
|
||||
d.set_bytes(proto_v1_field_name::network_config::TAGS, certs);
|
||||
}
|
||||
}
|
||||
|
||||
// node_info is not supported by V1 nodes
|
||||
|
||||
|
@ -299,27 +306,33 @@ impl NetworkConfig {
|
|||
}
|
||||
}
|
||||
|
||||
nc.certificate_of_membership = Some(CertificateOfMembership::v1_proto_from_bytes(
|
||||
let mut v1cred = V1Credentials {
|
||||
certificate_of_membership: CertificateOfMembership::from_bytes(
|
||||
d.get_bytes(proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP)
|
||||
.ok_or(InvalidParameterError("missing certificate of membership"))?,
|
||||
)?);
|
||||
)?,
|
||||
certificates_of_ownership: Vec::new(),
|
||||
tags: HashMap::new(),
|
||||
};
|
||||
|
||||
if let Some(mut coo_bin) = d.get_bytes(proto_v1_field_name::network_config::CERTIFICATES_OF_OWNERSHIP) {
|
||||
while !coo_bin.is_empty() {
|
||||
let c = CertificateOfOwnership::v1_proto_from_bytes(coo_bin)?;
|
||||
nc.certificates_of_ownership.push(c.0);
|
||||
let c = CertificateOfOwnership::from_bytes(coo_bin)?;
|
||||
v1cred.certificates_of_ownership.push(c.0);
|
||||
coo_bin = c.1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut tag_bin) = d.get_bytes(proto_v1_field_name::network_config::TAGS) {
|
||||
while !tag_bin.is_empty() {
|
||||
let t = Tag::v1_proto_from_bytes(tag_bin)?;
|
||||
let _ = nc.tags.insert(t.0.id, t.0);
|
||||
let t = Tag::from_bytes(tag_bin)?;
|
||||
let _ = v1cred.tags.insert(t.0.id, t.0);
|
||||
tag_bin = t.1;
|
||||
}
|
||||
}
|
||||
|
||||
nc.v1_credentials = Some(v1cred);
|
||||
|
||||
if let Some(central_url) = d.get_str(proto_v1_field_name::network_config::CENTRAL_URL) {
|
||||
nc.central_url = central_url.to_string();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::io::Write;
|
||||
|
||||
use crate::vl1::identity;
|
||||
use crate::vl1::identity::Identity;
|
||||
use crate::vl1::Address;
|
||||
use crate::vl2::NetworkId;
|
||||
|
@ -20,22 +19,14 @@ pub struct CertificateOfMembership {
|
|||
pub timestamp: i64,
|
||||
pub max_delta: i64,
|
||||
pub issued_to: Address,
|
||||
pub issued_to_fingerprint: Blob<{ identity::IDENTITY_FINGERPRINT_SIZE }>,
|
||||
pub signature: ArrayVec<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
|
||||
pub version: u8,
|
||||
pub issued_to_fingerprint: Blob<{ Identity::FINGERPRINT_SIZE }>,
|
||||
pub signature: ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }>,
|
||||
}
|
||||
|
||||
impl CertificateOfMembership {
|
||||
/// Create a new signed certificate of membership.
|
||||
/// None is returned if an error occurs, such as the issuer missing its secrets.
|
||||
pub fn new(
|
||||
issuer: &Identity,
|
||||
network_id: NetworkId,
|
||||
issued_to: &Identity,
|
||||
timestamp: i64,
|
||||
max_delta: i64,
|
||||
legacy_v1: bool,
|
||||
) -> Option<Self> {
|
||||
pub fn new(issuer: &Identity, network_id: NetworkId, issued_to: &Identity, timestamp: i64, max_delta: i64) -> Option<Self> {
|
||||
let mut com = CertificateOfMembership {
|
||||
network_id,
|
||||
timestamp,
|
||||
|
@ -43,26 +34,18 @@ impl CertificateOfMembership {
|
|||
issued_to: issued_to.address,
|
||||
issued_to_fingerprint: Blob::default(),
|
||||
signature: ArrayVec::new(),
|
||||
version: 0,
|
||||
};
|
||||
if legacy_v1 {
|
||||
|
||||
com.issued_to_fingerprint = Blob::from(Self::v1_proto_issued_to_fingerprint(issued_to, Some(issuer.address)));
|
||||
com.version = 1;
|
||||
if let Some(signature) = issuer.sign(&com.v1_proto_get_qualifier_bytes(), true) {
|
||||
com.signature = signature;
|
||||
Some(com)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
com.issued_to_fingerprint = Blob::from(issued_to.fingerprint);
|
||||
com.version = 2;
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn v1_proto_get_qualifier_bytes(&self) -> [u8; 168] {
|
||||
assert_eq!(self.version, 1);
|
||||
let mut q = [0u64; 21];
|
||||
|
||||
q[0] = 0;
|
||||
|
@ -109,7 +92,6 @@ impl CertificateOfMembership {
|
|||
|
||||
/// Get this certificate of membership in byte encoded format.
|
||||
pub fn to_bytes(&self) -> Option<Vec<u8>> {
|
||||
if self.version == 1 {
|
||||
if self.signature.len() == 96 {
|
||||
let mut v: Vec<u8> = Vec::with_capacity(3 + 168 + 5 + 96);
|
||||
v.push(1); // version byte from v1 protocol
|
||||
|
@ -120,14 +102,11 @@ impl CertificateOfMembership {
|
|||
let _ = v.write_all(self.signature.as_bytes());
|
||||
return Some(v);
|
||||
}
|
||||
} else if self.version == 2 {
|
||||
todo!()
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
/// Decode a V1 legacy format certificate of membership in byte format.
|
||||
pub fn v1_proto_from_bytes(mut b: &[u8]) -> Result<Self, InvalidParameterError> {
|
||||
pub fn from_bytes(mut b: &[u8]) -> Result<Self, InvalidParameterError> {
|
||||
if b.len() <= 3 || b[0] != 1 {
|
||||
return Err(InvalidParameterError("version mismatch"));
|
||||
}
|
||||
|
@ -185,21 +164,16 @@ impl CertificateOfMembership {
|
|||
s.push_slice(&b[..96]);
|
||||
s
|
||||
},
|
||||
version: 1,
|
||||
})
|
||||
}
|
||||
|
||||
/// Verify this certificate of membership.
|
||||
pub fn verify(self, issuer: &Identity, expect_issued_to: &Identity) -> Option<Verified<Self>> {
|
||||
if self.version == 1 {
|
||||
if Self::v1_proto_issued_to_fingerprint(expect_issued_to, None).eq(&self.issued_to_fingerprint.as_bytes()[..32]) {
|
||||
if issuer.verify(&self.v1_proto_get_qualifier_bytes(), self.signature.as_bytes()) {
|
||||
return Some(Verified(self));
|
||||
}
|
||||
}
|
||||
} else if self.version == 2 {
|
||||
todo!()
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
|
@ -34,23 +34,17 @@ pub struct CertificateOfOwnership {
|
|||
pub things: HashSet<Thing>,
|
||||
pub issued_to: Address,
|
||||
pub signature: ArrayVec<u8, { crate::vl1::identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
|
||||
pub version: u8,
|
||||
}
|
||||
|
||||
impl CertificateOfOwnership {
|
||||
/// Create a new empty and unsigned certificate.
|
||||
pub fn new(network_id: NetworkId, timestamp: i64, issued_to: Address, legacy_v1: bool) -> Self {
|
||||
pub fn new(network_id: NetworkId, timestamp: i64, issued_to: Address) -> Self {
|
||||
Self {
|
||||
network_id,
|
||||
timestamp,
|
||||
things: HashSet::with_capacity(4),
|
||||
issued_to,
|
||||
signature: ArrayVec::new(),
|
||||
version: if legacy_v1 {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,13 +111,13 @@ impl CertificateOfOwnership {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn v1_proto_to_bytes(&self, signed_by: Address) -> Option<Vec<u8>> {
|
||||
pub fn to_bytes(&self, signed_by: Address) -> Option<Vec<u8>> {
|
||||
self.internal_v1_proto_to_bytes(false, signed_by)
|
||||
}
|
||||
|
||||
/// Decode a V1 legacy format certificate of ownership in byte format.
|
||||
/// The certificate and the current position slice are returned so multiple certs can be easily read from a buffer.
|
||||
pub fn v1_proto_from_bytes(mut b: &[u8]) -> Result<(Self, &[u8]), InvalidParameterError> {
|
||||
pub fn from_bytes(mut b: &[u8]) -> Result<(Self, &[u8]), InvalidParameterError> {
|
||||
if b.len() < 30 {
|
||||
return Err(InvalidParameterError("incomplete"));
|
||||
}
|
||||
|
@ -167,7 +161,6 @@ impl CertificateOfOwnership {
|
|||
s.push_slice(&b[13..109]);
|
||||
s
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
&b[END_LEN..],
|
||||
))
|
||||
|
@ -175,7 +168,6 @@ impl CertificateOfOwnership {
|
|||
|
||||
/// Sign certificate of ownership for use by V1 nodes.
|
||||
pub fn sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool {
|
||||
if self.version == 1 {
|
||||
self.issued_to = issued_to.address;
|
||||
if let Some(to_sign) = self.internal_v1_proto_to_bytes(true, issuer.address) {
|
||||
if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) {
|
||||
|
@ -183,9 +175,6 @@ impl CertificateOfOwnership {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
} else if self.version == 2 {
|
||||
todo!()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
9
network-hypervisor/src/vl2/v1/mod.rs
Normal file
9
network-hypervisor/src/vl2/v1/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
mod certificateofmembership;
|
||||
mod certificateofownership;
|
||||
mod revocation;
|
||||
mod tag;
|
||||
|
||||
pub use certificateofmembership::CertificateOfMembership;
|
||||
pub use certificateofownership::{CertificateOfOwnership, Thing};
|
||||
pub use revocation::Revocation;
|
||||
pub use tag::Tag;
|
15
network-hypervisor/src/vl2/v1/revocation.rs
Normal file
15
network-hypervisor/src/vl2/v1/revocation.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use zerotier_utils::arrayvec::ArrayVec;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::vl1::{Address, Identity};
|
||||
use crate::vl2::NetworkId;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Revocation {
|
||||
pub network_id: NetworkId,
|
||||
pub threshold: i64,
|
||||
pub issued_to: Address,
|
||||
pub signature: ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }>,
|
||||
pub version: u8,
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use std::io::Write;
|
||||
|
||||
use crate::vl1::identity;
|
||||
use crate::vl1::identity::Identity;
|
||||
use crate::vl1::Address;
|
||||
use crate::vl2::NetworkId;
|
||||
|
@ -17,20 +16,11 @@ pub struct Tag {
|
|||
pub issued_to: Address,
|
||||
pub id: u32,
|
||||
pub value: u32,
|
||||
pub signature: ArrayVec<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
|
||||
pub version: u8,
|
||||
pub signature: ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }>,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn new(
|
||||
id: u32,
|
||||
value: u32,
|
||||
issuer: &Identity,
|
||||
network_id: NetworkId,
|
||||
issued_to: &Identity,
|
||||
timestamp: i64,
|
||||
legacy_v1: bool,
|
||||
) -> Option<Self> {
|
||||
pub fn new(id: u32, value: u32, issuer: &Identity, network_id: NetworkId, issued_to: &Identity, timestamp: i64) -> Option<Self> {
|
||||
let mut tag = Self {
|
||||
network_id,
|
||||
timestamp,
|
||||
|
@ -38,26 +28,17 @@ impl Tag {
|
|||
id,
|
||||
value,
|
||||
signature: ArrayVec::new(),
|
||||
version: if legacy_v1 {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
},
|
||||
};
|
||||
if legacy_v1 {
|
||||
let to_sign = tag.internal_v1_proto_to_bytes(true, issuer.address)?;
|
||||
if let Some(signature) = issuer.sign(to_sign.as_slice(), true) {
|
||||
tag.signature = signature;
|
||||
return Some(tag);
|
||||
}
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn internal_v1_proto_to_bytes(&self, for_sign: bool, signed_by: Address) -> Option<Vec<u8>> {
|
||||
if self.version == 1 && self.signature.len() == 96 {
|
||||
if self.signature.len() == 96 {
|
||||
let mut v = Vec::with_capacity(256);
|
||||
if for_sign {
|
||||
let _ = v.write_all(&[0x7f; 8]);
|
||||
|
@ -85,11 +66,11 @@ impl Tag {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn v1_proto_to_bytes(&self, signed_by: Address) -> Option<Vec<u8>> {
|
||||
pub fn to_bytes(&self, signed_by: Address) -> Option<Vec<u8>> {
|
||||
self.internal_v1_proto_to_bytes(false, signed_by)
|
||||
}
|
||||
|
||||
pub fn v1_proto_sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool {
|
||||
pub fn sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool {
|
||||
self.issued_to = issued_to.address;
|
||||
if let Some(to_sign) = self.internal_v1_proto_to_bytes(true, issuer.address) {
|
||||
if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) {
|
||||
|
@ -100,7 +81,7 @@ impl Tag {
|
|||
return false;
|
||||
}
|
||||
|
||||
pub fn v1_proto_from_bytes(b: &[u8]) -> Result<(Self, &[u8]), InvalidParameterError> {
|
||||
pub fn from_bytes(b: &[u8]) -> Result<(Self, &[u8]), InvalidParameterError> {
|
||||
const LEN: usize = 8 + 8 + 4 + 4 + 5 + 5 + 3 + 96 + 2;
|
||||
if b.len() < LEN {
|
||||
return Err(InvalidParameterError("incomplete"));
|
||||
|
@ -117,7 +98,6 @@ impl Tag {
|
|||
s.push_slice(&b[37..133]);
|
||||
s
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
&b[LEN..],
|
||||
))
|
|
@ -19,3 +19,4 @@ winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] }
|
|||
|
||||
[target."cfg(not(windows))".dependencies]
|
||||
libc = "^0"
|
||||
signal-hook = "^0"
|
||||
|
|
|
@ -51,6 +51,26 @@ pub fn ms_monotonic() -> i64 {
|
|||
std::time::Instant::now().duration_since(instant_zero).as_millis() as i64
|
||||
}
|
||||
|
||||
/// Wait for a kill signal (e.g. SIGINT or OS-equivalent) and return when received.
|
||||
#[cfg(unix)]
|
||||
pub fn wait_for_process_abort() {
|
||||
if let Ok(mut signals) = signal_hook::iterator::Signals::new(&[libc::SIGINT, libc::SIGTERM, libc::SIGQUIT]) {
|
||||
'wait_for_exit: loop {
|
||||
for signal in signals.wait() {
|
||||
match signal as libc::c_int {
|
||||
libc::SIGINT | libc::SIGTERM | libc::SIGQUIT => {
|
||||
break 'wait_for_exit;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
} else {
|
||||
panic!("unable to listen to OS signals");
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
pub extern "C" fn unlikely_branch() {}
|
||||
|
|
Loading…
Add table
Reference in a new issue