Merge lp:~cwarner/unattended-upgrades/whitelisting into lp:unattended-upgrades
- whitelisting
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Vogt | Needs Information | ||
Review via email: mp+236954@code.launchpad.net |
Commit message
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_
Looks like it works pretty well but would like some vetting for other package conditions.
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-
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.
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-
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
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
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", |
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.