Merge lp:~cwarner/unattended-upgrades/whitelisting into lp:unattended-upgrades

Proposed by Christopher Warner
Status: Merged
Merged at revision: 312
Proposed branch: lp:~cwarner/unattended-upgrades/whitelisting
Merge into: lp:unattended-upgrades
Diff against target: 269 lines (+105/-44)
1 file modified
unattended-upgrade (+105/-44)
To merge this branch: bzr merge lp:~cwarner/unattended-upgrades/whitelisting
Reviewer Review Type Date Requested Status
Michael Vogt Needs Information
Review via email: mp+236954@code.launchpad.net

Description of the change

Added whitelist option, this allows one to whitelist a package, meaning it's the only package installed from origin/archive. Moved some functionality in calculate_upgradable_pkgs into a new method flag_for_upgrade and a new Unattended-Upgrade::Package-Whitelist option.

Looks like it works pretty well but would like some vetting for other package conditions.

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

Thanks for this branch! It looks good overall, some made some comments inline.

One issue right now is that the tests need a update, i.e.:
$ (cd tests ; make)
fails right now and there is no test that checks if the whitelisting works.

It also seems that the test for the whitelisted strings needs to check if there are only whitelisted packages in the upgrade set to ensure that there is no package that is pulled in via a dependency there (unless that is not what you want).

I pushed lp:~mvo/unattended-upgrades/whitelisting with the test fixes and the whitelist verification changes. Please have a look and merge if it seems sensilble.

review: Needs Information
Revision history for this message
Christopher Warner (cwarner) wrote :

Yeah this looks good, about the whitelisted strings. I battled with the dependency issue as I can't tell if that would necessarily be an issue going forward for our needs specifically but for the greater good i'm assuming others wouldn't want to deal with the inconsistency. Operating on the notion that a dependency would be apart of an overall update maybe we should just allow it?

So for example someone wants to use whitelisting and they automatically assume deps are included: whitelist bash, then bash and any deps would be whitelisted. So in the case of Ubuntu, Canonical would test/smoke/vet an update of x.pkg with x.pkgs && deps. That should cover most cases, specifically if it's a security update. In the cases where one wants to hold back a dep of a whitelisted package, they could simply forcefully downgrade or link to the dep at runtime or install from source and point to the dep they care about or etc.

The opposite side of the coin is I assume all deps are upgraded and they aren't. If a lib is a dep, it's possible i'm loading upgraded executables looking for undef'd refs unattended or all the other nasty things. Of course I don't have the data to prove which use case would be a more common rationalization. It just seems like if you ok the whitelist pkg any deps surrounding it should be considered OK for unattended-upgrading.

DISCLAIMER: If this comes back to bite me in the ass at some point I'll whine about it, wave my hands and then blame you.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks for your reply and sorry (again) for my slow reply.

I was thinking the strict whitelist vs relaxed whitelist over and I think the best option is to provide both but default to your initial version with the relaxed whitelist as this is probably what most people expect. If you whitelist e.g. ^apt$ you will want libapt-pkg4.11 as well.

I implemented this in lp:~mvo/unattended-upgrades/whitelist - it has a Unattended-Upgrade::Package-Whitelist-Strict" option now for people who want the strict behavior.

If you could double check by branch and also if the documentation makes sense, that would be great. If its all looking good I will make a new release with this feature. Thanks again for working on this!

Cheers,
 Michael

Revision history for this message
Christopher Warner (cwarner) wrote :

