Files
dokploy/packages/server/src/utils/vault/index.ts
T

175 lines
4.8 KiB
TypeScript

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 { awsParameterStoreClient } from "./aws-parameter-store";
import { azureClient } from "./azure";
import { dopplerClient } from "./doppler";
import { hashicorpClient } from "./hashicorp";
import { infisicalClient } from "./infisical";
import { phaseClient } from "./phase";
import { scalewayClient } from "./scaleway";
import type { VaultClient } from "./types";
export const VAULT_REF_REGEX = /\$\{\{vault\.([a-zA-Z0-9_-]+)\.([^}]+)\}\}/g;
const clients: Record<VaultProviderConfig["providerType"], VaultClient> = {
hashicorp: hashicorpClient as VaultClient,
infisical: infisicalClient as VaultClient,
aws: awsClient as VaultClient,
"aws-parameter-store": awsParameterStoreClient as VaultClient,
doppler: dopplerClient as VaultClient,
azure: azureClient as VaultClient,
scaleway: scalewayClient as VaultClient,
phase: phaseClient 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<string | null> => {
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<string, Set<string>>();
for (const match of matches) {
const providerName = match[1] as string;
const ref = match[2] as string;
const refs = refsByProvider.get(providerName) ?? new Set<string>();
refs.add(ref);
refsByProvider.set(providerName, refs);
}
const providers = await db.query.vaultProvider.findMany({
where: eq(vaultProvider.organizationId, scope.organizationId),
});
const resolved = new Map<string, string>();
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 <T extends EnvBearingEntity>(
entity: T,
): Promise<T> => {
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;
};