@appliance.sh/infra 1.13.0 → 1.14.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/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as pulumi from '@pulumi/pulumi';
|
|
2
2
|
import * as aws from '@pulumi/aws';
|
|
3
|
-
import * as awsNative from '@pulumi/aws-native';
|
|
4
3
|
|
|
5
4
|
import { ApplianceBaseConfigInput, ApplianceBaseType } from '@appliance.sh/sdk';
|
|
6
5
|
|
|
@@ -170,6 +169,193 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
|
|
|
170
169
|
{ parent: this, provider: opts?.globalProvider }
|
|
171
170
|
);
|
|
172
171
|
|
|
172
|
+
const edgeRouterRole = new aws.iam.Role(
|
|
173
|
+
`${name}-edge-router-role`,
|
|
174
|
+
{
|
|
175
|
+
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({
|
|
176
|
+
Service: ['lambda.amazonaws.com', 'edgelambda.amazonaws.com'],
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
{ parent: this, provider: opts?.globalProvider }
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
new aws.iam.RolePolicyAttachment(
|
|
183
|
+
`${name}-edge-router-role-logging`,
|
|
184
|
+
{
|
|
185
|
+
role: edgeRouterRole.name,
|
|
186
|
+
policyArn: aws.iam.ManagedPolicy.AWSLambdaBasicExecutionRole,
|
|
187
|
+
},
|
|
188
|
+
{ parent: this, provider: opts?.globalProvider }
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const edgeFunction = new aws.lambda.CallbackFunction(
|
|
192
|
+
`${name.replaceAll('.', '-')}-edge-router`,
|
|
193
|
+
{
|
|
194
|
+
role: edgeRouterRole,
|
|
195
|
+
runtime: 'nodejs22.x',
|
|
196
|
+
timeout: 5,
|
|
197
|
+
publish: true,
|
|
198
|
+
loggingConfig: {
|
|
199
|
+
logGroup: `/appliance/base/${name}/edge-router-logs`,
|
|
200
|
+
logFormat: 'Text',
|
|
201
|
+
},
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
203
|
+
callback: async (event: any) => {
|
|
204
|
+
const request = event.Records[0].cf.request;
|
|
205
|
+
const headers = request.headers;
|
|
206
|
+
const host = headers.host[0].value;
|
|
207
|
+
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
209
|
+
const dns = require('dns').promises;
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
211
|
+
const crypto = require('crypto');
|
|
212
|
+
|
|
213
|
+
let originUrl: URL;
|
|
214
|
+
let signatureRequired = false;
|
|
215
|
+
try {
|
|
216
|
+
const txtRecords = await dns.resolveTxt(`origin.${host}`);
|
|
217
|
+
const functionUrl = txtRecords[0][0];
|
|
218
|
+
originUrl = new URL(functionUrl);
|
|
219
|
+
signatureRequired = true;
|
|
220
|
+
} catch (e) {
|
|
221
|
+
console.error(`Failed to resolve TXT record for origin.${host}`, e);
|
|
222
|
+
originUrl = new URL(lambdaOriginFunctionUrl.functionUrl.get());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (signatureRequired) {
|
|
226
|
+
// Extract the region from the Lambda Function URL hostname
|
|
227
|
+
// Format: <function-id>.lambda-url.<region>.on.aws
|
|
228
|
+
const hostnameParts = originUrl.hostname.split('.');
|
|
229
|
+
const regionIndex = hostnameParts.indexOf('lambda-url');
|
|
230
|
+
const targetRegion =
|
|
231
|
+
regionIndex >= 0 && hostnameParts[regionIndex + 1] ? hostnameParts[regionIndex + 1] : 'us-east-1';
|
|
232
|
+
|
|
233
|
+
// Helper functions for SigV4 signing
|
|
234
|
+
const sha256 = (data: string | Buffer) => crypto.createHash('sha256').update(data).digest();
|
|
235
|
+
const hmacSha256 = (key: Buffer | string, data: string) =>
|
|
236
|
+
crypto.createHmac('sha256', key).update(data).digest();
|
|
237
|
+
const toHex = (buffer: Buffer) => buffer.toString('hex');
|
|
238
|
+
|
|
239
|
+
const getSignatureKey = (secretKey: string, dateStamp: string, regionName: string, serviceName: string) => {
|
|
240
|
+
const kDate = hmacSha256(`AWS4${secretKey}`, dateStamp);
|
|
241
|
+
const kRegion = hmacSha256(kDate, regionName);
|
|
242
|
+
const kService = hmacSha256(kRegion, serviceName);
|
|
243
|
+
const kSigning = hmacSha256(kService, 'aws4_request');
|
|
244
|
+
return kSigning;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Sign the request using SigV4
|
|
248
|
+
// Get credentials from environment (Lambda@Edge has access to execution role credentials)
|
|
249
|
+
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
250
|
+
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
251
|
+
const sessionToken = process.env.AWS_SESSION_TOKEN;
|
|
252
|
+
|
|
253
|
+
if (accessKeyId && secretAccessKey) {
|
|
254
|
+
const method = request.method;
|
|
255
|
+
const service = 'lambda';
|
|
256
|
+
const canonicalUri = request.uri || '/';
|
|
257
|
+
const canonicalQuerystring = request.querystring || '';
|
|
258
|
+
|
|
259
|
+
const now = new Date();
|
|
260
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
261
|
+
const dateStamp = amzDate.substring(0, 8);
|
|
262
|
+
|
|
263
|
+
// Create canonical headers
|
|
264
|
+
const payloadHash = toHex(
|
|
265
|
+
sha256(request.body?.data ? Buffer.from(request.body.data, request.body.encoding || 'base64') : '')
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const canonicalHeaders =
|
|
269
|
+
`host:${originUrl.hostname}\n` +
|
|
270
|
+
`x-amz-content-sha256:${payloadHash}\n` +
|
|
271
|
+
`x-amz-date:${amzDate}\n` +
|
|
272
|
+
(sessionToken ? `x-amz-security-token:${sessionToken}\n` : '');
|
|
273
|
+
|
|
274
|
+
const signedHeaders = sessionToken
|
|
275
|
+
? 'host;x-amz-content-sha256;x-amz-date;x-amz-security-token'
|
|
276
|
+
: 'host;x-amz-content-sha256;x-amz-date';
|
|
277
|
+
|
|
278
|
+
const canonicalRequest = [
|
|
279
|
+
method,
|
|
280
|
+
canonicalUri,
|
|
281
|
+
canonicalQuerystring,
|
|
282
|
+
canonicalHeaders,
|
|
283
|
+
signedHeaders,
|
|
284
|
+
payloadHash,
|
|
285
|
+
].join('\n');
|
|
286
|
+
|
|
287
|
+
const algorithm = 'AWS4-HMAC-SHA256';
|
|
288
|
+
const credentialScope = `${dateStamp}/${targetRegion}/${service}/aws4_request`;
|
|
289
|
+
const stringToSign = [algorithm, amzDate, credentialScope, toHex(sha256(canonicalRequest))].join('\n');
|
|
290
|
+
|
|
291
|
+
const signingKey = getSignatureKey(secretAccessKey, dateStamp, targetRegion, service);
|
|
292
|
+
const signature = toHex(hmacSha256(signingKey, stringToSign));
|
|
293
|
+
|
|
294
|
+
const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
295
|
+
|
|
296
|
+
// Add SigV4 headers to the request
|
|
297
|
+
request.headers['authorization'] = [{ key: 'Authorization', value: authorizationHeader }];
|
|
298
|
+
request.headers['x-amz-date'] = [{ key: 'X-Amz-Date', value: amzDate }];
|
|
299
|
+
request.headers['x-amz-content-sha256'] = [{ key: 'X-Amz-Content-Sha256', value: payloadHash }];
|
|
300
|
+
if (sessionToken) {
|
|
301
|
+
request.headers['x-amz-security-token'] = [{ key: 'X-Amz-Security-Token', value: sessionToken }];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
request.origin = {
|
|
307
|
+
custom: {
|
|
308
|
+
domainName: originUrl.hostname,
|
|
309
|
+
port: 443,
|
|
310
|
+
protocol: 'https',
|
|
311
|
+
path: request.path,
|
|
312
|
+
sslProtocols: ['TLSv1', 'TLSv1.1', 'TLSv1.2'],
|
|
313
|
+
readTimeout: 30,
|
|
314
|
+
keepaliveTimeout: 5,
|
|
315
|
+
customHeaders: {},
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
request.headers['host'] = [{ key: 'host', value: originUrl.hostname }];
|
|
319
|
+
request.headers['X-Forwarded-Host'] = [{ key: 'X-Forwarded-Host', value: host }];
|
|
320
|
+
|
|
321
|
+
return request;
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
{ parent: this, provider: opts?.globalProvider }
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
new aws.lambda.Permission(
|
|
328
|
+
`${name}-edge-router-invoke-url-permission`,
|
|
329
|
+
{
|
|
330
|
+
function: edgeFunction.name,
|
|
331
|
+
action: 'lambda:InvokeFunction',
|
|
332
|
+
principal: 'edgelambda.amazonaws.com',
|
|
333
|
+
statementId: 'AllowExecutionFromCloudFront',
|
|
334
|
+
},
|
|
335
|
+
{ parent: this, provider: opts?.globalProvider }
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const distributionLogs = new aws.cloudwatch.LogGroup(
|
|
339
|
+
`${name}-distribution-logs`,
|
|
340
|
+
{
|
|
341
|
+
name: pulumi.interpolate`/appliance/base/${name}/distribution-logs`,
|
|
342
|
+
retentionInDays: 7,
|
|
343
|
+
},
|
|
344
|
+
{ parent: this, provider: opts?.globalProvider }
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const distributionDeliveryDestination = new aws.cloudwatch.LogDeliveryDestination(
|
|
348
|
+
`${name.replace('.', '-')}-distribution-delivery-destination`,
|
|
349
|
+
{
|
|
350
|
+
outputFormat: 'json',
|
|
351
|
+
deliveryDestinationType: 'CWL',
|
|
352
|
+
deliveryDestinationConfiguration: {
|
|
353
|
+
destinationResourceArn: distributionLogs.arn,
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
{ parent: this, provider: opts?.globalProvider }
|
|
357
|
+
);
|
|
358
|
+
|
|
173
359
|
this.cloudfrontDistribution = new aws.cloudfront.Distribution(
|
|
174
360
|
`${name}-distribution`,
|
|
175
361
|
{
|
|
@@ -185,17 +371,24 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
|
|
|
185
371
|
.apply((res) => res.id ?? ''),
|
|
186
372
|
originRequestPolicyId: aws.cloudfront
|
|
187
373
|
.getOriginRequestPolicyOutput(
|
|
188
|
-
{ name: 'Managed-
|
|
374
|
+
{ name: 'Managed-AllViewer' },
|
|
189
375
|
{
|
|
190
376
|
parent: this,
|
|
191
377
|
provider: opts?.globalProvider,
|
|
192
378
|
}
|
|
193
379
|
)
|
|
194
380
|
.apply((res) => res.id ?? ''),
|
|
195
|
-
allowedMethods: ['
|
|
196
|
-
cachedMethods: ['
|
|
381
|
+
allowedMethods: ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'],
|
|
382
|
+
cachedMethods: ['GET', 'HEAD'],
|
|
197
383
|
targetOriginId: 'LambdaOrigin',
|
|
198
384
|
viewerProtocolPolicy: 'redirect-to-https',
|
|
385
|
+
lambdaFunctionAssociations: [
|
|
386
|
+
{
|
|
387
|
+
eventType: 'origin-request',
|
|
388
|
+
lambdaArn: edgeFunction.qualifiedArn,
|
|
389
|
+
includeBody: true,
|
|
390
|
+
},
|
|
391
|
+
],
|
|
199
392
|
},
|
|
200
393
|
origins: [
|
|
201
394
|
{
|
|
@@ -222,28 +415,24 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
|
|
|
222
415
|
{ parent: this, provider: opts?.globalProvider }
|
|
223
416
|
);
|
|
224
417
|
|
|
225
|
-
new aws.
|
|
226
|
-
`${name.
|
|
418
|
+
const distributionDeliverySource = new aws.cloudwatch.LogDeliverySource(
|
|
419
|
+
`${name.replace('.', '-')}-distribution-delivery-source`,
|
|
227
420
|
{
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
functionUrlAuthType: 'AWS_IAM',
|
|
232
|
-
sourceArn: this.cloudfrontDistribution.arn,
|
|
421
|
+
region: 'us-east-1',
|
|
422
|
+
logType: 'ACCESS_LOGS',
|
|
423
|
+
resourceArn: this.cloudfrontDistribution.arn,
|
|
233
424
|
},
|
|
234
|
-
{ parent: this, provider: opts?.
|
|
425
|
+
{ parent: this, provider: opts?.globalProvider }
|
|
235
426
|
);
|
|
236
427
|
|
|
237
|
-
new
|
|
238
|
-
`${name.
|
|
428
|
+
new aws.cloudwatch.LogDelivery(
|
|
429
|
+
`${name.replace('.', '-')}-distribution-logging`,
|
|
239
430
|
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
functionName: lambdaOrigin.name,
|
|
244
|
-
invokedViaFunctionUrl: true,
|
|
431
|
+
region: 'us-east-1',
|
|
432
|
+
deliverySourceName: distributionDeliverySource.name,
|
|
433
|
+
deliveryDestinationArn: distributionDeliveryDestination.arn,
|
|
245
434
|
},
|
|
246
|
-
{ parent: this, provider: opts?.
|
|
435
|
+
{ parent: this, provider: opts?.globalProvider }
|
|
247
436
|
);
|
|
248
437
|
|
|
249
438
|
new aws.route53.Record(
|
|
@@ -260,9 +449,16 @@ export class ApplianceBaseAwsPublic extends pulumi.ComponentResource {
|
|
|
260
449
|
|
|
261
450
|
this.config = {
|
|
262
451
|
name: name,
|
|
263
|
-
region: args.config.region,
|
|
264
452
|
stateBackendUrl: pulumi.interpolate`s3://${state.bucket}`,
|
|
265
453
|
domainName: args.config.dns.domainName,
|
|
454
|
+
type: ApplianceBaseType.ApplianceAwsPublic,
|
|
455
|
+
aws: {
|
|
456
|
+
region: args.config.region,
|
|
457
|
+
zoneId: this.zoneId,
|
|
458
|
+
cloudfrontDistributionId: this.cloudfrontDistribution.id,
|
|
459
|
+
cloudfrontDistributionDomainName: this.cloudfrontDistribution.domainName,
|
|
460
|
+
edgeRouterRoleArn: edgeRouterRole.arn,
|
|
461
|
+
},
|
|
266
462
|
};
|
|
267
463
|
|
|
268
464
|
new aws.ssm.Parameter(
|