sdw_notify/Notify.py (64 lines of code) (raw):
"""
Utility library for warning the user that security updates have not been applied
in some time.
"""
import os
from datetime import datetime
from sdw_util import Util
sdlog = Util.get_logger(module=__name__)
# The directory where status files and logs are stored
BASE_DIRECTORY = Util.BASE_DIRECTORY
# The file and format that contains the timestamp of the last successful update
LAST_UPDATED_FILE = os.path.join(BASE_DIRECTORY, "sdw-last-updated")
LAST_UPDATED_FORMAT = "%Y-%m-%d %H:%M:%S"
# The lockfile basename used to ensure this script can only be executed once.
# Default path for lockfiles is specified in sdw_util
LOCK_FILE = "sdw-notify.lock"
# Log file name, base directories defined in sdw_util
LOG_FILE = "sdw-notify.log"
# Process names that should not be running while this script runs. We do not
# want to encourage running the updater during provisioning or system updates.
# Caution is advised in expanding this list; a more precise detection method
# is generally preferable.
CONFLICTING_PROCESSES = ["qubesctl", "make"]
# The maximum uptime this script should permit (specified in seconds) before
# showing a warning. This is to avoid situations where the user boots the
# computer after several days and immediately sees a warning.
UPTIME_GRACE_PERIOD = 1800 # 30 minutes
# The amount of time without updates (specified in seconds) which this script
# should permit before showing a warning to the user
WARNING_THRESHOLD = 432000 # 5 days
def is_update_check_necessary():
"""
Perform a series of checks to determine if a security warning should be
shown to the user, reminding them to check for available software updates
using the preflight updater.
"""
last_updated_file_exists = os.path.exists(LAST_UPDATED_FILE)
# For consistent logging
grace_period_hours = UPTIME_GRACE_PERIOD / 60 / 60
warning_threshold_hours = WARNING_THRESHOLD / 60 / 60
# Get timestamp from last update (if it exists)
if last_updated_file_exists:
with open(LAST_UPDATED_FILE) as f:
last_update_time = f.readline().splitlines()[0]
try:
last_update_time = datetime.strptime(last_update_time, LAST_UPDATED_FORMAT)
except ValueError:
sdlog.error(
f"Data in {LAST_UPDATED_FILE} not in the expected format. "
f"Expecting a timestamp in format '{LAST_UPDATED_FORMAT}'. "
"Showing security warning."
)
return True
now = datetime.now()
updated_seconds_ago = (now - last_update_time).total_seconds()
updated_hours_ago = updated_seconds_ago / 60 / 60
uptime_seconds = get_uptime_seconds()
uptime_hours = uptime_seconds / 60 / 60
if not last_updated_file_exists:
sdlog.warning(
f"Timestamp file '{LAST_UPDATED_FILE}' does not exist. "
"Updater may never have run. Showing security warning."
)
return True
if updated_seconds_ago > WARNING_THRESHOLD:
if uptime_seconds > UPTIME_GRACE_PERIOD:
sdlog.warning(
f"Last successful update ({updated_hours_ago:.1f} hours ago) is above "
f"warning threshold ({warning_threshold_hours:.1f} hours). Uptime grace period of "
f"{grace_period_hours:.1f} hours has elapsed (uptime: {uptime_hours:.1f} hours). "
"Showing security warning."
)
return True
sdlog.info(
f"Last successful update ({updated_hours_ago:.1f} hours ago) is above "
f"warning threshold ({warning_threshold_hours:.1f} hours). Uptime grace period "
f"of {grace_period_hours:.1f} hours has not elapsed yet (uptime: {uptime_hours:.1f} "
"hours). Exiting without warning."
)
return False
sdlog.info(
f"Last successful update ({updated_hours_ago:.1f} hours ago) "
f"is below the warning threshold ({warning_threshold_hours:.1f} hours). "
"Exiting without warning."
)
return False
def get_uptime_seconds():
# Obtain current uptime
with open("/proc/uptime") as f:
return float(f.readline().split()[0])