From cc89d9de44dce2da2b95e1c3d873b7eae0a2a410 Mon Sep 17 00:00:00 2001 From: Philip Paetz Date: Tue, 23 Jul 2024 10:38:57 +0200 Subject: [PATCH] refactor: consolidated secret loading code into one generic reusable function --- src/get-discord-webhook-url.ts | 24 ---------------------- src/get-quicknode-security-token.ts | 28 -------------------------- src/get-secret.ts | 31 +++++++++++++++++++++++++++++ src/get-telegram-bot-token.ts | 24 ---------------------- src/send-discord-notification.ts | 5 +++-- src/send-telegram-notification.ts | 4 ++-- src/validate-request-origin.ts | 7 +++++-- 7 files changed, 41 insertions(+), 82 deletions(-) delete mode 100644 src/get-discord-webhook-url.ts delete mode 100644 src/get-quicknode-security-token.ts create mode 100644 src/get-secret.ts delete mode 100644 src/get-telegram-bot-token.ts diff --git a/src/get-discord-webhook-url.ts b/src/get-discord-webhook-url.ts deleted file mode 100644 index 20cb3dc..0000000 --- a/src/get-discord-webhook-url.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; -import config from "./config.js"; - -/** - * Get the Discord webhook URL from GCloud Secret Manager. When ran locally, it assumes that the user is - * authenticated in the gcloud cli and has the necessary permissions to access the secret. - */ -export default async function getDiscordWebhookUrl(): Promise { - const secretManager = new SecretManagerServiceClient(); - const secretFullResourceName = `projects/${config.GCP_PROJECT_ID}/secrets/${config.DISCORD_WEBHOOK_URL_SECRET_ID}/versions/latest`; - const [version] = await secretManager.accessSecretVersion({ - name: secretFullResourceName, - }); - - const webhookUrl = version.payload?.data?.toString(); - - if (!webhookUrl) { - throw new Error( - "Failed to retrieve discord webhook url from secret manager", - ); - } - - return webhookUrl; -} diff --git a/src/get-quicknode-security-token.ts b/src/get-quicknode-security-token.ts deleted file mode 100644 index 6f104bd..0000000 --- a/src/get-quicknode-security-token.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; -import config from "./config.js"; - -/** - * Get the Quicknode Security Token from GCloud Secret Manager - * - * NOTE: This will fail locally because the local function will lack the necessary permissions to access the secret. - * That's why read the webhook URL from our .env file when running locally. We could probably make it work by having - * the local function impersonate the service account used by the function in GCP, but that was a rabbit hole I didn't - * want to go down when a simple .env approach also works for local testing. - */ -export default async function getQuicknodeSecurityToken(): Promise { - const secretManager = new SecretManagerServiceClient(); - const secretFullResourceName = `projects/${config.GCP_PROJECT_ID}/secrets/${config.QUICKNODE_SECURITY_TOKEN_SECRET_ID}/versions/latest`; - const [version] = await secretManager.accessSecretVersion({ - name: secretFullResourceName, - }); - - const securityToken = version.payload?.data?.toString(); - - if (!securityToken) { - throw new Error( - "Failed to retrieve Quicknode security token from secret manager", - ); - } - - return securityToken; -} diff --git a/src/get-secret.ts b/src/get-secret.ts new file mode 100644 index 0000000..d75d726 --- /dev/null +++ b/src/get-secret.ts @@ -0,0 +1,31 @@ +import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; +import config from "./config.js"; + +/** + * Load a secret from Secret Manager + */ +export default async function getSecret(secretId: string): Promise { + try { + const secretManager = new SecretManagerServiceClient(); + const secretFullResourceName = `projects/${config.GCP_PROJECT_ID}/secrets/${secretId}/versions/latest`; + const [version] = await secretManager.accessSecretVersion({ + name: secretFullResourceName, + }); + + const secret = version.payload?.data?.toString(); + + if (!secret) { + throw new Error( + `Secret '${secretId}' is empty or undefined. Please check the secret in Secret Manager.`, + ); + } + + return secret; + } catch (error) { + console.error( + `Failed to retrieve secret '${secretId}' from secret manager:`, + error, + ); + throw error; + } +} diff --git a/src/get-telegram-bot-token.ts b/src/get-telegram-bot-token.ts deleted file mode 100644 index 9b617ab..0000000 --- a/src/get-telegram-bot-token.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; -import config from "./config.js"; - -/** - * Get the Telegram Bot Token from GCloud Secret Manager. When ran locally, it assumes that the user is - * authenticated in the gcloud cli and has the necessary permissions to access the secret. - */ -export default async function getTelegramBotToken(): Promise { - const secretManager = new SecretManagerServiceClient(); - const secretFullResourceName = `projects/${config.GCP_PROJECT_ID}/secrets/${config.TELEGRAM_BOT_TOKEN_SECRET_ID}/versions/latest`; - const [version] = await secretManager.accessSecretVersion({ - name: secretFullResourceName, - }); - - const botToken = version.payload?.data?.toString(); - - if (!botToken) { - throw new Error( - "Failed to retrieve telegram bot token from secret manager", - ); - } - - return botToken; -} diff --git a/src/send-discord-notification.ts b/src/send-discord-notification.ts index 883a7bc..ee7a234 100644 --- a/src/send-discord-notification.ts +++ b/src/send-discord-notification.ts @@ -1,5 +1,6 @@ import { EmbedBuilder, WebhookClient } from "discord.js"; -import getDiscordWebhookUrl from "./get-discord-webhook-url.js"; +import config from "./config"; +import getSecret from "./get-secret.js"; import type { ProposalCreatedEvent } from "./types"; export default async function sendDiscordNotification( @@ -30,7 +31,7 @@ export default async function sendDiscordNotification( .setColor(0xa6e5f6); const discordWebhookClient = new WebhookClient({ - url: await getDiscordWebhookUrl(), + url: await getSecret(config.DISCORD_WEBHOOK_URL_SECRET_ID), }); await discordWebhookClient.send({ diff --git a/src/send-telegram-notification.ts b/src/send-telegram-notification.ts index 348f45a..bd38043 100644 --- a/src/send-telegram-notification.ts +++ b/src/send-telegram-notification.ts @@ -1,12 +1,12 @@ import config from "./config.js"; -import getTelegramBotToken from "./get-telegram-bot-token"; +import getSecret from "./get-secret.js"; import { ProposalCreatedEvent } from "./types"; export default async function sendTelegramNotification( event: ProposalCreatedEvent, txHash: string, ) { - const botToken = await getTelegramBotToken(); + const botToken = await getSecret(config.TELEGRAM_BOT_TOKEN_SECRET_ID); const botUrl = `https://api.telegram.org/bot${botToken}/sendMessage`; const { title, description } = JSON.parse(event.args.description) as { diff --git a/src/validate-request-origin.ts b/src/validate-request-origin.ts index cc4500c..9f093dc 100644 --- a/src/validate-request-origin.ts +++ b/src/validate-request-origin.ts @@ -1,9 +1,12 @@ import type { Request } from "@google-cloud/functions-framework"; import crypto from "crypto"; -import getQuicknodeSecurityToken from "./get-quicknode-security-token"; +import config from "./config"; +import getSecret from "./get-secret"; export default async function validateRequestOrigin(req: Request) { - const quicknodeSecurityToken = await getQuicknodeSecurityToken(); + const quicknodeSecurityToken = await getSecret( + config.QUICKNODE_SECURITY_TOKEN_SECRET_ID, + ); const givenSignature = req.headers["x-qn-signature"]; const nonce = req.headers["x-qn-nonce"]; const contentHash = req.headers["x-qn-content-hash"];