forked from DGNum/colmena
Ensure key ownerships are set correctly
Depending on when keys are uploaded (`deployment.keys.<name>.uploadAt`): `pre-activation`: We set the ownerships in the uploader script opportunistically and continue if the user/group does not exist. Then, in the activation script, we set the ownerships of all pre-activation keys. `post-activation`: We set the ownerships in the uploader script and fail if the user/group does not exist. The ownerships will be correct regardless of which mode is in use. Fixes #23. Also a more complete solution to #10.
This commit is contained in:
parent
e98a66bc2e
commit
7b69946d98
7 changed files with 67 additions and 15 deletions
|
@ -284,7 +284,7 @@ impl Deployment {
|
||||||
|
|
||||||
task.log("Uploading keys...");
|
task.log("Uploading keys...");
|
||||||
|
|
||||||
if let Err(e) = target.host.upload_keys(&target.config.keys).await {
|
if let Err(e) = target.host.upload_keys(&target.config.keys, true).await {
|
||||||
task.failure_err(&e);
|
task.failure_err(&e);
|
||||||
|
|
||||||
let mut results = arc_self.results.lock().await;
|
let mut results = arc_self.results.lock().await;
|
||||||
|
@ -537,7 +537,7 @@ impl Deployment {
|
||||||
if self.options.upload_keys && !pre_activation_keys.is_empty() {
|
if self.options.upload_keys && !pre_activation_keys.is_empty() {
|
||||||
bar.log("Uploading keys...");
|
bar.log("Uploading keys...");
|
||||||
|
|
||||||
if let Err(e) = target.host.upload_keys(&pre_activation_keys).await {
|
if let Err(e) = target.host.upload_keys(&pre_activation_keys, false).await {
|
||||||
bar.failure_err(&e);
|
bar.failure_err(&e);
|
||||||
|
|
||||||
let mut results = self.results.lock().await;
|
let mut results = self.results.lock().await;
|
||||||
|
@ -561,7 +561,7 @@ impl Deployment {
|
||||||
if self.options.upload_keys && !post_activation_keys.is_empty() {
|
if self.options.upload_keys && !post_activation_keys.is_empty() {
|
||||||
bar.log("Uploading keys (post-activation)...");
|
bar.log("Uploading keys (post-activation)...");
|
||||||
|
|
||||||
if let Err(e) = target.host.upload_keys(&post_activation_keys).await {
|
if let Err(e) = target.host.upload_keys(&post_activation_keys, true).await {
|
||||||
bar.failure_err(&e);
|
bar.failure_err(&e);
|
||||||
|
|
||||||
let mut results = self.results.lock().await;
|
let mut results = self.results.lock().await;
|
||||||
|
|
|
@ -348,10 +348,56 @@ let
|
||||||
lib.optional (length remainingKeys != 0)
|
lib.optional (length remainingKeys != 0)
|
||||||
"The following Nixpkgs configuration keys set in meta.nixpkgs will be ignored: ${toString remainingKeys}";
|
"The following Nixpkgs configuration keys set in meta.nixpkgs will be ignored: ${toString remainingKeys}";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Change the ownership of all keys uploaded pre-activation
|
||||||
|
#
|
||||||
|
# This is built as part of the system profile.
|
||||||
|
# We must be careful not to access `text` / `keyCommand` / `keyFile` here
|
||||||
|
keyChownModule = { lib, config, ... }: let
|
||||||
|
preActivationKeys = lib.filterAttrs (name: key: key.uploadAt == "pre-activation") config.deployment.keys;
|
||||||
|
scriptDeps = if config.system.activationScripts ? groups then [ "groups" ] else [ "users" ];
|
||||||
|
|
||||||
|
commands = lib.mapAttrsToList (name: key: let
|
||||||
|
keyPath = "${key.destDir}/${name}";
|
||||||
|
in ''
|
||||||
|
if [ -f "${keyPath}" ]; then
|
||||||
|
if ! chown ${key.user}:${key.group} "${keyPath}"; then
|
||||||
|
# Error should be visible in stderr
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
>&2 echo "Key ${keyPath} does not exist. Skipping chown."
|
||||||
|
fi
|
||||||
|
'') preActivationKeys;
|
||||||
|
|
||||||
|
script = lib.stringAfter scriptDeps ''
|
||||||
|
# This script is injected by Colmena to change the ownerships
|
||||||
|
# of keys (`deployment.keys`) deployed before system activation.
|
||||||
|
|
||||||
|
>&2 echo "setting up key ownerships..."
|
||||||
|
|
||||||
|
# We set the ownership of as many keys as possible before failing
|
||||||
|
failed=
|
||||||
|
|
||||||
|
${concatStringsSep "\n" commands}
|
||||||
|
|
||||||
|
if [ -n "$failed" ]; then
|
||||||
|
>&2 echo "Failed to set the ownership of some keys."
|
||||||
|
|
||||||
|
# The activation script has a trap to handle failed
|
||||||
|
# commands and print out various debug information.
|
||||||
|
# Let's trigger that instead of `exit 1`.
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
system.activationScripts.colmena-chown-keys = lib.mkIf (length commands != 0) script;
|
||||||
|
};
|
||||||
in evalConfig {
|
in evalConfig {
|
||||||
modules = [
|
modules = [
|
||||||
assertionModule
|
assertionModule
|
||||||
nixpkgsModule
|
nixpkgsModule
|
||||||
|
keyChownModule
|
||||||
deploymentOptions
|
deploymentOptions
|
||||||
hive.defaults
|
hive.defaults
|
||||||
config
|
config
|
||||||
|
|
|
@ -18,12 +18,13 @@ use crate::util::capture_stream;
|
||||||
|
|
||||||
const SCRIPT_TEMPLATE: &'static str = include_str!("./key_uploader.template.sh");
|
const SCRIPT_TEMPLATE: &'static str = include_str!("./key_uploader.template.sh");
|
||||||
|
|
||||||
pub fn generate_script<'a>(key: &'a Key, destination: &'a Path) -> Cow<'a, str> {
|
pub fn generate_script<'a>(key: &'a Key, destination: &'a Path, require_ownership: bool) -> Cow<'a, str> {
|
||||||
let key_script = SCRIPT_TEMPLATE.to_string()
|
let key_script = SCRIPT_TEMPLATE.to_string()
|
||||||
.replace("%DESTINATION%", destination.to_str().unwrap())
|
.replace("%DESTINATION%", destination.to_str().unwrap())
|
||||||
.replace("%USER%", &escape(key.user().into()))
|
.replace("%USER%", &escape(key.user().into()))
|
||||||
.replace("%GROUP%", &escape(key.group().into()))
|
.replace("%GROUP%", &escape(key.group().into()))
|
||||||
.replace("%PERMISSIONS%", &escape(key.permissions().into()))
|
.replace("%PERMISSIONS%", &escape(key.permissions().into()))
|
||||||
|
.replace("%REQUIRE_OWNERSHIP%", if require_ownership { "1" } else { "" })
|
||||||
.trim_end_matches('\n').to_string();
|
.trim_end_matches('\n').to_string();
|
||||||
|
|
||||||
escape(key_script.into())
|
escape(key_script.into())
|
||||||
|
|
|
@ -5,11 +5,12 @@ tmp="${destination}.tmp"
|
||||||
user=%USER%
|
user=%USER%
|
||||||
group=%GROUP%
|
group=%GROUP%
|
||||||
permissions=%PERMISSIONS%
|
permissions=%PERMISSIONS%
|
||||||
|
require_ownership=%REQUIRE_OWNERSHIP%
|
||||||
|
|
||||||
mkdir -p $(dirname "$destination")
|
mkdir -p $(dirname "$destination")
|
||||||
touch "$tmp"
|
touch "$tmp"
|
||||||
|
|
||||||
if getent passwd "$user" >/dev/null && getent group "$group" >/dev/null; then
|
if [ -n "$require_ownership" ] || getent passwd "$user" >/dev/null && getent group "$group" >/dev/null; then
|
||||||
chown "$user:$group" "$tmp"
|
chown "$user:$group" "$tmp"
|
||||||
else
|
else
|
||||||
>&2 echo "User $user and/or group $group do not exist. Skipping chown."
|
>&2 echo "User $user and/or group $group do not exist. Skipping chown."
|
||||||
|
|
|
@ -61,9 +61,9 @@ impl Host for Local {
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn upload_keys(&mut self, keys: &HashMap<String, Key>) -> NixResult<()> {
|
async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> NixResult<()> {
|
||||||
for (name, key) in keys {
|
for (name, key) in keys {
|
||||||
self.upload_key(&name, &key).await?;
|
self.upload_key(&name, &key, require_ownership).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -109,11 +109,11 @@ impl Host for Local {
|
||||||
|
|
||||||
impl Local {
|
impl Local {
|
||||||
/// "Uploads" a single key.
|
/// "Uploads" a single key.
|
||||||
async fn upload_key(&mut self, name: &str, key: &Key) -> NixResult<()> {
|
async fn upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> NixResult<()> {
|
||||||
self.progress_bar.log(&format!("Deploying key {}", name));
|
self.progress_bar.log(&format!("Deploying key {}", name));
|
||||||
|
|
||||||
let dest_path = key.dest_dir().join(name);
|
let dest_path = key.dest_dir().join(name);
|
||||||
let key_script = format!("'{}'", key_uploader::generate_script(key, &dest_path));
|
let key_script = format!("'{}'", key_uploader::generate_script(key, &dest_path, require_ownership));
|
||||||
|
|
||||||
let mut command = Command::new("sh");
|
let mut command = Command::new("sh");
|
||||||
|
|
||||||
|
|
|
@ -96,9 +96,13 @@ pub trait Host: Send + Sync + std::fmt::Debug {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
/// Uploads a set of keys to the host.
|
/// Uploads a set of keys to the host.
|
||||||
async fn upload_keys(&mut self, keys: &HashMap<String, Key>) -> NixResult<()> {
|
///
|
||||||
|
/// If `require_ownership` is false, then the ownership of a key
|
||||||
|
/// will not be applied if the specified user/group does not
|
||||||
|
/// exist.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> NixResult<()> {
|
||||||
Err(NixError::Unsupported)
|
Err(NixError::Unsupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,9 @@ impl Host for Ssh {
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn upload_keys(&mut self, keys: &HashMap<String, Key>) -> NixResult<()> {
|
async fn upload_keys(&mut self, keys: &HashMap<String, Key>, require_ownership: bool) -> NixResult<()> {
|
||||||
for (name, key) in keys {
|
for (name, key) in keys {
|
||||||
self.upload_key(&name, &key).await?;
|
self.upload_key(&name, &key, require_ownership).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -227,11 +227,11 @@ impl Ssh {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uploads a single key.
|
/// Uploads a single key.
|
||||||
async fn upload_key(&mut self, name: &str, key: &Key) -> NixResult<()> {
|
async fn upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> NixResult<()> {
|
||||||
self.progress_bar.log(&format!("Deploying key {}", name));
|
self.progress_bar.log(&format!("Deploying key {}", name));
|
||||||
|
|
||||||
let dest_path = key.dest_dir().join(name);
|
let dest_path = key.dest_dir().join(name);
|
||||||
let key_script = key_uploader::generate_script(key, &dest_path);
|
let key_script = key_uploader::generate_script(key, &dest_path, require_ownership);
|
||||||
|
|
||||||
let mut command = self.ssh(&["sh", "-c", &key_script]);
|
let mut command = self.ssh(&["sh", "-c", &key_script]);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue