Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c7865cd8b | ||
|
|
63fc54edc5 | ||
|
|
154ab6c4ae |
5 changed files with 17 additions and 45 deletions
|
|
@ -26,8 +26,6 @@ swarm-level operator CLI
|
|||
|
||||
* `--authelia-bin <PATH>` — authelia binary used to hash passwords. The argon2 parameters must match the verifier's, so this has to be the *configured* package rather than whatever is on `PATH`
|
||||
* `--users-file <PATH>` — Host-side path of authelia's users database — i.e. the path inside the container, prefixed with the container's root
|
||||
* `--machine <NAME>` — Machine name of the authelia container, for `systemctl -M`
|
||||
* `--unit <UNIT>` — authelia's systemd unit inside that container
|
||||
* `--store <PATH>` — Canonical user store
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -967,7 +967,16 @@ in
|
|||
server.address = "tcp://127.0.0.1:${toString cfg.port}";
|
||||
log.level = "info";
|
||||
|
||||
authentication_backend.file.path = cfg.usersFile;
|
||||
# `watch` is load-bearing, not a convenience: authelia reads
|
||||
# this file once at startup, and `swarm-authelia-bridge` writes
|
||||
# it to create agent identities while being unable to restart
|
||||
# authelia — running unprivileged is the whole reason it may
|
||||
# write the file at all. Without this, an identity it creates is
|
||||
# real on disk and invisible until something unrelated restarts.
|
||||
authentication_backend.file = {
|
||||
path = cfg.usersFile;
|
||||
watch = true;
|
||||
};
|
||||
|
||||
# ⚠️ `one_factor` as the DEFAULT means "any authenticated
|
||||
# user", which is authentication, not authorisation. The
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ let
|
|||
# parameters baked into a hash have to match the verifier's.
|
||||
SWARMCTL_AUTHELIA_BIN = "${autheliaCfg.package}/bin/authelia";
|
||||
SWARMCTL_AUTHELIA_USERS_FILE = autheliaCfg.hostUsersFile;
|
||||
SWARMCTL_AUTHELIA_MACHINE = autheliaCfg.machine;
|
||||
SWARMCTL_AUTHELIA_UNIT = autheliaCfg.unit;
|
||||
};
|
||||
|
||||
natsCfg = config.services.hyperhive.swarm.nats;
|
||||
|
|
|
|||
|
|
@ -69,12 +69,6 @@ struct PathArgs {
|
|||
/// the container, prefixed with the container's root.
|
||||
#[arg(long, value_name = "PATH")]
|
||||
users_file: Option<PathBuf>,
|
||||
/// Machine name of the authelia container, for `systemctl -M`.
|
||||
#[arg(long, value_name = "NAME")]
|
||||
machine: Option<String>,
|
||||
/// authelia's systemd unit inside that container.
|
||||
#[arg(long, value_name = "UNIT")]
|
||||
unit: Option<String>,
|
||||
/// Canonical user store.
|
||||
#[arg(long, value_name = "PATH")]
|
||||
store: Option<PathBuf>,
|
||||
|
|
@ -83,8 +77,6 @@ struct PathArgs {
|
|||
struct Paths {
|
||||
authelia_bin: PathBuf,
|
||||
users_file: PathBuf,
|
||||
machine: String,
|
||||
unit: String,
|
||||
store: PathBuf,
|
||||
}
|
||||
|
||||
|
|
@ -93,8 +85,6 @@ impl PathArgs {
|
|||
Ok(Paths {
|
||||
authelia_bin: path_from(self.authelia_bin, "SWARMCTL_AUTHELIA_BIN")?,
|
||||
users_file: path_from(self.users_file, "SWARMCTL_AUTHELIA_USERS_FILE")?,
|
||||
machine: string_from(self.machine, "SWARMCTL_AUTHELIA_MACHINE")?,
|
||||
unit: string_from(self.unit, "SWARMCTL_AUTHELIA_UNIT")?,
|
||||
store: self
|
||||
.store
|
||||
.or_else(|| std::env::var_os("SWARMCTL_STORE").map(PathBuf::from))
|
||||
|
|
@ -108,11 +98,6 @@ fn path_from(flag: Option<PathBuf>, env: &str) -> Result<PathBuf> {
|
|||
.with_context(|| missing(env))
|
||||
}
|
||||
|
||||
fn string_from(flag: Option<String>, env: &str) -> Result<String> {
|
||||
flag.or_else(|| std::env::var(env).ok())
|
||||
.with_context(|| missing(env))
|
||||
}
|
||||
|
||||
fn missing(env: &str) -> String {
|
||||
format!(
|
||||
"{env} is unset and no flag was given — swarmctl is installed and \
|
||||
|
|
@ -358,7 +343,12 @@ fn publish(paths: &Paths, store: &UserStore) -> Result<()> {
|
|||
write_atomic(&paths.store, &format!("{store_json}\n"))?;
|
||||
write_atomic(&paths.users_file, &rendered)?;
|
||||
|
||||
restart_authelia(&paths.machine, &paths.unit)
|
||||
// No restart: authelia watches this file
|
||||
// (`authentication_backend.file.watch`). Deliberately not `swarmctl`'s job
|
||||
// — `swarm-authelia-bridge` writes the same file and *cannot* restart
|
||||
// anything, since running unprivileged is the whole reason it may write
|
||||
// it. A reload that depends on which process wrote is not a reload.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load the canonical store, or start an empty one if this deployment has
|
||||
|
|
@ -456,29 +446,6 @@ fn parse_field(stdout: &str, marker: &str) -> Option<String> {
|
|||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
/// authelia re-reads its file backend at startup, so a users change needs
|
||||
/// a restart.
|
||||
///
|
||||
/// The file watcher (`authentication_backend.file.watch`) would remove
|
||||
/// this step entirely, and is deliberately not relied on: it could not be
|
||||
/// verified against the pinned build, and it carries two unknowns —
|
||||
/// whether the watch survives the `rename(2)` used above, and whether it
|
||||
/// can observe a partially written file. An explicit restart assumes
|
||||
/// nothing.
|
||||
fn restart_authelia(machine: &str, unit: &str) -> Result<()> {
|
||||
let status = Command::new("systemctl")
|
||||
.args(["-M", machine, "restart", unit])
|
||||
.status()
|
||||
.context("running systemctl")?;
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"restarting {unit} in {machine} failed ({status}); the users file is \
|
||||
already written, so re-running the restart by hand completes the change"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replace `path`'s contents atomically, preserving the existing owner
|
||||
/// and mode.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -495,7 +495,7 @@ mod tests {
|
|||
..UserUpdate::default()
|
||||
},
|
||||
)
|
||||
.expect_err("a no-op must not restart authelia");
|
||||
.expect_err("a no-op must not rewrite the user database");
|
||||
assert!(err.to_string().contains("nothing to change"), "{err}");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue