Skip to content

many: add reprovision API - #17034

Merged
sergiocazzolato merged 1 commit into
canonical:masterfrom
valentindavid:valentindavid/reprovision-api
Jul 24, 2026
Merged

many: add reprovision API#17034
sergiocazzolato merged 1 commit into
canonical:masterfrom
valentindavid:valentindavid/reprovision-api

Conversation

@valentindavid

@valentindavid valentindavid commented May 7, 2026

Copy link
Copy Markdown
Member

No description provided.

@valentindavid valentindavid added the Run nested The PR also runs tests inluded in nested suite label May 7, 2026
@github-actions github-actions Bot added Needs Documentation -auto- Label automatically added which indicates the change needs documentation Run only one system Only runs spread tests on one system labels May 7, 2026
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown

Fri Jul 24 04:38:05 UTC 2026
The following results are from: https://github.com/canonical/snapd/actions/runs/30045262895

Failures:

Preparing:

  • openstack-ext:ubuntu-22.04-64:tests/nested/manual/snapd-removes-vulnerable-snap-confine-revs:snapd
  • openstack-ext:ubuntu-22.04-64:tests/nested/manual/snapd-removes-vulnerable-snap-confine-revs:core
  • openstack-arm-ext:ubuntu-24.04-arm-64:tests/nested/manual/optee-fde:optee
  • openstack-arm-ext:ubuntu-24.04-arm-64:tests/nested/core/core20-basic
  • openstack-arm-ext:ubuntu-24.04-arm-64:tests/nested/manual/optee-fde:hooks
  • openstack-arm:ubuntu-24.04-arm-64:tests/main/snap-seccomp-blocks-certain-creat
  • openstack-arm:ubuntu-24.04-arm-64:tests/main/snap-seccomp-blocks-certain-mknod

Executing:

  • openstack:centos-9-64:tests/main/prepare-image-validation-sets
  • openstack:opensuse-tumbleweed-64:tests/main/services-watchdog
  • openstack:opensuse-tumbleweed-64:tests/main/manpages
  • openstack:ubuntu-22.04-64:tests/main/lxd-mount-units

Skipped tests from snapd-testing-skip

If you wish to have any of the below tests run in your PR, in your PR description, add 'unskip:' followed by a copy-and-pasted list of the below tests you wish to run (unskip plus test list must be valid yaml)

  • garden:ubuntu-25.10-64:tests/main/apparmor-prompting-support
  • openstack-arm:ubuntu-24.04-arm-64:tests/main/i18n
  • openstack-arm:ubuntu-core-24-arm-64:tests/main/i18n
  • openstack:debian-sid-64:tests/main/interfaces-network-status-classic
  • openstack:debian-sid-64:tests/main/interfaces-xdg-portal-permission-store
  • openstack:ubuntu-24.04-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-24.04-64:tests/main/apparmor-prompting-integration-tests
  • openstack:ubuntu-24.04-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-24.04-64:tests/main/apparmor-prompting-smoke
  • openstack:ubuntu-24.04-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-24.04-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-24.04-64:tests/main/i18n
  • openstack:ubuntu-24.04-64:tests/main/interfaces-requests-activates-handlers
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-integration-tests
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-smoke
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-25.10-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-25.10-64:tests/main/interfaces-requests-activates-handlers
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-flag-restart
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-integration-tests
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-prompt-restoration
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-smoke
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-snapd-startup
  • openstack:ubuntu-26.04-64:tests/main/apparmor-prompting-support
  • openstack:ubuntu-26.04-64:tests/main/i18n
  • openstack:ubuntu-26.04-64:tests/main/interfaces-requests-activates-handlers

@valentindavid
valentindavid force-pushed the valentindavid/reprovision-api branch 6 times, most recently from f8dc8f0 to 02ddab7 Compare June 1, 2026 08:55
@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.07539% with 153 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.16%. Comparing base (ba46c97) to head (c29b9b0).

