Skip to main content

vta_backup/
lib.rs

1//! Backup/restore subsystem for the VTA, extracted from `vta-service`.
2//!
3//! - [`backup_bundle_store`] — the sealed backup-bundle store (bundle records +
4//!   on-disk blobs for the two-phase export/import trust tasks).
5//! - [`backup_bundle_sweeper`] — TTL sweep of expired backup bundles.
6//! - [`ops`] — the encrypted full-state export and the import that stages a
7//!   restore (Argon2id + AES-256-GCM), the compatibility check, and the
8//!   two-phase descriptor flow.
9//! - [`restore`] — applying a staged restore at boot.
10//!
11//! # Portability
12//!
13//! A backup restores into any kind of deployment — plain, hardened, or a Nitro
14//! enclave — from any kind. Export writes every backed-up keyspace in
15//! plaintext (inside the password envelope); import *stages* the restore and
16//! commits the restored seed the target's own way ([`RestoreCommitter`]); the
17//! next boot applies it under the storage key that seed yields. See
18//! `vta_support::restore_stage` for why it cannot be applied in place, and
19//! `docs/05-design-notes/backup-restore-portability.md` for the whole design.
20
21// The bundle store and its sweeper are the node-neutral half of backup
22// transfer, shared with the VTC, and live in `vti_common::backup_transfer`.
23// Re-exported under their old paths so every caller keeps working.
24pub use vti_common::backup_transfer::bundle_store as backup_bundle_store;
25pub use vti_common::backup_transfer::sweeper as backup_bundle_sweeper;
26pub mod ops;
27pub mod restore;
28
29#[cfg(test)]
30mod restore_tests;
31#[cfg(test)]
32mod test_support;
33
34use vta_sdk::protocols::backup_management::types::BackupEnvironment;
35use vta_support::restore_stage::AnchorBinding;
36use vti_common::error::AppError;
37use vti_common::store::{KeyspaceHandle, Store};
38
39/// The store a backup is read from, or a restore staged into, and how it is
40/// protected at rest.
41#[derive(Clone, Copy)]
42pub struct BackupTarget<'a> {
43    pub store: &'a Store,
44    /// The at-rest key every keyspace except `bootstrap` is encrypted under,
45    /// or `None` for a plain store.
46    pub storage_key: Option<[u8; 32]>,
47    pub environment: BackupEnvironment,
48}
49
50impl BackupTarget<'_> {
51    /// Open `name` the way the running VTA does: encrypted under
52    /// [`Self::storage_key`] when there is one.
53    ///
54    /// Never use this for `bootstrap`, which is unencrypted in every
55    /// deployment — open that with `store.keyspace` directly.
56    pub fn keyspace(&self, name: &str) -> Result<KeyspaceHandle, AppError> {
57        let ks = self.store.keyspace(name)?;
58        Ok(match self.storage_key {
59            Some(key) => ks.with_encryption(key),
60            None => ks,
61        })
62    }
63
64    /// The unencrypted `bootstrap` keyspace.
65    pub fn bootstrap(&self) -> Result<KeyspaceHandle, AppError> {
66        self.store.keyspace(vta_keyspaces::BOOTSTRAP)
67    }
68}
69
70/// The restored secrets an import asks the target to adopt.
71pub struct RestoredSecrets<'a> {
72    pub seed: &'a [u8],
73    /// The backup's JWT signing key, or `None` if it carried none (the target
74    /// then keeps its own).
75    pub jwt_key: Option<[u8; 32]>,
76    /// The DID the VTA will run as after the restore.
77    pub vta_did: Option<&'a str>,
78}
79
80/// What a [`RestoreCommitter`] prepared, to be recorded in the staged restore's
81/// authenticated metadata before [`RestoreCommitter::commit`] runs.
82#[derive(Default)]
83pub struct PreparedCommit {
84    /// The KMS-sealed secrets row an enclave will commit (opaque here).
85    pub tee_secrets_row: Option<Vec<u8>>,
86    /// Anti-rollback reservation, when the target runs an external counter.
87    pub anchor: Option<AnchorBinding>,
88}
89
90/// How a target adopts a restored seed. The seam between this crate and the
91/// deployment: [`SeedStoreCommitter`] for plain and hardened VTAs, and an
92/// enclave implementation in `vta-service` that seals the seed under KMS.
93///
94/// The two steps are split so the staged restore can record what `prepare`
95/// produced *before* anything changes which seed the next boot sees.
96#[async_trait::async_trait]
97pub trait RestoreCommitter: Sync {
98    /// Validate that the target can adopt `secrets` and compute anything the
99    /// stage must bind to. Must not change what the next boot starts from.
100    async fn prepare(&self, secrets: &RestoredSecrets<'_>) -> Result<PreparedCommit, AppError>;
101
102    /// Make the restored seed the one the next boot starts from. After this
103    /// returns the staged restore opens, and the next boot applies it.
104    async fn commit(
105        &self,
106        secrets: &RestoredSecrets<'_>,
107        prepared: PreparedCommit,
108    ) -> Result<(), AppError>;
109
110    /// The import failed after [`Self::prepare`] and before a successful
111    /// [`Self::commit`]: undo whatever `prepare` did to the running VTA (an
112    /// enclave freezes its integrity sealer there). The VTA carries on as it
113    /// was.
114    async fn abort(&self) {}
115}
116
117/// [`RestoreCommitter`] for a plain or hardened VTA: the seed lives in the
118/// configured secret store, and the JWT key is written at boot by the restore.
119pub struct SeedStoreCommitter<'a> {
120    pub seed_store: &'a dyn vta_keys::seed_store::SeedStore,
121}
122
123#[async_trait::async_trait]
124impl RestoreCommitter for SeedStoreCommitter<'_> {
125    async fn prepare(&self, _secrets: &RestoredSecrets<'_>) -> Result<PreparedCommit, AppError> {
126        if !self.seed_store.set_persists_across_restart() {
127            return Err(AppError::Validation(
128                "this VTA's seed store cannot persist a new seed across a restart, so a \
129                 restore could never take effect. Configure a persistent secret-store \
130                 backend (keyring, aws, gcp, azure, vault, k8s) before restoring."
131                    .into(),
132            ));
133        }
134        Ok(PreparedCommit::default())
135    }
136
137    async fn commit(
138        &self,
139        secrets: &RestoredSecrets<'_>,
140        _prepared: PreparedCommit,
141    ) -> Result<(), AppError> {
142        self.seed_store
143            .set(secrets.seed)
144            .await
145            .map_err(|e| AppError::Internal(format!("seed store: {e}")))
146    }
147}