Add reencryption - #569
Add reencryption#569frederic-hoerni wants to merge 13 commits into
Conversation
d881e43 to
3815939
Compare
3815939 to
731117d
Compare
|
Would be awesome if this go cryptsetup api was in its own repository. u-root and other projects would benefit of this as well |
0ba1781 to
d6ced40
Compare
@tlaurion, we have no plan for that at the moment. |
81a124a to
a95d46c
Compare
There was a problem hiding this comment.
It lacks tests about Unmarshalling RecoveryToken and KeyDataToken with:
- 2 keyslots (nominal)
- 0 or 3 keyslots (error)
| "--resume-only", | ||
| "--progress-frequency", "1", | ||
| "--progress-json", | ||
| "--hotzone-size", "10M", |
There was a problem hiding this comment.
Please explain how/why this option and size "10M" are chosen.
How much does it impact performance? Does it have any other side effects?
| } | ||
|
|
||
| // NewOnlineReencryption implements [secboot.StorageContainerBackend.NewOnlineReencryption]. | ||
| func (b *storageContainerBackend) NewOnlineReencryption(activeName string) (secboot.Reencryption, error) { |
There was a problem hiding this comment.
Shouldn't this detect if the underlying cryptsetup supports reencryption?
Eg: DetectCryptsetupFeatures()&FeatureReencrypt
.. and return (nil, nil) if it does not?
There was a problem hiding this comment.
Returning (nil, nil) would make an unsupported cryptsetup version look like an unrecognized backend
| if len(token.Keyslots()) != 1 { | ||
| return nil, fmt.Errorf("token has %v keyslot(s)", len(token.Keyslots())) |
There was a problem hiding this comment.
Support for multiple keyslots is needed, for example for the "plainkey" mechanism.
Error seen after reencryption was interrupted:
./secboot-tool activate $DEVICE crypt02 30303030 -v
Error with keyslot "default": invalid key data: cannot read keyslot: cannot obtain reader for "default": token has 2 keyslot(s)
|
Please rebase on top of master (and expect conflicts). |
| func ReencryptInitialize(ctx context.Context, activeName string, unlockKeys [][]byte) error { | ||
| log.Debugf("ReencryptInitialize: unlockKeys=%v", unlockKeys) | ||
| sizes := []string{} | ||
| // Prepare concatenated keys | ||
| var allKeys []byte | ||
| for _, unlockKey := range unlockKeys { | ||
| sizes = append(sizes, strconv.Itoa(len(unlockKey))) | ||
| allKeys = append(allKeys, unlockKey...) | ||
| } | ||
| log.Debugf("ReencryptInitialize: sizes=%v", strings.Join(sizes, ",")) | ||
| log.Debugf("ReencryptInitialize: keys=%v", allKeys) |
There was a problem hiding this comment.
Won't this write every unlock key to stderr when debug logging is enabled?
| func (r reencryptionImpl) Resume(ctx context.Context, unlockKey []byte) (<-chan secboot.ReencryptionProgressEvent, error) { | ||
| outChan := make(chan secboot.ReencryptionProgressEvent) | ||
|
|
||
| cmd, stdout, stderr, err := luks2.ReencryptResume(ctx, r.dmActiveName, unlockKey) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| go superviseReencryption(cmd, stdout, stderr, outChan) |
There was a problem hiding this comment.
Deactivation will select the first matching device. The existing lookup assumes one mapping per container. With this layout, it selects data-hotzone-forward instead of data`.
Maybe we should either change that or pass the active name directly?
| Status() (*ReencryptionStatus, error) | ||
| Initialize(ctx context.Context, unlockKeys map[string][]byte) error | ||
| Resume(ctx context.Context, unlockKey []byte) (<-chan ReencryptionProgressEvent, error) | ||
| } |
| func (v *mockLuksView) TokenNamesSortedByKeyslotId() ([]string, error) { | ||
| if v.data == nil { | ||
| return nil, errors.New("error while getting token names") | ||
| } | ||
|
|
||
| names := []string{} | ||
| for name, _ := range v.data.recoveryKeyslots { | ||
| names = append(names, name) | ||
| } | ||
| for name, _ := range v.data.platformKeyslots { | ||
| names = append(names, name) | ||
| } | ||
|
|
||
| return names, nil |
There was a problem hiding this comment.
The mock is returning names in map order but the tests expect a fixed order. This can lead to flaky tests, we should not allow this sort of indeterminism
| details, err := decodeReencryptionProgressDetails(rawBytes) | ||
| if err != nil { | ||
| outChan <- secboot.ReencryptionProgressEvent{Type: secboot.ReencryptionProgressRunning, Error: err} | ||
| } else { | ||
| outChan <- secboot.ReencryptionProgressEvent{Type: secboot.ReencryptionProgressRunning, Details: details} | ||
| } | ||
| } | ||
| err := scanner.Err() | ||
| if err != nil { | ||
| outChan <- secboot.ReencryptionProgressEvent{Type: secboot.ReencryptionProgressRunning, Error: err} |
There was a problem hiding this comment.
Progress sends can block after a caller cancels and stops reading. Can this be tested? (just a hypothetical but a risk nonetheless)
There was a problem hiding this comment.
It is up to the caller to make sure that they read up to the end.
I will add documentation about it.
| tokenNames, err := view.TokenNamesSortedByKeyslotId() | ||
| log.Debugf("Initialize: tokenNames: %v", tokenNames) | ||
|
|
||
| if len(tokenNames) == 0 { | ||
| return fmt.Errorf("cannot get any token name") |
There was a problem hiding this comment.
return err here otherwise we are hiding the true error
| ks.keyslotId = token.Keyslots()[0] | ||
| } else { | ||
| // Two keyslots associated with this token (happens when reencryption is in progress) | ||
| ks.keyslotId = internal_luks2.AnySlot |
There was a problem hiding this comment.
Doesn't this break the detection done above?
if ks.keyslotId != internal_luks2.AnySlot {
// We already have everything for this keyslot.
There was a problem hiding this comment.
reworked and fixed
| // | ||
| // activeName: name of the active device mapper volume | ||
| // unlockKeys: all unlock keys (passphrases) of the LUK2 container, in the order of the keyslots | ||
| func ReencryptInitialize(ctx context.Context, activeName string, unlockKeys [][]byte) error { |
There was a problem hiding this comment.
Doesn't this require StorageContainer.OpenRead() and StorageContainer.Activate() to acquire a shared lock, so that they do not get their cached data outdated by ReencryptInitialize()?
There was a problem hiding this comment.
done for StorageContainer.OpenRead()
No need identified for StorageContainer.Activate()
This adds the feature of reencryption of active LUKS2 containers.
Main changes: