feat(edh-keygen): support user and oneshot service actions
Validate configured systemd units before applying service-specific DH parameter synchronization. Synchronize per-service DH parameters before restarting active units. Query system-wide and per-user systemd managers with LoadState, ActiveState, SubState, and Type. Restart active units, start inactive or failed Type=oneshot units, and leave inactive or failed non-oneshot units without a service action. Skip missing units, unavailable user managers, and invalid or incomplete service definitions without creating misleading synchronization messages. Add robust error handling for DH parameter generation, permission changes, directory creation, global synchronization, and temporary file cleanup.
This commit is contained in:
+314
-53
@@ -84,7 +84,10 @@ done < "$my_service_conf"
|
||||
|
||||
# Create path for 'tmp' in '/etc/pki/tls'
|
||||
if [ ! -d "$tls_tmp_path" ]; then
|
||||
mkdir -p "$tls_tmp_path"
|
||||
if ! mkdir -p "$tls_tmp_path"; then
|
||||
echo "Failed to create temporary directory $tls_tmp_path." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
umask 022
|
||||
@@ -98,17 +101,165 @@ fi
|
||||
# Generate DH params
|
||||
for bits in $key_sizes; do
|
||||
echo "Generating DH parameters for $bits bits..."
|
||||
openssl dhparam -out "$tls_tmp_path/dh_${bits}.pem" "$bits"
|
||||
|
||||
if ! openssl dhparam -out "$tls_tmp_path/dh_${bits}.pem" "$bits"; then
|
||||
echo "Failed to generate ${bits}-bit DH parameters." >&2
|
||||
rm -rf "$tls_tmp_path"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Set permissions
|
||||
find "$tls_tmp_path" -type f -name "*.pem" -exec chmod 644 {} \;
|
||||
if ! find "$tls_tmp_path" -type f -name '*.pem' -exec chmod 644 {} \;; then
|
||||
echo "Failed to set permissions on generated DH parameters." >&2
|
||||
rm -rf "$tls_tmp_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sync certs from temporary to private folder
|
||||
rsync -a "$tls_tmp_path/"*.pem "$tls_private_path"
|
||||
# Synchronize DH parameters from the temporary to the private directory.
|
||||
if [ ! -d "$tls_private_path" ]; then
|
||||
if ! mkdir -p "$tls_private_path"; then
|
||||
echo "Failed to create private key directory $tls_private_path." >&2
|
||||
rm -rf "$tls_tmp_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
if ! chown root:root "$tls_private_path"; then
|
||||
echo "Failed to set ownership on private key directory $tls_private_path." >&2
|
||||
rm -rf "$tls_tmp_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! chmod 750 "$tls_private_path"; then
|
||||
echo "Failed to set permissions on private key directory $tls_private_path." >&2
|
||||
rm -rf "$tls_tmp_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for dh_file in "$tls_tmp_path"/dh_*.pem; do
|
||||
if [ ! -f "$dh_file" ]; then
|
||||
echo "No generated DH parameter files found in $tls_tmp_path." >&2
|
||||
rm -rf "$tls_tmp_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! rsync -a "$dh_file" "$tls_private_path"; then
|
||||
echo "Failed to synchronize $dh_file to $tls_private_path." >&2
|
||||
rm -rf "$tls_tmp_path"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Delete the temporary files
|
||||
rm -rf "$tls_tmp_path"
|
||||
if ! rm -rf "$tls_tmp_path"; then
|
||||
echo "Failed to remove temporary directory $tls_tmp_path." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Query systemd unit properties for system-wide and per-user service units.
|
||||
#
|
||||
# get_service_property <property> <service> <owner> <uid>
|
||||
#
|
||||
# Prints the value of the requested systemd property to standard output.
|
||||
# Root-owned services are queried through the system manager. Services owned
|
||||
# by another user are queried through that user's systemd --user manager.
|
||||
# The caller must provide the numeric UID for per-user units so that the
|
||||
# required XDG_RUNTIME_DIR can be set explicitly.
|
||||
#
|
||||
# get_service_state <service> <owner>
|
||||
#
|
||||
# Resolves and validates the systemd state required for service handling.
|
||||
# On success, it sets the following global variables for the current service:
|
||||
#
|
||||
# load_state Unit load state, e.g. loaded or not-found.
|
||||
# active_state High-level unit state, e.g. active, inactive, or failed.
|
||||
# sub_state Unit type-specific detailed state, e.g. running, dead,
|
||||
# exited, or failed.
|
||||
# service_type Service type, e.g. simple, notify, forking, or oneshot.
|
||||
# service_uid Numeric UID for per-user units; empty for root units.
|
||||
#
|
||||
# A LoadState of "not-found" is returned as a successful lookup so the caller
|
||||
# can report a missing configured unit explicitly. Other query failures return
|
||||
# a non-zero status.
|
||||
get_service_property() {
|
||||
edh_property_name=$1
|
||||
edh_service_name=$2
|
||||
edh_service_owner=$3
|
||||
edh_service_uid=$4
|
||||
|
||||
if [ "$edh_service_owner" = "root" ]; then
|
||||
/usr/bin/systemctl show \
|
||||
--property="$edh_property_name" \
|
||||
--value \
|
||||
"$edh_service_name.service" 2>/dev/null
|
||||
else
|
||||
sudo -u "$edh_service_owner" \
|
||||
XDG_RUNTIME_DIR="/run/user/$edh_service_uid" \
|
||||
/usr/bin/systemctl --user show \
|
||||
--property="$edh_property_name" \
|
||||
--value \
|
||||
"$edh_service_name.service" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Return 0 for a valid unit state or for LoadState=not-found.
|
||||
# Return non-zero if the owner does not exist or systemd cannot be queried.
|
||||
# Result values are exported through the global variables documented above.
|
||||
get_service_state() {
|
||||
edh_state_service=$1
|
||||
edh_state_owner=$2
|
||||
|
||||
load_state=
|
||||
active_state=
|
||||
sub_state=
|
||||
service_type=
|
||||
service_uid=
|
||||
|
||||
if [ "$edh_state_owner" != "root" ]; then
|
||||
service_uid=$(id -u "$edh_state_owner" 2>/dev/null)
|
||||
if [ -z "$service_uid" ]; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
load_state=$(get_service_property \
|
||||
LoadState \
|
||||
"$edh_state_service" \
|
||||
"$edh_state_owner" \
|
||||
"$service_uid")
|
||||
|
||||
if [ -z "$load_state" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$load_state" = "not-found" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
active_state=$(get_service_property \
|
||||
ActiveState \
|
||||
"$edh_state_service" \
|
||||
"$edh_state_owner" \
|
||||
"$service_uid") || return 1
|
||||
|
||||
sub_state=$(get_service_property \
|
||||
SubState \
|
||||
"$edh_state_service" \
|
||||
"$edh_state_owner" \
|
||||
"$service_uid") || return 1
|
||||
|
||||
service_type=$(get_service_property \
|
||||
Type \
|
||||
"$edh_state_service" \
|
||||
"$edh_state_owner" \
|
||||
"$service_uid") || return 1
|
||||
|
||||
[ -n "$active_state" ] &&
|
||||
[ -n "$sub_state" ] &&
|
||||
[ -n "$service_type" ]
|
||||
}
|
||||
|
||||
# Read and process service list
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
@@ -118,76 +269,186 @@ while IFS= read -r line || [ -n "$line" ]; do
|
||||
esac
|
||||
|
||||
# Extract service, owner and sync parameters
|
||||
service=$(printf "%s" "$line" | awk -F: '{print $1}')
|
||||
owner=$(printf "%s" "$line" | awk -F: '{print $2}')
|
||||
key_size=$(printf "%s" "$line" | awk -F: '{print $3}')
|
||||
sync_path=$(printf "%s" "$line" | awk -F: '{print $4}')
|
||||
user_group=$(printf "%s" "$line" | awk -F: '{print $5}')
|
||||
permissions=$(printf "%s" "$line" | awk -F: '{print $6}')
|
||||
service=$(printf '%s\n' "$line" | awk -F: '{print $1}')
|
||||
owner=$(printf '%s\n' "$line" | awk -F: '{print $2}')
|
||||
key_size=$(printf '%s\n' "$line" | awk -F: '{print $3}')
|
||||
sync_path=$(printf '%s\n' "$line" | awk -F: '{print $4}')
|
||||
user_group=$(printf '%s\n' "$line" | awk -F: '{print $5}')
|
||||
permissions=$(printf '%s\n' "$line" | awk -F: '{print $6}')
|
||||
|
||||
# Convert legacy user.group notation to user:group
|
||||
ug_user=${user_group%%.*}
|
||||
ug_group=${user_group#*.}
|
||||
user_group=$ug_user:$ug_group
|
||||
# Validate required base fields.
|
||||
if [ -z "$service" ] || [ -z "$owner" ]; then
|
||||
echo "Invalid service definition: $line" >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "$service.service (owner: $owner)..."
|
||||
|
||||
# Check service status (must run as root)
|
||||
if [ "$owner" = "root" ]; then
|
||||
mySubState=$(systemctl show -p SubState --value "$service.service" 2>/dev/null)
|
||||
else
|
||||
uid=$(id -u "$owner" 2>/dev/null)
|
||||
if [ -n "$uid" ]; then
|
||||
mySubState=$(sudo -u "$owner" XDG_RUNTIME_DIR="/run/user/$uid" /usr/bin/systemctl --user show -p SubState --value "$service.service" 2>/dev/null)
|
||||
if ! get_service_state "$service" "$owner"; then
|
||||
if [ "$owner" != "root" ] && [ -z "$service_uid" ]; then
|
||||
echo "User $owner not found. Skipping $service." >&2
|
||||
else
|
||||
echo "User $owner not found! Skipping $service." 1>&2
|
||||
continue
|
||||
echo "Could not query the systemd state of $service.service. Skipping restart and DH key sync." >&2
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$mySubState" = "running" ]; then
|
||||
echo "$service.service is running, restarting as $owner..."
|
||||
if [ "$load_state" = "not-found" ]; then
|
||||
if [ "$owner" = "root" ]; then
|
||||
/usr/bin/systemctl restart "$service.service"
|
||||
echo "$service.service does not exist. Skipping restart and DH key sync." >&2
|
||||
else
|
||||
sudo -u "$owner" XDG_RUNTIME_DIR="/run/user/$uid" /usr/bin/systemctl --user restart "$service"
|
||||
echo "$service.service does not exist for user $owner. Skipping restart and DH key sync." >&2
|
||||
fi
|
||||
echo "$service.service restarted."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Handle DH key sync if parameters exist
|
||||
if [ -n "$key_size" ] && [ -n "$sync_path" ] && [ -n "$user_group" ] && [ -n "$permissions" ]; then
|
||||
if ! echo "$user_group" | grep -q "."; then
|
||||
echo "Error: user_group must be 'user.group' for $service. Skipping sync." 1>&2
|
||||
continue
|
||||
fi
|
||||
# Decide which action to apply after a successful optional DH key sync.
|
||||
service_action=none
|
||||
|
||||
dh_file="$tls_private_path/dh_${key_size}.pem"
|
||||
if [ ! -f "$dh_file" ]; then
|
||||
echo "DH key $dh_file not found. Skipping sync for $service." 1>&2
|
||||
case "$active_state" in
|
||||
active)
|
||||
service_action=restart
|
||||
;;
|
||||
inactive)
|
||||
if [ "$service_type" = "oneshot" ]; then
|
||||
service_action=start
|
||||
echo "$service.service is inactive (SubState: $sub_state, Type: oneshot). Syncing DH key and starting unit."
|
||||
else
|
||||
echo "$service.service is inactive (SubState: $sub_state). DH parameters are updated; skipping service action."
|
||||
fi
|
||||
;;
|
||||
failed)
|
||||
if [ "$service_type" = "oneshot" ]; then
|
||||
service_action=start
|
||||
echo "$service.service is failed (SubState: $sub_state, Type: oneshot). Syncing DH key and starting unit." >&2
|
||||
else
|
||||
echo "$service.service is failed (SubState: $sub_state). Syncing DH key without restart." >&2
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "$service.service is $active_state (SubState: $sub_state). Skipping restart and DH key sync." >&2
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create directory if missing
|
||||
if [ ! -d "$sync_path" ]; then
|
||||
mkdir -p "$sync_path" || {
|
||||
echo "Failed to create $sync_path for $service." 1>&2
|
||||
# Synchronize DH parameters when a complete extended configuration is present.
|
||||
if [ -z "$key_size" ] && [ -z "$sync_path" ] && [ -z "$user_group" ] && [ -z "$permissions" ]; then
|
||||
:
|
||||
elif [ -z "$key_size" ] || [ -z "$sync_path" ] || [ -z "$user_group" ] || [ -z "$permissions" ]; then
|
||||
echo "Incomplete DH key sync configuration for $service. Skipping sync and service action." >&2
|
||||
continue
|
||||
else
|
||||
# The configuration format uses user.group. Convert it to user:group for chown.
|
||||
case "$user_group" in
|
||||
*.*)
|
||||
ug_user=${user_group%%.*}
|
||||
ug_group=${user_group#*.}
|
||||
;;
|
||||
*)
|
||||
echo "Invalid owner '$user_group' for $service; expected user.group. Skipping sync." >&2
|
||||
continue
|
||||
}
|
||||
chown "$user_group" "$sync_path"
|
||||
chmod 750 "$sync_path"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$ug_user" ] || [ -z "$ug_group" ]; then
|
||||
echo "Invalid owner '$user_group' for $service; expected user.group. Skipping sync." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
dh_file="${tls_private_path%/}/dh_${key_size}.pem"
|
||||
target_file="${sync_path%/}/dh_${key_size}.pem"
|
||||
|
||||
if [ ! -f "$dh_file" ]; then
|
||||
echo "DH key $dh_file not found. Skipping sync for $service." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
# Create the destination directory if it does not exist.
|
||||
if [ ! -d "$sync_path" ]; then
|
||||
if ! mkdir -p "$sync_path"; then
|
||||
echo "Failed to create $sync_path for $service." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! chown "$ug_user:$ug_group" "$sync_path"; then
|
||||
echo "Failed to set ownership on $sync_path for $service." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! chmod 750 "$sync_path"; then
|
||||
echo "Failed to set permissions on $sync_path for $service." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Created directory $sync_path for $service."
|
||||
fi
|
||||
|
||||
# Copy DH key and set permissions
|
||||
cp "$dh_file" "$sync_path/" || {
|
||||
echo "Failed to copy DH key to $sync_path for $service." 1>&2
|
||||
if ! cp "$dh_file" "$target_file"; then
|
||||
echo "Failed to copy DH key to $target_file for $service." >&2
|
||||
continue
|
||||
}
|
||||
chown "$user_group" "$sync_path/dh_${key_size}.pem"
|
||||
chmod "$permissions" "$sync_path/dh_${key_size}.pem"
|
||||
fi
|
||||
|
||||
if ! chown "$ug_user:$ug_group" "$target_file"; then
|
||||
echo "Failed to set ownership on $target_file for $service." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! chmod "$permissions" "$target_file"; then
|
||||
echo "Failed to set permissions on $target_file for $service." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Synced DH key (${key_size}-bit) to $sync_path for $service."
|
||||
fi
|
||||
|
||||
# Apply the selected action after the optional DH key synchronization.
|
||||
case "$service_action" in
|
||||
restart)
|
||||
echo "$service.service is active (SubState: $sub_state), restarting as $owner..."
|
||||
|
||||
if [ "$owner" = "root" ]; then
|
||||
if ! /usr/bin/systemctl restart "$service.service"; then
|
||||
echo "Failed to restart $service.service." >&2
|
||||
continue
|
||||
fi
|
||||
else
|
||||
if ! sudo -u "$owner" \
|
||||
XDG_RUNTIME_DIR="/run/user/$service_uid" \
|
||||
/usr/bin/systemctl --user restart "$service.service"; then
|
||||
echo "Failed to restart $service.service as $owner." >&2
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$service.service restarted."
|
||||
;;
|
||||
start)
|
||||
echo "$service.service is $active_state oneshot, starting as $owner..."
|
||||
|
||||
if [ "$owner" = "root" ]; then
|
||||
if ! /usr/bin/systemctl start "$service.service"; then
|
||||
echo "Failed to start $service.service." >&2
|
||||
continue
|
||||
fi
|
||||
else
|
||||
if ! sudo -u "$owner" \
|
||||
XDG_RUNTIME_DIR="/run/user/$service_uid" \
|
||||
/usr/bin/systemctl --user start "$service.service"; then
|
||||
echo "Failed to start $service.service as $owner." >&2
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$service.service started."
|
||||
;;
|
||||
none)
|
||||
:
|
||||
;;
|
||||
*)
|
||||
echo "Internal error: unsupported action '$service_action' for $service." >&2
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
done < "$my_service_conf"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user