في مقال النسخ الاحتياطي بقاعدة 3-2-1 ذكرنا أن برامج الفدية تبحث عن النسخ الاحتياطية التي يصل إليها السيرفر فتحذفها، وأن الحل نسخة واحدة على الأقل لا يستطيع السيرفر حذفها، وطبقنا هناك طريقة الإضافة فقط Append-only مع rest-server. وبقيت الطريقة الثانية التي ذكرناها في سطر واحد: قفل الكائنات Object Lock في S3، وهي الطريقة التي تعمل مع أي أداة نسخ تكتب إلى S3، ومع التخزين السحابي والمستضاف ذاتياً. وفي هذا المقال سوف نعطي مخترقاً وهمياً مفاتيح النسخ الاحتياطي وكلمة مرور المستودع نفسها، ونتركه يحاول أن يمحو كل شيء، ثم نستعيد البيانات من جهاز نظيف.
وسوف نناقش في هذا المقال ما يلي:
- ما هو قفل الكائنات Object Lock ونموذج الكتابة مرة والقراءة مرات WORM، ولماذا يشترط النسخ Versioning.
- الفرق بين وضع الحوكمة Governance ووضع الامتثال Compliance، وبين مدة الاحتفاظ Retention والحجز القانوني Legal Hold.
- أي أدوات النسخ تدعمه: Kopia وrclone وrestic، وأي خوادم S3 المستضافة ذاتياً تدعمه.
- تجربة عملية: نسخة Kopia إلى SeaweedFS بقفل Compliance، ثم هجوم بمفاتيح النسخ نفسها، ثم استعادة إلى ما قبل الهجوم.
- تجربة كسر: تمديد القفل تلقائياً على SeaweedFS 4.48 جعل الاستعادة الزمنية تفشل، ولماذا.
الكتابة مرة واحدة: ماذا يقفل Object Lock بالضبط؟
بحسب توثيق Object Lock في Amazon S3، فهو يحفظ الكائنات بنموذج WORM، أي Write Once Read Many، فلا تحذف ولا يكتب فوقها لمدة محددة أو إلى أن يرفع القفل. والنقطة التي يجب أن تفهمها قبل أي شيء أن القفل على النسخة Version وليس على المفتاح Key، ولذلك لا يعمل Object Lock إلا على Bucket فيه النسخ Versioning مفعل، وبعد تفعيله لا تستطيع أن تلغيه ولا أن توقف النسخ على ذلك ال Bucket كما في توثيق الإعداد. وبالتالي:
- الكتابة فوق مفتاح مقفل تنجح، ولكنها تضيف نسخة جديدة، والقديمة تبقى مقفلة.
- الحذف العادي دون رقم نسخة ينجح بالرد 200، ولكنه يضيف علامة حذف Delete Marker تخفي المفتاح فقط، وهذا ما يربك كثيرين عند أول تجربة فيظنون أن القفل لا يعمل.
- الحذف برقم النسخة، وهو الحذف الذي يمحو البيانات فعلاً، يرفض بالخطأ 403 ما دامت النسخة مقفلة.
وهناك ثلاث طرق لقفل النسخة، والفرق بينها من يستطيع فك القفل، كما في الصورة التالية:

