Skip to content

Commit fb83d1a

Browse files
feat(gateway): add system registry support and source indicators (#1625)
* feat(bootstrap): add system gateway registry for installer defaults Adds a read-only installer-seeded gateway registry that the CLI consults after per-user gateway config. The registry uses the same layout as per-user config with `active_gateway` at the root and `gateways/<name>/metadata.json` beneath it. By default the system config root is `/etc/openshell`, while `OPENSHELL_SYSTEM_GATEWAY_DIR` remains available as an override for packages that need a different location. User-managed gateways continue to shadow installer entries on name collision. Originally-authored-by: Mark Shuttleworth <mark@ubuntu.com> Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com> * feat(cli): show gateway config source in list and term Expose whether a gateway registration comes from user or system config in `openshell gateway list`, the TUI gateway pane, and list JSON output. The CLI also refuses to remove system-managed registrations and the smoke tests cover the new list output. Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com> * fix(bootstrap): preserve user shadowing on invalid metadata Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com> * fix(cli): keep system fallback out of rollback state * docs(gateway): describe system config fallback layout * test(bootstrap): cover system gateway last_sandbox persistence * test(gateway): cover system-only removal rejection * fix(bootstrap): validate gateway names before path joins --------- Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>
1 parent e73745f commit fb83d1a

15 files changed

Lines changed: 1519 additions & 129 deletions

File tree

architecture/gateway.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,21 @@ Driver-specific values that are not part of the inheritance allowlist
446446
(e.g. Podman `socket_path`, VM `vcpus`) only come from the driver's own
447447
table.
448448

449+
### Package-managed gateway registry
450+
451+
The CLI reads its active-gateway and per-gateway metadata from
452+
`$XDG_CONFIG_HOME/openshell/`. It also looks for a package-manager owned
453+
system config root at `/etc/openshell`, using the same layout as the per-user
454+
config root: `active_gateway` plus `gateways/<name>/metadata.json`. Packages
455+
or runtimes that need a different location can override that root with a
456+
non-empty absolute `OPENSHELL_SYSTEM_GATEWAY_DIR`; empty or relative values
457+
fall back to `/etc/openshell` and emit a warning. The CLI falls back to this
458+
system config when no per-user `metadata.json` exists; malformed user metadata
459+
still shadows the system entry, but stray empty directories do not.
460+
461+
System entries are read-only from the CLI, so `gateway remove` rejects a pure
462+
system entry instead of pretending to delete package-manager owned state.
463+
449464
## Operational Constraints
450465

451466
- Gateway TLS and client certificate distribution are deployment concerns owned

crates/openshell-bootstrap/src/edge_token.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@
77
//! `$XDG_CONFIG_HOME/openshell/gateways/<name>/edge_token`.
88
//! The token is a plain-text JWT string with `0600` permissions.
99
10-
use crate::paths::gateways_dir;
10+
use crate::paths::user_gateway_dir;
1111
use miette::{IntoDiagnostic, Result, WrapErr};
1212
use openshell_core::paths::{ensure_parent_dir_restricted, set_file_owner_only};
1313
use std::path::PathBuf;
1414

1515
/// Path to the stored edge auth token for a gateway.
1616
pub fn edge_token_path(gateway_name: &str) -> Result<PathBuf> {
17-
Ok(gateways_dir()?.join(gateway_name).join("edge_token"))
17+
Ok(user_gateway_dir(gateway_name)?.join("edge_token"))
1818
}
1919

2020
/// Legacy path used before the rename to `edge_token`.
2121
fn legacy_token_path(gateway_name: &str) -> Result<PathBuf> {
22-
Ok(gateways_dir()?.join(gateway_name).join("cf_token"))
22+
Ok(user_gateway_dir(gateway_name)?.join("cf_token"))
2323
}
2424

2525
/// Store an edge authentication token for a gateway.
@@ -158,6 +158,15 @@ mod tests {
158158
});
159159
}
160160

161+
#[test]
162+
fn edge_token_paths_reject_multi_component_gateway_names() {
163+
let tmp = tempfile::tempdir().unwrap();
164+
with_tmp_xdg(tmp.path(), || {
165+
assert!(store_edge_token("../escape", "token").is_err());
166+
assert_eq!(load_edge_token("../escape"), None);
167+
assert!(remove_edge_token("../escape").is_err());
168+
});
169+
}
161170
#[test]
162171
fn load_edge_token_trims_whitespace() {
163172
let tmp = tempfile::tempdir().unwrap();

crates/openshell-bootstrap/src/lib.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub mod oidc_token;
88

99
mod metadata;
1010
pub mod mtls;
11-
pub mod paths;
11+
mod paths;
1212
pub mod pki;
1313

1414
#[cfg(test)]
@@ -21,8 +21,9 @@ use std::sync::Mutex;
2121
pub(crate) static XDG_TEST_LOCK: Mutex<()> = Mutex::new(());
2222

2323
pub use crate::metadata::{
24-
GatewayMetadata, clear_active_gateway, clear_last_sandbox_if_matches,
25-
extract_host_from_ssh_destination, get_gateway_metadata, list_gateways, load_active_gateway,
26-
load_gateway_metadata, load_last_sandbox, remove_gateway_metadata, resolve_ssh_hostname,
27-
save_active_gateway, save_last_sandbox, store_gateway_metadata,
24+
GatewayMetadata, GatewayMetadataSource, ListedGateway, clear_active_gateway,
25+
clear_last_sandbox_if_matches, extract_host_from_ssh_destination, gateway_metadata_source,
26+
get_gateway_metadata, list_gateways, list_gateways_with_source, load_active_gateway,
27+
load_gateway_metadata, load_last_sandbox, load_user_active_gateway, remove_gateway_metadata,
28+
resolve_ssh_hostname, save_active_gateway, save_last_sandbox, store_gateway_metadata,
2829
};

0 commit comments

Comments
 (0)