rastack 0.0.41 → 0.0.42
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/CHANGELOG.md +7 -0
- package/dist/deploy/config.d.ts +23 -7
- package/dist/deploy/config.js +29 -12
- package/dist/deploy/io.d.ts +15 -0
- package/dist/deploy/io.js +32 -2
- package/dist/rastack-bootstrap.d.ts +2 -1
- package/dist/rastack-bootstrap.js +14 -6
- package/dist/rastack-init.js +20 -6
- package/dist/templates/cloudformation/app.yaml +635 -0
- package/dist/templates/cloudformation/bootstrap.yaml +342 -0
- package/dist/templates/cloudformation/placeholder/bootstrap +28 -0
- package/package.json +2 -2
- package/scripts/copy-templates.js +42 -0
- package/src/deploy/config.ts +35 -10
- package/src/deploy/io.ts +34 -2
- package/src/rastack-bootstrap.ts +17 -6
- package/src/rastack-init.ts +25 -6
- package/test/deploy.spec.ts +53 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
AWSTemplateFormatVersion: "2010-09-09"
|
|
2
|
+
Description: >-
|
|
3
|
+
React API Stack per-app backend. Runs the rastack-server Rust API as a Lambda
|
|
4
|
+
custom runtime over an S3 Iceberg warehouse with Cognito identity. Deployed
|
|
5
|
+
once per app; thereafter CI only runs aws lambda update-function-code. See
|
|
6
|
+
docs/serverless-deploy.md.
|
|
7
|
+
|
|
8
|
+
Parameters:
|
|
9
|
+
AppName:
|
|
10
|
+
Type: String
|
|
11
|
+
Default: airline
|
|
12
|
+
Description: >
|
|
13
|
+
Short slug identifying this app; namespaces buckets, roles, SSM. No hyphens:
|
|
14
|
+
it feeds the Cognito IdentityPoolName, which AWS validates against [\w ]+
|
|
15
|
+
(letters/digits/underscore/space only).
|
|
16
|
+
AllowedPattern: "^[a-z0-9]{1,20}$"
|
|
17
|
+
|
|
18
|
+
NamePrefix:
|
|
19
|
+
Type: String
|
|
20
|
+
Default: rastack
|
|
21
|
+
# No hyphens — same Cognito IdentityPoolName [\w ]+ constraint as AppName.
|
|
22
|
+
AllowedPattern: "^[a-z0-9]{1,20}$"
|
|
23
|
+
|
|
24
|
+
GitHubOwner:
|
|
25
|
+
Type: String
|
|
26
|
+
Default: theserverkid
|
|
27
|
+
Description: GitHub org/user of the consumer repo whose CI ships the Lambda code.
|
|
28
|
+
|
|
29
|
+
GitHubRepo:
|
|
30
|
+
Type: String
|
|
31
|
+
Default: reactapistack-demo
|
|
32
|
+
Description: Consumer repo whose workflow may assume the deploy role (code-only).
|
|
33
|
+
|
|
34
|
+
GitHubBranch:
|
|
35
|
+
Type: String
|
|
36
|
+
Default: master
|
|
37
|
+
Description: Branch of GitHubRepo allowed to deploy.
|
|
38
|
+
|
|
39
|
+
ArtifactsBucket:
|
|
40
|
+
Type: String
|
|
41
|
+
Description: >
|
|
42
|
+
Name of the bootstrap artifacts bucket that holds the Lambda placeholder
|
|
43
|
+
bundle (ArtifactsBucketName output of bootstrap.yaml).
|
|
44
|
+
|
|
45
|
+
PlaceholderKey:
|
|
46
|
+
Type: String
|
|
47
|
+
Default: placeholders/bootstrap.zip
|
|
48
|
+
Description: >
|
|
49
|
+
Key of the placeholder bundle the function is CREATED from. CI replaces the
|
|
50
|
+
real code out of band via update-function-code; this key never changes, so
|
|
51
|
+
stack updates never revert CI's code.
|
|
52
|
+
|
|
53
|
+
OIDCProviderArn:
|
|
54
|
+
Type: String
|
|
55
|
+
Default: ""
|
|
56
|
+
Description: >
|
|
57
|
+
GitHub OIDC provider ARN. Leave blank to derive the account's single
|
|
58
|
+
provider ARN automatically (bootstrap.yaml created it).
|
|
59
|
+
|
|
60
|
+
IcebergNamespace:
|
|
61
|
+
Type: String
|
|
62
|
+
Default: production
|
|
63
|
+
|
|
64
|
+
EnableDataEventTrail:
|
|
65
|
+
Type: String
|
|
66
|
+
Default: "true"
|
|
67
|
+
AllowedValues: ["true", "false"]
|
|
68
|
+
Description: >
|
|
69
|
+
Create a CloudTrail trail of S3 object-level data events on the warehouse.
|
|
70
|
+
Accounts are limited to 5 trails per region — set "false" to skip if you
|
|
71
|
+
are near the limit or centralize data events elsewhere.
|
|
72
|
+
|
|
73
|
+
AdvancedSecurity:
|
|
74
|
+
Type: String
|
|
75
|
+
Default: "ENFORCED"
|
|
76
|
+
AllowedValues: ["ENFORCED", "AUDIT", "OFF"]
|
|
77
|
+
Description: >
|
|
78
|
+
Cognito advanced security (threat protection) mode. Several regions do not
|
|
79
|
+
offer it — e.g. af-south-1, ap-east-1, ap-south-2, ap-southeast-3/4/5,
|
|
80
|
+
eu-central-2, eu-south-2, me-central-1, ca-west-1 — and there a User Pool
|
|
81
|
+
carrying a UserPoolAddOns block is undeployable (CloudFormation's early
|
|
82
|
+
validation rejects the change set). Set OFF in those regions to omit the
|
|
83
|
+
add-on entirely; `rastack init` sets this automatically from the target
|
|
84
|
+
region.
|
|
85
|
+
|
|
86
|
+
Conditions:
|
|
87
|
+
DeriveOIDCArn: !Equals [!Ref OIDCProviderArn, ""]
|
|
88
|
+
ShouldTrailDataEvents: !Equals [!Ref EnableDataEventTrail, "true"]
|
|
89
|
+
UseAdvancedSecurity: !Not [!Equals [!Ref AdvancedSecurity, "OFF"]]
|
|
90
|
+
|
|
91
|
+
Resources:
|
|
92
|
+
|
|
93
|
+
# -- Encryption key -----------------------------------------------------------
|
|
94
|
+
WarehouseKey:
|
|
95
|
+
Type: AWS::KMS::Key
|
|
96
|
+
DeletionPolicy: Retain
|
|
97
|
+
UpdateReplacePolicy: Retain
|
|
98
|
+
Properties:
|
|
99
|
+
Description: !Sub "React API Stack warehouse key (${AppName})"
|
|
100
|
+
EnableKeyRotation: true
|
|
101
|
+
KeyPolicy:
|
|
102
|
+
Version: "2012-10-17"
|
|
103
|
+
Statement:
|
|
104
|
+
- Sid: AccountAdministration
|
|
105
|
+
Effect: Allow
|
|
106
|
+
Principal:
|
|
107
|
+
AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root"
|
|
108
|
+
Action: "kms:*"
|
|
109
|
+
Resource: "*"
|
|
110
|
+
|
|
111
|
+
WarehouseKeyAlias:
|
|
112
|
+
Type: AWS::KMS::Alias
|
|
113
|
+
Properties:
|
|
114
|
+
AliasName: !Sub "alias/${NamePrefix}-${AppName}-warehouse"
|
|
115
|
+
TargetKeyId: !Ref WarehouseKey
|
|
116
|
+
|
|
117
|
+
# -- Storage — S3 IS the database (Iceberg warehouse) -------------------------
|
|
118
|
+
WarehouseBucket:
|
|
119
|
+
Type: AWS::S3::Bucket
|
|
120
|
+
DeletionPolicy: Retain
|
|
121
|
+
UpdateReplacePolicy: Retain
|
|
122
|
+
# Server-access logging needs the audit bucket's log-delivery grant in place
|
|
123
|
+
# first (ACLs are disabled, so the permission comes from its bucket policy).
|
|
124
|
+
DependsOn: AuditLogBucketPolicy
|
|
125
|
+
Properties:
|
|
126
|
+
BucketName: !Sub "${NamePrefix}-${AppName}-${AWS::AccountId}-warehouse"
|
|
127
|
+
VersioningConfiguration:
|
|
128
|
+
Status: Enabled
|
|
129
|
+
BucketEncryption:
|
|
130
|
+
ServerSideEncryptionConfiguration:
|
|
131
|
+
- ServerSideEncryptionByDefault:
|
|
132
|
+
SSEAlgorithm: aws:kms
|
|
133
|
+
KMSMasterKeyID: !GetAtt WarehouseKey.Arn
|
|
134
|
+
BucketKeyEnabled: true
|
|
135
|
+
PublicAccessBlockConfiguration:
|
|
136
|
+
BlockPublicAcls: true
|
|
137
|
+
BlockPublicPolicy: true
|
|
138
|
+
IgnorePublicAcls: true
|
|
139
|
+
RestrictPublicBuckets: true
|
|
140
|
+
OwnershipControls:
|
|
141
|
+
Rules:
|
|
142
|
+
- ObjectOwnership: BucketOwnerEnforced
|
|
143
|
+
LifecycleConfiguration:
|
|
144
|
+
Rules:
|
|
145
|
+
- Id: expire-noncurrent-versions
|
|
146
|
+
Status: Enabled
|
|
147
|
+
NoncurrentVersionExpiration:
|
|
148
|
+
NoncurrentDays: 7
|
|
149
|
+
AbortIncompleteMultipartUpload:
|
|
150
|
+
DaysAfterInitiation: 7
|
|
151
|
+
LoggingConfiguration:
|
|
152
|
+
DestinationBucketName: !Ref AuditLogBucket
|
|
153
|
+
LogFilePrefix: s3-access/warehouse/
|
|
154
|
+
|
|
155
|
+
WarehouseBucketPolicy:
|
|
156
|
+
Type: AWS::S3::BucketPolicy
|
|
157
|
+
Properties:
|
|
158
|
+
Bucket: !Ref WarehouseBucket
|
|
159
|
+
PolicyDocument:
|
|
160
|
+
Version: "2012-10-17"
|
|
161
|
+
Statement:
|
|
162
|
+
- Sid: DenyInsecureTransport
|
|
163
|
+
Effect: Deny
|
|
164
|
+
Principal: "*"
|
|
165
|
+
Action: "s3:*"
|
|
166
|
+
Resource:
|
|
167
|
+
- !GetAtt WarehouseBucket.Arn
|
|
168
|
+
- !Sub "${WarehouseBucket.Arn}/*"
|
|
169
|
+
Condition:
|
|
170
|
+
Bool:
|
|
171
|
+
aws:SecureTransport: "false"
|
|
172
|
+
|
|
173
|
+
# -- Audit trail --------------------------------------------------------------
|
|
174
|
+
AuditLogBucket:
|
|
175
|
+
Type: AWS::S3::Bucket
|
|
176
|
+
DeletionPolicy: Retain
|
|
177
|
+
UpdateReplacePolicy: Retain
|
|
178
|
+
Properties:
|
|
179
|
+
BucketName: !Sub "${NamePrefix}-${AppName}-${AWS::AccountId}-audit"
|
|
180
|
+
BucketEncryption:
|
|
181
|
+
ServerSideEncryptionConfiguration:
|
|
182
|
+
- ServerSideEncryptionByDefault:
|
|
183
|
+
SSEAlgorithm: AES256
|
|
184
|
+
PublicAccessBlockConfiguration:
|
|
185
|
+
BlockPublicAcls: true
|
|
186
|
+
BlockPublicPolicy: true
|
|
187
|
+
IgnorePublicAcls: true
|
|
188
|
+
RestrictPublicBuckets: true
|
|
189
|
+
OwnershipControls:
|
|
190
|
+
Rules:
|
|
191
|
+
- ObjectOwnership: BucketOwnerEnforced
|
|
192
|
+
LifecycleConfiguration:
|
|
193
|
+
Rules:
|
|
194
|
+
- Id: expire-audit-logs
|
|
195
|
+
Status: Enabled
|
|
196
|
+
ExpirationInDays: 400
|
|
197
|
+
|
|
198
|
+
AuditLogBucketPolicy:
|
|
199
|
+
Type: AWS::S3::BucketPolicy
|
|
200
|
+
Properties:
|
|
201
|
+
Bucket: !Ref AuditLogBucket
|
|
202
|
+
PolicyDocument:
|
|
203
|
+
Version: "2012-10-17"
|
|
204
|
+
Statement:
|
|
205
|
+
- Sid: S3ServerAccessLogsPolicy
|
|
206
|
+
Effect: Allow
|
|
207
|
+
Principal:
|
|
208
|
+
Service: logging.s3.amazonaws.com
|
|
209
|
+
Action: s3:PutObject
|
|
210
|
+
Resource: !Sub "${AuditLogBucket.Arn}/s3-access/*"
|
|
211
|
+
Condition:
|
|
212
|
+
StringEquals:
|
|
213
|
+
aws:SourceAccount: !Ref AWS::AccountId
|
|
214
|
+
- Sid: CloudTrailAclCheck
|
|
215
|
+
Effect: Allow
|
|
216
|
+
Principal:
|
|
217
|
+
Service: cloudtrail.amazonaws.com
|
|
218
|
+
Action: s3:GetBucketAcl
|
|
219
|
+
Resource: !GetAtt AuditLogBucket.Arn
|
|
220
|
+
- Sid: CloudTrailWrite
|
|
221
|
+
Effect: Allow
|
|
222
|
+
Principal:
|
|
223
|
+
Service: cloudtrail.amazonaws.com
|
|
224
|
+
Action: s3:PutObject
|
|
225
|
+
Resource: !Sub "${AuditLogBucket.Arn}/cloudtrail/AWSLogs/${AWS::AccountId}/*"
|
|
226
|
+
Condition:
|
|
227
|
+
StringEquals:
|
|
228
|
+
s3:x-amz-acl: bucket-owner-full-control
|
|
229
|
+
- Sid: DenyInsecureTransport
|
|
230
|
+
Effect: Deny
|
|
231
|
+
Principal: "*"
|
|
232
|
+
Action: "s3:*"
|
|
233
|
+
Resource:
|
|
234
|
+
- !GetAtt AuditLogBucket.Arn
|
|
235
|
+
- !Sub "${AuditLogBucket.Arn}/*"
|
|
236
|
+
Condition:
|
|
237
|
+
Bool:
|
|
238
|
+
aws:SecureTransport: "false"
|
|
239
|
+
|
|
240
|
+
WarehouseTrail:
|
|
241
|
+
Type: AWS::CloudTrail::Trail
|
|
242
|
+
Condition: ShouldTrailDataEvents
|
|
243
|
+
DependsOn: AuditLogBucketPolicy
|
|
244
|
+
Properties:
|
|
245
|
+
TrailName: !Sub "${NamePrefix}-${AppName}-warehouse-data-events"
|
|
246
|
+
S3BucketName: !Ref AuditLogBucket
|
|
247
|
+
S3KeyPrefix: cloudtrail
|
|
248
|
+
IsLogging: true
|
|
249
|
+
IncludeGlobalServiceEvents: false
|
|
250
|
+
EventSelectors:
|
|
251
|
+
- ReadWriteType: All
|
|
252
|
+
IncludeManagementEvents: false
|
|
253
|
+
DataResources:
|
|
254
|
+
- Type: AWS::S3::Object
|
|
255
|
+
Values:
|
|
256
|
+
- !Sub "${WarehouseBucket.Arn}/"
|
|
257
|
+
|
|
258
|
+
# -- Identity -----------------------------------------------------------------
|
|
259
|
+
UserPool:
|
|
260
|
+
Type: AWS::Cognito::UserPool
|
|
261
|
+
Properties:
|
|
262
|
+
UserPoolName: !Sub "${NamePrefix}-${AppName}-users"
|
|
263
|
+
AutoVerifiedAttributes: [email]
|
|
264
|
+
UsernameAttributes: [email]
|
|
265
|
+
MfaConfiguration: "ON"
|
|
266
|
+
EnabledMfas:
|
|
267
|
+
- SOFTWARE_TOKEN_MFA
|
|
268
|
+
# Omitted entirely (not just set OFF) where the region lacks threat
|
|
269
|
+
# protection — the property itself is unavailable there, so a null add-on
|
|
270
|
+
# would still fail early validation.
|
|
271
|
+
UserPoolAddOns: !If
|
|
272
|
+
- UseAdvancedSecurity
|
|
273
|
+
- AdvancedSecurityMode: !Ref AdvancedSecurity
|
|
274
|
+
- !Ref "AWS::NoValue"
|
|
275
|
+
Policies:
|
|
276
|
+
PasswordPolicy:
|
|
277
|
+
MinimumLength: 12
|
|
278
|
+
RequireLowercase: false
|
|
279
|
+
RequireUppercase: false
|
|
280
|
+
RequireNumbers: false
|
|
281
|
+
RequireSymbols: false
|
|
282
|
+
|
|
283
|
+
UserPoolClient:
|
|
284
|
+
Type: AWS::Cognito::UserPoolClient
|
|
285
|
+
Properties:
|
|
286
|
+
UserPoolId: !Ref UserPool
|
|
287
|
+
ClientName: !Sub "${NamePrefix}-${AppName}-web"
|
|
288
|
+
GenerateSecret: false
|
|
289
|
+
ExplicitAuthFlows:
|
|
290
|
+
- ALLOW_USER_SRP_AUTH
|
|
291
|
+
- ALLOW_REFRESH_TOKEN_AUTH
|
|
292
|
+
PreventUserExistenceErrors: ENABLED
|
|
293
|
+
TokenValidityUnits:
|
|
294
|
+
AccessToken: minutes
|
|
295
|
+
IdToken: minutes
|
|
296
|
+
RefreshToken: days
|
|
297
|
+
AccessTokenValidity: 30
|
|
298
|
+
IdTokenValidity: 30
|
|
299
|
+
RefreshTokenValidity: 30
|
|
300
|
+
EnableTokenRevocation: true
|
|
301
|
+
|
|
302
|
+
IdentityPool:
|
|
303
|
+
Type: AWS::Cognito::IdentityPool
|
|
304
|
+
Properties:
|
|
305
|
+
IdentityPoolName: !Sub "${NamePrefix}_${AppName}_identity"
|
|
306
|
+
AllowUnauthenticatedIdentities: false
|
|
307
|
+
CognitoIdentityProviders:
|
|
308
|
+
- ClientId: !Ref UserPoolClient
|
|
309
|
+
ProviderName: !GetAtt UserPool.ProviderName
|
|
310
|
+
|
|
311
|
+
# Temp S3 credentials fenced to warehouse/tenants/{own sub}/ via the
|
|
312
|
+
# ${cognito-identity.amazonaws.com:sub} IAM policy variable. One role, every user.
|
|
313
|
+
WarehouseTenantRole:
|
|
314
|
+
Type: AWS::IAM::Role
|
|
315
|
+
Properties:
|
|
316
|
+
RoleName: !Sub "${NamePrefix}-${AppName}-tenant"
|
|
317
|
+
AssumeRolePolicyDocument:
|
|
318
|
+
Version: "2012-10-17"
|
|
319
|
+
Statement:
|
|
320
|
+
- Effect: Allow
|
|
321
|
+
Principal:
|
|
322
|
+
Federated: cognito-identity.amazonaws.com
|
|
323
|
+
Action: sts:AssumeRoleWithWebIdentity
|
|
324
|
+
Condition:
|
|
325
|
+
StringEquals:
|
|
326
|
+
cognito-identity.amazonaws.com:aud: !Ref IdentityPool
|
|
327
|
+
ForAnyValue:StringLike:
|
|
328
|
+
cognito-identity.amazonaws.com:amr: authenticated
|
|
329
|
+
Policies:
|
|
330
|
+
- PolicyName: own-tenant-prefix-only
|
|
331
|
+
PolicyDocument:
|
|
332
|
+
Version: "2012-10-17"
|
|
333
|
+
Statement:
|
|
334
|
+
- Sid: OwnTenantObjects
|
|
335
|
+
Effect: Allow
|
|
336
|
+
Action:
|
|
337
|
+
- s3:GetObject
|
|
338
|
+
- s3:PutObject
|
|
339
|
+
- s3:DeleteObject
|
|
340
|
+
Resource: !Sub "${WarehouseBucket.Arn}/warehouse/tenants/${!cognito-identity.amazonaws.com:sub}/*"
|
|
341
|
+
- Sid: OwnTenantListing
|
|
342
|
+
Effect: Allow
|
|
343
|
+
Action: s3:ListBucket
|
|
344
|
+
Resource: !GetAtt WarehouseBucket.Arn
|
|
345
|
+
Condition:
|
|
346
|
+
StringLike:
|
|
347
|
+
s3:prefix: !Sub "warehouse/tenants/${!cognito-identity.amazonaws.com:sub}/*"
|
|
348
|
+
- Sid: WarehouseKeyViaS3Only
|
|
349
|
+
Effect: Allow
|
|
350
|
+
Action:
|
|
351
|
+
- kms:Decrypt
|
|
352
|
+
- kms:GenerateDataKey
|
|
353
|
+
Resource: !GetAtt WarehouseKey.Arn
|
|
354
|
+
Condition:
|
|
355
|
+
StringEquals:
|
|
356
|
+
kms:ViaService: !Sub "s3.${AWS::Region}.amazonaws.com"
|
|
357
|
+
|
|
358
|
+
IdentityPoolRoles:
|
|
359
|
+
Type: AWS::Cognito::IdentityPoolRoleAttachment
|
|
360
|
+
Properties:
|
|
361
|
+
IdentityPoolId: !Ref IdentityPool
|
|
362
|
+
Roles:
|
|
363
|
+
authenticated: !GetAtt WarehouseTenantRole.Arn
|
|
364
|
+
|
|
365
|
+
# -- API (API Gateway v2 HTTP API) --------------------------------------------
|
|
366
|
+
ApiAccessLogGroup:
|
|
367
|
+
Type: AWS::Logs::LogGroup
|
|
368
|
+
Properties:
|
|
369
|
+
LogGroupName: !Sub "/rastack/${AppName}/api-access"
|
|
370
|
+
RetentionInDays: 90
|
|
371
|
+
|
|
372
|
+
HttpApi:
|
|
373
|
+
Type: AWS::ApiGatewayV2::Api
|
|
374
|
+
Properties:
|
|
375
|
+
Name: !Sub "${NamePrefix}-${AppName}-api"
|
|
376
|
+
ProtocolType: HTTP
|
|
377
|
+
|
|
378
|
+
HttpApiIntegration:
|
|
379
|
+
Type: AWS::ApiGatewayV2::Integration
|
|
380
|
+
Properties:
|
|
381
|
+
ApiId: !Ref HttpApi
|
|
382
|
+
IntegrationType: AWS_PROXY
|
|
383
|
+
IntegrationUri: !GetAtt ApiFunction.Arn
|
|
384
|
+
IntegrationMethod: POST
|
|
385
|
+
PayloadFormatVersion: "2.0"
|
|
386
|
+
|
|
387
|
+
HttpApiRoute:
|
|
388
|
+
Type: AWS::ApiGatewayV2::Route
|
|
389
|
+
Properties:
|
|
390
|
+
ApiId: !Ref HttpApi
|
|
391
|
+
# $default catches every method + path (incl. /) so the Rust router owns
|
|
392
|
+
# all routing — /api/{app}/v1/{model}/ and /admin alike.
|
|
393
|
+
RouteKey: "$default"
|
|
394
|
+
Target: !Sub "integrations/${HttpApiIntegration}"
|
|
395
|
+
|
|
396
|
+
HttpApiStage:
|
|
397
|
+
Type: AWS::ApiGatewayV2::Stage
|
|
398
|
+
Properties:
|
|
399
|
+
ApiId: !Ref HttpApi
|
|
400
|
+
StageName: "$default"
|
|
401
|
+
AutoDeploy: true
|
|
402
|
+
AccessLogSettings:
|
|
403
|
+
DestinationArn: !GetAtt ApiAccessLogGroup.Arn
|
|
404
|
+
Format: '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","method":"$context.httpMethod","path":"$context.path","status":"$context.status","latencyMs":"$context.responseLatency","userAgent":"$context.identity.userAgent"}'
|
|
405
|
+
DefaultRouteSettings:
|
|
406
|
+
ThrottlingBurstLimit: 200
|
|
407
|
+
ThrottlingRateLimit: 100
|
|
408
|
+
|
|
409
|
+
# -- Compute — the Rust API as a Lambda custom runtime ------------------------
|
|
410
|
+
# Least-privilege execution role: the API touches only warehouse/* (plus its
|
|
411
|
+
# KMS key via S3) and writes its own logs — not the whole bucket, not others.
|
|
412
|
+
LambdaExecutionRole:
|
|
413
|
+
Type: AWS::IAM::Role
|
|
414
|
+
Properties:
|
|
415
|
+
RoleName: !Sub "${NamePrefix}-${AppName}-lambda-exec"
|
|
416
|
+
AssumeRolePolicyDocument:
|
|
417
|
+
Version: "2012-10-17"
|
|
418
|
+
Statement:
|
|
419
|
+
- Effect: Allow
|
|
420
|
+
Principal:
|
|
421
|
+
Service: lambda.amazonaws.com
|
|
422
|
+
Action: sts:AssumeRole
|
|
423
|
+
ManagedPolicyArns:
|
|
424
|
+
- !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
425
|
+
Policies:
|
|
426
|
+
- PolicyName: warehouse-access
|
|
427
|
+
PolicyDocument:
|
|
428
|
+
Version: "2012-10-17"
|
|
429
|
+
Statement:
|
|
430
|
+
- Sid: WarehouseObjects
|
|
431
|
+
Effect: Allow
|
|
432
|
+
Action:
|
|
433
|
+
- s3:GetObject
|
|
434
|
+
- s3:PutObject
|
|
435
|
+
- s3:DeleteObject
|
|
436
|
+
Resource: !Sub "${WarehouseBucket.Arn}/warehouse/*"
|
|
437
|
+
- Sid: WarehouseListing
|
|
438
|
+
Effect: Allow
|
|
439
|
+
Action: s3:ListBucket
|
|
440
|
+
Resource: !GetAtt WarehouseBucket.Arn
|
|
441
|
+
Condition:
|
|
442
|
+
StringLike:
|
|
443
|
+
s3:prefix: "warehouse/*"
|
|
444
|
+
- Sid: WarehouseKeyViaS3Only
|
|
445
|
+
Effect: Allow
|
|
446
|
+
Action:
|
|
447
|
+
- kms:Decrypt
|
|
448
|
+
- kms:GenerateDataKey
|
|
449
|
+
Resource: !GetAtt WarehouseKey.Arn
|
|
450
|
+
Condition:
|
|
451
|
+
StringEquals:
|
|
452
|
+
kms:ViaService: !Sub "s3.${AWS::Region}.amazonaws.com"
|
|
453
|
+
|
|
454
|
+
ApiFunctionLogGroup:
|
|
455
|
+
Type: AWS::Logs::LogGroup
|
|
456
|
+
Properties:
|
|
457
|
+
LogGroupName: !Sub "/aws/lambda/${NamePrefix}-${AppName}-api"
|
|
458
|
+
RetentionInDays: 90
|
|
459
|
+
|
|
460
|
+
# provided.al2023 arm64 (Graviton). Created from the placeholder bundle in the
|
|
461
|
+
# bootstrap artifacts bucket; CI replaces the code out of band. Code.S3Key is
|
|
462
|
+
# fixed, so stack updates never diff/overwrite what CI shipped.
|
|
463
|
+
ApiFunction:
|
|
464
|
+
Type: AWS::Lambda::Function
|
|
465
|
+
DependsOn: ApiFunctionLogGroup
|
|
466
|
+
Properties:
|
|
467
|
+
FunctionName: !Sub "${NamePrefix}-${AppName}-api"
|
|
468
|
+
Runtime: provided.al2023
|
|
469
|
+
Handler: bootstrap
|
|
470
|
+
Architectures:
|
|
471
|
+
- arm64
|
|
472
|
+
MemorySize: 512
|
|
473
|
+
Timeout: 30
|
|
474
|
+
Role: !GetAtt LambdaExecutionRole.Arn
|
|
475
|
+
Code:
|
|
476
|
+
S3Bucket: !Ref ArtifactsBucket
|
|
477
|
+
S3Key: !Ref PlaceholderKey
|
|
478
|
+
Environment:
|
|
479
|
+
Variables:
|
|
480
|
+
RASTACK_MANIFEST: /var/task/schema.rastack.json
|
|
481
|
+
RASTACK_OPENAPI: /var/task/openapi.json
|
|
482
|
+
RASTACK_WAREHOUSE: !Sub "s3://${WarehouseBucket}/warehouse"
|
|
483
|
+
RASTACK_NAMESPACE: !Ref IcebergNamespace
|
|
484
|
+
RASTACK_JWKS: /var/task/jwks.json
|
|
485
|
+
RASTACK_ISSUER: !Sub "https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}"
|
|
486
|
+
RASTACK_AUDIENCE: !Ref UserPoolClient
|
|
487
|
+
RASTACK_TOKEN_USE: access
|
|
488
|
+
|
|
489
|
+
ApiInvokePermission:
|
|
490
|
+
Type: AWS::Lambda::Permission
|
|
491
|
+
Properties:
|
|
492
|
+
FunctionName: !Ref ApiFunction
|
|
493
|
+
Action: lambda:InvokeFunction
|
|
494
|
+
Principal: apigateway.amazonaws.com
|
|
495
|
+
SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*/*"
|
|
496
|
+
|
|
497
|
+
# -- Deploy role (GitHub OIDC) — code only ------------------------------------
|
|
498
|
+
# CI assumes this to ship compiled code. It can UpdateFunctionCode on THIS one
|
|
499
|
+
# function and read this app's SSM params — nothing else — with an explicit
|
|
500
|
+
# deny that stops it touching data, keys, identity, or its own trust.
|
|
501
|
+
DeployRole:
|
|
502
|
+
Type: AWS::IAM::Role
|
|
503
|
+
Properties:
|
|
504
|
+
RoleName: !Sub "${NamePrefix}-${AppName}-deploy"
|
|
505
|
+
Description: OIDC role CI assumes to run `aws lambda update-function-code`.
|
|
506
|
+
MaxSessionDuration: 3600
|
|
507
|
+
AssumeRolePolicyDocument:
|
|
508
|
+
Version: "2012-10-17"
|
|
509
|
+
Statement:
|
|
510
|
+
- Effect: Allow
|
|
511
|
+
Principal:
|
|
512
|
+
Federated: !If
|
|
513
|
+
- DeriveOIDCArn
|
|
514
|
+
- !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com"
|
|
515
|
+
- !Ref OIDCProviderArn
|
|
516
|
+
Action: sts:AssumeRoleWithWebIdentity
|
|
517
|
+
Condition:
|
|
518
|
+
StringEquals:
|
|
519
|
+
token.actions.githubusercontent.com:aud: sts.amazonaws.com
|
|
520
|
+
StringLike:
|
|
521
|
+
token.actions.githubusercontent.com:sub: !Sub "repo:${GitHubOwner}/${GitHubRepo}:ref:refs/heads/${GitHubBranch}"
|
|
522
|
+
Policies:
|
|
523
|
+
- PolicyName: update-function-code-only
|
|
524
|
+
PolicyDocument:
|
|
525
|
+
Version: "2012-10-17"
|
|
526
|
+
Statement:
|
|
527
|
+
- Sid: UpdateThisFunction
|
|
528
|
+
Effect: Allow
|
|
529
|
+
Action:
|
|
530
|
+
- lambda:UpdateFunctionCode
|
|
531
|
+
- lambda:GetFunction
|
|
532
|
+
- lambda:GetFunctionConfiguration
|
|
533
|
+
Resource: !GetAtt ApiFunction.Arn
|
|
534
|
+
- Sid: ReadAppParameters
|
|
535
|
+
Effect: Allow
|
|
536
|
+
Action:
|
|
537
|
+
- ssm:GetParameter
|
|
538
|
+
- ssm:GetParameters
|
|
539
|
+
- ssm:GetParametersByPath
|
|
540
|
+
Resource: !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/rastack/${AppName}/*"
|
|
541
|
+
- PolicyName: deny-privilege-escalation
|
|
542
|
+
PolicyDocument:
|
|
543
|
+
Version: "2012-10-17"
|
|
544
|
+
Statement:
|
|
545
|
+
- Sid: DenyPrivilegeEscalation
|
|
546
|
+
Effect: Deny
|
|
547
|
+
Action:
|
|
548
|
+
- iam:*
|
|
549
|
+
- sts:AssumeRole
|
|
550
|
+
- organizations:*
|
|
551
|
+
- account:*
|
|
552
|
+
- kms:*
|
|
553
|
+
- s3:*
|
|
554
|
+
- cognito-idp:*
|
|
555
|
+
- cognito-identity:*
|
|
556
|
+
- lambda:AddPermission
|
|
557
|
+
- lambda:UpdateFunctionConfiguration
|
|
558
|
+
- lambda:CreateFunction
|
|
559
|
+
- lambda:DeleteFunction
|
|
560
|
+
Resource: "*"
|
|
561
|
+
|
|
562
|
+
# -- SSM — the non-secret interface CI and the frontend read ------------------
|
|
563
|
+
ParamApiUrl:
|
|
564
|
+
Type: AWS::SSM::Parameter
|
|
565
|
+
Properties:
|
|
566
|
+
Name: !Sub "/rastack/${AppName}/api-url"
|
|
567
|
+
Type: String
|
|
568
|
+
Value: !Sub "https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com"
|
|
569
|
+
|
|
570
|
+
ParamFunctionName:
|
|
571
|
+
Type: AWS::SSM::Parameter
|
|
572
|
+
Properties:
|
|
573
|
+
Name: !Sub "/rastack/${AppName}/function-name"
|
|
574
|
+
Type: String
|
|
575
|
+
Value: !Ref ApiFunction
|
|
576
|
+
|
|
577
|
+
ParamUserPoolId:
|
|
578
|
+
Type: AWS::SSM::Parameter
|
|
579
|
+
Properties:
|
|
580
|
+
Name: !Sub "/rastack/${AppName}/user-pool-id"
|
|
581
|
+
Type: String
|
|
582
|
+
Value: !Ref UserPool
|
|
583
|
+
|
|
584
|
+
ParamUserPoolClientId:
|
|
585
|
+
Type: AWS::SSM::Parameter
|
|
586
|
+
Properties:
|
|
587
|
+
Name: !Sub "/rastack/${AppName}/user-pool-client-id"
|
|
588
|
+
Type: String
|
|
589
|
+
Value: !Ref UserPoolClient
|
|
590
|
+
|
|
591
|
+
ParamIdentityPoolId:
|
|
592
|
+
Type: AWS::SSM::Parameter
|
|
593
|
+
Properties:
|
|
594
|
+
Name: !Sub "/rastack/${AppName}/identity-pool-id"
|
|
595
|
+
Type: String
|
|
596
|
+
Value: !Ref IdentityPool
|
|
597
|
+
|
|
598
|
+
ParamJwksUrl:
|
|
599
|
+
Type: AWS::SSM::Parameter
|
|
600
|
+
Properties:
|
|
601
|
+
Name: !Sub "/rastack/${AppName}/jwks-url"
|
|
602
|
+
Type: String
|
|
603
|
+
Value: !Sub "https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}/.well-known/jwks.json"
|
|
604
|
+
|
|
605
|
+
Outputs:
|
|
606
|
+
ApiUrl:
|
|
607
|
+
Description: Base URL of the deployed API (set as the frontend's remote base).
|
|
608
|
+
Value: !Sub "https://${HttpApi}.execute-api.${AWS::Region}.amazonaws.com"
|
|
609
|
+
FunctionName:
|
|
610
|
+
Description: Lambda function name — CI targets this with update-function-code.
|
|
611
|
+
Value: !Ref ApiFunction
|
|
612
|
+
DeployRoleArn:
|
|
613
|
+
Description: Wire as the AWS_DEPLOY_ROLE_ARN repo Variable in the consumer repo.
|
|
614
|
+
Value: !GetAtt DeployRole.Arn
|
|
615
|
+
Region:
|
|
616
|
+
Description: Deployment region — wire as the AWS_REGION repo Variable.
|
|
617
|
+
Value: !Ref AWS::Region
|
|
618
|
+
UserPoolId:
|
|
619
|
+
Description: Cognito User Pool (identity / JWTs).
|
|
620
|
+
Value: !Ref UserPool
|
|
621
|
+
UserPoolClientId:
|
|
622
|
+
Description: App client id — the token audience rastack-server pins.
|
|
623
|
+
Value: !Ref UserPoolClient
|
|
624
|
+
IdentityPoolId:
|
|
625
|
+
Description: Cognito Identity Pool (temp S3 credentials, sub-scoped).
|
|
626
|
+
Value: !Ref IdentityPool
|
|
627
|
+
JwksUrl:
|
|
628
|
+
Description: JWKS to bake into the Lambda bundle at build time.
|
|
629
|
+
Value: !Sub "https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}/.well-known/jwks.json"
|
|
630
|
+
WarehouseBucketName:
|
|
631
|
+
Description: S3 Iceberg warehouse — the system of record.
|
|
632
|
+
Value: !Ref WarehouseBucket
|
|
633
|
+
AuditLogBucketName:
|
|
634
|
+
Description: S3 access logs + CloudTrail data events for the warehouse.
|
|
635
|
+
Value: !Ref AuditLogBucket
|