- مدة الاحتفاظ Retention Period بوضع الحوكمة Governance: لا يحذف النسخة إلا من يملك صلاحية
s3:BypassGovernanceRetentionويرسل الترويسةx-amz-bypass-governance-retention:true، ولذلك هو مناسب للتجربة قبل الامتثال، أو عندما تريد أن يبقى عند المدير طريق للتنظيف. - مدة الاحتفاظ بوضع الامتثال Compliance: لا يحذفها أحد قبل التاريخ، حتى المستخدم الرئيسي Root في حساب AWS، ولا يغير الوضع ولا تقصر المدة، والطريقة الوحيدة لحذفها قبل التاريخ بحسب التوثيق هي حذف حساب AWS كله. وهذا ما تريده ضد الفدية، والسبب أن المخترق الذي يصل إلى حساب المدير لا يستطيع شيئاً أيضاً.
- الحجز القانوني Legal Hold: قفل دون تاريخ، يبقى حتى يرفعه من يملك صلاحية
s3:PutObjectLegalHold، ومستقل عن مدة الاحتفاظ، فيستخدم مثلاً أثناء تدقيق أو قضية حتى يقال «انتهينا».
ويمكن أن تضع مدة احتفاظ افتراضية على ال Bucket كله، فتأخذ كل نسخة جديدة تاريخها تلقائياً، أو أن تضع لكل كائن تاريخه عند الرفع، وهذا ما تفعله أداة النسخ عادة. وقد أضافت AWS أيضاً الاحتفاظ المتغير Variable Retention بحجز حدث Event Hold، يبدأ فيه العد عند رفع الحجز، ولكنه خاص ب Amazon حالياً، والأدوات المستضافة ذاتياً لا تذكره في صفحات توافقها.
أدوات النسخ وخوادم S3: من يدعم ماذا؟
قارنا في restic أم Borg أم Kopia أدوات النسخ في السرعة والحجم، وهنا ننظر إليها من جهة Object Lock فقط:
- Kopia يدعمه من داخله: تنشئ المستودع بالخيارين
--retention-modeو--retention-periodفيضع القفل على كل ملف يكتبه، ويستطيع أن يمدد الأقفال أثناء الصيانة الكاملة، ويقرأ المستودع كما كان في لحظة سابقة بالخيار--point-in-time، كما في صفحة الحماية من الفدية، وهو ما سوف نجربه. - rclone أضاف دعم Object Lock في الإصدار 1.74.0 بتاريخ 1 مايو 2026 بحسب سجل التغييرات، بالخيارات
--s3-object-lock-modeو--s3-object-lock-retain-until-date، ويقرأ ال Bucket كما كان في وقت سابق بالخيار--s3-version-atكما في توثيق S3 في rclone، وهذا مناسب لنسخ الملفات كما هي إلى Bucket مقفل. - restic لا يضع القفل بنفسه، ولم نجد في توثيقه خياراً لذلك، والطريقة المعروفة معه هي الإضافة فقط عبر rest-server أو
rclone serve restic --append-onlyكما في تجربة 3-2-1.
أما الخوادم المستضافة ذاتياً، فقد سألنا كل واحد منها في تجربة التوافق أن يقفل نسخة ثم يحذفها، والنتيجة مع ما يقوله التوثيق كما يلي:
| الخادم | Object Lock | المصدر |
|---|---|---|
| Garage v2.4.1 | غير موجود، رد NotImplemented على PutObjectLockConfiguration | تجربتنا وصفحة التوافق |
| SeaweedFS 4.48 | موجود، ورفض حذف النسخة المقفلة | تجربتنا وصفحة واجهة S3 |
| MinIO RELEASE.2025-10-15 | موجود، ورفض حذف النسخة المقفلة | تجربتنا، والمستودع لم يعد يصان |
| Ceph RGW | موجود بحسب التوثيق، ولم نجربه | عمليات ال Bucket في RGW |
وقد يتساءل البعض: إذا كان Garage لا يدعم Object Lock، فهل أستغني عنه للنسخ الاحتياطي؟ والإجابة أنك تستطيع أن تستخدمه نسخة خارج الموقع، ولكن الحماية من الحذف تأتي حينها من مكان آخر، مثل rest-server بالإضافة فقط أمامه، أو أن يكون السيرفر الذي يملك صلاحية الحذف غير السيرفر الذي ينسخ.
تجربة عملية: مخترق يملك كل مفاتيح النسخ
الآن سوف نقوم بتشغيل SeaweedFS ببوابة S3 بهويتين: مدير، وهوية للنسخ الاحتياطي لا تملك إلا القراءة والكتابة والعرض ووضع القفل على Bucket واحد، ثم نأخذ نسخة Kopia لمجلد فيه 201 ملف، ثم نعطي «المخترق» مفاتيح هوية النسخ وكلمة مرور المستودع، ونراقب ما يستطيع وما لا يستطيع. ونستخدم Image الأدوات التي بنيناها في مقال S3 من داخل تطبيقك.
ال Bucket المقفل وهوية النسخ
mkdir -p ~/lab-s3lock && cd ~/lab-s3lock
cat > s3.json <<'EOF'
{
"identities": [
{
"name": "admin",
"credentials": [{ "accessKey": "LOCKADMIN001", "secretKey": "lock-admin-secret-change-me" }],
"actions": ["Admin", "Read", "List", "Tagging", "Write"]
},
{
"name": "backup",
"credentials": [{ "accessKey": "BACKUPKEY001", "secretKey": "backup-secret-change-me-01" }],
"actions": ["Read:backups", "Write:backups", "List:backups", "Tagging:backups",
"PutObjectRetention:backups", "GetObjectRetention:backups",
"GetBucketObjectLockConfiguration:backups"]
}
]
}
EOF
printf 'AWS_ACCESS_KEY_ID=LOCKADMIN001\nAWS_SECRET_ACCESS_KEY=lock-admin-secret-change-me\n' > admin.env
printf 'AWS_ACCESS_KEY_ID=BACKUPKEY001\nAWS_SECRET_ACCESS_KEY=backup-secret-change-me-01\n' > backup.env
chmod 600 s3.json admin.env backup.env
docker network create lab-s3lock-net
docker run -d --name lab-s3lock-seaweed --network lab-s3lock-net -p 127.0.0.1:38334:8333 \
-v "$PWD/s3.json:/etc/seaweedfs/s3.json:ro" -v lab-s3lock-s3data:/data \
chrislusf/seaweedfs:4.48 server -s3 -s3.config=/etc/seaweedfs/s3.json -dir=/data
docker run -d --name lab-s3lock-tools --network lab-s3lock-net -v "$PWD:/lab" \
-e AWS_DEFAULT_REGION=us-east-1 -e AWS_ENDPOINT_URL=http://lab-s3lock-seaweed:8333 lab/s3-toolbox:1
admin() { docker exec --env-file admin.env lab-s3lock-tools aws "$@"; }
backup() { docker exec --env-file backup.env lab-s3lock-tools aws "$@"; }
admin s3api create-bucket --bucket backups --object-lock-enabled-for-bucket
admin s3api get-object-lock-configuration --bucket backups
admin s3api get-bucket-versioning --bucket backupsوالمخرج سوف يكون كما يلي:
{
"Location": "/backups"
}
{
"ObjectLockConfiguration": {
"ObjectLockEnabled": "Enabled"
}
}
{
"Status": "Enabled"
}في الإعداد أعلاه لاحظ التالي:
- إنشاء ال Bucket بالخيار
--object-lock-enabled-for-bucketفعل النسخ تلقائياً، فالقفل لا يعمل دونه. - هوية النسخ تملك
PutObjectRetentionلأن Kopia يضع القفل مع كل ملف، وبدونها رفض SeaweedFS أول كتابة بالخطأ Access Denied في محاولتنا الأولى، وهي لا تملكBypassGovernanceRetentionولا أي صلاحية إدارة. - هذه أسماء أفعال SeaweedFS في ملف الهويات، وفي AWS تكتب الصلاحيات نفسها في سياسة IAM بالأسماء
s3:PutObjectRetentionوما يشبهها.
النسخة الأولى بقفل Compliance لسبعة أيام
نصنع بيانات تجربة في Volume: 200 فاتورة وملف قاعدة بيانات من 30 MB، ثم ننشئ مستودع Kopia بقفل Compliance لسبعة أيام، ونأخذ النسخة:
docker run --rm -v lab-s3lock-appdata:/data --entrypoint sh kopia/kopia:0.23.1 -c '
mkdir -p /data/invoices /data/db
for i in $(seq 1001 1200); do echo "invoice $i total $((i*7)) SAR" > /data/invoices/invoice-$i.txt; done
head -c 30M /dev/urandom > /data/db/shop.dump
cd /data && find . -type f | wc -l && find . -type f -exec md5sum {} + | sort -k2 | md5sum'
kopia() { docker run --rm --network lab-s3lock-net --hostname app-server \
-v lab-s3lock-kopia:/app/config -v lab-s3lock-appdata:/data:ro \
-e KOPIA_PASSWORD=repo-pass-change-me-01 kopia/kopia:0.23.1 "$@"; }
kopia repository create s3 --bucket backups --prefix kopia/ --endpoint lab-s3lock-seaweed:8333 --disable-tls \
--access-key BACKUPKEY001 --secret-access-key backup-secret-change-me-01 \
--retention-mode COMPLIANCE --retention-period 7d
kopia snapshot create /data
kopia snapshot list
docker exec --env-file backup.env lab-s3lock-tools sh -c \
'K=$(aws s3 ls s3://backups/kopia/ | awk "\$4 ~ /^p/ {print \$4; exit}"); echo "$K"; aws s3api get-object-retention --bucket backups --key "kopia/$K"'والمخرج سوف يكون كما يلي، بعد حذف أسطر سياسة الاحتفاظ الافتراضية التي يطبعها Kopia:
201
4b39fc8814cfb050b7d0b382008cf9ab -
Initializing repository with:
Connected to repository.
...
root@app-server:/data
2026-10-11 10:46:48 UTC k48c4de681b945c4673e17e3a8a75b179 31.5 MB drwxr-xr-x files:201 dirs:3 (latest-1,hourly-1,daily-1,weekly-1,monthly-1,annual-1)
p30947f6863c6d40e3c98690e8059db96-s4f65e0441f7f1af5146
{
"Retention": {
"Mode": "COMPLIANCE",
"RetainUntilDate": "2026-10-18T10:46:48+00:00"
}
}البصمة 4b39fc88… هي بصمة كل الملفات معاً، وسوف نقارن بها بعد الاستعادة. والملف الذي يبدأ بالحرف p هو ملف بيانات من ملفات Kopia، وعليه قفل Compliance حتى 18 أكتوبر، أي سبعة أيام من لحظة كتابته. وسجلنا الوقت قبل الهجوم:
date -u +%Y-%m-%dT%H:%M:%SZ # 2026-10-11T10:47:08Zالهجوم: ماذا يفعل المخترق بمفاتيح النسخ؟
المخترق على سيرفر التطبيق، ومعه ما يحتاجه Kopia ليعمل، فيبدأ بالطريقة المباشرة: يحذف النسخة بأداة Kopia نفسها، ثم يشغل الصيانة الكاملة بأخطر إعداد حتى تحذف البيانات غير المستخدمة فوراً:
kopia snapshot delete k48c4de681b945c4673e17e3a8a75b179 --delete
kopia maintenance run --full --safety=none
kopia snapshot list --allDeleting snapshot 61cac53d5147445043e5c191fa25b55a of root@app-server:/data at 2026-10-11 10:46:48 UTC...
GC found 209 unused contents (31.5 MB)
GC found 0 unused contents that are too recent to delete (0 B)
GC found 0 in-use contents (0 B)
GC found 3 in-use system-contents (1.3 KB)القائمة فارغة، ومن وجهة نظر Kopia لم يبق شيء. ثم ينتقل إلى S3 مباشرة بالمفاتيح نفسها: يحذف كل الملفات، ثم يحاول أن يمحو نسخة بعينها برقمها، مرة عادية ومرة بترويسة التجاوز، ثم يحاول أن يقصر مدة القفل، ثم يكتب فوق ملف المستودع الرئيسي:
docker exec --env-file backup.env lab-s3lock-tools sh -c '
aws s3 rm s3://backups/kopia/ --recursive | tail -1
echo "--- objects visible:"; aws s3 ls s3://backups/kopia/ | wc -l
set -- $(aws s3api list-object-versions --bucket backups --prefix kopia/p --query "Versions[0].[Key,VersionId]" --output text)
echo "--- permanent delete of $1 version $2:"
aws s3api delete-object --bucket backups --key "$1" --version-id "$2"
aws s3api delete-object --bucket backups --key "$1" --version-id "$2" --bypass-governance-retention
echo "--- shorten the retention:"
aws s3api put-object-retention --bucket backups --key "$1" --version-id "$2" --retention Mode=COMPLIANCE,RetainUntilDate=2026-10-12T00:00:00Z
echo "--- overwrite kopia.repository:"
echo garbage > /tmp/g; aws s3api put-object --bucket backups --key kopia/kopia.repository --body /tmp/g --query VersionId
echo "--- versions kept:"; aws s3api list-object-versions --bucket backups --prefix kopia/ --query "{versions: length(Versions), deleteMarkers: length(DeleteMarkers)}"'والمخرج سوف يكون كما يلي:
delete: s3://backups/kopia/xn0_cf03ea61e52ceaffa9a97c9c87a8dee0-sa41ae0b513d00cbe146-c1
--- objects visible:
0
--- permanent delete of kopia/p30947f6863c6d40e3c98690e8059db96-s4f65e0441f7f1af5146 version 67228c25998e52ad4d2afe2e42dea6db:
aws: [ERROR]: An error occurred (AccessDenied) when calling the DeleteObject operation: Access Denied.
aws: [ERROR]: An error occurred (AccessDenied) when calling the DeleteObject operation: Access Denied.
--- shorten the retention:
aws: [ERROR]: An error occurred (AccessDenied) when calling the PutObjectRetention operation: Access Denied.
--- overwrite kopia.repository:
"67228c100fe65a315c48a6b30abe4dc5"
--- versions kept:
{
"versions": 44,
"deleteMarkers": 24
}والصورة التالية تلخص الهجوم ونتيجته:

