From 628c2114a100177dc47a6f8b65d5d26c9202acd8 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 2 Nov 2026 09:35:31 +0100 Subject: [PATCH 1/2] fix(recovery): retain the imported Cloudflare state package --- AGENTS.md | 5 +++-- README.md | 6 ++++++ recovery/state/configuration.json.age | Bin 0 -> 493 bytes recovery/state/manifest.json | 16 ++++++++++++++++ recovery/state/state.json.age | Bin 0 -> 2380 bytes 5 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 recovery/state/configuration.json.age create mode 100644 recovery/state/manifest.json create mode 100644 recovery/state/state.json.age diff --git a/AGENTS.md b/AGENTS.md index 0fe17fb..7149c27 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,8 +30,9 @@ the clean and disposable failure fixtures locally. plans. The maintainer-approved exception is authenticated ciphertext for essential bootstrap recovery in `recovery/`, bound by its registered package validator and an independently recoverable key. Ordinary service credentials - stay in OpenBao. The current package covers a historical DNS snapshot only; - adding state recovery requires its own reviewed format and recovery proof. + stay in OpenBao. Paired state packages bind encrypted state and desired + configuration to their reviewed source and run; authenticate recovery with + the independently held identity before using either file. - Import every existing resource before any apply. During migration, convert the plan to JSON and run `scripts/assert-no-destructive-plan.sh`; any delete or replacement is a hard stop. diff --git a/README.md b/README.md index 0993097..7fecc20 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,12 @@ The retained pre-migration DNS snapshot lives as authenticated ciphertext in Keychain. This historical snapshot is neither current desired configuration nor OpenTofu state; the runbook describes its limits and recovery checks. +The paired package in [`recovery/state/`](recovery/state/) retains captured +OpenTofu state and its complete desired configuration. Its manifest binds both +encrypted files to their source and run. Verify authenticated recovery before +using a generation, and refresh it after each reviewed apply. Provider convergence +and remote backend locking have separate acceptance checks in the runbook. + In those procedures the provider reads `CLOUDFLARE_API_TOKEN`, and the state backend reads the standard `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` variables. Keep all three out of shell history and plaintext repository files. diff --git a/recovery/state/configuration.json.age b/recovery/state/configuration.json.age new file mode 100644 index 0000000000000000000000000000000000000000..530c36e628b811e24615383576e558033b5d3cbe GIT binary patch literal 493 zcmVeWIaEhOMNJAZF*vf=EFb{Q70=}+L(y3Q&##6*IV$`_S0MlGb)?lM zax=%81igPpBSYv1hd*RtkfQbPpXV_e*3)0y@yq@hJTB_!29FMR z3i4C;xLm?luyMnaI{(^iXv?qNCyc8w9Nq6YSATqA2Dp|%5EPo4v6pw)2Y-eRLpJ$u z=e>nh^Ae%PrV>fe%YlRgAZ`jQrp;8jy+6AeKOT}k*BGjK?@TRt)GQk@4trI|iUM+z z>O&KLlIhTgD_yIztJD^SOGIipkJ7L9k%jTT6_L`aqfIi4eno6Yz#Ys1p>-^QdJa{+ zq(-jOrq1fMYgQe&U$nA`hp?4;o@1aZdR@5T4B0${5ByO~)b jr_S3_cAVf4bUtGq*-QZP%)HlUs$T60+`JIB!ukYk@%Y&y literal 0 HcmV?d00001 diff --git a/recovery/state/manifest.json b/recovery/state/manifest.json new file mode 100644 index 0000000..e444314 --- /dev/null +++ b/recovery/state/manifest.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "kind": "cloudflare-state-recovery", + "capturedAt": "2026-11-02T00:46:14Z", + "sourceCommit": "0925d157610d9e281e07557e689a321dadc791e9", + "sourceRun": 41368905763, + "recipient": "age1rk6fs67kly3h4zux5za429z3grjtvs8vcunav4sa28pxk738najsk8wgs6", + "lineage": "d981e029-9986-63a7-2f54-379ce10ee6a4", + "serial": 1, + "accountIdSha256": "3080d90d2ff8a5d38ec8c5dae7b9207b928b353c68f4dd81e0a378cf932a5f95", + "zoneNameSha256": "1fc6c364e0d592421f88ba2d2cf6cf15691bbc711113d7907c159a638f35aeb0", + "stateCiphertextSha256": "4dd0e162814c478c0d675e034eec887de40a382a96f9bb251afb841fabe2709f", + "configurationCiphertextSha256": "5c9527c73d0bfacf126939b555a609c6895ca98cbeb044d7793a33ee65460030", + "statePlaintextSha256": "bf7b431748872f53861aa655cb40c0f2734410f03514e97de5f30ea0aaf69442", + "configurationPlaintextSha256": "84c9b6ea504f82a9fd694e701287e30d54fe9b42ecf81dfccfbf2cba9873239a" +} diff --git a/recovery/state/state.json.age b/recovery/state/state.json.age new file mode 100644 index 0000000000000000000000000000000000000000..8607037c4e93ed420b1b2b18bf63c7287121b5ac GIT binary patch literal 2380 zcmV-S3A6TLXJsvAZewzJaCB*JZZ2Q zP--_wb5lfiV{|c4T5WD*Yfw&3YHCtQLUc`RXfz60bXs?DWNA%MV{|x2XLxWpdP+!5 zb!caAXKqU~MR;ssOHX+;Z)j{mW^W2DEiE7`NJw-|Qg2LoF?UljXfif!Zee0VWm0N1 zOKVm(S5rf7a#l4pT5n`mMM(-*k^^1Cq~%5oKDKpErLxtU@1l$b&f19ITXY0E5;0{Y zupP0e$)ao-!A620KL~gM2uLopqn%y;&DutbahsowLhBP9T38+D@_GkwQ;c6A7jjkG(%v4Tu@quI;;4`KR}ZU}Y80{& z-2yB8ZVI&Y%ZjbUF*&4IEdd9O=*KT6u3U(lmD*=15!lM=+Lk-4RTwr@DiZ{wBEvOP zI}|vXj_fBdD;INxIVivs^Drb+ZH%j+jDi+~Xv>Y;1LzXcO{K^EALsXW04{CR^ z>EO`dI}I^>kUH47B1|UiG}2Oz^(xl}gfM-Bki_#cDdw+ht4T!|6a-8*`QIo4a;AP4ZtFR^c)DZ{G(7bq*mIZKeJaiOD`7`k43xXe`uWRjGL z#2vVcYJX~X#DMCM#$9+ykNjm!QB)<2v`A@FAuwwtN=pTsaAVs6Y8HUJ7x>zFSFs*c z=1l?ir%|JvDYzrY`nE>bjaY?MsU^R_XgR0+?ieqCAu19RUq_qjD0KQ!D~FB z0DC4v1E~6UfG&BHha>h6AQ??58-7$!7rX>`#osR4UON+oTPU{_40|vpg>j^RWgbbK zaCBpuIHwy0bW6xF;BSmNL%Cv_6sV@pGAZp}|K6eL!vBnsPuCp&amE zR2BK>^7zaLQ#<;)5_-|5y{5=1T!(}~l^TtsrvFyazzi;4vo;%<4VTj~w9i!s)o8*6D9us_eBz6>rMhc;YqeAX%A1r+0o%S&rJ#7>Vd?pWGm5=SBlc-0B$4KG z-yOnv>cD~ds>qqYoWYuqgIl}nl8a)i@-rD_%@Z)M`Ebv?T*cUaa>(Chrw9+nFNsP% zi*{;}(R$tfw%d;9;5TT#y=3x<{>9hV_f5w4w?FZda;BkDgb-;q8nC;Oisf+v1-wIE zx3dQGhkKzGR^cULxf{oI*RDRLQumO5%eb;}?70Fpr-XhMGq3r93iZ=7iMtc=WnWPfTmX zqoFp69C2O9*T^rR2lkCO5-ms;5Ulbepy(UuXvDbDpJHVxu+DsFzVy-oh;#KzhF%iB zlXsL!$zl#A$=|^#y%tfUk-GEWugXrQWo3+sV-fOdfJ^+`w@Lof2=sIZXR&Q5yDj{* zo(t;@Qn{>w2mlk5=PMkfv2>k-THwyOIC<&i%4rv)>wVi#J`mq=)xO!WC@$QOCtWFs z{dTk;O}b5znK-GPjtbC`(Cm6}G!HK!nMw&9gcXF~PN@xC=uVxaK%(l=J|i@fiI2RB zrm%zTRUj1p8*-V9lCY_pL`v%sx=k4K2&VI5HueQ3qb7h5H^IK;Vi}V+>l}di*Mn+2LN&uO`v1}Ifm>lFEE#SBqn!QJ+rYj*m0{I_5s4_VQ9t-yyY z+0SutlmTv$PiFl?XjaqqM{-y4M!^FJcFY+H)DXsztZ3S>RV6PP01Jj4cCc zyA8(`N8Nk#s%I2fyKV3=pQDa=U?AmNGh1GivWpJL<*)h13DP}P>5dkQzLNeNe{n9> zBW7@{yxB=Xp-M92bz-irh6tY}#si#JyWgD?pz7PwOTkQLkyztm)ixRJPR(WE1cgup z*u{eDR&5cDCXSWy$w95gV0e8AFvf9Zp^@flp0x_Eez3L2NnrlV4T?Mun;@(q0)w3t z0YkSqPK%DOT$?C6TMcz_2<}^Wu82&i#a@h literal 0 HcmV?d00001 From cd658e6552e3d74bec0b2183af08cae1c5776713 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Mon, 2 Nov 2026 09:44:20 +0100 Subject: [PATCH 2/2] docs(recovery): describe the retained state generation --- docs/runbooks/bootstrap-and-recovery.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/runbooks/bootstrap-and-recovery.md b/docs/runbooks/bootstrap-and-recovery.md index ba73b15..8ec46f0 100644 --- a/docs/runbooks/bootstrap-and-recovery.md +++ b/docs/runbooks/bootstrap-and-recovery.md @@ -2,7 +2,8 @@ This procedure keeps Cloudflare recovery independent of the Kubernetes platform. It never reads credentials from a cluster-hosted secret and never -writes credentials or state into this repository. +writes plaintext credentials or state into this repository. Essential encrypted +state and configuration recovery packages use the custody procedure below. ## Prerequisites @@ -311,8 +312,10 @@ Active state still belongs in the independently accessible locked R2 backend. Its minimum authenticated encrypted recovery copy belongs with this owner, including source revision, lineage and serial bindings. That state format, publication path, actual lock-contention test and clean-environment recovery -remain part of the first migration. No real state recovery package is currently -retained, and no apply automation is enabled by the packaging helpers. +remain part of the first migration. The retained initial generation contains the +runner-local zone import and matching desired configuration from the confirmed +bootstrap plan; it does not establish provider convergence or remote locking. +No apply automation is enabled by the packaging helpers. ### State recovery format