Skip to content
This repository was archived by the owner on Oct 30, 2018. It is now read-only.

Commit 2fd2e53

Browse files
committed
Fix apt_key check mode with long ids
* More cleanups * apt-key can be given a key id longer than 16 chars to more accurately define what key to download. However, we can use a maximum of 16 chars to verify whether a key is installed or not. So we need to use different lengths for the id depending on what we're doing with it. Fixes #2622
1 parent e24c780 commit 2fd2e53

File tree

1 file changed

+86
-47
lines changed

1 file changed

+86
-47
lines changed

packaging/os/apt_key.py

Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -113,26 +113,63 @@
113113
from ansible.module_utils.urls import fetch_url
114114

115115

116-
gpg_bin = None
117-
grep_bin = None
118116
apt_key_bin = None
119117

120118

121119
def find_needed_binaries(module):
122-
global gpg_bin
123-
global grep_bin
124120
global apt_key_bin
125121

126-
gpg_bin = module.get_bin_path('gpg', required=True)
127-
grep_bin = module.get_bin_path('grep', required=True)
128122
apt_key_bin = module.get_bin_path('apt-key', required=True)
129123

124+
### FIXME: Is there a reason that gpg and grep are checked? Is it just
125+
# cruft or does the apt .deb package not require them (and if they're not
126+
# installed, /usr/bin/apt-key fails?)
127+
module.get_bin_path('gpg', required=True)
128+
module.get_bin_path('grep', required=True)
129+
130+
131+
def parse_key_id(key_id):
132+
"""validate the key_id and break it into segments
133+
134+
:arg key_id: The key_id as supplied by the user. A valid key_id will be
135+
8, 16, or more hexadecimal chars with an optional leading ``0x``.
136+
:returns: The portion of key_id suitable for apt-key del, the portion
137+
suitable for comparisons with --list-public-keys, and the portion that
138+
can be used with --recv-key. If key_id is long enough, these will be
139+
the last 8 characters of key_id, the last 16 characters, and all of
140+
key_id. If key_id is not long enough, some of the values will be the
141+
same.
142+
143+
* apt-key del <= 1.10 has a bug with key_id != 8 chars
144+
* apt-key adv --list-public-keys prints 16 chars
145+
* apt-key adv --recv-key can take more chars
146+
147+
"""
148+
# Make sure the key_id is valid hexadecimal
149+
int(key_id, 16)
150+
151+
key_id = key_id.upper()
152+
if key_id.startswith('0X'):
153+
key_id = key_id[2:]
154+
155+
key_id_len = len(key_id)
156+
if (key_id_len != 8 or key_id_len != 16) and key_id_len <= 16:
157+
raise ValueError('key_id must be 8, 16, or 16+ hexadecimal characters in length')
158+
159+
short_key_id = key_id[-8:]
160+
161+
fingerprint = key_id
162+
if key_id_len > 16:
163+
fingerprint = key_id[-16:]
164+
165+
return short_key_id, fingerprint, key_id
166+
130167

131168
def all_keys(module, keyring, short_format):
132169
if keyring:
133-
cmd = "apt-key --keyring %s adv --list-public-keys --keyid-format=long" % keyring
170+
cmd = "%s --keyring %s adv --list-public-keys --keyid-format=long" % (apt_key_bin, keyring)
134171
else:
135-
cmd = "apt-key adv --list-public-keys --keyid-format=long"
172+
cmd = "%s adv --list-public-keys --keyid-format=long" % apt_key_bin
136173
(rc, out, err) = module.run_command(cmd)
137174
results = []
138175
lines = to_native(out).split('\n')
@@ -176,9 +213,9 @@ def download_key(module, url):
176213

177214
def import_key(module, keyring, keyserver, key_id):
178215
if keyring:
179-
cmd = "apt-key --keyring %s adv --keyserver %s --recv %s" % (keyring, keyserver, key_id)
216+
cmd = "%s --keyring %s adv --keyserver %s --recv %s" % (apt_key_bin, keyring, keyserver, key_id)
180217
else:
181-
cmd = "apt-key adv --keyserver %s --recv %s" % (keyserver, key_id)
218+
cmd = "%s adv --keyserver %s --recv %s" % (apt_key_bin, keyserver, key_id)
182219
for retry in range(5):
183220
lang_env = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')
184221
(rc, out, err) = module.run_command(cmd, environ_update=lang_env)
@@ -198,25 +235,25 @@ def import_key(module, keyring, keyserver, key_id):
198235
def add_key(module, keyfile, keyring, data=None):
199236
if data is not None:
200237
if keyring:
201-
cmd = "apt-key --keyring %s add -" % keyring
238+
cmd = "%s --keyring %s add -" % (apt_key_bin, keyring)
202239
else:
203-
cmd = "apt-key add -"
240+
cmd = "%s add -" % apt_key_bin
204241
(rc, out, err) = module.run_command(cmd, data=data, check_rc=True, binary_data=True)
205242
else:
206243
if keyring:
207-
cmd = "apt-key --keyring %s add %s" % (keyring, keyfile)
244+
cmd = "%s --keyring %s add %s" % (apt_key_bin, keyring, keyfile)
208245
else:
209-
cmd = "apt-key add %s" % (keyfile)
246+
cmd = "%s add %s" % (apt_key_bin, keyfile)
210247
(rc, out, err) = module.run_command(cmd, check_rc=True)
211248
return True
212249