Files with missing lines Patch % Lines
overlord/devicestate/handlers_reprovision.go 64.61% 80 Missing and 41 partials ⚠️
secboot/secboot_sb.go 46.15% 14 Missing and 7 partials ⚠️
secboot/secboot_tpm.go 73.68% 4 Missing and 1 partial ⚠️
boot/assets.go 0.00% 3 Missing ⚠️
daemon/api_systems.go 82.35% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master   #17034       +/-   ##
===========================================
+ Coverage   78.92%   89.16%   +10.23%     
===========================================
  Files        1398     1396        -2     
  Lines      196163   382473   +186310     
  Branches     2462     2462               
===========================================
+ Hits       154830   341040   +186210     
- Misses      32045    32105       +60     
- Partials     9288     9328       +40     
Flag Coverage Δ
unittests 89.16% <66.07%> (+10.23%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@valentindavid
valentindavid force-pushed the valentindavid/reprovision-api branch 2 times, most recently from a8debde to 7c3be44 Compare June 3, 2026 12:03
@valentindavid
valentindavid force-pushed the valentindavid/reprovision-api branch 13 times, most recently from 4a6a91e to 4df1ded Compare June 15, 2026 14:55
@valentindavid
valentindavid force-pushed the valentindavid/reprovision-api branch from 4df1ded to dc377e6 Compare June 17, 2026 15:12
@valentindavid
valentindavid force-pushed the valentindavid/reprovision-api branch 2 times, most recently from dcb3525 to dab10a3 Compare June 26, 2026 14:11
@valentindavid
valentindavid requested a review from pedronis July 14, 2026 09:59

@pedronis pedronis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thanks, gave this a deeper look, some comments and some questions.

Also a LLM review futther noted this:

Rollback/cleanup state isn't asserted. The error tests (e.g. TestDoReprovisionMakeRunnableError) only assert the returned error; none verify that cleanup() renamed snapd-reprovision-* keys back to their originals and removed bootstrap keys. Since cleanup is the core safety mechanism, its resulting key state should be asserted on at least one error path.


# TODO:FDEM: Grow test to check that we can reprovision and reseal
# then reboot without recovery keys
remote.exec "sudo snap debug api /v2/systems?running=true 2>/dev/null" | gojq '.result["storage-encryption"].support' | MATCH "available"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ah, this is incomplete because we haven't added the reprovision state yet?

Comment thread overlord/devicestate/handlers_reprovision.go
return err
}
if oldKeyMatches {
// We must have been restarted in the middle. The backed up keys are the correct ones.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you mention setp 6 in here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what should happen if we are past step 6, shouldn't we just do the clean ups?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

"cleanup" reverts back to the old keys. So no, once we have passed step 6, we are committed. We will try to remove the old keys. But if we fail before, they will be there still.

If we retry, we will reprovision again, because we are done with reprovision, all the keys new keys are in place and the old keys will just be ignored.

We could maybe add the primary key digest to the task maybe so that we can detect that we are done, and we jump to directly to the other cleanup (the clean up of old keys). We just have to verify the plainkey recovered recovered the new primary key, then we know we have switched.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you leave a comment about what happens now in that case and also a TODO

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I will also rename "cleanup" to "revertReprovisionAttempt" so it is a bit more clear.

volumesAuth,
checkResult)

err = bootMakeRunnableReprovision(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

does this do the right things with the auth value?

a. Create and persist a new storage primary key and endorsement key, if required.
b. Rotate the authorization value for the lockout hierarchy as described in Rotating the TPM lockout hierarchy authorization value.
b. Using the new lockout hierarchy authorization value, configure the dictionary attack parameters as configured by the provision operation.
c. Create a new NV counter index for PCR policy revocation, if required.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It will call tpm provision function in partial mode. Which should do the 3 first points (at least the rotation and configuration of DA, I can double check the first point).

The nv counter is created when sealing objects if I remember correctly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please double check, thx

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

EnsureProvisioned is the method that sets up the lockout hierarchy, DA, as well as the SRK and EK. So the 3 first points are done together in that function.

The last one we do it in overlord/fdestate/backend/seal.go. We just look up for a free index in a range, and use that for the calls to create new keys.

Comment thread overlord/devicestate/handlers_reprovision.go
Comment thread secboot/secboot_sb.go
return false, err
}

