Cross-boundary media transfer runbook¶
Purpose¶
This runbook standardizes how Linux update content moves from the connected low side into the disconnected high side using either Azure Data Box or manual hard drives. The format stays the same regardless of media type so the operator flow is easy to learn, repeat, and audit.
Standard bundle layout¶
Every transfer should use this shape:
<snapshot-id>/
├── payload/
├── transfer-manifest.json
├── transfer-manifest.json.sig
└── SHA256SUMS
payload/holds the exported Pulp contenttransfer-manifest.jsonrecords bundle ID, snapshot ID, source/target environment, repos, classification, operator identity, signing metadata, and file metadatatransfer-manifest.json.sigis the detached manifest signature generated with an operator-controlled private keySHA256SUMSgives a per-file integrity check that can be verified on either side
The transfer script validates the manifest schema, rejects paths that escape the bundle, rejects symlinks, enforces the approved classification labels (public, controlled, confidential, secret, top-secret), and fails on malformed or unexpected checksum entries.
Signing key custody¶
- Do not store signing private keys in this repository or on transfer media.
- Keep the low-side manifest-signing private key in the local approved key custody system.
- Keep the matching trusted public key available to the high-side receiving operator through an approved, independently controlled channel.
- Treat a bundle with
transfer-manifest.json.sigas unverifiable unless the high-sideverifycommand is run with--trusted-public-key.
Low-side staging flow¶
- Export the approved snapshot content from the low-side Pulp host into a local directory.
- Build the transfer bundle metadata:
python3 automation/bootstrap/transfer_bundle.py build \
--source /srv/pulp/exports/<export-dir> \
--bundle-dir /srv/pulp/staging/<snapshot-id> \
--bundle-id <bundle-id> \
--snapshot-id <snapshot-id> \
--mode delta \
--transfer-device databox \
--source-environment azure-gov-low \
--target-environment azure-secret-high \
--classification controlled \
--operator <low-side-operator-or-automation-id> \
--signing-key /secure/operator-keys/transfer-manifest-signing.key \
--repo ubuntu-jammy-amd64-base \
--repo ubuntu-jammy-amd64-updates \
--repo ubuntu-jammy-amd64-security
- Confirm that
transfer-manifest.json.sigwas created and record the signing key identifier in the local operational system of record. - Copy the staged bundle to the approved transport device.
- Record chain-of-custody details in the local operational system of record.
If the same bundle is traveling by escorted removable drive instead of Data Box, only change --transfer-device manual-drive.
High-side receiving flow¶
- Mount or connect the inbound media.
- Receive the bundle into the local import staging root. This verifies the manifest, signature, hashes, paths, and symlink policy before copying anything into the import root:
python3 automation/bootstrap/transfer_bundle.py receive \
--bundle-dir /mnt/inbound-media/<snapshot-id> \
--import-root /srv/pulp/import \
--trusted-public-key /secure/operator-keys/transfer-manifest-signing.pub \
--operator <high-side-receiving-operator-or-automation-id>
The command stages the verified bundle at /srv/pulp/import/<snapshot-id> and writes a receipt at /srv/pulp/import/_receipts/<snapshot-id>.receive-receipt.json.
3. Preserve the receipt as import evidence. It records bundle ID, snapshot ID, previous snapshot ID, source/target environments, repositories, classification, hash digests, signature verification status, receiving operator, and staged path.
4. If receiving succeeds, hand /srv/pulp/import/<snapshot-id> to the high-side Pulp import workflow.
5. Publish the approved snapshot behind the stable internal repo URL.
Operational rules¶
- Use baseline bundles for first hydration and disaster recovery.
- Use delta bundles for routine patch movement.
- Never modify files in
payload/after the manifest and checksums are generated. - Never import a signed bundle without verifying it with the trusted public key.
- Never manually copy verified payload files around the import root; use
transfer_bundle.py receiveso retry behavior and receipt metadata stay deterministic. - Keep the previous known-good snapshot available for rollback until the new snapshot is proven.
- Use the same bundle format for Commercial/Government to Secret/Top Secret so training and runbooks stay consistent.
Script-level validation guidance¶
When changing the transfer script, validate at least these cases in local scratch directories under the repository workspace:
- Build, verify, and receive a signed known-good bundle with an operator-generated test key.
- Tamper with
transfer-manifest.json, a payload file, andSHA256SUMSseparately; each change must makeverifyfail. - Add an unexpected payload file and a symlink inside the bundle; each must make
verifyfail. - Attempt to build with a classification outside the approved whitelist;
buildmust fail before writing a usable bundle.