Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions ak-sysd/src/cfg/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,17 +212,41 @@ impl DomainManager {
refresh_interval
}

/// First enabled domain — mirrors Go's `dom[0]` shortcut for
/// single-tenant components (ping, auth, directory, device). Do not
/// First enabled domain that has a token — mirrors Go's `dom[0]` shortcut
/// for single-tenant components (ping, auth, directory, device). Do not
/// invent smarter "current domain" selection here.
///
/// An MDM-managed domain wins when there is one: it carries the connector
/// and device group the organisation assigned, whereas a hand-run
/// `domains join` may point somewhere else entirely. Policy beats ad hoc.
///
/// The token check matters because `load_managed` adds an MDM-managed
/// domain alongside any user-enrolled ones. If its enrollment produced no
/// token, it is still `enabled`, and returning it means every request goes
/// out as `Bearer+agent ` and comes back 403 "Authentication credentials
/// were not provided" — while a perfectly good domain sits further down the
/// list.
///
/// Note this still only ever returns one domain, while the storage layer
/// happily holds several. Every auth path (`ping`, `auth/*`, `session`,
/// `directory`) calls this and silently ignores the rest; only `device`
/// check-in and `healthcheck_all` fan out. Making multi-domain genuinely
/// work means binding a domain to each of those call sites.
pub async fn active(&self) -> Result<Arc<LoadedDomain>> {
self.domains
.read()
.await
let domains = self.domains.read().await;
let usable = |d: &&Arc<LoadedDomain>| d.cfg.enabled && !d.cfg.token.is_empty();
let selected = domains
.iter()
.find(|d| d.cfg.enabled)
.find(|d| usable(d) && d.cfg.managed)
.or_else(|| domains.iter().find(usable))
.cloned()
.ok_or_else(|| eyre!("no enabled domain configured"))
.ok_or_else(|| eyre!("no enabled domain with a token configured"))?;
tracing::debug!(
domain = %selected.cfg.domain,
managed = selected.cfg.managed,
"selected active domain"
);
Ok(selected)
}

pub async fn save_domain(&self, cfg: DomainConfig) -> Result<()> {
Expand Down Expand Up @@ -370,11 +394,25 @@ impl DomainManager {
/// Loads (or re-enrolls, or removes) the MDM-managed domain. See
/// `cfg::managed` for the platform-specific config source.
pub async fn load_managed(&self) -> Result<()> {
const MANAGED_DOMAIN_NAME: &str = "ak-mdm-managed";

let Some(managed) = crate::cfg::managed::load_managed_config()? else {
// No managed config means the profile is gone, so a managed domain
// left behind is stale policy. `load_all` reloads every `*.json`
// regardless of whether the config that created it still exists, so
// without this the domain lingers forever — never refreshed, never
// revoked, and still competing to be the active one.
if self
.domains()
.await
.iter()
.any(|d| d.cfg.domain == MANAGED_DOMAIN_NAME)
{
tracing::info!("managed config absent, removing managed domain");
self.delete_domain(MANAGED_DOMAIN_NAME).await?;
}
return Ok(());
};

const MANAGED_DOMAIN_NAME: &str = "ak-mdm-managed";
let existing = self
.domains()
.await
Expand Down
Loading