Yeah reviewed this and it covers everything and the doc lines up. I've got nothing but thanks on this end.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'unattended-upgrade'
--- unattended-upgrade 2014-09-11 13:12:05 +0000
+++ unattended-upgrade 2014-10-02 19:25:32 +0000
@@ -118,6 +118,7 @@
118 if is_allowed_origin(ver, self.allowed_origins):118 if is_allowed_origin(ver, self.allowed_origins):
119 # leave as soon as we have the highest new candidate119 # leave as soon as we have the highest new candidate
120 new_cand = ver120 new_cand = ver
121 print "newest candidate version applied"
121 break122 break
122 if new_cand and new_cand != pkg.candidate:123 if new_cand and new_cand != pkg.candidate:
123 logging.debug("adjusting candidate version: '%s'" % new_cand)124 logging.debug("adjusting candidate version: '%s'" % new_cand)
@@ -391,7 +392,7 @@
391392
392393
393def upgrade_in_minimal_steps(cache, pkgs_to_upgrade, blacklist,394def upgrade_in_minimal_steps(cache, pkgs_to_upgrade, blacklist,
394 logfile_dpkg="", verbose=False):395 whitelist, logfile_dpkg="", verbose=False):
395396
396 install_log = LogInstallProgress(logfile_dpkg, verbose)397 install_log = LogInstallProgress(logfile_dpkg, verbose)
397 install_log.progress_log += ".minimal-steps"398 install_log.progress_log += ".minimal-steps"
@@ -420,7 +421,8 @@
420 continue421 continue
421 # double check that we are not running into side effects like422 # double check that we are not running into side effects like
422 # what could have been caused LP: #1020680423 # what could have been caused LP: #1020680
423 if not check_changes_for_sanity(cache, allowed_origins, blacklist):424 if not check_changes_for_sanity(cache, allowed_origins, blacklist,
425 whitelist):
424 logging.info("While building minimal partition: "426 logging.info("While building minimal partition: "
425 "cache has not allowed changes")427 "cache has not allowed changes")
426 cache.clear()428 cache.clear()
@@ -491,8 +493,16 @@
491 return True493 return True
492 return False494 return False
493495
494496def is_pkgname_in_whitelist(pkgname, whitelist):
495def check_changes_for_sanity(cache, allowed_origins, blacklist):497 for whitelist_regexp in whitelist:
498 if re.match(whitelist_regexp, pkgname):
499 logging.debug("only upgrading the following package '%s'" %
500 pkgname)
501 return True
502 return False
503
504
505def check_changes_for_sanity(cache, allowed_origins, blacklist, whitelist):
496 if cache._depcache.broken_count != 0:506 if cache._depcache.broken_count != 0:
497 return False507 return False
498 for pkg in cache:508 for pkg in cache:
@@ -504,7 +514,13 @@
504 logging.debug("pkg '%s' not in allowed origin" % pkg.name)514 logging.debug("pkg '%s' not in allowed origin" % pkg.name)
505 return False515 return False
506 if is_pkgname_in_blacklist(pkg.name, blacklist):516 if is_pkgname_in_blacklist(pkg.name, blacklist):
517 logging.debug("pkg '%s' package has been blacklisted" %
518 pkg.name)
507 return False519 return False
520 if is_pkgname_in_whitelist(pkg.name, whitelist):
521 logging.debug("pkg '%s' package has been whitelisted" %
522 pkg.name)
523 return True
508 if pkg._pkg.selected_state == apt_pkg.SELSTATE_HOLD:524 if pkg._pkg.selected_state == apt_pkg.SELSTATE_HOLD:
509 logging.debug("pkg '%s' is on hold" % pkg.name)525 logging.debug("pkg '%s' is on hold" % pkg.name)
510 return False526 return False
@@ -778,8 +794,8 @@
778 logging.debug("mail returned: %s" % ret)794 logging.debug("mail returned: %s" % ret)
779795
780796
781def do_install(cache, pkgs_to_upgrade, blacklisted_pkgs, options,797def do_install(cache, pkgs_to_upgrade, blacklisted_pkgs, whitelisted_pkgs,
782 logfile_dpkg):798 options, logfile_dpkg):
783 # set debconf to NON_INTERACTIVE, redirect output799 # set debconf to NON_INTERACTIVE, redirect output
784 os.putenv("DEBIAN_FRONTEND", "noninteractive")800 os.putenv("DEBIAN_FRONTEND", "noninteractive")
785 setup_apt_listchanges()801 setup_apt_listchanges()
@@ -801,7 +817,7 @@
801 # try upgrade all "pkgs" in minimal steps817 # try upgrade all "pkgs" in minimal steps
802 pkg_install_success = upgrade_in_minimal_steps(818 pkg_install_success = upgrade_in_minimal_steps(
803 cache, [pkg.name for pkg in pkgs_to_upgrade],819 cache, [pkg.name for pkg in pkgs_to_upgrade],
804 blacklisted_pkgs, logfile_dpkg,820 blacklisted_pkgs, whitelisted_pkgs, logfile_dpkg,
805 options.verbose or options.debug)821 options.verbose or options.debug)
806 else:822 else:
807 pkg_install_success = upgrade_normal(823 pkg_install_success = upgrade_normal(
@@ -885,6 +901,9 @@
885def get_blacklisted_pkgs():901def get_blacklisted_pkgs():
886 return apt_pkg.config.value_list("Unattended-Upgrade::Package-Blacklist")902 return apt_pkg.config.value_list("Unattended-Upgrade::Package-Blacklist")
887903
904def get_whitelisted_pkgs():
905 return apt_pkg.config.value_list("Unattended-Upgrade::Package-Whitelist")
906
888907
889def reboot_if_requested_and_needed(shutdown_lock=0):908def reboot_if_requested_and_needed(shutdown_lock=0):
890 # auto-reboot (if required and the config for this is set909 # auto-reboot (if required and the config for this is set
@@ -906,44 +925,77 @@
906 pass925 pass
907926
908927
909def calculate_upgradable_pkgs(cache, options, allowed_origins,928def flag_for_upgrade(pkg, cache, allowed_origins,
910 blacklisted_pkgs):929 blacklisted_pkgs, whitelisted_pkgs):
930
911 pkgs_to_upgrade = []931 pkgs_to_upgrade = []
912 pkgs_kept_back = []932 pkgs_kept_back = []
933
934 try:
935 pkg.mark_upgrade()
936 if check_changes_for_sanity(cache, allowed_origins,
937 blacklisted_pkgs, whitelisted_pkgs):
938 # add to packages to upgrade
939 pkgs_to_upgrade.append(pkg)
940 # re-eval pkgs_kept_back as the resolver may fail to
941 # directly upgrade a pkg, but that may work during
942 # a subsequent operation, see debian bug #639840
943 for pkgname in pkgs_kept_back:
944 if (cache[pkgname].marked_install or
945 cache[pkgname].marked_upgrade):
946 pkgs_kept_back.remove(pkgname)
947 pkgs_to_upgrade.append(cache[pkgname])
948 else:
949 logging.debug("sanity check failed")
950 rewind_cache(cache, pkgs_to_upgrade)
951 pkgs_kept_back.append(pkg.name)
952 except SystemError as e:
953 # can't upgrade
954 logging.warning(
955 _("package '%s' upgradable but fails to "
956 "be marked for upgrade (%s)") % (pkg.name, e))
957 rewind_cache(cache, pkgs_to_upgrade)
958 pkgs_kept_back.append(pkg.name)
959 return pkgs_to_upgrade, pkgs_kept_back
960
961def calculate_upgradable_pkgs(cache, options, allowed_origins,
962 blacklisted_pkgs, whitelisted_pkgs):
913 # now do the actual upgrade963 # now do the actual upgrade
914 for pkg in cache:964 for pkg in cache:
915 if options.debug and pkg.is_upgradable:965 if options.debug and pkg.is_upgradable:
916 logging.debug("Checking: %s (%s)" % (966 logging.debug("Checking: %s (%s)" % (
917 pkg.name, getattr(pkg.candidate, "origins", [])))967 pkg.name, getattr(pkg.candidate, "origins", [])))
918 if (pkg.is_upgradable and968 if (pkg.is_upgradable and
919 not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs) and969 not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs) and
920 is_allowed_origin(pkg.candidate, allowed_origins)):970 is_pkgname_in_whitelist(pkg.name, whitelisted_pkgs) and
921 try:971 is_allowed_origin(pkg.candidate, allowed_origins)):
922 pkg.mark_upgrade()972
923 if check_changes_for_sanity(cache, allowed_origins,973 logging.debug("Whitelisted package")
924 blacklisted_pkgs):974 pkgs_to_upgrade, pkgs_kept_back = flag_for_upgrade(pkg,
925 # add to packages to upgrade975 cache,
926 pkgs_to_upgrade.append(pkg)976 allowed_origins,
927 # re-eval pkgs_kept_back as the resolver may fail to977 blacklisted_pkgs,
928 # directly upgrade a pkg, but that may work during978 whitelisted_pkgs)
929 # a subsequent operation, see debian bug #639840979 return pkgs_to_upgrade, pkgs_kept_back
930 for pkgname in pkgs_kept_back:980
931 if (cache[pkgname].marked_install or981 elif (pkg.is_upgradable and
932 cache[pkgname].marked_upgrade):982 not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs) and
933 pkgs_kept_back.remove(pkgname)983 is_allowed_origin(pkg.candidate, allowed_origins)):
934 pkgs_to_upgrade.append(cache[pkgname])984
935 else:985 pkgs_to_upgrade, pkgs_kept_back = flag_for_upgrade(pkg,
936 logging.debug("sanity check failed")986 cache,
937 rewind_cache(cache, pkgs_to_upgrade)987 allowed_origins,
938 pkgs_kept_back.append(pkg.name)988 blacklisted_pkgs,
939 except SystemError as e:989 whitelisted_pkgs)
940 # can't upgrade990 return pkgs_to_upgrade, pkgs_kept_back
941 logging.warning(991 else:
942 _("package '%s' upgradable but fails to "992
943 "be marked for upgrade (%s)") % (pkg.name, e))993 # Send back the empty lists
944 rewind_cache(cache, pkgs_to_upgrade)994
945 pkgs_kept_back.append(pkg.name)995 pkgs_to_upgrade = []
946 return pkgs_to_upgrade, pkgs_kept_back996 pkgs_kept_back = []
997
998 return pkgs_to_upgrade, pkgs_kept_back
947999
9481000
949def main(options, rootdir=""):1001def main(options, rootdir=""):
@@ -958,10 +1010,16 @@
958 # format (origin, archive), e.g. ("Ubuntu","dapper-security")1010 # format (origin, archive), e.g. ("Ubuntu","dapper-security")
959 allowed_origins = get_allowed_origins()1011 allowed_origins = get_allowed_origins()
9601012
961 # pkgs that are (for some reason) not save to install1013 # pkgs that are (for some reason) not safe to install
962 blacklisted_pkgs = get_blacklisted_pkgs()1014 blacklisted_pkgs = get_blacklisted_pkgs()
963 logging.info(_("Initial blacklisted packages: %s"),1015 logging.info(_("Initial blacklisted packages: %s"),
964 " ".join(blacklisted_pkgs))1016 " ".join(blacklisted_pkgs))
1017
1018 # install only these packages regardless of other upgrades available
1019 whitelisted_pkgs = get_whitelisted_pkgs()
1020 logging.info(_("Initial whitelisted packages: %s"),
1021 " ".join(whitelisted_pkgs))
1022
965 logging.info(_("Starting unattended upgrades script"))1023 logging.info(_("Starting unattended upgrades script"))
9661024
967 # display available origin1025 # display available origin
@@ -1025,7 +1083,7 @@
1025 if pkg.is_auto_removable])1083 if pkg.is_auto_removable])
1026 # find out about the packages that are upgradable (in a allowed_origin)1084 # find out about the packages that are upgradable (in a allowed_origin)
1027 pkgs_to_upgrade, pkgs_kept_back = calculate_upgradable_pkgs(1085 pkgs_to_upgrade, pkgs_kept_back = calculate_upgradable_pkgs(
1028 cache, options, allowed_origins, blacklisted_pkgs)1086 cache, options, allowed_origins, blacklisted_pkgs, whitelisted_pkgs)
1029 pkgs_to_upgrade.sort(key=lambda p: p.name)1087 pkgs_to_upgrade.sort(key=lambda p: p.name)
1030 pkgs = "\n".join([pkg.name for pkg in pkgs_to_upgrade])1088 pkgs = "\n".join([pkg.name for pkg in pkgs_to_upgrade])
1031 logging.debug("pkgs that look like they should be upgraded: %s" % pkgs)1089 logging.debug("pkgs that look like they should be upgraded: %s" % pkgs)
@@ -1094,16 +1152,19 @@
1094 # redo the selection about the packages to upgrade based on the new1152 # redo the selection about the packages to upgrade based on the new
1095 # blacklist1153 # blacklist
1096 logging.debug("blacklist: %s" % blacklisted_pkgs)1154 logging.debug("blacklist: %s" % blacklisted_pkgs)
1155 # whitelist
1156 logging.debug("whitelist: %s" % whitelisted_pkgs)
1097 # find out about the packages that are upgradable (in a allowed_origin)1157 # find out about the packages that are upgradable (in a allowed_origin)
1098 if len(blacklisted_pkgs) > 0:1158 if len(blacklisted_pkgs) > 0 or len(whitelisted_pkgs) > 0:
1099 cache.clear()1159 cache.clear()
1100 old_pkgs_to_upgrade = pkgs_to_upgrade[:]1160 old_pkgs_to_upgrade = pkgs_to_upgrade[:]
1101 pkgs_to_upgrade = []1161 pkgs_to_upgrade = []
1102 for pkg in old_pkgs_to_upgrade:1162 for pkg in old_pkgs_to_upgrade:
1103 logging.debug("Checking (blacklist): %s" % (pkg.name))1163 logging.debug("Checking the black and whitelist: %s" %
1164 (pkg.name))
1104 pkg.mark_upgrade()1165 pkg.mark_upgrade()
1105 if check_changes_for_sanity(cache, allowed_origins,1166 if check_changes_for_sanity(cache, allowed_origins,
1106 blacklisted_pkgs):1167 blacklisted_pkgs, whitelisted_pkgs):
1107 pkgs_to_upgrade.append(pkg)1168 pkgs_to_upgrade.append(pkg)
1108 else:1169 else:
1109 if not (pkg.name in pkgs_kept_back):1170 if not (pkg.name in pkgs_kept_back):
@@ -1169,7 +1230,7 @@
1169 shutdown_lock = apt_pkg.get_lock("/var/run/unattended-upgrades.lock")1230 shutdown_lock = apt_pkg.get_lock("/var/run/unattended-upgrades.lock")
1170 # do install1231 # do install
1171 pkg_install_success = do_install(1232 pkg_install_success = do_install(
1172 cache, pkgs_to_upgrade, blacklisted_pkgs, options, logfile_dpkg)1233 cache, pkgs_to_upgrade, blacklisted_pkgs, whitelisted_pkgs, options, logfile_dpkg)
11731234
1174 # send a mail (if needed)1235 # send a mail (if needed)
1175 if not options.dry_run:1236 if not options.dry_run:
@@ -1193,7 +1254,7 @@
11931254
1194 # this ensures the commandline is logged in /var/log/apt/history.log1255 # this ensures the commandline is logged in /var/log/apt/history.log
1195 apt_pkg.config.set("Commandline::AsString", " ".join(sys.argv))1256 apt_pkg.config.set("Commandline::AsString", " ".join(sys.argv))
1196 1257
1197 # init the options1258 # init the options
1198 parser = OptionParser()1259 parser = OptionParser()
1199 parser.add_option("-d", "--debug",1260 parser.add_option("-d", "--debug",

Subscribers

People subscribed via source and target branches