[{"data":1,"prerenderedAt":3252},["ShallowReactive",2],{"post:\u002F2026\u002F03\u002F04\u002Fnode-js-gcloud-rotating-service-account-keys-before-they-expire\u002F":3},{"post":4,"newer":3219,"older":3229,"related":3240,"series":3250},{"id":5,"title":6,"body":7,"canonical":3201,"categories":3202,"date":3205,"description":3206,"extension":3207,"featured":3208,"hero":3209,"image":3209,"meta":3210,"navigation":139,"path":3211,"readingTime":143,"seo":3212,"series":3209,"seriesOrder":3209,"sites":3213,"source":3209,"stem":3214,"tags":3215,"updated":3209,"url":3217,"__hash__":3218},"blog\u002Fblog\u002F2026\u002F03\u002F04\u002Fnode-js-gcloud-rotating-service-account-keys-before-they-expire.md","Node.js: gcloud – Rotating Service Account Keys Before They Expire",{"type":8,"value":9,"toc":3192},"minimark",[10,19,24,27,30,81,84,244,248,302,306,422,426,429,471,474,511,514,522,525,559,562,596,602,606,3021,3025,3110,3114,3188],[11,12,13,14,18],"p",{},"Service account keys are the credential everyone forgets about until a security review flags one that is four years old and still active. By default a user-managed key never expires, and once the JSON file is downloaded Google can't give it to you again, so the only copy lives wherever someone saved it. This script follows Google's rotation order in two runs. The first finds keys past an age threshold on a service account, mints one replacement, and hands it off through a local file or Secret Manager. Once the new key is deployed wherever the old one was used, a second run with ",[15,16,17],"code",{},"--disable-old"," disables rather than deletes the old keys, so anything still referencing them fails loudly, with a one-command rollback.",[20,21,23],"h2",{"id":22},"before-you-rotate-can-you-delete-the-key-instead","Before you rotate: can you delete the key instead?",[11,25,26],{},"Google's key-management guidance is to pick a more secure alternative \"whenever possible,\" and it lists them: an attached service account for code on Compute Engine, GKE, Cloud Run, and similar; Workload Identity Federation for workloads outside Google Cloud (GitHub Actions, AWS, Azure, on-premises OIDC or SAML providers). Rotation is the fallback for the keys you genuinely can't remove, such as a third-party SaaS tool that only accepts a JSON key.",[11,28,29],{},"Two organization policy facts change how this script behaves in practice:",[31,32,33,49],"ul",{},[34,35,36,37,40,41,44,45,48],"li",{},"Organizations created on or after May 3, 2024 enforce ",[15,38,39],{},"iam.managed.disableServiceAccountKeyCreation"," by default (the older equivalent is ",[15,42,43],{},"iam.disableServiceAccountKeyCreation","). In those orgs ",[15,46,47],{},"keys.create"," fails until the project is exempted, which is the point: every key becomes a conscious exception.",[34,50,51,54,55,58,59,58,62,58,65,58,68,58,71,58,74,58,77,80],{},[15,52,53],{},"iam.serviceAccountKeyExpiryHours"," makes new keys expire automatically. It accepts only allowed values: ",[15,56,57],{},"1h",", ",[15,60,61],{},"8h",[15,63,64],{},"24h",[15,66,67],{},"168h",[15,69,70],{},"336h",[15,72,73],{},"720h",[15,75,76],{},"1440h",[15,78,79],{},"2160h"," (90 days), and applies only to keys created after the policy is set. Google recommends expiry for temporary access rather than production workloads, but a 90-day cap is a good backstop under a rotation job like this one.",[11,82,83],{},"A GitHub Actions deployment, for example, doesn't need a key at all. The Workload Identity Federation setup from Google's deployment-pipeline guide replaces it (the attribute condition is mandatory for GitHub, to stop tokens from other GitHub organizations being accepted):",[85,86,91],"pre",{"className":87,"code":88,"language":89,"meta":90,"style":90},"language-bash shiki shiki-themes github-dark","gcloud iam workload-identity-pools create github \\\n  --project=my-project --location=global --display-name=\"GitHub Actions\"\n\ngcloud iam workload-identity-pools providers create-oidc my-org \\\n  --project=my-project --location=global --workload-identity-pool=github \\\n  --issuer-uri=\"https:\u002F\u002Ftoken.actions.githubusercontent.com\u002F\" \\\n  --attribute-mapping=\"google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner\" \\\n  --attribute-condition=\"assertion.repository_owner=='my-org'\"\n\ngcloud iam service-accounts add-iam-policy-binding deployer@my-project.iam.gserviceaccount.com \\\n  --role=roles\u002Fiam.workloadIdentityUser \\\n  --member=\"principalSet:\u002F\u002Fiam.googleapis.com\u002Fprojects\u002F123456789012\u002Flocations\u002Fglobal\u002FworkloadIdentityPools\u002Fgithub\u002Fattribute.repository\u002Fmy-org\u002Fmy-repo\"\n","bash","",[15,92,93,119,134,141,161,173,184,195,204,209,227,235],{"__ignoreMap":90},[94,95,98,102,106,109,112,115],"span",{"class":96,"line":97},"line",1,[94,99,101],{"class":100},"svObZ","gcloud",[94,103,105],{"class":104},"sU2Wk"," iam",[94,107,108],{"class":104}," workload-identity-pools",[94,110,111],{"class":104}," create",[94,113,114],{"class":104}," github",[94,116,118],{"class":117},"sDLfK"," \\\n",[94,120,122,125,128,131],{"class":96,"line":121},2,[94,123,124],{"class":117},"  --project=my-project",[94,126,127],{"class":117}," --location=global",[94,129,130],{"class":117}," --display-name=",[94,132,133],{"class":104},"\"GitHub Actions\"\n",[94,135,137],{"class":96,"line":136},3,[94,138,140],{"emptyLinePlaceholder":139},true,"\n",[94,142,144,146,148,150,153,156,159],{"class":96,"line":143},4,[94,145,101],{"class":100},[94,147,105],{"class":104},[94,149,108],{"class":104},[94,151,152],{"class":104}," providers",[94,154,155],{"class":104}," create-oidc",[94,157,158],{"class":104}," my-org",[94,160,118],{"class":117},[94,162,164,166,168,171],{"class":96,"line":163},5,[94,165,124],{"class":117},[94,167,127],{"class":117},[94,169,170],{"class":117}," --workload-identity-pool=github",[94,172,118],{"class":117},[94,174,176,179,182],{"class":96,"line":175},6,[94,177,178],{"class":117},"  --issuer-uri=",[94,180,181],{"class":104},"\"https:\u002F\u002Ftoken.actions.githubusercontent.com\u002F\"",[94,183,118],{"class":117},[94,185,187,190,193],{"class":96,"line":186},7,[94,188,189],{"class":117},"  --attribute-mapping=",[94,191,192],{"class":104},"\"google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner\"",[94,194,118],{"class":117},[94,196,198,201],{"class":96,"line":197},8,[94,199,200],{"class":117},"  --attribute-condition=",[94,202,203],{"class":104},"\"assertion.repository_owner=='my-org'\"\n",[94,205,207],{"class":96,"line":206},9,[94,208,140],{"emptyLinePlaceholder":139},[94,210,212,214,216,219,222,225],{"class":96,"line":211},10,[94,213,101],{"class":100},[94,215,105],{"class":104},[94,217,218],{"class":104}," service-accounts",[94,220,221],{"class":104}," add-iam-policy-binding",[94,223,224],{"class":104}," deployer@my-project.iam.gserviceaccount.com",[94,226,118],{"class":117},[94,228,230,233],{"class":96,"line":229},11,[94,231,232],{"class":117},"  --role=roles\u002Fiam.workloadIdentityUser",[94,234,118],{"class":117},[94,236,238,241],{"class":96,"line":237},12,[94,239,240],{"class":117},"  --member=",[94,242,243],{"class":104},"\"principalSet:\u002F\u002Fiam.googleapis.com\u002Fprojects\u002F123456789012\u002Flocations\u002Fglobal\u002FworkloadIdentityPools\u002Fgithub\u002Fattribute.repository\u002Fmy-org\u002Fmy-repo\"\n",[20,245,247],{"id":246},"requirements","Requirements",[31,249,250,261,268,274,293],{},[34,251,252,253,256,257,260],{},"A supported Node.js LTS release (22 or later) and the ",[15,254,255],{},"googleapis"," package (",[15,258,259],{},"npm install googleapis",").",[34,262,263,264,267],{},"Application Default Credentials: ",[15,265,266],{},"gcloud auth application-default login"," interactively, or, on a schedule, the service account a Cloud Run job runs as (Cloud Scheduler only triggers the job; it doesn't lend the script an identity).",[34,269,270,273],{},[15,271,272],{},"roles\u002Fiam.serviceAccountKeyAdmin"," on the project (or on the service account). It covers listing, creating, disabling, and deleting user-managed keys.",[34,275,276,277,280,281,284,285,288,289,292],{},"For ",[15,278,279],{},"--secret",": an existing secret in the same project and ",[15,282,283],{},"roles\u002Fsecretmanager.secretVersionAdder"," on it. Create the secret once with ",[15,286,287],{},"gcloud secrets create sa-reporting-key --replication-policy=automatic --project=my-project",". Whoever collects the key from it needs ",[15,290,291],{},"roles\u002Fsecretmanager.secretAccessor"," on that secret.",[34,294,295,296,298,299,301],{},"If the organization enforces ",[15,297,39],{}," or ",[15,300,43],{},", an exemption for this project, or the create call is refused.",[20,303,305],{"id":304},"parameters","Parameters",[307,308,309,325],"table",{},[310,311,312],"thead",{},[313,314,315,319,322],"tr",{},[316,317,318],"th",{},"Parameter",[316,320,321],{},"Required",[316,323,324],{},"Description",[326,327,328,342,354,371,383,399,410],"tbody",{},[313,329,330,336,339],{},[331,332,333],"td",{},[15,334,335],{},"--project",[331,337,338],{},"Yes",[331,340,341],{},"Project ID that owns the service account.",[313,343,344,349,351],{},[331,345,346],{},[15,347,348],{},"--service-account",[331,350,338],{},[331,352,353],{},"Service account email.",[313,355,356,361,364],{},[331,357,358],{},[15,359,360],{},"--max-age-days",[331,362,363],{},"No",[331,365,366,367,370],{},"Keys older than this (by ",[15,368,369],{},"validAfterTime",") are rotated. Default 90.",[313,372,373,377,380],{},[331,374,375],{},[15,376,279],{},[331,378,379],{},"One of",[331,381,382],{},"Secret ID in the same project; the new key is added as a new version.",[313,384,385,390,392],{},[331,386,387],{},[15,388,389],{},"--output",[331,391,379],{},[331,393,394,395,398],{},"Local path for the new key file, written with mode ",[15,396,397],{},"0600"," (also applied to an existing file).",[313,400,401,405,407],{},[331,402,403],{},[15,404,17],{},[331,406,363],{},[331,408,409],{},"Second run: create nothing, disable the keys over the threshold. Refuses if no enabled key within the threshold is left.",[313,411,412,417,419],{},[331,413,414],{},[15,415,416],{},"--dry-run",[331,418,363],{},[331,420,421],{},"Report what would happen; creates and disables nothing.",[20,423,425],{"id":424},"usage","Usage",[11,427,428],{},"See what would be rotated without making changes:",[85,430,432],{"className":87,"code":431,"language":89,"meta":90,"style":90},"node rotate-sa-keys.js --project my-project \\\n  --service-account reporting@my-project.iam.gserviceaccount.com \\\n  --max-age-days 90 --dry-run\n",[15,433,434,450,460],{"__ignoreMap":90},[94,435,436,439,442,445,448],{"class":96,"line":97},[94,437,438],{"class":100},"node",[94,440,441],{"class":104}," rotate-sa-keys.js",[94,443,444],{"class":117}," --project",[94,446,447],{"class":104}," my-project",[94,449,118],{"class":117},[94,451,452,455,458],{"class":96,"line":121},[94,453,454],{"class":117},"  --service-account",[94,456,457],{"class":104}," reporting@my-project.iam.gserviceaccount.com",[94,459,118],{"class":117},[94,461,462,465,468],{"class":96,"line":136},[94,463,464],{"class":117},"  --max-age-days",[94,466,467],{"class":117}," 90",[94,469,470],{"class":117}," --dry-run\n",[11,472,473],{},"Mint a replacement for keys older than 90 days and store it as a Secret Manager version:",[85,475,477],{"className":87,"code":476,"language":89,"meta":90,"style":90},"node rotate-sa-keys.js --project my-project \\\n  --service-account reporting@my-project.iam.gserviceaccount.com \\\n  --max-age-days 90 --secret sa-reporting-key\n",[15,478,479,491,499],{"__ignoreMap":90},[94,480,481,483,485,487,489],{"class":96,"line":97},[94,482,438],{"class":100},[94,484,441],{"class":104},[94,486,444],{"class":117},[94,488,447],{"class":104},[94,490,118],{"class":117},[94,492,493,495,497],{"class":96,"line":121},[94,494,454],{"class":117},[94,496,457],{"class":104},[94,498,118],{"class":117},[94,500,501,503,505,508],{"class":96,"line":136},[94,502,464],{"class":117},[94,504,467],{"class":117},[94,506,507],{"class":117}," --secret",[94,509,510],{"class":104}," sa-reporting-key\n",[11,512,513],{},"Sample output (illustrative):",[85,515,520],{"className":516,"code":518,"language":519,"meta":90},[517],"language-text","Found 3 user-managed key(s) on reporting@my-project.iam.gserviceaccount.com\nKey 3f9a1c2b... is 142 days old, exceeds 90-day threshold\nKey 77d0e4aa... is already disabled (SERVICE_ACCOUNT_KEY_DISABLE_REASON_USER_INITIATED)\nKey ef567890... is 12 days old, within threshold\nCreated key 0b1c2d3e..., stored in Secret Manager version projects\u002F123456789012\u002Fsecrets\u002Fsa-reporting-key\u002Fversions\u002F4\nOld key(s) left enabled. Deploy the new key, then rerun with --disable-old.\n","text",[15,521,518],{"__ignoreMap":90},[11,523,524],{},"Google doesn't recommend Secret Manager as the place a workload reads its service account key from: anything that can authenticate to Secret Manager already has an identity and should use it directly. Treat the secret as a hand-off point for the key's real destination, the out-of-cloud system that can't use an attached service account or federation. The person updating that system collects it once:",[85,526,528],{"className":87,"code":527,"language":89,"meta":90,"style":90},"gcloud secrets versions access latest --secret=sa-reporting-key --project=my-project > new-key.json\n",[15,529,530],{"__ignoreMap":90},[94,531,532,534,537,540,543,546,549,552,556],{"class":96,"line":97},[94,533,101],{"class":100},[94,535,536],{"class":104}," secrets",[94,538,539],{"class":104}," versions",[94,541,542],{"class":104}," access",[94,544,545],{"class":104}," latest",[94,547,548],{"class":117}," --secret=sa-reporting-key",[94,550,551],{"class":117}," --project=my-project",[94,553,555],{"class":554},"snl16"," >",[94,557,558],{"class":104}," new-key.json\n",[11,560,561],{},"Once the new key is in place there, disable the old ones:",[85,563,565],{"className":87,"code":564,"language":89,"meta":90,"style":90},"node rotate-sa-keys.js --project my-project \\\n  --service-account reporting@my-project.iam.gserviceaccount.com \\\n  --max-age-days 90 --disable-old\n",[15,566,567,579,587],{"__ignoreMap":90},[94,568,569,571,573,575,577],{"class":96,"line":97},[94,570,438],{"class":100},[94,572,441],{"class":104},[94,574,444],{"class":117},[94,576,447],{"class":104},[94,578,118],{"class":117},[94,580,581,583,585],{"class":96,"line":121},[94,582,454],{"class":117},[94,584,457],{"class":104},[94,586,118],{"class":117},[94,588,589,591,593],{"class":96,"line":136},[94,590,464],{"class":117},[94,592,467],{"class":117},[94,594,595],{"class":117}," --disable-old\n",[85,597,600],{"className":598,"code":599,"language":519,"meta":90},[517],"Found 4 user-managed key(s) on reporting@my-project.iam.gserviceaccount.com\nKey 3f9a1c2b... is 142 days old, exceeds 90-day threshold\nKey 77d0e4aa... is already disabled (SERVICE_ACCOUNT_KEY_DISABLE_REASON_USER_INITIATED)\nKey ef567890... is 13 days old, within threshold\nKey 0b1c2d3e... is 1 days old, within threshold\nDisabled old key 3f9a1c2b...\n",[15,601,599],{"__ignoreMap":90},[20,603,605],{"id":604},"script","Script",[85,607,611],{"className":608,"code":609,"language":610,"meta":90,"style":90},"language-javascript shiki shiki-themes github-dark","#!\u002Fusr\u002Fbin\u002Fenv node\n\u002F**\n * rotate-sa-keys.js\n *\n * Finds user-managed Google Cloud service account keys older than a threshold\n * and creates one replacement key. After the replacement is deployed, a second\n * run with --disable-old disables (not deletes) the old ones so anything still\n * depending on them fails loudly and can be rolled back.\n *\n * Reads:  service account key metadata via the IAM API (iam.googleapis.com).\n * Writes: the new key either as a new Secret Manager version (--secret) or to\n *         a local file with mode 0600 (--output); with --disable-old, disables\n *         old keys in place.\n *\n * Usage:\n *   node rotate-sa-keys.js --project \u003Cproject-id> \\\n *     --service-account \u003Csa>@\u003Cproject-id>.iam.gserviceaccount.com \\\n *     --max-age-days 90 (--secret \u003Csecret-id> | --output .\u002Fnew-key.json | --disable-old) [--dry-run]\n *\u002F\n\nconst fs = require('fs');\nconst { google } = require('googleapis');\n\nconst MAX_KEYS_PER_ACCOUNT = 10;\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nfunction parseArgs(argv) {\n    const args = {\n        project: null,\n        serviceAccount: null,\n        maxAgeDays: 90,\n        output: null,\n        secret: null,\n        disableOld: false,\n        dryRun: false,\n    };\n\n    for (let i = 0; i \u003C argv.length; i += 1) {\n        switch (argv[i]) {\n            case '--project':\n                args.project = argv[++i];\n                break;\n            case '--service-account':\n                args.serviceAccount = argv[++i];\n                break;\n            case '--max-age-days':\n                args.maxAgeDays = Number(argv[++i]);\n                break;\n            case '--output':\n                args.output = argv[++i];\n                break;\n            case '--secret':\n                args.secret = argv[++i];\n                break;\n            case '--disable-old':\n                args.disableOld = true;\n                break;\n            case '--dry-run':\n                args.dryRun = true;\n                break;\n            default:\n                throw new Error(`Unknown argument: ${argv[i]}`);\n        }\n    }\n\n    if (!args.project || !args.serviceAccount) {\n        throw new Error('--project and --service-account are required');\n    }\n    if (!Number.isFinite(args.maxAgeDays) || args.maxAgeDays \u003C 1) {\n        throw new Error('--max-age-days must be a positive number');\n    }\n    if (args.disableOld && (args.output || args.secret)) {\n        throw new Error('--disable-old creates no key; drop --secret\u002F--output');\n    }\n    if (!args.dryRun && !args.disableOld && !args.output === !args.secret) {\n        throw new Error('Specify exactly one of --secret or --output');\n    }\n\n    return args;\n}\n\nfunction ageInDays(validAfterTime) {\n    return Math.floor((Date.now() - new Date(validAfterTime).getTime()) \u002F DAY_MS);\n}\n\nasync function storeKey(args, auth, privateKeyData) {\n    if (args.secret) {\n        \u002F\u002F privateKeyData is already base64, which is what SecretPayload.data expects.\n        const secretManager = google.secretmanager({ version: 'v1', auth });\n        const { data } = await secretManager.projects.secrets.addVersion({\n            parent: `projects\u002F${args.project}\u002Fsecrets\u002F${args.secret}`,\n            requestBody: { payload: { data: privateKeyData } },\n        });\n        return `Secret Manager version ${data.name}`;\n    }\n\n    const json = Buffer.from(privateKeyData, 'base64').toString('utf8');\n    const fd = fs.openSync(args.output, 'w', 0o600);\n    try {\n        fs.fchmodSync(fd, 0o600); \u002F\u002F open's mode only applies when it creates the file\n        fs.writeSync(fd, json);\n    } finally {\n        fs.closeSync(fd);\n    }\n    return args.output;\n}\n\nasync function main() {\n    const args = parseArgs(process.argv.slice(2));\n    const auth = new google.auth.GoogleAuth({\n        scopes: ['https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fcloud-platform'],\n    });\n    const iam = google.iam({ version: 'v1', auth });\n    const accountName = `projects\u002F${args.project}\u002FserviceAccounts\u002F${args.serviceAccount}`;\n\n    const { data } = await iam.projects.serviceAccounts.keys.list({\n        name: accountName,\n        keyTypes: ['USER_MANAGED'],\n    });\n    const keys = data.keys || [];\n    console.log(`Found ${keys.length} user-managed key(s) on ${args.serviceAccount}`);\n\n    const expired = [];\n    let current = 0;\n    for (const key of keys) {\n        const keyId = key.name.split('\u002F').pop();\n        const days = ageInDays(key.validAfterTime);\n        const expiry = key.validBeforeTime && !key.validBeforeTime.startsWith('9999')\n            ? `, expires ${key.validBeforeTime}`\n            : '';\n\n        if (key.disabled) {\n            console.log(`Key ${keyId.slice(0, 8)}... is already disabled (${key.disableReason || 'no reason'})`);\n        } else if (days > args.maxAgeDays) {\n            console.log(`Key ${keyId.slice(0, 8)}... is ${days} days old${expiry}, exceeds ${args.maxAgeDays}-day threshold`);\n            expired.push(key);\n        } else {\n            console.log(`Key ${keyId.slice(0, 8)}... is ${days} days old${expiry}, within threshold`);\n            current += 1;\n        }\n    }\n\n    if (expired.length === 0) {\n        console.log('No keys required rotation.');\n        return;\n    }\n\n    if (args.disableOld) {\n        if (current === 0) {\n            throw new Error('No enabled key within the threshold; run without --disable-old to create one first');\n        }\n        if (args.dryRun) {\n            console.log(`Dry run: would disable ${expired.length} key(s).`);\n            return;\n        }\n        for (const key of expired) {\n            await iam.projects.serviceAccounts.keys.disable({ name: key.name });\n            console.log(`Disabled old key ${key.name.split('\u002F').pop().slice(0, 8)}...`);\n        }\n        return;\n    }\n\n    if (keys.length >= MAX_KEYS_PER_ACCOUNT) {\n        throw new Error(`Account already has ${keys.length} keys (limit ${MAX_KEYS_PER_ACCOUNT}); delete disabled keys first`);\n    }\n    if (args.dryRun) {\n        console.log('Dry run: would create 1 replacement key.');\n        return;\n    }\n\n    const created = await iam.projects.serviceAccounts.keys.create({\n        name: accountName,\n        requestBody: { privateKeyType: 'TYPE_GOOGLE_CREDENTIALS_FILE' },\n    });\n    const newKeyId = created.data.name.split('\u002F').pop();\n    const location = await storeKey(args, auth, created.data.privateKeyData);\n    console.log(`Created key ${newKeyId.slice(0, 8)}..., stored in ${location}`);\n    console.log('Old key(s) left enabled. Deploy the new key, then rerun with --disable-old.');\n}\n\nmain().catch((error) => {\n    console.error(error.message);\n    process.exitCode = 1;\n});\n","javascript",[15,612,613,619,624,629,634,639,644,649,654,658,663,668,673,679,684,690,696,702,708,714,719,744,770,775,791,821,826,844,858,870,880,891,901,911,922,932,938,943,985,994,1006,1023,1031,1041,1055,1062,1072,1091,1098,1108,1122,1129,1139,1153,1160,1170,1183,1190,1200,1212,1219,1227,1260,1266,1272,1277,1300,1317,1322,1351,1367,1372,1391,1407,1412,1446,1462,1467,1472,1481,1487,1492,1506,1550,1555,1560,1589,1597,1603,1629,1655,1686,1692,1698,1718,1723,1728,1762,1791,1799,1819,1830,1841,1852,1857,1865,1870,1875,1888,1913,1933,1945,1951,1971,2004,2009,2032,2038,2049,2054,2072,2106,2111,2123,2138,2156,2185,2200,2231,2251,2262,2267,2276,2327,2348,2402,2414,2423,2463,2475,2480,2485,2490,2507,2522,2529,2534,2539,2547,2561,2578,2583,2591,2615,2623,2628,2645,2659,2709,2714,2721,2726,2731,2748,2779,2784,2791,2805,2812,2817,2822,2841,2846,2858,2863,2888,2905,2944,2958,2963,2968,2993,3003,3015],{"__ignoreMap":90},[94,614,615],{"class":96,"line":97},[94,616,618],{"class":617},"sAwPA","#!\u002Fusr\u002Fbin\u002Fenv node\n",[94,620,621],{"class":96,"line":121},[94,622,623],{"class":617},"\u002F**\n",[94,625,626],{"class":96,"line":136},[94,627,628],{"class":617}," * rotate-sa-keys.js\n",[94,630,631],{"class":96,"line":143},[94,632,633],{"class":617}," *\n",[94,635,636],{"class":96,"line":163},[94,637,638],{"class":617}," * Finds user-managed Google Cloud service account keys older than a threshold\n",[94,640,641],{"class":96,"line":175},[94,642,643],{"class":617}," * and creates one replacement key. After the replacement is deployed, a second\n",[94,645,646],{"class":96,"line":186},[94,647,648],{"class":617}," * run with --disable-old disables (not deletes) the old ones so anything still\n",[94,650,651],{"class":96,"line":197},[94,652,653],{"class":617}," * depending on them fails loudly and can be rolled back.\n",[94,655,656],{"class":96,"line":206},[94,657,633],{"class":617},[94,659,660],{"class":96,"line":211},[94,661,662],{"class":617}," * Reads:  service account key metadata via the IAM API (iam.googleapis.com).\n",[94,664,665],{"class":96,"line":229},[94,666,667],{"class":617}," * Writes: the new key either as a new Secret Manager version (--secret) or to\n",[94,669,670],{"class":96,"line":237},[94,671,672],{"class":617}," *         a local file with mode 0600 (--output); with --disable-old, disables\n",[94,674,676],{"class":96,"line":675},13,[94,677,678],{"class":617}," *         old keys in place.\n",[94,680,682],{"class":96,"line":681},14,[94,683,633],{"class":617},[94,685,687],{"class":96,"line":686},15,[94,688,689],{"class":617}," * Usage:\n",[94,691,693],{"class":96,"line":692},16,[94,694,695],{"class":617}," *   node rotate-sa-keys.js --project \u003Cproject-id> \\\n",[94,697,699],{"class":96,"line":698},17,[94,700,701],{"class":617}," *     --service-account \u003Csa>@\u003Cproject-id>.iam.gserviceaccount.com \\\n",[94,703,705],{"class":96,"line":704},18,[94,706,707],{"class":617}," *     --max-age-days 90 (--secret \u003Csecret-id> | --output .\u002Fnew-key.json | --disable-old) [--dry-run]\n",[94,709,711],{"class":96,"line":710},19,[94,712,713],{"class":617}," *\u002F\n",[94,715,717],{"class":96,"line":716},20,[94,718,140],{"emptyLinePlaceholder":139},[94,720,722,725,728,731,734,738,741],{"class":96,"line":721},21,[94,723,724],{"class":554},"const",[94,726,727],{"class":117}," fs",[94,729,730],{"class":554}," =",[94,732,733],{"class":100}," require",[94,735,737],{"class":736},"s95oV","(",[94,739,740],{"class":104},"'fs'",[94,742,743],{"class":736},");\n",[94,745,747,749,752,755,758,761,763,765,768],{"class":96,"line":746},22,[94,748,724],{"class":554},[94,750,751],{"class":736}," { ",[94,753,754],{"class":117},"google",[94,756,757],{"class":736}," } ",[94,759,760],{"class":554},"=",[94,762,733],{"class":100},[94,764,737],{"class":736},[94,766,767],{"class":104},"'googleapis'",[94,769,743],{"class":736},[94,771,773],{"class":96,"line":772},23,[94,774,140],{"emptyLinePlaceholder":139},[94,776,778,780,783,785,788],{"class":96,"line":777},24,[94,779,724],{"class":554},[94,781,782],{"class":117}," MAX_KEYS_PER_ACCOUNT",[94,784,730],{"class":554},[94,786,787],{"class":117}," 10",[94,789,790],{"class":736},";\n",[94,792,794,796,799,801,804,807,810,812,814,816,819],{"class":96,"line":793},25,[94,795,724],{"class":554},[94,797,798],{"class":117}," DAY_MS",[94,800,730],{"class":554},[94,802,803],{"class":117}," 24",[94,805,806],{"class":554}," *",[94,808,809],{"class":117}," 60",[94,811,806],{"class":554},[94,813,809],{"class":117},[94,815,806],{"class":554},[94,817,818],{"class":117}," 1000",[94,820,790],{"class":736},[94,822,824],{"class":96,"line":823},26,[94,825,140],{"emptyLinePlaceholder":139},[94,827,829,832,835,837,841],{"class":96,"line":828},27,[94,830,831],{"class":554},"function",[94,833,834],{"class":100}," parseArgs",[94,836,737],{"class":736},[94,838,840],{"class":839},"s9osk","argv",[94,842,843],{"class":736},") {\n",[94,845,847,850,853,855],{"class":96,"line":846},28,[94,848,849],{"class":554},"    const",[94,851,852],{"class":117}," args",[94,854,730],{"class":554},[94,856,857],{"class":736}," {\n",[94,859,861,864,867],{"class":96,"line":860},29,[94,862,863],{"class":736},"        project: ",[94,865,866],{"class":117},"null",[94,868,869],{"class":736},",\n",[94,871,873,876,878],{"class":96,"line":872},30,[94,874,875],{"class":736},"        serviceAccount: ",[94,877,866],{"class":117},[94,879,869],{"class":736},[94,881,883,886,889],{"class":96,"line":882},31,[94,884,885],{"class":736},"        maxAgeDays: ",[94,887,888],{"class":117},"90",[94,890,869],{"class":736},[94,892,894,897,899],{"class":96,"line":893},32,[94,895,896],{"class":736},"        output: ",[94,898,866],{"class":117},[94,900,869],{"class":736},[94,902,904,907,909],{"class":96,"line":903},33,[94,905,906],{"class":736},"        secret: ",[94,908,866],{"class":117},[94,910,869],{"class":736},[94,912,914,917,920],{"class":96,"line":913},34,[94,915,916],{"class":736},"        disableOld: ",[94,918,919],{"class":117},"false",[94,921,869],{"class":736},[94,923,925,928,930],{"class":96,"line":924},35,[94,926,927],{"class":736},"        dryRun: ",[94,929,919],{"class":117},[94,931,869],{"class":736},[94,933,935],{"class":96,"line":934},36,[94,936,937],{"class":736},"    };\n",[94,939,941],{"class":96,"line":940},37,[94,942,140],{"emptyLinePlaceholder":139},[94,944,946,949,952,955,958,960,963,966,969,972,975,977,980,983],{"class":96,"line":945},38,[94,947,948],{"class":554},"    for",[94,950,951],{"class":736}," (",[94,953,954],{"class":554},"let",[94,956,957],{"class":736}," i ",[94,959,760],{"class":554},[94,961,962],{"class":117}," 0",[94,964,965],{"class":736},"; i ",[94,967,968],{"class":554},"\u003C",[94,970,971],{"class":736}," argv.",[94,973,974],{"class":117},"length",[94,976,965],{"class":736},[94,978,979],{"class":554},"+=",[94,981,982],{"class":117}," 1",[94,984,843],{"class":736},[94,986,988,991],{"class":96,"line":987},39,[94,989,990],{"class":554},"        switch",[94,992,993],{"class":736}," (argv[i]) {\n",[94,995,997,1000,1003],{"class":96,"line":996},40,[94,998,999],{"class":554},"            case",[94,1001,1002],{"class":104}," '--project'",[94,1004,1005],{"class":736},":\n",[94,1007,1009,1012,1014,1017,1020],{"class":96,"line":1008},41,[94,1010,1011],{"class":736},"                args.project ",[94,1013,760],{"class":554},[94,1015,1016],{"class":736}," argv[",[94,1018,1019],{"class":554},"++",[94,1021,1022],{"class":736},"i];\n",[94,1024,1026,1029],{"class":96,"line":1025},42,[94,1027,1028],{"class":554},"                break",[94,1030,790],{"class":736},[94,1032,1034,1036,1039],{"class":96,"line":1033},43,[94,1035,999],{"class":554},[94,1037,1038],{"class":104}," '--service-account'",[94,1040,1005],{"class":736},[94,1042,1044,1047,1049,1051,1053],{"class":96,"line":1043},44,[94,1045,1046],{"class":736},"                args.serviceAccount ",[94,1048,760],{"class":554},[94,1050,1016],{"class":736},[94,1052,1019],{"class":554},[94,1054,1022],{"class":736},[94,1056,1058,1060],{"class":96,"line":1057},45,[94,1059,1028],{"class":554},[94,1061,790],{"class":736},[94,1063,1065,1067,1070],{"class":96,"line":1064},46,[94,1066,999],{"class":554},[94,1068,1069],{"class":104}," '--max-age-days'",[94,1071,1005],{"class":736},[94,1073,1075,1078,1080,1083,1086,1088],{"class":96,"line":1074},47,[94,1076,1077],{"class":736},"                args.maxAgeDays ",[94,1079,760],{"class":554},[94,1081,1082],{"class":100}," Number",[94,1084,1085],{"class":736},"(argv[",[94,1087,1019],{"class":554},[94,1089,1090],{"class":736},"i]);\n",[94,1092,1094,1096],{"class":96,"line":1093},48,[94,1095,1028],{"class":554},[94,1097,790],{"class":736},[94,1099,1101,1103,1106],{"class":96,"line":1100},49,[94,1102,999],{"class":554},[94,1104,1105],{"class":104}," '--output'",[94,1107,1005],{"class":736},[94,1109,1111,1114,1116,1118,1120],{"class":96,"line":1110},50,[94,1112,1113],{"class":736},"                args.output ",[94,1115,760],{"class":554},[94,1117,1016],{"class":736},[94,1119,1019],{"class":554},[94,1121,1022],{"class":736},[94,1123,1125,1127],{"class":96,"line":1124},51,[94,1126,1028],{"class":554},[94,1128,790],{"class":736},[94,1130,1132,1134,1137],{"class":96,"line":1131},52,[94,1133,999],{"class":554},[94,1135,1136],{"class":104}," '--secret'",[94,1138,1005],{"class":736},[94,1140,1142,1145,1147,1149,1151],{"class":96,"line":1141},53,[94,1143,1144],{"class":736},"                args.secret ",[94,1146,760],{"class":554},[94,1148,1016],{"class":736},[94,1150,1019],{"class":554},[94,1152,1022],{"class":736},[94,1154,1156,1158],{"class":96,"line":1155},54,[94,1157,1028],{"class":554},[94,1159,790],{"class":736},[94,1161,1163,1165,1168],{"class":96,"line":1162},55,[94,1164,999],{"class":554},[94,1166,1167],{"class":104}," '--disable-old'",[94,1169,1005],{"class":736},[94,1171,1173,1176,1178,1181],{"class":96,"line":1172},56,[94,1174,1175],{"class":736},"                args.disableOld ",[94,1177,760],{"class":554},[94,1179,1180],{"class":117}," true",[94,1182,790],{"class":736},[94,1184,1186,1188],{"class":96,"line":1185},57,[94,1187,1028],{"class":554},[94,1189,790],{"class":736},[94,1191,1193,1195,1198],{"class":96,"line":1192},58,[94,1194,999],{"class":554},[94,1196,1197],{"class":104}," '--dry-run'",[94,1199,1005],{"class":736},[94,1201,1203,1206,1208,1210],{"class":96,"line":1202},59,[94,1204,1205],{"class":736},"                args.dryRun ",[94,1207,760],{"class":554},[94,1209,1180],{"class":117},[94,1211,790],{"class":736},[94,1213,1215,1217],{"class":96,"line":1214},60,[94,1216,1028],{"class":554},[94,1218,790],{"class":736},[94,1220,1222,1225],{"class":96,"line":1221},61,[94,1223,1224],{"class":554},"            default",[94,1226,1005],{"class":736},[94,1228,1230,1233,1236,1239,1241,1244,1246,1249,1252,1255,1258],{"class":96,"line":1229},62,[94,1231,1232],{"class":554},"                throw",[94,1234,1235],{"class":554}," new",[94,1237,1238],{"class":100}," Error",[94,1240,737],{"class":736},[94,1242,1243],{"class":104},"`Unknown argument: ${",[94,1245,840],{"class":736},[94,1247,1248],{"class":104},"[",[94,1250,1251],{"class":736},"i",[94,1253,1254],{"class":104},"]",[94,1256,1257],{"class":104},"}`",[94,1259,743],{"class":736},[94,1261,1263],{"class":96,"line":1262},63,[94,1264,1265],{"class":736},"        }\n",[94,1267,1269],{"class":96,"line":1268},64,[94,1270,1271],{"class":736},"    }\n",[94,1273,1275],{"class":96,"line":1274},65,[94,1276,140],{"emptyLinePlaceholder":139},[94,1278,1280,1283,1285,1288,1291,1294,1297],{"class":96,"line":1279},66,[94,1281,1282],{"class":554},"    if",[94,1284,951],{"class":736},[94,1286,1287],{"class":554},"!",[94,1289,1290],{"class":736},"args.project ",[94,1292,1293],{"class":554},"||",[94,1295,1296],{"class":554}," !",[94,1298,1299],{"class":736},"args.serviceAccount) {\n",[94,1301,1303,1306,1308,1310,1312,1315],{"class":96,"line":1302},67,[94,1304,1305],{"class":554},"        throw",[94,1307,1235],{"class":554},[94,1309,1238],{"class":100},[94,1311,737],{"class":736},[94,1313,1314],{"class":104},"'--project and --service-account are required'",[94,1316,743],{"class":736},[94,1318,1320],{"class":96,"line":1319},68,[94,1321,1271],{"class":736},[94,1323,1325,1327,1329,1331,1334,1337,1340,1342,1345,1347,1349],{"class":96,"line":1324},69,[94,1326,1282],{"class":554},[94,1328,951],{"class":736},[94,1330,1287],{"class":554},[94,1332,1333],{"class":736},"Number.",[94,1335,1336],{"class":100},"isFinite",[94,1338,1339],{"class":736},"(args.maxAgeDays) ",[94,1341,1293],{"class":554},[94,1343,1344],{"class":736}," args.maxAgeDays ",[94,1346,968],{"class":554},[94,1348,982],{"class":117},[94,1350,843],{"class":736},[94,1352,1354,1356,1358,1360,1362,1365],{"class":96,"line":1353},70,[94,1355,1305],{"class":554},[94,1357,1235],{"class":554},[94,1359,1238],{"class":100},[94,1361,737],{"class":736},[94,1363,1364],{"class":104},"'--max-age-days must be a positive number'",[94,1366,743],{"class":736},[94,1368,1370],{"class":96,"line":1369},71,[94,1371,1271],{"class":736},[94,1373,1375,1377,1380,1383,1386,1388],{"class":96,"line":1374},72,[94,1376,1282],{"class":554},[94,1378,1379],{"class":736}," (args.disableOld ",[94,1381,1382],{"class":554},"&&",[94,1384,1385],{"class":736}," (args.output ",[94,1387,1293],{"class":554},[94,1389,1390],{"class":736}," args.secret)) {\n",[94,1392,1394,1396,1398,1400,1402,1405],{"class":96,"line":1393},73,[94,1395,1305],{"class":554},[94,1397,1235],{"class":554},[94,1399,1238],{"class":100},[94,1401,737],{"class":736},[94,1403,1404],{"class":104},"'--disable-old creates no key; drop --secret\u002F--output'",[94,1406,743],{"class":736},[94,1408,1410],{"class":96,"line":1409},74,[94,1411,1271],{"class":736},[94,1413,1415,1417,1419,1421,1424,1426,1428,1431,1433,1435,1438,1441,1443],{"class":96,"line":1414},75,[94,1416,1282],{"class":554},[94,1418,951],{"class":736},[94,1420,1287],{"class":554},[94,1422,1423],{"class":736},"args.dryRun ",[94,1425,1382],{"class":554},[94,1427,1296],{"class":554},[94,1429,1430],{"class":736},"args.disableOld ",[94,1432,1382],{"class":554},[94,1434,1296],{"class":554},[94,1436,1437],{"class":736},"args.output ",[94,1439,1440],{"class":554},"===",[94,1442,1296],{"class":554},[94,1444,1445],{"class":736},"args.secret) {\n",[94,1447,1449,1451,1453,1455,1457,1460],{"class":96,"line":1448},76,[94,1450,1305],{"class":554},[94,1452,1235],{"class":554},[94,1454,1238],{"class":100},[94,1456,737],{"class":736},[94,1458,1459],{"class":104},"'Specify exactly one of --secret or --output'",[94,1461,743],{"class":736},[94,1463,1465],{"class":96,"line":1464},77,[94,1466,1271],{"class":736},[94,1468,1470],{"class":96,"line":1469},78,[94,1471,140],{"emptyLinePlaceholder":139},[94,1473,1475,1478],{"class":96,"line":1474},79,[94,1476,1477],{"class":554},"    return",[94,1479,1480],{"class":736}," args;\n",[94,1482,1484],{"class":96,"line":1483},80,[94,1485,1486],{"class":736},"}\n",[94,1488,1490],{"class":96,"line":1489},81,[94,1491,140],{"emptyLinePlaceholder":139},[94,1493,1495,1497,1500,1502,1504],{"class":96,"line":1494},82,[94,1496,831],{"class":554},[94,1498,1499],{"class":100}," ageInDays",[94,1501,737],{"class":736},[94,1503,369],{"class":839},[94,1505,843],{"class":736},[94,1507,1509,1511,1514,1517,1520,1523,1526,1529,1531,1534,1537,1540,1543,1546,1548],{"class":96,"line":1508},83,[94,1510,1477],{"class":554},[94,1512,1513],{"class":736}," Math.",[94,1515,1516],{"class":100},"floor",[94,1518,1519],{"class":736},"((Date.",[94,1521,1522],{"class":100},"now",[94,1524,1525],{"class":736},"() ",[94,1527,1528],{"class":554},"-",[94,1530,1235],{"class":554},[94,1532,1533],{"class":100}," Date",[94,1535,1536],{"class":736},"(validAfterTime).",[94,1538,1539],{"class":100},"getTime",[94,1541,1542],{"class":736},"()) ",[94,1544,1545],{"class":554},"\u002F",[94,1547,798],{"class":117},[94,1549,743],{"class":736},[94,1551,1553],{"class":96,"line":1552},84,[94,1554,1486],{"class":736},[94,1556,1558],{"class":96,"line":1557},85,[94,1559,140],{"emptyLinePlaceholder":139},[94,1561,1563,1566,1569,1572,1574,1577,1579,1582,1584,1587],{"class":96,"line":1562},86,[94,1564,1565],{"class":554},"async",[94,1567,1568],{"class":554}," function",[94,1570,1571],{"class":100}," storeKey",[94,1573,737],{"class":736},[94,1575,1576],{"class":839},"args",[94,1578,58],{"class":736},[94,1580,1581],{"class":839},"auth",[94,1583,58],{"class":736},[94,1585,1586],{"class":839},"privateKeyData",[94,1588,843],{"class":736},[94,1590,1592,1594],{"class":96,"line":1591},87,[94,1593,1282],{"class":554},[94,1595,1596],{"class":736}," (args.secret) {\n",[94,1598,1600],{"class":96,"line":1599},88,[94,1601,1602],{"class":617},"        \u002F\u002F privateKeyData is already base64, which is what SecretPayload.data expects.\n",[94,1604,1606,1609,1612,1614,1617,1620,1623,1626],{"class":96,"line":1605},89,[94,1607,1608],{"class":554},"        const",[94,1610,1611],{"class":117}," secretManager",[94,1613,730],{"class":554},[94,1615,1616],{"class":736}," google.",[94,1618,1619],{"class":100},"secretmanager",[94,1621,1622],{"class":736},"({ version: ",[94,1624,1625],{"class":104},"'v1'",[94,1627,1628],{"class":736},", auth });\n",[94,1630,1632,1634,1636,1639,1641,1643,1646,1649,1652],{"class":96,"line":1631},90,[94,1633,1608],{"class":554},[94,1635,751],{"class":736},[94,1637,1638],{"class":117},"data",[94,1640,757],{"class":736},[94,1642,760],{"class":554},[94,1644,1645],{"class":554}," await",[94,1647,1648],{"class":736}," secretManager.projects.secrets.",[94,1650,1651],{"class":100},"addVersion",[94,1653,1654],{"class":736},"({\n",[94,1656,1658,1661,1664,1666,1669,1672,1675,1677,1679,1682,1684],{"class":96,"line":1657},91,[94,1659,1660],{"class":736},"            parent: ",[94,1662,1663],{"class":104},"`projects\u002F${",[94,1665,1576],{"class":736},[94,1667,1668],{"class":104},".",[94,1670,1671],{"class":736},"project",[94,1673,1674],{"class":104},"}\u002Fsecrets\u002F${",[94,1676,1576],{"class":736},[94,1678,1668],{"class":104},[94,1680,1681],{"class":736},"secret",[94,1683,1257],{"class":104},[94,1685,869],{"class":736},[94,1687,1689],{"class":96,"line":1688},92,[94,1690,1691],{"class":736},"            requestBody: { payload: { data: privateKeyData } },\n",[94,1693,1695],{"class":96,"line":1694},93,[94,1696,1697],{"class":736},"        });\n",[94,1699,1701,1704,1707,1709,1711,1714,1716],{"class":96,"line":1700},94,[94,1702,1703],{"class":554},"        return",[94,1705,1706],{"class":104}," `Secret Manager version ${",[94,1708,1638],{"class":736},[94,1710,1668],{"class":104},[94,1712,1713],{"class":736},"name",[94,1715,1257],{"class":104},[94,1717,790],{"class":736},[94,1719,1721],{"class":96,"line":1720},95,[94,1722,1271],{"class":736},[94,1724,1726],{"class":96,"line":1725},96,[94,1727,140],{"emptyLinePlaceholder":139},[94,1729,1731,1733,1736,1738,1741,1744,1747,1750,1752,1755,1757,1760],{"class":96,"line":1730},97,[94,1732,849],{"class":554},[94,1734,1735],{"class":117}," json",[94,1737,730],{"class":554},[94,1739,1740],{"class":736}," Buffer.",[94,1742,1743],{"class":100},"from",[94,1745,1746],{"class":736},"(privateKeyData, ",[94,1748,1749],{"class":104},"'base64'",[94,1751,260],{"class":736},[94,1753,1754],{"class":100},"toString",[94,1756,737],{"class":736},[94,1758,1759],{"class":104},"'utf8'",[94,1761,743],{"class":736},[94,1763,1765,1767,1770,1772,1775,1778,1781,1784,1786,1789],{"class":96,"line":1764},98,[94,1766,849],{"class":554},[94,1768,1769],{"class":117}," fd",[94,1771,730],{"class":554},[94,1773,1774],{"class":736}," fs.",[94,1776,1777],{"class":100},"openSync",[94,1779,1780],{"class":736},"(args.output, ",[94,1782,1783],{"class":104},"'w'",[94,1785,58],{"class":736},[94,1787,1788],{"class":117},"0o600",[94,1790,743],{"class":736},[94,1792,1794,1797],{"class":96,"line":1793},99,[94,1795,1796],{"class":554},"    try",[94,1798,857],{"class":736},[94,1800,1802,1805,1808,1811,1813,1816],{"class":96,"line":1801},100,[94,1803,1804],{"class":736},"        fs.",[94,1806,1807],{"class":100},"fchmodSync",[94,1809,1810],{"class":736},"(fd, ",[94,1812,1788],{"class":117},[94,1814,1815],{"class":736},"); ",[94,1817,1818],{"class":617},"\u002F\u002F open's mode only applies when it creates the file\n",[94,1820,1822,1824,1827],{"class":96,"line":1821},101,[94,1823,1804],{"class":736},[94,1825,1826],{"class":100},"writeSync",[94,1828,1829],{"class":736},"(fd, json);\n",[94,1831,1833,1836,1839],{"class":96,"line":1832},102,[94,1834,1835],{"class":736},"    } ",[94,1837,1838],{"class":554},"finally",[94,1840,857],{"class":736},[94,1842,1844,1846,1849],{"class":96,"line":1843},103,[94,1845,1804],{"class":736},[94,1847,1848],{"class":100},"closeSync",[94,1850,1851],{"class":736},"(fd);\n",[94,1853,1855],{"class":96,"line":1854},104,[94,1856,1271],{"class":736},[94,1858,1860,1862],{"class":96,"line":1859},105,[94,1861,1477],{"class":554},[94,1863,1864],{"class":736}," args.output;\n",[94,1866,1868],{"class":96,"line":1867},106,[94,1869,1486],{"class":736},[94,1871,1873],{"class":96,"line":1872},107,[94,1874,140],{"emptyLinePlaceholder":139},[94,1876,1878,1880,1882,1885],{"class":96,"line":1877},108,[94,1879,1565],{"class":554},[94,1881,1568],{"class":554},[94,1883,1884],{"class":100}," main",[94,1886,1887],{"class":736},"() {\n",[94,1889,1891,1893,1895,1897,1899,1902,1905,1907,1910],{"class":96,"line":1890},109,[94,1892,849],{"class":554},[94,1894,852],{"class":117},[94,1896,730],{"class":554},[94,1898,834],{"class":100},[94,1900,1901],{"class":736},"(process.argv.",[94,1903,1904],{"class":100},"slice",[94,1906,737],{"class":736},[94,1908,1909],{"class":117},"2",[94,1911,1912],{"class":736},"));\n",[94,1914,1916,1918,1921,1923,1925,1928,1931],{"class":96,"line":1915},110,[94,1917,849],{"class":554},[94,1919,1920],{"class":117}," auth",[94,1922,730],{"class":554},[94,1924,1235],{"class":554},[94,1926,1927],{"class":736}," google.auth.",[94,1929,1930],{"class":100},"GoogleAuth",[94,1932,1654],{"class":736},[94,1934,1936,1939,1942],{"class":96,"line":1935},111,[94,1937,1938],{"class":736},"        scopes: [",[94,1940,1941],{"class":104},"'https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fcloud-platform'",[94,1943,1944],{"class":736},"],\n",[94,1946,1948],{"class":96,"line":1947},112,[94,1949,1950],{"class":736},"    });\n",[94,1952,1954,1956,1958,1960,1962,1965,1967,1969],{"class":96,"line":1953},113,[94,1955,849],{"class":554},[94,1957,105],{"class":117},[94,1959,730],{"class":554},[94,1961,1616],{"class":736},[94,1963,1964],{"class":100},"iam",[94,1966,1622],{"class":736},[94,1968,1625],{"class":104},[94,1970,1628],{"class":736},[94,1972,1974,1976,1979,1981,1984,1986,1988,1990,1993,1995,1997,2000,2002],{"class":96,"line":1973},114,[94,1975,849],{"class":554},[94,1977,1978],{"class":117}," accountName",[94,1980,730],{"class":554},[94,1982,1983],{"class":104}," `projects\u002F${",[94,1985,1576],{"class":736},[94,1987,1668],{"class":104},[94,1989,1671],{"class":736},[94,1991,1992],{"class":104},"}\u002FserviceAccounts\u002F${",[94,1994,1576],{"class":736},[94,1996,1668],{"class":104},[94,1998,1999],{"class":736},"serviceAccount",[94,2001,1257],{"class":104},[94,2003,790],{"class":736},[94,2005,2007],{"class":96,"line":2006},115,[94,2008,140],{"emptyLinePlaceholder":139},[94,2010,2012,2014,2016,2018,2020,2022,2024,2027,2030],{"class":96,"line":2011},116,[94,2013,849],{"class":554},[94,2015,751],{"class":736},[94,2017,1638],{"class":117},[94,2019,757],{"class":736},[94,2021,760],{"class":554},[94,2023,1645],{"class":554},[94,2025,2026],{"class":736}," iam.projects.serviceAccounts.keys.",[94,2028,2029],{"class":100},"list",[94,2031,1654],{"class":736},[94,2033,2035],{"class":96,"line":2034},117,[94,2036,2037],{"class":736},"        name: accountName,\n",[94,2039,2041,2044,2047],{"class":96,"line":2040},118,[94,2042,2043],{"class":736},"        keyTypes: [",[94,2045,2046],{"class":104},"'USER_MANAGED'",[94,2048,1944],{"class":736},[94,2050,2052],{"class":96,"line":2051},119,[94,2053,1950],{"class":736},[94,2055,2057,2059,2062,2064,2067,2069],{"class":96,"line":2056},120,[94,2058,849],{"class":554},[94,2060,2061],{"class":117}," keys",[94,2063,730],{"class":554},[94,2065,2066],{"class":736}," data.keys ",[94,2068,1293],{"class":554},[94,2070,2071],{"class":736}," [];\n",[94,2073,2075,2078,2081,2083,2086,2089,2091,2093,2096,2098,2100,2102,2104],{"class":96,"line":2074},121,[94,2076,2077],{"class":736},"    console.",[94,2079,2080],{"class":100},"log",[94,2082,737],{"class":736},[94,2084,2085],{"class":104},"`Found ${",[94,2087,2088],{"class":736},"keys",[94,2090,1668],{"class":104},[94,2092,974],{"class":117},[94,2094,2095],{"class":104},"} user-managed key(s) on ${",[94,2097,1576],{"class":736},[94,2099,1668],{"class":104},[94,2101,1999],{"class":736},[94,2103,1257],{"class":104},[94,2105,743],{"class":736},[94,2107,2109],{"class":96,"line":2108},122,[94,2110,140],{"emptyLinePlaceholder":139},[94,2112,2114,2116,2119,2121],{"class":96,"line":2113},123,[94,2115,849],{"class":554},[94,2117,2118],{"class":117}," expired",[94,2120,730],{"class":554},[94,2122,2071],{"class":736},[94,2124,2126,2129,2132,2134,2136],{"class":96,"line":2125},124,[94,2127,2128],{"class":554},"    let",[94,2130,2131],{"class":736}," current ",[94,2133,760],{"class":554},[94,2135,962],{"class":117},[94,2137,790],{"class":736},[94,2139,2141,2143,2145,2147,2150,2153],{"class":96,"line":2140},125,[94,2142,948],{"class":554},[94,2144,951],{"class":736},[94,2146,724],{"class":554},[94,2148,2149],{"class":117}," key",[94,2151,2152],{"class":554}," of",[94,2154,2155],{"class":736}," keys) {\n",[94,2157,2159,2161,2164,2166,2169,2172,2174,2177,2179,2182],{"class":96,"line":2158},126,[94,2160,1608],{"class":554},[94,2162,2163],{"class":117}," keyId",[94,2165,730],{"class":554},[94,2167,2168],{"class":736}," key.name.",[94,2170,2171],{"class":100},"split",[94,2173,737],{"class":736},[94,2175,2176],{"class":104},"'\u002F'",[94,2178,260],{"class":736},[94,2180,2181],{"class":100},"pop",[94,2183,2184],{"class":736},"();\n",[94,2186,2188,2190,2193,2195,2197],{"class":96,"line":2187},127,[94,2189,1608],{"class":554},[94,2191,2192],{"class":117}," days",[94,2194,730],{"class":554},[94,2196,1499],{"class":100},[94,2198,2199],{"class":736},"(key.validAfterTime);\n",[94,2201,2203,2205,2208,2210,2213,2215,2217,2220,2223,2225,2228],{"class":96,"line":2202},128,[94,2204,1608],{"class":554},[94,2206,2207],{"class":117}," expiry",[94,2209,730],{"class":554},[94,2211,2212],{"class":736}," key.validBeforeTime ",[94,2214,1382],{"class":554},[94,2216,1296],{"class":554},[94,2218,2219],{"class":736},"key.validBeforeTime.",[94,2221,2222],{"class":100},"startsWith",[94,2224,737],{"class":736},[94,2226,2227],{"class":104},"'9999'",[94,2229,2230],{"class":736},")\n",[94,2232,2234,2237,2240,2243,2245,2248],{"class":96,"line":2233},129,[94,2235,2236],{"class":554},"            ?",[94,2238,2239],{"class":104}," `, expires ${",[94,2241,2242],{"class":736},"key",[94,2244,1668],{"class":104},[94,2246,2247],{"class":736},"validBeforeTime",[94,2249,2250],{"class":104},"}`\n",[94,2252,2254,2257,2260],{"class":96,"line":2253},130,[94,2255,2256],{"class":554},"            :",[94,2258,2259],{"class":104}," ''",[94,2261,790],{"class":736},[94,2263,2265],{"class":96,"line":2264},131,[94,2266,140],{"emptyLinePlaceholder":139},[94,2268,2270,2273],{"class":96,"line":2269},132,[94,2271,2272],{"class":554},"        if",[94,2274,2275],{"class":736}," (key.disabled) {\n",[94,2277,2279,2282,2284,2286,2289,2292,2294,2296,2298,2301,2303,2306,2309,2312,2314,2316,2319,2322,2325],{"class":96,"line":2278},133,[94,2280,2281],{"class":736},"            console.",[94,2283,2080],{"class":100},[94,2285,737],{"class":736},[94,2287,2288],{"class":104},"`Key ${",[94,2290,2291],{"class":736},"keyId",[94,2293,1668],{"class":104},[94,2295,1904],{"class":100},[94,2297,737],{"class":104},[94,2299,2300],{"class":117},"0",[94,2302,58],{"class":104},[94,2304,2305],{"class":117},"8",[94,2307,2308],{"class":104},")",[94,2310,2311],{"class":104},"}... is already disabled (${",[94,2313,2242],{"class":736},[94,2315,1668],{"class":104},[94,2317,2318],{"class":736},"disableReason",[94,2320,2321],{"class":554}," ||",[94,2323,2324],{"class":104}," 'no reason'})`",[94,2326,743],{"class":736},[94,2328,2330,2333,2336,2339,2342,2345],{"class":96,"line":2329},134,[94,2331,2332],{"class":736},"        } ",[94,2334,2335],{"class":554},"else",[94,2337,2338],{"class":554}," if",[94,2340,2341],{"class":736}," (days ",[94,2343,2344],{"class":554},">",[94,2346,2347],{"class":736}," args.maxAgeDays) {\n",[94,2349,2351,2353,2355,2357,2359,2361,2363,2365,2367,2369,2371,2373,2375,2378,2381,2384,2387,2390,2392,2394,2397,2400],{"class":96,"line":2350},135,[94,2352,2281],{"class":736},[94,2354,2080],{"class":100},[94,2356,737],{"class":736},[94,2358,2288],{"class":104},[94,2360,2291],{"class":736},[94,2362,1668],{"class":104},[94,2364,1904],{"class":100},[94,2366,737],{"class":104},[94,2368,2300],{"class":117},[94,2370,58],{"class":104},[94,2372,2305],{"class":117},[94,2374,2308],{"class":104},[94,2376,2377],{"class":104},"}... is ${",[94,2379,2380],{"class":736},"days",[94,2382,2383],{"class":104},"} days old${",[94,2385,2386],{"class":736},"expiry",[94,2388,2389],{"class":104},"}, exceeds ${",[94,2391,1576],{"class":736},[94,2393,1668],{"class":104},[94,2395,2396],{"class":736},"maxAgeDays",[94,2398,2399],{"class":104},"}-day threshold`",[94,2401,743],{"class":736},[94,2403,2405,2408,2411],{"class":96,"line":2404},136,[94,2406,2407],{"class":736},"            expired.",[94,2409,2410],{"class":100},"push",[94,2412,2413],{"class":736},"(key);\n",[94,2415,2417,2419,2421],{"class":96,"line":2416},137,[94,2418,2332],{"class":736},[94,2420,2335],{"class":554},[94,2422,857],{"class":736},[94,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444,2446,2448,2450,2452,2454,2456,2458,2461],{"class":96,"line":2425},138,[94,2427,2281],{"class":736},[94,2429,2080],{"class":100},[94,2431,737],{"class":736},[94,2433,2288],{"class":104},[94,2435,2291],{"class":736},[94,2437,1668],{"class":104},[94,2439,1904],{"class":100},[94,2441,737],{"class":104},[94,2443,2300],{"class":117},[94,2445,58],{"class":104},[94,2447,2305],{"class":117},[94,2449,2308],{"class":104},[94,2451,2377],{"class":104},[94,2453,2380],{"class":736},[94,2455,2383],{"class":104},[94,2457,2386],{"class":736},[94,2459,2460],{"class":104},"}, within threshold`",[94,2462,743],{"class":736},[94,2464,2466,2469,2471,2473],{"class":96,"line":2465},139,[94,2467,2468],{"class":736},"            current ",[94,2470,979],{"class":554},[94,2472,982],{"class":117},[94,2474,790],{"class":736},[94,2476,2478],{"class":96,"line":2477},140,[94,2479,1265],{"class":736},[94,2481,2483],{"class":96,"line":2482},141,[94,2484,1271],{"class":736},[94,2486,2488],{"class":96,"line":2487},142,[94,2489,140],{"emptyLinePlaceholder":139},[94,2491,2493,2495,2498,2500,2503,2505],{"class":96,"line":2492},143,[94,2494,1282],{"class":554},[94,2496,2497],{"class":736}," (expired.",[94,2499,974],{"class":117},[94,2501,2502],{"class":554}," ===",[94,2504,962],{"class":117},[94,2506,843],{"class":736},[94,2508,2510,2513,2515,2517,2520],{"class":96,"line":2509},144,[94,2511,2512],{"class":736},"        console.",[94,2514,2080],{"class":100},[94,2516,737],{"class":736},[94,2518,2519],{"class":104},"'No keys required rotation.'",[94,2521,743],{"class":736},[94,2523,2525,2527],{"class":96,"line":2524},145,[94,2526,1703],{"class":554},[94,2528,790],{"class":736},[94,2530,2532],{"class":96,"line":2531},146,[94,2533,1271],{"class":736},[94,2535,2537],{"class":96,"line":2536},147,[94,2538,140],{"emptyLinePlaceholder":139},[94,2540,2542,2544],{"class":96,"line":2541},148,[94,2543,1282],{"class":554},[94,2545,2546],{"class":736}," (args.disableOld) {\n",[94,2548,2550,2552,2555,2557,2559],{"class":96,"line":2549},149,[94,2551,2272],{"class":554},[94,2553,2554],{"class":736}," (current ",[94,2556,1440],{"class":554},[94,2558,962],{"class":117},[94,2560,843],{"class":736},[94,2562,2564,2567,2569,2571,2573,2576],{"class":96,"line":2563},150,[94,2565,2566],{"class":554},"            throw",[94,2568,1235],{"class":554},[94,2570,1238],{"class":100},[94,2572,737],{"class":736},[94,2574,2575],{"class":104},"'No enabled key within the threshold; run without --disable-old to create one first'",[94,2577,743],{"class":736},[94,2579,2581],{"class":96,"line":2580},151,[94,2582,1265],{"class":736},[94,2584,2586,2588],{"class":96,"line":2585},152,[94,2587,2272],{"class":554},[94,2589,2590],{"class":736}," (args.dryRun) {\n",[94,2592,2594,2596,2598,2600,2603,2606,2608,2610,2613],{"class":96,"line":2593},153,[94,2595,2281],{"class":736},[94,2597,2080],{"class":100},[94,2599,737],{"class":736},[94,2601,2602],{"class":104},"`Dry run: would disable ${",[94,2604,2605],{"class":736},"expired",[94,2607,1668],{"class":104},[94,2609,974],{"class":117},[94,2611,2612],{"class":104},"} key(s).`",[94,2614,743],{"class":736},[94,2616,2618,2621],{"class":96,"line":2617},154,[94,2619,2620],{"class":554},"            return",[94,2622,790],{"class":736},[94,2624,2626],{"class":96,"line":2625},155,[94,2627,1265],{"class":736},[94,2629,2631,2634,2636,2638,2640,2642],{"class":96,"line":2630},156,[94,2632,2633],{"class":554},"        for",[94,2635,951],{"class":736},[94,2637,724],{"class":554},[94,2639,2149],{"class":117},[94,2641,2152],{"class":554},[94,2643,2644],{"class":736}," expired) {\n",[94,2646,2648,2651,2653,2656],{"class":96,"line":2647},157,[94,2649,2650],{"class":554},"            await",[94,2652,2026],{"class":736},[94,2654,2655],{"class":100},"disable",[94,2657,2658],{"class":736},"({ name: key.name });\n",[94,2660,2662,2664,2666,2668,2671,2673,2675,2677,2679,2681,2683,2685,2687,2689,2692,2694,2696,2698,2700,2702,2704,2707],{"class":96,"line":2661},158,[94,2663,2281],{"class":736},[94,2665,2080],{"class":100},[94,2667,737],{"class":736},[94,2669,2670],{"class":104},"`Disabled old key ${",[94,2672,2242],{"class":736},[94,2674,1668],{"class":104},[94,2676,1713],{"class":736},[94,2678,1668],{"class":104},[94,2680,2171],{"class":100},[94,2682,737],{"class":104},[94,2684,2176],{"class":104},[94,2686,260],{"class":104},[94,2688,2181],{"class":100},[94,2690,2691],{"class":104},"().",[94,2693,1904],{"class":100},[94,2695,737],{"class":104},[94,2697,2300],{"class":117},[94,2699,58],{"class":104},[94,2701,2305],{"class":117},[94,2703,2308],{"class":104},[94,2705,2706],{"class":104},"}...`",[94,2708,743],{"class":736},[94,2710,2712],{"class":96,"line":2711},159,[94,2713,1265],{"class":736},[94,2715,2717,2719],{"class":96,"line":2716},160,[94,2718,1703],{"class":554},[94,2720,790],{"class":736},[94,2722,2724],{"class":96,"line":2723},161,[94,2725,1271],{"class":736},[94,2727,2729],{"class":96,"line":2728},162,[94,2730,140],{"emptyLinePlaceholder":139},[94,2732,2734,2736,2739,2741,2744,2746],{"class":96,"line":2733},163,[94,2735,1282],{"class":554},[94,2737,2738],{"class":736}," (keys.",[94,2740,974],{"class":117},[94,2742,2743],{"class":554}," >=",[94,2745,782],{"class":117},[94,2747,843],{"class":736},[94,2749,2751,2753,2755,2757,2759,2762,2764,2766,2768,2771,2774,2777],{"class":96,"line":2750},164,[94,2752,1305],{"class":554},[94,2754,1235],{"class":554},[94,2756,1238],{"class":100},[94,2758,737],{"class":736},[94,2760,2761],{"class":104},"`Account already has ${",[94,2763,2088],{"class":736},[94,2765,1668],{"class":104},[94,2767,974],{"class":117},[94,2769,2770],{"class":104},"} keys (limit ${",[94,2772,2773],{"class":117},"MAX_KEYS_PER_ACCOUNT",[94,2775,2776],{"class":104},"}); delete disabled keys first`",[94,2778,743],{"class":736},[94,2780,2782],{"class":96,"line":2781},165,[94,2783,1271],{"class":736},[94,2785,2787,2789],{"class":96,"line":2786},166,[94,2788,1282],{"class":554},[94,2790,2590],{"class":736},[94,2792,2794,2796,2798,2800,2803],{"class":96,"line":2793},167,[94,2795,2512],{"class":736},[94,2797,2080],{"class":100},[94,2799,737],{"class":736},[94,2801,2802],{"class":104},"'Dry run: would create 1 replacement key.'",[94,2804,743],{"class":736},[94,2806,2808,2810],{"class":96,"line":2807},168,[94,2809,1703],{"class":554},[94,2811,790],{"class":736},[94,2813,2815],{"class":96,"line":2814},169,[94,2816,1271],{"class":736},[94,2818,2820],{"class":96,"line":2819},170,[94,2821,140],{"emptyLinePlaceholder":139},[94,2823,2825,2827,2830,2832,2834,2836,2839],{"class":96,"line":2824},171,[94,2826,849],{"class":554},[94,2828,2829],{"class":117}," created",[94,2831,730],{"class":554},[94,2833,1645],{"class":554},[94,2835,2026],{"class":736},[94,2837,2838],{"class":100},"create",[94,2840,1654],{"class":736},[94,2842,2844],{"class":96,"line":2843},172,[94,2845,2037],{"class":736},[94,2847,2849,2852,2855],{"class":96,"line":2848},173,[94,2850,2851],{"class":736},"        requestBody: { privateKeyType: ",[94,2853,2854],{"class":104},"'TYPE_GOOGLE_CREDENTIALS_FILE'",[94,2856,2857],{"class":736}," },\n",[94,2859,2861],{"class":96,"line":2860},174,[94,2862,1950],{"class":736},[94,2864,2866,2868,2871,2873,2876,2878,2880,2882,2884,2886],{"class":96,"line":2865},175,[94,2867,849],{"class":554},[94,2869,2870],{"class":117}," newKeyId",[94,2872,730],{"class":554},[94,2874,2875],{"class":736}," created.data.name.",[94,2877,2171],{"class":100},[94,2879,737],{"class":736},[94,2881,2176],{"class":104},[94,2883,260],{"class":736},[94,2885,2181],{"class":100},[94,2887,2184],{"class":736},[94,2889,2891,2893,2896,2898,2900,2902],{"class":96,"line":2890},176,[94,2892,849],{"class":554},[94,2894,2895],{"class":117}," location",[94,2897,730],{"class":554},[94,2899,1645],{"class":554},[94,2901,1571],{"class":100},[94,2903,2904],{"class":736},"(args, auth, created.data.privateKeyData);\n",[94,2906,2908,2910,2912,2914,2917,2920,2922,2924,2926,2928,2930,2932,2934,2937,2940,2942],{"class":96,"line":2907},177,[94,2909,2077],{"class":736},[94,2911,2080],{"class":100},[94,2913,737],{"class":736},[94,2915,2916],{"class":104},"`Created key ${",[94,2918,2919],{"class":736},"newKeyId",[94,2921,1668],{"class":104},[94,2923,1904],{"class":100},[94,2925,737],{"class":104},[94,2927,2300],{"class":117},[94,2929,58],{"class":104},[94,2931,2305],{"class":117},[94,2933,2308],{"class":104},[94,2935,2936],{"class":104},"}..., stored in ${",[94,2938,2939],{"class":736},"location",[94,2941,1257],{"class":104},[94,2943,743],{"class":736},[94,2945,2947,2949,2951,2953,2956],{"class":96,"line":2946},178,[94,2948,2077],{"class":736},[94,2950,2080],{"class":100},[94,2952,737],{"class":736},[94,2954,2955],{"class":104},"'Old key(s) left enabled. Deploy the new key, then rerun with --disable-old.'",[94,2957,743],{"class":736},[94,2959,2961],{"class":96,"line":2960},179,[94,2962,1486],{"class":736},[94,2964,2966],{"class":96,"line":2965},180,[94,2967,140],{"emptyLinePlaceholder":139},[94,2969,2971,2974,2976,2979,2982,2985,2988,2991],{"class":96,"line":2970},181,[94,2972,2973],{"class":100},"main",[94,2975,2691],{"class":736},[94,2977,2978],{"class":100},"catch",[94,2980,2981],{"class":736},"((",[94,2983,2984],{"class":839},"error",[94,2986,2987],{"class":736},") ",[94,2989,2990],{"class":554},"=>",[94,2992,857],{"class":736},[94,2994,2996,2998,3000],{"class":96,"line":2995},182,[94,2997,2077],{"class":736},[94,2999,2984],{"class":100},[94,3001,3002],{"class":736},"(error.message);\n",[94,3004,3006,3009,3011,3013],{"class":96,"line":3005},183,[94,3007,3008],{"class":736},"    process.exitCode ",[94,3010,760],{"class":554},[94,3012,982],{"class":117},[94,3014,790],{"class":736},[94,3016,3018],{"class":96,"line":3017},184,[94,3019,3020],{"class":736},"});\n",[20,3022,3024],{"id":3023},"notes","Notes",[31,3026,3027,3042,3053,3056,3062,3065,3090,3101],{},[34,3028,3029,3030,3033,3034,3037,3038,3041],{},"Before disabling anything, check whether the old key is still in use. Activity Analyzer reports the last authentication per key: ",[15,3031,3032],{},"gcloud policy-intelligence query-activity --activity-type=serviceAccountKeyLastAuthentication --project=my-project --query-filter='activities.full_resource_name=\"\u002F\u002Fiam.googleapis.com\u002Fprojects\u002Fmy-project\u002FserviceAccounts\u002Freporting@my-project.iam.gserviceaccount.com\u002Fkeys\u002FKEY_ID\"'",". It needs ",[15,3035,3036],{},"roles\u002Fpolicyanalyzer.activityAnalysisViewer",", and recent events may not be included yet; check the ",[15,3039,3040],{},"observationPeriod"," in the result.",[34,3043,3044,3045,3048,3049,3052],{},"Disable, wait, then delete. A disabled key can be re-enabled if something breaks (",[15,3046,3047],{},"gcloud iam service-accounts keys enable KEY_ID --iam-account=reporting@my-project.iam.gserviceaccount.com","). I run a separate cleanup a week later with ",[15,3050,3051],{},"gcloud iam service-accounts keys delete KEY_ID --iam-account=...",", which is permanent. The API doesn't record when a key was disabled, so the cleanup works from a list, not a timestamp.",[34,3054,3055],{},"Disabling a key does not revoke short-lived access tokens already minted from it. If the key leaked, the docs say to disable or delete the service account itself (or at least treat those tokens as live until they expire).",[34,3057,3058,3059,3061],{},"One replacement per run, regardless of how many expired keys the account has. The goal is a single current credential. Nothing is disabled in that run: Google's order is create, replace in every application, then disable and monitor, so ",[15,3060,17],{}," is a separate step you take after the deploy.",[34,3063,3064],{},"A service account can hold at most 10 keys. The script refuses to create an eleventh; delete disabled keys first.",[34,3066,3067,3068,3070,3071,298,3074,3077,3078,3081,3082,3085,3086,3089],{},"Keys disabled by Google carry a ",[15,3069,2318],{}," of ",[15,3072,3073],{},"SERVICE_ACCOUNT_KEY_DISABLE_REASON_EXPOSED",[15,3075,3076],{},"..._COMPROMISE_DETECTED",". Google scans public repositories for leaked keys, and since June 16, 2024 it disables exposed keys by default unless ",[15,3079,3080],{},"iam.serviceAccountKeyExposureResponse"," has been set to ",[15,3083,3084],{},"WAIT_FOR_ABUSE"," (Google recommends ",[15,3087,3088],{},"DISABLE_KEY","). Treat those as incidents, not rotation candidates.",[34,3091,3092,3093,3096,3097,3100],{},"The script lists ",[15,3094,3095],{},"USER_MANAGED"," keys only. ",[15,3098,3099],{},"SYSTEM_MANAGED"," keys are rotated by Google and can't be downloaded.",[34,3102,3103,3105,3106,3109],{},[15,3104,1586],{}," comes back base64-encoded. Secret Manager's ",[15,3107,3108],{},"payload.data"," also expects base64, so the script passes it through unchanged; only the file path decodes it.",[20,3111,3113],{"id":3112},"references","References",[31,3115,3116,3125,3132,3139,3146,3153,3160,3167,3174,3181],{},[34,3117,3118],{},[3119,3120,3124],"a",{"href":3121,"rel":3122},"https:\u002F\u002Fdocs.cloud.google.com\u002Fiam\u002Fdocs\u002Fbest-practices-for-managing-service-account-keys",[3123],"nofollow","Best practices for managing service account keys",[34,3126,3127],{},[3119,3128,3131],{"href":3129,"rel":3130},"https:\u002F\u002Fdocs.cloud.google.com\u002Fiam\u002Fdocs\u002Fkeys-create-delete",[3123],"Create and delete service account keys",[34,3133,3134],{},[3119,3135,3138],{"href":3136,"rel":3137},"https:\u002F\u002Fdocs.cloud.google.com\u002Fiam\u002Fdocs\u002Fkeys-disable-enable",[3123],"Disable and enable service account keys",[34,3140,3141],{},[3119,3142,3145],{"href":3143,"rel":3144},"https:\u002F\u002Fdocs.cloud.google.com\u002Forganization-policy\u002Frestrict-service-accounts",[3123],"Restrict service account usage (key constraints and expiry values)",[34,3147,3148],{},[3119,3149,3152],{"href":3150,"rel":3151},"https:\u002F\u002Fdocs.cloud.google.com\u002Fresource-manager\u002Fdocs\u002Fsecure-by-default-organizations",[3123],"Secure-by-default organization policies",[34,3154,3155],{},[3119,3156,3159],{"href":3157,"rel":3158},"https:\u002F\u002Fdocs.cloud.google.com\u002Fiam\u002Fdocs\u002Fworkload-identity-federation-with-deployment-pipelines",[3123],"Workload Identity Federation with deployment pipelines",[34,3161,3162],{},[3119,3163,3166],{"href":3164,"rel":3165},"https:\u002F\u002Fdocs.cloud.google.com\u002Fpolicy-intelligence\u002Fdocs\u002Factivity-analyzer-service-account-authentication",[3123],"Activity Analyzer: service account key authentication",[34,3168,3169],{},[3119,3170,3173],{"href":3171,"rel":3172},"https:\u002F\u002Fcloud.google.com\u002Fblog\u002Fproducts\u002Fidentity-security\u002Fautomatically-disabling-leaked-service-account-keys-what-you-need-to-know",[3123],"Automatically disabling leaked service account keys (Google Cloud blog)",[34,3175,3176],{},[3119,3177,3180],{"href":3178,"rel":3179},"https:\u002F\u002Fdocs.cloud.google.com\u002Fsecret-manager\u002Fdocs\u002Faccess-control",[3123],"Secret Manager access control",[34,3182,3183],{},[3119,3184,3187],{"href":3185,"rel":3186},"https:\u002F\u002Fiam.googleapis.com\u002F$discovery\u002Frest?version=v1",[3123],"IAM API discovery document (ServiceAccountKey fields, keys methods)",[3189,3190,3191],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":90,"searchDepth":121,"depth":121,"links":3193},[3194,3195,3196,3197,3198,3199,3200],{"id":22,"depth":121,"text":23},{"id":246,"depth":121,"text":247},{"id":304,"depth":121,"text":305},{"id":424,"depth":121,"text":425},{"id":604,"depth":121,"text":605},{"id":3023,"depth":121,"text":3024},{"id":3112,"depth":121,"text":3113},"techcolumnist",[3203,3204],"scripts","engineering","2026-03-04T14:00:00Z","A Node.js script that finds aging Google Cloud service account keys, mints one replacement, and disables the old keys once it is deployed.","md",false,null,{},"\u002Fblog\u002F2026\u002F03\u002F04\u002Fnode-js-gcloud-rotating-service-account-keys-before-they-expire",{"title":6,"description":3206},[3201],"blog\u002F2026\u002F03\u002F04\u002Fnode-js-gcloud-rotating-service-account-keys-before-they-expire",[3216,101],"nodejs","\u002F2026\u002F03\u002F04\u002Fnode-js-gcloud-rotating-service-account-keys-before-they-expire\u002F","JeUgDr6J4gpKcpY-j12ENUv4xDJjxto7BdzFydgKU60",{"title":3220,"description":3221,"date":3222,"url":3223,"categories":3224,"tags":3225,"image":3209,"readingTime":136,"canonical":3201,"sites":3228,"series":3209,"seriesOrder":3209},"Node.js: Discovery – Diffing Two Network Scans to Flag New Hosts","A dependency-free Node.js script that diffs two nmap XML scans and flags new, missing and moved hosts plus port changes, with an exit code for cron or CI.","2026-03-11T14:00:00Z","\u002F2026\u002F03\u002F11\u002Fnode-js-discovery-diffing-two-network-scans-to-flag-new-hosts\u002F",[3203,3204],[3216,3226,3227],"discovery","network",[3201],{"title":3230,"description":3231,"date":3232,"url":3233,"categories":3234,"tags":3235,"image":3209,"readingTime":163,"canonical":3201,"sites":3239,"series":3209,"seriesOrder":3209},"Node.js: Data Warehouse – Streaming ETL Rows Without Buffering Them","A Node.js script that streams Postgres rows through a cursor, a transform and COPY FROM STDIN with backpressure, so memory tracks batch size, not table size.","2026-02-25T14:00:00Z","\u002F2026\u002F02\u002F25\u002Fnode-js-data-warehouse-streaming-etl-rows-without-loading-them-all-in-memory\u002F",[3203,3204],[3216,3236,3237,3238],"data-warehouse","etl","sql",[3201],[3219,3229,3241],{"title":3242,"description":3243,"date":3244,"url":3245,"categories":3246,"tags":3247,"image":3209,"readingTime":163,"canonical":3201,"sites":3249,"series":3209,"seriesOrder":3209},"Node.js: Azure – A Slack Bot for Resource Group Budget Alerts","A Node.js script that queries month-to-date Azure cost per resource group, projects month-end spend, and posts Block Kit messages to Slack for groups over budget.","2026-02-18T14:00:00Z","\u002F2026\u002F02\u002F18\u002Fnode-js-azure-a-slack-bot-for-resource-group-budget-alerts\u002F",[3203,3204],[3216,3248],"azure",[3201],{"doc":3209,"posts":3251},[],1790052513653]