Skip to content
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

Implement openpgp.cert.d based keystore #3437

Merged
merged 2 commits into from
Nov 12, 2024

Conversation

ffesti
Copy link
Contributor

@ffesti ffesti commented Nov 8, 2024

Refactor code from the fs backend into shared helper functions

This does implement the layout on the file system and the write lock of the openpgp.cert.d proposal according to
https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html but not the Trust root, Petname mapping or Trusted introducers.

This still is a mess of C and C++ style strings that we want to clean up later by adding C++ string based path handling and may be using the filesystem C++ library.

Resolves: #3341

@ffesti ffesti requested a review from a team as a code owner November 8, 2024 13:07
@ffesti ffesti requested review from dmnks and removed request for a team November 8, 2024 13:07
lib/keystore.cc Outdated

/*****************************************************************************/

static rpmRC acquire_write_lock(rpmtxn txn)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The txn handle is the lock, that's the whole point of those things. There's no other locking needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know. But the standard requires a lockfile on disk as part of the openpgp.cert.d format. If we want other tools to be able to read it we should try to adhere to that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see concurrent access with other tools being something we need to support. Poking at rpm directories with external tools is more like a developer convenience thing rather than something we'd be suggesting to users. But, if you want to support locking then the actual lock seems to be missing, https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html#platform-specifics says it should use flock().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...ie it's not the existence of 'writelock' file that is the lock, but a flock() on that file. Mere file existence would be broken lock mechanism because if the process dies in the middle it de-facto leaves a stale lock behind, whereas locks on the file are cleaned up by the OS.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

urgs should have read the last sentence, too...
If I read that correctly you are not actually required to delete writelock the file again. Just to be fine if it doesn't exist. @nwalfield any opinion on that?

Anyway implemented the flock and adjusted the test case.

Copy link
Member

@pmatilai pmatilai Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I read that correctly you are not actually required to delete writelock the file again. Just to be fine if it doesn't exist.

Yup, that's how such lockfiles commonly work, including our own transaction lock. Only the term "lockfile" is misleading because it makes you think the file itself is the lock, when it's only a placeholder for it.

