From 97cfab7168a08507644266d1c72945060b05c41d Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 6 Dec 2025 17:53:34 -0800 Subject: Move secrets to infisical. --- worker/Dockerfile | 14 +--- worker/package.json | 3 +- worker/scripts/ansible_playbook.ts | 8 +- worker/scripts/build_docker_image.ts | 4 +- worker/scripts/checkout_ci.ts | 2 +- worker/scripts/npm_publish.ts | 8 +- worker/secret.ts | 146 +++++++++++++++++++++++++++++++++++ 7 files changed, 161 insertions(+), 24 deletions(-) (limited to 'worker') diff --git a/worker/Dockerfile b/worker/Dockerfile index 0dfd816..c93fa2c 100644 --- a/worker/Dockerfile +++ b/worker/Dockerfile @@ -1,18 +1,9 @@ # -- -- -FROM debian:stable-slim AS worker_dependencies - -RUN apt-get update && apt-get install -yqq unzip curl - -ARG BITWARDEN_VERSION=2025.4.0 -RUN curl -L -o /bw-linux.zip "https://github.com/bitwarden/clients/releases/download/cli-v${BITWARDEN_VERSION}/bw-linux-${BITWARDEN_VERSION}.zip" -RUN unzip /bw-linux.zip -d / \ - && chmod +x /bw - -COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/ +FROM docker:dind AS worker_dependencies # -- -- # -- -- -FROM oci.liz.coffee/emprespresso/ci_base:release AS worker +FROM img.liz.coffee/emprespresso/ci_base:release AS worker RUN apt-get update && apt-get install -yqq git jq @@ -33,7 +24,6 @@ ARG DOCKER_GID="995" # but it may be overridden via this `DOCKER_GID` build arg. RUN groupadd -g ${DOCKER_GID} docker RUN usermod -a -d /var/lib/laminar -G docker node -COPY --from=worker_dependencies /bw /usr/local/bin/ COPY --from=worker_dependencies /usr/local/bin/docker /usr/local/bin/ USER node diff --git a/worker/package.json b/worker/package.json index b912750..f8eae65 100644 --- a/worker/package.json +++ b/worker/package.json @@ -18,7 +18,8 @@ }, "dependencies": { "@emprespresso/ci_model": "*", - "@emprespresso/pengueno": "^0.0.13" + "@emprespresso/pengueno": "^0.0.13", + "@infisical/sdk": "^4.0.6" }, "devDependencies": { "copyfiles": "2.4.1" diff --git a/worker/scripts/ansible_playbook.ts b/worker/scripts/ansible_playbook.ts index f7315ab..5c101ba 100755 --- a/worker/scripts/ansible_playbook.ts +++ b/worker/scripts/ansible_playbook.ts @@ -12,7 +12,7 @@ import { TraceUtil, } from '@emprespresso/pengueno'; import type { AnsiblePlaybookJob } from '@emprespresso/ci_model'; -import { Bitwarden, BitwardenKey, getPathOnHost, type SecureNote } from '@emprespresso/ci_worker'; +import { Infisical, InfisicalKey, getPathOnHost, type SecureNote } from '@emprespresso/ci_worker'; import { writeFile, mkdir } from 'fs/promises'; import { join } from 'path'; import { rmSync } from 'fs'; @@ -25,7 +25,7 @@ const eitherJob = getRequiredEnvVars(['path', 'playbooks']).mapRight( }, ); -const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config)); +const eitherVault = Infisical.getConfigFromEnvironment().mapRight((config) => new Infisical(config)); const playbookMetric = Metric.fromName('ansiblePlaybook.playbook'); const _logJob = LogTraceable.of(eitherJob).flatMap(TraceUtil.withTrace('ansible_playbook')); @@ -49,8 +49,8 @@ await LogMetricTraceable.ofLogTraceable(_logJob) Error, { secretFiles: { ssh_key: string; ansible_secrets: string }; - key: BitwardenKey; - vault: Bitwarden; + key: InfisicalKey; + vault: Infisical; job: AnsiblePlaybookJob; } > diff --git a/worker/scripts/build_docker_image.ts b/worker/scripts/build_docker_image.ts index 759dfc1..527120a 100755 --- a/worker/scripts/build_docker_image.ts +++ b/worker/scripts/build_docker_image.ts @@ -10,7 +10,7 @@ import { Command, } from '@emprespresso/pengueno'; import type { BuildDockerImageJob, BuildDockerImageJobProps } from '@emprespresso/ci_model'; -import { Bitwarden, type LoginItem } from '@emprespresso/ci_worker'; +import { Infisical, type LoginItem } from '@emprespresso/ci_worker'; import path from 'path'; const job = getRequiredEnvVars([ @@ -36,7 +36,7 @@ const job = getRequiredEnvVars([ (x) => x, ); -const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config)); +const eitherVault = Infisical.getConfigFromEnvironment().mapRight((config) => new Infisical(config)); const buildImageMetric = Metric.fromName('dockerImage.build').asResult(); const loginMetric = Metric.fromName('dockerRegistry.login').asResult(); diff --git a/worker/scripts/checkout_ci.ts b/worker/scripts/checkout_ci.ts index c4006e6..009d3fa 100755 --- a/worker/scripts/checkout_ci.ts +++ b/worker/scripts/checkout_ci.ts @@ -26,7 +26,7 @@ function isCiWorkflow(t: unknown): t is CiWorkflow { } const CI_WORKFLOW_FILE = '.ci/ci.json'; -const OCI_REGISTRY = 'oci.liz.coffee'; +const OCI_REGISTRY = 'img.liz.coffee'; const PIPELINE_IMAGE = OCI_REGISTRY + '/emprespresso/ci_worker:release'; const READONLY_CREDENTIALS = { username: 'readonly', password: 'readonly' }; diff --git a/worker/scripts/npm_publish.ts b/worker/scripts/npm_publish.ts index 9324856..bcb2796 100644 --- a/worker/scripts/npm_publish.ts +++ b/worker/scripts/npm_publish.ts @@ -11,7 +11,7 @@ import { TraceUtil, getStdoutMany, } from '@emprespresso/pengueno'; -import { Bitwarden, getPathOnHost, type SecureNote } from '@emprespresso/ci_worker'; +import { Infisical, getPathOnHost, type SecureNote } from '@emprespresso/ci_worker'; import { writeFile, mkdir } from 'fs/promises'; import { join } from 'path'; import { rmSync } from 'fs'; @@ -25,11 +25,11 @@ const eitherJob = getRequiredEnvVars(['source', 'registry']).mapRight( }, ); -const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config)); +const eitherVault = Infisical.getConfigFromEnvironment().mapRight((config) => new Infisical(config)); const READONLY_CREDENTIALS = { username: 'readonly', password: 'readonly' }; -const REGISTRY = 'oci.liz.coffee'; -const CI_PACKPUB_IMG = 'oci.liz.coffee/emprespresso/ci_packpub_npm:release'; +const REGISTRY = 'img.liz.coffee'; +const CI_PACKPUB_IMG = 'img.liz.coffee/emprespresso/ci_packpub_npm:release'; const packPubMetric = Metric.fromName('npm_publish.packpub'); const _logJob = LogTraceable.of(eitherJob).flatMap(TraceUtil.withTrace('npm_publish')); diff --git a/worker/secret.ts b/worker/secret.ts index 34056c2..c375693 100644 --- a/worker/secret.ts +++ b/worker/secret.ts @@ -12,6 +12,7 @@ import { import { randomUUID } from 'node:crypto'; import { mkdirSync } from 'node:fs'; import path from 'node:path'; +import { InfisicalSDK } from '@infisical/sdk'; // -- -- export interface SecretItem { @@ -155,3 +156,148 @@ export interface BitwardenConfig { clientId: string; } // -- -- + +// -- -- +export type InfisicalKey = { + client: InfisicalSDK; +}; + +export class Infisical implements IVault { + constructor(private readonly config: InfisicalConfig) {} + + public async unlock(client: TClient): Promise> { + return client + .move(this.config) + .flatMap(TraceUtil.withMetricTrace(Infisical.loginMetric)) + .peek((tConfig) => tConfig.trace.trace('initializing infisical client (⑅˘꒳˘)')) + .map(async (tConfig) => { + try { + const sdk = new InfisicalSDK({ + siteUrl: tConfig.get().siteUrl, + }); + + const authenticatedClient = await sdk.auth().universalAuth.login({ + clientId: tConfig.get().clientId, + clientSecret: tConfig.get().clientSecret, + }); + + return Either.right({ client: authenticatedClient }); + } catch (error) { + return Either.left( + error instanceof Error ? error : new Error(`failed to authenticate: ${error}`), + ); + } + }) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(Infisical.loginMetric))) + .get(); + } + + public async fetchSecret( + client: TClient, + key: InfisicalKey, + item: string, + ): Promise> { + return client + .move(key) + .flatMap(TraceUtil.withMetricTrace(Infisical.fetchSecretMetric)) + .peek((tSession) => tSession.trace.trace(`looking for your secret ${item} (⑅˘꒳˘)`)) + .map(async (tSession) => { + const eitherSecret = await Either.fromFailableAsync(() => + tSession.get().client.secrets().getSecret({ + secretName: item, + projectId: this.config.projectId, + environment: this.config.environment, + }), + ); + + return eitherSecret.flatMapAsync(async (secret) => { + if (!secret || !secret.secretValue) { + return Either.left(new Error(`couldn't find the secret ${item} (。•́︿•̀。)`)); + } + + const parsedSecret = await Either.fromFailableAsync(() => + Promise.resolve().then(() => JSON.parse(secret.secretValue)), + ); + + const secretItem = parsedSecret.fold( + () => ({ + name: secret.secretKey, + notes: secret.secretValue, + }), + (parsed: any) => ({ + name: secret.secretKey, + ...parsed, + }), + ); + + return Either.right(secretItem as unknown as T); + }); + }) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(Infisical.fetchSecretMetric))) + .get(); + } + + public async lock(client: TClient, key: InfisicalKey): Promise> { + return client + .move(key) + .flatMap(TraceUtil.withMetricTrace(Infisical.lockVaultMetric)) + .peek((tSession) => tSession.trace.trace('closing infisical client :3')) + .map(async (tSession) => { + try { + // Infisical SDK doesn't require explicit logout, but we can cleanup here if needed + return Either.right(tSession.get()); + } catch (error) { + return Either.left( + error instanceof Error ? error : new Error(`failed to close client: ${error}`), + ); + } + }) + .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(Infisical.lockVaultMetric))) + .peek( + TraceUtil.promiseify((tEitherWithLocked) => + tEitherWithLocked + .get() + .mapRight(() => tEitherWithLocked.trace.trace('all cleaned up now~ (。•̀ᴗ-)✧')), + ), + ) + .map(TraceUtil.promiseify((e) => e.get().mapRight(() => key))) + .get(); + } + + public static getConfigFromEnvironment(): IEither { + return getRequiredEnvVars([ + 'INFISICAL_CLIENT_ID', + 'INFISICAL_CLIENT_SECRET', + 'INFISICAL_PROJECT_ID', + 'INFISICAL_ENVIRONMENT', + 'INFISICAL_SITE_URL', + ]).mapRight( + ({ + INFISICAL_CLIENT_ID, + INFISICAL_CLIENT_SECRET, + INFISICAL_PROJECT_ID, + INFISICAL_ENVIRONMENT, + INFISICAL_SITE_URL, + }) => ({ + clientId: INFISICAL_CLIENT_ID, + clientSecret: INFISICAL_CLIENT_SECRET, + projectId: INFISICAL_PROJECT_ID, + environment: INFISICAL_ENVIRONMENT, + siteUrl: INFISICAL_SITE_URL, + }), + ); + } + + private static loginMetric = Metric.fromName('Infisical.login').asResult(); + private static fetchSecretMetric = Metric.fromName('Infisical.fetchSecret').asResult(); + private static lockVaultMetric = Metric.fromName('Infisical.lock').asResult(); +} + +export interface InfisicalConfig { + clientId: string; + clientSecret: string; + projectId: string; + environment: string; + siteUrl: string; +} +// -- -- -- cgit v1.2.3-70-g09d2