| | | 1 | | import { GetParametersCommand, type SSMClient } from '@aws-sdk/client-ssm'; |
| | | 2 | | import type { ISecretProvider } from '../../domain/ports/secret-provider.js'; |
| | | 3 | | |
| | 2 | 4 | | const SSM_BATCH_SIZE = 10; |
| | | 5 | | |
| | | 6 | | /** |
| | | 7 | | * {@link ISecretProvider} backed by AWS SSM Parameter Store. |
| | | 8 | | * |
| | | 9 | | * Parameters are retrieved with decryption enabled so that |
| | | 10 | | * SecureString values are returned in plain text. |
| | | 11 | | * |
| | | 12 | | * SSM supports fetching up to 10 parameters per request, |
| | | 13 | | * so names are chunked into batches automatically. |
| | | 14 | | */ |
| | | 15 | | export class AwsSsmSecretProvider implements ISecretProvider { |
| | | 16 | | private readonly ssmClient: SSMClient; |
| | | 17 | | |
| | | 18 | | constructor(ssmClient: SSMClient) { |
| | 10 | 19 | | if (!ssmClient) { |
| | 1 | 20 | | throw new Error('ssmClient cannot be null'); |
| | | 21 | | } |
| | 9 | 22 | | this.ssmClient = ssmClient; |
| | | 23 | | } |
| | | 24 | | |
| | | 25 | | async getSecrets(names: string[]): Promise<Map<string, string>> { |
| | 10 | 26 | | const result = new Map<string, string>(); |
| | 10 | 27 | | if (names.length === 0) { |
| | 1 | 28 | | return result; |
| | | 29 | | } |
| | | 30 | | |
| | 9 | 31 | | for (const name of names) { |
| | 23 | 32 | | if (!name?.trim()) { |
| | 1 | 33 | | throw new Error('Secret name cannot be null or whitespace'); |
| | | 34 | | } |
| | | 35 | | } |
| | | 36 | | |
| | 8 | 37 | | for (let i = 0; i < names.length; i += SSM_BATCH_SIZE) { |
| | 9 | 38 | | const batch = names.slice(i, i + SSM_BATCH_SIZE); |
| | 9 | 39 | | const response = await this.ssmClient.send( |
| | | 40 | | new GetParametersCommand({ |
| | | 41 | | Names: batch, |
| | | 42 | | WithDecryption: true, |
| | | 43 | | }), |
| | | 44 | | ); |
| | | 45 | | |
| | 7 | 46 | | for (const param of response.Parameters ?? []) { |
| | 5 | 47 | | if (param.Name && param.Value != null) { |
| | 5 | 48 | | result.set(param.Name, param.Value); |
| | | 49 | | } |
| | | 50 | | } |
| | | 51 | | } |
| | | 52 | | |
| | 6 | 53 | | return result; |
| | | 54 | | } |
| | | 55 | | } |