From efd511ae90b4c9d40217e20949859d6a16d0fec9 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:26:23 +0100 Subject: [PATCH 1/2] feat(aws): add IAM to Cloud Explorer --- README.md | 17 + bun.lock | 59 ++- packages/api/package.json | 1 + .../api/src/adapter-aws/AwsIamAdapter.test.ts | 151 +++++++ packages/api/src/adapter-aws/AwsIamAdapter.ts | 133 +++++++ packages/api/src/adapter-aws/awsErrors.ts | 2 + packages/api/src/aws.ts | 4 + packages/api/src/cloud-spi/iamSchema.ts | 57 +++ packages/api/src/cloud-spi/serviceCatalog.ts | 1 + packages/api/src/cloud-spi/types.ts | 2 +- packages/api/src/cloudProxy.ts | 2 + .../src/components/DynamicResourceView.tsx | 2 + packages/frontend/src/types/resource.ts | 2 +- pnpm-lock.yaml | 375 ++++++++++++++---- 14 files changed, 736 insertions(+), 72 deletions(-) create mode 100644 packages/api/src/adapter-aws/AwsIamAdapter.test.ts create mode 100644 packages/api/src/adapter-aws/AwsIamAdapter.ts create mode 100644 packages/api/src/cloud-spi/iamSchema.ts diff --git a/README.md b/README.md index b0b699e..3a68b0f 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ cd packages/api && bun run scripts/service-matrix.ts | Databases | Database | Yes (list, inspect) | Yes (list, create, delete, inspect) | Yes (list, create, inspect, delete) | | Networking | Networking | Yes (list) | No | No | | Provisioning | CloudFormation / Infrastructure as Code | No | No | No | +| Security | Identity | Yes (list, create, delete, inspect) | No | No | | Security | Secrets Manager / Key Vault | Yes (legacy page) | Yes (list, create, delete, inspect) | No | Console Home is available for all three clouds. @@ -181,6 +182,22 @@ Current gaps: +
+Identity + +AWS only, through the generic identity service category. + +- List and inspect IAM users. +- Create and delete IAM users. +- IAM user paths are supported during creation. + +Current gaps: + +- Roles, groups, policies, access keys, and other advanced IAM workflows are not exposed yet. +- No Azure or GCP identity adapter yet. + +
+
Serverless diff --git a/bun.lock b/bun.lock index 76e7b48..e38dae8 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "dependencies": { "@aws-sdk/client-ec2": "^3.1076.0", "@aws-sdk/client-eks": "^3.1076.0", + "@aws-sdk/client-iam": "^3.1076.0", "@aws-sdk/client-lambda": "^3.1076.0", "@aws-sdk/client-rds": "^3.1076.0", "@aws-sdk/client-s3": "^3.1076.0", @@ -25,7 +26,7 @@ }, "packages/frontend": { "name": "@floci/frontend", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "@tanstack/react-query": "^5.101.2", "@tanstack/react-query-devtools": "^5.101.2", @@ -65,6 +66,8 @@ "@aws-sdk/client-eks": ["@aws-sdk/client-eks@3.1080.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/credential-provider-node": "^3.972.63", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-tN9O6f9vc9ns/uaPsN6vzEOrD7ftkWOMgzXoxY1J438WM2M8qcGbRJkhD2c+PvaZvZY3MVF7843mdIVrOA3zzQ=="], + "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.1096.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/credential-provider-node": "^3.972.73", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-39a+pXB0V9qWciaXsfPj4yhPNLBh69xiB6VG3auWCdj2PUW/oz1wjVOpuiH6mGUjWw8SMzVTcKrg0UaiUIPgXQ=="], + "@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.1080.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/credential-provider-node": "^3.972.63", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-rTitNyvjgBrJzjDkzLRcLi1QcxSomomLRjhYXJWID97uaw1uxtmer8wOVi8l6UFxzlRVa83laZ3srAeWRs509A=="], "@aws-sdk/client-rds": ["@aws-sdk/client-rds@3.1080.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.28", "@aws-sdk/credential-provider-node": "^3.972.63", "@aws-sdk/middleware-sdk-rds": "^3.972.42", "@aws-sdk/types": "^3.973.15", "@smithy/core": "^3.29.0", "@smithy/fetch-http-handler": "^5.6.2", "@smithy/node-http-handler": "^4.9.2", "@smithy/types": "^4.15.1", "tslib": "^2.6.2" } }, "sha512-nkUu5ZN2TYTWslADZxMxNHMwl9NGdRGyWR+ZOPWL2MlDGbid3a2b7QAku6eaCoiDkWHonKbaHZYNtGoalkmyGA=="], @@ -533,6 +536,20 @@ "zustand": ["zustand@5.0.14", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g=="], + "@aws-sdk/client-iam/@aws-sdk/core": ["@aws-sdk/core@3.977.1", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.29.8", "@smithy/signature-v4": "^5.6.9", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-KVtQRtc00ES/y+Sc3vYXeP6pCIcNlBJCZOwvqSy8ZpVGmbM5+IG+AfhuTKQ2oXmIVqZJewaGMMpzPkywC6xg0w=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.73", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.62", "@aws-sdk/credential-provider-http": "^3.972.64", "@aws-sdk/credential-provider-ini": "^3.973.7", "@aws-sdk/credential-provider-process": "^3.972.62", "@aws-sdk/credential-provider-sso": "^3.973.6", "@aws-sdk/credential-provider-web-identity": "^3.972.68", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/credential-provider-imds": "^4.4.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-VTzdbf8Ukjdb9yUubZzRI678CWZvKovhE8Nv3qihwhC187sRMGls+r9N8Wuht5q1xjKx2nmpS48ar8ppupjkCA=="], + + "@aws-sdk/client-iam/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="], + + "@aws-sdk/client-iam/@smithy/core": ["@smithy/core@3.31.0", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-sylYk2l9d7CmRv8ts8p0SDQUr3VO+HMeS1nrjL6+UtbO8ktJHTOeQ1McX+aAyvGGccp5aZX9eNtdcXrSwzoZaw=="], + + "@aws-sdk/client-iam/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.12", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-OpQgP6IGH4j0NJ2zjfYZLjQL85ai+Wi/q51EmZJovXsEwKSvu89qiXUq77Q6EmwZ/hSl7fKpn2Z9mhiDN6OM+Q=="], + + "@aws-sdk/client-iam/@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.12", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-dWW5KRt4mnEvjNzbGqGeCuAvgum85Y9ZoyuMQqcTEfapndyVJ1k9BEHK7kdXJZ32enyRmmwcFjMwlB/KgLKI3Q=="], + + "@aws-sdk/client-iam/@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="], + "@babel/helper-compilation-targets/browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -543,6 +560,24 @@ "bun-types/@types/node": ["@types/node@20.19.39", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw=="], + "@aws-sdk/client-iam/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="], + + "@aws-sdk/client-iam/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.6.11", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7HsspeiNCZvZHEJ22vV5L/QYuJdTyJvPJvMrYD3AgkM3IJB0pkln4jkjPvtpTWRMkHXbO8WKwNjoVdVlBFwHmw=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.62", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-BkDrk2cNjed31IKin/Oksb2ziF+gfuyRskFVuT4EU9Mep7M8Y/d8DJG4+anHme4Vuse7CwaEscwEfGyR6mzBhQ=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.64", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-Wj1FGK2IxY5EccQCvH+niTYhIvDoDujJf2CpRRgS3NpYNEgiFNVItNbJYQjINRlu7fG7jSsXkKV0UWKriEplrw=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.7", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/credential-provider-env": "^3.972.62", "@aws-sdk/credential-provider-http": "^3.972.64", "@aws-sdk/credential-provider-login": "^3.972.69", "@aws-sdk/credential-provider-process": "^3.972.62", "@aws-sdk/credential-provider-sso": "^3.973.6", "@aws-sdk/credential-provider-web-identity": "^3.972.68", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/credential-provider-imds": "^4.4.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-2CefB8cCxDu52P24B8Ay93/cTT199bcSvNHQ8e2f4BjSCF83yErBnTIZEBo0VeIgCfmw+PJKFUXnlQWxm2dkug=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.62", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zXYU9UWNL66gtMgNLhmxlrvEokuI7r6G2q7FRGu41Bya4iS30JLelUipJX9SV4zhyCPWJhI9Li54R1d9H8Tq6A=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.6", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/token-providers": "3.1096.0", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-DobZggy3K49xdCpjeyMou0FQhkoYbluVGNydL6D+lcxF8GoAsttFX0xnH5GmiQ89We5dB6TRpW+CD/VowBH6HQ=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.68", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-bq+yTt+uWJx60VVp/OIAX5xqUAu/K2Uc3eknWnWl+KtfcU2CQe0uNw6lySrn2t5GKHq7jsV0Z63HiBGVtzr/lg=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.15", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-xYVGrisQqTJWhOnScUhbx8s9H63TMtoxzuUoxG6mP8J+B/YbX3vZxVsgV0xDf43abJnJP0fjP7BkQh7OESwuRA=="], + "@babel/helper-compilation-targets/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.10.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA=="], "@babel/helper-compilation-targets/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], @@ -552,5 +587,27 @@ "@babel/helper-compilation-targets/browserslist/node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], "bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.69", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-gM3j0Ie9+FoLNTYODY+QWbg3vCRBc7mR9cRdntxTMkFYIrwfRmuucfavP6HNBlYSuaYww54TNJGej4GFgoPZAg=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.36", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/signature-v4-multi-region": "^3.996.42", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-b71Suv7L+DnhM0MsQHU4WO42I32kxLZi96PbVhZbxMYIoKnEZz3v+LSrG8fupAoA4cBSshCk1Dl/PeRz49qUSg=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.36", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/signature-v4-multi-region": "^3.996.42", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-b71Suv7L+DnhM0MsQHU4WO42I32kxLZi96PbVhZbxMYIoKnEZz3v+LSrG8fupAoA4cBSshCk1Dl/PeRz49qUSg=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1096.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/nested-clients": "^3.997.36", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-hdUS2hDppy3vkWeFl5y86RLNU6OWH2mQB09yOSsRefwhhGTSFPkaZvfLDD/9vFcvMzlr8QFQFw3fw2FtrurVQA=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.36", "", { "dependencies": { "@aws-sdk/core": "^3.977.1", "@aws-sdk/signature-v4-multi-region": "^3.996.42", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/fetch-http-handler": "^5.6.10", "@smithy/node-http-handler": "^4.9.10", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-b71Suv7L+DnhM0MsQHU4WO42I32kxLZi96PbVhZbxMYIoKnEZz3v+LSrG8fupAoA4cBSshCk1Dl/PeRz49qUSg=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.42", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.9", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-DBV4naZP6HYBlAvPpoQzOP12Wvfou/5rN8yJPXjBTBylU5qwCbh/tXr2MddHoIjgoRkEl/eS+IljiUqvmwey1Q=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.42", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.9", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-DBV4naZP6HYBlAvPpoQzOP12Wvfou/5rN8yJPXjBTBylU5qwCbh/tXr2MddHoIjgoRkEl/eS+IljiUqvmwey1Q=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.42", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.9", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-DBV4naZP6HYBlAvPpoQzOP12Wvfou/5rN8yJPXjBTBylU5qwCbh/tXr2MddHoIjgoRkEl/eS+IljiUqvmwey1Q=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.11", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7HsspeiNCZvZHEJ22vV5L/QYuJdTyJvPJvMrYD3AgkM3IJB0pkln4jkjPvtpTWRMkHXbO8WKwNjoVdVlBFwHmw=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.11", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7HsspeiNCZvZHEJ22vV5L/QYuJdTyJvPJvMrYD3AgkM3IJB0pkln4jkjPvtpTWRMkHXbO8WKwNjoVdVlBFwHmw=="], + + "@aws-sdk/client-iam/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.6.11", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-7HsspeiNCZvZHEJ22vV5L/QYuJdTyJvPJvMrYD3AgkM3IJB0pkln4jkjPvtpTWRMkHXbO8WKwNjoVdVlBFwHmw=="], } } diff --git a/packages/api/package.json b/packages/api/package.json index 4c60859..5cc4d8a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,6 +12,7 @@ "dependencies": { "@aws-sdk/client-ec2": "^3.1076.0", "@aws-sdk/client-eks": "^3.1076.0", + "@aws-sdk/client-iam": "^3.1076.0", "@aws-sdk/client-lambda": "^3.1076.0", "@aws-sdk/client-rds": "^3.1076.0", "@aws-sdk/client-s3": "^3.1076.0", diff --git a/packages/api/src/adapter-aws/AwsIamAdapter.test.ts b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts new file mode 100644 index 0000000..6a752c7 --- /dev/null +++ b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts @@ -0,0 +1,151 @@ +import {describe, expect, test} from 'bun:test' +import { + CreateUserCommand, + DeleteUserCommand, + GetUserCommand, + ListUsersCommand, + type IAMClient, +} from '@aws-sdk/client-iam' +import {AwsIamAdapter} from './AwsIamAdapter' + +function fakeClient(handler: (command: unknown) => unknown): IAMClient { + return { + send: async (command: unknown) => handler(command), + } as unknown as IAMClient +} + +const alice = { + UserName: 'alice', + UserId: 'AIDAALICE', + Arn: 'arn:aws:iam::000000000000:user/team/alice', + Path: '/team/', + CreateDate: new Date('2026-01-02T03:04:05.000Z'), +} + +describe('AwsIamAdapter', () => { + test('lists every page and maps IAM users to normalized resources', async () => { + const adapter = new AwsIamAdapter(fakeClient((command) => { + if (!(command instanceof ListUsersCommand)) throw new Error('Unexpected command') + if (!command.input.Marker) return {Users: [alice], IsTruncated: true, Marker: 'next'} + return {Users: [{...alice, UserName: 'bob', UserId: 'AIDABOB'}], IsTruncated: false} + })) + + const result = await adapter.list() + + expect(result).toHaveLength(2) + expect(result[0]).toMatchObject({ + id: 'alice', + name: 'alice', + cloud: 'aws', + service: 'identity', + type: 'iam-user', + region: null, + createdAt: '2026-01-02T03:04:05.000Z', + }) + expect(result[0].metadata).toMatchObject({ + identityService: 'iam', + userId: 'AIDAALICE', + path: '/team/', + }) + }) + + test('filters users by search term', async () => { + const adapter = new AwsIamAdapter(fakeClient(() => ({ + Users: [alice, {...alice, UserName: 'bob'}], + IsTruncated: false, + }))) + + const result = await adapter.list({search: 'ALI'}) + + expect(result.map((resource) => resource.name)).toEqual(['alice']) + }) + + test('gets and maps one IAM user', async () => { + const adapter = new AwsIamAdapter(fakeClient((command) => { + expect(command).toBeInstanceOf(GetUserCommand) + return {User: alice} + })) + + const result = await adapter.get('alice') + + expect(result?.id).toBe('alice') + expect(result?.metadata.arn).toBe(alice.Arn) + }) + + test('returns null when IAM reports a missing user', async () => { + const adapter = new AwsIamAdapter(fakeClient(() => { + throw Object.assign(new Error('missing'), {name: 'NoSuchEntityException'}) + })) + + await expect(adapter.get('missing')).resolves.toBeNull() + }) + + test('creates an IAM user with an optional path', async () => { + const adapter = new AwsIamAdapter(fakeClient((command) => { + expect(command).toBeInstanceOf(CreateUserCommand) + expect((command as CreateUserCommand).input).toEqual({UserName: 'alice', Path: '/team/'}) + return {User: alice} + })) + + const result = await adapter.create({values: {userName: 'alice', path: '/team/'}}) + + expect(result.id).toBe('alice') + }) + + test('rejects invalid user names before calling IAM', async () => { + let called = false + const adapter = new AwsIamAdapter(fakeClient(() => { + called = true + return {} + })) + + await expect(adapter.create({values: {userName: 'not valid'}})).rejects.toThrow('Use a valid IAM user name') + await expect(adapter.create({values: {userName: 'alice', path: 'team'}})).rejects.toThrow('Use a valid IAM path') + expect(called).toBeFalse() + }) + + test('deletes the requested IAM user', async () => { + const adapter = new AwsIamAdapter(fakeClient((command) => { + expect(command).toBeInstanceOf(DeleteUserCommand) + expect((command as DeleteUserCommand).input.UserName).toBe('alice') + return {} + })) + + await adapter.delete('alice') + }) + + test('reports a delete of a missing IAM user as not found', async () => { + const adapter = new AwsIamAdapter(fakeClient(() => { + throw Object.assign(new Error('The user with name alice cannot be found.'), {name: 'NoSuchEntityException'}) + })) + + await expect(adapter.delete('alice')).rejects.toMatchObject({ + name: 'NotFoundError', + message: 'IAM user alice not found', + }) + }) + + test('reports a delete blocked by attached resources as a conflict', async () => { + const adapter = new AwsIamAdapter(fakeClient(() => { + throw Object.assign(new Error('Cannot delete entity, must delete access keys first.'), { + name: 'DeleteConflictException', + }) + })) + + await expect(adapter.delete('alice')).rejects.toMatchObject({ + name: 'ConflictError', + message: expect.stringContaining('IAM user alice still has attached resources'), + }) + }) + + test('returns the AWS IAM schema', () => { + const adapter = new AwsIamAdapter(fakeClient(() => ({}))) + + expect(adapter.schema()).toMatchObject({ + cloud: 'aws', + service: 'identity', + displayName: 'AWS IAM users', + actions: ['list', 'create', 'delete', 'inspect'], + }) + }) +}) diff --git a/packages/api/src/adapter-aws/AwsIamAdapter.ts b/packages/api/src/adapter-aws/AwsIamAdapter.ts new file mode 100644 index 0000000..5b8093d --- /dev/null +++ b/packages/api/src/adapter-aws/AwsIamAdapter.ts @@ -0,0 +1,133 @@ +import { + CreateUserCommand, + DeleteUserCommand, + GetUserCommand, + ListUsersCommand, + type IAMClient, + type User, +} from '@aws-sdk/client-iam' +import {iam as defaultIam} from '../aws' +import { + awsIamSchema, + IAM_PATH_MAX_LENGTH, + IAM_PATH_MESSAGE, + IAM_PATH_PATTERN, + IAM_USER_NAME_MESSAGE, + IAM_USER_NAME_PATTERN, +} from '../cloud-spi/iamSchema' +import {ConflictError, NotFoundError, ValidationError} from '../cloud-spi/errors' +import type {CloudResource, CloudServiceAdapter, CreateResourceInput, ResourceQuery, ServiceSchema} from '../cloud-spi/types' + +const MAX_LIST_PAGES = 50 + +export class AwsIamAdapter implements CloudServiceAdapter { + readonly cloud = 'aws' as const + readonly service = 'identity' as const + + constructor(private readonly iam: IAMClient = defaultIam) {} + + schema(): ServiceSchema { + return awsIamSchema() + } + + async list(query: ResourceQuery = {}): Promise { + const users: User[] = [] + let marker: string | undefined + let pages = 0 + + do { + const res = await this.iam.send(new ListUsersCommand({Marker: marker})) + users.push(...(res.Users ?? [])) + marker = res.IsTruncated ? res.Marker : undefined + pages += 1 + } while (marker && pages < MAX_LIST_PAGES) + + return filterBySearch(users.map(toResource), query.search) + } + + async get(id: string): Promise { + try { + const res = await this.iam.send(new GetUserCommand({UserName: id})) + return res.User ? toResource(res.User) : null + } catch (error) { + if (isNotFound(error)) return null + throw error + } + } + + async create(input: CreateResourceInput): Promise { + const userName = stringValue(input.values.userName) + const path = stringValue(input.values.path) + if (!userName) throw new ValidationError('userName is required') + if (!new RegExp(IAM_USER_NAME_PATTERN).test(userName)) throw new ValidationError(IAM_USER_NAME_MESSAGE) + if (path && (path.length > IAM_PATH_MAX_LENGTH || !new RegExp(IAM_PATH_PATTERN).test(path))) { + throw new ValidationError(IAM_PATH_MESSAGE) + } + + const res = await this.iam.send(new CreateUserCommand({ + UserName: userName, + Path: path || undefined, + })) + if (!res.User) throw new Error('AWS IAM did not return the created user') + return toResource(res.User) + } + + async delete(id: string): Promise { + try { + await this.iam.send(new DeleteUserCommand({UserName: id})) + } catch (error) { + if (isNotFound(error)) throw new NotFoundError(`IAM user ${id} not found`, {cause: error}) + if (isDeleteConflict(error)) { + throw new ConflictError( + `IAM user ${id} still has attached resources. Remove group memberships, access keys, ` + + 'certificates, MFA devices, and policies before deleting the user.', + {cause: error}, + ) + } + throw error + } + } +} + +function toResource(user: User): CloudResource { + const userName = user.UserName ?? '' + return { + id: userName, + name: userName, + cloud: 'aws', + service: 'identity', + type: 'iam-user', + region: null, + createdAt: user.CreateDate?.toISOString() ?? null, + metadata: { + provider: 'aws', + identityService: 'iam', + userId: user.UserId, + arn: user.Arn, + path: user.Path, + permissionsBoundary: user.PermissionsBoundary, + tags: user.Tags, + }, + } +} + +function stringValue(value: unknown): string { + return typeof value === 'string' ? value.trim() : '' +} + +function filterBySearch(resources: CloudResource[], search?: string): CloudResource[] { + const normalized = search?.trim().toLowerCase() + if (!normalized) return resources + return resources.filter((resource) => resource.name.toLowerCase().includes(normalized)) +} + +function isNotFound(error: unknown): boolean { + if (typeof error !== 'object' || error === null) return false + const candidate = error as {name?: string; $metadata?: {httpStatusCode?: number}} + return candidate.name === 'NoSuchEntityException' || candidate.$metadata?.httpStatusCode === 404 +} + +function isDeleteConflict(error: unknown): boolean { + if (typeof error !== 'object' || error === null) return false + return (error as {name?: string}).name === 'DeleteConflictException' +} diff --git a/packages/api/src/adapter-aws/awsErrors.ts b/packages/api/src/adapter-aws/awsErrors.ts index 63e807f..1b02a9a 100644 --- a/packages/api/src/adapter-aws/awsErrors.ts +++ b/packages/api/src/adapter-aws/awsErrors.ts @@ -56,6 +56,7 @@ const CONFLICT_NAMES = new Set([ 'DBInstanceAlreadyExistsFault', 'ConcurrentModificationException', 'IncorrectState', + 'DeleteConflictException', ]) const ACCESS_DENIED_NAMES = new Set([ @@ -94,6 +95,7 @@ const NOT_FOUND_NAMES = new Set([ 'NoSuchBucket', 'NoSuchKey', 'NoSuchEntity', + 'NoSuchEntityException', 'NotFound', 'NotFoundException', 'ResourceNotFoundException', diff --git a/packages/api/src/aws.ts b/packages/api/src/aws.ts index e9ae0c9..f14e47d 100644 --- a/packages/api/src/aws.ts +++ b/packages/api/src/aws.ts @@ -4,6 +4,7 @@ import { EKSClient } from "@aws-sdk/client-eks"; import { EC2Client } from "@aws-sdk/client-ec2"; import { RDSClient } from "@aws-sdk/client-rds"; import { SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; +import { IAMClient } from "@aws-sdk/client-iam"; const endpoint = process.env.FLOCI_ENDPOINT; const region = process.env.AWS_REGION || "us-east-1"; @@ -39,6 +40,7 @@ export type AwsClients = { ec2: EC2Client; rds: RDSClient; secretsManager: SecretsManagerClient; + iam: IAMClient; }; export type AwsClientName = keyof AwsClients; @@ -58,6 +60,7 @@ function buildClients(accountId: string): AwsClients { ec2: new EC2Client(base), rds: new RDSClient(base), secretsManager: new SecretsManagerClient(base), + iam: new IAMClient(base), }; } @@ -89,3 +92,4 @@ export const eks = awsClients.eks; export const ec2 = awsClients.ec2; export const rds = awsClients.rds; export const secretsManager = awsClients.secretsManager; +export const iam = awsClients.iam; diff --git a/packages/api/src/cloud-spi/iamSchema.ts b/packages/api/src/cloud-spi/iamSchema.ts new file mode 100644 index 0000000..95cc4f7 --- /dev/null +++ b/packages/api/src/cloud-spi/iamSchema.ts @@ -0,0 +1,57 @@ +import type {CapabilitySchema, CloudProvider, ResourceActionName, ServiceSchema} from './types' + +export const IAM_USER_NAME_PATTERN = '^[A-Za-z0-9_+=,.@-]{1,64}$' +export const IAM_USER_NAME_MESSAGE = 'Use a valid IAM user name: 1-64 letters, numbers, and +=,.@_- characters.' +export const IAM_PATH_PATTERN = '^/(?:[!-~]+/)?$' +export const IAM_PATH_MAX_LENGTH = 512 +export const IAM_PATH_MESSAGE = 'Use a valid IAM path: begin and end with / and use only printable ASCII characters.' + +const resourceActions: CapabilitySchema[] = [ + {name: 'list', label: 'List users', enabled: true, status: 'available', runtimeRequired: true}, + {name: 'create', label: 'Create user', enabled: true, status: 'available', runtimeRequired: true}, + {name: 'delete', label: 'Delete user', enabled: true, status: 'available', runtimeRequired: true}, + {name: 'inspect', label: 'Inspect user', enabled: true, status: 'available', runtimeRequired: false}, +] + +export function awsIamSchema(): ServiceSchema { + return { + cloud: 'aws', + service: 'identity', + displayName: 'AWS IAM users', + fields: [ + { + name: 'userName', + label: 'User Name', + type: 'text', + required: true, + description: '1-64 letters, numbers, and +=,.@_- characters.', + validation: { + pattern: IAM_USER_NAME_PATTERN, + minLength: 1, + maxLength: 64, + message: IAM_USER_NAME_MESSAGE, + }, + }, + { + name: 'path', + label: 'Path', + type: 'text', + required: false, + description: 'Optional IAM path, beginning and ending with /. Defaults to /.', + validation: { + pattern: IAM_PATH_PATTERN, + maxLength: IAM_PATH_MAX_LENGTH, + message: IAM_PATH_MESSAGE, + }, + }, + ], + actions: ['list', 'create', 'delete', 'inspect'], + capabilities: {resourceActions}, + filters: [{name: 'search', label: 'Search', type: 'text', required: false}], + columns: [ + {name: 'name', label: 'User Name'}, + {name: 'type', label: 'Type'}, + {name: 'createdAt', label: 'Created At'}, + ], + } +} diff --git a/packages/api/src/cloud-spi/serviceCatalog.ts b/packages/api/src/cloud-spi/serviceCatalog.ts index 92df8a1..e08dc9b 100644 --- a/packages/api/src/cloud-spi/serviceCatalog.ts +++ b/packages/api/src/cloud-spi/serviceCatalog.ts @@ -73,6 +73,7 @@ export const SERVICE_CATALOG = { storage: {displayName: 'Storage', iconKey: 'storage', group: 'Storage', order: 10}, database: {displayName: 'Database', iconKey: 'database', group: 'Databases', order: 10}, networking: {displayName: 'Networking', iconKey: 'networking', group: 'Networking', order: 10}, + identity: {displayName: 'Identity', iconKey: 'iam', group: 'Security', order: 5}, secrets: { displayName: 'Secrets Manager', displayNameByCloud: {azure: 'Key Vault'}, diff --git a/packages/api/src/cloud-spi/types.ts b/packages/api/src/cloud-spi/types.ts index 69ea0ba..8262696 100644 --- a/packages/api/src/cloud-spi/types.ts +++ b/packages/api/src/cloud-spi/types.ts @@ -152,7 +152,7 @@ export interface CloudResource { name: string cloud: CloudProvider service: CloudServiceType - type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | 'azure-function' | 'gcp-function' | 'secret' + type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | 'azure-function' | 'gcp-function' | 'secret' | 'iam-user' region: string | null createdAt: string | null status?: string | null diff --git a/packages/api/src/cloudProxy.ts b/packages/api/src/cloudProxy.ts index 6240686..3f20f35 100644 --- a/packages/api/src/cloudProxy.ts +++ b/packages/api/src/cloudProxy.ts @@ -14,6 +14,7 @@ import {CloudProxyService} from './service/CloudProxyService' import {AzureServerlessAdapter} from './adapter-azure/AzureServerlessAdapter' import {AzureKeyVaultAdapter} from './adapter-azure/AzureKeyVaultAdapter' import {AwsServerlessAdapter} from './adapter-aws/AwsServerlessAdapter' +import {AwsIamAdapter} from './adapter-aws/AwsIamAdapter' import {awsClientsForAccount, resolveAccountId} from './aws' import {createEc2Service} from './services/ec2' import {createEksService} from './services/eks' @@ -39,6 +40,7 @@ export function createCloudAdapterRegistry(accountId?: string | null): CloudAdap new AwsComputeAdapter(ec2Service), new AwsNetworkingAdapter(ec2Service), new AwsServerlessAdapter(clients.lambda), + new AwsIamAdapter(clients.iam), new AzureStorageAdapter(), new AzureDatabaseAdapter(), new GcpStorageAdapter(), diff --git a/packages/frontend/src/components/DynamicResourceView.tsx b/packages/frontend/src/components/DynamicResourceView.tsx index 8770a45..0f7fd68 100644 --- a/packages/frontend/src/components/DynamicResourceView.tsx +++ b/packages/frontend/src/components/DynamicResourceView.tsx @@ -359,6 +359,8 @@ function resourceCreateLabel(schema: ServiceSchema): string { return "Create container"; if (schema.cloud === "azure" && schema.service === "database") return "Create database"; + if (schema.cloud === "aws" && schema.service === "identity") + return "Create user"; if (schema.cloud === "azure" && schema.service === "secrets") return "Create secret"; return "Create resource"; diff --git a/packages/frontend/src/types/resource.ts b/packages/frontend/src/types/resource.ts index a69e1df..c63a056 100644 --- a/packages/frontend/src/types/resource.ts +++ b/packages/frontend/src/types/resource.ts @@ -5,7 +5,7 @@ export interface CloudResource { name: string cloud: CloudProvider service: CloudServiceType - type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | "azure-function" | 'gcp-function' | 'secret'; + type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | "azure-function" | 'gcp-function' | 'secret' | 'iam-user'; region: string | null createdAt: string | null status?: string | null diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f04df5d..1673560 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,9 @@ importers: '@aws-sdk/client-eks': specifier: ^3.1076.0 version: 3.1076.0 + '@aws-sdk/client-iam': + specifier: ^3.1076.0 + version: 3.1076.0 '@aws-sdk/client-lambda': specifier: ^3.1076.0 version: 3.1076.0 @@ -157,6 +160,10 @@ packages: resolution: {integrity: sha512-Hqr3hdwUgqWAQbdx6UDy81UBF+jdu3ws6RrykQ4YmBhe/XOKv57lt2lhkjgBuF9fiSw4UUl2XQteks9oxgm4XA==} engines: {node: '>=20.0.0'} + '@aws-sdk/client-iam@3.1076.0': + resolution: {integrity: sha512-9joUyOTN8KZUTi2UIuvD+kyiYImM9E40cXwNYUvc2plV18OQ0GoXcBE3Xey6czc+rtsgvq6Grp3UHFGuxfZmoQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/client-lambda@3.1076.0': resolution: {integrity: sha512-uueu0Do1dy3cm42AdjL3zivHoc5b55vOL3g6BjxUv2xMifSLKZi5Ilh5FhXVDdS/Uwxak/JH+vVQDDnEuQ52gA==} engines: {node: '>=20.0.0'} @@ -177,38 +184,70 @@ packages: resolution: {integrity: sha512-4/1DtLwgLqzIg2uFzkFaFjMQHhhhwHIZN4PfziIVqXYX7koO78omuchQlLHyzQBw80l255dtmTA/J4W5yhb7zw==} engines: {node: '>=20.0.0'} + '@aws-sdk/core@3.975.3': + resolution: {integrity: sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-env@3.972.54': resolution: {integrity: sha512-F4WQCG8GULIt+XrMHsqUM9dZc0eTwZM3HUWByOjIKOwBqJSzUxX8CFAtbgMvWfCua52FS1oi5FXarH3+khFq8A==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-env@3.972.59': + resolution: {integrity: sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.972.56': resolution: {integrity: sha512-PiwHgEK2srdfi/lFveyQ+3w/qikyh6MKvuZAdlMC+dwQi4K5iVBr32oh7cugnYw+jA8iGtnQMhCGvLm/mqplmw==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-http@3.972.61': + resolution: {integrity: sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-ini@3.972.61': resolution: {integrity: sha512-9CZWMjhBlgfKlqJk40R7kvMOLEIoOr3HJ1J/ELjAH9H5LzR4bCxLujPVM/1PKAEjzSZKZCxWOalm7JUGZbhQqw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.60': - resolution: {integrity: sha512-Ak6OOrCbXvACyxLFIP1mcS+JTLS9ZpW1ZqyBtqu6axvdpsbG1gVNhUlAfQq8TK/gar/h2w35LrzlQU0PcUzJpw==} + '@aws-sdk/credential-provider-ini@3.973.4': + resolution: {integrity: sha512-e6ZvVsj90aRALf1kHP+J4iqC1496ZpVgqI/+u0LJ5HL7q7ATauGy4gdDvRCP13L1pN/fMiZLah162PGIYkbUVQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.66': + resolution: {integrity: sha512-g2fsqm87r/nKthLZ0VkkDBElkGg0PvSa8d97HQ6EilMbJTZ6hxa8FxkSZyJfgPfFdZn0TTmkOffQmTSUcAHIng==} engines: {node: '>=20.0.0'} '@aws-sdk/credential-provider-node@3.972.63': resolution: {integrity: sha512-YmgWtTPZDStyT74ApSHpApD3r7W9znsc+WEZjW0vceom+NAxRx9/F3TyukOKix8kJkPaa49aIREQJdpGeLDiEw==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-node@3.972.70': + resolution: {integrity: sha512-3xzvkGdykBunxqh8WudmUpSyLWvIhfI6aBQo1b5rb3mDO5mNLadK+0hiI0qBQBMVynJbfLO+Ajy9dztMwy9O8w==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.972.54': resolution: {integrity: sha512-UNmUjtTnp3wH3YTZcctN7yK17S2AdaJpiNIdIf0l70hHOdGuDfdMxk62P5rt64GwGm6qkFOYG0js/cU6cdZDdg==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-process@3.972.59': + resolution: {integrity: sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.972.60': resolution: {integrity: sha512-zH8SvJkTRw1Kb7GARjGPjzkQPfL3jDi/OxK32I5SQq7bgwgFHJZaoDEwkEvFlfNfpByM6n4Rs20Y1mLyOdb6ag==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-sso@3.973.3': + resolution: {integrity: sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.60': resolution: {integrity: sha512-q2rJSQ/AMjemUS18OtQqczqSo2R6VjOCLmLVmLVcdrP5/AKoj632lGCrMUWQ6EgnaLMEHbq0UO4mWh/u6gjPhA==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-provider-web-identity@3.972.65': + resolution: {integrity: sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-flexible-checksums@3.974.38': resolution: {integrity: sha512-grguw9ssx2zVIn8/KpgQ7AQLjBE/YPwOns94n0seWkNWS3dJ7k7VFD2voAkh9Xvf8Azxw8Ajc5+KicdClucMqQ==} engines: {node: '>=20.0.0'} @@ -225,22 +264,34 @@ packages: resolution: {integrity: sha512-KCf/UjbnzV3QIXR1C2MeE9eRUG8EUXUf0V4y65hf2bKWQXzol5f3I8wOhE6OBdEYohn8zHfnC/cyy5+s7/HwLw==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.28': - resolution: {integrity: sha512-1oG/mM3jmE2M3kad2zWJS6IKIY8hjRV4l5kAgg+xTQdLMTtehhcSucL/y4WqQpcHmQwi6+gRK8E46GYl1NBW9Q==} + '@aws-sdk/nested-clients@3.997.33': + resolution: {integrity: sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==} engines: {node: '>=20.0.0'} '@aws-sdk/signature-v4-multi-region@3.996.38': resolution: {integrity: sha512-C379Sk+MiFZCfWZphKlMyLHKxV22OjoGM5KJjj5IJNJcOCWL4IGIpnEGzv1FQiRwhYXfq55SJMfxlqPE08JJ9g==} engines: {node: '>=20.0.0'} + '@aws-sdk/signature-v4-multi-region@3.996.41': + resolution: {integrity: sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==} + engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1080.0': resolution: {integrity: sha512-8PufAQvncWXvdZUvODbuyXa8l3aszefEzwSMBUcgheNbZOmJMcNn388Ebt/piVrUHyxKN1MlxnR2OonzTyZaGw==} engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1088.0': + resolution: {integrity: sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==} + engines: {node: '>=20.0.0'} + '@aws-sdk/types@3.973.15': resolution: {integrity: sha512-IULn8uBV/SMtmOIANsm4WHXIOtVPBWfOWs3WGL0j/sI+KhaYehvOw0ET+9urnn8MBpiijuU/0JOpuwKOE451PQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/types@3.974.2': + resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/util-locate-window@3.965.8': resolution: {integrity: sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==} engines: {node: '>=20.0.0'} @@ -249,6 +300,10 @@ packages: resolution: {integrity: sha512-ezbwz9WpuLctm6o7P2t2naDhVVPI5jFGrVefVybhcKGjU57VIyT46pQVO0RI2RYkUdhdj2Z9uSIlAzGZE9NW9A==} engines: {node: '>=20.0.0'} + '@aws-sdk/xml-builder@3.972.36': + resolution: {integrity: sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==} + engines: {node: '>=20.0.0'} + '@aws/lambda-invoke-store@0.3.0': resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} engines: {node: '>=18.0.0'} @@ -509,6 +564,14 @@ packages: resolution: {integrity: sha512-qoiY4nrk5OCu1+eIR1VB8l5DmON/oKiqrd5zZFAhXJXjJlLWQusKEW/SkBDAtGDcPaz86m9kfcE1lngU0GlM6A==} engines: {node: '>=18.0.0'} + '@smithy/core@3.29.5': + resolution: {integrity: sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.4.10': + resolution: {integrity: sha512-MJenAe4OKRZUo1LdYYFDCsSHxaHvInIU/z52GsheO9vl1/VSySVCr0zkyKD6TFiGkSUaWGxvKZ/70OvgUZR5HQ==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.4.6': resolution: {integrity: sha512-B2WQ/PV/H6Jeg3lrIq6bKUfa6Hy01mtK7CGs6lhjzHA6k4aagldH6T6eEjnzKl4HI0cJnAsxfJ19pgb5PV+CVQ==} engines: {node: '>=18.0.0'} @@ -517,6 +580,10 @@ packages: resolution: {integrity: sha512-CwCc/7SMTj45y97MUnDTbTaxvtAsiNNRm81z3abROIuMbMsC2Iy5EKfkkVdsKrz8WExQAAMx1EJapq+9j4fFTQ==} engines: {node: '>=18.0.0'} + '@smithy/fetch-http-handler@5.6.7': + resolution: {integrity: sha512-3zpg8yqqyXzoK2TsRDdkqVOj2RDBFfLXwCczOZ5c7TWB4eiaebfSCsbMjDPYB3PJ9ihV62QaeadZ+wLadZtNGA==} + engines: {node: '>=18.0.0'} + '@smithy/is-array-buffer@2.2.0': resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} @@ -525,14 +592,26 @@ packages: resolution: {integrity: sha512-qZTa4gQFUo8RM02rk6q5UVTDLNrQ1oS20LsepBzqq1QBVc/EHJ03OOUADcqMZiXHArW+Y7+OGY0BpdTwZRq/Yg==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.9.7': + resolution: {integrity: sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==} + engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.6.2': resolution: {integrity: sha512-QgHflghMoPxCJ9axiCVh8KZfbC9fuP6vkXXyK//E3cq7nLaSSyyLj0GAoqVWezYeDQmXIZhmlRvLE16jsqDK6g==} engines: {node: '>=18.0.0'} + '@smithy/signature-v4@5.6.6': + resolution: {integrity: sha512-efP6DN3UTFrzIsGO42/xcabv8jU7+9nwEdphFUH7yL0k010ERyAWaO41KFQIDLcFZLZ8xzIQr4wplFxNzslSGQ==} + engines: {node: '>=18.0.0'} + '@smithy/types@4.15.1': resolution: {integrity: sha512-x3L0XSACF6UYzKpa9biqiRMgvH5+wnFFew9Tm/grFYqgaupPwx/+ojDPpPJM8dZON3S9tjz5U+PQYsCBd1Mw5Q==} engines: {node: '>=18.0.0'} + '@smithy/types@4.16.1': + resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==} + engines: {node: '>=18.0.0'} + '@smithy/util-buffer-from@2.2.0': resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} engines: {node: '>=14.0.0'} @@ -1302,16 +1381,16 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.973.15 + '@aws-sdk/types': 3.974.2 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 '@aws-sdk/checksums@3.1000.13': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/client-ec2@3.1076.0': @@ -1341,6 +1420,19 @@ snapshots: '@smithy/types': 4.15.1 tslib: 2.8.1 + '@aws-sdk/client-iam@3.1076.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/credential-provider-node': 3.972.70 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/fetch-http-handler': 5.6.7 + '@smithy/node-http-handler': 4.9.7 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/client-lambda@3.1076.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -1409,47 +1501,92 @@ snapshots: bowser: 2.14.1 tslib: 2.8.1 + '@aws-sdk/core@3.975.3': + dependencies: + '@aws-sdk/types': 3.974.2 + '@aws-sdk/xml-builder': 3.972.36 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.29.5 + '@smithy/signature-v4': 5.6.6 + '@smithy/types': 4.16.1 + bowser: 2.14.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-env@3.972.54': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.59': + dependencies: + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/credential-provider-http@3.972.56': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/fetch-http-handler': 5.6.3 - '@smithy/node-http-handler': 4.9.3 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/fetch-http-handler': 5.6.7 + '@smithy/node-http-handler': 4.9.7 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.61': + dependencies: + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/fetch-http-handler': 5.6.7 + '@smithy/node-http-handler': 4.9.7 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/credential-provider-ini@3.972.61': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/credential-provider-env': 3.972.54 - '@aws-sdk/credential-provider-http': 3.972.56 - '@aws-sdk/credential-provider-login': 3.972.60 - '@aws-sdk/credential-provider-process': 3.972.54 - '@aws-sdk/credential-provider-sso': 3.972.60 - '@aws-sdk/credential-provider-web-identity': 3.972.60 - '@aws-sdk/nested-clients': 3.997.28 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/credential-provider-imds': 4.4.6 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/credential-provider-env': 3.972.59 + '@aws-sdk/credential-provider-http': 3.972.61 + '@aws-sdk/credential-provider-login': 3.972.66 + '@aws-sdk/credential-provider-process': 3.972.59 + '@aws-sdk/credential-provider-sso': 3.973.3 + '@aws-sdk/credential-provider-web-identity': 3.972.65 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/credential-provider-imds': 4.4.10 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.973.4': + dependencies: + '@aws-sdk/core': 3.975.3 + '@aws-sdk/credential-provider-env': 3.972.59 + '@aws-sdk/credential-provider-http': 3.972.61 + '@aws-sdk/credential-provider-login': 3.972.66 + '@aws-sdk/credential-provider-process': 3.972.59 + '@aws-sdk/credential-provider-sso': 3.973.3 + '@aws-sdk/credential-provider-web-identity': 3.972.65 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/credential-provider-imds': 4.4.10 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.60': + '@aws-sdk/credential-provider-login@3.972.66': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/nested-clients': 3.997.28 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/credential-provider-node@3.972.63': @@ -1466,31 +1603,72 @@ snapshots: '@smithy/types': 4.15.1 tslib: 2.8.1 + '@aws-sdk/credential-provider-node@3.972.70': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.59 + '@aws-sdk/credential-provider-http': 3.972.61 + '@aws-sdk/credential-provider-ini': 3.973.4 + '@aws-sdk/credential-provider-process': 3.972.59 + '@aws-sdk/credential-provider-sso': 3.973.3 + '@aws-sdk/credential-provider-web-identity': 3.972.65 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/credential-provider-imds': 4.4.10 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/credential-provider-process@3.972.54': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.59': + dependencies: + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/credential-provider-sso@3.972.60': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/nested-clients': 3.997.28 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 '@aws-sdk/token-providers': 3.1080.0 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.973.3': + dependencies: + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/token-providers': 3.1088.0 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/credential-provider-web-identity@3.972.60': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/nested-clients': 3.997.28 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.65': + dependencies: + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/middleware-flexible-checksums@3.974.38': @@ -1525,15 +1703,15 @@ snapshots: '@smithy/types': 4.15.1 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.28': + '@aws-sdk/nested-clients@3.997.33': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/signature-v4-multi-region': 3.996.38 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/fetch-http-handler': 5.6.3 - '@smithy/node-http-handler': 4.9.3 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/signature-v4-multi-region': 3.996.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/fetch-http-handler': 5.6.7 + '@smithy/node-http-handler': 4.9.7 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/signature-v4-multi-region@3.996.38': @@ -1543,13 +1721,29 @@ snapshots: '@smithy/types': 4.15.1 tslib: 2.8.1 + '@aws-sdk/signature-v4-multi-region@3.996.41': + dependencies: + '@aws-sdk/types': 3.974.2 + '@smithy/signature-v4': 5.6.6 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/token-providers@3.1080.0': dependencies: - '@aws-sdk/core': 3.974.28 - '@aws-sdk/nested-clients': 3.997.28 - '@aws-sdk/types': 3.973.15 - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1088.0': + dependencies: + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/types@3.973.15': @@ -1557,13 +1751,23 @@ snapshots: '@smithy/types': 4.15.1 tslib: 2.8.1 + '@aws-sdk/types@3.974.2': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@aws-sdk/util-locate-window@3.965.8': dependencies: tslib: 2.8.1 '@aws-sdk/xml-builder@3.972.33': dependencies: - '@smithy/types': 4.15.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.36': + dependencies: + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws/lambda-invoke-store@0.3.0': {} @@ -1818,10 +2022,21 @@ snapshots: '@smithy/types': 4.15.1 tslib: 2.8.1 + '@smithy/core@3.29.5': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.4.10': + dependencies: + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/credential-provider-imds@4.4.6': dependencies: - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@smithy/fetch-http-handler@5.6.3': @@ -1830,6 +2045,12 @@ snapshots: '@smithy/types': 4.15.1 tslib: 2.8.1 + '@smithy/fetch-http-handler@5.6.7': + dependencies: + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/is-array-buffer@2.2.0': dependencies: tslib: 2.8.1 @@ -1840,16 +2061,32 @@ snapshots: '@smithy/types': 4.15.1 tslib: 2.8.1 + '@smithy/node-http-handler@4.9.7': + dependencies: + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/signature-v4@5.6.2': dependencies: - '@smithy/core': 3.29.1 - '@smithy/types': 4.15.1 + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@smithy/signature-v4@5.6.6': + dependencies: + '@smithy/core': 3.29.5 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@smithy/types@4.15.1': dependencies: tslib: 2.8.1 + '@smithy/types@4.16.1': + dependencies: + tslib: 2.8.1 + '@smithy/util-buffer-from@2.2.0': dependencies: '@smithy/is-array-buffer': 2.2.0 From 35edfb92539da83f41c05299e71ed9aeec7b0d27 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 18 Aug 2026 06:36:15 +0100 Subject: [PATCH 2/2] fix(iam): type empty create responses --- packages/api/src/adapter-aws/AwsIamAdapter.test.ts | 9 +++++++++ packages/api/src/adapter-aws/AwsIamAdapter.ts | 4 ++-- packages/api/src/cloud-spi/iamSchema.ts | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/api/src/adapter-aws/AwsIamAdapter.test.ts b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts index 6a752c7..595c1c8 100644 --- a/packages/api/src/adapter-aws/AwsIamAdapter.test.ts +++ b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts @@ -92,6 +92,15 @@ describe('AwsIamAdapter', () => { expect(result.id).toBe('alice') }) + test('reports an empty create response as a runtime error', async () => { + const adapter = new AwsIamAdapter(fakeClient(() => ({}))) + + await expect(adapter.create({values: {userName: 'alice'}})).rejects.toMatchObject({ + name: 'RuntimeError', + message: 'AWS IAM did not return the created user', + }) + }) + test('rejects invalid user names before calling IAM', async () => { let called = false const adapter = new AwsIamAdapter(fakeClient(() => { diff --git a/packages/api/src/adapter-aws/AwsIamAdapter.ts b/packages/api/src/adapter-aws/AwsIamAdapter.ts index 5b8093d..faef1fe 100644 --- a/packages/api/src/adapter-aws/AwsIamAdapter.ts +++ b/packages/api/src/adapter-aws/AwsIamAdapter.ts @@ -15,7 +15,7 @@ import { IAM_USER_NAME_MESSAGE, IAM_USER_NAME_PATTERN, } from '../cloud-spi/iamSchema' -import {ConflictError, NotFoundError, ValidationError} from '../cloud-spi/errors' +import {ConflictError, NotFoundError, RuntimeError, ValidationError} from '../cloud-spi/errors' import type {CloudResource, CloudServiceAdapter, CreateResourceInput, ResourceQuery, ServiceSchema} from '../cloud-spi/types' const MAX_LIST_PAGES = 50 @@ -68,7 +68,7 @@ export class AwsIamAdapter implements CloudServiceAdapter { UserName: userName, Path: path || undefined, })) - if (!res.User) throw new Error('AWS IAM did not return the created user') + if (!res.User) throw new RuntimeError('AWS IAM did not return the created user') return toResource(res.User) } diff --git a/packages/api/src/cloud-spi/iamSchema.ts b/packages/api/src/cloud-spi/iamSchema.ts index 95cc4f7..0847013 100644 --- a/packages/api/src/cloud-spi/iamSchema.ts +++ b/packages/api/src/cloud-spi/iamSchema.ts @@ -1,4 +1,4 @@ -import type {CapabilitySchema, CloudProvider, ResourceActionName, ServiceSchema} from './types' +import type {CapabilitySchema, ResourceActionName, ServiceSchema} from './types' export const IAM_USER_NAME_PATTERN = '^[A-Za-z0-9_+=,.@-]{1,64}$' export const IAM_USER_NAME_MESSAGE = 'Use a valid IAM user name: 1-64 letters, numbers, and +=,.@_- characters.'