-
Notifications
You must be signed in to change notification settings - Fork 1.7k
pacman_key: support checking for expired and untrusted keys #9950
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your contribution! I've added some first comments.
Regarding the interface: I'm not sure whether a new state is the best way to do this. I think it would be better to be able to specify the update mode, because what you want to do is tell the module to update the key if the fingerprint matches, but other parameters do not.
Right now the module supports two update modes: "always" (force_update=true
), and "compare fingerprint" (force_update=false
). Having another mode "full idempotent" (checks ID and other parameters) is basically what you want, I think?
Thanks for the quick review, Felix.
I agree that a separate state is not very intuitive. IMHO this should just be the default behavior of the In that vein I wouldn't even make this new functionality configurable, because even if a user prefers to keep an expired key, having an import attempt of the expired key on each run is likely not noticeable. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
I'm happy with this PR. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small suggestion on handling the gpg
command, other than that I do not have any strong opinion about solving this with an extra state or some other way, so I'll say it LGTM
def gpg(self, args, keyring=None, **kwargs): | ||
cmd = [self.gpg_binary] | ||
if keyring: | ||
cmd.append('--homedir={keyring}'.format(keyring=keyring)) | ||
cmd.extend(['--no-permission-warning', '--with-colons', '--quiet', '--batch', '--no-tty']) | ||
return self.module.run_command(cmd + args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to consider using CmdRunner
. See https://docs.ansible.com/ansible/latest/collections/community/general/docsite/guide_cmdrunner.html for more details.
plugins/modules/pacman_key.py
Outdated
@@ -78,9 +78,9 @@ | |||
default: /etc/pacman.d/gnupg | |||
state: | |||
description: | |||
- Ensures that the key is present (added) or absent (revoked). | |||
- Ensures that the key is present (added), trusted (signed and not expired) or absent (revoked). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does the PR ensure that the key is not expired? If the key provided to the module is already expired, I don't see how the module can change that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can't extend keys of course, you're right. It just won't return a success status if the key is expired.
Maybe "Ensures/checks" would be clearer? How long are these descriptions supposed to be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The descriptions should be long enough that it's clear what the option actually does.
I would move the description for trusted
into a new paragraph (instead of cramming everything into a single paragraph).
plugins/modules/pacman_key.py
Outdated
@@ -78,9 +78,9 @@ | |||
default: /etc/pacman.d/gnupg | |||
state: | |||
description: | |||
- Ensures that the key is present (added) or absent (revoked). | |||
- Ensures that the key is present (added), trusted (signed and not expired) or absent (revoked). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should mention when trusted
was added:
- Ensures that the key is present (added), trusted (signed and not expired) or absent (revoked). | |
- Ensures that the key is V(present) (added), V(trusted) (signed and not expired) or V(absent) (revoked). | |
- The state V(trusted) has been added in community.general 10.6.0. |
Also formatting the state names as values is IMO a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're not opposed to folding the new functionality into the present
state, I think that would be preferable. IMHO the currently implemented functionality of the present
state does not solve a real life use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be a breaking change, since this considerably changes the behavior of state=present
. Since we don't accept breaking changes this isn't really acceptable.
(That's why I would solve this not with a new state, but with an option. That way we can deprecate its default, and eventually change it to the better behavior, and fix the issue that way.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Finally got around to making the change to make it an option now.
I squashed all commits now, but I could restore the individual commits as well of course. Just let me know what's easier to review. Not super familiar with GitHub review workflows.
Adds option `ensure_trusted`. Fixes ansible-collections#9949
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hi @AlD
A couple of suggestions, mostly about naming and readability, but also some simplifications.
def key_is_trusted(self, keyring, keyid): | ||
"""Check if key is signed and not expired.""" | ||
unused_rc, stdout, unused_stderr = self.gpg(['--check-signatures', keyid], keyring=keyring) | ||
return self.pacman_machine_key(keyring) in gpg_gather_all(stdout.splitlines(), 'sig', 'key') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function gpg_gather_all()
does more than just gathering, it also filters. TBH, I think that function is doing more than it should and the code would be leaner if it only returned the list of GpgListResult
objects. Then, the code here could be changed to:
return self.pacman_machine_key(keyring) in gpg_gather_all(stdout.splitlines(), 'sig', 'key') | |
return self.pacman_machine_key(keyring) in [x.key for x in gpg_gather_all(stdout.splitlines() if x.kind == 'sig') |
I think that makes it easier for the reader to understand what is really going on.
Same applies to line 341 above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking was that it gathers the key
of all kind
GPG results. I'm not really convinced that the list comprehension is easier to understand tbh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, list comprehensions some people love them, some people don't. You can guess my type ;-).
Anyways, I'm OK without the comprehension, but simple fact that you had to add more words to explain what the function does is a good sign that its name could be improved. Maybe gather_key_from_all()
? Your thinking makes sense for you who wrote this about now. Someone else, one year from now, will read this code out of the blue and they will have to go up and down the file, read that function, see what it does, just to figure out what took a couple of words to explain. So, the point is, do the explaining now, save everybody else (including yourself 1, 3 or 5 years from now) that time later on.
def gpg_gather_all(lines, kind, attr): | ||
result = [] | ||
for line in lines: | ||
glr = GpgListResult(line) | ||
if glr.kind == kind: | ||
result.append(getattr(glr, attr)) | ||
return result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As suggested before, this could/should be simpler, as:
def gpg_gather_all(lines, kind, attr): | |
result = [] | |
for line in lines: | |
glr = GpgListResult(line) | |
if glr.kind == kind: | |
result.append(getattr(glr, attr)) | |
return result | |
def gpg_gather_all(lines): | |
return [GpgListResult(line) for line in lines] |
def gpg_get_first(lines, kind, attr): | ||
for line in lines: | ||
glr = GpgListResult(line) | ||
if glr.kind == kind: | ||
return getattr(glr, attr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And that would make this one:
def gpg_get_first(lines, kind, attr): | |
for line in lines: | |
glr = GpgListResult(line) | |
if glr.kind == kind: | |
return getattr(glr, attr) | |
def gpg_get_first(lines, kind, attr): | |
for glr in gpg_gather_all(lines): | |
if glr.kind == kind: | |
return getattr(glr, attr) |
return getattr(glr, attr) | ||
|
||
|
||
def gpg_gather_all(lines, kind, attr): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename it to parse_gpg_list
?
if key_present: | ||
self.remove_key(keyring, keyid) | ||
module.exit_json(changed=True) | ||
module.exit_json(changed=False) | ||
|
||
def gpg(self, args, keyring=None, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently **kwargs
is here just to handle the different cases of check_rc
. That being the case, I would suggest to drop the **
thing and make it explicit:
def gpg(self, args, keyring=None, **kwargs): | |
def gpg(self, args, keyring=None, check_rc=True): |
or whichever default value you prefer, then adjust the calls to that method accordingly - I mean, the calls will work as they are right now, but you might want to remove the extra parameter for the default value (True
in my suggestion).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function doesn't do anything with check_rc
. IMHO **kwargs
conveys that it's only passed through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@russoz means that kwargs
right now is only ever used for check_rc
, so it's better to make this more explicit.
The same is true for pacman_key()
's **kwargs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I got that. What I was trying to say was that both functions are just wrappers around gpg
/pacman-key
via module.run_command
, and having the parameters of the wrapped function not named explicitly helps conveying which function (wrapper/wrapped) owns which set of parameters. I acknowledge that the benefit may be slim as the size of each set is currently small (2 and 1).
For some empirical evidence, the top answers in https://stackoverflow.com/questions/1415812/why-use-kwargs-in-python-what-are-some-real-world-advantages-over-using-named for example mention wrappers as a valid use case for usage of **kwargs
, so I think the use here matches common expectations.
In addition to the semantics it e.g. enables IDEs that support it to show all parameters + any present doc strings supported by run_command
on these functions, without having to duplicate them.
That said, if you feel strongly about it, happy to change it. Just wanted to clarify that it was a deliberate choice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that strong. I still think the readability would be improved by naming - or removing that parameter as it is not used - but happy to merge the PR as-is wrt this.
Thanks a lot for the suggestions. Happy to discuss everything further of course. |
- Ensure that the key is trusted (signed by the Pacman machine key and not expired). | ||
type: bool | ||
default: false | ||
version_added: 10.7.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That ship has sailed, please update to
version_added: 10.7.0 | |
version_added: 11.0.0 |
Adds state
trusted
.Fixes #9949
SUMMARY
Adds functionality for
pacman_key
to check for key validity in terms of expiration and signature of the pacman machine key.Introduces a new
trusted
state for the new functionality in order to preserve current behavior ofpresent
, although this should arguably be merged intopresent
.Moves
gpg
/pacman-key
parsing into a trivial wrapper (GpgListResult
) and adds two helpers that either return a single match or a collection of all matches for a certaingpg
output item type.Moves binary invocations with common flags into module methods.
Replaces
--keyring
with equivalent--homedir
flag in order to allow for signature checks to work.ISSUE TYPE
COMPONENT NAME
pacman_key
ADDITIONAL INFORMATION
The existing test output already used an expired key, hence the
GPG_SHOWKEY_OUTPUT{ → _EXPIRED}
renaming of the variable holding the unchanged test data .