Created
July 20, 2026 14:15
-
-
Save dreamorosi/6638540e996268a9c6dbae8da0f76fdd to your computer and use it in GitHub Desktop.
AWS SDK-free SSM Parameters provider using AWS Lambda Powertools Signer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { BaseProvider } from '@aws-lambda-powertools/parameters/base'; | |
| import type { | |
| GetMultipleOptionsInterface, | |
| GetOptionsInterface, | |
| } from '@aws-lambda-powertools/parameters/base/types'; | |
| import { createSignedFetcher } from '@aws-lambda-powertools/signer/fetch'; | |
| import { SigV4Signer } from '@aws-lambda-powertools/signer/sigv4'; | |
| type SsmParameter = { | |
| Name?: string; | |
| Value?: string; | |
| }; | |
| type GetParameterResponse = { | |
| Parameter?: SsmParameter; | |
| }; | |
| type GetParametersByPathResponse = { | |
| NextToken?: string; | |
| Parameters?: SsmParameter[]; | |
| }; | |
| type SsmErrorResponse = { | |
| Message?: string; | |
| __type?: string; | |
| code?: string; | |
| message?: string; | |
| }; | |
| interface SignedSsmGetOptions extends GetOptionsInterface { | |
| decrypt?: boolean; | |
| } | |
| interface SignedSsmGetMultipleOptions extends GetMultipleOptionsInterface { | |
| decrypt?: boolean; | |
| maxResults?: number; | |
| recursive?: boolean; | |
| } | |
| class SsmServiceError extends Error { | |
| readonly code: string; | |
| readonly statusCode: number; | |
| constructor(code: string, message: string, statusCode: number) { | |
| super(message); | |
| this.name = 'SsmServiceError'; | |
| this.code = code; | |
| this.statusCode = statusCode; | |
| } | |
| } | |
| class SignedSsmProvider extends BaseProvider { | |
| readonly #endpoint: string; | |
| readonly #fetch: typeof fetch; | |
| constructor(options: { endpoint?: string; region?: string } = {}) { | |
| super({}); | |
| const region = options.region ?? process.env.AWS_REGION; | |
| if (!region) throw new Error('AWS_REGION or options.region is required'); | |
| const dnsSuffix = region.startsWith('cn-') | |
| ? 'amazonaws.com.cn' | |
| : 'amazonaws.com'; | |
| this.#endpoint = options.endpoint ?? `https://ssm.${region}.${dnsSuffix}/`; | |
| this.#fetch = createSignedFetcher( | |
| new SigV4Signer({ region, service: 'ssm' }) | |
| ); | |
| } | |
| public override get( | |
| name: string, | |
| options?: SignedSsmGetOptions | |
| ): Promise<unknown> { | |
| return super.get(name, options); | |
| } | |
| public override getMultiple( | |
| path: string, | |
| options?: SignedSsmGetMultipleOptions | |
| ): Promise<unknown> { | |
| return super.getMultiple(path, options); | |
| } | |
| protected async _get( | |
| name: string, | |
| options?: SignedSsmGetOptions | |
| ): Promise<string | undefined> { | |
| try { | |
| const response = await this.#request<GetParameterResponse>( | |
| 'GetParameter', | |
| { | |
| Name: name, | |
| WithDecryption: options?.decrypt ?? false, | |
| } | |
| ); | |
| return response.Parameter?.Value; | |
| } catch (error) { | |
| if ( | |
| options?.throwOnMissing && | |
| error instanceof SsmServiceError && | |
| error.code === 'ParameterNotFound' | |
| ) { | |
| return undefined; | |
| } | |
| throw error; | |
| } | |
| } | |
| protected async _getMultiple( | |
| path: string, | |
| options?: SignedSsmGetMultipleOptions | |
| ): Promise<Record<string, string | undefined>> { | |
| const parameters: Record<string, string | undefined> = {}; | |
| let nextToken: string | undefined; | |
| do { | |
| const response: GetParametersByPathResponse = | |
| await this.#request<GetParametersByPathResponse>( | |
| 'GetParametersByPath', | |
| { | |
| MaxResults: options?.maxResults, | |
| NextToken: nextToken, | |
| Path: path, | |
| Recursive: options?.recursive ?? false, | |
| WithDecryption: options?.decrypt ?? false, | |
| } | |
| ); | |
| for (const parameter of response.Parameters ?? []) { | |
| if (!parameter.Name) continue; | |
| let name = parameter.Name.replace(path, ''); | |
| if (name.startsWith('/')) name = name.slice(1); | |
| parameters[name] = parameter.Value; | |
| } | |
| nextToken = response.NextToken; | |
| } while (nextToken); | |
| return parameters; | |
| } | |
| async #request<T>( | |
| operation: string, | |
| input: Record<string, unknown> | |
| ): Promise<T> { | |
| const body = JSON.stringify( | |
| Object.fromEntries( | |
| Object.entries(input).filter(([, value]) => value !== undefined) | |
| ) | |
| ); | |
| const response = await this.#fetch(this.#endpoint, { | |
| method: 'POST', | |
| headers: { | |
| 'content-type': 'application/x-amz-json-1.1', | |
| 'x-amz-target': `AmazonSSM.${operation}`, | |
| }, | |
| body, | |
| }); | |
| const payload = (await response.json()) as T | SsmErrorResponse; | |
| if (!response.ok) { | |
| const error = payload as SsmErrorResponse; | |
| const rawCode = error.__type ?? error.code ?? 'UnknownError'; | |
| const code = rawCode.slice(rawCode.lastIndexOf('#') + 1); | |
| const message = | |
| error.message ?? | |
| error.Message ?? | |
| `SSM ${operation} failed with HTTP ${response.status}`; | |
| throw new SsmServiceError(code, `${code}: ${message}`, response.status); | |
| } | |
| return payload as T; | |
| } | |
| } | |
| export { SignedSsmProvider }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment