diff --git a/README.md b/README.md index 1c4bc79..bbd6fb8 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,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/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..c4d5a99 --- /dev/null +++ b/packages/api/src/adapter-aws/AwsIamAdapter.test.ts @@ -0,0 +1,127 @@ +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('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..cbc7689 --- /dev/null +++ b/packages/api/src/adapter-aws/AwsIamAdapter.ts @@ -0,0 +1,106 @@ +import { + CreateUserCommand, + DeleteUserCommand, + GetUserCommand, + ListUsersCommand, + type IAMClient, + type User, +} from '@aws-sdk/client-iam' +import {iam as defaultIam} from '../aws' +import {awsIamSchema} from '../cloud-spi/iamSchema' +import type {CloudResource, CloudServiceAdapter, CreateResourceInput, ResourceQuery, ServiceSchema} from '../cloud-spi/types' + +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 + + do { + const res = await this.iam.send(new ListUsersCommand({Marker: marker})) + users.push(...(res.Users ?? [])) + marker = res.IsTruncated ? res.Marker : undefined + } while (marker) + + 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 Error('userName is required') + if (!/^[A-Za-z0-9_+=,.@-]{1,64}$/.test(userName)) { + throw new Error('Use a valid IAM user name: 1-64 letters, numbers, and +=,.@_- characters.') + } + if (path && (path.length > 512 || !/^\/(?:[!-~]+\/)?$/.test(path))) { + throw new Error('Use a valid IAM path: begin and end with / and use only printable ASCII characters.') + } + + 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 { + await this.iam.send(new DeleteUserCommand({UserName: id})) + } +} + +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 +} 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..db7e816 --- /dev/null +++ b/packages/api/src/cloud-spi/iamSchema.ts @@ -0,0 +1,55 @@ +import type {CapabilitySchema, CloudProvider, ResourceActionName, ServiceSchema} from './types' + +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: '^[A-Za-z0-9_+=,.@-]{1,64}$', + minLength: 1, + maxLength: 64, + message: 'Use a valid IAM user name: 1-64 letters, numbers, and +=,.@_- characters.', + }, + }, + { + name: 'path', + label: 'Path', + type: 'text', + required: false, + description: 'Optional IAM path, beginning and ending with /. Defaults to /.', + validation: { + pattern: '^/(?:[!-~]+/)?$', + maxLength: 512, + message: 'Use a valid IAM path: begin and end with / and use only printable ASCII characters.', + }, + }, + ], + 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'}, + ], + } +} + +export function iamSchemaFor(cloud: CloudProvider): ServiceSchema | null { + return cloud === 'aws' ? awsIamSchema() : null +} diff --git a/packages/api/src/cloud-spi/types.ts b/packages/api/src/cloud-spi/types.ts index bf9da0d..998ac63 100644 --- a/packages/api/src/cloud-spi/types.ts +++ b/packages/api/src/cloud-spi/types.ts @@ -1,6 +1,6 @@ export type CloudProvider = 'aws' | 'azure' | 'gcp' -export type CloudServiceType = 'storage' | 'k8s' | 'database' | 'serverless' | 'compute' | 'networking' +export type CloudServiceType = 'storage' | 'k8s' | 'database' | 'serverless' | 'compute' | 'networking' | 'identity' export type CloudAvailability = 'available' | 'coming_soon' @@ -83,7 +83,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' + type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | 'azure-function' | 'gcp-function' | '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 b9740c0..8417a58 100644 --- a/packages/api/src/cloudProxy.ts +++ b/packages/api/src/cloudProxy.ts @@ -11,6 +11,7 @@ import {GcpCloudFunctionsAdapter} from './adapter-gcp/GcpCloudFunctionsAdapter' import {CloudProxyService} from './service/CloudProxyService' import {AzureServerlessAdapter} from './adapter-azure/AzureServerlessAdapter' 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' @@ -33,6 +34,7 @@ export function createCloudProxyService(accountId?: string | null): CloudProxySe new AwsComputeAdapter(ec2Service), new AwsNetworkingAdapter(ec2Service), new AwsServerlessAdapter(clients.lambda), + new AwsIamAdapter(clients.iam), new AzureStorageAdapter(), new AzureDatabaseAdapter(), new GcpStorageAdapter(), diff --git a/packages/api/src/routes/clouds.test.ts b/packages/api/src/routes/clouds.test.ts index 49296be..b107224 100644 --- a/packages/api/src/routes/clouds.test.ts +++ b/packages/api/src/routes/clouds.test.ts @@ -2,6 +2,7 @@ import {describe, expect, test} from 'bun:test' import {Hono} from 'hono' import {azureDatabaseSchema} from '../cloud-spi/databaseSchema' import {awsStorageSchema, azureStorageSchema} from '../cloud-spi/storageSchema' +import {awsIamSchema} from '../cloud-spi/iamSchema' import type {CloudResource, CloudServiceAdapter, CosmosContainer, CosmosItem, CosmosQueryResult, CreateResourceInput} from '../cloud-spi/types' import {CloudAdapterRegistry} from '../registry/CloudAdapterRegistry' import {CloudProxyService} from '../service/CloudProxyService' @@ -68,6 +69,20 @@ describe('cloud schema routes', () => { expect(body.fields[0].name).toBe('containerName') }) + test('returns AWS IAM schema through the identity service route', async () => { + const app = appWithRoutes([mockAdapter('aws', { + service: 'identity', + schema: awsIamSchema, + })]) + const res = await app.request('/api/clouds/aws/services/identity/schema') + const body = await res.json() + + expect(res.status).toBe(200) + expect(body.cloud).toBe('aws') + expect(body.service).toBe('identity') + expect(body.displayName).toBe('AWS IAM users') + }) + test('returns Azure database schema when the adapter is registered', async () => { const app = appWithRoutes([mockAdapter('azure', { service: 'database', @@ -312,4 +327,42 @@ describe('cloud schema routes', () => { expect(body.code).toBe('operation_not_implemented') expect(body.message).toBe('Operation is not implemented by the selected runtime') }) + + test('normalizes adapter validation errors', async () => { + const app = appWithRoutes([mockAdapter('aws', { + service: 'identity', + schema: awsIamSchema, + create: async () => { + throw new Error('Use a valid IAM path: begin and end with /.') + }, + })]) + + const res = await app.request('/api/clouds/aws/services/identity/resources', { + method: 'POST', + body: JSON.stringify({userName: 'alice', path: 'team'}), + }) + const body = await res.json() + + expect(res.status).toBe(400) + expect(body.code).toBe('invalid_request') + expect(body.message).toContain('Use a valid IAM path') + }) + + test('normalizes IAM delete conflicts', async () => { + const app = appWithRoutes([mockAdapter('aws', { + service: 'identity', + schema: awsIamSchema, + delete: async () => { + throw Object.assign(new Error('Cannot delete entity'), {name: 'DeleteConflictException'}) + }, + })]) + + const res = await app.request('/api/clouds/aws/services/identity/resources/alice', {method: 'DELETE'}) + const body = await res.json() + + expect(res.status).toBe(409) + expect(body.code).toBe('resource_conflict') + expect(body.message).toBe('IAM user has attached resources') + expect(body.detail).toContain('access keys') + }) }) diff --git a/packages/api/src/routes/clouds.ts b/packages/api/src/routes/clouds.ts index 93cb831..63c0cc9 100644 --- a/packages/api/src/routes/clouds.ts +++ b/packages/api/src/routes/clouds.ts @@ -261,7 +261,7 @@ function isCloudProvider(value: string): value is CloudProvider { } function isServiceType(value: string): value is CloudServiceType { - return value === 'storage' || value === 'k8s' || value === 'database' || value === 'serverless' || value === 'compute' || value === 'networking' + return value === 'storage' || value === 'k8s' || value === 'database' || value === 'serverless' || value === 'compute' || value === 'networking' || value === 'identity' } async function withRuntime(c: Context, handler: () => Promise): Promise { @@ -274,10 +274,11 @@ async function withRuntime(c: Context, handler: () => Promise): Promis } function normalizeRuntimeError(err: unknown): { - status: 400 | 404 | 501 | 502 | 503 + status: 400 | 404 | 409 | 501 | 502 | 503 body: {error: string; code: string; message: string; detail?: string} } { const message = err instanceof Error ? err.message : 'Runtime request failed' + const errorName = err instanceof Error ? err.name : '' if (message.includes('Cannot reach')) { return errorResponse(503, 'runtime_unavailable', 'Runtime unavailable', message) @@ -304,6 +305,15 @@ function normalizeRuntimeError(err: unknown): { return errorResponse(501, 'operation_not_supported', 'Operation is not supported by this adapter', message) } + if (errorName === 'DeleteConflictException') { + return errorResponse( + 409, + 'resource_conflict', + 'IAM user has attached resources', + 'Remove group memberships, access keys, certificates, MFA devices, and policies before deleting the user.', + ) + } + if (message.includes('is required') || message.includes('Use a valid')) { return errorResponse(400, 'invalid_request', message) } @@ -312,12 +322,12 @@ function normalizeRuntimeError(err: unknown): { } function errorResponse( - status: 400 | 404 | 501 | 502 | 503, + status: 400 | 404 | 409 | 501 | 502 | 503, code: string, message: string, detail?: string, ): { - status: 400 | 404 | 501 | 502 | 503 + status: 400 | 404 | 409 | 501 | 502 | 503 body: {error: string; code: string; message: string; detail?: string} } { return { diff --git a/packages/api/src/service/CloudProxyService.ts b/packages/api/src/service/CloudProxyService.ts index 826bf12..ebe9e3c 100644 --- a/packages/api/src/service/CloudProxyService.ts +++ b/packages/api/src/service/CloudProxyService.ts @@ -20,6 +20,7 @@ import {CloudAdapterRegistry} from '../registry/CloudAdapterRegistry' import {serverlessSchemaFor} from '../cloud-spi/serverlessSchema' import {k8sSchemaFor} from '../cloud-spi/eksSchema' import {databaseSchemaFor} from '../cloud-spi/databaseSchema' +import {iamSchemaFor} from '../cloud-spi/iamSchema' import {azureEndpoint} from '../azure' import {checkGcpRuntime, gcpEndpoint} from '../gcp' @@ -73,6 +74,12 @@ export class CloudProxyService { displayName: 'Networking', availability: this.registry.get(cloud, 'networking') ? 'available' : 'coming_soon', }) + services.push({ + cloud, + service: 'identity', + displayName: 'Identity', + availability: this.registry.get(cloud, 'identity') ? 'available' : 'coming_soon', + }) return services } @@ -83,6 +90,7 @@ export class CloudProxyService { if (service === 'k8s') return k8sSchemaFor(cloud) if (service === 'database') return databaseSchemaFor(cloud) if (service === 'serverless') return serverlessSchemaFor(cloud) + if (service === 'identity') return iamSchemaFor(cloud) return null } diff --git a/packages/frontend/src/components/DynamicResourceView.tsx b/packages/frontend/src/components/DynamicResourceView.tsx index 706b77b..2410b41 100644 --- a/packages/frontend/src/components/DynamicResourceView.tsx +++ b/packages/frontend/src/components/DynamicResourceView.tsx @@ -355,6 +355,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"; return "Create resource"; } diff --git a/packages/frontend/src/components/Layout.tsx b/packages/frontend/src/components/Layout.tsx index 31a1891..5624618 100644 --- a/packages/frontend/src/components/Layout.tsx +++ b/packages/frontend/src/components/Layout.tsx @@ -9,6 +9,7 @@ import { Network, Search, Server, + ShieldCheck, Sun, Table2, Zap, @@ -40,6 +41,7 @@ const CLOUD_SERVICE_ICONS = { compute: Server, networking: Network, serverless: Zap, + identity: ShieldCheck, } satisfies Record type CloudSidebarService = keyof typeof CLOUD_SERVICE_ICONS @@ -50,6 +52,7 @@ const CLOUD_SERVICE_ITEMS: Array<{name: CloudSidebarService; label: string; rout {name: 'database', label: 'Database', route: 'database'}, {name: 'compute', label: 'Compute', route: 'compute'}, {name: 'networking', label: 'Networking', route: 'networking'}, + {name: 'identity', label: 'Identity', route: 'identity'}, {name: 'secretsmanager', label: 'Secrets Manager', route: '/secretsmanager'}, {name: 'serverless', label: 'Serverless', route: 'serverless'}, {name: 'queue', label: 'Queue'}, @@ -70,6 +73,7 @@ function CloudServiceNav() { || (service.name === 'secretsmanager' && cloud === 'aws') || (service.name === 'database' && (cloud === 'aws' || cloud === 'azure')) || ((service.name === 'k8s' || service.name === 'compute' || service.name === 'networking') && cloud === 'aws') + || (service.name === 'identity' && cloud === 'aws') || (service.name === 'serverless' && (cloud === 'aws' || cloud === 'azure')) if (service.route && available) { const target = service.route.startsWith('/') ? service.route : `/cloud-explorer/${cloud}/${service.route}` diff --git a/packages/frontend/src/pages/CloudExplorerPage.tsx b/packages/frontend/src/pages/CloudExplorerPage.tsx index 37efef0..a7a5583 100644 --- a/packages/frontend/src/pages/CloudExplorerPage.tsx +++ b/packages/frontend/src/pages/CloudExplorerPage.tsx @@ -102,7 +102,7 @@ function normalizeCloud(value?: string): CloudProvider | null { } function normalizeService(value?: string): CloudServiceType | null { - return value === 'storage' || value === 'k8s' || value === 'database' || value === 'compute' || value === 'networking' || value === 'serverless' ? value : null + return value === 'storage' || value === 'k8s' || value === 'database' || value === 'compute' || value === 'networking' || value === 'serverless' || value === 'identity' ? value : null } function ServiceInfoDialog({ @@ -256,6 +256,7 @@ function connectionValue(status?: CloudStatus, loading?: boolean): string { function serviceLabel(service: CloudServiceType): string { if (service === 'k8s') return 'k8s Engine' if (service === 'serverless') return 'Serverless' + if (service === 'identity') return 'Identity' return service.charAt(0).toUpperCase() + service.slice(1) } @@ -265,5 +266,6 @@ function limitationCopy(cloud: CloudProvider, service: CloudServiceType): string if (cloud === 'azure' && service === 'database') return 'Cosmos DB uses a richer panel below for databases, containers, items, and SQL queries; a fully normalized database model is still evolving.' if (cloud === 'aws' && service === 'compute') return 'Compute workflows still rely on AWS-specific forms for dependent infrastructure choices such as VPC, subnet, and security group.' if (cloud === 'aws' && service === 'networking') return 'Networking uses AWS-specific operational panels because many actions require nested workflows instead of a flat generic form.' + if (cloud === 'aws' && service === 'identity') return 'IAM user management is available through the normalized contract; roles, groups, policies, and credentials are not exposed yet.' return 'This service is available through the current adapter, but the normalized contract is still expanding.' } diff --git a/packages/frontend/src/types/cloud.ts b/packages/frontend/src/types/cloud.ts index 668a147..496a3e1 100644 --- a/packages/frontend/src/types/cloud.ts +++ b/packages/frontend/src/types/cloud.ts @@ -1,6 +1,6 @@ export type CloudProvider = 'aws' | 'azure' | 'gcp' export type CloudAvailability = 'available' | 'coming_soon' -export type CloudServiceType = 'storage' | 'k8s' | 'database' | 'compute' | 'networking' | 'serverless' +export type CloudServiceType = 'storage' | 'k8s' | 'database' | 'compute' | 'networking' | 'serverless' | 'identity' export interface CloudDescriptor { id: CloudProvider diff --git a/packages/frontend/src/types/resource.ts b/packages/frontend/src/types/resource.ts index 71f464f..de878ec 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'; + type: 'bucket' | 'container' | 'cluster' | 'db-instance' | 'cosmos-database' | 'instance' | 'image' | 'vpc' | 'lambda' | "azure-function" | 'gcp-function' | '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