Skip to content

Commit 8eecb94

Browse files
committed
Also attempt update if subkey is expired
1 parent 4d25456 commit 8eecb94

3 files changed

Lines changed: 447 additions & 22 deletions

File tree

salt/modules/gpg.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,9 @@ def list_secret_keys(user=None, gnupghome=None, keyring=None):
455455
def _render_key(_key):
456456
tmp = {
457457
"keyid": _key["keyid"],
458-
"uids": _key["uids"],
459458
}
459+
if "uids" in _key:
460+
tmp["uids"] = _key["uids"]
460461
if "fingerprint" in _key:
461462
tmp["fingerprint"] = _key["fingerprint"]
462463

@@ -465,6 +466,7 @@ def _render_key(_key):
465466
length = _key.get("length", None)
466467
owner_trust = _key.get("ownertrust", None)
467468
trust = _key.get("trust", None)
469+
subkeys = _key.get("subkey_info", {})
468470

469471
if expires:
470472
tmp["expires"] = time.strftime(
@@ -479,6 +481,8 @@ def _render_key(_key):
479481
tmp["ownerTrust"] = LETTER_TRUST_DICT[_key["ownertrust"]]
480482
if trust:
481483
tmp["trust"] = LETTER_TRUST_DICT[_key["trust"]]
484+
for data in subkeys.values():
485+
tmp.setdefault("subkeys", []).append(_render_key(data))
482486
return tmp
483487

484488

salt/states/gpg.py

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88

99
import logging
10+
from datetime import date
1011

1112
import salt.utils.dictupdate
1213
import salt.utils.immutabletypes as immutabletypes
@@ -32,6 +33,24 @@ class KeyNotContained(CommandExecutionError):
3233
"""
3334

3435

36+
def _expired_subkeys(key, max_days=100):
37+
"""
38+
From a single key of the output of gpg.list_keys, iterate over
39+
subkeys and return those that have expired recently.
40+
We don't want to keep checking old subkeys for updates.
41+
"""
42+
ret = {}
43+
if not max_days:
44+
return ret
45+
for subkey in key.get("subkeys", []):
46+
if subkey.get("expired"):
47+
# Only try to refresh subkeys that have expired recently
48+
expired_date = date.fromisoformat(subkey["expires"])
49+
if (date.today() - expired_date).days <= max_days:
50+
ret[subkey["keyid"]] = subkey
51+
return ret
52+
53+
3554
def present(
3655
name,
3756
keys=None,
@@ -43,6 +62,7 @@ def present(
4362
source=None,
4463
skip_keyserver=False,
4564
text=None,
65+
subkey_maxage=100,
4666
**kwargs,
4767
):
4868
"""
@@ -109,6 +129,17 @@ def present(
109129
Requires python-gnupg v0.5.1.
110130
111131
.. versionadded:: 3008.0
132+
133+
subkey_maxage
134+
If the managed key has expired subkeys, this state attempts an update.
135+
Since sometimes keys have long expired subkeys, it filters eligible subkeys
136+
that trigger the update check.
137+
This parameter specifies the maximum number of days since a subkey's
138+
expiration for the key to be eligible. Defaults to ``100``, meaning
139+
subkeys that have expired more than 100 days ago do not trigger an attempt.
140+
Set this to a falsy value to skip the explicit management of subkeys.
141+
142+
.. versionadded:: 3008.0
112143
"""
113144

114145
ret = {"name": name, "result": True, "changes": {}, "comment": ""}
@@ -132,12 +163,17 @@ def present(
132163

133164
current_keys = {}
134165
expired_keys = []
166+
expired_subkeys = {}
135167
for key in _current_keys:
136168
keyid = key["keyid"]
137169
current_keys[keyid] = {}
138170
current_keys[keyid]["trust"] = key["trust"]
171+
current_keys[keyid]["subkeys"] = key.get("subkeys", {})
139172
if key.get("expired"):
140173
expired_keys.append(keyid)
174+
key_expired_subs = _expired_subkeys(key, max_days=subkey_maxage)
175+
if key_expired_subs:
176+
expired_subkeys[keyid] = key_expired_subs
141177

142178
if not keys:
143179
keys = name
@@ -149,22 +185,34 @@ def present(
149185
for key in keys:
150186
key_res[key] = []
151187
try:
152-
refresh = key in expired_keys
188+
is_expired = key in expired_keys
189+
has_expired_subkeys = key in expired_subkeys
190+
refresh = is_expired or has_expired_subkeys
153191
if key in current_keys and not refresh:
154192
key_res[key].append(f"GPG Public Key {key} already in keychain")
155193
continue
156194
if __opts__["test"]:
157195
ret["result"] = None
158-
key_res[key] = [f"Would have added {key} to GPG keychain"]
159-
salt.utils.dictupdate.set_dict_key_value(
160-
ret, f"changes:{key}:added", True
161-
)
162196
if refresh:
163-
key_res[key][-1] += " (the existing one was expired)"
197+
if is_expired:
198+
key_res[key] = [
199+
f"Would have attempted to update {key} because it is expired"
200+
]
201+
else:
202+
key_res[key] = [
203+
f"Would have attempted to update {key} because it has expired subkeys:"
204+
] + [
205+
f" - {subkey} (expired on {data['expires']})"
206+
for subkey, data in expired_subkeys[key].items()
207+
]
164208
salt.utils.dictupdate.set_dict_key_value(
165209
ret, f"changes:{key}:refresh", True
166210
)
167211
else:
212+
key_res[key] = [f"Would have added {key} to GPG keychain"]
213+
salt.utils.dictupdate.set_dict_key_value(
214+
ret, f"changes:{key}:added", True
215+
)
168216
current_keys[key] = {"trust": "unknown"}
169217
continue
170218
result = {}
@@ -185,8 +233,11 @@ def present(
185233
result["res"] = any(
186234
"updated: new" in x for x in result["message"]
187235
)
236+
if not is_expired and not result["res"]:
237+
# Don't fail if we're only updating expired subkeys though
238+
result["res"] = None
188239
result["message"] = "\n".join(result["message"])
189-
if (not result or result["res"] is False) and source:
240+
if (not result or not result["res"]) and source:
190241
if not isinstance(source, list):
191242
source = [source]
192243
prev_msg = ""
@@ -224,24 +275,62 @@ def present(
224275
result["message"]
225276
+ f"\nThe new key {key} could not be retrieved though."
226277
)
227-
salt.utils.dictupdate.set_dict_key_value(ret, f"changes:{key}:added", True)
278+
if not refresh:
279+
salt.utils.dictupdate.set_dict_key_value(
280+
ret, f"changes:{key}:added", True
281+
)
228282
if new_key.get("expired"):
229283
raise CommandExecutionError(
230284
result["message"] + f"\nThe new key {key} is expired though."
231285
)
232-
key_res[key].append(f"Added {key} to GPG keychain")
233-
current_keys[key] = {"trust": new_key["trust"]}
234286
if refresh:
235-
key_res[key][-1] += " (the existing one was expired)"
236-
salt.utils.dictupdate.set_dict_key_value(
237-
ret, f"changes:{key}:refresh", True
238-
)
287+
added_subs = {
288+
subkey["keyid"] for subkey in new_key.get("subkeys", {})
289+
} - {subkey["keyid"] for subkey in current_keys[key]["subkeys"]}
290+
updated_subs = set()
291+
if has_expired_subkeys:
292+
after_expired_subs = _expired_subkeys(
293+
new_key, max_days=subkey_maxage
294+
)
295+
updated_subs = set(expired_subkeys[key]) - set(after_expired_subs)
296+
297+
if is_expired:
298+
key_res[key].append(f"Updated {key} because it was expired")
299+
salt.utils.dictupdate.set_dict_key_value(
300+
ret, f"changes:{key}:refresh", True
301+
)
302+
elif added_subs or updated_subs:
303+
key_res[key].append(
304+
f"Updated {key} because it had expired subkeys."
305+
)
306+
salt.utils.dictupdate.set_dict_key_value(
307+
ret, f"changes:{key}:refresh", True
308+
)
309+
if added_subs:
310+
salt.utils.dictupdate.set_dict_key_value(
311+
ret, f"changes:{key}:subkeys:added", list(added_subs)
312+
)
313+
if updated_subs:
314+
salt.utils.dictupdate.set_dict_key_value(
315+
ret, f"changes:{key}:subkeys:extended", list(updated_subs)
316+
)
317+
else:
318+
key_res[key].append(
319+
f"Attempted to update {key} because it has expired subkeys, but no new signatures or keys were found"
320+
)
321+
else:
322+
key_res[key].append(f"Added {key} to GPG keychain")
323+
current_keys[key] = {"trust": new_key["trust"]}
239324
except (CommandExecutionError, SaltInvocationError) as err:
240325
ret["result"] = False
241-
if refresh:
326+
if is_expired:
242327
key_res[key].append(
243328
"Existing key is expired, tried to fetch updated one"
244329
)
330+
elif has_expired_subkeys:
331+
key_res[key].append(
332+
"Existing key has expired subkeys, tried to refresh"
333+
)
245334
key_res[key].extend(str(err).splitlines())
246335

247336
# Now all possible keys are present, manage their trust if requested

0 commit comments

Comments
 (0)