pub struct IngressEdge {
pub provider: IngressProvider,
pub machines: Vec<String>,
pub slots: Vec<String>,
pub hostnames: Vec<String>,
pub tunnel_id: Option<String>,
pub provider_id: Option<String>,
pub image: Option<String>,
pub auth: Option<PasswayAuth>,
pub via: Option<IngressVia>,
pub tunnel_door: Option<TunnelDoor>,
}Expand description
One declared edge: a front door, the slots it fronts, and the nodes it is placed on (W305 F2).
A mirror declares a list of these, which is what lets one service mix
front doors — cloudflare for the public web tier, passway for an internal or
high-throughput one. Before this, MirrorConfig::ingress was a single
IngressProvider, so a mirror could swap front doors but never mix
them.
[[ingress]]
provider = "passway"
machines = ["us-east-001", "us-south-001"]
slots = ["bundle"]
[[ingress]]
provider = "cloudflare-tunnel"
hostnames = ["issues.yah.dev"]The per-node appliance is derived from this, never declared beside it.
An edge does invoke a cloudflared or passway process on a box, but that is a
consequence of the service’s declaration:
collate_front_doors walks every
service and derives what each node must run. Declaring it node-side too is
what produces two sources of truth for one fact.
Fields§
§provider: IngressProviderWhich front door this edge is. IngressProvider::None is rejected at
plan time — an edge that fronts with nothing is always a typo, never an
intent (write no edge instead).
machines: Vec<String>Nodes this front door is placed on — independent of where the fronted workload runs (R330-F37).
Empty falls back to the fronted slot’s own machine / machines, which
is the co-located shape every mirror had before front-door placement was
expressible. Listing several is what lets the ingress tier and the
service tier scale independently: N front doors over ONE deployment,
one rendered copy, so no cache coherence to settle.
slots: Vec<String>Provider slot roles this edge fronts ("bundle", "compute", …).
One of the two selectors. With a single edge both may be empty, meaning “every fronted slot” — the legacy shape. With several edges a selector is mandatory on each, and the partition must be total and disjoint: a slot claimed by no edge, or by two, is an error naming it. An implicit catch-all across mixed front doors would silently publish a service through the wrong one.
hostnames: Vec<String>Public hostnames this edge fronts — the other selector, for partitioning by what the world dials rather than by which slot serves it.
tunnel_id: Option<String>Cloudflare Tunnel id this edge publishes through, overriding the
fronting machine’s MachineConfig::cloudflared.
This is W267 Gap 3’s real fix, and it is the service side of it: a node
can join two cohorts’ orange networks, and since §Granularity argues the
tunnel credential is the isolation boundary, which cohort a given
service fronts through is a property of the service, not of the box.
MachineConfig.cloudflared stays as the per-node default (one tunnel is
the common case, and the credential does live on the node), but it is no
longer the only way to say it — so the node never has to enumerate
cohorts.
provider_id: Option<String>Infra provider id whose credentials this edge’s front door authenticates
with — use = "cloudflare", resolved through
.yah/infra/providers/<id>.toml exactly as a slot’s use is.
Same split as tunnel_id, one field over: whose
Cloudflare account holds the tunnel is a property of the front door,
not of the box that runs the compute. Without this the account was read
off the fronted slot’s own use, which conflates two unrelated facts —
and is unwritable for a slot whose compute provider is kind = "static"
(a borrowed bare box: placement only, no credentials). Such a mirror had
no way to name a Cloudflare account at all, short of writing
use = "cloudflare" on the compute slot and lying about what runs it
(R845).
None falls back to the fronted slot’s use, which is what every
mirror written before this field meant.
image: Option<String>Digest-pinned image reference for this edge’s front-door appliance,
e.g. localhost/passway:tag@sha256:<hex> (R870-F16).
None is the state of every mirror on disk today: the passway arm of
yah cloud apply cannot deploy an appliance the mirror doesn’t name an
image for, so it renders the manual yah cloud ingress deploy … --image <passway-ref> step instead of running it. Declaring this field
is what makes the arm self-sufficient, matching the CloudflareTunnel
arm’s real-API-call shape rather than only printing for an operator to
copy by hand.
auth: Option<PasswayAuth>Cheers bearer-auth for this edge’s door, spelled as an [ingress.auth]
table under the [[ingress]] entry (R870-F26).
[[ingress]]
provider = "passway"
image = "localhost/passway:v1@sha256:…"
[ingress.auth]
key_secret = "cheers/yah-camp/verify"
kid = "YOHV4Riq-g8fX4uYl8rTjQ"
iss = "yah-camp"
aud = "analytics.yah.dev"
require_prefixes = ["/"]This is what makes an apply-driven push FAITHFUL rather than merely
blocked. Before it, yah cloud apply’s Passway arm rebuilt the door’s
spec with auth: None because a mirror had no way to say otherwise, and
/workloads/deploy is a full replace — so pushing at a door someone had
deployed with --auth-key-secret … took its auth away and brought it
back anonymous (R870-B24). That strip is guarded by a read-back in
push_passway_ingress, and the guard STAYS: it covers a door that
acquired auth in a way no mirror can see. This field is what lets the
common case sail past that guard by carrying the auth instead of losing
it — the guard early-returns on any push that carries auth of its own.
PasswayAuth verbatim, not a config-side copy of its five fields: all
five are required by Deserialize, so a half-written table is refused
by serde naming the missing field, and the renderer that emits the
PASSWAY_AUTH_* variables reads the very same struct.
Only meaningful on a provider = "passway" edge — a cloudflare-tunnel
edge carrying one is refused by MirrorConfig::ingress_edges rather
than silently ignored, since ignoring it yields exactly the
believed-protected-but-public door this vocabulary exists to prevent.
via: Option<IngressVia>Stack this edge in front of another front door on the same node instead
of dialing the workload (R910) — see IngressVia.
[[ingress]]
provider = "passway"
machines = ["us-west-011"]
hostnames = ["api-staging.noisetable.com"]
[[ingress]]
provider = "cloudflare-tunnel"
via = "passway"
use = "cloudflare-tunnel-staging"
machines = ["us-west-011"]
hostnames = ["api-staging.noisetable.com"]Only a cloudflare-tunnel edge may carry it, and the mirror must also
declare the edge it names, claiming the same hostnames on the same
machines — the tunnel dials its own node’s loopback demux, so a pair
split across nodes routes to nothing. Refused otherwise by
plan_ingress, naming both edges.
None is every mirror written before R910.
tunnel_door: Option<TunnelDoor>The door behind a tunnel, spelled [ingress.tunnel_door] on the
passway edge a via = "passway" tunnel stacks in front of (R910-F2).
[ingress.tunnel_door]
contact_email = "ops@example.com"
zone_id = "<cloudflare zone id>"
token_secret = "example/staging/cf-dns-token"
ports = { "staging.example.com" = 8445 }Required exactly when the edge is derived behind a tunnel, refused
otherwise — see partition. yah cloud apply turns it into one scoped
enrollment per hostname, which the tunnel’s machines route, arm and
issue from; see TunnelDoor.
Implementations§
Source§impl IngressEdge
impl IngressEdge
Sourcepub fn all_slots(provider: IngressProvider, machines: Vec<String>) -> Self
pub fn all_slots(provider: IngressProvider, machines: Vec<String>) -> Self
An edge with no selector — fronts every fronted slot, legal only when it is the mirror’s only edge.
Sourcepub fn validate_via(&self) -> Result<()>
pub fn validate_via(&self) -> Result<()>
Refuse via on an edge that cannot stack (R910). A passway edge
terminates the connection itself, so via there would be ignored — and
an ignored via is a mirror that reads as tunnel-fronted while
publishing the door’s own address.
Sourcepub fn validate_auth(&self) -> Result<()>
pub fn validate_auth(&self) -> Result<()>
Refuse an [ingress.auth] table that cannot produce a protected door
(R870-F26). Called from MirrorConfig::ingress_edges, so every reader
of a mirror — plan, collate, yah cloud validate, apply — gets it.
Two failures, and they fail in opposite directions, which is why both are here rather than left to the deploy:
- Auth on a non-passway edge. Nothing downstream would read it, so
the operator gets a door they believe is protected and is not. Only
passway renders
PASSWAY_AUTH_*; a cloudflare-tunnel edge publishes through Cloudflare Access instead and has no place to put these. - A present-but-empty field.
Deserializealready refuses a missing one by name;PasswayAuth::validatecovers the rest, and is the same implementationyah cloud ingress deployruns on its flags — so the two doors cannot diverge on what counts as configured.
Sourcepub fn validate_tunnel_door(&self) -> Result<()>
pub fn validate_tunnel_door(&self) -> Result<()>
Refuse an [ingress.tunnel_door] that cannot produce a working door
(R910-F2). Whether the edge is actually behind a tunnel is a property
of the pair, so partition checks that half.
Sourcepub fn has_selector(&self) -> bool
pub fn has_selector(&self) -> bool
true when this edge names which slots/hostnames it fronts.
Trait Implementations§
Source§impl Clone for IngressEdge
impl Clone for IngressEdge
Source§impl Debug for IngressEdge
impl Debug for IngressEdge
Source§impl<'de> Deserialize<'de> for IngressEdge
impl<'de> Deserialize<'de> for IngressEdge
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for IngressEdge
Source§impl PartialEq for IngressEdge
impl PartialEq for IngressEdge
Source§impl Serialize for IngressEdge
impl Serialize for IngressEdge
impl StructuralPartialEq for IngressEdge
Auto Trait Implementations§
impl Freeze for IngressEdge
impl RefUnwindSafe for IngressEdge
impl Send for IngressEdge
impl Sync for IngressEdge
impl Unpin for IngressEdge
impl UnsafeUnpin for IngressEdge
impl UnwindSafe for IngressEdge
Blanket Implementations§
impl<T> Allocation for T
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,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
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>. Box<dyn Any> can
then be further downcast into Box<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>. Rc<Any> 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> 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<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<K, Q> Equivalent<Q> for K
impl<K, Q> Equivalent<Q> for K
Source§fn equivalent(&self, key: &Q) -> bool
fn equivalent(&self, key: &Q) -> bool
key and return true if they are equal.impl<T> ErasedDestructor for Twhere
T: 'static,
impl<T> Fruit for T
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