aboutsummaryrefslogtreecommitdiff
path: root/worker/secret.ts
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-12-06 17:53:34 -0800
committerElizabeth Hunt <me@liz.coffee>2025-12-06 18:08:46 -0800
commit97cfab7168a08507644266d1c72945060b05c41d (patch)
tree98094f24a334108451ab65f03c229184750609cb /worker/secret.ts
parenta24545074ee575f37f7d4d3d4058f71e04ba2fba (diff)
downloadci-97cfab7168a08507644266d1c72945060b05c41d.tar.gz
ci-97cfab7168a08507644266d1c72945060b05c41d.zip
Move secrets to infisical.
Diffstat (limited to 'worker/secret.ts')
-rw-r--r--worker/secret.ts146
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> --