Files
dokploy/packages/server/src/utils/vault/azure.ts
T
Mauricio Siu f6622428f7 feat: enhance vault provider management and environment variable resolution
- Introduced support for project and environment assignments for vault providers, allowing for more granular access control.
- Updated the vault provider schema to include assignments and modified related components to handle these changes.
- Enhanced UI to display assignment status and allow users to manage project and environment associations effectively.
- Added validation to ensure assignments reference valid projects and environments within the organization.
- Updated tests to cover new functionality related to vault provider assignments and environment variable resolution.
2026-08-10 16:25:39 -06:00

120 lines
3.4 KiB
TypeScript

import type { azureVaultConfigSchema } from "@dokploy/server/db/schema";
import type { z } from "zod";
import { type VaultClient, vaultFetch } from "./types";
type AzureConfig = z.infer<typeof azureVaultConfigSchema>;
const API_VERSION = "7.4";
const baseUrl = (config: AzureConfig) => config.vaultUri.replace(/\/+$/, "");
const getAccessToken = async (config: AzureConfig) => {
const body = new URLSearchParams({
grant_type: "client_credentials",
client_id: config.clientId,
client_secret: config.clientSecret,
scope: "https://vault.azure.net/.default",
});
const response = await vaultFetch(
`https://login.microsoftonline.com/${encodeURIComponent(config.tenantId)}/oauth2/v2.0/token`,
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString(),
},
);
if (!response.ok) {
let detail = "";
try {
const body = (await response.json()) as { error_description?: string };
detail = (body.error_description ?? "").split("\n")[0] ?? "";
} catch {}
throw new Error(
`Azure Key Vault: authentication failed (status ${response.status}${detail ? `: ${detail}` : ""})`,
);
}
const data = (await response.json()) as { access_token?: string };
if (!data.access_token) {
throw new Error("Azure Key Vault: no access token returned");
}
return data.access_token;
};
const readSecret = async (config: AzureConfig, token: string, name: string) => {
const response = await vaultFetch(
`${baseUrl(config)}/secrets/${encodeURIComponent(name)}?api-version=${API_VERSION}`,
{ headers: { Authorization: `Bearer ${token}` } },
);
if (response.status === 404) {
throw new Error(`Azure Key Vault: secret "${name}" not found`);
}
if (!response.ok) {
throw new Error(
`Azure Key Vault: failed to read secret "${name}" (status ${response.status})`,
);
}
const data = (await response.json()) as { value?: string };
if (data.value === undefined) {
throw new Error(`Azure Key Vault: secret "${name}" has no value`);
}
return data.value;
};
export const azureClient: VaultClient<AzureConfig> = {
async getSecrets(config, refs) {
const token = await getAccessToken(config);
const result: Record<string, string> = {};
await Promise.all(
refs.map(async (ref) => {
result[ref] = await readSecret(config, token, ref);
}),
);
return result;
},
async testConnection(config) {
const token = await getAccessToken(config);
const response = await vaultFetch(
`${baseUrl(config)}/secrets?api-version=${API_VERSION}&maxresults=1`,
{ headers: { Authorization: `Bearer ${token}` } },
);
if (!response.ok) {
throw new Error(
`Azure Key Vault: cannot list secrets (status ${response.status})`,
);
}
},
async listSecretNames(config) {
const token = await getAccessToken(config);
const names: string[] = [];
let url: string | null =
`${baseUrl(config)}/secrets?api-version=${API_VERSION}&maxresults=25`;
while (url && names.length < 200) {
const response = await vaultFetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
if (!response.ok) {
break;
}
const data = (await response.json()) as {
value?: { id: string }[];
nextLink?: string | null;
};
for (const item of data.value ?? []) {
const name = item.id.split("/").pop();
if (name) {
names.push(name);
}
}
url = data.nextLink ?? null;
}
return names.slice(0, 200);
},
};