213250

214251
def remove_key(module, key_id, keyring):
215252
# FIXME: use module.run_command, fail at point of error and don't discard useful stdin/stdout
216253
if keyring:
217-
cmd = 'apt-key --keyring %s del %s' % (keyring, key_id)
254+
cmd = '%s --keyring %s del %s' % (apt_key_bin, keyring, key_id)
218255
else:
219-
cmd = 'apt-key del %s' % key_id
256+
cmd = '%s del %s' % (apt_key_bin, key_id)
220257
(rc, out, err) = module.run_command(cmd, check_rc=True)
221258
return True
222259

@@ -234,7 +271,8 @@ def main():
234271
keyserver=dict(required=False),
235272
state=dict(required=False, choices=['present', 'absent'], default='present')
236273
),
237-
supports_check_mode=True
274+
supports_check_mode=True,
275+
mutually_exclusive=(('filename', 'keyserver', 'data', 'url'),),
238276
)
239277

240278
key_id = module.params['id']
@@ -246,61 +284,62 @@ def main():
246284
keyserver = module.params['keyserver']
247285
changed = False
248286

287+
fingerprint = short_key_id = key_id
288+
short_format = False
249289
if key_id:
250290
try:
251-
int(key_id, 16)
252-
if key_id.startswith('0x'):
253-
key_id = key_id[2:]
254-
key_id = key_id.upper()
255-
short_key_id = key_id[-8:]
291+
key_id, fingerprint, short_key_id = parse_key_id(key_id)
256292
except ValueError:
257-
module.fail_json(msg="Invalid key_id", id=key_id)
293+
module.fail_json(msg='Invalid key_id', id=key_id)
258294

259-
find_needed_binaries(module)
295+
if len(fingerprint) == 8:
296+
short_format = True
260297

261-
short_format = False
262-
if key_id == short_key_id:
263-
short_format = True
298+
find_needed_binaries(module)
264299

265300
keys = all_keys(module, keyring, short_format)
266301
return_values = {}
267302

268303
if state == 'present':
269-
if key_id and key_id in keys:
304+
if fingerprint and fingerprint in keys:
270305
module.exit_json(changed=False)
306+
elif fingerprint and fingerprint not in keys and module.check_mode:
307+
### TODO: Someday we could go further -- write keys out to
308+
# a temporary file and then extract the key id from there via gpg
309+
# to decide if the key is installed or not.
310+
module.exit_json(changed=True)
271311
else:
272312
if not filename and not data and not keyserver:
273313
data = download_key(module, url)
274-
if key_id and key_id in keys:
275-
module.exit_json(changed=False)
314+
315+
if filename:
316+
add_key(module, filename, keyring)
317+
elif keyserver:
318+
import_key(module, keyring, keyserver, key_id)
276319
else:
277-
if module.check_mode:
278-
module.exit_json(changed=True)
279-
if filename:
280-
add_key(module, filename, keyring)
281-
elif keyserver:
282-
import_key(module, keyring, keyserver, key_id)
283-
else:
284-
add_key(module, "-", keyring, data)
285-
changed=False
286-
keys2 = all_keys(module, keyring, short_format)
287-
if len(keys) != len(keys2):
288-
changed=True
289-
if key_id and key_id not in keys2:
290-
module.fail_json(msg="key does not seem to have been added", id=key_id)
291-
module.exit_json(changed=changed)
320+
add_key(module, "-", keyring, data)
321+
322+
changed = False
323+
keys2 = all_keys(module, keyring, short_format)
324+
if len(keys) != len(keys2):
325+
changed=True
326+
327+
if fingerprint and fingerprint not in keys2:
328+
module.fail_json(msg="key does not seem to have been added", id=key_id)
329+
module.exit_json(changed=changed)
330+
292331
elif state == 'absent':
293332
if not key_id:
294333
module.fail_json(msg="key is required")
295-
if key_id in keys:
334+
if fingerprint in keys:
296335
if module.check_mode:
297336
module.exit_json(changed=True)
298337

299338
# we use the "short" id: key_id[-8:], short_format=True
300339
# it's a workaround for https://bugs.launchpad.net/ubuntu/+source/apt/+bug/1481871
301340
if remove_key(module, short_key_id, keyring):
302341
keys = all_keys(module, keyring, short_format)
303-
if key_id in keys:
342+
if fingerprint in keys:
304343
module.fail_json(msg="apt-key del did not return an error but the key was not removed (check that the id is correct and *not* a subkey)", id=key_id)
305344
changed = True
306345
else:

0 commit comments

Comments
 (0)