From 8bf1d35e28984e46face75fdaef40dd160de7390 Mon Sep 17 00:00:00 2001 From: Steakley Date: Wed, 4 Jan 2023 12:34:24 -0800 Subject: [PATCH 1/7] use aws js sdk for credentials concerning aws --- frontend/server/aws-helper.test.ts | 94 +- frontend/server/aws-helper.ts | 107 --- frontend/server/handlers/artifacts.ts | 11 +- frontend/server/minio-helper.test.ts | 29 - frontend/server/minio-helper.ts | 30 +- frontend/server/package-lock.json | 1272 +++++++++++++++++++++++++ frontend/server/package.json | 1 + 7 files changed, 1292 insertions(+), 252 deletions(-) diff --git a/frontend/server/aws-helper.test.ts b/frontend/server/aws-helper.test.ts index 66a22ac66bd..48e59ad11f8 100644 --- a/frontend/server/aws-helper.test.ts +++ b/frontend/server/aws-helper.test.ts @@ -11,99 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import fetch from 'node-fetch'; -import { awsInstanceProfileCredentials, isS3Endpoint } from './aws-helper'; - -// mock node-fetch module -jest.mock('node-fetch'); - -function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -beforeEach(() => { - awsInstanceProfileCredentials.reset(); - jest.clearAllMocks(); -}); - -describe('awsInstanceProfileCredentials', () => { - const mockedFetch: jest.Mock = fetch as any; - - describe('getCredentials', () => { - it('retrieves, caches, and refreshes the AWS EC2 instance profile and session credentials everytime it is called.', async () => { - let count = 0; - const expectedCredentials = [ - { - AccessKeyId: 'AccessKeyId', - Code: 'Success', - Expiration: new Date(Date.now() + 1000).toISOString(), // expires 1 sec later - LastUpdated: '2019-12-17T10:55:38Z', - SecretAccessKey: 'SecretAccessKey', - Token: 'SessionToken', - Type: 'AWS-HMAC', - }, - { - AccessKeyId: 'AccessKeyId2', - Code: 'Success', - Expiration: new Date(Date.now() + 10000).toISOString(), // expires 10 sec later - LastUpdated: '2019-12-17T10:55:38Z', - SecretAccessKey: 'SecretAccessKey2', - Token: 'SessionToken2', - Type: 'AWS-HMAC', - }, - ]; - const mockFetch = (url: string) => { - if (url === 'http://169.254.169.254/latest/meta-data/iam/security-credentials/') { - return Promise.resolve({ text: () => Promise.resolve('some_iam_role') }); - } - return Promise.resolve({ - json: () => Promise.resolve(expectedCredentials[count++]), - }); - }; - mockedFetch.mockImplementation(mockFetch); - - // expect to get cred from ec2 instance metadata store - expect(await awsInstanceProfileCredentials.getCredentials()).toBe(expectedCredentials[0]); - // expect to call once for profile name, and once for credential - expect(mockedFetch.mock.calls.length).toBe(2); - // expect to get same credential as it has not expire - expect(await awsInstanceProfileCredentials.getCredentials()).toBe(expectedCredentials[0]); - // expect to not to have any more calls - expect(mockedFetch.mock.calls.length).toBe(2); - // let credential expire - await sleep(1500); - // expect to get new cred as old one expire - expect(await awsInstanceProfileCredentials.getCredentials()).toBe(expectedCredentials[1]); - // expect to get same cred as it has not expire - expect(await awsInstanceProfileCredentials.getCredentials()).toBe(expectedCredentials[1]); - }); - - it('fails gracefully if there is no instance profile.', async () => { - const mockFetch = (url: string) => { - if (url === 'http://169.254.169.254/latest/meta-data/iam/security-credentials/') { - return Promise.resolve({ text: () => Promise.resolve('') }); - } - return Promise.reject('Unknown error'); - }; - mockedFetch.mockImplementation(mockFetch); - - expect(await awsInstanceProfileCredentials.ok()).toBeFalsy(); - expect(async () => await awsInstanceProfileCredentials.getCredentials()).not.toThrow(); - expect(await awsInstanceProfileCredentials.getCredentials()).toBeUndefined(); - }); - - it('fails gracefully if there is no metadata store.', async () => { - const mockFetch = (_: string) => { - return Promise.reject('Unknown error'); - }; - mockedFetch.mockImplementation(mockFetch); - - expect(await awsInstanceProfileCredentials.ok()).toBeFalsy(); - expect(async () => await awsInstanceProfileCredentials.getCredentials()).not.toThrow(); - expect(await awsInstanceProfileCredentials.getCredentials()).toBeUndefined(); - }); - }); -}); +import {isS3Endpoint } from './aws-helper'; describe('isS3Endpoint', () => { it('checks a valid s3 endpoint', () => { diff --git a/frontend/server/aws-helper.ts b/frontend/server/aws-helper.ts index 35a023d2ec8..35a0ae8d9ea 100644 --- a/frontend/server/aws-helper.ts +++ b/frontend/server/aws-helper.ts @@ -11,40 +11,6 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import fetch from 'node-fetch'; - -/** AWSMetadataCredentials describes the credentials provided by aws metadata store. */ -export interface AWSMetadataCredentials { - Code: string; - LastUpdated: string; - Type: string; - AccessKeyId: string; - SecretAccessKey: string; - Token: string; - Expiration: string; -} - -/** url for aws metadata store. */ -const metadataUrl = 'http://169.254.169.254/latest/meta-data'; - -/** - * Get the AWS IAM instance profile. - */ -async function getIAMInstanceProfile(): Promise { - try { - const resp = await fetch(`${metadataUrl}/iam/security-credentials/`); - const profiles = (await resp.text()).split('\n'); - if (profiles.length > 0) { - return profiles[0].trim(); // return first profile - } - return; - } catch (error) { - console.error( - `Unable to fetch credentials from AWS metadata store (${metadataUrl}/iam/security-credentials/): ${error}`, - ); - return; - } -} /** * Check if the provided string is an S3 endpoint (can be any region). @@ -54,76 +20,3 @@ async function getIAMInstanceProfile(): Promise { export function isS3Endpoint(endpoint: string = ''): boolean { return !!endpoint.match(/s3.{0,}\.amazonaws\.com\.?.{0,}/i); } - -/** - * Class to handle the session credentials for AWS ec2 instance profile. - */ -class AWSInstanceProfileCredentials { - _iamProfile?: string; - _credentials?: AWSMetadataCredentials; - _expiration: number = 0; - - /** reset all caches */ - reset() { - this._iamProfile = undefined; - this._credentials = undefined; - this._expiration = 0; - return this; - } - - /** - * EC2 Instance profile - */ - async profile() { - if (!this._iamProfile) { - try { - this._iamProfile = await getIAMInstanceProfile(); - } catch (err) { - console.error(err); - } - } - return this._iamProfile; - } - - /** - * Return true only if there is a metadata store and instance profile. - */ - async ok() { - try { - const profile = await this.profile(); - return profile && profile.length > 0; - } catch (_) { - return false; - } - } - - async _fetchCredentials(): Promise { - try { - const profile = await this.profile(); - const resp = await fetch(`${metadataUrl}/iam/security-credentials/${profile}`); - const credentials = await resp.json(); - return credentials; - } catch (error) { - console.error(`Unable to fetch credentials from AWS metadata store: ${error}`); - return; - } - } - - /** - * Get the AWS metadata store session credentials. - */ - async getCredentials(): Promise { - // query for credentials if going to expire or no credentials yet - if (Date.now() + 10 >= this._expiration || !this._credentials) { - this._credentials = await this._fetchCredentials(); - if (this._credentials && this._credentials.Expiration) { - this._expiration = new Date(this._credentials.Expiration).getTime(); - } else { - this._expiration = -1; // always retry - } - } - return this._credentials; - } -} - -export const awsInstanceProfileCredentials = new AWSInstanceProfileCredentials(); diff --git a/frontend/server/handlers/artifacts.ts b/frontend/server/handlers/artifacts.ts index e87ee444e44..5d185a62a92 100644 --- a/frontend/server/handlers/artifacts.ts +++ b/frontend/server/handlers/artifacts.ts @@ -95,7 +95,6 @@ export function getArtifactsHandler({ peek, )(req, res); break; - case 's3': getMinioArtifactHandler( { @@ -216,11 +215,11 @@ function getGCSArtifactHandler(options: { key: string; bucket: string }, peek: n // escapes everything else. const regex = new RegExp( '^' + - key - .split(/\*+/) - .map(escapeRegexChars) - .join('.*') + - '$', + key + .split(/\*+/) + .map(escapeRegexChars) + .join('.*') + + '$', ); return regex.test(f.name); }); diff --git a/frontend/server/minio-helper.test.ts b/frontend/server/minio-helper.test.ts index f4910c404c1..12926700547 100644 --- a/frontend/server/minio-helper.test.ts +++ b/frontend/server/minio-helper.test.ts @@ -14,12 +14,10 @@ import * as zlib from 'zlib'; import { PassThrough } from 'stream'; import { Client as MinioClient } from 'minio'; -import { awsInstanceProfileCredentials } from './aws-helper'; import { createMinioClient, isTarball, maybeTarball, getObjectStream } from './minio-helper'; import { V1beta1JobTemplateSpec } from '@kubernetes/client-node'; jest.mock('minio'); -jest.mock('./aws-helper'); describe('minio-helper', () => { const MockedMinioClient: jest.Mock = MinioClient as any; @@ -54,33 +52,6 @@ describe('minio-helper', () => { endPoint: 'minio.kubeflow:80', }); }); - - it('uses EC2 metadata credentials if access key are not provided.', async () => { - (awsInstanceProfileCredentials.getCredentials as jest.Mock).mockImplementation(() => - Promise.resolve({ - AccessKeyId: 'AccessKeyId', - Code: 'Success', - Expiration: new Date(Date.now() + 1000).toISOString(), // expires 1 sec later - LastUpdated: '2019-12-17T10:55:38Z', - SecretAccessKey: 'SecretAccessKey', - Token: 'SessionToken', - Type: 'AWS-HMAC', - }), - ); - (awsInstanceProfileCredentials.ok as jest.Mock).mockImplementation(() => - Promise.resolve(true), - ); - - const client = await createMinioClient({ endPoint: 's3.awsamazon.com' }); - - expect(client).toBeInstanceOf(MinioClient); - expect(MockedMinioClient).toHaveBeenCalledWith({ - accessKey: 'AccessKeyId', - endPoint: 's3.awsamazon.com', - secretKey: 'SecretAccessKey', - sessionToken: 'SessionToken', - }); - }); }); describe('isTarball', () => { diff --git a/frontend/server/minio-helper.ts b/frontend/server/minio-helper.ts index f3979fde85b..b7d0de78410 100644 --- a/frontend/server/minio-helper.ts +++ b/frontend/server/minio-helper.ts @@ -17,7 +17,7 @@ import * as tar from 'tar-stream'; import peek from 'peek-stream'; import gunzip from 'gunzip-maybe'; import { Client as MinioClient, ClientOptions as MinioClientOptions } from 'minio'; -import { awsInstanceProfileCredentials } from './aws-helper'; +const { fromNodeProviderChain } = require("@aws-sdk/credential-providers"); /** MinioRequestConfig describes the info required to retrieve an artifact. */ export interface MinioRequestConfig { @@ -38,23 +38,19 @@ export interface MinioClientOptionsWithOptionalSecrets extends Partial transformStream.push(buffer)); stream.on('end', () => { transformStream.emit('end'); diff --git a/frontend/server/package-lock.json b/frontend/server/package-lock.json index 3e74f29e960..ce487f9dfe1 100644 --- a/frontend/server/package-lock.json +++ b/frontend/server/package-lock.json @@ -2,6 +2,1260 @@ "requires": true, "lockfileVersion": 1, "dependencies": { + "@aws-crypto/ie11-detection": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", + "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==", + "requires": { + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", + "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", + "requires": { + "@aws-crypto/ie11-detection": "^2.0.0", + "@aws-crypto/sha256-js": "^2.0.0", + "@aws-crypto/supports-web-crypto": "^2.0.0", + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/sha256-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", + "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", + "requires": { + "@aws-crypto/util": "^2.0.0", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz", + "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==", + "requires": { + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz", + "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==", + "requires": { + "@aws-sdk/types": "^3.110.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-sdk/abort-controller": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.226.0.tgz", + "integrity": "sha512-cJVzr1xxPBd08voknXvR0RLgtZKGKt6WyDpH/BaPCu3rfSqWCDZKzwqe940eqosjmKrxC6pUZNKASIqHOQ8xxQ==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/client-cognito-identity": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.241.0.tgz", + "integrity": "sha512-9X/MwcnSwWfB0ijggFjyBWa4gtlUAyI39eBaVSE0AxMcgLlHKedEK6w5F1RrtvWqb7KyJDsyAysVecU4E9zQQQ==", + "requires": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/client-sts": "3.241.0", + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/credential-provider-node": "3.241.0", + "@aws-sdk/fetch-http-handler": "3.226.0", + "@aws-sdk/hash-node": "3.226.0", + "@aws-sdk/invalid-dependency": "3.226.0", + "@aws-sdk/middleware-content-length": "3.226.0", + "@aws-sdk/middleware-endpoint": "3.226.0", + "@aws-sdk/middleware-host-header": "3.226.0", + "@aws-sdk/middleware-logger": "3.226.0", + "@aws-sdk/middleware-recursion-detection": "3.226.0", + "@aws-sdk/middleware-retry": "3.235.0", + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/middleware-signing": "3.226.0", + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/middleware-user-agent": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/node-http-handler": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/smithy-client": "3.234.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.208.0", + "@aws-sdk/util-defaults-mode-browser": "3.234.0", + "@aws-sdk/util-defaults-mode-node": "3.234.0", + "@aws-sdk/util-endpoints": "3.241.0", + "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/util-user-agent-browser": "3.226.0", + "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.208.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/client-sso": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.241.0.tgz", + "integrity": "sha512-Jm4HR+RYAqKMEYZvvWaq0NYUKKonyInOeubObXH4BLXZpmUBSdYCSjjLdNJY3jkQoxbDVPVMIurVNh5zT5SMRw==", + "requires": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/fetch-http-handler": "3.226.0", + "@aws-sdk/hash-node": "3.226.0", + "@aws-sdk/invalid-dependency": "3.226.0", + "@aws-sdk/middleware-content-length": "3.226.0", + "@aws-sdk/middleware-endpoint": "3.226.0", + "@aws-sdk/middleware-host-header": "3.226.0", + "@aws-sdk/middleware-logger": "3.226.0", + "@aws-sdk/middleware-recursion-detection": "3.226.0", + "@aws-sdk/middleware-retry": "3.235.0", + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/middleware-user-agent": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/node-http-handler": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/smithy-client": "3.234.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.208.0", + "@aws-sdk/util-defaults-mode-browser": "3.234.0", + "@aws-sdk/util-defaults-mode-node": "3.234.0", + "@aws-sdk/util-endpoints": "3.241.0", + "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/util-user-agent-browser": "3.226.0", + "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.208.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/client-sso-oidc": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.241.0.tgz", + "integrity": "sha512-/Ml2QBGpGfUEeBrPzBZhSTBkHuXFD2EAZEIHGCBH4tKaURDI6/FoGI8P1Rl4BzoFt+II/Cr91Eox6YT9EwChsQ==", + "requires": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/fetch-http-handler": "3.226.0", + "@aws-sdk/hash-node": "3.226.0", + "@aws-sdk/invalid-dependency": "3.226.0", + "@aws-sdk/middleware-content-length": "3.226.0", + "@aws-sdk/middleware-endpoint": "3.226.0", + "@aws-sdk/middleware-host-header": "3.226.0", + "@aws-sdk/middleware-logger": "3.226.0", + "@aws-sdk/middleware-recursion-detection": "3.226.0", + "@aws-sdk/middleware-retry": "3.235.0", + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/middleware-user-agent": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/node-http-handler": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/smithy-client": "3.234.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.208.0", + "@aws-sdk/util-defaults-mode-browser": "3.234.0", + "@aws-sdk/util-defaults-mode-node": "3.234.0", + "@aws-sdk/util-endpoints": "3.241.0", + "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/util-user-agent-browser": "3.226.0", + "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.208.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/client-sts": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.241.0.tgz", + "integrity": "sha512-vmlG8cJzRf8skCtTJbA2wBvD2c3NQ5gZryzJvTKDS06KzBzcEpnjlLseuTekcnOiRNekbFUX5hRu5Zj3N2ReLg==", + "requires": { + "@aws-crypto/sha256-browser": "2.0.0", + "@aws-crypto/sha256-js": "2.0.0", + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/credential-provider-node": "3.241.0", + "@aws-sdk/fetch-http-handler": "3.226.0", + "@aws-sdk/hash-node": "3.226.0", + "@aws-sdk/invalid-dependency": "3.226.0", + "@aws-sdk/middleware-content-length": "3.226.0", + "@aws-sdk/middleware-endpoint": "3.226.0", + "@aws-sdk/middleware-host-header": "3.226.0", + "@aws-sdk/middleware-logger": "3.226.0", + "@aws-sdk/middleware-recursion-detection": "3.226.0", + "@aws-sdk/middleware-retry": "3.235.0", + "@aws-sdk/middleware-sdk-sts": "3.226.0", + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/middleware-signing": "3.226.0", + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/middleware-user-agent": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/node-http-handler": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/smithy-client": "3.234.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "@aws-sdk/util-body-length-browser": "3.188.0", + "@aws-sdk/util-body-length-node": "3.208.0", + "@aws-sdk/util-defaults-mode-browser": "3.234.0", + "@aws-sdk/util-defaults-mode-node": "3.234.0", + "@aws-sdk/util-endpoints": "3.241.0", + "@aws-sdk/util-retry": "3.229.0", + "@aws-sdk/util-user-agent-browser": "3.226.0", + "@aws-sdk/util-user-agent-node": "3.226.0", + "@aws-sdk/util-utf8-browser": "3.188.0", + "@aws-sdk/util-utf8-node": "3.208.0", + "fast-xml-parser": "4.0.11", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/config-resolver": { + "version": "3.234.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.234.0.tgz", + "integrity": "sha512-uZxy4wzllfvgCQxVc+Iqhde0NGAnfmV2hWR6ejadJaAFTuYNvQiRg9IqJy3pkyDPqXySiJ8Bom5PoJfgn55J/A==", + "requires": { + "@aws-sdk/signature-v4": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-config-provider": "3.208.0", + "@aws-sdk/util-middleware": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-provider-cognito-identity": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.241.0.tgz", + "integrity": "sha512-e2hlXWG9DH93uVe2wHIUrUOrgZTLzCV3gBd10D3/usSzS4FvVVU7OmidnRPYCLLnt3EvnL5b4REOedO1q8hv8g==", + "requires": { + "@aws-sdk/client-cognito-identity": "3.241.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.226.0.tgz", + "integrity": "sha512-sd8uK1ojbXxaZXlthzw/VXZwCPUtU3PjObOfr3Evj7MPIM2IH8h29foOlggx939MdLQGboJf9gKvLlvKDWtJRA==", + "requires": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-provider-imds": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.226.0.tgz", + "integrity": "sha512-//z/COQm2AjYFI1Lb0wKHTQSrvLFTyuKLFQGPJsKS7DPoxGOCKB7hmYerlbl01IDoCxTdyL//TyyPxbZEOQD5Q==", + "requires": { + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.241.0.tgz", + "integrity": "sha512-CI+mu6h74Kzmscw35TvNkc/wYHsHPGAwP7humSHoWw53H9mVw21Ggft/dT1iFQQZWQ8BNXkzuXlNo1IlqwMgOA==", + "requires": { + "@aws-sdk/credential-provider-env": "3.226.0", + "@aws-sdk/credential-provider-imds": "3.226.0", + "@aws-sdk/credential-provider-process": "3.226.0", + "@aws-sdk/credential-provider-sso": "3.241.0", + "@aws-sdk/credential-provider-web-identity": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.241.0.tgz", + "integrity": "sha512-08zPQcD5o9brQmzEipWHeHgU85aQcEF8MWLfpeyjO6e1/l7ysQ35NsS+PYtv77nLpGCx/X+ZuW/KXWoRrbw77w==", + "requires": { + "@aws-sdk/credential-provider-env": "3.226.0", + "@aws-sdk/credential-provider-imds": "3.226.0", + "@aws-sdk/credential-provider-ini": "3.241.0", + "@aws-sdk/credential-provider-process": "3.226.0", + "@aws-sdk/credential-provider-sso": "3.241.0", + "@aws-sdk/credential-provider-web-identity": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.226.0.tgz", + "integrity": "sha512-iUDMdnrTvbvaCFhWwqyXrhvQ9+ojPqPqXhwZtY1X/Qaz+73S9gXBPJHZaZb2Ke0yKE1Ql3bJbKvmmxC/qLQMng==", + "requires": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.241.0.tgz", + "integrity": "sha512-6Bjd6eEIrVomRTrPrM4dlxusQm+KMJ9hLYKECCpFkwDKIK+pTgZNLRtQdalHyzwneHJPdimrm8cOv1kUQ8hPoA==", + "requires": { + "@aws-sdk/client-sso": "3.241.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/token-providers": "3.241.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.226.0.tgz", + "integrity": "sha512-CCpv847rLB0SFOHz2igvUMFAzeT2fD3YnY4C8jltuJoEkn0ITn1Hlgt13nTJ5BUuvyti2mvyXZHmNzhMIMrIlw==", + "requires": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/credential-providers": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.241.0.tgz", + "integrity": "sha512-J3Q45t1o35OhUI6gWks7rmosPT+mFWXiaHl2LST509Ovjwx6SFs2PvbGP6n7xqUzxyq5Rk6FzZBwB8ItuAa6Qw==", + "requires": { + "@aws-sdk/client-cognito-identity": "3.241.0", + "@aws-sdk/client-sso": "3.241.0", + "@aws-sdk/client-sts": "3.241.0", + "@aws-sdk/credential-provider-cognito-identity": "3.241.0", + "@aws-sdk/credential-provider-env": "3.226.0", + "@aws-sdk/credential-provider-imds": "3.226.0", + "@aws-sdk/credential-provider-ini": "3.241.0", + "@aws-sdk/credential-provider-node": "3.241.0", + "@aws-sdk/credential-provider-process": "3.226.0", + "@aws-sdk/credential-provider-sso": "3.241.0", + "@aws-sdk/credential-provider-web-identity": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/fetch-http-handler": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.226.0.tgz", + "integrity": "sha512-JewZPMNEBXfi1xVnRa7pVtK/zgZD8/lQ/YnD8pq79WuMa2cwyhDtr8oqCoqsPW+WJT5ScXoMtuHxN78l8eKWgg==", + "requires": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/querystring-builder": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-base64": "3.208.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/hash-node": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.226.0.tgz", + "integrity": "sha512-MdlJhJ9/Espwd0+gUXdZRsHuostB2WxEVAszWxobP0FTT9PnicqnfK7ExmW+DUAc0ywxtEbR3e0UND65rlSTVw==", + "requires": { + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/invalid-dependency": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.226.0.tgz", + "integrity": "sha512-QXOYFmap8g9QzRjumcRCIo2GEZkdCwd7ePQW0OABWPhKHzlJ74vvBxywjU3s39EEBEluWXtZ7Iufg6GxZM4ifw==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/is-array-buffer": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", + "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-content-length": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.226.0.tgz", + "integrity": "sha512-ksUzlHJN2JMuyavjA46a4sctvnrnITqt2tbGGWWrAuXY1mel2j+VbgnmJUiwHKUO6bTFBBeft5Vd1TSOb4JmiA==", + "requires": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-endpoint": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.226.0.tgz", + "integrity": "sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg==", + "requires": { + "@aws-sdk/middleware-serde": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/signature-v4": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/url-parser": "3.226.0", + "@aws-sdk/util-config-provider": "3.208.0", + "@aws-sdk/util-middleware": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.226.0.tgz", + "integrity": "sha512-haVkWVh6BUPwKgWwkL6sDvTkcZWvJjv8AgC8jiQuSl8GLZdzHTB8Qhi3IsfFta9HAuoLjxheWBE5Z/L0UrfhLA==", + "requires": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.226.0.tgz", + "integrity": "sha512-m9gtLrrYnpN6yckcQ09rV7ExWOLMuq8mMPF/K3DbL/YL0TuILu9i2T1W+JuxSX+K9FMG2HrLAKivE/kMLr55xA==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.226.0.tgz", + "integrity": "sha512-mwRbdKEUeuNH5TEkyZ5FWxp6bL2UC1WbY+LDv6YjHxmSMKpAoOueEdtU34PqDOLrpXXxIGHDFmjeGeMfktyEcA==", + "requires": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-retry": { + "version": "3.235.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.235.0.tgz", + "integrity": "sha512-50WHbJGpD3SNp9763MAlHqIhXil++JdQbKejNpHg7HsJne/ao3ub+fDOfx//mMBjpzBV25BGd5UlfL6blrClSg==", + "requires": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/service-error-classification": "3.229.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-retry": "3.229.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, + "@aws-sdk/middleware-sdk-sts": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.226.0.tgz", + "integrity": "sha512-NN9T/qoSD1kZvAT+VLny3NnlqgylYQcsgV3rvi/8lYzw/G/2s8VS6sm/VTWGGZhx08wZRv20MWzYu3bftcyqUg==", + "requires": { + "@aws-sdk/middleware-signing": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/signature-v4": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-serde": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.226.0.tgz", + "integrity": "sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-signing": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.226.0.tgz", + "integrity": "sha512-E6HmtPcl+IjYDDzi1xI2HpCbBq2avNWcjvCriMZWuTAtRVpnA6XDDGW5GY85IfS3A8G8vuWqEVPr8JcYUcjfew==", + "requires": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/signature-v4": "3.226.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-middleware": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-stack": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.226.0.tgz", + "integrity": "sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.226.0.tgz", + "integrity": "sha512-N1WnfzCW1Y5yWhVAphf8OPGTe8Df3vmV7/LdsoQfmpkCZgLZeK2o0xITkUQhRj1mbw7yp8tVFLFV3R2lMurdAQ==", + "requires": { + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/node-config-provider": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.226.0.tgz", + "integrity": "sha512-B8lQDqiRk7X5izFEUMXmi8CZLOKCTWQJU9HQf3ako+sF0gexo4nHN3jhoRWyLtcgC5S3on/2jxpAcqtm7kuY3w==", + "requires": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/node-http-handler": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.226.0.tgz", + "integrity": "sha512-xQCddnZNMiPmjr3W7HYM+f5ir4VfxgJh37eqZwX6EZmyItFpNNeVzKUgA920ka1VPz/ZUYB+2OFGiX3LCLkkaA==", + "requires": { + "@aws-sdk/abort-controller": "3.226.0", + "@aws-sdk/protocol-http": "3.226.0", + "@aws-sdk/querystring-builder": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/property-provider": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.226.0.tgz", + "integrity": "sha512-TsljjG+Sg0LmdgfiAlWohluWKnxB/k8xenjeozZfzOr5bHmNHtdbWv6BtNvD/R83hw7SFXxbJHlD5H4u9p2NFg==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/protocol-http": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.226.0.tgz", + "integrity": "sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/querystring-builder": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.226.0.tgz", + "integrity": "sha512-LVurypuNeotO4lmirKXRC4NYrZRAyMJXuwO0f2a5ZAUJCjauwYrifKue6yCfU7bls7gut7nfcR6B99WBYpHs3g==", + "requires": { + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-uri-escape": "3.201.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/querystring-parser": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.226.0.tgz", + "integrity": "sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/service-error-classification": { + "version": "3.229.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.229.0.tgz", + "integrity": "sha512-dnzWWQ0/NoWMUZ5C0DW3dPm0wC1O76Y/SpKbuJzWPkx1EYy6r8p32Ly4D9vUzrKDbRGf48YHIF2kOkBmu21CLg==" + }, + "@aws-sdk/shared-ini-file-loader": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.226.0.tgz", + "integrity": "sha512-661VQefsARxVyyV2FX9V61V+nNgImk7aN2hYlFKla6BCwZfMng+dEtD0xVGyg1PfRw0qvEv5LQyxMVgHcUSevA==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/signature-v4": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.226.0.tgz", + "integrity": "sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw==", + "requires": { + "@aws-sdk/is-array-buffer": "3.201.0", + "@aws-sdk/types": "3.226.0", + "@aws-sdk/util-hex-encoding": "3.201.0", + "@aws-sdk/util-middleware": "3.226.0", + "@aws-sdk/util-uri-escape": "3.201.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/smithy-client": { + "version": "3.234.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.234.0.tgz", + "integrity": "sha512-8AtR/k4vsFvjXeQbIzq/Wy7Nbk48Ou0wUEeVYPHWHPSU8QamFWORkOwmKtKMfHAyZvmqiAPeQqHFkq+UJhWyyQ==", + "requires": { + "@aws-sdk/middleware-stack": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/token-providers": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.241.0.tgz", + "integrity": "sha512-79okvuOS7V559OIL/RalIPG98wzmWxeFOChFnbEjn2pKOyGQ6FJRwLPYZaVRtNdAtnkBNgRpmFq9dX843QxhtQ==", + "requires": { + "@aws-sdk/client-sso-oidc": "3.241.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/shared-ini-file-loader": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/types": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.226.0.tgz", + "integrity": "sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/url-parser": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.226.0.tgz", + "integrity": "sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg==", + "requires": { + "@aws-sdk/querystring-parser": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-base64": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz", + "integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==", + "requires": { + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-body-length-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", + "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-body-length-node": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz", + "integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-buffer-from": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", + "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", + "requires": { + "@aws-sdk/is-array-buffer": "3.201.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-config-provider": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz", + "integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-defaults-mode-browser": { + "version": "3.234.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.234.0.tgz", + "integrity": "sha512-IHMKXjTbOD8XMz5+2oCOsVP94BYb9YyjXdns0aAXr2NAo7k2+RCzXQ2DebJXppGda1F6opFutoKwyVSN0cmbMw==", + "requires": { + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-defaults-mode-node": { + "version": "3.234.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.234.0.tgz", + "integrity": "sha512-UGjQ+OjBYYhxFVtUY+jtr0ZZgzZh6OHtYwRhFt8IHewJXFCfZTyfsbX20szBj5y1S4HRIUJ7cwBLIytTqMbI5w==", + "requires": { + "@aws-sdk/config-resolver": "3.234.0", + "@aws-sdk/credential-provider-imds": "3.226.0", + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/property-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.241.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.241.0.tgz", + "integrity": "sha512-jVf8bKrN22Ey0xLmj75sL7EUvm5HFpuOMkXsZkuXycKhCwLBcEUWlvtJYtRjOU1zScPQv9GMJd2QXQglp34iOQ==", + "requires": { + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-hex-encoding": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", + "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz", + "integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-middleware": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.226.0.tgz", + "integrity": "sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-retry": { + "version": "3.229.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.229.0.tgz", + "integrity": "sha512-0zKTqi0P1inD0LzIMuXRIYYQ/8c1lWMg/cfiqUcIAF1TpatlpZuN7umU0ierpBFud7S+zDgg0oemh+Nj8xliJw==", + "requires": { + "@aws-sdk/service-error-classification": "3.229.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-uri-escape": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", + "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.226.0.tgz", + "integrity": "sha512-PhBIu2h6sPJPcv2I7ELfFizdl5pNiL4LfxrasMCYXQkJvVnoXztHA1x+CQbXIdtZOIlpjC+6BjDcE0uhnpvfcA==", + "requires": { + "@aws-sdk/types": "3.226.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.226.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.226.0.tgz", + "integrity": "sha512-othPc5Dz/pkYkxH+nZPhc1Al0HndQT8zHD4e9h+EZ+8lkd8n+IsnLfTS/mSJWrfiC6UlNRVw55cItstmJyMe/A==", + "requires": { + "@aws-sdk/node-config-provider": "3.226.0", + "@aws-sdk/types": "3.226.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-utf8-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", + "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==", + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, + "@aws-sdk/util-utf8-node": { + "version": "3.208.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz", + "integrity": "sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ==", + "requires": { + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + } + } + }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -2112,6 +3366,11 @@ } } }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3130,6 +4389,14 @@ "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" }, + "fast-xml-parser": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", + "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", + "requires": { + "strnum": "^1.0.5" + } + }, "fb-watchman": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", @@ -7945,6 +9212,11 @@ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", diff --git a/frontend/server/package.json b/frontend/server/package.json index eb67371f7ea..9fe103ec28b 100644 --- a/frontend/server/package.json +++ b/frontend/server/package.json @@ -2,6 +2,7 @@ "description": "Frontend webserver package for Kubeflow Pipelines", "main": "server.js", "dependencies": { + "@aws-sdk/credential-providers": "^3.241.0", "@google-cloud/storage": "^2.5.0", "@kubernetes/client-node": "^0.8.2", "axios": ">=0.21.1", From 26a90619e77d7d201c7a93e3f6927bfb48c8e43e Mon Sep 17 00:00:00 2001 From: Steakley Date: Wed, 4 Jan 2023 13:13:50 -0800 Subject: [PATCH 2/7] format code --- frontend/server/aws-helper.test.ts | 2 +- frontend/server/handlers/artifacts.ts | 10 +++++----- frontend/server/minio-helper.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/server/aws-helper.test.ts b/frontend/server/aws-helper.test.ts index 48e59ad11f8..f4225ee79c3 100644 --- a/frontend/server/aws-helper.test.ts +++ b/frontend/server/aws-helper.test.ts @@ -11,7 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import {isS3Endpoint } from './aws-helper'; +import { isS3Endpoint } from './aws-helper'; describe('isS3Endpoint', () => { it('checks a valid s3 endpoint', () => { diff --git a/frontend/server/handlers/artifacts.ts b/frontend/server/handlers/artifacts.ts index 5d185a62a92..7af04b4aac5 100644 --- a/frontend/server/handlers/artifacts.ts +++ b/frontend/server/handlers/artifacts.ts @@ -215,11 +215,11 @@ function getGCSArtifactHandler(options: { key: string; bucket: string }, peek: n // escapes everything else. const regex = new RegExp( '^' + - key - .split(/\*+/) - .map(escapeRegexChars) - .join('.*') + - '$', + key + .split(/\*+/) + .map(escapeRegexChars) + .join('.*') + + '$', ); return regex.test(f.name); }); diff --git a/frontend/server/minio-helper.ts b/frontend/server/minio-helper.ts index b7d0de78410..a065fd83f10 100644 --- a/frontend/server/minio-helper.ts +++ b/frontend/server/minio-helper.ts @@ -17,7 +17,7 @@ import * as tar from 'tar-stream'; import peek from 'peek-stream'; import gunzip from 'gunzip-maybe'; import { Client as MinioClient, ClientOptions as MinioClientOptions } from 'minio'; -const { fromNodeProviderChain } = require("@aws-sdk/credential-providers"); +const { fromNodeProviderChain } = require('@aws-sdk/credential-providers'); /** MinioRequestConfig describes the info required to retrieve an artifact. */ export interface MinioRequestConfig { @@ -101,7 +101,7 @@ function extractFirstTarRecordAsStream() { extract.write(chunk, callback); }, }); - extract.once('entry', function (_header, stream, next) { + extract.once('entry', function(_header, stream, next) { stream.on('data', (buffer: any) => transformStream.push(buffer)); stream.on('end', () => { transformStream.emit('end'); From 7af672521e5d72eaa3e75a0a7054e43e62fe4ce1 Mon Sep 17 00:00:00 2001 From: Steakley Date: Wed, 11 Jan 2023 17:12:44 -0800 Subject: [PATCH 3/7] catch exception if no credentials are found after using credentialchain --- frontend/server/minio-helper.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/frontend/server/minio-helper.ts b/frontend/server/minio-helper.ts index a065fd83f10..a7b30d7902f 100644 --- a/frontend/server/minio-helper.ts +++ b/frontend/server/minio-helper.ts @@ -38,17 +38,20 @@ export interface MinioClientOptionsWithOptionalSecrets extends Partial Date: Wed, 18 Jan 2023 13:38:27 -0800 Subject: [PATCH 4/7] revert changes and add section for specific aws changes --- frontend/server/aws-helper.test.ts | 98 +++++++++++++++++++++++- frontend/server/aws-helper.ts | 107 +++++++++++++++++++++++++++ frontend/server/minio-helper.test.ts | 28 +++++++ frontend/server/minio-helper.ts | 29 +++++++- 4 files changed, 257 insertions(+), 5 deletions(-) diff --git a/frontend/server/aws-helper.test.ts b/frontend/server/aws-helper.test.ts index f4225ee79c3..dd0b2bff61e 100644 --- a/frontend/server/aws-helper.test.ts +++ b/frontend/server/aws-helper.test.ts @@ -11,7 +11,99 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import { isS3Endpoint } from './aws-helper'; +import fetch from 'node-fetch'; +import { awsInstanceProfileCredentials, isS3Endpoint } from './aws-helper'; + +// mock node-fetch module +jest.mock('node-fetch'); + +function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +beforeEach(() => { + awsInstanceProfileCredentials.reset(); + jest.clearAllMocks(); +}); + +describe('awsInstanceProfileCredentials', () => { + const mockedFetch: jest.Mock = fetch as any; + + describe('getCredentials', () => { + it('retrieves, caches, and refreshes the AWS EC2 instance profile and session credentials everytime it is called.', async () => { + let count = 0; + const expectedCredentials = [ + { + AccessKeyId: 'AccessKeyId', + Code: 'Success', + Expiration: new Date(Date.now() + 1000).toISOString(), // expires 1 sec later + LastUpdated: '2019-12-17T10:55:38Z', + SecretAccessKey: 'SecretAccessKey', + Token: 'SessionToken', + Type: 'AWS-HMAC', + }, + { + AccessKeyId: 'AccessKeyId2', + Code: 'Success', + Expiration: new Date(Date.now() + 10000).toISOString(), // expires 10 sec later + LastUpdated: '2019-12-17T10:55:38Z', + SecretAccessKey: 'SecretAccessKey2', + Token: 'SessionToken2', + Type: 'AWS-HMAC', + }, + ]; + const mockFetch = (url: string) => { + if (url === 'http://169.254.169.254/latest/meta-data/iam/security-credentials/') { + return Promise.resolve({ text: () => Promise.resolve('some_iam_role') }); + } + return Promise.resolve({ + json: () => Promise.resolve(expectedCredentials[count++]), + }); + }; + mockedFetch.mockImplementation(mockFetch); + + // expect to get cred from ec2 instance metadata store + expect(await awsInstanceProfileCredentials.getCredentials()).toBe(expectedCredentials[0]); + // expect to call once for profile name, and once for credential + expect(mockedFetch.mock.calls.length).toBe(2); + // expect to get same credential as it has not expire + expect(await awsInstanceProfileCredentials.getCredentials()).toBe(expectedCredentials[0]); + // expect to not to have any more calls + expect(mockedFetch.mock.calls.length).toBe(2); + // let credential expire + await sleep(1500); + // expect to get new cred as old one expire + expect(await awsInstanceProfileCredentials.getCredentials()).toBe(expectedCredentials[1]); + // expect to get same cred as it has not expire + expect(await awsInstanceProfileCredentials.getCredentials()).toBe(expectedCredentials[1]); + }); + + it('fails gracefully if there is no instance profile.', async () => { + const mockFetch = (url: string) => { + if (url === 'http://169.254.169.254/latest/meta-data/iam/security-credentials/') { + return Promise.resolve({ text: () => Promise.resolve('') }); + } + return Promise.reject('Unknown error'); + }; + mockedFetch.mockImplementation(mockFetch); + + expect(await awsInstanceProfileCredentials.ok()).toBeFalsy(); + expect(async () => await awsInstanceProfileCredentials.getCredentials()).not.toThrow(); + expect(await awsInstanceProfileCredentials.getCredentials()).toBeUndefined(); + }); + + it('fails gracefully if there is no metadata store.', async () => { + const mockFetch = (_: string) => { + return Promise.reject('Unknown error'); + }; + mockedFetch.mockImplementation(mockFetch); + + expect(await awsInstanceProfileCredentials.ok()).toBeFalsy(); + expect(async () => await awsInstanceProfileCredentials.getCredentials()).not.toThrow(); + expect(await awsInstanceProfileCredentials.getCredentials()).toBeUndefined(); + }); + }); +}); describe('isS3Endpoint', () => { it('checks a valid s3 endpoint', () => { @@ -26,6 +118,10 @@ describe('isS3Endpoint', () => { expect(isS3Endpoint('s3.cn-north-1.amazonaws.com.cn')).toBe(true); }); + it('checks a valid s3 fips GovCloud endpoint', () => { + expect(isS3Endpoint('s3-fips.us-gov-west-1.amazonaws.com')).toBe(true); + }); + it('checks an invalid s3 endpoint', () => { expect(isS3Endpoint('amazonaws.com')).toBe(false); }); diff --git a/frontend/server/aws-helper.ts b/frontend/server/aws-helper.ts index 35a0ae8d9ea..35a023d2ec8 100644 --- a/frontend/server/aws-helper.ts +++ b/frontend/server/aws-helper.ts @@ -11,6 +11,40 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +import fetch from 'node-fetch'; + +/** AWSMetadataCredentials describes the credentials provided by aws metadata store. */ +export interface AWSMetadataCredentials { + Code: string; + LastUpdated: string; + Type: string; + AccessKeyId: string; + SecretAccessKey: string; + Token: string; + Expiration: string; +} + +/** url for aws metadata store. */ +const metadataUrl = 'http://169.254.169.254/latest/meta-data'; + +/** + * Get the AWS IAM instance profile. + */ +async function getIAMInstanceProfile(): Promise { + try { + const resp = await fetch(`${metadataUrl}/iam/security-credentials/`); + const profiles = (await resp.text()).split('\n'); + if (profiles.length > 0) { + return profiles[0].trim(); // return first profile + } + return; + } catch (error) { + console.error( + `Unable to fetch credentials from AWS metadata store (${metadataUrl}/iam/security-credentials/): ${error}`, + ); + return; + } +} /** * Check if the provided string is an S3 endpoint (can be any region). @@ -20,3 +54,76 @@ export function isS3Endpoint(endpoint: string = ''): boolean { return !!endpoint.match(/s3.{0,}\.amazonaws\.com\.?.{0,}/i); } + +/** + * Class to handle the session credentials for AWS ec2 instance profile. + */ +class AWSInstanceProfileCredentials { + _iamProfile?: string; + _credentials?: AWSMetadataCredentials; + _expiration: number = 0; + + /** reset all caches */ + reset() { + this._iamProfile = undefined; + this._credentials = undefined; + this._expiration = 0; + return this; + } + + /** + * EC2 Instance profile + */ + async profile() { + if (!this._iamProfile) { + try { + this._iamProfile = await getIAMInstanceProfile(); + } catch (err) { + console.error(err); + } + } + return this._iamProfile; + } + + /** + * Return true only if there is a metadata store and instance profile. + */ + async ok() { + try { + const profile = await this.profile(); + return profile && profile.length > 0; + } catch (_) { + return false; + } + } + + async _fetchCredentials(): Promise { + try { + const profile = await this.profile(); + const resp = await fetch(`${metadataUrl}/iam/security-credentials/${profile}`); + const credentials = await resp.json(); + return credentials; + } catch (error) { + console.error(`Unable to fetch credentials from AWS metadata store: ${error}`); + return; + } + } + + /** + * Get the AWS metadata store session credentials. + */ + async getCredentials(): Promise { + // query for credentials if going to expire or no credentials yet + if (Date.now() + 10 >= this._expiration || !this._credentials) { + this._credentials = await this._fetchCredentials(); + if (this._credentials && this._credentials.Expiration) { + this._expiration = new Date(this._credentials.Expiration).getTime(); + } else { + this._expiration = -1; // always retry + } + } + return this._credentials; + } +} + +export const awsInstanceProfileCredentials = new AWSInstanceProfileCredentials(); diff --git a/frontend/server/minio-helper.test.ts b/frontend/server/minio-helper.test.ts index 12926700547..75ac64b2cce 100644 --- a/frontend/server/minio-helper.test.ts +++ b/frontend/server/minio-helper.test.ts @@ -14,6 +14,7 @@ import * as zlib from 'zlib'; import { PassThrough } from 'stream'; import { Client as MinioClient } from 'minio'; +import { awsInstanceProfileCredentials } from './aws-helper'; import { createMinioClient, isTarball, maybeTarball, getObjectStream } from './minio-helper'; import { V1beta1JobTemplateSpec } from '@kubernetes/client-node'; @@ -52,6 +53,33 @@ describe('minio-helper', () => { endPoint: 'minio.kubeflow:80', }); }); + + it('uses EC2 metadata credentials if access key are not provided.', async () => { + (awsInstanceProfileCredentials.getCredentials as jest.Mock).mockImplementation(() => + Promise.resolve({ + AccessKeyId: 'AccessKeyId', + Code: 'Success', + Expiration: new Date(Date.now() + 1000).toISOString(), // expires 1 sec later + LastUpdated: '2019-12-17T10:55:38Z', + SecretAccessKey: 'SecretAccessKey', + Token: 'SessionToken', + Type: 'AWS-HMAC', + }), + ); + (awsInstanceProfileCredentials.ok as jest.Mock).mockImplementation(() => + Promise.resolve(true), + ); + + const client = await createMinioClient({ endPoint: 's3.awsamazon.com' }); + + expect(client).toBeInstanceOf(MinioClient); + expect(MockedMinioClient).toHaveBeenCalledWith({ + accessKey: 'AccessKeyId', + endPoint: 's3.awsamazon.com', + secretKey: 'SecretAccessKey', + sessionToken: 'SessionToken', + }); + }); }); describe('isTarball', () => { diff --git a/frontend/server/minio-helper.ts b/frontend/server/minio-helper.ts index a7b30d7902f..b709195e01b 100644 --- a/frontend/server/minio-helper.ts +++ b/frontend/server/minio-helper.ts @@ -17,6 +17,7 @@ import * as tar from 'tar-stream'; import peek from 'peek-stream'; import gunzip from 'gunzip-maybe'; import { Client as MinioClient, ClientOptions as MinioClientOptions } from 'minio'; +import { awsInstanceProfileCredentials, isS3Endpoint } from './aws-helper'; const { fromNodeProviderChain } = require('@aws-sdk/credential-providers'); /** MinioRequestConfig describes the info required to retrieve an artifact. */ @@ -37,10 +38,11 @@ export interface MinioClientOptionsWithOptionalSecrets extends Partial Date: Wed, 18 Jan 2023 13:51:01 -0800 Subject: [PATCH 5/7] revert deleting aws-helper mock --- frontend/server/minio-helper.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/server/minio-helper.test.ts b/frontend/server/minio-helper.test.ts index 75ac64b2cce..f4910c404c1 100644 --- a/frontend/server/minio-helper.test.ts +++ b/frontend/server/minio-helper.test.ts @@ -19,6 +19,7 @@ import { createMinioClient, isTarball, maybeTarball, getObjectStream } from './m import { V1beta1JobTemplateSpec } from '@kubernetes/client-node'; jest.mock('minio'); +jest.mock('./aws-helper'); describe('minio-helper', () => { const MockedMinioClient: jest.Mock = MinioClient as any; From 37137f382585cc806615b763631528e8dff172f5 Mon Sep 17 00:00:00 2001 From: Steakley Date: Wed, 18 Jan 2023 13:59:33 -0800 Subject: [PATCH 6/7] add privatelink test for isS3endpiont --- frontend/server/aws-helper.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/server/aws-helper.test.ts b/frontend/server/aws-helper.test.ts index dd0b2bff61e..87a2a00ce01 100644 --- a/frontend/server/aws-helper.test.ts +++ b/frontend/server/aws-helper.test.ts @@ -122,6 +122,10 @@ describe('isS3Endpoint', () => { expect(isS3Endpoint('s3-fips.us-gov-west-1.amazonaws.com')).toBe(true); }); + it('checks a valid s3 PrivateLink endpoint', () => { + expect(isS3Endpoint('vpce-1a2b3c4d-5e6f.s3.us-east-1.vpce.amazonaws.com')).toBe(true); + }); + it('checks an invalid s3 endpoint', () => { expect(isS3Endpoint('amazonaws.com')).toBe(false); }); From 06b530ce2a55c9750cab079a4a3d5fc8e1edb1b1 Mon Sep 17 00:00:00 2001 From: Steakley Date: Wed, 18 Jan 2023 14:51:11 -0800 Subject: [PATCH 7/7] fix try block --- frontend/server/minio-helper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/server/minio-helper.ts b/frontend/server/minio-helper.ts index b709195e01b..bb272440d27 100644 --- a/frontend/server/minio-helper.ts +++ b/frontend/server/minio-helper.ts @@ -40,9 +40,9 @@ export interface MinioClientOptionsWithOptionalSecrets extends Partial