@@ -275,6 +275,8 @@ static keystore *getKeystore(rpmts ts)
ts->keystore = new keystore_fs();
} else if (rstreq(krtype, "rpmdb")) {
ts->keystore = new keystore_rpmdb();
} else if (rstreq(krtype, "openpgp")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've a feeling we want to leave this name for a proper implementation from Sequoia side, this being a simple standalone version that does not deal with the trust stuff. But lets bikeshed that in the ticket instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I am not too sold on the name either.

This will be useful for the next commit.

This still is a mess of C and C++ style strings that we want to clean
up later by adding C++ string based path handling and may be using the
filesystem C++ library.

Related: rpm-software-management#3341
@pmatilai
Copy link
Member

pmatilai commented Nov 11, 2024

Another random thought on standard compliance: since this does not process trust-root, it should probably emit a warning if that file exists (eg from doing stuff with an external tool) to make it clear it's not handled.

Oh, and on a related note: since the interoperability with other tools is a goal, what better test than actually do so, ie check that sq can read what we produced.

RPMTEST_CLEANUP
# ------------------------------
# Test rpmkeys write errors
AT_SETUP([[rpmkeys -K no space left on stdout]])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a copy-paste leftover, we already have such a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted.

Copy link
Contributor

@dmnks dmnks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, some comments in line.

lib/keystore.cc Outdated Show resolved Hide resolved
return RPMRC_FAIL;

string fp = rpmPubkeyFingerprintAsHex(key);
string dir = fp.substr(0, 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This routine of obtaining the storage location (where the first two hex chars are the directory name) is a specific thing in this keystore implementation and it's currently duplicated in two places so maybe deserves a separate private method.

That said, we can always do that later, when there's the third use, so feel free to ignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have considered that, but it doesn't really make things that much better. Guess it really is missing the third place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me.

@@ -275,6 +275,8 @@ static keystore *getKeystore(rpmts ts)
ts->keystore = new keystore_fs();
} else if (rstreq(krtype, "rpmdb")) {
ts->keystore = new keystore_rpmdb();
} else if (rstreq(krtype, "openpgp")) {
ts->keystore = new keystore_openpgp_cert_d();
Copy link
Contributor

@dmnks dmnks Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class name seems a bit verbose to me, how about just keystore_openpgp? I suppose this would be the defacto standard openpgp keystore implementation so it doesn't have to be more specific than that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind the above; I've realized that openpgp is way too generic, this whole signature business is about OpenPGP (at least keystore_rpmdb is, too) so just keep the name as is, it's good.

string fp = rpmPubkeyFingerprintAsHex(key);
string dir = fp.substr(0, 2);
string filename = fp.substr(2);
char * filepath = rpmGetPath(rpmtxnRootDir(txn), "%{_keyringpath}/", dir.c_str(), "/", filename.c_str(), NULL);
Copy link
Contributor

@dmnks dmnks Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a mixed use of rpmGetPath() and rpmGenPath() in the keystore.cc module. Is there a reason for preferring one over the other in those places?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, rpmGenPath was originally used. But as it only supports 3 pieces I used rpmGetPath here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, thanks.

@dmnks
Copy link
Contributor

dmnks commented Nov 11, 2024

Insertion doesn't seem to follow the spec:

  • It says the key being inserted should be merged with an existing key with that fingerprint, if found. Currently, it's just inserted, never looked up beforehand.
  • It should write to a temporary file and rename(2) it. The current keystore interface has the replace argument that defaults to 1 in the openpgp implementation, however I wonder if there's a point in allowing that to be 0 in the first place, given the spec.

@dmnks
Copy link
Contributor

dmnks commented Nov 11, 2024

Insertion doesn't seem to follow the spec:

* It says the key being inserted should be merged with an existing key with that fingerprint, if found. Currently, it's just inserted, never looked up beforehand.

That said, it's fine if this is not the goal of this (initial) implementation, in that case having a short note in the commit message would suffice.

@ffesti
Copy link
Contributor Author

ffesti commented Nov 11, 2024

As people don't believe me otherwise: rpmtxnImportPubkey does indeed merge keys...

This is a low level storage interface. A lot of functionality about handling keys is in other parts of the code. E.g. the keyring and the rpmts and rpmtxn key handling functions.

@dmnks
Copy link
Contributor

dmnks commented Nov 11, 2024

As people don't belive me otherwise: rpmtxnImportPubkey does indeed merge keys...

Oh, so it's handled at the level above the keystore itself. Checking that didn't occur to me.

I did notice your reply to Neal above regarding "merging is handled" but didn't connect the dots, so thanks for the context!

This does implement the layout on the file system and the write lock of
the openpgp.cert.d proposal according to
https://www.ietf.org/archive/id/draft-nwjw-openpgp-cert-d-00.html
but not the Trust root, Petname mapping or Trusted introducers.

Resolves:  rpm-software-management#3341
lib/keystore.cc Show resolved Hide resolved
@dmnks
Copy link
Contributor

dmnks commented Nov 12, 2024

The latest version you've pushed looks fine. The only remaining questions are:

  • Do we care that replace can be set to 0 and thus not use rename(2) for insertion? The latter is mandated by the spec, although it seems redundant, with the writelock in place. and seems to be key for ensuring atomic read access (the writelock is only for writing)
  • How do we externally name the store

@dmnks
Copy link
Contributor

dmnks commented Nov 12, 2024

Replying to the first question myself:

rpmtxnImportPubkey() is where replace is decided, based on whether the key can be found. That kinda alleviates the need for an atomic rename since only new keys would be inserted with it disabled.

That still doesn't ensure true atomicity but this is the best we can do without refactoring rpmtxnImportPubkey() or other parts. So I guess it's fine.

@ffesti
Copy link
Contributor Author

ffesti commented Nov 12, 2024

Well, it kinda does. By not replacing you make sure you fail if a key was added in the mean time instead of just overwriting it without merging.

Also as far as rpm is concerned we do have the transaction log (which is represented by the txn parameter passed down) whihc protects parallel access from other rpm instances.

@ffesti
Copy link
Contributor Author

ffesti commented Nov 12, 2024

I would suggest to not get hung up on the names especially as long as they are only internal. We might even still change the name in the configuration as soon as we know what back ends we'll end up with and what exactly they do.

@dmnks
Copy link
Contributor

dmnks commented Nov 12, 2024

Ack, let's merge this then.

@dmnks dmnks merged commit 6e19c16 into rpm-software-management:master Nov 12, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement a new openpgp.cert.d based keystore
4 participants