Skip to main content

liminal_server/config/types/
lys.rs

1use std::net::SocketAddr;
2
3/// `[auth.lys]`: the Lys issuer whose passes are admitted (ACCESS-001 R2).
4#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
5#[serde(deny_unknown_fields)]
6pub struct LysConfig {
7    #[serde(default, skip_serializing_if = "Option::is_none")]
8    pub drafts: Option<crate::auth_lys::live::drafts::DraftConfig>,
9    /// The issuer URL exactly as the pass's `iss` names it.
10    pub issuer: String,
11    /// The Lys network liminal's subjects are placed under.
12    pub network_id: String,
13    /// Path to the issuer's published key set as JSON, read once at start.
14    pub keys_path: std::path::PathBuf,
15    /// Ask the issuer live for deliberate decisions (opening a link,
16    /// deploying a service, an administrator's act) with the holder's own
17    /// pass. Off, Lys cannot be asked and every deliberate route refuses by
18    /// that name; it never falls back to the pass.
19    #[serde(default)]
20    pub live: bool,
21    /// How the three grant decisions answer a missing grant (ACCESS-011 R2):
22    /// `enforce` (the default) refuses the act as today; `advise` says the
23    /// missing grant by name at WARN and lets the act through. Neither
24    /// changes who the holder is, the bearer, or the wire pass: an
25    /// unverifiable pass is refused the same under both.
26    #[serde(default)]
27    pub decisions: Decisions,
28    /// This liminal as a machine Lys knows, for links (ACCESS-002 R1).
29    #[serde(default)]
30    pub machine: Option<MachineConfig>,
31    /// Argus's functions as liminal services, bound to Lys and the server's
32    /// publish path (ACCESS-003 R1).
33    #[serde(default)]
34    pub services: Option<LysServicesConfig>,
35    /// Address-only broker and its observed-address UDP echo.
36    #[serde(default)]
37    pub broker: Option<BrokerConfig>,
38    /// Blind forwarding of marked relay streams.
39    #[serde(default)]
40    pub relay: Option<crate::services::relay::RelayConfig>,
41    /// Channel membership from Lys's one shared membership contract
42    /// (ACCESS-006 R1): the explicit product mapping of liminal channels to
43    /// placed Lys resources, and the provider route it is asked through.
44    /// Absent, no channel is membership-protected and every channel keeps
45    /// its prior contract.
46    #[serde(default)]
47    pub membership: Option<MembershipConfig>,
48    /// One bounded reader for this log; required beside membership protection.
49    #[serde(default)]
50    pub grant_stream: Option<super::GrantStreamConfig>,
51    /// Agent station bounds; required when any agent is placed.
52    #[serde(default)]
53    pub agents: Option<crate::agents::config::AgentsConfig>,
54    /// The templates services are created from (SRV-028 R1): the only
55    /// command lines this server will ever run for a seat.
56    #[serde(default)]
57    pub templates: crate::services::lifecycle::templates::TemplateSources,
58    /// Required operator bounds and machine-held builder delegation sources.
59    #[serde(default)]
60    pub construction: Option<crate::services::lifecycle::site::ConstructionConfig>,
61    /// The base of Lys's API routes (`.../api/`) for live asks, placements
62    /// and the service routes, when they are not served at the issuer
63    /// itself; absent, the issuer is the base, as before.
64    #[serde(default)]
65    pub api_url: Option<String>,
66    /// Path to this liminal's Lys app credential, read once at start; a new
67    /// service and its subjects are placed under the network with it
68    /// (SRV-028 R3). Absent, every create is refused by name.
69    #[serde(default)]
70    pub app_credential_path: Option<std::path::PathBuf>,
71}
72
73/// `[[auth.lys.templates]]`: one command a seat may create services from
74/// (SRV-028 R1). Read-only at run time.
75#[derive(Clone, PartialEq, Eq, serde::Deserialize)]
76#[serde(deny_unknown_fields)]
77pub struct ServiceTemplateConfig {
78    /// The template's name, one segment.
79    pub name: String,
80    /// The program, an absolute path to an executable file.
81    pub command: std::path::PathBuf,
82    /// Its arguments, passed as written.
83    #[serde(default)]
84    pub args: Vec<String>,
85    /// The subject roots a service of this template serves; each service
86    /// serves `<root>.<service name>`.
87    pub subjects: Vec<String>,
88    /// Names of variables passed through from the server's environment;
89    /// nothing else of it reaches the process.
90    #[serde(default)]
91    pub env: Vec<String>,
92    /// How long a stop waits after SIGTERM before SIGKILL; positive.
93    pub stop_timeout_ms: u64,
94}
95
96/// `[auth.lys.membership]`: channel membership bound to the verified holder
97/// and the placed resource (ACCESS-006 R1).
98///
99/// The mapping is explicit: a channel is membership-protected only when it is
100/// listed here with its workspace and placed resource, and a participant
101/// conversation is placed under a channel only when it is listed here. No
102/// mapping is guessed from a subject string, and a readable parent is never
103/// taken as membership of a child. Every bound is required and positive;
104/// none has a default. Validated whole when the membership gate is built at
105/// start ([`crate::auth_membership::MembershipGate::new`]).
106#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
107#[serde(deny_unknown_fields)]
108pub struct MembershipConfig {
109    /// The membership contract version this liminal speaks; it must equal
110    /// `lys_pass::membership::CONTRACT_VERSION`.
111    pub contract: u32,
112    /// The identity of the Lys grant log decisions are asked from.
113    pub log_identity: String,
114    /// The reset epoch of that grant log.
115    pub log_epoch: u64,
116    /// The product schema's workspace kind, as Lys places it.
117    pub workspace_kind: String,
118    /// The product schema's channel kind, as Lys places it.
119    pub channel_kind: String,
120    /// Path to this liminal's app credential for the Lys membership routes,
121    /// read once at start. Its bytes never appear in a `Debug`.
122    pub credential_path: std::path::PathBuf,
123    /// Most rows one membership page carries; positive.
124    pub page_rows: u32,
125    /// Most encoded row bytes and HTTP answer bytes; positive. The same cap
126    /// bounds reading the app credential before opening any protected route.
127    pub page_bytes: u32,
128    /// Most decisions the local membership index retains at once; positive.
129    /// A decision past it is refused by name before anything is allocated
130    /// for it, never evicted to make room.
131    pub max_decisions: usize,
132    /// The workspace a Lys-admitted `Connect` must hold a current grant in
133    /// (scoped guest admission). Absent, `Connect` asks no admission and a
134    /// protected channel is still decided per act.
135    #[serde(default)]
136    pub connect_workspace: Option<String>,
137    /// The membership-protected channels.
138    pub channels: Vec<MembershipChannelConfig>,
139    /// Participant conversations placed under a protected channel.
140    #[serde(default)]
141    pub conversations: Vec<MembershipConversationConfig>,
142}
143
144/// One membership-protected channel: the liminal channel name, the
145/// workspace it is placed under and the placed resource id.
146#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
147#[serde(deny_unknown_fields)]
148pub struct MembershipChannelConfig {
149    /// The liminal channel (subject) name, matched exactly.
150    pub channel: String,
151    /// The workspace resource id the channel is placed under.
152    pub workspace: String,
153    /// The placed channel resource id.
154    pub resource: String,
155}
156
157/// One participant conversation placed under a protected channel.
158#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
159#[serde(deny_unknown_fields)]
160pub struct MembershipConversationConfig {
161    /// The participant conversation id.
162    pub conversation: u64,
163    /// The protected channel it is placed under, as listed in `channels`.
164    pub channel: String,
165}
166
167/// `[auth.lys.services]`: the production bindings of the ACCESS-003
168/// services. They ask Lys as this machine, so `[auth.lys.machine]` is
169/// required beside it.
170#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
171#[serde(deny_unknown_fields)]
172pub struct LysServicesConfig {
173    /// The machine id Lys records, on whose runner `seats.<name>` starts.
174    pub machine: String,
175    /// The host name service messages are published as (`liminal.<host>`).
176    pub host: String,
177    /// Run the context watch over the `context-watch` channel, reporting
178    /// each reading to Lys.
179    #[serde(default)]
180    pub context_watch: bool,
181}
182
183/// `[auth.lys.broker]`: the address-only broker's UDP echo listener.
184#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
185#[serde(deny_unknown_fields)]
186pub struct BrokerConfig {
187    /// The socket on which a device asks for its observed address.
188    pub udp_listen: SocketAddr,
189}
190
191/// `[auth.lys.machine]`: this liminal's agent identity and pass.
192///
193/// Its links are keyed from the Ed25519 identity Lys recorded for it,
194/// its own end of every crossing is decided from its own pass, and the
195/// holders that may open links to it are named with their public keys.
196/// The link identity is an agent's own key and pass. The machine section
197/// name is retained so existing file-pass configurations still read.
198#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
199#[serde(deny_unknown_fields)]
200pub struct MachineConfig {
201    /// Carrier order and message-scoped direct deadlines.
202    #[serde(default)]
203    pub carrier_choice: crate::link_open::ChoiceConfig,
204    /// The device this machine is a district of, when configured.
205    #[serde(default)]
206    pub parent: Option<String>,
207    /// Path to the agent's Ed25519 seed, 64 hexadecimal digits, read once
208    /// at start. The link handshake keys from its X25519 derivation.
209    pub identity_path: std::path::PathBuf,
210    /// Path to the agent's own Lys pass, verified at start; absent when
211    /// the signed agent dial is configured.
212    #[serde(default)]
213    pub pass_path: std::path::PathBuf,
214    /// The agent whose capability certificate names this identity's key.
215    /// With it set, the pass is asked of Lys at start and again at any link
216    /// open while none is held or the held one is expired or due for
217    /// renewal; a start whose ask Lys refuses is logged once and asks again
218    /// at the next open, never by a restart (ACCESS-009).
219    #[serde(default)]
220    pub agent_id: String,
221    /// The app the agent asks the issuer for a pass to.
222    #[serde(default)]
223    pub audience: String,
224    /// The public key recorded for each holder that may open a link,
225    /// holder id to Ed25519 public key as 64 hexadecimal digits.
226    #[serde(default)]
227    pub machines: std::collections::BTreeMap<String, String>,
228    /// Outbound links keyed from this machine, each configured by name.
229    #[serde(default)]
230    pub links: Vec<OutboundLinkConfig>,
231}
232
233/// One exporting link; an absent wire address leaves carrier choice to the caller.
234#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
235#[serde(deny_unknown_fields)]
236pub struct OutboundLinkConfig {
237    /// The link id placed under the configured network.
238    pub link_id: String,
239    /// The holder whose Ed25519 key is recorded in this machine's directory mirror.
240    pub peer_holder: String,
241    /// The peer's wire listener as host:port, if this link uses the wire.
242    #[serde(default)]
243    pub peer_address: Option<String>,
244    /// Placed subject prefixes exported over this link.
245    pub exports: Vec<String>,
246}
247
248/// How the three grant decisions (a hot act, a live ask, a site act) answer
249/// a grant Lys does not hold (ACCESS-011 R2).
250#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Deserialize)]
251#[serde(rename_all = "snake_case")]
252pub enum Decisions {
253    /// A missing grant refuses the act, as every exchange ran before this.
254    #[default]
255    Enforce,
256    /// A missing grant is said by name at WARN and the act proceeds.
257    Advise,
258}
259
260impl Decisions {
261    /// The word the configuration uses.
262    #[must_use]
263    pub const fn word(self) -> &'static str {
264        match self {
265            Self::Enforce => "enforce",
266            Self::Advise => "advise",
267        }
268    }
269}
270
271impl std::fmt::Display for Decisions {
272    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        formatter.write_str(self.word())
274    }
275}