pub struct Authority { /* private fields */ }Expand description
A tailnet-lock authority as a client tracks it: the current trusted-key State and the chain
head. Built by replaying the AUM chain (or from a control-provided checkpoint); the client
then uses Authority::node_key_authorized to decide whether a peer is trusted.
Implementations§
Source§impl Authority
impl Authority
Sourcepub fn head_matches(&self, head: &AumHash) -> bool
pub fn head_matches(&self, head: &AumHash) -> bool
Whether head (e.g. decoded from TkaInfo.head) matches this authority’s head. A client
that finds a mismatch must resync before trusting verifications.
Sourcepub fn key_trusted(&self, key_id: &[u8]) -> bool
pub fn key_trusted(&self, key_id: &[u8]) -> bool
Whether a key with the given key_id is currently trusted by this authority (Go
Authority.KeyTrusted / a State.GetKey(keyID) != nil check).
key_id is the 32-byte ed25519 public key (Go Key25519/NLKey id is the pubkey). This is
the gate a mutation caller uses before attempting to sign: a node whose network-lock key is not
trusted cannot produce a signature the authority would accept (see
NodeKeySignature::sign_direct / Aum::sign), so the builder checks this first rather than
submitting a doomed signature. Verify-and-log posture is unchanged — this only reads the
trusted-key set.
Verify that node_key is authorized under the current authority state by the given
node-key-signature CBOR blob (Go Authority.NodeKeyAuthorized).
Fail-closed: a credential-only signature, an untrusted authorizing key, a malformed blob, or
a bad signature all return Err.
§Errors
Returns TkaError::Decode if signature_cbor is malformed,
TkaError::CredentialCannotAuthorize for a credential-only signature,
TkaError::UntrustedKey if the authorizing key is not in the current state,
TkaError::NodeKeyMismatch if the signature does not cover node_key, or
TkaError::BadSignature if cryptographic verification fails.
As node_key_authorized, but on success also returns the
RotationDetails of the signature — Some iff it is a SigKind::Rotation chain, None
otherwise. Mirrors Go Authority.NodeKeyAuthorizedWithDetails.
The verification performed is byte-identical to node_key_authorized
(same decode → credential-reject → trusted-key lookup → signature check); the details are a
pure post-success walk of the already-decoded nested chain, never an extra cryptographic
operation. A netmap-wide rotation filter feeds these details to drop peers presenting a
rotated-away node key.
§Errors
Same as node_key_authorized: TkaError::Decode,
TkaError::CredentialCannotAuthorize, TkaError::UntrustedKey,
TkaError::NodeKeyMismatch, or TkaError::BadSignature.
Source§impl Authority
impl Authority
Sourcepub fn from_verified_chain(chain: VerifiedAumChain) -> Authority
pub fn from_verified_chain(chain: VerifiedAumChain) -> Authority
Build an Authority from a VerifiedAumChain — the trust-boundary constructor.
Because a VerifiedAumChain can only be produced by VerifiedAumChain::verify (which runs
Go’s aumVerify on every link), this is the constructor a live client must use when the chain
originates from an untrusted source (the control plane / /machine/tka/* sync RPC). The type
system makes the signature check un-skippable: there is no way to reach this function with an
unverified chain. Mirrors Go tka.Open, which folds only AUMs already verified by Inform.
Sourcepub fn from_chain(aums: &[Aum]) -> Result<Authority, TkaError>
pub fn from_chain(aums: &[Aum]) -> Result<Authority, TkaError>
Build an Authority by replaying a linear chain of AUMs from genesis to head (Go
tka.Authority.Head after computeActiveChain on a single confirmed branch), checking only
the chain’s structure (genesis kind, parent links, key-state transitions, checkpoint
StateID) — NOT AUM signatures.
§This is NOT a trust boundary
from_chain does not verify that each AUM is signed by keys trusted at its parent. It is safe
only for chains whose authenticity is already established by other means — the existing unit
tests, and a chain the caller has itself fed through VerifiedAumChain::verify. For any
chain that comes from an untrusted source (the control plane), use
VerifiedAumChain::verify + Authority::from_verified_chain, which the type system makes
impossible to bypass. (A malicious control plane could otherwise forge an AddKey/RemoveKey
here and silently defeat tailnet lock — the exact threat TKA exists to stop.)
aums must be ordered parent→child: the first is the genesis — a NoOp, AddKey, or
Checkpoint with no parent (Go computeStateAt rejects any other kind as an invalid
genesis) — and each subsequent AUM’s prev_aum_hash must equal the prior AUM’s Aum::hash.
A slice that is actually a suffix of a chain (its first AUM names a parent not in the slice)
is rejected rather than mis-rooted.
For the forked case (competing children of one parent), use Authority::from_forked_chain.
§Errors
TkaError::BadChain if aums is empty or its genesis is an invalid kind;
TkaError::BadParent if a link doesn’t chain (incl. a genesis that carries a parent);
TkaError::BadKeyState for an invalid add/remove/update or a mismatched checkpoint StateID;
TkaError::Decode for a malformed checkpoint/add.
Sourcepub fn from_forked_chain(
prefix: &[Aum],
branches: &[&[Aum]],
) -> Result<Authority, TkaError>
pub fn from_forked_chain( prefix: &[Aum], branches: &[&[Aum]], ) -> Result<Authority, TkaError>
Resolve a single fork point: a shared linear prefix (genesis→fork point, parent-ordered)
followed by branches, the competing children of the fork point. The active child is chosen by
pick_next_aum’s deterministic rules (weight → RemoveKey preference → lowest hash),
evaluated against the state at the fork point, and applied. This is the consensus-critical
selection every node must make identically; the linear Authority::from_chain is the common
(no-fork) case.
Each branch must be exactly one AUM. In this single-AUM-per-branch shape the choice is
provably identical to Go’s pickNextAUM over the fork point’s children. A multi-step branch
is rejected (TkaError::BadChain) rather than mis-resolved: Go re-runs pickNextAUM at
every link (advanceByPrimary), re-evaluating weight against the evolving state, so judging a
whole multi-AUM branch by its first AUM alone could pick a different active head than Go and
silently fork the trusted-key set. Implementing the per-step loop (and a general multi-fork DAG
walk) is deferred to when the sync layer can actually surface such a chain; until then this
guard keeps the model honest. (The common re-bootstrap case — competing single-AUM heads — is
fully covered.)
§Errors
As Authority::from_chain, plus TkaError::BadChain if branches is empty, or any branch
is not exactly one AUM.
Source§impl Authority
impl Authority
Sourcepub fn sync_offer(
&self,
storage: &dyn AumStore,
oldest: AumHash,
) -> Result<SyncOffer, TkaError>
pub fn sync_offer( &self, storage: &dyn AumStore, oldest: AumHash, ) -> Result<SyncOffer, TkaError>
Build the SyncOffer this authority would send a peer (Go Authority.SyncOffer): its
head plus every checkpoint on the chain back to oldest, ending with oldest.
oldest is the oldest AUM the caller holds (Go a.oldestAncestor.Hash()); our verify-only
Authority does not track it, so it is passed in — typically the genesis hash of the chain
the caller staged in storage.
Checkpoints, not a sample: a node compacts its chain (keep the last 24 AUMs, keep two weeks, then back to the last checkpoint) and can end up holding only a few dozen AUMs. A sparse sample of positions taken from a long chain can then be entirely disjoint from the window a peer kept, leaving the peer unable to find a common ancestor and stuck re-polling with a permanently stale view. Compaction is guaranteed to leave at least one checkpoint behind, so offering the checkpoints is what makes an intersection findable.
storage must contain the chain from head back to oldest; a gap simply truncates the
ancestor list early (the walk breaks on the first missing parent, exactly like Go).
Sourcepub fn missing_aums(
&self,
storage: &dyn AumStore,
remote_offer: &SyncOffer,
oldest: AumHash,
) -> Result<Vec<Aum>, TkaError>
pub fn missing_aums( &self, storage: &dyn AumStore, remote_offer: &SyncOffer, oldest: AumHash, ) -> Result<Vec<Aum>, TkaError>
Given a peer’s SyncOffer, compute the AUMs they are missing — the ones to send them so
their chain catches up to ours (Go Authority.MissingAUMs). storage must hold our chain.
Returns an empty Vec when the peer is already up to date.
Mirrors Go: compute our own offer, find the intersection of the two chains, then gather every AUM from the intersection forward to our head (excluding the intersection AUM itself).
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Authority
impl RefUnwindSafe for Authority
impl Send for Authority
impl Sync for Authority
impl Unpin for Authority
impl UnsafeUnpin for Authority
impl UnwindSafe for Authority
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<A, T> DynMessage<A> for T
impl<A, T> DynMessage<A> for T
Source§fn handle_dyn<'a>(
self: Box<T>,
state: &'a mut A,
actor_ref: ActorRef<A>,
tx: Option<Sender<Result<Box<dyn Any + Send>, SendError<Box<dyn Any + Send>, Box<dyn Any + Send>>>>>,
stop: &'a mut bool,
) -> Pin<Box<dyn Future<Output = Result<(), Box<dyn ReplyError>>> + Send + 'a>>
fn handle_dyn<'a>( self: Box<T>, state: &'a mut A, actor_ref: ActorRef<A>, tx: Option<Sender<Result<Box<dyn Any + Send>, SendError<Box<dyn Any + Send>, Box<dyn Any + Send>>>>>, stop: &'a mut bool, ) -> Pin<Box<dyn Future<Output = Result<(), Box<dyn ReplyError>>> + Send + 'a>>
impl<T> ErasedDestructor for Twhere
T: 'static,
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more