Conversation
Co-authored-by: Terts Diepraam <terts.diepraam@gmail.com>
- Adds KMIP server based key generation, signing and destruction, equivalent to the existing Ring/OpenSSL functionality. - Adds new kmip subcommands for managing KMIP server configurations. - Adds support for referring to KMIP keys by a new KMIP URL scheme. - Add a feature for the KMIP crypto backend just like the Ring and OpenSSL crypto backends. - Adds support for storing sensitive credentials in files separate to the KMIP server configuration.
…port to the proposed channel at packages.nlnetlabs.nl.
* Restructure roll commands. * Import public keys. * Import a public/private key pair from files. * Add a default TTL to config. Use that for DNSKEY/CDS/CDNSKEY/DS RRsets. * Cargo.lock. * Support for importing KMIP keys. * Import public/private keys in decoupled state * Add --private-key option to importing a public/private key pair from files. * Add remove-key command.
#138) * Introduce a 'WorkSpace' object to keep the current working state of keyset. * Fallout from merging. * Switch to the crypto-and-keyset-fixes branch in domain for the time being. Disable kmip because the branch does not support that. * Update lock file. * Bump Rust version to 1.85 because domain is at 1.85.0. * Switch to domain-kmip. (#142) * Switch to domain-kmip. * Use https URL for domain-kmip. * Update for domain-kmip change. * Switch to main branch of domain-kmip. * Clippy. * Bump Rust version to 1.88 for kmip-protocol.
| tracing-subscriber = "0.3.19" | ||
| url = "2.5.4" | ||
| futures = "0.3.31" | ||
| fs2 = "0.4.3" |
There was a problem hiding this comment.
I'm wondering if this is a good choice of dependency. What were your reasons for selecting it?
I ask because:
- Krill uses https://lib.rs/crates/fd-lock.
- fs2 has a single maintainer.
- That maintainer hasn't responded to issues in the GH repo since 2017 as far as I can see.
- fs2 hasn't had a release in 8 years (though if it "just works" it may not need any).
- https://stackoverflow.com/questions/27469928/is-there-file-locking-in-rust#comment132066314_45040455 says:
- (a) Rust has https://doc.rust-lang.org/stable/std/fs/struct.File.html#method.lock and,
- (b) that there is more maintained fork of fs2 at https://github.com/al8n/fs4-rs.
- Conversely, I see that fd2 seems to be the most widely used file locking crate of some I locked at (fs2, fd-lock and file-lock).
There was a problem hiding this comment.
I'll leave it at fs2 for now. If fs2 gets less popular or picks up security issues then we can switch. The interface of fd-lock looks scary.
src/commands/keyset/cmd.rs
Outdated
| /// locking the file, the function has to check if the locked file is this | ||
| /// the current fine under that name. |
There was a problem hiding this comment.
This comment has some typos and grammatical issues.
There was a problem hiding this comment.
Updated to:
/// Assume changes are saved by creating a new file and renaming. After
/// locking the file, the function has to check if the locked file is the
/// same as the current file under that name.
| // The config file is updated by writing to a new file and the renaming. | ||
| // Might have locked the old file. Check. Try a number of times and | ||
| // then give up. Lock contention is expected to be low. | ||
| for _try in 0..MAX_FILE_LOCK_TRIES { |
There was a problem hiding this comment.
Is it okay to retry lock acquisition with no delay between attempts?
There was a problem hiding this comment.
This will lead to the thundering herd problem. But concurrent access to keyset for a single domain is expected to be very low. We need to do something if we ever fail to get a lock within MAX_FILE_LOCK_TRIES.
| let file = File::open(filename) | ||
| .map_err(|e| format!("unable to open file {}: {e}", filename.display()))?; | ||
|
|
||
| file.lock_exclusive() |
There was a problem hiding this comment.
According to the documentation this call will block if the file is currently locked, is that desirable or would it be better to call try_lock_exclusive() instead?
There was a problem hiding this comment.
For this PR the goal is to block. Maybe the UI needs to be improved in the future.
| filename.display() | ||
| ) | ||
| })?; | ||
| let current_file_handle = Handle::from_path(filename) |
There was a problem hiding this comment.
You open a file by path, lock it and get its handle, then get another handle for the same path and compare the two handles. Why? What is this handle comparison achieving/intended to do?
There was a problem hiding this comment.
This is explained by the comment at the top of the function. Maybe it needs more text. The idea is that because the config is saved by creating a new file and then renaming that file, it is possible to endup with a lock on the old config file that no longer exists. So check that we have locked to current config file.
There was a problem hiding this comment.
You only call write_locked_file() once, so any rename that happens within your code when the config file is saved won't be detected later by this logic, at least currently. So is this for the some future potential second call to this function after saving/renaming of the config file?
Also, when this test fails you call continue so you go round the loop again, dropping the file you just opened and the lock you just acquired, and try re-opening the file by the same filename as you just tried, even though that succeeded. I don't understand what this achieves.
If this function were called while a second concurrent invocation of dnst keyset was busy saving the config file, one would be able to open the file then block until the lock can be acquired, is this the issue, that you would then be possibly be opening the old FD? (though what happens to that FD during the rename/once the rename completes, is it still valid?)
There was a problem hiding this comment.
After a rename, any processes that run keyset and that were trying to lock the config file now try to lock the wrong file. So they need to start again. The logic of keyset is that it gets a lock on the config file, and then saves the state file if it changed and saves the config file if it changed. There will not be a second call to lock the config file in a single run of keyset.
| /// | ||
| /// First write to a new filename and then rename to make sure that | ||
| /// changes are atomic. | ||
| fn write_config(&self, keyset_conf: &PathBuf) -> Result<(), Error> { |
There was a problem hiding this comment.
This and fn write_state() look almost identical, I would suggest factoring their core logic out into a single helper function, especially as they both contain the same comment and work around for lacking -fn add_extension() support.
src/commands/keyset/cmd.rs
Outdated
|
|
||
| let mut file = File::create(&conf_file_new) | ||
| .map_err(|e| format!("unable to create file {}: {e}", conf_file_new.display()))?; | ||
| write!(file, "{json}") |
There was a problem hiding this comment.
If some reason creation, writing or renaming fail, the created .new file will be left behind, right? Is that a problem? Subsequent attempts would fail I assume as the created file already exists. Should you attempt to remove the file in such scenarios?
There was a problem hiding this comment.
I don't expect create to fail if a file with the same name already exists.
src/commands/keyset/cmd.rs
Outdated
| /// Assume changes are saved by creating a new file and renaming. After | ||
| /// locking the file, the function has to check if the locked file is this | ||
| /// the current fine under that name. | ||
| fn write_locked_file(filename: &PathBuf) -> Result<File, Error> { |
There was a problem hiding this comment.
I find the name of this function confusing, as "write" reads like a verb yet this function does neither opens the file for writing nor writes to it.
There was a problem hiding this comment.
Changed to file_with_write_lock
Co-authored-by: Ximon Eighteen <3304436+ximon18@users.noreply.github.com>
Fixes #150.