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
1=== modified file 'unattended-upgrade'
2--- unattended-upgrade 2014-09-11 13:12:05 +0000
3+++ unattended-upgrade 2014-10-02 19:25:32 +0000
4@@ -118,6 +118,7 @@
5 if is_allowed_origin(ver, self.allowed_origins):
6 # leave as soon as we have the highest new candidate
7 new_cand = ver
8+ print "newest candidate version applied"
9 break
10 if new_cand and new_cand != pkg.candidate:
11 logging.debug("adjusting candidate version: '%s'" % new_cand)
12@@ -391,7 +392,7 @@
13
14
15 def upgrade_in_minimal_steps(cache, pkgs_to_upgrade, blacklist,
16- logfile_dpkg="", verbose=False):
17+ whitelist, logfile_dpkg="", verbose=False):
18
19 install_log = LogInstallProgress(logfile_dpkg, verbose)
20 install_log.progress_log += ".minimal-steps"
21@@ -420,7 +421,8 @@
22 continue
23 # double check that we are not running into side effects like
24 # what could have been caused LP: #1020680
25- if not check_changes_for_sanity(cache, allowed_origins, blacklist):
26+ if not check_changes_for_sanity(cache, allowed_origins, blacklist,
27+ whitelist):
28 logging.info("While building minimal partition: "
29 "cache has not allowed changes")
30 cache.clear()
31@@ -491,8 +493,16 @@
32 return True
33 return False
34
35-
36-def check_changes_for_sanity(cache, allowed_origins, blacklist):
37+def is_pkgname_in_whitelist(pkgname, whitelist):
38+ for whitelist_regexp in whitelist:
39+ if re.match(whitelist_regexp, pkgname):
40+ logging.debug("only upgrading the following package '%s'" %
41+ pkgname)
42+ return True
43+ return False
44+
45+
46+def check_changes_for_sanity(cache, allowed_origins, blacklist, whitelist):
47 if cache._depcache.broken_count != 0:
48 return False
49 for pkg in cache:
50@@ -504,7 +514,13 @@
51 logging.debug("pkg '%s' not in allowed origin" % pkg.name)
52 return False
53 if is_pkgname_in_blacklist(pkg.name, blacklist):
54+ logging.debug("pkg '%s' package has been blacklisted" %
55+ pkg.name)
56 return False
57+ if is_pkgname_in_whitelist(pkg.name, whitelist):
58+ logging.debug("pkg '%s' package has been whitelisted" %
59+ pkg.name)
60+ return True
61 if pkg._pkg.selected_state == apt_pkg.SELSTATE_HOLD:
62 logging.debug("pkg '%s' is on hold" % pkg.name)
63 return False
64@@ -778,8 +794,8 @@
65 logging.debug("mail returned: %s" % ret)
66
67
68-def do_install(cache, pkgs_to_upgrade, blacklisted_pkgs, options,
69- logfile_dpkg):
70+def do_install(cache, pkgs_to_upgrade, blacklisted_pkgs, whitelisted_pkgs,
71+ options, logfile_dpkg):
72 # set debconf to NON_INTERACTIVE, redirect output
73 os.putenv("DEBIAN_FRONTEND", "noninteractive")
74 setup_apt_listchanges()
75@@ -801,7 +817,7 @@
76 # try upgrade all "pkgs" in minimal steps
77 pkg_install_success = upgrade_in_minimal_steps(
78 cache, [pkg.name for pkg in pkgs_to_upgrade],
79- blacklisted_pkgs, logfile_dpkg,
80+ blacklisted_pkgs, whitelisted_pkgs, logfile_dpkg,
81 options.verbose or options.debug)
82 else:
83 pkg_install_success = upgrade_normal(
84@@ -885,6 +901,9 @@
85 def get_blacklisted_pkgs():
86 return apt_pkg.config.value_list("Unattended-Upgrade::Package-Blacklist")
87
88+def get_whitelisted_pkgs():
89+ return apt_pkg.config.value_list("Unattended-Upgrade::Package-Whitelist")
90+
91
92 def reboot_if_requested_and_needed(shutdown_lock=0):
93 # auto-reboot (if required and the config for this is set
94@@ -906,44 +925,77 @@
95 pass
96
97
98-def calculate_upgradable_pkgs(cache, options, allowed_origins,
99- blacklisted_pkgs):
100+def flag_for_upgrade(pkg, cache, allowed_origins,
101+ blacklisted_pkgs, whitelisted_pkgs):
102+
103 pkgs_to_upgrade = []
104 pkgs_kept_back = []
105+
106+ try:
107+ pkg.mark_upgrade()
108+ if check_changes_for_sanity(cache, allowed_origins,
109+ blacklisted_pkgs, whitelisted_pkgs):
110+ # add to packages to upgrade
111+ pkgs_to_upgrade.append(pkg)
112+ # re-eval pkgs_kept_back as the resolver may fail to
113+ # directly upgrade a pkg, but that may work during
114+ # a subsequent operation, see debian bug #639840
115+ for pkgname in pkgs_kept_back:
116+ if (cache[pkgname].marked_install or
117+ cache[pkgname].marked_upgrade):
118+ pkgs_kept_back.remove(pkgname)
119+ pkgs_to_upgrade.append(cache[pkgname])
120+ else:
121+ logging.debug("sanity check failed")
122+ rewind_cache(cache, pkgs_to_upgrade)
123+ pkgs_kept_back.append(pkg.name)
124+ except SystemError as e:
125+ # can't upgrade
126+ logging.warning(
127+ _("package '%s' upgradable but fails to "
128+ "be marked for upgrade (%s)") % (pkg.name, e))
129+ rewind_cache(cache, pkgs_to_upgrade)
130+ pkgs_kept_back.append(pkg.name)
131+ return pkgs_to_upgrade, pkgs_kept_back
132+
133+def calculate_upgradable_pkgs(cache, options, allowed_origins,
134+ blacklisted_pkgs, whitelisted_pkgs):
135 # now do the actual upgrade
136 for pkg in cache:
137 if options.debug and pkg.is_upgradable:
138 logging.debug("Checking: %s (%s)" % (
139 pkg.name, getattr(pkg.candidate, "origins", [])))
140 if (pkg.is_upgradable and
141- not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs) and
142- is_allowed_origin(pkg.candidate, allowed_origins)):
143- try:
144- pkg.mark_upgrade()
145- if check_changes_for_sanity(cache, allowed_origins,
146- blacklisted_pkgs):
147- # add to packages to upgrade
148- pkgs_to_upgrade.append(pkg)
149- # re-eval pkgs_kept_back as the resolver may fail to
150- # directly upgrade a pkg, but that may work during
151- # a subsequent operation, see debian bug #639840
152- for pkgname in pkgs_kept_back:
153- if (cache[pkgname].marked_install or
154- cache[pkgname].marked_upgrade):
155- pkgs_kept_back.remove(pkgname)
156- pkgs_to_upgrade.append(cache[pkgname])
157- else:
158- logging.debug("sanity check failed")
159- rewind_cache(cache, pkgs_to_upgrade)
160- pkgs_kept_back.append(pkg.name)
161- except SystemError as e:
162- # can't upgrade
163- logging.warning(
164- _("package '%s' upgradable but fails to "
165- "be marked for upgrade (%s)") % (pkg.name, e))
166- rewind_cache(cache, pkgs_to_upgrade)
167- pkgs_kept_back.append(pkg.name)
168- return pkgs_to_upgrade, pkgs_kept_back
169+ not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs) and
170+ is_pkgname_in_whitelist(pkg.name, whitelisted_pkgs) and
171+ is_allowed_origin(pkg.candidate, allowed_origins)):
172+
173+ logging.debug("Whitelisted package")
174+ pkgs_to_upgrade, pkgs_kept_back = flag_for_upgrade(pkg,
175+ cache,
176+ allowed_origins,
177+ blacklisted_pkgs,
178+ whitelisted_pkgs)
179+ return pkgs_to_upgrade, pkgs_kept_back
180+
181+ elif (pkg.is_upgradable and
182+ not is_pkgname_in_blacklist(pkg.name, blacklisted_pkgs) and
183+ is_allowed_origin(pkg.candidate, allowed_origins)):
184+
185+ pkgs_to_upgrade, pkgs_kept_back = flag_for_upgrade(pkg,
186+ cache,
187+ allowed_origins,
188+ blacklisted_pkgs,
189+ whitelisted_pkgs)
190+ return pkgs_to_upgrade, pkgs_kept_back
191+ else:
192+
193+ # Send back the empty lists
194+
195+ pkgs_to_upgrade = []
196+ pkgs_kept_back = []
197+
198+ return pkgs_to_upgrade, pkgs_kept_back
199
200
201 def main(options, rootdir=""):
202@@ -958,10 +1010,16 @@
203 # format (origin, archive), e.g. ("Ubuntu","dapper-security")
204 allowed_origins = get_allowed_origins()
205
206- # pkgs that are (for some reason) not save to install
207+ # pkgs that are (for some reason) not safe to install
208 blacklisted_pkgs = get_blacklisted_pkgs()
209 logging.info(_("Initial blacklisted packages: %s"),
210 " ".join(blacklisted_pkgs))
211+
212+ # install only these packages regardless of other upgrades available
213+ whitelisted_pkgs = get_whitelisted_pkgs()
214+ logging.info(_("Initial whitelisted packages: %s"),
215+ " ".join(whitelisted_pkgs))
216+
217 logging.info(_("Starting unattended upgrades script"))
218
219 # display available origin
220@@ -1025,7 +1083,7 @@
221 if pkg.is_auto_removable])
222 # find out about the packages that are upgradable (in a allowed_origin)
223 pkgs_to_upgrade, pkgs_kept_back = calculate_upgradable_pkgs(
224- cache, options, allowed_origins, blacklisted_pkgs)
225+ cache, options, allowed_origins, blacklisted_pkgs, whitelisted_pkgs)
226 pkgs_to_upgrade.sort(key=lambda p: p.name)
227 pkgs = "\n".join([pkg.name for pkg in pkgs_to_upgrade])
228 logging.debug("pkgs that look like they should be upgraded: %s" % pkgs)
229@@ -1094,16 +1152,19 @@
230 # redo the selection about the packages to upgrade based on the new
231 # blacklist
232 logging.debug("blacklist: %s" % blacklisted_pkgs)
233+ # whitelist
234+ logging.debug("whitelist: %s" % whitelisted_pkgs)
235 # find out about the packages that are upgradable (in a allowed_origin)
236- if len(blacklisted_pkgs) > 0:
237+ if len(blacklisted_pkgs) > 0 or len(whitelisted_pkgs) > 0:
238 cache.clear()
239 old_pkgs_to_upgrade = pkgs_to_upgrade[:]
240 pkgs_to_upgrade = []
241 for pkg in old_pkgs_to_upgrade:
242- logging.debug("Checking (blacklist): %s" % (pkg.name))
243+ logging.debug("Checking the black and whitelist: %s" %
244+ (pkg.name))
245 pkg.mark_upgrade()
246 if check_changes_for_sanity(cache, allowed_origins,
247- blacklisted_pkgs):
248+ blacklisted_pkgs, whitelisted_pkgs):
249 pkgs_to_upgrade.append(pkg)
250 else:
251 if not (pkg.name in pkgs_kept_back):
252@@ -1169,7 +1230,7 @@
253 shutdown_lock = apt_pkg.get_lock("/var/run/unattended-upgrades.lock")
254 # do install
255 pkg_install_success = do_install(
256- cache, pkgs_to_upgrade, blacklisted_pkgs, options, logfile_dpkg)
257+ cache, pkgs_to_upgrade, blacklisted_pkgs, whitelisted_pkgs, options, logfile_dpkg)
258
259 # send a mail (if needed)
260 if not options.dry_run:
261@@ -1193,7 +1254,7 @@
262
263 # this ensures the commandline is logged in /var/log/apt/history.log
264 apt_pkg.config.set("Commandline::AsString", " ".join(sys.argv))
265-
266+
267 # init the options
268 parser = OptionParser()
269 parser.add_option("-d", "--debug",

Subscribers

People subscribed via source and target branches