Fixes from the phase-1 code review
- one locking protocol per location: backup steps and restores hold a shared lock, prune and untrash an exclusive one and refuse while any run (backup, restore, export) exists; starting a backup checks and creates the run under the exclusive lock, closing the race with prune - snapshot entries are validated when read (relative path without '.', '..', NUL, backslash or control characters); raw restore also checks that the resolved directory stays below the target; the restore API and service accept canonical paths only - recovery kit confirmation requires a recent password confirmation and the confirmation code printed in the current kit file (new code per kit version), proving the kit was actually downloaded - prune decides on the authenticated index files on the location and refreshes the local index cache from them - restore job marks the run failed on any error; restore runs without progress for 6 hours are failed so they cannot block cleanup forever - tests for safe paths and a malicious snapshot path
This commit is contained in:
@@ -63,8 +63,10 @@
|
||||
: t('ng_backup', 'Before the first backup: download the recovery kit and store it, together with the passphrase, somewhere safe outside this server.') }}
|
||||
</NcNoteCard>
|
||||
<NcButton @click="downloadKit">{{ t('ng_backup', 'Download recovery kit') }}</NcButton>
|
||||
<NcTextField v-model="code" :disabled="!downloaded" :label="t('ng_backup', 'Confirmation code from the kit file')"
|
||||
:helper-text="t('ng_backup', 'Open the downloaded file and type the value of confirmation_code, e.g. ABCD-2345.')" />
|
||||
<NcCheckboxRadioSwitch v-model="accepted" :disabled="!downloaded">{{ status.statement }}</NcCheckboxRadioSwitch>
|
||||
<NcButton variant="primary" :disabled="!downloaded || !accepted || busy" @click="confirm">{{ status.confirmationPhrase }}</NcButton>
|
||||
<NcButton variant="primary" :disabled="!downloaded || !accepted || code.length < 8 || busy" @click="confirm">{{ status.confirmationPhrase }}</NcButton>
|
||||
</div>
|
||||
<p v-else>
|
||||
{{ t('ng_backup', 'Recovery kit confirmed by {user} on {date}.', { user: status.key.confirmation.uid, date: new Date(status.key.confirmation.time * 1000).toLocaleString() }) }}
|
||||
@@ -99,6 +101,7 @@ const delay = ref(props.status.key.deleteDelayDays)
|
||||
const slotForm = ref(null)
|
||||
const downloaded = ref(false)
|
||||
const accepted = ref(false)
|
||||
const code = ref('')
|
||||
const busy = ref(false)
|
||||
const error = ref('')
|
||||
const kitUrl = api.kitUrl()
|
||||
@@ -148,18 +151,10 @@ const removeSlot = (slot) => guarded(async () => {
|
||||
downloaded.value = accepted.value = false
|
||||
})
|
||||
|
||||
async function confirm() {
|
||||
busy.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
await api.confirmKit(accepted.value, props.status.confirmationPhrase)
|
||||
emit('changed')
|
||||
} catch (e) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
busy.value = false
|
||||
}
|
||||
}
|
||||
const confirm = () => guarded(async () => {
|
||||
await api.confirmKit(accepted.value, props.status.confirmationPhrase, code.value)
|
||||
code.value = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user