containerReader, err := container.OpenRead(ctx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we call Close on this later on?

Comment thread overlord/devicestate/devicemgr.go
Comment thread overlord/devicestate/devicestate.go Outdated
Comment on lines +2710 to +2711
chg := st.NewChange(reprovisionKind, fmt.Sprintf("Reprovision"))
reprovisionTask := st.NewTask("fde-reprovision", fmt.Sprintf("Reprovision"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reprovision encrypted disks perhaps?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we stop starting a reprovision though if there is already one running?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I am not totally sure what you meant. Should we refrain from starting a new reprovision? Or should we stop the previous ones?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sorry, I meant make sure that there is at most one reprovision running, so return a conflict error if there is one still going and not yet finished

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added a call to checkFDEChangeConflict which will prevent from a conflicting reprovision task.


st := t.State()
st.Lock()
defer st.Unlock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

related to conflict and the fact that now this will not run concurrently to other seal/resealing tasks should we unlock during some of it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We should probably unlock while sealing yes. However, reprovision happens in very special circumstance. Resealing happens very often. So maybe we can wait and see if people complain that snapd is not responsive while they are repairing their installation. I do not expect anybody to care. Maybe I am wrong.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am also in favor of defensive state locking until someone complains.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let's leave a TODO

Comment thread daemon/api_systems.go Outdated

chg, err := devicestateReprovision(st)
if err != nil {
return BadRequest("unexpected error: %s", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we probably need to do proper error mapping as we might want to return conflicts error from Reprovision

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done.

@ZeyadYasser ZeyadYasser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

did a pass, I will try to do another pass tomorrow morning on the handler code

Comment thread overlord/devicestate/devicemgr.go
Comment thread overlord/devicestate/devicestate.go
Comment thread overlord/devicestate/devicestate.go Outdated
Comment thread overlord/devicestate/devicestate.go Outdated
Comment thread overlord/fdestate/fdestate.go Outdated
Comment thread secboot/secboot_sb.go
Comment on lines +663 to +664
sbSetProtectorKeys(protectorKey)
defer sbSetProtectorKeys()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

as this is global state in secboot, this needs to be guarded with an external lock. is this usually called with the state lock held? maybe updating the doc comment to reflect this to callers is enough.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we should change that in secboot. I do not think it makes sense to have it as a global state.

Comment thread secboot/secboot_tpm.go

@ZeyadYasser ZeyadYasser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

did a pass over the re-provision handler

return fmt.Errorf("multiple containers found with role system-data")
}
dataDisk = disk
case "system-save":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we add a TODO comment that if we later support other containers (e.g. user containers) this would need to be updated

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Are we going to use the same keys for user containers (specifically, the same recovery key)? This can probably be reprovisioned separately.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Also there is a big difference between a disk where we take system configuration from and a disk we do not. Because for the system ones we need lock them with PCRs then to specific context within the initrd. Whereas non system disks do not have the same restrictions. Which means we probably do not need the same pcr profiles.

What do you have in mind with user containers?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think there are any designs for user containers, but I remember it was something that might be considered in the future.

continue
}
// This rename replaces the rename.from if it exists
if err := secbootRenameContainerKey(disk, rename.to, rename.from); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wouldn't this fail if rename.to and rename.from both exist?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We only log a warning.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Let me double check. I think it should be just overwriting

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You are right. I will have to fix that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Should be fixed now.

if rename.from == "default" && disk == saveDisk.DevPath() {
continue
}
// This rename replaces the rename.from if it exists

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why are we not removing leftover keyslots before renaming?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Should be fixed now.

Comment thread overlord/devicestate/handlers_reprovision.go
// Step 1. rename existing keyslots that we will overwrite
for _, rename := range renames {
for _, disk := range []string{dataDisk.DevPath(), saveDisk.DevPath()} {
if err := secbootRenameContainerKey(disk, rename.from, rename.to); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

instead of ignoring failures due to potentially missing keyslots due to a re-run, you could obtain the current subset of keyslots given a set using m.GetKeyslots(keyslotRefs), this way we could simple hard error here.

something like:

// we only care about current key slots because this might be
// a re-run due a force reboot or abrupt shutdown, so we want
// to continue renaming the remaining key slots.
currentKeyslots, _, err := m.GetKeyslots(keyslotRefs)
if err != nil {
return fmt.Errorf("cannot get key slots: %v", err)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That is a "time of check to time of use" pattern. I prefer to try and fail.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sorry... try and warn.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Alright, I think I see the potential issues here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I now check for existing keys, and skip rename if it did not exist.

for _, rename := range renames {
for _, disk := range []string{dataDisk.DevPath(), saveDisk.DevPath()} {
if err := secbootRenameContainerKey(disk, rename.from, rename.to); err != nil {
logger.Noticef("WARNING: could not rename %s to %s on %s", rename.from, rename.to, disk)

@ZeyadYasser ZeyadYasser Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wrote a comment #17034 (comment), we already have a mechanism for detecting the target keyslots to be robust against re-runs.

Comment thread overlord/devicestate/handlers_reprovision.go Outdated
Comment thread overlord/devicestate/handlers_reprovision.go
Comment on lines +344 to +346
if err := secbootSaveCheckResult(setupData.checkContext, device.PreinstallCheckResultUnder(dirs.SnapSaveDir)); err != nil {
return err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This alters reality a bit, "preinstall" file means something else now after reprovisioning. I don't have strong opinions here but might be worth writing a comment highlighting this fact.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Though "preinstall" is not correct for that file name. It is more of "run check context". We cannot really rename that file. I will comment it.

@valentindavid valentindavid Jul 15, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added a comment in gadget/device/encrypt.go

In general, we should do a refactoring all the "preinstall" and rename most of them. But that is going to be a big refactoring-only PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agreed

@valentindavid valentindavid added this to the 2.77 milestone Jul 15, 2026
Comment thread overlord/fdestate/conflict.go

st := t.State()
st.Lock()
defer st.Unlock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am also in favor of defensive state locking until someone complains.

return fmt.Errorf("multiple containers found with role system-data")
}
dataDisk = disk
case "system-save":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think there are any designs for user containers, but I remember it was something that might be considered in the future.

continue
}
if err := secbootDeleteContainerKey(disk, rename.to); err != nil {
logger.Debugf("could remove %s on %s", rename.to, disk)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
logger.Debugf("could remove %s on %s", rename.to, disk)
logger.Debugf("could not remove %s on %s", rename.to, disk)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick, Usually errors are prefixed with cannot ....

}
for _, rename := range renames {
if hasPlatformKeyslot[rename.to] && hasPlatformKeyslot[rename.from] {
nv, err := secbootGetPCRHandleFromToken(disk, rename.from)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

step 1 says erasing of NV index should be done for the new keys, I am confused why rename.from is passed.

1. Erase the nv indices from new keys that were not used

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

During revert cleanup "from" and "to" are inverted. Maybe I should call them "newName" and "oldName".

if err := secbootDeleteContainerKey(disk, rename.to); err != nil {
logger.Debugf("could remove %s on %s", rename.to, disk)
}
if err := secbootRenameContainerKey(disk, rename.to, rename.from); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This will now always fail since rename.to is deleted above, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oops, yes, we should remove "rename.from" (the new key).

@pedronis pedronis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

some minor things, I still need to re-review the main task handler

Comment thread overlord/devicestate/devicestate.go Outdated
return nil, err
}

chg := st.NewChange(reprovisionKind, fmt.Sprintf("Reprovision"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: the summary should be the same as the task I think

Comment thread overlord/fdestate/conflict.go
Comment thread overlord/fdestate/conflict.go
@pedronis
pedronis self-requested a review July 16, 2026 11:37

@pedronis pedronis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

did another pass, minor things plus test questions, do we need test that run doReprovision twice one after another for various scenarios in the first of the two?


st := t.State()
st.Lock()
defer st.Unlock()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let's leave a TODO

}
}
}
// This one always need to be the last one in case we crash

@pedronis pedronis Jul 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// the save disk default key needs to be handled last outside the loop in case we crash

perhaps

// For example due to previous run not cleaned up.
// We know it is not a key that is still in use because we
// checked snapd-reprovision-default previously.
logger.Debugf("cannot delete %s on %s: %v", rename.old, disk, err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can't we ue the known keys information to produce slightly more precise logs?

}

if key == "snapd-reprovision-default" && disk == saveDisk.DevPath() {
// always the last one to remove

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

..., done after the loop

continue
}
if err := secbootDeleteContainerKey(disk, rename.new); err != nil {
logger.Debugf("cannot remove %s on %s", rename.old, disk)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this should say rename.new?

// remediate the issues.
func Reprovision(st *state.State) (*state.Change, error) {
if err := fdestate.CheckFDEChangeConflict(st); err != nil {
return nil, err

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

also a test getting here, and also checking the reverse case of something else against reprovision being present, unless that's covered in fdestate

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is also tested in overlord/fdestate/conflict_test.go.

Comment thread overlord/devicestate/handlers_reprovision.go
defer st.Lock()
return devicestate.DoReprovision(s.mgr, t)
}()
c.Assert(err, ErrorMatches, "protector key failed")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't we check dataKeys and saveKeys here and in similar error tests?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have refactored to make the check for rollback a function and added it everywhere where it fails.

Comment thread overlord/devicestate/handlers_reprovision.go
defer devicestate.MockBootMakeRunnableReprovision(func(model *asserts.Model, protector secboot.KeyProtectorFactory, encryption *boot.EncryptionSetup) error {
bootMakeRunnableReprovisionCalls++

// TODO: check primary key

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we address this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done

@pedronis pedronis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looking good up to the known TODOs, one question about reverts


// Step 6. write the protector key
if err := keysSaveProtectorKey(protectorKey, saveKeyPath); err != nil {
return fmt.Errorf("cannot save the system-save key: %v", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@valentindavid if we fail here we will still revert but we will need to reboot/restart to recreate the state that we lost above, should we save the state when nil, to put it back if we hit this case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That can work.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done

@valentindavid
valentindavid requested a review from pedronis July 17, 2026 13:33

@ZeyadYasser ZeyadYasser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall looks really good, Thank you!

My only concerns are about cleanup of recovery-keys and edge-case of double re-runs.

Comment thread boot/assets.go
if oldKeyMatches {
// We must have been restarted in the middle, before step 6.
// The backed up keys are the correct ones, we need to cancel that previous
// attempt and them back to expected name.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
// attempt and them back to expected name.
// attempt and then go back to expected name.

Comment thread overlord/devicestate/handlers_reprovision.go
Comment thread overlord/devicestate/handlers_reprovision.go
Comment thread overlord/devicestate/handlers_reprovision.go
Comment thread overlord/devicestate/handlers_reprovision.go
{"default-recovery", "snapd-reprovision-default-recovery"},
}

// TODO: we show allow unlocking during sealing since hashing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
// TODO: we show allow unlocking during sealing since hashing
// TODO: we should allow unlocking during sealing since hashing

Comment thread overlord/devicestate/handlers_reprovision.go

@ZeyadYasser ZeyadYasser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, Thank you!

@pedronis pedronis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thanks

@valentindavid
valentindavid force-pushed the valentindavid/reprovision-api branch from b6782d5 to ea75e9c Compare July 20, 2026 11:18
@github-actions github-actions Bot added the Auto rerun spread Auto reruns spread up to 4 times in non-draft PRs w/ >=1 approval and <20 fails in any fund. system label Jul 20, 2026
@valentindavid
valentindavid force-pushed the valentindavid/reprovision-api branch 3 times, most recently from 9a17d26 to 484b15e Compare July 23, 2026 08:49
@valentindavid
valentindavid force-pushed the valentindavid/reprovision-api branch from 484b15e to c29b9b0 Compare July 23, 2026 21:12
@sergiocazzolato

Copy link
Copy Markdown
Contributor

Tests failing here are also failing in other prs. No new errors introduced.

@sergiocazzolato
sergiocazzolato merged commit 983808a into canonical:master Jul 24, 2026
267 of 305 checks passed
@sergiocazzolato sergiocazzolato mentioned this pull request Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Auto rerun spread Auto reruns spread up to 4 times in non-draft PRs w/ >=1 approval and <20 fails in any fund. system Needs Documentation -auto- Label automatically added which indicates the change needs documentation Run nested The PR also runs tests inluded in nested suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants