mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-20 07:31:37 +00:00
Merge pull request #5266 from its-felix/codex/5265-aws-parameter-store
feat(vault): add AWS Parameter Store provider
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
type HasInput = { input: Record<string, unknown> };
|
||||
|
||||
const findMany = vi.hoisted(() => vi.fn());
|
||||
|
||||
const {
|
||||
send,
|
||||
paginate,
|
||||
SSMClient,
|
||||
GetParametersCommand,
|
||||
DescribeParametersCommand,
|
||||
} = vi.hoisted(() => {
|
||||
class FakeCommand {
|
||||
input: Record<string, unknown>;
|
||||
constructor(input: Record<string, unknown>) {
|
||||
this.input = input;
|
||||
}
|
||||
}
|
||||
const send = vi.fn();
|
||||
const paginate = vi.fn();
|
||||
class SSMClient {
|
||||
send(command: unknown) {
|
||||
return send(command);
|
||||
}
|
||||
}
|
||||
return {
|
||||
send,
|
||||
paginate,
|
||||
SSMClient,
|
||||
GetParametersCommand: class extends FakeCommand {},
|
||||
DescribeParametersCommand: class extends FakeCommand {},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@aws-sdk/client-ssm", () => ({
|
||||
SSMClient,
|
||||
GetParametersCommand,
|
||||
DescribeParametersCommand,
|
||||
paginateDescribeParameters: paginate,
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/db", () => ({
|
||||
db: {
|
||||
query: {
|
||||
vaultProvider: {
|
||||
findMany: (...args: unknown[]) => findMany(...args),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
import { resolveVaultReferences } from "@dokploy/server/utils/vault";
|
||||
import { awsParameterStoreClient } from "@dokploy/server/utils/vault/aws-parameter-store";
|
||||
|
||||
const config = {
|
||||
providerType: "aws-parameter-store" as const,
|
||||
region: "eu-central-1",
|
||||
accessKeyId: "AKIA_TEST",
|
||||
secretAccessKey: "secret",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
send.mockReset();
|
||||
paginate.mockReset();
|
||||
findMany.mockReset();
|
||||
});
|
||||
|
||||
describe("awsParameterStoreClient", () => {
|
||||
it("decrypts parameters in batches of ten and preserves selectors", async () => {
|
||||
const refs = [
|
||||
"/prod/database:CURRENT",
|
||||
...Array.from({ length: 10 }, (_, index) => `/prod/secret-${index}`),
|
||||
];
|
||||
send.mockImplementation(async (command: HasInput) => ({
|
||||
Parameters: (command.input.Names as string[]).map((ref) => {
|
||||
if (ref === "/prod/database:CURRENT") {
|
||||
return {
|
||||
Name: "/prod/database",
|
||||
Selector: ":CURRENT",
|
||||
Value: "selected-value",
|
||||
};
|
||||
}
|
||||
return { Name: ref, Value: `value-for-${ref}` };
|
||||
}),
|
||||
}));
|
||||
|
||||
const result = await awsParameterStoreClient.getSecrets(config, refs);
|
||||
|
||||
expect(send).toHaveBeenCalledTimes(2);
|
||||
expect((send.mock.calls[0]?.[0] as HasInput).input).toMatchObject({
|
||||
WithDecryption: true,
|
||||
});
|
||||
expect(
|
||||
((send.mock.calls[0]?.[0] as HasInput).input.Names as string[]).length,
|
||||
).toBe(10);
|
||||
expect(
|
||||
((send.mock.calls[1]?.[0] as HasInput).input.Names as string[]).length,
|
||||
).toBe(1);
|
||||
expect(result["/prod/database:CURRENT"]).toBe("selected-value");
|
||||
expect(result["/prod/secret-9"]).toBe("value-for-/prod/secret-9");
|
||||
});
|
||||
|
||||
it("reports a missing parameter without exposing other values", async () => {
|
||||
send.mockResolvedValue({ Parameters: [], InvalidParameters: ["/missing"] });
|
||||
|
||||
await expect(
|
||||
awsParameterStoreClient.getSecrets(config, ["/missing"]),
|
||||
).rejects.toThrow('AWS Parameter Store: parameter "/missing" not found');
|
||||
});
|
||||
|
||||
it("tests the connection within the configured hierarchy", async () => {
|
||||
send.mockResolvedValue({ Parameters: [] });
|
||||
|
||||
await awsParameterStoreClient.testConnection({
|
||||
...config,
|
||||
parameterPath: "/production/my-app/",
|
||||
});
|
||||
|
||||
expect(send).toHaveBeenCalledTimes(1);
|
||||
expect((send.mock.calls[0]?.[0] as HasInput).input).toEqual({
|
||||
ParameterFilters: [
|
||||
{
|
||||
Key: "Path",
|
||||
Option: "Recursive",
|
||||
Values: ["/production/my-app"],
|
||||
},
|
||||
],
|
||||
MaxResults: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("explains the discovery permission when connection testing is denied", async () => {
|
||||
const error = new Error("not authorized");
|
||||
error.name = "AccessDeniedException";
|
||||
send.mockRejectedValue(error);
|
||||
|
||||
await expect(
|
||||
awsParameterStoreClient.testConnection(config),
|
||||
).rejects.toThrow("ssm:DescribeParameters");
|
||||
});
|
||||
|
||||
it("lists parameter names across pages within the configured hierarchy", async () => {
|
||||
paginate.mockReturnValue(
|
||||
(async function* () {
|
||||
yield { Parameters: [{ Name: "/prod/db" }] };
|
||||
yield { Parameters: [{ Name: "/prod/api" }] };
|
||||
})(),
|
||||
);
|
||||
|
||||
const names = await awsParameterStoreClient.listSecretNames?.({
|
||||
...config,
|
||||
parameterPath: " /production/my-app/ ",
|
||||
});
|
||||
|
||||
expect(names).toEqual(["/prod/db", "/prod/api"]);
|
||||
expect(paginate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pageSize: 50 }),
|
||||
{
|
||||
ParameterFilters: [
|
||||
{
|
||||
Key: "Path",
|
||||
Option: "Recursive",
|
||||
Values: ["/production/my-app"],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves a vault reference through the registered provider", async () => {
|
||||
findMany.mockResolvedValue([
|
||||
{
|
||||
name: "ssm-prod",
|
||||
providerType: "aws-parameter-store",
|
||||
config,
|
||||
assignments: [{ projectId: "project-1", environmentIds: [] }],
|
||||
},
|
||||
]);
|
||||
send.mockResolvedValue({
|
||||
Parameters: [{ Name: "/prod/database-password", Value: "resolved" }],
|
||||
});
|
||||
|
||||
const result = await resolveVaultReferences(
|
||||
"DB_PASSWORD=${{vault.ssm-prod./prod/database-password}}",
|
||||
{
|
||||
organizationId: "organization-1",
|
||||
projectId: "project-1",
|
||||
environmentId: "environment-1",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toBe("DB_PASSWORD=resolved");
|
||||
});
|
||||
});
|
||||
@@ -40,6 +40,7 @@ const providerLabels = {
|
||||
hashicorp: "HashiCorp Vault / OpenBao",
|
||||
infisical: "Infisical",
|
||||
aws: "AWS Secrets Manager",
|
||||
"aws-parameter-store": "AWS Parameter Store",
|
||||
doppler: "Doppler",
|
||||
azure: "Azure Key Vault",
|
||||
scaleway: "Scaleway Secret Manager",
|
||||
@@ -61,6 +62,7 @@ const VaultProviderSchema = z
|
||||
"hashicorp",
|
||||
"infisical",
|
||||
"aws",
|
||||
"aws-parameter-store",
|
||||
"doppler",
|
||||
"azure",
|
||||
"scaleway",
|
||||
@@ -79,6 +81,7 @@ const VaultProviderSchema = z
|
||||
region: z.string(),
|
||||
accessKeyId: z.string(),
|
||||
secretAccessKey: z.string(),
|
||||
parameterPath: z.string(),
|
||||
serviceToken: z.string(),
|
||||
awsEndpoint: z.string(),
|
||||
vaultUri: z.string(),
|
||||
@@ -136,7 +139,8 @@ const VaultProviderSchema = z
|
||||
});
|
||||
}
|
||||
if (
|
||||
data.providerType === "aws" &&
|
||||
(data.providerType === "aws" ||
|
||||
data.providerType === "aws-parameter-store") &&
|
||||
data.awsEndpoint &&
|
||||
!isValidUrl(data.awsEndpoint)
|
||||
) {
|
||||
@@ -146,6 +150,17 @@ const VaultProviderSchema = z
|
||||
path: ["awsEndpoint"],
|
||||
});
|
||||
}
|
||||
if (
|
||||
data.providerType === "aws-parameter-store" &&
|
||||
data.parameterPath &&
|
||||
!data.parameterPath.trim().startsWith("/")
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Parameter discovery path must start with /",
|
||||
path: ["parameterPath"],
|
||||
});
|
||||
}
|
||||
if (
|
||||
data.providerType === "scaleway" &&
|
||||
data.scalewayApiUrl &&
|
||||
@@ -200,6 +215,11 @@ const VaultProviderSchema = z
|
||||
["accessKeyId", "Access Key ID is required"],
|
||||
["secretAccessKey", "Secret Access Key is required"],
|
||||
],
|
||||
"aws-parameter-store": [
|
||||
["region", "Region is required"],
|
||||
["accessKeyId", "Access Key ID is required"],
|
||||
["secretAccessKey", "Secret Access Key is required"],
|
||||
],
|
||||
doppler: [["serviceToken", "Service Token is required"]],
|
||||
azure: [
|
||||
["vaultUri", "Vault URI is required"],
|
||||
@@ -265,6 +285,7 @@ const defaultValues: VaultProviderForm = {
|
||||
region: "",
|
||||
accessKeyId: "",
|
||||
secretAccessKey: "",
|
||||
parameterPath: "",
|
||||
serviceToken: "",
|
||||
awsEndpoint: "",
|
||||
vaultUri: "",
|
||||
@@ -313,6 +334,15 @@ const buildConfig = (data: VaultProviderForm) => {
|
||||
secretAccessKey: data.secretAccessKey,
|
||||
endpoint: data.awsEndpoint || undefined,
|
||||
};
|
||||
case "aws-parameter-store":
|
||||
return {
|
||||
providerType: "aws-parameter-store" as const,
|
||||
region: data.region,
|
||||
accessKeyId: data.accessKeyId,
|
||||
secretAccessKey: data.secretAccessKey,
|
||||
endpoint: data.awsEndpoint || undefined,
|
||||
parameterPath: data.parameterPath.trim() || undefined,
|
||||
};
|
||||
case "doppler":
|
||||
return {
|
||||
providerType: "doppler" as const,
|
||||
@@ -443,12 +473,16 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
|
||||
environmentSlug: provider.config.environmentSlug,
|
||||
secretPath: provider.config.secretPath,
|
||||
}),
|
||||
...(provider.config.providerType === "aws" && {
|
||||
...((provider.config.providerType === "aws" ||
|
||||
provider.config.providerType === "aws-parameter-store") && {
|
||||
region: provider.config.region,
|
||||
accessKeyId: provider.config.accessKeyId,
|
||||
secretAccessKey: provider.config.secretAccessKey,
|
||||
awsEndpoint: provider.config.endpoint ?? "",
|
||||
}),
|
||||
...(provider.config.providerType === "aws-parameter-store" && {
|
||||
parameterPath: provider.config.parameterPath ?? "",
|
||||
}),
|
||||
...(provider.config.providerType === "doppler" && {
|
||||
serviceToken: provider.config.serviceToken,
|
||||
dopplerProject: provider.config.project ?? "",
|
||||
@@ -757,7 +791,8 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{providerType === "aws" && (
|
||||
{(providerType === "aws" ||
|
||||
providerType === "aws-parameter-store") && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -815,12 +850,44 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormDescription>
|
||||
Reference format:{" "}
|
||||
<code>{"${{vault.<name>.secret-name}}"}</code> or{" "}
|
||||
<code>{"${{vault.<name>.secret-name:field}}"}</code> for JSON
|
||||
secrets
|
||||
</FormDescription>
|
||||
{providerType === "aws-parameter-store" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="parameterPath"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Discovery path (optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="/production/my-app" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Limits parameter browsing and autocomplete to this
|
||||
hierarchy. IAM permissions remain the security
|
||||
boundary.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{providerType === "aws" ? (
|
||||
<FormDescription>
|
||||
Reference format:{" "}
|
||||
<code>{"${{vault.<name>.secret-name}}"}</code> or{" "}
|
||||
<code>{"${{vault.<name>.secret-name:field}}"}</code> for
|
||||
JSON secrets
|
||||
</FormDescription>
|
||||
) : (
|
||||
<FormDescription>
|
||||
Reference format:{" "}
|
||||
<code>{"${{vault.<name>./path/to/parameter}}"}</code>.
|
||||
SecureString parameters are decrypted automatically.
|
||||
Resolution requires <code>ssm:GetParameters</code>.
|
||||
Discovery requires <code>ssm:DescribeParameters</code>.
|
||||
Customer-managed KMS keys also require{" "}
|
||||
<code>kms:Decrypt</code>.
|
||||
</FormDescription>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ const providerLabels: Record<string, string> = {
|
||||
hashicorp: "HashiCorp Vault",
|
||||
infisical: "Infisical",
|
||||
aws: "AWS Secrets Manager",
|
||||
"aws-parameter-store": "AWS Parameter Store",
|
||||
doppler: "Doppler",
|
||||
azure: "Azure Key Vault",
|
||||
scaleway: "Scaleway Secret Manager",
|
||||
|
||||
@@ -608,6 +608,7 @@ export const vaultProviderIcons = {
|
||||
hashicorp: HashicorpVaultIcon,
|
||||
infisical: InfisicalIcon,
|
||||
aws: AwsIcon,
|
||||
"aws-parameter-store": AwsIcon,
|
||||
doppler: DopplerIcon,
|
||||
azure: AzureIcon,
|
||||
scaleway: ScalewayIcon,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TYPE "public"."VaultProviderType" ADD VALUE 'aws-parameter-store' BEFORE 'doppler';
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1352,6 +1352,13 @@
|
||||
"when": 1788600069255,
|
||||
"tag": "0192_light_lake",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 193,
|
||||
"version": "7",
|
||||
"when": 1788802431830,
|
||||
"tag": "0193_chemical_the_liberteens",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -50,6 +50,7 @@
|
||||
"@ai-sdk/openai-compatible": "^2.0.30",
|
||||
"@aws-sdk/client-route-53": "^3.1108.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.1108.0",
|
||||
"@aws-sdk/client-ssm": "3.1108.0",
|
||||
"@better-auth/api-key": "1.6.23",
|
||||
"@better-auth/passkey": "1.6.23",
|
||||
"@better-auth/scim": "1.6.23",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"@ai-sdk/openai-compatible": "^2.0.30",
|
||||
"@aws-sdk/client-route-53": "^3.1108.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.1108.0",
|
||||
"@aws-sdk/client-ssm": "3.1108.0",
|
||||
"@better-auth/api-key": "1.6.23",
|
||||
"@better-auth/passkey": "1.6.23",
|
||||
"@better-auth/scim": "1.6.23",
|
||||
|
||||
@@ -8,6 +8,7 @@ export const vaultProviderType = pgEnum("VaultProviderType", [
|
||||
"hashicorp",
|
||||
"infisical",
|
||||
"aws",
|
||||
"aws-parameter-store",
|
||||
"doppler",
|
||||
"azure",
|
||||
"scaleway",
|
||||
@@ -40,6 +41,21 @@ export const awsVaultConfigSchema = z.object({
|
||||
endpoint: z.string().url().optional(),
|
||||
});
|
||||
|
||||
export const awsParameterStoreVaultConfigSchema = z.object({
|
||||
providerType: z.literal("aws-parameter-store"),
|
||||
region: z.string().min(1),
|
||||
accessKeyId: z.string().min(1),
|
||||
secretAccessKey: z.string().min(1),
|
||||
endpoint: z.string().url().optional(),
|
||||
parameterPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((path) => path === "" || path.startsWith("/"), {
|
||||
message: "Parameter discovery path must start with /",
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const dopplerVaultConfigSchema = z.object({
|
||||
providerType: z.literal("doppler"),
|
||||
serviceToken: z.string().min(1),
|
||||
@@ -76,6 +92,7 @@ export const vaultProviderConfigSchema = z.discriminatedUnion("providerType", [
|
||||
hashicorpVaultConfigSchema,
|
||||
infisicalVaultConfigSchema,
|
||||
awsVaultConfigSchema,
|
||||
awsParameterStoreVaultConfigSchema,
|
||||
dopplerVaultConfigSchema,
|
||||
azureVaultConfigSchema,
|
||||
scalewayVaultConfigSchema,
|
||||
|
||||
@@ -20,6 +20,7 @@ const SENSITIVE_FIELDS: Record<VaultProviderConfig["providerType"], string[]> =
|
||||
hashicorp: ["token"],
|
||||
infisical: ["clientSecret"],
|
||||
aws: ["secretAccessKey"],
|
||||
"aws-parameter-store": ["secretAccessKey"],
|
||||
doppler: ["serviceToken"],
|
||||
azure: ["clientSecret"],
|
||||
scaleway: ["secretKey"],
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import {
|
||||
DescribeParametersCommand,
|
||||
GetParametersCommand,
|
||||
paginateDescribeParameters,
|
||||
SSMClient,
|
||||
} from "@aws-sdk/client-ssm";
|
||||
import type { awsParameterStoreVaultConfigSchema } from "@dokploy/server/db/schema";
|
||||
import type { z } from "zod";
|
||||
import type { VaultClient } from "./types";
|
||||
|
||||
type AwsParameterStoreConfig = z.infer<
|
||||
typeof awsParameterStoreVaultConfigSchema
|
||||
>;
|
||||
|
||||
const MAX_PARAMETERS_PER_REQUEST = 10;
|
||||
|
||||
const normalizeParameterPath = (path: string | undefined) => {
|
||||
const trimmed = path?.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
if (trimmed === "/") {
|
||||
return trimmed;
|
||||
}
|
||||
return trimmed.replace(/\/+$/, "");
|
||||
};
|
||||
|
||||
const describeParametersInput = (config: AwsParameterStoreConfig) => {
|
||||
const parameterPath = normalizeParameterPath(config.parameterPath);
|
||||
return parameterPath
|
||||
? {
|
||||
ParameterFilters: [
|
||||
{
|
||||
Key: "Path",
|
||||
Option: "Recursive",
|
||||
Values: [parameterPath],
|
||||
},
|
||||
],
|
||||
}
|
||||
: {};
|
||||
};
|
||||
|
||||
const isAccessDeniedError = (error: unknown) =>
|
||||
error instanceof Error &&
|
||||
(error.name === "AccessDeniedException" || error.name === "AccessDenied");
|
||||
|
||||
const createClient = (config: AwsParameterStoreConfig) =>
|
||||
new SSMClient({
|
||||
region: config.region,
|
||||
credentials: {
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
...(config.endpoint && { endpoint: config.endpoint }),
|
||||
});
|
||||
|
||||
const findRequestedRef = (
|
||||
refs: string[],
|
||||
parameter: { Name?: string; ARN?: string; Selector?: string },
|
||||
) => {
|
||||
const bases = [parameter.Name, parameter.ARN].filter(
|
||||
(value): value is string => Boolean(value),
|
||||
);
|
||||
if (!parameter.Selector) {
|
||||
return refs.find((ref) => bases.includes(ref));
|
||||
}
|
||||
|
||||
const selector = parameter.Name
|
||||
? parameter.Selector.replace(`${parameter.Name}:`, "").replace(/^:/, "")
|
||||
: parameter.Selector.replace(/^:/, "");
|
||||
return refs.find((ref) =>
|
||||
bases.some((base) => ref === `${base}:${selector}`),
|
||||
);
|
||||
};
|
||||
|
||||
export const awsParameterStoreClient: VaultClient<AwsParameterStoreConfig> = {
|
||||
async getSecrets(config, refs) {
|
||||
const client = createClient(config);
|
||||
const uniqueRefs = [...new Set(refs)];
|
||||
const result: Record<string, string> = {};
|
||||
|
||||
for (
|
||||
let index = 0;
|
||||
index < uniqueRefs.length;
|
||||
index += MAX_PARAMETERS_PER_REQUEST
|
||||
) {
|
||||
const batch = uniqueRefs.slice(index, index + MAX_PARAMETERS_PER_REQUEST);
|
||||
const response = await client.send(
|
||||
new GetParametersCommand({
|
||||
Names: batch,
|
||||
WithDecryption: true,
|
||||
}),
|
||||
);
|
||||
|
||||
for (const parameter of response.Parameters ?? []) {
|
||||
const ref = findRequestedRef(batch, parameter);
|
||||
if (!ref) {
|
||||
continue;
|
||||
}
|
||||
if (parameter.Value === undefined) {
|
||||
throw new Error(
|
||||
`AWS Parameter Store: parameter "${ref}" has no value`,
|
||||
);
|
||||
}
|
||||
result[ref] = parameter.Value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const ref of uniqueRefs) {
|
||||
if (result[ref] === undefined) {
|
||||
throw new Error(`AWS Parameter Store: parameter "${ref}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
async testConnection(config) {
|
||||
const client = createClient(config);
|
||||
try {
|
||||
await client.send(
|
||||
new DescribeParametersCommand({
|
||||
...describeParametersInput(config),
|
||||
MaxResults: 1,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (isAccessDeniedError(error)) {
|
||||
throw new Error(
|
||||
"AWS Parameter Store: credentials were accepted, but connection testing and parameter discovery require ssm:DescribeParameters. Manual references can still work when ssm:GetParameters is allowed.",
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async listSecretNames(config) {
|
||||
const client = createClient(config);
|
||||
const names: string[] = [];
|
||||
for await (const page of paginateDescribeParameters(
|
||||
{ client, pageSize: 50 },
|
||||
describeParametersInput(config),
|
||||
)) {
|
||||
for (const parameter of page.Parameters ?? []) {
|
||||
if (parameter.Name) {
|
||||
names.push(parameter.Name);
|
||||
}
|
||||
if (names.length >= 500) {
|
||||
return names;
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
},
|
||||
};
|
||||
Binary file not shown.
Generated
+21
@@ -121,6 +121,9 @@ importers:
|
||||
'@aws-sdk/client-secrets-manager':
|
||||
specifier: ^3.1108.0
|
||||
version: 3.1108.0
|
||||
'@aws-sdk/client-ssm':
|
||||
specifier: 3.1108.0
|
||||
version: 3.1108.0
|
||||
'@better-auth/api-key':
|
||||
specifier: 1.6.23
|
||||
version: 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.3.7(zod@4.3.6))(jose@6.1.3)(kysely@0.29.3)(nanostores@1.1.1))(@better-auth/utils@0.4.2)(better-auth@1.6.23(22e2e61d74361da37f96c4dc24795f27))(better-call@1.3.7(zod@4.3.6))
|
||||
@@ -597,6 +600,9 @@ importers:
|
||||
'@aws-sdk/client-secrets-manager':
|
||||
specifier: ^3.1108.0
|
||||
version: 3.1108.0
|
||||
'@aws-sdk/client-ssm':
|
||||
specifier: 3.1108.0
|
||||
version: 3.1108.0
|
||||
'@better-auth/api-key':
|
||||
specifier: 1.6.23
|
||||
version: 1.6.23(@better-auth/core@1.6.23(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.0)(better-call@1.3.7(zod@4.3.6))(jose@6.1.3)(kysely@0.29.3)(nanostores@1.1.1))(@better-auth/utils@0.4.2)(better-auth@1.6.23(2ed6fd84380fde286153435e0641a0ef))(better-call@1.3.7(zod@4.3.6))
|
||||
@@ -894,6 +900,10 @@ packages:
|
||||
resolution: {integrity: sha512-pZtoHWD+WorgM9xK1ikNcKLbtEMIS6wFtELuCX5AOo9v3ZajV8wOEmXz/7JnBHbsLmyBnyv3NCf7whyV++joiw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/client-ssm@3.1108.0':
|
||||
resolution: {integrity: sha512-9bKhMapsv17V2lrC/HtjhfqH72EZOWhdJKod8JkITHALLwdCKgsnnOZ1JPWB1bCMx0hWMW5OtQKdyHjGobE4NQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/core@3.977.7':
|
||||
resolution: {integrity: sha512-I88Iov89NVmjSmJLKSv7Cn9M2J+a2942OkA8nZCbz+sl4ZeY4zEOcoLOrbt1GRfQ8zEQKnjAJdXixA3J/p1fDQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -9309,6 +9319,17 @@ snapshots:
|
||||
'@smithy/types': 4.17.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/client-ssm@3.1108.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.977.7
|
||||
'@aws-sdk/credential-provider-node': 3.972.79
|
||||
'@aws-sdk/types': 3.974.3
|
||||
'@smithy/core': 3.32.0
|
||||
'@smithy/fetch-http-handler': 5.7.0
|
||||
'@smithy/node-http-handler': 4.10.0
|
||||
'@smithy/types': 4.17.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/core@3.977.7':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.974.3
|
||||
|
||||
Reference in New Issue
Block a user