في المخرج أعلاه لاحظ التالي:
- الحذف العادي نجح، فلم يعد يظهر أي ملف، ولكن هذا هو الجزء المخيف فقط، فكل ما حدث هو 24 علامة حذف فوق 44 نسخة باقية.
- الحذف برقم النسخة رفض مرتين، والثانية بترويسة التجاوز لم تفد، لأن الوضع Compliance والهوية لا تملك صلاحية التجاوز أصلاً.
- تقصير مدة القفل رفض، أما تمديدها فمسموح، وهذا ما يجعل المخترق عاجزاً عن «تحضير» الملفات للحذف لاحقاً.
- الكتابة فوق
kopia.repositoryنجحت، فأي جهاز يتصل بالمستودع الآن سوف يقرأ «garbage»، وهذا ما سنراه في الاستعادة.
وهل يستطيع المدير نفسه أن يحذف؟ حاولنا بمفاتيح المدير مع ترويسة التجاوز على نسخة Compliance، ثم على كائن جديد بوضع Governance مرة دون الترويسة ومرة معها:
docker exec --env-file admin.env lab-s3lock-tools sh -c '
set -- $(aws s3api list-object-versions --bucket backups --prefix kopia/p --query "Versions[0].[Key,VersionId]" --output text)
aws s3api delete-object --bucket backups --key "$1" --version-id "$2" --bypass-governance-retention
echo x > /tmp/x; V=$(aws s3api put-object --bucket backups --key governance-test.txt --body /tmp/x --object-lock-mode GOVERNANCE --object-lock-retain-until-date 2026-10-18T00:00:00Z --query VersionId --output text)
echo "--- admin, no bypass:"; aws s3api delete-object --bucket backups --key governance-test.txt --version-id $V
echo "--- admin, with bypass:"; aws s3api delete-object --bucket backups --key governance-test.txt --version-id $V --bypass-governance-retention'aws: [ERROR]: An error occurred (AccessDenied) when calling the DeleteObject operation: Access Denied.
--- admin, no bypass:
aws: [ERROR]: An error occurred (AccessDenied) when calling the DeleteObject operation: Access Denied.
--- admin, with bypass:
{
"VersionId": "67228c0a781825b9e750d9e70af62e83"
}نسخة Compliance رفضت حتى للمدير مع التجاوز، ونسخة Governance حذفها المدير عندما أرسل الترويسة فقط، وهذا هو الفرق بين الوضعين في سطرين: من يسرق مفاتيح المدير يستطيع أن يمحو نسخ Governance، ولا يستطيع أن يمحو نسخ Compliance.
الاستعادة من جهاز نظيف إلى ما قبل الهجوم
نفترض الآن أن سيرفر التطبيق لم يعد موثوقاً، فنستعيد من جهاز آخر بإعداد Kopia جديد، ونحاول أولاً الاتصال بالمستودع كما هو، ثم كما كان في الوقت الذي سجلناه قبل الهجوم:
kr() { docker run --rm --network lab-s3lock-net --hostname restore-box \
-v lab-s3lock-kopia-restore:/app/config -v lab-s3lock-restored:/restore \
-e KOPIA_PASSWORD=repo-pass-change-me-01 kopia/kopia:0.23.1 "$@"; }
C="--bucket backups --prefix kopia/ --endpoint lab-s3lock-seaweed:8333 --disable-tls --access-key BACKUPKEY001 --secret-access-key backup-secret-change-me-01"
kr repository connect s3 $C
kr repository connect s3 $C --point-in-time=2026-10-11T10:47:08Z
kr snapshot list --all
kr snapshot restore k48c4de681b945c4673e17e3a8a75b179 /restore/data
docker run --rm -v lab-s3lock-restored:/b:ro --entrypoint sh kopia/kopia:0.23.1 -c \
'cd /b/data && find . -type f | wc -l && find . -type f -exec md5sum {} + | sort -k2 | md5sum'والمخرج سوف يكون كما يلي:
error connecting to repository: invalid format blob: invalid character 'g' looking for beginning of value
Connected to repository.
root@app-server:/data
2026-10-11 10:46:48 UTC k48c4de681b945c4673e17e3a8a75b179 31.5 MB drwxr-xr-x files:201 dirs:3 (latest-1,hourly-1,daily-1,weekly-1,monthly-1,annual-1)
Restoring to local filesystem (/restore/data) with parallelism=8...
Restored 201 files, 3 directories and 0 symbolic links (31.5 MB).
201
4b39fc8814cfb050b7d0b382008cf9ab -الاتصال العادي فشل لأن ملف المستودع أصبح «garbage»، أما الاتصال بالخيار --point-in-time فجعل Kopia يقرأ من كل ملف النسخة التي كانت الأحدث في ذلك الوقت، ويتجاهل علامات الحذف والكتابات اللاحقة، فظهرت النسخة المحذوفة واستعدنا 201 ملفاً بالبصمة نفسها التي سجلناها قبل النسخ. ولاحظ أن هذا الاتصال للقراءة، فلا تأخذ منه نسخاً جديدة، وإنما استعد البيانات، ثم أنشئ Bucket جديداً ومستودعاً جديداً من جهاز نظيف.
نكسرها: تمديد القفل جعل الاستعادة الزمنية تفشل
قفل لسبعة أيام له مشكلة واضحة: Kopia لا يعيد كتابة البيانات التي لم تتغير، فملف البيانات الذي كتب اليوم تستخدمه نسخ الشهر القادم كله، وبعد سبعة أيام يصبح قابلاً للحذف، فيستطيع المخترق أن يمحوه برقمه. ولذلك يقدم Kopia الخيار kopia maintenance set --extend-object-locks true الذي يمدد أقفال الملفات المستخدمة في كل صيانة كاملة، بشرط أن تكون فترة الصيانة الكاملة أقصر من مدة القفل بيوم على الأقل كما في توثيقه. وقد أعدنا التجربة نفسها مع هذا الخيار، والهجوم نفسه، فكانت نتيجة الاستعادة كما يلي:
--- connect now:
error connecting to repository: invalid format blob: invalid character 'g' looking for beginning of value
--- connect as of 2026-10-11T10:37:32Z:
error connecting to repository: repository not initialized in the provided storageالبيانات كلها ما زالت مقفلة وموجودة، ولكن Kopia لم يجد المستودع في ذلك الوقت. والسبب أن المخترق نفسه شغل الصيانة الكاملة، فمددت الأقفال، ووجدنا أن SeaweedFS 4.48 يغير تاريخ آخر تعديل LastModified للنسخة عندما يمدد قفلها، والخيار --point-in-time يعتمد على هذا التاريخ، فأصبحت النسخ الأصلية تبدو وكأنها كتبت بعد الهجوم. وأثبتنا ذلك بثلاثة أوامر: نرفع كائناً مقفلاً، وننتظر عشرين ثانية، ونمدد قفله:
echo z > /tmp/z
V=$(aws s3api put-object --bucket backups --key mtime-test.txt --body /tmp/z --object-lock-mode COMPLIANCE \
--object-lock-retain-until-date 2026-10-13T00:00:00Z --query VersionId --output text)
aws s3api list-object-versions --bucket backups --prefix mtime-test --query "Versions[].LastModified"
sleep 20
aws s3api put-object-retention --bucket backups --key mtime-test.txt --version-id $V \
--retention Mode=COMPLIANCE,RetainUntilDate=2026-10-14T00:00:00Z
aws s3api head-object --bucket backups --key mtime-test.txt --version-id $V --query "[LastModified,ObjectLockRetainUntilDate]"والمخرج على SeaweedFS 4.48 ثم على MinIO الذي بنيناه في مقال التثبيت كما يلي:
[
"2026-10-11T10:39:59+00:00"
]
[
"2026-10-11T10:40:27+00:00",
"2026-10-14T00:00:00+00:00"
]
[
"2026-10-11T10:43:14.788000+00:00"
]
[
"2026-10-11T10:43:14+00:00",
"2026-10-14T00:00:00+00:00"
]في SeaweedFS تقدم تاريخ التعديل 28 ثانية مع تمديد القفل، وفي MinIO بقي كما هو. والدرس هنا أكبر من هذا الخلل بعينه: الاستعادة من هجوم تعتمد على تفاصيل صغيرة في الخادم لا تظهر في أي جدول توافق، لذلك جرب الاستعادة الكاملة بعد هجوم مثل هذا على الخادم الذي تستخدمه وبالإعداد الذي تستخدمه، قبل أن تحتاجها. وحتى يتغير هذا السلوك في SeaweedFS، فأمامك خياران: أن تترك تمديد الأقفال مغلقاً كما في تجربتنا الناجحة، وتجعل مدة القفل أطول من أطول مدة قد يبقى فيها المخترق دون أن تلاحظه، مع أخذ نسخة كاملة جديدة إلى مستودع جديد كل فترة حتى لا يعتمد كل شيء على ملفات قديمة انتهى قفلها، أو أن تستخدم خادماً يحفظ تاريخ التعديل عند التمديد بعد أن تختبره بالأوامر أعلاه.
التنظيف
ال Volumes محلية، فحذفها يحذف البيانات المقفلة معها، وهذا ممكن هنا لأننا نملك القرص، أما في AWS فلا يوجد ما يشبهه قبل انتهاء المدة:
docker rm -f lab-s3lock-tools lab-s3lock-seaweed
docker volume rm lab-s3lock-s3data lab-s3lock-kopia lab-s3lock-kopia-restore lab-s3lock-restored lab-s3lock-appdata
docker network rm lab-s3lock-net
rm -f admin.env backup.envكيف تصمم النسخ بقفل الكائنات؟
- مدة القفل: أطول من الوقت الذي قد يمر قبل أن تكتشف الاختراق، مضافاً إليه وقت الاستعادة، وتذكر أنك تدفع تخزين كل نسخة طوال هذه المدة، حتى المحذوفة منها.
- هويتان منفصلتان: هوية للنسخ على السيرفر تكتب وتضع القفل فقط، وهوية للإدارة لا تعيش على أي سيرفر تطبيق، والأفضل أن يكون ال Bucket في حساب أو خادم منفصل عن الإنتاج.
- Compliance بعد التجربة: ابدأ ب Governance ومدة قصيرة، فإذا عملت الأداة والاستعادة انتقل إلى Compliance.
- Lifecycle للنسخ القديمة: بعد انتهاء القفل لا يحذف أحد النسخ غير الحالية، فضع قاعدة NoncurrentVersionExpiration أطول من مدة القفل، كما شرحنا في قواعد Lifecycle.
- اختبار الاستعادة: بالخيار
--point-in-timeمن جهاز نظيف، بشكل دوري، كما في قاعدة 3-2-1. - مراقبة علامات الحذف: ارتفاع مفاجئ في عدد علامات الحذف أو الكتابات على ملفات المستودع علامة هجوم، فراقبها في سجلات الخادم.
أخطاء شائعة
- Versioning وحده: يحفظ النسخ، ولكن من يملك صلاحية الحذف برقم النسخة يمحوها كلها.
- Governance مع مفاتيح مدير على السيرفر: المخترق يرسل ترويسة التجاوز ويحذف.
- الظن أن القفل لا يعمل لأن الحذف نجح: الحذف العادي يضيف علامة حذف فقط، واختبر الحذف برقم النسخة.
- Compliance بمدة خاطئة: لا يمكن تقصيرها، فاحسبها قبل التفعيل.
- Garage للنسخ المقفلة: لا يدعم Object Lock ولا Versioning.
- عدم تجربة الاستعادة بعد هجوم: في تجربتنا فشلت الاستعادة الزمنية مع تمديد الأقفال على SeaweedFS 4.48، ولم نكن لنعرف دون التجربة.
الخلاصة
- Object Lock يقفل النسخة وليس المفتاح، ويشترط Versioning، والحذف العادي يضيف علامة حذف، والحذف برقم النسخة يرفض بالخطأ 403.
- Governance يفكه من يملك صلاحية التجاوز، وCompliance لا يفكه أحد قبل التاريخ، وLegal Hold دون تاريخ حتى يرفع.
- في التجربة حذف المخترق النسخة وكل الملفات وكتب فوق ملف المستودع بمفاتيح النسخ نفسها، ولكنه لم يستطع أن يمحو نسخة واحدة، واستعدنا 201 ملفاً بالبصمة نفسها بالخيار --point-in-time.
- Kopia يدعم القفل من داخله، وrclone منذ 1.74.0، وrestic يحتاج الإضافة فقط بدلاً منه، وSeaweedFS وMinIO وCeph RGW تدعمه، وGarage لا يدعمه.
- مع تمديد الأقفال تلقائياً على SeaweedFS 4.48 تغير تاريخ آخر تعديل وفشلت الاستعادة الزمنية، فجرب الاستعادة على خادمك قبل أن تحتاجها.
دليلالنسخ الاحتياطي بقاعدة 3-2-1 واختبار الاستعادةكيف تحدد كم بيانات تقبل أن تخسر، وتبني نسخاً لا يستطيع المخترق حذفها، وتتأكد أنك تستطيع استعادتها فعلاً.
دليلأي أداة نسخ احتياطي Backup تناسب سيرفرك؟مقارنة عملية لأشهر أدوات النسخ الاحتياطي مفتوحة المصدر على البيانات نفسها في الوقت والحجم والذاكرة، لتعرف أيها يناسب سيرفرك.
دليلS3 من داخل تطبيقك: الصلاحيات والروابط الموقعة والتكلفةكيف يرفع تطبيقك الملفات إلى S3 بمفتاح محدود وروابط موقعة، ويحمي الملفات من الكتابة فوقها، ويتجنب البنود التي تضخم الفاتورة.
دليلتخزين الكائنات S3 على سيرفرك: التثبيت والبنيةكيف تثبت ثلاث أدوات تخزين S3 مفتوحة المصدر في إعداد صغير يتحمل الأعطال، وماذا يحدث فعلاً عندما يتوقف سيرفر أو يتلف قرص.سجل التحديثات
- أكتوبر 2026: كتابة المقال، والتجربة على SeaweedFS 4.48 مع Kopia 0.23.1 وaws-cli 2.37.12 في Docker Engine 29.8.0، واختبار تاريخ التعديل أيضاً على MinIO RELEASE.2025-10-15T17-29-55Z مبني من الكود، والمعلومات من توثيق Amazon S3 وKopia وrclone كما قرأناها في 11 أكتوبر 2026.