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...");
|
||||
|
||||
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);
|
||||
|
||||
let mut results = arc_self.results.lock().await;
|
||||
|
@ -537,7 +537,7 @@ impl Deployment {
|
|||
if self.options.upload_keys && !pre_activation_keys.is_empty() {
|
||||
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);
|
||||
|
||||
let mut results = self.results.lock().await;
|
||||
|
@ -561,7 +561,7 @@ impl Deployment {
|
|||
if self.options.upload_keys && !post_activation_keys.is_empty() {
|
||||
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);
|
||||
|
||||
let mut results = self.results.lock().await;
|
||||
|
|
|
@ -348,10 +348,56 @@ let
|
|||
lib.optional (length remainingKeys != 0)
|
||||
"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 {
|
||||
modules = [
|
||||
assertionModule
|
||||
nixpkgsModule
|
||||
keyChownModule
|
||||
deploymentOptions
|
||||
hive.defaults
|
||||
config
|
||||
|
|
|
@ -18,12 +18,13 @@ use crate::util::capture_stream;
|
|||
|
||||
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()
|
||||
.replace("%DESTINATION%", destination.to_str().unwrap())
|
||||
.replace("%USER%", &escape(key.user().into()))
|
||||
.replace("%GROUP%", &escape(key.group().into()))
|
||||
.replace("%PERMISSIONS%", &escape(key.permissions().into()))
|
||||
.replace("%REQUIRE_OWNERSHIP%", if require_ownership { "1" } else { "" })
|
||||
.trim_end_matches('\n').to_string();
|
||||
|
||||
escape(key_script.into())
|
||||
|
|
|
@ -5,11 +5,12 @@ tmp="${destination}.tmp"
|
|||
user=%USER%
|
||||
group=%GROUP%
|
||||
permissions=%PERMISSIONS%
|
||||
require_ownership=%REQUIRE_OWNERSHIP%
|
||||
|
||||
mkdir -p $(dirname "$destination")
|
||||
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"
|
||||
else
|
||||
>&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),
|
||||
}
|
||||
}
|
||||
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 {
|
||||
self.upload_key(&name, &key).await?;
|
||||
self.upload_key(&name, &key, require_ownership).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -109,11 +109,11 @@ impl Host for Local {
|
|||
|
||||
impl Local {
|
||||
/// "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));
|
||||
|
||||
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");
|
||||
|
||||
|
|
|
@ -96,9 +96,13 @@ pub trait Host: Send + Sync + std::fmt::Debug {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
/// 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)
|
||||
}
|
||||
|
||||
|
|
|
@ -53,9 +53,9 @@ impl Host for Ssh {
|
|||
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 {
|
||||
self.upload_key(&name, &key).await?;
|
||||
self.upload_key(&name, &key, require_ownership).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -227,11 +227,11 @@ impl Ssh {
|
|||
}
|
||||
|
||||
/// 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));
|
||||
|
||||
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]);
|
||||
|
||||
|
|
Loading…
Reference in a new issue