diff --git a/ubuntu-security-status b/ubuntu-security-status index 92251924..ee5994ff 100755 --- a/ubuntu-security-status +++ b/ubuntu-security-status @@ -3,6 +3,7 @@ import apt import argparse import distro_info +import json import os import sys import gettext @@ -17,6 +18,7 @@ from urllib.request import urlopen # TODO make DEBUG an environmental variable DEBUG = False +UA_STATUS_FILE = "/var/lib/ubuntu-advantage/status.json" class PatchStats: @@ -81,32 +83,46 @@ def whats_in_esm(url): return pkgs -def livepatch_is_enabled(): - """ Check to see if livepatch is enabled on the system""" +def get_ua_status(): + """Return dict of active ua status information from ubuntu-advantage-tools + + Prefer to obtain status information from cache on disk to avoid costly + roundtrips to contracts.canonical.com to check on available services. + + Fallback to call ua status --format=json. + + If there are errors running: ua status --format=json or if the status on + disk is unparseable, return an empty dict. + """ + if os.path.exists(UA_STATUS_FILE): + with open(UA_STATUS_FILE, "r") as stream: + status = stream.read() + else: + try: + status = subprocess.check_output( + ['ua', 'status', '--format=json'] + ).decode() + except subprocess.CalledProcessError as e: + print_debug('failed to run ua status: %s' % e) + return {} try: - c_livepatch = subprocess.run(["/snap/bin/canonical-livepatch", - "status"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - # it can't be enabled if it isn't installed - except FileNotFoundError: - return False - if c_livepatch.returncode == 0: - return True - elif c_livepatch.returncode == 1: - return False - - -def esm_is_enabled(): - """ Check to see if esm is an available source""" - acp = subprocess.Popen(["apt-cache", "policy"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - grep = subprocess.run(["grep", "-F", "-q", "https://%s" % esm_site], - stdin=acp.stdout, stdout=subprocess.PIPE) - if grep.returncode == 0: - return True - elif grep.returncode == -1: - return False + return json.loads(status) + except json.decoder.JSONDecodeError as e: + print_debug('failed to parse JSON from ua status output: %s' % e) + return {} + + +def is_ua_service_enabled(service_name: str) -> bool: + """Check to see if named esm service is enabled. + + :return: True if UA status reports service as enabled. + """ + status = get_ua_status() + for service in status.get("services", []): + if service["name"] == service_name: + # Machines unattached to UA will not provide service 'status' key. + return service.get("status") == "enabled" + return False def trim_archive(archive): @@ -353,12 +369,9 @@ if __name__ == "__main__": "%s-apps-security" % codename, "main") - livepatch_enabled = livepatch_is_enabled() - esm_enabled = esm_is_enabled() - is_esm_infra_used = (suite_esm_main in all_origins) or \ - (suite_esm_main_security in all_origins) - is_esm_apps_used = (suite_esm_universe in all_origins) or \ - (suite_esm_universe_security in all_origins) + livepatch_enabled = is_ua_service_enabled("livepatch") + esm_enabled = is_ua_service_enabled("esm-infra") + ua_attached = get_ua_status().get("attached", False) # Now do the final loop through for pkg in cache: @@ -561,6 +574,6 @@ if __name__ == "__main__": len(pkgstats.pkgs_um))) if livepatch_enabled: print("\nEnable ESM Apps with: ua enable esm-apps") - if lts and not livepatch_enabled: + if lts and not ua_attached: print("\nThis machine is not attached to an Ubuntu Advantage " "subscription.\nSee https://ubuntu.com/advantage")