一覧SP-22-2 · バックアップ保護:破壊への耐性

バックアップの復元と整合性確認

保存世代の内容、設定変更の影響、復旧先の業務データを一続きで確認します。

説明

バックアップの設定が保護されていても、設定変更、内容の破損、復旧手順の不足によって戻せない場合があります。設定と保存データ、復旧先を順に確認します。

固定の注文DBをコピーした後で元データを変更します。暗号文の改変検査と別DBへの復旧を行い、構造検査だけでなく注文の件数と合計も照合します。

保存世代の内容、設定変更の影響、復旧先の業務データを一続きで確認します。

1保存した世代と元データを比較する

このページでは注文1が100、注文2が200の状態から始めます。

from sp22 import * from sp02 import show lab=Backup() show(lab.source,"SELECT * FROM orders ORDER BY id") print(hashlib.sha256保存した世代と元データを比較する(lab.cipher).hexdigest())

バックアップのハッシュを記録しました。これは保存バイトの同一性に使う値で、業務データが正しいことの判定とは別です。

2取得後に元データを変更する

注文2を元DBから削除します。

lab.source.execute("DELETE FROM orders WHERE id=2");lab.source.commit() show取得後に元データを変更する(lab.source,"SELECT * FROM orders ORDER BY id") show(lab.snapshot,"SELECT * FROM orders ORDER BY id")

元DBは1行、コピーは2行です。どの時点のデータへ戻るのかが具体的に確認できます。

3保持設定の変更履歴を残す

新規取得に使う設定値を100から50へ変えた固定例です。

lab.db.execute("INSERT INTO audit VALUES (20,'backup-admin','retention',100,50)") lab.db.execute("UPDATE config SET value=50 WHERE name='retention'") show保持設定の変更履歴を残す(lab.db,"SELECT * FROM audit") show(lab.db,"SELECT retain_until FROM backup")

既存b7の期限は100のままです。既存世代への適用と、今後作る世代の既定値を分けて確認します。

4暗号文の変更を検出する

一つのバイトを反転した試料です。

changed=bytes([lab.cipher[0]^1])+lab.cipher[1:] print(hashlib.sha256(changed).digest()==hashlib.sha256(lab.cipher).digest()) try: lab.restore暗号文の変更を検出する(changed) except InvalidTag as e: print(type(e).__name__)

ハッシュは不一致で、AES-GCMもInvalidTagになります。認証に失敗したデータをそのまま復旧先へ書き込みません。

5別DBへ復旧して構造を検査する

元DBとは別の接続へ復旧します。

restored=lab.restore() print(restored is lab.source) show(restored,"PRAGMA integrity_check別DBへ復旧して構造を検査する")

別接続であり、構造検査はokです。実復旧ではネットワークや資格情報も隔離し、業務への接続を段階的に戻します。

6件数と業務上の集計値を照合する

注文の件数・合計と個々の値を確認します。

show(restored,"SELECT COUNT(*) AS orders,SUM件数と業務上の集計値を照合する(amount) AS total FROM orders") show(restored,"SELECT * FROM orders ORDER BY id")

2件、合計300です。構造が壊れていないだけでなく、この保存世代に期待する注文内容も確認できました。

7元DBへの意図しない上書きがないことを確認する

復旧後も元DBの件数を読み直します。

show元DBへの意図しない上書きがないことを確認する(lab.source,"SELECT COUNT(*) AS source_orders FROM orders") restored.close()

元DBは1件のままです。復旧先を分けることで、確認前に現行データを上書きせず比較できます。復旧時間の目標は、別途実際の手順を通して測定します。

まとめ

設定変更が既存世代へ及ぶ範囲を確認します。

保存バイトの検査と復旧を実施します。

構造と業務上の値の両方を照合します。

操作履歴と解説を振り返れます。

Python 3.15 / SQLite backup / AES-GCM / 固定の保管権限モデル

1
from sp22 import * from sp02 import show lab=Backup() show(lab.source,"SELECT * FROM orders ORDER BY id") print(hashlib.sha256(lab.cipher).hexdigest())
id | amount
1 | 100
2 | 200
bc1eb0b90c67cd366a1bf7a67769e025925325dece77834dff45b4e68dd35550
2
lab.source.execute("DELETE FROM orders WHERE id=2");lab.source.commit() show(lab.source,"SELECT * FROM orders ORDER BY id") show(lab.snapshot,"SELECT * FROM orders ORDER BY id")
id | amount
1 | 100
id | amount
1 | 100
2 | 200
3
lab.db.execute("INSERT INTO audit VALUES (20,'backup-admin','retention',100,50)") lab.db.execute("UPDATE config SET value=50 WHERE name='retention'") show(lab.db,"SELECT * FROM audit") show(lab.db,"SELECT retain_until FROM backup")
t | actor | setting | before_value | after_value
20 | backup-admin | retention | 100 | 50
retain_until
100
4
changed=bytes([lab.cipher[0]^1])+lab.cipher[1:] print(hashlib.sha256(changed).digest()==hashlib.sha256(lab.cipher).digest()) try: lab.restore(changed) except InvalidTag as e: print(type(e).__name__)
False
InvalidTag
5
restored=lab.restore() print(restored is lab.source) show(restored,"PRAGMA integrity_check")
False
integrity_check
ok
6
show(restored,"SELECT COUNT(*) AS orders,SUM(amount) AS total FROM orders") show(restored,"SELECT * FROM orders ORDER BY id")
orders | total
2 | 300
id | amount
1 | 100
2 | 200
7
show(lab.source,"SELECT COUNT(*) AS source_orders FROM orders") restored.close()
source_orders
1

演習完了