import { db } from "@dokploy/server/db"; import { type VaultProviderConfig, vaultProvider, } from "@dokploy/server/db/schema"; import { eq } from "drizzle-orm"; import { awsClient } from "./aws"; import { azureClient } from "./azure"; import { dopplerClient } from "./doppler"; import { hashicorpClient } from "./hashicorp"; import { infisicalClient } from "./infisical"; import { scalewayClient } from "./scaleway"; import type { VaultClient } from "./types"; export const VAULT_REF_REGEX = /\$\{\{vault\.([a-zA-Z0-9_-]+)\.([^}]+)\}\}/g; const clients: Record = { hashicorp: hashicorpClient as VaultClient, infisical: infisicalClient as VaultClient, aws: awsClient as VaultClient, doppler: dopplerClient as VaultClient, azure: azureClient as VaultClient, scaleway: scalewayClient as VaultClient, }; export const getVaultClient = ( providerType: VaultProviderConfig["providerType"], ) => clients[providerType]; const refKey = (providerName: string, ref: string) => `${providerName}${ref}`; export type VaultResolutionScope = { organizationId: string; projectId: string; environmentId?: string; }; export const isVaultProviderAssigned = ( assignments: { projectId: string; environmentIds: string[] }[], projectId: string, environmentId?: string, ) => assignments.some( (assignment) => assignment.projectId === projectId && (assignment.environmentIds.length === 0 || !environmentId || assignment.environmentIds.includes(environmentId)), ); export const resolveVaultReferences = async ( rawEnv: string | null | undefined, scope?: VaultResolutionScope, ): Promise => { if (!rawEnv) { return rawEnv ?? null; } const matches = [...rawEnv.matchAll(VAULT_REF_REGEX)]; if (matches.length === 0) { return rawEnv; } if (!scope) { throw new Error( "Vault references (${{vault.*}}) are not supported in this context", ); } const refsByProvider = new Map>(); for (const match of matches) { const providerName = match[1] as string; const ref = match[2] as string; const refs = refsByProvider.get(providerName) ?? new Set(); refs.add(ref); refsByProvider.set(providerName, refs); } const providers = await db.query.vaultProvider.findMany({ where: eq(vaultProvider.organizationId, scope.organizationId), }); const resolved = new Map(); await Promise.all( [...refsByProvider.entries()].map(async ([providerName, refs]) => { const provider = providers.find((p) => p.name === providerName); if (!provider) { throw new Error( `Vault provider "${providerName}" not found. Configure it in Settings -> Secrets Providers.`, ); } if ( !isVaultProviderAssigned( provider.assignments, scope.projectId, scope.environmentId, ) ) { throw new Error( `Vault provider "${providerName}" is not enabled for this project/environment. Assign it in Settings -> Secrets Providers.`, ); } const client = clients[provider.providerType]; const values = await client.getSecrets(provider.config, [...refs]); for (const ref of refs) { const value = values[ref]; if (value === undefined) { throw new Error( `Secret "${ref}" not found in vault provider "${providerName}"`, ); } resolved.set(refKey(providerName, ref), value); } }), ); return rawEnv.replace( VAULT_REF_REGEX, (_, providerName, ref) => resolved.get(refKey(providerName, ref)) ?? "", ); }; type EnvBearingEntity = { env: string | null; buildArgs?: string | null; buildSecrets?: string | null; environment: { environmentId: string; env: string | null; project: { projectId: string; env: string | null; organizationId: string; }; }; }; export const withResolvedVaultRefs = async ( entity: T, ): Promise => { const scope: VaultResolutionScope = { organizationId: entity.environment.project.organizationId, projectId: entity.environment.project.projectId, environmentId: entity.environment.environmentId, }; const resolve = (value: string | null | undefined) => resolveVaultReferences(value ?? null, scope); const resolved: T = { ...entity, env: await resolve(entity.env), environment: { ...entity.environment, env: await resolve(entity.environment.env), project: { ...entity.environment.project, env: await resolve(entity.environment.project.env), }, }, }; if (entity.buildArgs !== undefined) { resolved.buildArgs = await resolve(entity.buildArgs); } if (entity.buildSecrets !== undefined) { resolved.buildSecrets = await resolve(entity.buildSecrets); } return resolved; };