diff options
| author | Elizabeth Hunt <me@liz.coffee> | 2025-12-06 17:53:34 -0800 |
|---|---|---|
| committer | Elizabeth Hunt <me@liz.coffee> | 2025-12-06 18:08:46 -0800 |
| commit | 97cfab7168a08507644266d1c72945060b05c41d (patch) | |
| tree | 98094f24a334108451ab65f03c229184750609cb /worker/secret.ts | |
| parent | a24545074ee575f37f7d4d3d4058f71e04ba2fba (diff) | |
| download | ci-97cfab7168a08507644266d1c72945060b05c41d.tar.gz ci-97cfab7168a08507644266d1c72945060b05c41d.zip | |
Move secrets to infisical.
Diffstat (limited to 'worker/secret.ts')
| -rw-r--r-- | worker/secret.ts | 146 |
1 files changed, 146 insertions, 0 deletions
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'; // -- <ISecret> -- export interface SecretItem { @@ -155,3 +156,148 @@ export interface BitwardenConfig { clientId: string; } // -- </Vault> -- + +// -- <Infisical> -- +export type InfisicalKey = { + client: InfisicalSDK; +}; + +export class Infisical implements IVault<TClient, InfisicalKey, TItemId> { + constructor(private readonly config: InfisicalConfig) {} + + public async unlock(client: TClient): Promise<IEither<Error, InfisicalKey>> { + 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<Error, InfisicalKey>({ client: authenticatedClient }); + } catch (error) { + return Either.left<Error, InfisicalKey>( + error instanceof Error ? error : new Error(`failed to authenticate: ${error}`), + ); + } + }) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(Infisical.loginMetric))) + .get(); + } + + public async fetchSecret<T extends SecretItem>( + client: TClient, + key: InfisicalKey, + item: string, + ): Promise<IEither<Error, T>> { + 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<Error, any>(() => + tSession.get().client.secrets().getSecret({ + secretName: item, + projectId: this.config.projectId, + environment: this.config.environment, + }), + ); + + return eitherSecret.flatMapAsync<T>(async (secret) => { + if (!secret || !secret.secretValue) { + return Either.left<Error, T>(new Error(`couldn't find the secret ${item} (。•́︿•̀。)`)); + } + + const parsedSecret = await Either.fromFailableAsync<Error, any>(() => + 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<Error, T>(secretItem as unknown as T); + }); + }) + .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(Infisical.fetchSecretMetric))) + .get(); + } + + public async lock(client: TClient, key: InfisicalKey): Promise<IEither<Error, InfisicalKey>> { + 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<Error, InfisicalKey>(tSession.get()); + } catch (error) { + return Either.left<Error, InfisicalKey>( + 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<Error, InfisicalConfig> { + 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; +} +// -- </Infisical> -- |
