@emulators/aws 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/dist/fonts/GeistPixel-Square.woff2 +0 -0
- package/dist/fonts/geist-sans.woff2 +0 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +1211 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1211 @@
|
|
|
1
|
+
// src/store.ts
|
|
2
|
+
function getAwsStore(store) {
|
|
3
|
+
return {
|
|
4
|
+
s3Buckets: store.collection("aws.s3_buckets", ["bucket_name"]),
|
|
5
|
+
s3Objects: store.collection("aws.s3_objects", ["key", "bucket_name"]),
|
|
6
|
+
sqsQueues: store.collection("aws.sqs_queues", ["queue_name", "queue_url"]),
|
|
7
|
+
sqsMessages: store.collection("aws.sqs_messages", ["message_id", "queue_name"]),
|
|
8
|
+
iamUsers: store.collection("aws.iam_users", ["user_name", "user_id"]),
|
|
9
|
+
iamRoles: store.collection("aws.iam_roles", ["role_name", "role_id"])
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/helpers.ts
|
|
14
|
+
import { randomBytes, createHash } from "crypto";
|
|
15
|
+
var ACCOUNT_ID = "123456789012";
|
|
16
|
+
var DEFAULT_REGION = "us-east-1";
|
|
17
|
+
function generateAwsId(prefix) {
|
|
18
|
+
return prefix + randomBytes(8).toString("hex").toUpperCase();
|
|
19
|
+
}
|
|
20
|
+
function generateMessageId() {
|
|
21
|
+
return [
|
|
22
|
+
randomBytes(4).toString("hex"),
|
|
23
|
+
randomBytes(2).toString("hex"),
|
|
24
|
+
randomBytes(2).toString("hex"),
|
|
25
|
+
randomBytes(2).toString("hex"),
|
|
26
|
+
randomBytes(6).toString("hex")
|
|
27
|
+
].join("-");
|
|
28
|
+
}
|
|
29
|
+
function generateReceiptHandle() {
|
|
30
|
+
return randomBytes(48).toString("base64url");
|
|
31
|
+
}
|
|
32
|
+
function md5(content) {
|
|
33
|
+
return createHash("md5").update(content).digest("hex");
|
|
34
|
+
}
|
|
35
|
+
function getAccountId() {
|
|
36
|
+
return ACCOUNT_ID;
|
|
37
|
+
}
|
|
38
|
+
function getDefaultRegion() {
|
|
39
|
+
return DEFAULT_REGION;
|
|
40
|
+
}
|
|
41
|
+
function awsXmlResponse(c, xml, status = 200) {
|
|
42
|
+
return c.text(xml, status, { "Content-Type": "application/xml" });
|
|
43
|
+
}
|
|
44
|
+
function awsErrorXml(c, code, message, status = 400) {
|
|
45
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
46
|
+
<ErrorResponse>
|
|
47
|
+
<Error>
|
|
48
|
+
<Code>${escapeXml(code)}</Code>
|
|
49
|
+
<Message>${escapeXml(message)}</Message>
|
|
50
|
+
</Error>
|
|
51
|
+
<RequestId>${generateMessageId()}</RequestId>
|
|
52
|
+
</ErrorResponse>`;
|
|
53
|
+
return c.text(xml, status, { "Content-Type": "application/xml" });
|
|
54
|
+
}
|
|
55
|
+
function escapeXml(str) {
|
|
56
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
57
|
+
}
|
|
58
|
+
function parseQueryString(body) {
|
|
59
|
+
const params = new URLSearchParams(body);
|
|
60
|
+
const result = {};
|
|
61
|
+
for (const [key, value] of params) {
|
|
62
|
+
result[key] = value;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/routes/s3.ts
|
|
68
|
+
function s3Routes(ctx) {
|
|
69
|
+
const { app, store, baseUrl } = ctx;
|
|
70
|
+
const aws = () => getAwsStore(store);
|
|
71
|
+
app.get("/s3/", (c) => {
|
|
72
|
+
const buckets = aws().s3Buckets.all();
|
|
73
|
+
const bucketXml = buckets.map(
|
|
74
|
+
(b) => ` <Bucket>
|
|
75
|
+
<Name>${escapeXml(b.bucket_name)}</Name>
|
|
76
|
+
<CreationDate>${b.creation_date}</CreationDate>
|
|
77
|
+
</Bucket>`
|
|
78
|
+
).join("\n");
|
|
79
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
80
|
+
<ListAllMyBucketsResult>
|
|
81
|
+
<Owner>
|
|
82
|
+
<ID>owner-id</ID>
|
|
83
|
+
<DisplayName>emulate</DisplayName>
|
|
84
|
+
</Owner>
|
|
85
|
+
<Buckets>
|
|
86
|
+
${bucketXml}
|
|
87
|
+
</Buckets>
|
|
88
|
+
</ListAllMyBucketsResult>`;
|
|
89
|
+
return awsXmlResponse(c, xml);
|
|
90
|
+
});
|
|
91
|
+
app.put("/s3/:bucket", (c) => {
|
|
92
|
+
const bucketName = c.req.param("bucket");
|
|
93
|
+
const existing = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
94
|
+
if (existing) {
|
|
95
|
+
return awsErrorXml(c, "BucketAlreadyOwnedByYou", "Your previous request to create the named bucket succeeded and you already own it.", 409);
|
|
96
|
+
}
|
|
97
|
+
aws().s3Buckets.insert({
|
|
98
|
+
bucket_name: bucketName,
|
|
99
|
+
region: "us-east-1",
|
|
100
|
+
creation_date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
101
|
+
acl: "private",
|
|
102
|
+
versioning_enabled: false
|
|
103
|
+
});
|
|
104
|
+
return c.text("", 200, { Location: `/${bucketName}` });
|
|
105
|
+
});
|
|
106
|
+
app.delete("/s3/:bucket", (c) => {
|
|
107
|
+
const bucketName = c.req.param("bucket");
|
|
108
|
+
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
109
|
+
if (!bucket) {
|
|
110
|
+
return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
|
|
111
|
+
}
|
|
112
|
+
const objects = aws().s3Objects.findBy("bucket_name", bucketName);
|
|
113
|
+
if (objects.length > 0) {
|
|
114
|
+
return awsErrorXml(c, "BucketNotEmpty", "The bucket you tried to delete is not empty.", 409);
|
|
115
|
+
}
|
|
116
|
+
aws().s3Buckets.delete(bucket.id);
|
|
117
|
+
return c.body(null, 204);
|
|
118
|
+
});
|
|
119
|
+
app.on("HEAD", "/s3/:bucket", (c) => {
|
|
120
|
+
const bucketName = c.req.param("bucket");
|
|
121
|
+
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
122
|
+
if (!bucket) {
|
|
123
|
+
return c.text("", 404);
|
|
124
|
+
}
|
|
125
|
+
return c.text("", 200, { "x-amz-bucket-region": bucket.region });
|
|
126
|
+
});
|
|
127
|
+
app.get("/s3/:bucket", (c) => {
|
|
128
|
+
const bucketName = c.req.param("bucket");
|
|
129
|
+
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
130
|
+
if (!bucket) {
|
|
131
|
+
return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
|
|
132
|
+
}
|
|
133
|
+
const prefix = c.req.query("prefix") ?? "";
|
|
134
|
+
const delimiter = c.req.query("delimiter") ?? "";
|
|
135
|
+
const maxKeys = Math.min(parseInt(c.req.query("max-keys") ?? "1000", 10), 1e3);
|
|
136
|
+
let objects = aws().s3Objects.findBy("bucket_name", bucketName);
|
|
137
|
+
if (prefix) {
|
|
138
|
+
objects = objects.filter((o) => o.key.startsWith(prefix));
|
|
139
|
+
}
|
|
140
|
+
const commonPrefixes = [];
|
|
141
|
+
let contents = objects;
|
|
142
|
+
if (delimiter) {
|
|
143
|
+
const prefixSet = /* @__PURE__ */ new Set();
|
|
144
|
+
contents = [];
|
|
145
|
+
for (const obj of objects) {
|
|
146
|
+
const remaining = obj.key.slice(prefix.length);
|
|
147
|
+
const delimIndex = remaining.indexOf(delimiter);
|
|
148
|
+
if (delimIndex >= 0) {
|
|
149
|
+
prefixSet.add(prefix + remaining.slice(0, delimIndex + delimiter.length));
|
|
150
|
+
} else {
|
|
151
|
+
contents.push(obj);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
commonPrefixes.push(...Array.from(prefixSet).sort());
|
|
155
|
+
}
|
|
156
|
+
const truncated = contents.length > maxKeys;
|
|
157
|
+
const page = contents.slice(0, maxKeys);
|
|
158
|
+
const contentsXml = page.map(
|
|
159
|
+
(o) => ` <Contents>
|
|
160
|
+
<Key>${escapeXml(o.key)}</Key>
|
|
161
|
+
<LastModified>${o.last_modified}</LastModified>
|
|
162
|
+
<ETag>"${o.etag}"</ETag>
|
|
163
|
+
<Size>${o.content_length}</Size>
|
|
164
|
+
<StorageClass>STANDARD</StorageClass>
|
|
165
|
+
</Contents>`
|
|
166
|
+
).join("\n");
|
|
167
|
+
const prefixesXml = commonPrefixes.map((p) => ` <CommonPrefixes><Prefix>${escapeXml(p)}</Prefix></CommonPrefixes>`).join("\n");
|
|
168
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
169
|
+
<ListBucketResult>
|
|
170
|
+
<Name>${escapeXml(bucketName)}</Name>
|
|
171
|
+
<Prefix>${escapeXml(prefix)}</Prefix>
|
|
172
|
+
<MaxKeys>${maxKeys}</MaxKeys>
|
|
173
|
+
<IsTruncated>${truncated}</IsTruncated>
|
|
174
|
+
<KeyCount>${page.length}</KeyCount>
|
|
175
|
+
${contentsXml}
|
|
176
|
+
${prefixesXml}
|
|
177
|
+
</ListBucketResult>`;
|
|
178
|
+
return awsXmlResponse(c, xml);
|
|
179
|
+
});
|
|
180
|
+
app.put("/s3/:bucket/:key{.+}", async (c) => {
|
|
181
|
+
const bucketName = c.req.param("bucket");
|
|
182
|
+
const key = c.req.param("key");
|
|
183
|
+
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
184
|
+
if (!bucket) {
|
|
185
|
+
return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
|
|
186
|
+
}
|
|
187
|
+
const copySource = c.req.header("x-amz-copy-source");
|
|
188
|
+
if (copySource) {
|
|
189
|
+
const normalized = copySource.startsWith("/") ? copySource.slice(1) : copySource;
|
|
190
|
+
const slashIndex = normalized.indexOf("/");
|
|
191
|
+
if (slashIndex < 0) {
|
|
192
|
+
return awsErrorXml(c, "InvalidArgument", "Invalid copy source.", 400);
|
|
193
|
+
}
|
|
194
|
+
const srcBucket = normalized.slice(0, slashIndex);
|
|
195
|
+
const srcKey = normalized.slice(slashIndex + 1);
|
|
196
|
+
const srcObj = aws().s3Objects.findBy("bucket_name", srcBucket).find((o) => o.key === srcKey);
|
|
197
|
+
if (!srcObj) {
|
|
198
|
+
return awsErrorXml(c, "NoSuchKey", "The specified source key does not exist.", 404);
|
|
199
|
+
}
|
|
200
|
+
const etag2 = srcObj.etag;
|
|
201
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
202
|
+
const existing2 = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
|
|
203
|
+
if (existing2) {
|
|
204
|
+
aws().s3Objects.update(existing2.id, {
|
|
205
|
+
body: srcObj.body,
|
|
206
|
+
content_type: srcObj.content_type,
|
|
207
|
+
content_length: srcObj.content_length,
|
|
208
|
+
etag: etag2,
|
|
209
|
+
last_modified: now,
|
|
210
|
+
metadata: { ...srcObj.metadata }
|
|
211
|
+
});
|
|
212
|
+
} else {
|
|
213
|
+
aws().s3Objects.insert({
|
|
214
|
+
bucket_name: bucketName,
|
|
215
|
+
key,
|
|
216
|
+
body: srcObj.body,
|
|
217
|
+
content_type: srcObj.content_type,
|
|
218
|
+
content_length: srcObj.content_length,
|
|
219
|
+
etag: etag2,
|
|
220
|
+
last_modified: now,
|
|
221
|
+
metadata: { ...srcObj.metadata }
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
225
|
+
<CopyObjectResult>
|
|
226
|
+
<ETag>"${etag2}"</ETag>
|
|
227
|
+
<LastModified>${now}</LastModified>
|
|
228
|
+
</CopyObjectResult>`;
|
|
229
|
+
return awsXmlResponse(c, xml);
|
|
230
|
+
}
|
|
231
|
+
const body = await c.req.text();
|
|
232
|
+
const contentType = c.req.header("Content-Type") ?? "application/octet-stream";
|
|
233
|
+
const etag = md5(body);
|
|
234
|
+
const metadata = {};
|
|
235
|
+
for (const [headerName, headerValue] of Object.entries(c.req.header())) {
|
|
236
|
+
if (typeof headerValue === "string" && headerName.toLowerCase().startsWith("x-amz-meta-")) {
|
|
237
|
+
metadata[headerName.slice("x-amz-meta-".length)] = headerValue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const existing = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
|
|
241
|
+
if (existing) {
|
|
242
|
+
aws().s3Objects.update(existing.id, {
|
|
243
|
+
body,
|
|
244
|
+
content_type: contentType,
|
|
245
|
+
content_length: new TextEncoder().encode(body).byteLength,
|
|
246
|
+
etag,
|
|
247
|
+
last_modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
248
|
+
metadata
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
aws().s3Objects.insert({
|
|
252
|
+
bucket_name: bucketName,
|
|
253
|
+
key,
|
|
254
|
+
body,
|
|
255
|
+
content_type: contentType,
|
|
256
|
+
content_length: new TextEncoder().encode(body).byteLength,
|
|
257
|
+
etag,
|
|
258
|
+
last_modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
259
|
+
metadata
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return c.text("", 200, { ETag: `"${etag}"` });
|
|
263
|
+
});
|
|
264
|
+
app.get("/s3/:bucket/:key{.+}", (c) => {
|
|
265
|
+
const bucketName = c.req.param("bucket");
|
|
266
|
+
const key = c.req.param("key");
|
|
267
|
+
const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
|
|
268
|
+
if (!bucket) {
|
|
269
|
+
return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
|
|
270
|
+
}
|
|
271
|
+
const obj = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
|
|
272
|
+
if (!obj) {
|
|
273
|
+
return awsErrorXml(c, "NoSuchKey", "The specified key does not exist.", 404);
|
|
274
|
+
}
|
|
275
|
+
const headers = {
|
|
276
|
+
"Content-Type": obj.content_type,
|
|
277
|
+
"Content-Length": String(obj.content_length),
|
|
278
|
+
ETag: `"${obj.etag}"`,
|
|
279
|
+
"Last-Modified": obj.last_modified
|
|
280
|
+
};
|
|
281
|
+
for (const [k, v] of Object.entries(obj.metadata)) {
|
|
282
|
+
headers[`x-amz-meta-${k}`] = v;
|
|
283
|
+
}
|
|
284
|
+
return c.text(obj.body, 200, headers);
|
|
285
|
+
});
|
|
286
|
+
app.on("HEAD", "/s3/:bucket/:key{.+}", (c) => {
|
|
287
|
+
const bucketName = c.req.param("bucket");
|
|
288
|
+
const key = c.req.param("key");
|
|
289
|
+
const obj = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
|
|
290
|
+
if (!obj) {
|
|
291
|
+
return c.text("", 404);
|
|
292
|
+
}
|
|
293
|
+
return c.text("", 200, {
|
|
294
|
+
"Content-Type": obj.content_type,
|
|
295
|
+
"Content-Length": String(obj.content_length),
|
|
296
|
+
ETag: `"${obj.etag}"`,
|
|
297
|
+
"Last-Modified": obj.last_modified
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
app.delete("/s3/:bucket/:key{.+}", (c) => {
|
|
301
|
+
const bucketName = c.req.param("bucket");
|
|
302
|
+
const key = c.req.param("key");
|
|
303
|
+
const obj = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
|
|
304
|
+
if (obj) {
|
|
305
|
+
aws().s3Objects.delete(obj.id);
|
|
306
|
+
}
|
|
307
|
+
return c.body(null, 204);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/routes/sqs.ts
|
|
312
|
+
function sqsRoutes(ctx) {
|
|
313
|
+
const { app, store, baseUrl } = ctx;
|
|
314
|
+
const aws = () => getAwsStore(store);
|
|
315
|
+
const accountId = getAccountId();
|
|
316
|
+
app.post("/sqs/", async (c) => {
|
|
317
|
+
const body = await c.req.text();
|
|
318
|
+
const params = parseQueryString(body);
|
|
319
|
+
const action = params["Action"] ?? c.req.query("Action") ?? "";
|
|
320
|
+
switch (action) {
|
|
321
|
+
case "CreateQueue":
|
|
322
|
+
return createQueue(c, params);
|
|
323
|
+
case "DeleteQueue":
|
|
324
|
+
return deleteQueue(c, params);
|
|
325
|
+
case "ListQueues":
|
|
326
|
+
return listQueues(c, params);
|
|
327
|
+
case "GetQueueUrl":
|
|
328
|
+
return getQueueUrl(c, params);
|
|
329
|
+
case "GetQueueAttributes":
|
|
330
|
+
return getQueueAttributes(c, params);
|
|
331
|
+
case "SendMessage":
|
|
332
|
+
return sendMessage(c, params);
|
|
333
|
+
case "ReceiveMessage":
|
|
334
|
+
return receiveMessage(c, params);
|
|
335
|
+
case "DeleteMessage":
|
|
336
|
+
return deleteMessage(c, params);
|
|
337
|
+
case "PurgeQueue":
|
|
338
|
+
return purgeQueue(c, params);
|
|
339
|
+
default:
|
|
340
|
+
return awsErrorXml(c, "InvalidAction", `The action ${action} is not valid for this endpoint.`, 400);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
function createQueue(c, params) {
|
|
344
|
+
const queueName = params["QueueName"] ?? "";
|
|
345
|
+
if (!queueName) {
|
|
346
|
+
return awsErrorXml(c, "MissingParameter", "The request must contain the parameter QueueName.", 400);
|
|
347
|
+
}
|
|
348
|
+
const existing = aws().sqsQueues.findOneBy("queue_name", queueName);
|
|
349
|
+
if (existing) {
|
|
350
|
+
const xml2 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
351
|
+
<CreateQueueResponse>
|
|
352
|
+
<CreateQueueResult>
|
|
353
|
+
<QueueUrl>${escapeXml(existing.queue_url)}</QueueUrl>
|
|
354
|
+
</CreateQueueResult>
|
|
355
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
356
|
+
</CreateQueueResponse>`;
|
|
357
|
+
return awsXmlResponse(c, xml2);
|
|
358
|
+
}
|
|
359
|
+
const fifo = queueName.endsWith(".fifo");
|
|
360
|
+
const queueUrl = `${baseUrl}/sqs/${accountId}/${queueName}`;
|
|
361
|
+
const arn = `arn:aws:sqs:us-east-1:${accountId}:${queueName}`;
|
|
362
|
+
const attrs = {};
|
|
363
|
+
for (let i = 1; params[`Attribute.${i}.Name`]; i++) {
|
|
364
|
+
attrs[params[`Attribute.${i}.Name`]] = params[`Attribute.${i}.Value`] ?? "";
|
|
365
|
+
}
|
|
366
|
+
aws().sqsQueues.insert({
|
|
367
|
+
queue_name: queueName,
|
|
368
|
+
queue_url: queueUrl,
|
|
369
|
+
arn,
|
|
370
|
+
visibility_timeout: parseInt(attrs["VisibilityTimeout"] ?? "30", 10),
|
|
371
|
+
delay_seconds: parseInt(attrs["DelaySeconds"] ?? "0", 10),
|
|
372
|
+
max_message_size: parseInt(attrs["MaximumMessageSize"] ?? "262144", 10),
|
|
373
|
+
message_retention_period: parseInt(attrs["MessageRetentionPeriod"] ?? "345600", 10),
|
|
374
|
+
receive_message_wait_time: parseInt(attrs["ReceiveMessageWaitTimeSeconds"] ?? "0", 10),
|
|
375
|
+
fifo
|
|
376
|
+
});
|
|
377
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
378
|
+
<CreateQueueResponse>
|
|
379
|
+
<CreateQueueResult>
|
|
380
|
+
<QueueUrl>${escapeXml(queueUrl)}</QueueUrl>
|
|
381
|
+
</CreateQueueResult>
|
|
382
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
383
|
+
</CreateQueueResponse>`;
|
|
384
|
+
return awsXmlResponse(c, xml);
|
|
385
|
+
}
|
|
386
|
+
function deleteQueue(c, params) {
|
|
387
|
+
const queueUrl = params["QueueUrl"] ?? "";
|
|
388
|
+
const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
|
|
389
|
+
if (!queue) {
|
|
390
|
+
return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
|
|
391
|
+
}
|
|
392
|
+
const messages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
|
|
393
|
+
for (const msg of messages) {
|
|
394
|
+
aws().sqsMessages.delete(msg.id);
|
|
395
|
+
}
|
|
396
|
+
aws().sqsQueues.delete(queue.id);
|
|
397
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
398
|
+
<DeleteQueueResponse>
|
|
399
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
400
|
+
</DeleteQueueResponse>`;
|
|
401
|
+
return awsXmlResponse(c, xml);
|
|
402
|
+
}
|
|
403
|
+
function listQueues(c, params) {
|
|
404
|
+
const prefix = params["QueueNamePrefix"] ?? "";
|
|
405
|
+
let queues = aws().sqsQueues.all();
|
|
406
|
+
if (prefix) {
|
|
407
|
+
queues = queues.filter((q) => q.queue_name.startsWith(prefix));
|
|
408
|
+
}
|
|
409
|
+
const queueUrlsXml = queues.map((q) => ` <QueueUrl>${escapeXml(q.queue_url)}</QueueUrl>`).join("\n");
|
|
410
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
411
|
+
<ListQueuesResponse>
|
|
412
|
+
<ListQueuesResult>
|
|
413
|
+
${queueUrlsXml}
|
|
414
|
+
</ListQueuesResult>
|
|
415
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
416
|
+
</ListQueuesResponse>`;
|
|
417
|
+
return awsXmlResponse(c, xml);
|
|
418
|
+
}
|
|
419
|
+
function getQueueUrl(c, params) {
|
|
420
|
+
const queueName = params["QueueName"] ?? "";
|
|
421
|
+
const queue = aws().sqsQueues.findOneBy("queue_name", queueName);
|
|
422
|
+
if (!queue) {
|
|
423
|
+
return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
|
|
424
|
+
}
|
|
425
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
426
|
+
<GetQueueUrlResponse>
|
|
427
|
+
<GetQueueUrlResult>
|
|
428
|
+
<QueueUrl>${escapeXml(queue.queue_url)}</QueueUrl>
|
|
429
|
+
</GetQueueUrlResult>
|
|
430
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
431
|
+
</GetQueueUrlResponse>`;
|
|
432
|
+
return awsXmlResponse(c, xml);
|
|
433
|
+
}
|
|
434
|
+
function getQueueAttributes(c, params) {
|
|
435
|
+
const queueUrl = params["QueueUrl"] ?? "";
|
|
436
|
+
const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
|
|
437
|
+
if (!queue) {
|
|
438
|
+
return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
|
|
439
|
+
}
|
|
440
|
+
const messages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
|
|
441
|
+
const now = Date.now();
|
|
442
|
+
const visibleCount = messages.filter((m) => m.visible_after <= now).length;
|
|
443
|
+
const inFlightCount = messages.filter((m) => m.visible_after > now).length;
|
|
444
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
445
|
+
<GetQueueAttributesResponse>
|
|
446
|
+
<GetQueueAttributesResult>
|
|
447
|
+
<Attribute><Name>QueueArn</Name><Value>${queue.arn}</Value></Attribute>
|
|
448
|
+
<Attribute><Name>ApproximateNumberOfMessages</Name><Value>${visibleCount}</Value></Attribute>
|
|
449
|
+
<Attribute><Name>ApproximateNumberOfMessagesNotVisible</Name><Value>${inFlightCount}</Value></Attribute>
|
|
450
|
+
<Attribute><Name>VisibilityTimeout</Name><Value>${queue.visibility_timeout}</Value></Attribute>
|
|
451
|
+
<Attribute><Name>MaximumMessageSize</Name><Value>${queue.max_message_size}</Value></Attribute>
|
|
452
|
+
<Attribute><Name>MessageRetentionPeriod</Name><Value>${queue.message_retention_period}</Value></Attribute>
|
|
453
|
+
<Attribute><Name>DelaySeconds</Name><Value>${queue.delay_seconds}</Value></Attribute>
|
|
454
|
+
<Attribute><Name>ReceiveMessageWaitTimeSeconds</Name><Value>${queue.receive_message_wait_time}</Value></Attribute>
|
|
455
|
+
<Attribute><Name>FifoQueue</Name><Value>${queue.fifo}</Value></Attribute>
|
|
456
|
+
</GetQueueAttributesResult>
|
|
457
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
458
|
+
</GetQueueAttributesResponse>`;
|
|
459
|
+
return awsXmlResponse(c, xml);
|
|
460
|
+
}
|
|
461
|
+
function sendMessage(c, params) {
|
|
462
|
+
const queueUrl = params["QueueUrl"] ?? "";
|
|
463
|
+
const messageBody = params["MessageBody"] ?? "";
|
|
464
|
+
const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
|
|
465
|
+
if (!queue) {
|
|
466
|
+
return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
|
|
467
|
+
}
|
|
468
|
+
if (!messageBody) {
|
|
469
|
+
return awsErrorXml(c, "MissingParameter", "The request must contain the parameter MessageBody.", 400);
|
|
470
|
+
}
|
|
471
|
+
const bodyBytes = new TextEncoder().encode(messageBody).byteLength;
|
|
472
|
+
if (bodyBytes > queue.max_message_size) {
|
|
473
|
+
return awsErrorXml(c, "InvalidParameterValue", `One or more parameters are invalid. Reason: Message must be shorter than ${queue.max_message_size} bytes.`, 400);
|
|
474
|
+
}
|
|
475
|
+
const messageId = generateMessageId();
|
|
476
|
+
const bodyMd5 = md5(messageBody);
|
|
477
|
+
const now = Date.now();
|
|
478
|
+
const messageAttributes = {};
|
|
479
|
+
let attrIndex = 1;
|
|
480
|
+
while (params[`MessageAttribute.${attrIndex}.Name`]) {
|
|
481
|
+
const name = params[`MessageAttribute.${attrIndex}.Name`];
|
|
482
|
+
const dataType = params[`MessageAttribute.${attrIndex}.Value.DataType`] ?? "String";
|
|
483
|
+
const stringValue = params[`MessageAttribute.${attrIndex}.Value.StringValue`];
|
|
484
|
+
messageAttributes[name] = { DataType: dataType, StringValue: stringValue };
|
|
485
|
+
attrIndex++;
|
|
486
|
+
}
|
|
487
|
+
aws().sqsMessages.insert({
|
|
488
|
+
queue_name: queue.queue_name,
|
|
489
|
+
message_id: messageId,
|
|
490
|
+
receipt_handle: generateReceiptHandle(),
|
|
491
|
+
body: messageBody,
|
|
492
|
+
md5_of_body: bodyMd5,
|
|
493
|
+
attributes: {
|
|
494
|
+
SentTimestamp: String(now),
|
|
495
|
+
ApproximateReceiveCount: "0",
|
|
496
|
+
ApproximateFirstReceiveTimestamp: "",
|
|
497
|
+
SenderId: getAccountId()
|
|
498
|
+
},
|
|
499
|
+
message_attributes: messageAttributes,
|
|
500
|
+
visible_after: now + queue.delay_seconds * 1e3,
|
|
501
|
+
sent_timestamp: now,
|
|
502
|
+
receive_count: 0
|
|
503
|
+
});
|
|
504
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
505
|
+
<SendMessageResponse>
|
|
506
|
+
<SendMessageResult>
|
|
507
|
+
<MessageId>${messageId}</MessageId>
|
|
508
|
+
<MD5OfMessageBody>${bodyMd5}</MD5OfMessageBody>
|
|
509
|
+
</SendMessageResult>
|
|
510
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
511
|
+
</SendMessageResponse>`;
|
|
512
|
+
return awsXmlResponse(c, xml);
|
|
513
|
+
}
|
|
514
|
+
function receiveMessage(c, params) {
|
|
515
|
+
const queueUrl = params["QueueUrl"] ?? "";
|
|
516
|
+
const maxMessages = Math.min(parseInt(params["MaxNumberOfMessages"] ?? "1", 10), 10);
|
|
517
|
+
const visibilityTimeout = parseInt(params["VisibilityTimeout"] ?? "", 10);
|
|
518
|
+
const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
|
|
519
|
+
if (!queue) {
|
|
520
|
+
return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
|
|
521
|
+
}
|
|
522
|
+
const now = Date.now();
|
|
523
|
+
const timeout = isNaN(visibilityTimeout) ? queue.visibility_timeout : visibilityTimeout;
|
|
524
|
+
const allMessages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
|
|
525
|
+
const visible = allMessages.filter((m) => m.visible_after <= now);
|
|
526
|
+
const batch = visible.slice(0, maxMessages);
|
|
527
|
+
for (const msg of batch) {
|
|
528
|
+
const newReceiptHandle = generateReceiptHandle();
|
|
529
|
+
aws().sqsMessages.update(msg.id, {
|
|
530
|
+
receipt_handle: newReceiptHandle,
|
|
531
|
+
visible_after: now + timeout * 1e3,
|
|
532
|
+
receive_count: msg.receive_count + 1
|
|
533
|
+
});
|
|
534
|
+
msg.receipt_handle = newReceiptHandle;
|
|
535
|
+
msg.receive_count += 1;
|
|
536
|
+
}
|
|
537
|
+
const messagesXml = batch.map(
|
|
538
|
+
(m) => ` <Message>
|
|
539
|
+
<MessageId>${m.message_id}</MessageId>
|
|
540
|
+
<ReceiptHandle>${m.receipt_handle}</ReceiptHandle>
|
|
541
|
+
<MD5OfBody>${m.md5_of_body}</MD5OfBody>
|
|
542
|
+
<Body>${escapeXml(m.body)}</Body>
|
|
543
|
+
<Attribute><Name>SentTimestamp</Name><Value>${m.sent_timestamp}</Value></Attribute>
|
|
544
|
+
<Attribute><Name>ApproximateReceiveCount</Name><Value>${m.receive_count}</Value></Attribute>
|
|
545
|
+
<Attribute><Name>ApproximateFirstReceiveTimestamp</Name><Value>${m.sent_timestamp}</Value></Attribute>
|
|
546
|
+
</Message>`
|
|
547
|
+
).join("\n");
|
|
548
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
549
|
+
<ReceiveMessageResponse>
|
|
550
|
+
<ReceiveMessageResult>
|
|
551
|
+
${messagesXml}
|
|
552
|
+
</ReceiveMessageResult>
|
|
553
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
554
|
+
</ReceiveMessageResponse>`;
|
|
555
|
+
return awsXmlResponse(c, xml);
|
|
556
|
+
}
|
|
557
|
+
function deleteMessage(c, params) {
|
|
558
|
+
const queueUrl = params["QueueUrl"] ?? "";
|
|
559
|
+
const receiptHandle = params["ReceiptHandle"] ?? "";
|
|
560
|
+
const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
|
|
561
|
+
if (!queue) {
|
|
562
|
+
return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
|
|
563
|
+
}
|
|
564
|
+
const messages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
|
|
565
|
+
const msg = messages.find((m) => m.receipt_handle === receiptHandle);
|
|
566
|
+
if (msg) {
|
|
567
|
+
aws().sqsMessages.delete(msg.id);
|
|
568
|
+
}
|
|
569
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
570
|
+
<DeleteMessageResponse>
|
|
571
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
572
|
+
</DeleteMessageResponse>`;
|
|
573
|
+
return awsXmlResponse(c, xml);
|
|
574
|
+
}
|
|
575
|
+
function purgeQueue(c, params) {
|
|
576
|
+
const queueUrl = params["QueueUrl"] ?? "";
|
|
577
|
+
const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
|
|
578
|
+
if (!queue) {
|
|
579
|
+
return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
|
|
580
|
+
}
|
|
581
|
+
const messages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
|
|
582
|
+
for (const msg of messages) {
|
|
583
|
+
aws().sqsMessages.delete(msg.id);
|
|
584
|
+
}
|
|
585
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
586
|
+
<PurgeQueueResponse>
|
|
587
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
588
|
+
</PurgeQueueResponse>`;
|
|
589
|
+
return awsXmlResponse(c, xml);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/routes/iam.ts
|
|
594
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
595
|
+
function iamRoutes(ctx) {
|
|
596
|
+
const { app, store } = ctx;
|
|
597
|
+
const aws = () => getAwsStore(store);
|
|
598
|
+
const accountId = getAccountId();
|
|
599
|
+
app.post("/iam/", async (c) => {
|
|
600
|
+
const body = await c.req.text();
|
|
601
|
+
const params = parseQueryString(body);
|
|
602
|
+
const action = params["Action"] ?? c.req.query("Action") ?? "";
|
|
603
|
+
switch (action) {
|
|
604
|
+
case "CreateUser":
|
|
605
|
+
return createUser(c, params);
|
|
606
|
+
case "GetUser":
|
|
607
|
+
return getUser(c, params);
|
|
608
|
+
case "DeleteUser":
|
|
609
|
+
return deleteUser(c, params);
|
|
610
|
+
case "ListUsers":
|
|
611
|
+
return listUsers(c);
|
|
612
|
+
case "CreateAccessKey":
|
|
613
|
+
return createAccessKey(c, params);
|
|
614
|
+
case "ListAccessKeys":
|
|
615
|
+
return listAccessKeys(c, params);
|
|
616
|
+
case "DeleteAccessKey":
|
|
617
|
+
return deleteAccessKey(c, params);
|
|
618
|
+
case "CreateRole":
|
|
619
|
+
return createRole(c, params);
|
|
620
|
+
case "GetRole":
|
|
621
|
+
return getRole(c, params);
|
|
622
|
+
case "DeleteRole":
|
|
623
|
+
return deleteRole(c, params);
|
|
624
|
+
case "ListRoles":
|
|
625
|
+
return listRoles(c);
|
|
626
|
+
default:
|
|
627
|
+
return awsErrorXml(c, "InvalidAction", `The action ${action} is not valid for this endpoint.`, 400);
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
app.post("/sts/", async (c) => {
|
|
631
|
+
const body = await c.req.text();
|
|
632
|
+
const params = parseQueryString(body);
|
|
633
|
+
const action = params["Action"] ?? c.req.query("Action") ?? "";
|
|
634
|
+
switch (action) {
|
|
635
|
+
case "GetCallerIdentity":
|
|
636
|
+
return getCallerIdentity(c);
|
|
637
|
+
case "AssumeRole":
|
|
638
|
+
return assumeRole(c, params);
|
|
639
|
+
default:
|
|
640
|
+
return awsErrorXml(c, "InvalidAction", `The action ${action} is not valid for this endpoint.`, 400);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
function createUser(c, params) {
|
|
644
|
+
const userName = params["UserName"] ?? "";
|
|
645
|
+
if (!userName) {
|
|
646
|
+
return awsErrorXml(c, "ValidationError", "The request must contain the parameter UserName.", 400);
|
|
647
|
+
}
|
|
648
|
+
const existing = aws().iamUsers.findOneBy("user_name", userName);
|
|
649
|
+
if (existing) {
|
|
650
|
+
return awsErrorXml(c, "EntityAlreadyExists", `User with name ${escapeXml(userName)} already exists.`, 409);
|
|
651
|
+
}
|
|
652
|
+
const userId = generateAwsId("AIDA");
|
|
653
|
+
const path = params["Path"] ?? "/";
|
|
654
|
+
const arn = `arn:aws:iam::${accountId}:user${path}${userName}`;
|
|
655
|
+
aws().iamUsers.insert({
|
|
656
|
+
user_name: userName,
|
|
657
|
+
user_id: userId,
|
|
658
|
+
arn,
|
|
659
|
+
path,
|
|
660
|
+
access_keys: []
|
|
661
|
+
});
|
|
662
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
663
|
+
<CreateUserResponse>
|
|
664
|
+
<CreateUserResult>
|
|
665
|
+
<User>
|
|
666
|
+
<Path>${escapeXml(path)}</Path>
|
|
667
|
+
<UserName>${escapeXml(userName)}</UserName>
|
|
668
|
+
<UserId>${userId}</UserId>
|
|
669
|
+
<Arn>${escapeXml(arn)}</Arn>
|
|
670
|
+
<CreateDate>${(/* @__PURE__ */ new Date()).toISOString()}</CreateDate>
|
|
671
|
+
</User>
|
|
672
|
+
</CreateUserResult>
|
|
673
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
674
|
+
</CreateUserResponse>`;
|
|
675
|
+
return awsXmlResponse(c, xml);
|
|
676
|
+
}
|
|
677
|
+
function getUser(c, params) {
|
|
678
|
+
const userName = params["UserName"] ?? "";
|
|
679
|
+
const user = aws().iamUsers.findOneBy("user_name", userName);
|
|
680
|
+
if (!user) {
|
|
681
|
+
return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
|
|
682
|
+
}
|
|
683
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
684
|
+
<GetUserResponse>
|
|
685
|
+
<GetUserResult>
|
|
686
|
+
<User>
|
|
687
|
+
<Path>${escapeXml(user.path)}</Path>
|
|
688
|
+
<UserName>${escapeXml(user.user_name)}</UserName>
|
|
689
|
+
<UserId>${user.user_id}</UserId>
|
|
690
|
+
<Arn>${escapeXml(user.arn)}</Arn>
|
|
691
|
+
<CreateDate>${user.created_at}</CreateDate>
|
|
692
|
+
</User>
|
|
693
|
+
</GetUserResult>
|
|
694
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
695
|
+
</GetUserResponse>`;
|
|
696
|
+
return awsXmlResponse(c, xml);
|
|
697
|
+
}
|
|
698
|
+
function deleteUser(c, params) {
|
|
699
|
+
const userName = params["UserName"] ?? "";
|
|
700
|
+
const user = aws().iamUsers.findOneBy("user_name", userName);
|
|
701
|
+
if (!user) {
|
|
702
|
+
return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
|
|
703
|
+
}
|
|
704
|
+
aws().iamUsers.delete(user.id);
|
|
705
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
706
|
+
<DeleteUserResponse>
|
|
707
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
708
|
+
</DeleteUserResponse>`;
|
|
709
|
+
return awsXmlResponse(c, xml);
|
|
710
|
+
}
|
|
711
|
+
function listUsers(c) {
|
|
712
|
+
const users = aws().iamUsers.all();
|
|
713
|
+
const usersXml = users.map(
|
|
714
|
+
(u) => ` <member>
|
|
715
|
+
<Path>${escapeXml(u.path)}</Path>
|
|
716
|
+
<UserName>${escapeXml(u.user_name)}</UserName>
|
|
717
|
+
<UserId>${u.user_id}</UserId>
|
|
718
|
+
<Arn>${escapeXml(u.arn)}</Arn>
|
|
719
|
+
<CreateDate>${u.created_at}</CreateDate>
|
|
720
|
+
</member>`
|
|
721
|
+
).join("\n");
|
|
722
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
723
|
+
<ListUsersResponse>
|
|
724
|
+
<ListUsersResult>
|
|
725
|
+
<IsTruncated>false</IsTruncated>
|
|
726
|
+
<Users>
|
|
727
|
+
${usersXml}
|
|
728
|
+
</Users>
|
|
729
|
+
</ListUsersResult>
|
|
730
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
731
|
+
</ListUsersResponse>`;
|
|
732
|
+
return awsXmlResponse(c, xml);
|
|
733
|
+
}
|
|
734
|
+
function createAccessKey(c, params) {
|
|
735
|
+
const userName = params["UserName"] ?? "";
|
|
736
|
+
const user = aws().iamUsers.findOneBy("user_name", userName);
|
|
737
|
+
if (!user) {
|
|
738
|
+
return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
|
|
739
|
+
}
|
|
740
|
+
const accessKeyId = "AKIA" + randomBytes2(8).toString("hex").toUpperCase();
|
|
741
|
+
const secretAccessKey = randomBytes2(30).toString("base64");
|
|
742
|
+
const keys = [...user.access_keys, { access_key_id: accessKeyId, secret_access_key: secretAccessKey, status: "Active" }];
|
|
743
|
+
aws().iamUsers.update(user.id, { access_keys: keys });
|
|
744
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
745
|
+
<CreateAccessKeyResponse>
|
|
746
|
+
<CreateAccessKeyResult>
|
|
747
|
+
<AccessKey>
|
|
748
|
+
<UserName>${escapeXml(userName)}</UserName>
|
|
749
|
+
<AccessKeyId>${accessKeyId}</AccessKeyId>
|
|
750
|
+
<Status>Active</Status>
|
|
751
|
+
<SecretAccessKey>${secretAccessKey}</SecretAccessKey>
|
|
752
|
+
<CreateDate>${(/* @__PURE__ */ new Date()).toISOString()}</CreateDate>
|
|
753
|
+
</AccessKey>
|
|
754
|
+
</CreateAccessKeyResult>
|
|
755
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
756
|
+
</CreateAccessKeyResponse>`;
|
|
757
|
+
return awsXmlResponse(c, xml);
|
|
758
|
+
}
|
|
759
|
+
function listAccessKeys(c, params) {
|
|
760
|
+
const userName = params["UserName"] ?? "";
|
|
761
|
+
const user = aws().iamUsers.findOneBy("user_name", userName);
|
|
762
|
+
if (!user) {
|
|
763
|
+
return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
|
|
764
|
+
}
|
|
765
|
+
const keysXml = user.access_keys.map(
|
|
766
|
+
(k) => ` <member>
|
|
767
|
+
<UserName>${escapeXml(userName)}</UserName>
|
|
768
|
+
<AccessKeyId>${k.access_key_id}</AccessKeyId>
|
|
769
|
+
<Status>${k.status}</Status>
|
|
770
|
+
</member>`
|
|
771
|
+
).join("\n");
|
|
772
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
773
|
+
<ListAccessKeysResponse>
|
|
774
|
+
<ListAccessKeysResult>
|
|
775
|
+
<IsTruncated>false</IsTruncated>
|
|
776
|
+
<AccessKeyMetadata>
|
|
777
|
+
${keysXml}
|
|
778
|
+
</AccessKeyMetadata>
|
|
779
|
+
</ListAccessKeysResult>
|
|
780
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
781
|
+
</ListAccessKeysResponse>`;
|
|
782
|
+
return awsXmlResponse(c, xml);
|
|
783
|
+
}
|
|
784
|
+
function deleteAccessKey(c, params) {
|
|
785
|
+
const userName = params["UserName"] ?? "";
|
|
786
|
+
const accessKeyId = params["AccessKeyId"] ?? "";
|
|
787
|
+
const user = aws().iamUsers.findOneBy("user_name", userName);
|
|
788
|
+
if (!user) {
|
|
789
|
+
return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
|
|
790
|
+
}
|
|
791
|
+
const keys = user.access_keys.filter((k) => k.access_key_id !== accessKeyId);
|
|
792
|
+
aws().iamUsers.update(user.id, { access_keys: keys });
|
|
793
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
794
|
+
<DeleteAccessKeyResponse>
|
|
795
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
796
|
+
</DeleteAccessKeyResponse>`;
|
|
797
|
+
return awsXmlResponse(c, xml);
|
|
798
|
+
}
|
|
799
|
+
function createRole(c, params) {
|
|
800
|
+
const roleName = params["RoleName"] ?? "";
|
|
801
|
+
if (!roleName) {
|
|
802
|
+
return awsErrorXml(c, "ValidationError", "The request must contain the parameter RoleName.", 400);
|
|
803
|
+
}
|
|
804
|
+
const existing = aws().iamRoles.findOneBy("role_name", roleName);
|
|
805
|
+
if (existing) {
|
|
806
|
+
return awsErrorXml(c, "EntityAlreadyExists", `Role with name ${escapeXml(roleName)} already exists.`, 409);
|
|
807
|
+
}
|
|
808
|
+
const roleId = generateAwsId("AROA");
|
|
809
|
+
const path = params["Path"] ?? "/";
|
|
810
|
+
const arn = `arn:aws:iam::${accountId}:role${path}${roleName}`;
|
|
811
|
+
const assumeRolePolicy = params["AssumeRolePolicyDocument"] ?? "{}";
|
|
812
|
+
const description = params["Description"] ?? "";
|
|
813
|
+
aws().iamRoles.insert({
|
|
814
|
+
role_name: roleName,
|
|
815
|
+
role_id: roleId,
|
|
816
|
+
arn,
|
|
817
|
+
path,
|
|
818
|
+
assume_role_policy_document: assumeRolePolicy,
|
|
819
|
+
description
|
|
820
|
+
});
|
|
821
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
822
|
+
<CreateRoleResponse>
|
|
823
|
+
<CreateRoleResult>
|
|
824
|
+
<Role>
|
|
825
|
+
<Path>${escapeXml(path)}</Path>
|
|
826
|
+
<RoleName>${escapeXml(roleName)}</RoleName>
|
|
827
|
+
<RoleId>${roleId}</RoleId>
|
|
828
|
+
<Arn>${escapeXml(arn)}</Arn>
|
|
829
|
+
<CreateDate>${(/* @__PURE__ */ new Date()).toISOString()}</CreateDate>
|
|
830
|
+
<AssumeRolePolicyDocument>${encodeURIComponent(assumeRolePolicy)}</AssumeRolePolicyDocument>
|
|
831
|
+
<Description>${escapeXml(description)}</Description>
|
|
832
|
+
</Role>
|
|
833
|
+
</CreateRoleResult>
|
|
834
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
835
|
+
</CreateRoleResponse>`;
|
|
836
|
+
return awsXmlResponse(c, xml);
|
|
837
|
+
}
|
|
838
|
+
function getRole(c, params) {
|
|
839
|
+
const roleName = params["RoleName"] ?? "";
|
|
840
|
+
const role = aws().iamRoles.findOneBy("role_name", roleName);
|
|
841
|
+
if (!role) {
|
|
842
|
+
return awsErrorXml(c, "NoSuchEntity", `The role with name ${escapeXml(roleName)} cannot be found.`, 404);
|
|
843
|
+
}
|
|
844
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
845
|
+
<GetRoleResponse>
|
|
846
|
+
<GetRoleResult>
|
|
847
|
+
<Role>
|
|
848
|
+
<Path>${escapeXml(role.path)}</Path>
|
|
849
|
+
<RoleName>${escapeXml(role.role_name)}</RoleName>
|
|
850
|
+
<RoleId>${role.role_id}</RoleId>
|
|
851
|
+
<Arn>${escapeXml(role.arn)}</Arn>
|
|
852
|
+
<CreateDate>${role.created_at}</CreateDate>
|
|
853
|
+
<AssumeRolePolicyDocument>${encodeURIComponent(role.assume_role_policy_document)}</AssumeRolePolicyDocument>
|
|
854
|
+
<Description>${escapeXml(role.description)}</Description>
|
|
855
|
+
</Role>
|
|
856
|
+
</GetRoleResult>
|
|
857
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
858
|
+
</GetRoleResponse>`;
|
|
859
|
+
return awsXmlResponse(c, xml);
|
|
860
|
+
}
|
|
861
|
+
function deleteRole(c, params) {
|
|
862
|
+
const roleName = params["RoleName"] ?? "";
|
|
863
|
+
const role = aws().iamRoles.findOneBy("role_name", roleName);
|
|
864
|
+
if (!role) {
|
|
865
|
+
return awsErrorXml(c, "NoSuchEntity", `The role with name ${escapeXml(roleName)} cannot be found.`, 404);
|
|
866
|
+
}
|
|
867
|
+
aws().iamRoles.delete(role.id);
|
|
868
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
869
|
+
<DeleteRoleResponse>
|
|
870
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
871
|
+
</DeleteRoleResponse>`;
|
|
872
|
+
return awsXmlResponse(c, xml);
|
|
873
|
+
}
|
|
874
|
+
function listRoles(c) {
|
|
875
|
+
const roles = aws().iamRoles.all();
|
|
876
|
+
const rolesXml = roles.map(
|
|
877
|
+
(r) => ` <member>
|
|
878
|
+
<Path>${escapeXml(r.path)}</Path>
|
|
879
|
+
<RoleName>${escapeXml(r.role_name)}</RoleName>
|
|
880
|
+
<RoleId>${r.role_id}</RoleId>
|
|
881
|
+
<Arn>${escapeXml(r.arn)}</Arn>
|
|
882
|
+
<CreateDate>${r.created_at}</CreateDate>
|
|
883
|
+
<Description>${escapeXml(r.description)}</Description>
|
|
884
|
+
</member>`
|
|
885
|
+
).join("\n");
|
|
886
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
887
|
+
<ListRolesResponse>
|
|
888
|
+
<ListRolesResult>
|
|
889
|
+
<IsTruncated>false</IsTruncated>
|
|
890
|
+
<Roles>
|
|
891
|
+
${rolesXml}
|
|
892
|
+
</Roles>
|
|
893
|
+
</ListRolesResult>
|
|
894
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
895
|
+
</ListRolesResponse>`;
|
|
896
|
+
return awsXmlResponse(c, xml);
|
|
897
|
+
}
|
|
898
|
+
function getCallerIdentity(c) {
|
|
899
|
+
const authUser = c.get("authUser");
|
|
900
|
+
const userName = authUser?.login ?? "admin";
|
|
901
|
+
const arn = `arn:aws:iam::${accountId}:user/${userName}`;
|
|
902
|
+
const user = aws().iamUsers.findOneBy("user_name", userName);
|
|
903
|
+
const userId = user?.user_id ?? generateAwsId("AIDA");
|
|
904
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
905
|
+
<GetCallerIdentityResponse>
|
|
906
|
+
<GetCallerIdentityResult>
|
|
907
|
+
<Arn>${escapeXml(arn)}</Arn>
|
|
908
|
+
<UserId>${userId}</UserId>
|
|
909
|
+
<Account>${accountId}</Account>
|
|
910
|
+
</GetCallerIdentityResult>
|
|
911
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
912
|
+
</GetCallerIdentityResponse>`;
|
|
913
|
+
return awsXmlResponse(c, xml);
|
|
914
|
+
}
|
|
915
|
+
function assumeRole(c, params) {
|
|
916
|
+
const roleArn = params["RoleArn"] ?? "";
|
|
917
|
+
const sessionName = params["RoleSessionName"] ?? "session";
|
|
918
|
+
const role = aws().iamRoles.all().find((r) => r.arn === roleArn);
|
|
919
|
+
if (!role) {
|
|
920
|
+
return awsErrorXml(c, "NoSuchEntity", `The role specified cannot be found.`, 404);
|
|
921
|
+
}
|
|
922
|
+
const accessKeyId = "ASIA" + randomBytes2(8).toString("hex").toUpperCase();
|
|
923
|
+
const secretAccessKey = randomBytes2(30).toString("base64");
|
|
924
|
+
const sessionToken = randomBytes2(64).toString("base64");
|
|
925
|
+
const expiration = new Date(Date.now() + 3600 * 1e3).toISOString();
|
|
926
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
927
|
+
<AssumeRoleResponse>
|
|
928
|
+
<AssumeRoleResult>
|
|
929
|
+
<Credentials>
|
|
930
|
+
<AccessKeyId>${accessKeyId}</AccessKeyId>
|
|
931
|
+
<SecretAccessKey>${secretAccessKey}</SecretAccessKey>
|
|
932
|
+
<SessionToken>${sessionToken}</SessionToken>
|
|
933
|
+
<Expiration>${expiration}</Expiration>
|
|
934
|
+
</Credentials>
|
|
935
|
+
<AssumedRoleUser>
|
|
936
|
+
<Arn>${roleArn}/${sessionName}</Arn>
|
|
937
|
+
<AssumedRoleId>${role.role_id}:${sessionName}</AssumedRoleId>
|
|
938
|
+
</AssumedRoleUser>
|
|
939
|
+
</AssumeRoleResult>
|
|
940
|
+
<ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
|
|
941
|
+
</AssumeRoleResponse>`;
|
|
942
|
+
return awsXmlResponse(c, xml);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/routes/inspector.ts
|
|
947
|
+
function inspectorRoutes(ctx) {
|
|
948
|
+
const { app, store } = ctx;
|
|
949
|
+
const aws = () => getAwsStore(store);
|
|
950
|
+
app.get("/", (c) => {
|
|
951
|
+
const tab = c.req.query("tab") ?? "s3";
|
|
952
|
+
const s3Store = aws();
|
|
953
|
+
const buckets = s3Store.s3Buckets.all();
|
|
954
|
+
const queues = s3Store.sqsQueues.all();
|
|
955
|
+
const users = s3Store.iamUsers.all();
|
|
956
|
+
const roles = s3Store.iamRoles.all();
|
|
957
|
+
let contentHtml = "";
|
|
958
|
+
if (tab === "s3") {
|
|
959
|
+
const rows = buckets.map((b) => {
|
|
960
|
+
const objects = s3Store.s3Objects.findBy("bucket_name", b.bucket_name);
|
|
961
|
+
return `<tr>
|
|
962
|
+
<td>${escapeXml(b.bucket_name)}</td>
|
|
963
|
+
<td>${objects.length}</td>
|
|
964
|
+
<td>${escapeXml(b.region)}</td>
|
|
965
|
+
<td>${escapeXml(b.creation_date)}</td>
|
|
966
|
+
</tr>`;
|
|
967
|
+
}).join("\n");
|
|
968
|
+
contentHtml = `
|
|
969
|
+
<h2>S3 Buckets (${buckets.length})</h2>
|
|
970
|
+
<table>
|
|
971
|
+
<thead><tr><th>Bucket</th><th>Objects</th><th>Region</th><th>Created</th></tr></thead>
|
|
972
|
+
<tbody>${rows || '<tr><td colspan="4">No buckets</td></tr>'}</tbody>
|
|
973
|
+
</table>`;
|
|
974
|
+
for (const bucket of buckets) {
|
|
975
|
+
const objects = s3Store.s3Objects.findBy("bucket_name", bucket.bucket_name);
|
|
976
|
+
if (objects.length > 0) {
|
|
977
|
+
const objRows = objects.map(
|
|
978
|
+
(o) => `<tr>
|
|
979
|
+
<td>${escapeXml(o.key)}</td>
|
|
980
|
+
<td>${o.content_length}</td>
|
|
981
|
+
<td>${escapeXml(o.content_type)}</td>
|
|
982
|
+
<td>${escapeXml(o.last_modified)}</td>
|
|
983
|
+
</tr>`
|
|
984
|
+
).join("\n");
|
|
985
|
+
contentHtml += `
|
|
986
|
+
<h3>${escapeXml(bucket.bucket_name)} objects</h3>
|
|
987
|
+
<table>
|
|
988
|
+
<thead><tr><th>Key</th><th>Size</th><th>Type</th><th>Last Modified</th></tr></thead>
|
|
989
|
+
<tbody>${objRows}</tbody>
|
|
990
|
+
</table>`;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
} else if (tab === "sqs") {
|
|
994
|
+
const rows = queues.map((q) => {
|
|
995
|
+
const messages = s3Store.sqsMessages.findBy("queue_name", q.queue_name);
|
|
996
|
+
return `<tr>
|
|
997
|
+
<td>${escapeXml(q.queue_name)}</td>
|
|
998
|
+
<td>${messages.length}</td>
|
|
999
|
+
<td>${q.fifo ? "Yes" : "No"}</td>
|
|
1000
|
+
<td>${q.visibility_timeout}s</td>
|
|
1001
|
+
</tr>`;
|
|
1002
|
+
}).join("\n");
|
|
1003
|
+
contentHtml = `
|
|
1004
|
+
<h2>SQS Queues (${queues.length})</h2>
|
|
1005
|
+
<table>
|
|
1006
|
+
<thead><tr><th>Queue</th><th>Messages</th><th>FIFO</th><th>Visibility Timeout</th></tr></thead>
|
|
1007
|
+
<tbody>${rows || '<tr><td colspan="4">No queues</td></tr>'}</tbody>
|
|
1008
|
+
</table>`;
|
|
1009
|
+
} else if (tab === "iam") {
|
|
1010
|
+
const userRows = users.map(
|
|
1011
|
+
(u) => `<tr>
|
|
1012
|
+
<td>${escapeXml(u.user_name)}</td>
|
|
1013
|
+
<td>${escapeXml(u.user_id)}</td>
|
|
1014
|
+
<td>${u.access_keys.length}</td>
|
|
1015
|
+
<td>${escapeXml(u.arn)}</td>
|
|
1016
|
+
</tr>`
|
|
1017
|
+
).join("\n");
|
|
1018
|
+
const roleRows = roles.map(
|
|
1019
|
+
(r) => `<tr>
|
|
1020
|
+
<td>${escapeXml(r.role_name)}</td>
|
|
1021
|
+
<td>${escapeXml(r.role_id)}</td>
|
|
1022
|
+
<td>${escapeXml(r.description)}</td>
|
|
1023
|
+
<td>${escapeXml(r.arn)}</td>
|
|
1024
|
+
</tr>`
|
|
1025
|
+
).join("\n");
|
|
1026
|
+
contentHtml = `
|
|
1027
|
+
<h2>IAM Users (${users.length})</h2>
|
|
1028
|
+
<table>
|
|
1029
|
+
<thead><tr><th>User</th><th>User ID</th><th>Access Keys</th><th>ARN</th></tr></thead>
|
|
1030
|
+
<tbody>${userRows || '<tr><td colspan="4">No users</td></tr>'}</tbody>
|
|
1031
|
+
</table>
|
|
1032
|
+
<h2>IAM Roles (${roles.length})</h2>
|
|
1033
|
+
<table>
|
|
1034
|
+
<thead><tr><th>Role</th><th>Role ID</th><th>Description</th><th>ARN</th></tr></thead>
|
|
1035
|
+
<tbody>${roleRows || '<tr><td colspan="4">No roles</td></tr>'}</tbody>
|
|
1036
|
+
</table>`;
|
|
1037
|
+
}
|
|
1038
|
+
const html = `<!DOCTYPE html>
|
|
1039
|
+
<html>
|
|
1040
|
+
<head>
|
|
1041
|
+
<meta charset="UTF-8">
|
|
1042
|
+
<title>AWS Emulator - Inspector</title>
|
|
1043
|
+
<style>
|
|
1044
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
|
|
1045
|
+
.header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
|
1046
|
+
.header h1 { margin: 0; font-size: 24px; }
|
|
1047
|
+
.badge { background: #ff9900; color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
|
|
1048
|
+
.tabs { display: flex; gap: 4px; margin-bottom: 20px; }
|
|
1049
|
+
.tabs a { padding: 8px 16px; border-radius: 6px 6px 0 0; text-decoration: none; color: #333; background: #e0e0e0; }
|
|
1050
|
+
.tabs a.active { background: #fff; font-weight: 600; }
|
|
1051
|
+
.content { background: #fff; border-radius: 8px; padding: 20px; }
|
|
1052
|
+
table { width: 100%; border-collapse: collapse; margin-bottom: 16px; }
|
|
1053
|
+
th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #eee; }
|
|
1054
|
+
th { background: #f9f9f9; font-weight: 600; }
|
|
1055
|
+
h2 { margin-top: 0; }
|
|
1056
|
+
h3 { margin-top: 16px; color: #555; }
|
|
1057
|
+
</style>
|
|
1058
|
+
</head>
|
|
1059
|
+
<body>
|
|
1060
|
+
<div class="header">
|
|
1061
|
+
<h1>AWS Emulator</h1>
|
|
1062
|
+
<span class="badge">Inspector</span>
|
|
1063
|
+
</div>
|
|
1064
|
+
<div class="tabs">
|
|
1065
|
+
<a href="/?tab=s3" class="${tab === "s3" ? "active" : ""}">S3</a>
|
|
1066
|
+
<a href="/?tab=sqs" class="${tab === "sqs" ? "active" : ""}">SQS</a>
|
|
1067
|
+
<a href="/?tab=iam" class="${tab === "iam" ? "active" : ""}">IAM</a>
|
|
1068
|
+
</div>
|
|
1069
|
+
<div class="content">
|
|
1070
|
+
${contentHtml}
|
|
1071
|
+
</div>
|
|
1072
|
+
</body>
|
|
1073
|
+
</html>`;
|
|
1074
|
+
return c.html(html);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/index.ts
|
|
1079
|
+
function seedDefaults(store, baseUrl) {
|
|
1080
|
+
const aws = getAwsStore(store);
|
|
1081
|
+
const accountId = getAccountId();
|
|
1082
|
+
const region = getDefaultRegion();
|
|
1083
|
+
aws.s3Buckets.insert({
|
|
1084
|
+
bucket_name: "emulate-default",
|
|
1085
|
+
region,
|
|
1086
|
+
creation_date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1087
|
+
acl: "private",
|
|
1088
|
+
versioning_enabled: false
|
|
1089
|
+
});
|
|
1090
|
+
const queueName = "emulate-default-queue";
|
|
1091
|
+
aws.sqsQueues.insert({
|
|
1092
|
+
queue_name: queueName,
|
|
1093
|
+
queue_url: `${baseUrl}/sqs/${accountId}/${queueName}`,
|
|
1094
|
+
arn: `arn:aws:sqs:${region}:${accountId}:${queueName}`,
|
|
1095
|
+
visibility_timeout: 30,
|
|
1096
|
+
delay_seconds: 0,
|
|
1097
|
+
max_message_size: 262144,
|
|
1098
|
+
message_retention_period: 345600,
|
|
1099
|
+
receive_message_wait_time: 0,
|
|
1100
|
+
fifo: false
|
|
1101
|
+
});
|
|
1102
|
+
const userId = generateAwsId("AIDA");
|
|
1103
|
+
aws.iamUsers.insert({
|
|
1104
|
+
user_name: "admin",
|
|
1105
|
+
user_id: userId,
|
|
1106
|
+
arn: `arn:aws:iam::${accountId}:user/admin`,
|
|
1107
|
+
path: "/",
|
|
1108
|
+
access_keys: [
|
|
1109
|
+
{
|
|
1110
|
+
access_key_id: "AKIAIOSFODNN7EXAMPLE",
|
|
1111
|
+
secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
1112
|
+
status: "Active"
|
|
1113
|
+
}
|
|
1114
|
+
]
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
function seedFromConfig(store, baseUrl, config) {
|
|
1118
|
+
const aws = getAwsStore(store);
|
|
1119
|
+
const accountId = getAccountId();
|
|
1120
|
+
const region = config.region ?? getDefaultRegion();
|
|
1121
|
+
if (config.s3?.buckets) {
|
|
1122
|
+
for (const b of config.s3.buckets) {
|
|
1123
|
+
const existing = aws.s3Buckets.findOneBy("bucket_name", b.name);
|
|
1124
|
+
if (existing) continue;
|
|
1125
|
+
aws.s3Buckets.insert({
|
|
1126
|
+
bucket_name: b.name,
|
|
1127
|
+
region: b.region ?? region,
|
|
1128
|
+
creation_date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1129
|
+
acl: "private",
|
|
1130
|
+
versioning_enabled: false
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (config.sqs?.queues) {
|
|
1135
|
+
for (const q of config.sqs.queues) {
|
|
1136
|
+
const existing = aws.sqsQueues.findOneBy("queue_name", q.name);
|
|
1137
|
+
if (existing) continue;
|
|
1138
|
+
const fifo = q.fifo ?? q.name.endsWith(".fifo");
|
|
1139
|
+
aws.sqsQueues.insert({
|
|
1140
|
+
queue_name: q.name,
|
|
1141
|
+
queue_url: `${baseUrl}/sqs/${accountId}/${q.name}`,
|
|
1142
|
+
arn: `arn:aws:sqs:${region}:${accountId}:${q.name}`,
|
|
1143
|
+
visibility_timeout: q.visibility_timeout ?? 30,
|
|
1144
|
+
delay_seconds: 0,
|
|
1145
|
+
max_message_size: 262144,
|
|
1146
|
+
message_retention_period: 345600,
|
|
1147
|
+
receive_message_wait_time: 0,
|
|
1148
|
+
fifo
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
if (config.iam?.users) {
|
|
1153
|
+
for (const u of config.iam.users) {
|
|
1154
|
+
const existing = aws.iamUsers.findOneBy("user_name", u.user_name);
|
|
1155
|
+
if (existing) continue;
|
|
1156
|
+
const userId = generateAwsId("AIDA");
|
|
1157
|
+
const path = u.path ?? "/";
|
|
1158
|
+
const accessKeys = u.create_access_key ? [
|
|
1159
|
+
{
|
|
1160
|
+
access_key_id: "AKIA" + generateAwsId("").slice(0, 16),
|
|
1161
|
+
secret_access_key: generateAwsId("") + generateAwsId(""),
|
|
1162
|
+
status: "Active"
|
|
1163
|
+
}
|
|
1164
|
+
] : [];
|
|
1165
|
+
aws.iamUsers.insert({
|
|
1166
|
+
user_name: u.user_name,
|
|
1167
|
+
user_id: userId,
|
|
1168
|
+
arn: `arn:aws:iam::${accountId}:user${path}${u.user_name}`,
|
|
1169
|
+
path,
|
|
1170
|
+
access_keys: accessKeys
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (config.iam?.roles) {
|
|
1175
|
+
for (const r of config.iam.roles) {
|
|
1176
|
+
const existing = aws.iamRoles.findOneBy("role_name", r.role_name);
|
|
1177
|
+
if (existing) continue;
|
|
1178
|
+
const roleId = generateAwsId("AROA");
|
|
1179
|
+
const path = r.path ?? "/";
|
|
1180
|
+
aws.iamRoles.insert({
|
|
1181
|
+
role_name: r.role_name,
|
|
1182
|
+
role_id: roleId,
|
|
1183
|
+
arn: `arn:aws:iam::${accountId}:role${path}${r.role_name}`,
|
|
1184
|
+
path,
|
|
1185
|
+
assume_role_policy_document: r.assume_role_policy ?? "{}",
|
|
1186
|
+
description: r.description ?? ""
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
var awsPlugin = {
|
|
1192
|
+
name: "aws",
|
|
1193
|
+
register(app, store, webhooks, baseUrl, tokenMap) {
|
|
1194
|
+
const ctx = { app, store, webhooks, baseUrl, tokenMap };
|
|
1195
|
+
s3Routes(ctx);
|
|
1196
|
+
sqsRoutes(ctx);
|
|
1197
|
+
iamRoutes(ctx);
|
|
1198
|
+
inspectorRoutes(ctx);
|
|
1199
|
+
},
|
|
1200
|
+
seed(store, baseUrl) {
|
|
1201
|
+
seedDefaults(store, baseUrl);
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
var index_default = awsPlugin;
|
|
1205
|
+
export {
|
|
1206
|
+
awsPlugin,
|
|
1207
|
+
index_default as default,
|
|
1208
|
+
getAwsStore,
|
|
1209
|
+
seedFromConfig
|
|
1210
|
+
};
|
|
1211
|
+
//# sourceMappingURL=index.js.map
|