mirror of
https://github.com/AlchemillaHQ/Sylve.git
synced 2026-06-14 00:46:34 +03:00
backups: zfs: fix encryption handling post OOB restore
This commit is contained in:
@@ -121,17 +121,58 @@ func (s *Service) EnsureEncryptionKeyFile(uuid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ensureEncryptionKeyForDataset(ctx context.Context, ds *gzfs.Dataset) error {
|
||||
func (s *Service) ensureEncryptionKeyForDataset(ctx context.Context, ds *gzfs.Dataset) (keyLoaded bool, err error) {
|
||||
keylocProp, err := ds.GetProperty(ctx, "keylocation")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get_keylocation_failed: %w", err)
|
||||
return false, fmt.Errorf("get_keylocation_failed: %w", err)
|
||||
}
|
||||
|
||||
keyloc := strings.TrimSpace(keylocProp.Value)
|
||||
if keyloc == "" || keyloc == "none" || !strings.HasPrefix(keyloc, "file://") {
|
||||
return fmt.Errorf("unexpected_keylocation: %s", keyloc)
|
||||
if keyloc == "" || keyloc == "none" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
uuid := filepath.Base(strings.TrimPrefix(keyloc, "file://"))
|
||||
return s.EnsureEncryptionKeyFile(uuid)
|
||||
if strings.HasPrefix(keyloc, "file://") {
|
||||
uuid := filepath.Base(strings.TrimPrefix(keyloc, "file://"))
|
||||
if err := s.EnsureEncryptionKeyFile(uuid); err != nil {
|
||||
return false, fmt.Errorf("encryption_key_not_found_in_cluster_store: %s", uuid)
|
||||
}
|
||||
if err := ds.LoadKey(ctx, false); err != nil {
|
||||
return false, fmt.Errorf("load_key_failed: %w", err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// keylocation is "prompt" — the original key file wasn't available on
|
||||
// the server that received this dataset (e.g. a backup target). Try each
|
||||
// key in the cluster store; match by attempting to load.
|
||||
keys, listErr := s.Cluster.ListEncryptionKeys()
|
||||
if listErr != nil {
|
||||
logger.L.Warn().Err(listErr).Str("dataset", ds.Name).
|
||||
Msg("encrypted_dataset_cannot_list_keys_for_prompt_keylocation")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
if err := ds.LoadKeyWithPassphrase(ctx, key.KeyData); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Key loaded — materialize the key file and fix keylocation
|
||||
// so it auto-loads on next boot.
|
||||
if writeErr := s.EnsureEncryptionKeyFile(key.UUID); writeErr != nil {
|
||||
logger.L.Warn().Err(writeErr).Str("uuid", key.UUID).
|
||||
Str("dataset", ds.Name).Msg("prompt_key_loaded_but_file_write_failed")
|
||||
} else {
|
||||
_ = ds.SetProperties(ctx, "keylocation", "file://"+filepath.Join(EncryptionKeyDirectory, key.UUID))
|
||||
}
|
||||
|
||||
logger.L.Info().Str("dataset", ds.Name).Str("uuid", key.UUID).
|
||||
Msg("encrypted_dataset_key_loaded_via_prompt_fallback")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
logger.L.Warn().Str("dataset", ds.Name).
|
||||
Msg("encrypted_dataset_requires_manual_key_load_run_zfs_load_key")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -3707,11 +3707,12 @@ func (s *Service) prepareReplicatedDatasetForActivation(ctx context.Context, roo
|
||||
}
|
||||
|
||||
if ds.IsEncrypted() {
|
||||
if err := s.ensureEncryptionKeyForDataset(ctx, ds); err != nil {
|
||||
return fmt.Errorf("replication_encryption_key_missing_%s: %w", dataset, err)
|
||||
keyLoaded, err := s.ensureEncryptionKeyForDataset(ctx, ds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("replication_encryption_key_failed_%s: %w", dataset, err)
|
||||
}
|
||||
if err := ds.LoadKey(ctx, false); err != nil {
|
||||
return fmt.Errorf("replication_load_key_failed_%s: %w", dataset, err)
|
||||
if !keyLoaded {
|
||||
logger.L.Warn().Str("dataset", dataset).Msg("replication_encryption_key_not_auto_loaded")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -358,13 +358,13 @@ func (s *Service) fixRestoredProperties(ctx context.Context, dataset string) {
|
||||
}
|
||||
|
||||
if ds.IsEncrypted() {
|
||||
if err := s.ensureEncryptionKeyForDataset(ctx, ds); err != nil {
|
||||
logger.L.Error().Err(err).Str("dataset", dataset).Msg("fix_restored_property_key_missing")
|
||||
keyLoaded, err := s.ensureEncryptionKeyForDataset(ctx, ds)
|
||||
if err != nil {
|
||||
logger.L.Error().Err(err).Str("dataset", dataset).Msg("fix_restored_property_key_load_failed")
|
||||
return
|
||||
}
|
||||
if err := ds.LoadKey(ctx, false); err != nil {
|
||||
logger.L.Error().Err(err).Str("dataset", dataset).Msg("fix_restored_property_load_key_failed")
|
||||
return
|
||||
if !keyLoaded {
|
||||
logger.L.Warn().Str("dataset", dataset).Msg("fix_restored_property_key_not_auto_loaded")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import { vmBaseDataset, vmStoragePools } from '$lib/utils/vm/vm';
|
||||
import { watch } from 'runed';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { sleep } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -172,11 +173,18 @@
|
||||
}
|
||||
|
||||
srcEncryptionChecking = true;
|
||||
await sleep(1000);
|
||||
|
||||
try {
|
||||
const datasets = await getDatasets(GZFSDatasetTypeSchema.enum.ALL);
|
||||
const datasets = await Promise.all([
|
||||
getDatasets(GZFSDatasetTypeSchema.enum.FILESYSTEM),
|
||||
getDatasets(GZFSDatasetTypeSchema.enum.VOLUME)
|
||||
]).then(([filesystems, volumes]) => [...filesystems, ...volumes]);
|
||||
|
||||
const match = datasets.find((d) => {
|
||||
if (d.name !== dataset) return false;
|
||||
const enc = d.properties?.encryption?.value;
|
||||
console.log(d.properties);
|
||||
const enc = d.properties?.encryption || '';
|
||||
return enc && enc !== 'off' && enc !== '-' && enc !== 'none';
|
||||
});
|
||||
srcEncrypted = !!match;
|
||||
@@ -460,19 +468,42 @@
|
||||
}
|
||||
});
|
||||
|
||||
watch([() => open, () => form.mode, () => form.sourceDataset, () => form.selectedJailId, () => form.selectedVmId], ([isOpen]) => {
|
||||
if (!isOpen) return;
|
||||
let dataset = '';
|
||||
if (form.mode === 'dataset') {
|
||||
dataset = form.sourceDataset;
|
||||
} else if (form.mode === 'jail' && selectedJail) {
|
||||
const base = selectedJail.storages?.find((s) => s.isBase);
|
||||
if (base) dataset = `${base.pool}/sylve/jails/${selectedJail.ctId}`;
|
||||
} else if (form.mode === 'vm' && selectedVM) {
|
||||
dataset = vmBaseDataset(selectedVM) || '';
|
||||
watch(
|
||||
[
|
||||
() => open,
|
||||
() => form.mode,
|
||||
() => form.sourceDataset,
|
||||
() => form.selectedJailId,
|
||||
() => form.selectedVmId
|
||||
],
|
||||
([isOpen]) => {
|
||||
if (!isOpen) return;
|
||||
let dataset = '';
|
||||
if (form.mode === 'dataset') {
|
||||
dataset = form.sourceDataset;
|
||||
} else if (form.mode === 'jail' && selectedJail) {
|
||||
const base = selectedJail.storages?.find((s) => s.isBase);
|
||||
if (base) dataset = `${base.pool}/sylve/jails/${selectedJail.ctId}`;
|
||||
} else if (form.mode === 'vm' && selectedVM) {
|
||||
dataset = vmBaseDataset(selectedVM) || '';
|
||||
}
|
||||
void checkSourceEncryption(dataset);
|
||||
}
|
||||
void checkSourceEncryption(dataset);
|
||||
});
|
||||
);
|
||||
|
||||
let disableStopBeforeBackup = $state(false);
|
||||
|
||||
watch(
|
||||
() => form.mode,
|
||||
(mode) => {
|
||||
if (mode === 'dataset') {
|
||||
form.stopBeforeBackup = false;
|
||||
disableStopBeforeBackup = true;
|
||||
} else {
|
||||
disableStopBeforeBackup = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
@@ -601,6 +632,10 @@
|
||||
label="Stop before backup"
|
||||
bind:checked={form.stopBeforeBackup}
|
||||
classes="flex items-center gap-2"
|
||||
disabled={disableStopBeforeBackup}
|
||||
title={form.mode === 'dataset'
|
||||
? 'This option is only applicable for jail and VM backups'
|
||||
: ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -637,19 +672,6 @@
|
||||
will be backed up
|
||||
</li>
|
||||
{/if}
|
||||
<li>
|
||||
Encryption:
|
||||
<code class="rounded bg-background px-1">
|
||||
{#if srcEncryptionChecking}
|
||||
...
|
||||
{:else if srcEncrypted}
|
||||
<span class="icon-[material-symbols--lock] h-3.5 w-3.5 inline-block align-text-bottom"></span>
|
||||
Enabled (keys auto-managed)
|
||||
{:else}
|
||||
None
|
||||
{/if}
|
||||
</code>
|
||||
</li>
|
||||
<li>
|
||||
Schedule: <code class="rounded bg-background px-1"
|
||||
>{cronToHuman(form.cronExpr) || '(not set)'}</code
|
||||
@@ -669,12 +691,43 @@
|
||||
>{form.pruneTarget ? 'Enabled' : 'Disabled'}</code
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
Stop before backup:
|
||||
<code class="rounded bg-background px-1"
|
||||
>{form.stopBeforeBackup ? 'Enabled' : 'Disabled'}</code
|
||||
>
|
||||
</li>
|
||||
|
||||
{#if !disableStopBeforeBackup}
|
||||
<li>
|
||||
Stop before backup:
|
||||
<code class="rounded bg-background px-1"
|
||||
>{form.stopBeforeBackup ? 'Enabled' : 'Disabled'}</code
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#if form.mode !== 'jail' && form.mode !== 'vm'}
|
||||
{#if srcEncryptionChecking}
|
||||
<li>
|
||||
<span
|
||||
class="icon-[mdi--loading] h-3.5 w-3.5 animate-spin inline-block align-text-bottom mb-0.5"
|
||||
></span>
|
||||
</li>
|
||||
{:else if form.sourceDataset === ''}
|
||||
<li>
|
||||
<span class="icon-[mdi--help] h-3.5 w-3.5 inline-block align-text-bottom mb-0.5"
|
||||
></span>
|
||||
<span>Select a dataset to view encryption information</span>
|
||||
</li>
|
||||
{:else if srcEncrypted}
|
||||
<li>
|
||||
<span class="icon-[mdi--lock] h-3.5 w-3.5 inline-block align-text-bottom mb-0.5"
|
||||
></span>
|
||||
<span>Source is encrypted</span>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<span class="icon-[mdi--unlocked] h-3.5 w-3.5 inline-block align-text-bottom mb-0.5"
|
||||
></span>
|
||||
<span>Source is not encrypted</span>
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13920,3 +13920,37 @@ msgstr ""
|
||||
#: src/lib/components/skeleton/BottomPanel.svelte
|
||||
msgid "Jail Template - Create"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0/>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Enabled (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0>Enabled (keys auto-managed)</0>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Checking source encryption..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Source is encrypted (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is not encrypted"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "No encryption information available"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Select a dataset to view encryption information"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "This option is only applicable for jail and VM backups"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is encrypted"
|
||||
msgstr ""
|
||||
|
||||
@@ -13898,3 +13898,37 @@ msgstr ""
|
||||
#: src/lib/components/skeleton/BottomPanel.svelte
|
||||
msgid "Jail Template - Create"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0/>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Enabled (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0>Enabled (keys auto-managed)</0>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Checking source encryption..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Source is encrypted (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is not encrypted"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "No encryption information available"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Select a dataset to view encryption information"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "This option is only applicable for jail and VM backups"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is encrypted"
|
||||
msgstr ""
|
||||
|
||||
@@ -13921,3 +13921,37 @@ msgstr "VM Template - Create"
|
||||
#: src/lib/components/skeleton/BottomPanel.svelte
|
||||
msgid "Jail Template - Create"
|
||||
msgstr "Jail Template - Create"
|
||||
|
||||
#~ msgid "Encryption: <0/>"
|
||||
#~ msgstr "Encryption: <0/>"
|
||||
|
||||
#~ msgid "Enabled (keys auto-managed)"
|
||||
#~ msgstr "Enabled (keys auto-managed)"
|
||||
|
||||
#~ msgid "Encryption: <0>Enabled (keys auto-managed)</0>"
|
||||
#~ msgstr "Encryption: <0>Enabled (keys auto-managed)</0>"
|
||||
|
||||
#~ msgid "Checking source encryption..."
|
||||
#~ msgstr "Checking source encryption..."
|
||||
|
||||
#~ msgid "Source is encrypted (keys auto-managed)"
|
||||
#~ msgstr "Source is encrypted (keys auto-managed)"
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is not encrypted"
|
||||
msgstr "Source is not encrypted"
|
||||
|
||||
#~ msgid "No encryption information available"
|
||||
#~ msgstr "No encryption information available"
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Select a dataset to view encryption information"
|
||||
msgstr "Select a dataset to view encryption information"
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "This option is only applicable for jail and VM backups"
|
||||
msgstr "This option is only applicable for jail and VM backups"
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is encrypted"
|
||||
msgstr "Source is encrypted"
|
||||
|
||||
@@ -13898,3 +13898,37 @@ msgstr ""
|
||||
#: src/lib/components/skeleton/BottomPanel.svelte
|
||||
msgid "Jail Template - Create"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0/>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Enabled (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0>Enabled (keys auto-managed)</0>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Checking source encryption..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Source is encrypted (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is not encrypted"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "No encryption information available"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Select a dataset to view encryption information"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "This option is only applicable for jail and VM backups"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is encrypted"
|
||||
msgstr ""
|
||||
|
||||
@@ -13898,3 +13898,37 @@ msgstr ""
|
||||
#: src/lib/components/skeleton/BottomPanel.svelte
|
||||
msgid "Jail Template - Create"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0/>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Enabled (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0>Enabled (keys auto-managed)</0>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Checking source encryption..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Source is encrypted (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is not encrypted"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "No encryption information available"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Select a dataset to view encryption information"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "This option is only applicable for jail and VM backups"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is encrypted"
|
||||
msgstr ""
|
||||
|
||||
@@ -13895,3 +13895,37 @@ msgstr ""
|
||||
#: src/lib/components/skeleton/BottomPanel.svelte
|
||||
msgid "Jail Template - Create"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0/>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Enabled (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Encryption: <0>Enabled (keys auto-managed)</0>"
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Checking source encryption..."
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid "Source is encrypted (keys auto-managed)"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is not encrypted"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "No encryption information available"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Select a dataset to view encryption information"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "This option is only applicable for jail and VM backups"
|
||||
msgstr ""
|
||||
|
||||
#: src/lib/components/custom/DataCenter/Backups/Jobs/Form.svelte
|
||||
msgid "Source is encrypted"
|
||||
msgstr ""
|
||||
|
||||
Reference in New Issue
Block a user