@hasna/uptime 0.1.12 → 0.1.14

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 CHANGED
@@ -6,6 +6,24 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.14] - 2026-06-28
10
+
11
+ ### Added
12
+
13
+ - Added explicit Terraform NAT task egress support. Infra owners can set
14
+ `enable_nat_task_egress = true` to allow web and non-public worker task
15
+ security groups to reach AWS public APIs through a private subnet NAT route on
16
+ TCP/443 when private VPC endpoints are not the approved egress model.
17
+
18
+ ## [0.1.13] - 2026-06-28
19
+
20
+ ### Added
21
+
22
+ - Added opt-in Terraform support for private AWS VPC endpoints. Infra owners can
23
+ enable interface endpoints for ECR API, ECR Docker, CloudWatch Logs, and
24
+ Secrets Manager, plus an S3 gateway endpoint when private route tables are
25
+ provided.
26
+
9
27
  ## [0.1.12] - 2026-06-28
10
28
 
11
29
  ### Added
package/dist/cli/index.js CHANGED
@@ -6932,7 +6932,7 @@ function buildAwsDeploymentPlan(options = {}) {
6932
6932
  const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
6933
6933
  const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
6934
6934
  const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
6935
- const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.12");
6935
+ const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.14");
6936
6936
  const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
6937
6937
  const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
6938
6938
  const cluster = `${prefix}-${stage}`;
@@ -21,7 +21,7 @@ function buildAwsDeploymentPlan(options = {}) {
21
21
  const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
22
22
  const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
23
23
  const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
24
- const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.12");
24
+ const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.14");
25
25
  const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
26
26
  const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
27
27
  const cluster = `${prefix}-${stage}`;
package/dist/index.js CHANGED
@@ -4338,7 +4338,7 @@ function buildAwsDeploymentPlan(options = {}) {
4338
4338
  const image = clean(options.image, `${imageRepositoryUri}@sha256:<image-digest>`);
4339
4339
  const evidenceBucket = clean(options.evidenceBucket, `hasna-${stage}-${prefix}-evidence`);
4340
4340
  const hostedSqliteDbPath = clean(options.hostedSqliteDbPath, DEFAULT_HOSTED_SQLITE_DB);
4341
- const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.12");
4341
+ const runtimePackageVersion = clean(options.runtimePackageVersion, "0.1.14");
4342
4342
  const protectedAccessMode = options.protectedAccessMode ?? DEFAULT_PROTECTED_ACCESS_MODE;
4343
4343
  const protectedAccessUrl = protectedAccessMode === "cloudfront_default_domain" ? "https://<cloudfront-domain>" : `https://${hostname}`;
4344
4344
  const cluster = `${prefix}-${stage}`;
@@ -80,8 +80,10 @@ The plan expects:
80
80
  - Encrypted EFS file system, access point, mount targets, and AWS Backup plan
81
81
  for `HASNA_UPTIME_HOSTED_SQLITE_DB=/data/uptime/uptime.db`.
82
82
  - S3 bucket for redacted browser evidence and generated report artifacts.
83
- - Secrets Manager or SSM refs for app env, hosted token, probe config, and
84
- reporting channel refs.
83
+ - Secrets Manager refs for app env, hosted token, probe config, and reporting
84
+ channel refs. If any ECS secret uses an SSM Parameter Store ARN, add `ssm` to
85
+ `interface_vpc_endpoint_services` or document the approved alternate egress
86
+ path before running private-only tasks.
85
87
  - CloudWatch log groups for every component plus initial web 5xx/unhealthy
86
88
  alarms. Scheduler-stall, stale-probe, and report-delivery alarms remain
87
89
  blocked until those workers emit cloud metrics.
@@ -176,8 +178,12 @@ Before setting `desired_counts.web = 1`, verify:
176
178
  - `HASNA_UPTIME_ALLOWED_ORIGINS` matches the public HTTPS edge origin;
177
179
  - CloudFront origin access is distribution-bound, not just narrowed to
178
180
  CloudFront origin-facing ranges;
179
- - web egress to ECR, Secrets Manager, CloudWatch Logs, S3, EFS, and any required
180
- endpoints has been proven through NAT or VPC endpoints;
181
+ - web egress to ECR, Secrets Manager or SSM, CloudWatch Logs, S3, EFS, and any
182
+ required endpoints has been proven from a real ECS task. Terraform endpoint
183
+ ids, route tables, and security-group rules are creation evidence only; the
184
+ scale-up evidence must include image pull, secret injection, log delivery, S3
185
+ access, and EFS mount checks through the selected NAT or private-endpoint
186
+ path;
181
187
  - scheduler, public-probe, reporter, and migration remain at `0`.
182
188
 
183
189
  Scale only the web task, then capture the ECS deployment id and task definition
@@ -52,6 +52,32 @@ type, and cost-center tags. Set `monthly_budget_limit_usd` plus
52
52
  forecasted and actual spend alerts. Leaving the email list empty skips budget
53
53
  creation and is not sufficient for live scale-out approval.
54
54
 
55
+ Private AWS API egress can be pinned through opt-in VPC endpoints by setting
56
+ `enable_private_vpc_endpoints = true` and passing `private_route_table_ids`.
57
+ This creates interface endpoints for ECR API, ECR Docker, CloudWatch Logs, and
58
+ Secrets Manager, plus an S3 gateway endpoint when route tables are supplied. The
59
+ default is `false` so package consumers do not create endpoint hourly cost
60
+ without explicit infra-owner approval. The S3 gateway endpoint is required for
61
+ private ECR image layer pulls; the module adds S3 managed-prefix-list egress for
62
+ web and non-public worker security groups when the gateway endpoint is enabled.
63
+ Endpoint policies are scoped to the Open Uptime repository, log groups,
64
+ configured secret refs, KMS key, evidence bucket, and the regional ECR layer
65
+ bucket.
66
+
67
+ If private endpoints are not approved yet, infra owners can instead set
68
+ `enable_nat_task_egress = true` to allow web and non-public worker task security
69
+ groups to reach AWS public APIs through the private subnet NAT route on TCP/443.
70
+ Keep this disabled when private endpoints are the approved egress path. Runtime
71
+ scale-up still requires ECS task evidence for image pull, secret injection, log
72
+ delivery, S3 access, and EFS mount behavior.
73
+
74
+ Interface endpoint private DNS is VPC-wide. In shared VPCs, either keep endpoint
75
+ creation in the approved networking root, or pass
76
+ `additional_vpc_endpoint_source_security_group_ids` for every workload that must
77
+ keep using those private DNS names. If any ECS secret ref uses SSM Parameter
78
+ Store instead of Secrets Manager, add `ssm` to
79
+ `interface_vpc_endpoint_services` or keep an approved non-endpoint egress path.
80
+
55
81
  ## Current Blockers
56
82
 
57
83
  - Hosted production auth/RBAC still needs scoped, revocable credentials.
package/infra/aws/main.tf CHANGED
@@ -14,6 +14,7 @@ provider "aws" {
14
14
  }
15
15
 
16
16
  data "aws_caller_identity" "current" {}
17
+ data "aws_partition" "current" {}
17
18
 
18
19
  locals {
19
20
  prefix = "${var.service_name}-${var.stage}"
@@ -63,6 +64,21 @@ locals {
63
64
  AppType = var.app_type
64
65
  CostCenter = var.cost_center
65
66
  }
67
+ s3_gateway_endpoint_enabled = var.enable_private_vpc_endpoints && contains(var.gateway_vpc_endpoint_services, "s3") && length(var.private_route_table_ids) > 0
68
+ endpoint_secret_refs = distinct(flatten([for service in values(local.services) : values(service.secrets)]))
69
+ secretsmanager_secret_refs = [for ref in local.endpoint_secret_refs : ref if can(regex(":secretsmanager:", ref))]
70
+ ssm_parameter_refs = [for ref in local.endpoint_secret_refs : ref if can(regex(":ssm:", ref))]
71
+ secretsmanager_policy_refs = (
72
+ length(local.secretsmanager_secret_refs) > 0
73
+ ? local.secretsmanager_secret_refs
74
+ : ["arn:${data.aws_partition.current.partition}:secretsmanager:${var.region}:${data.aws_caller_identity.current.account_id}:secret:${local.prefix}/no-secretsmanager-refs-configured-*"]
75
+ )
76
+ ssm_policy_refs = (
77
+ length(local.ssm_parameter_refs) > 0
78
+ ? local.ssm_parameter_refs
79
+ : ["arn:${data.aws_partition.current.partition}:ssm:${var.region}:${data.aws_caller_identity.current.account_id}:parameter/${local.prefix}/no-ssm-refs-configured"]
80
+ )
81
+ service_log_group_arns = [for group in aws_cloudwatch_log_group.service : "${group.arn}:*"]
66
82
  }
67
83
 
68
84
  data "aws_vpc" "target" {
@@ -383,6 +399,304 @@ resource "aws_security_group_rule" "worker_egress" {
383
399
  cidr_blocks = each.key == "public-probe" ? ["0.0.0.0/0"] : [data.aws_vpc.target.cidr_block]
384
400
  }
385
401
 
402
+ resource "aws_security_group_rule" "web_nat_https_egress" {
403
+ count = var.enable_nat_task_egress ? 1 : 0
404
+
405
+ type = "egress"
406
+ description = "HTTPS egress through approved NAT path"
407
+ security_group_id = aws_security_group.web.id
408
+ from_port = 443
409
+ to_port = 443
410
+ protocol = "tcp"
411
+ cidr_blocks = var.nat_task_egress_cidr_blocks
412
+ }
413
+
414
+ resource "aws_security_group_rule" "worker_nat_https_egress" {
415
+ for_each = var.enable_nat_task_egress ? {
416
+ for key, value in aws_security_group.worker : key => value if key != "public-probe"
417
+ } : {}
418
+
419
+ type = "egress"
420
+ description = "HTTPS egress through approved NAT path"
421
+ security_group_id = each.value.id
422
+ from_port = 443
423
+ to_port = 443
424
+ protocol = "tcp"
425
+ cidr_blocks = var.nat_task_egress_cidr_blocks
426
+ }
427
+
428
+ resource "aws_security_group_rule" "web_s3_gateway_egress" {
429
+ count = local.s3_gateway_endpoint_enabled ? 1 : 0
430
+
431
+ type = "egress"
432
+ description = "HTTPS to S3 gateway endpoint prefix list"
433
+ security_group_id = aws_security_group.web.id
434
+ from_port = 443
435
+ to_port = 443
436
+ protocol = "tcp"
437
+ prefix_list_ids = [aws_vpc_endpoint.gateway["s3"].prefix_list_id]
438
+ }
439
+
440
+ resource "aws_security_group_rule" "worker_s3_gateway_egress" {
441
+ for_each = local.s3_gateway_endpoint_enabled ? {
442
+ for key, value in aws_security_group.worker : key => value if key != "public-probe"
443
+ } : {}
444
+
445
+ type = "egress"
446
+ description = "HTTPS to S3 gateway endpoint prefix list"
447
+ security_group_id = each.value.id
448
+ from_port = 443
449
+ to_port = 443
450
+ protocol = "tcp"
451
+ prefix_list_ids = [aws_vpc_endpoint.gateway["s3"].prefix_list_id]
452
+ }
453
+
454
+ resource "aws_security_group" "vpc_endpoints" {
455
+ count = var.enable_private_vpc_endpoints ? 1 : 0
456
+ name = "${local.prefix}-vpc-endpoints-sg"
457
+ description = "Open Uptime interface VPC endpoints"
458
+ vpc_id = data.aws_vpc.target.id
459
+ tags = merge(local.tags, { Component = "vpc-endpoints" })
460
+ }
461
+
462
+ resource "aws_security_group_rule" "vpc_endpoints_from_web" {
463
+ count = var.enable_private_vpc_endpoints ? 1 : 0
464
+ type = "ingress"
465
+ description = "HTTPS from Open Uptime web tasks"
466
+ security_group_id = aws_security_group.vpc_endpoints[0].id
467
+ from_port = 443
468
+ to_port = 443
469
+ protocol = "tcp"
470
+ source_security_group_id = aws_security_group.web.id
471
+ }
472
+
473
+ resource "aws_security_group_rule" "vpc_endpoints_from_worker" {
474
+ for_each = var.enable_private_vpc_endpoints ? aws_security_group.worker : {}
475
+
476
+ type = "ingress"
477
+ description = "HTTPS from Open Uptime ${each.key} tasks"
478
+ security_group_id = aws_security_group.vpc_endpoints[0].id
479
+ from_port = 443
480
+ to_port = 443
481
+ protocol = "tcp"
482
+ source_security_group_id = each.value.id
483
+ }
484
+
485
+ resource "aws_security_group_rule" "vpc_endpoints_from_additional_sources" {
486
+ for_each = var.enable_private_vpc_endpoints ? toset(var.additional_vpc_endpoint_source_security_group_ids) : toset([])
487
+
488
+ type = "ingress"
489
+ description = "HTTPS from additional approved source security group"
490
+ security_group_id = aws_security_group.vpc_endpoints[0].id
491
+ from_port = 443
492
+ to_port = 443
493
+ protocol = "tcp"
494
+ source_security_group_id = each.value
495
+ }
496
+
497
+ data "aws_iam_policy_document" "vpc_endpoint_ecr_api" {
498
+ statement {
499
+ sid = "AllowEcrAuthorization"
500
+ actions = ["ecr:GetAuthorizationToken"]
501
+ resources = ["*"]
502
+
503
+ principals {
504
+ type = "*"
505
+ identifiers = ["*"]
506
+ }
507
+ }
508
+
509
+ statement {
510
+ sid = "AllowOpenUptimeRepositoryRead"
511
+ actions = [
512
+ "ecr:BatchCheckLayerAvailability",
513
+ "ecr:BatchGetImage",
514
+ "ecr:DescribeImages",
515
+ "ecr:DescribeRepositories",
516
+ "ecr:GetDownloadUrlForLayer",
517
+ ]
518
+ resources = [aws_ecr_repository.open_uptime.arn]
519
+
520
+ principals {
521
+ type = "*"
522
+ identifiers = ["*"]
523
+ }
524
+ }
525
+ }
526
+
527
+ data "aws_iam_policy_document" "vpc_endpoint_ecr_dkr" {
528
+ statement {
529
+ sid = "AllowOpenUptimeRegistryRead"
530
+ actions = [
531
+ "ecr:BatchCheckLayerAvailability",
532
+ "ecr:BatchGetImage",
533
+ "ecr:GetDownloadUrlForLayer",
534
+ ]
535
+ resources = [aws_ecr_repository.open_uptime.arn]
536
+
537
+ principals {
538
+ type = "*"
539
+ identifiers = ["*"]
540
+ }
541
+ }
542
+ }
543
+
544
+ data "aws_iam_policy_document" "vpc_endpoint_logs" {
545
+ statement {
546
+ sid = "AllowOpenUptimeLogDelivery"
547
+ actions = [
548
+ "logs:CreateLogStream",
549
+ "logs:DescribeLogStreams",
550
+ "logs:PutLogEvents",
551
+ ]
552
+ resources = local.service_log_group_arns
553
+
554
+ principals {
555
+ type = "*"
556
+ identifiers = ["*"]
557
+ }
558
+ }
559
+ }
560
+
561
+ data "aws_iam_policy_document" "vpc_endpoint_secretsmanager" {
562
+ statement {
563
+ sid = "AllowOpenUptimeSecretReads"
564
+ actions = [
565
+ "secretsmanager:DescribeSecret",
566
+ "secretsmanager:GetSecretValue",
567
+ ]
568
+ resources = local.secretsmanager_policy_refs
569
+
570
+ principals {
571
+ type = "*"
572
+ identifiers = ["*"]
573
+ }
574
+ }
575
+ }
576
+
577
+ data "aws_iam_policy_document" "vpc_endpoint_ssm" {
578
+ statement {
579
+ sid = "AllowOpenUptimeParameterReads"
580
+ actions = [
581
+ "ssm:GetParameter",
582
+ "ssm:GetParameters",
583
+ ]
584
+ resources = local.ssm_policy_refs
585
+
586
+ principals {
587
+ type = "*"
588
+ identifiers = ["*"]
589
+ }
590
+ }
591
+ }
592
+
593
+ data "aws_iam_policy_document" "vpc_endpoint_sts" {
594
+ statement {
595
+ sid = "AllowCallerIdentity"
596
+ actions = ["sts:GetCallerIdentity"]
597
+ resources = ["*"]
598
+
599
+ principals {
600
+ type = "*"
601
+ identifiers = ["*"]
602
+ }
603
+ }
604
+ }
605
+
606
+ data "aws_iam_policy_document" "vpc_endpoint_kms" {
607
+ statement {
608
+ sid = "AllowOpenUptimeKeyUse"
609
+ actions = [
610
+ "kms:Decrypt",
611
+ "kms:DescribeKey",
612
+ "kms:GenerateDataKey*",
613
+ ]
614
+ resources = [var.kms_key_arn]
615
+
616
+ principals {
617
+ type = "*"
618
+ identifiers = ["*"]
619
+ }
620
+ }
621
+ }
622
+
623
+ data "aws_iam_policy_document" "vpc_endpoint_s3" {
624
+ statement {
625
+ sid = "AllowOpenUptimeEvidenceBucket"
626
+ actions = [
627
+ "s3:AbortMultipartUpload",
628
+ "s3:GetBucketLocation",
629
+ "s3:GetObject",
630
+ "s3:ListBucket",
631
+ "s3:PutObject",
632
+ ]
633
+ resources = [
634
+ aws_s3_bucket.evidence.arn,
635
+ "${aws_s3_bucket.evidence.arn}/*",
636
+ ]
637
+
638
+ principals {
639
+ type = "*"
640
+ identifiers = ["*"]
641
+ }
642
+ }
643
+
644
+ statement {
645
+ sid = "AllowEcrLayerBucket"
646
+ actions = ["s3:GetObject"]
647
+ resources = ["arn:${data.aws_partition.current.partition}:s3:::prod-${var.region}-starport-layer-bucket/*"]
648
+
649
+ principals {
650
+ type = "*"
651
+ identifiers = ["*"]
652
+ }
653
+ }
654
+ }
655
+
656
+ resource "aws_vpc_endpoint" "interface" {
657
+ for_each = var.enable_private_vpc_endpoints ? toset(var.interface_vpc_endpoint_services) : toset([])
658
+
659
+ vpc_id = data.aws_vpc.target.id
660
+ service_name = "com.amazonaws.${var.region}.${each.key}"
661
+ vpc_endpoint_type = "Interface"
662
+ subnet_ids = var.private_subnet_ids
663
+ security_group_ids = [aws_security_group.vpc_endpoints[0].id]
664
+ private_dns_enabled = true
665
+ policy = {
666
+ "ecr.api" = data.aws_iam_policy_document.vpc_endpoint_ecr_api.json
667
+ "ecr.dkr" = data.aws_iam_policy_document.vpc_endpoint_ecr_dkr.json
668
+ logs = data.aws_iam_policy_document.vpc_endpoint_logs.json
669
+ secretsmanager = data.aws_iam_policy_document.vpc_endpoint_secretsmanager.json
670
+ ssm = data.aws_iam_policy_document.vpc_endpoint_ssm.json
671
+ sts = data.aws_iam_policy_document.vpc_endpoint_sts.json
672
+ kms = data.aws_iam_policy_document.vpc_endpoint_kms.json
673
+ }[each.key]
674
+
675
+ tags = merge(local.tags, {
676
+ Name = "${local.prefix}-${replace(each.key, ".", "-")}-endpoint"
677
+ Component = "vpc-endpoint"
678
+ Endpoint = each.key
679
+ })
680
+ }
681
+
682
+ resource "aws_vpc_endpoint" "gateway" {
683
+ for_each = var.enable_private_vpc_endpoints && length(var.private_route_table_ids) > 0 ? toset(var.gateway_vpc_endpoint_services) : toset([])
684
+
685
+ vpc_id = data.aws_vpc.target.id
686
+ service_name = "com.amazonaws.${var.region}.${each.key}"
687
+ vpc_endpoint_type = "Gateway"
688
+ route_table_ids = var.private_route_table_ids
689
+ policy = {
690
+ s3 = data.aws_iam_policy_document.vpc_endpoint_s3.json
691
+ }[each.key]
692
+
693
+ tags = merge(local.tags, {
694
+ Name = "${local.prefix}-${each.key}-endpoint"
695
+ Component = "vpc-endpoint"
696
+ Endpoint = each.key
697
+ })
698
+ }
699
+
386
700
  resource "aws_security_group" "efs" {
387
701
  name = "${local.prefix}-efs-sg"
388
702
  description = "Open Uptime EFS data store"
@@ -75,3 +75,10 @@ output "service_names" {
75
75
  [for service in aws_ecs_service.worker : service.name],
76
76
  )
77
77
  }
78
+
79
+ output "vpc_endpoint_ids" {
80
+ value = {
81
+ interface = { for service, endpoint in aws_vpc_endpoint.interface : service => endpoint.id }
82
+ gateway = { for service, endpoint in aws_vpc_endpoint.gateway : service => endpoint.id }
83
+ }
84
+ }
@@ -14,8 +14,9 @@ protected_access_mode = "cloudfront_default_domain"
14
14
  public_subnet_ids = ["subnet-replace-public-a", "subnet-replace-public-b"]
15
15
  alb_ingress_cidr_blocks = []
16
16
  private_subnet_ids = ["subnet-replace-private-a", "subnet-replace-private-b"]
17
+ private_route_table_ids = ["rtb-replace-private"]
17
18
  container_image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/open-uptime@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
18
- runtime_package_version = "0.1.12"
19
+ runtime_package_version = "0.1.14"
19
20
  certificate_arn = null
20
21
  hosted_zone_id = null
21
22
  app_env_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:open-uptime/prod/app/env"
@@ -25,6 +26,9 @@ reporting_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret
25
26
  kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/00000000-0000-0000-0000-000000000000"
26
27
  alarm_actions = []
27
28
  monthly_budget_limit_usd = 0
29
+ enable_nat_task_egress = false
30
+ enable_private_vpc_endpoints = false
31
+ additional_vpc_endpoint_source_security_group_ids = []
28
32
 
29
33
  desired_counts = {
30
34
  web = 0
@@ -116,7 +116,7 @@ variable "container_image" {
116
116
  variable "runtime_package_version" {
117
117
  description = "Published @hasna/uptime package version that CodeBuild should build into the ECR image."
118
118
  type = string
119
- default = "0.1.12"
119
+ default = "0.1.14"
120
120
 
121
121
  validation {
122
122
  condition = can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$", var.runtime_package_version))
@@ -237,3 +237,62 @@ variable "budget_alert_email_addresses" {
237
237
  type = list(string)
238
238
  default = []
239
239
  }
240
+
241
+ variable "enable_nat_task_egress" {
242
+ description = "Allow web and non-public worker tasks to reach AWS public APIs through NAT on TCP/443. Keep false when private VPC endpoints are the approved egress path."
243
+ type = bool
244
+ default = false
245
+ }
246
+
247
+ variable "nat_task_egress_cidr_blocks" {
248
+ description = "CIDR blocks allowed for NAT-backed HTTPS egress when enable_nat_task_egress is true."
249
+ type = list(string)
250
+ default = ["0.0.0.0/0"]
251
+
252
+ validation {
253
+ condition = length(var.nat_task_egress_cidr_blocks) > 0
254
+ error_message = "nat_task_egress_cidr_blocks must not be empty when NAT task egress is enabled."
255
+ }
256
+ }
257
+
258
+ variable "enable_private_vpc_endpoints" {
259
+ description = "Create private VPC endpoints for ECS access to AWS APIs. Requires private subnet ids; S3 gateway endpoint also requires private_route_table_ids."
260
+ type = bool
261
+ default = false
262
+ }
263
+
264
+ variable "interface_vpc_endpoint_services" {
265
+ description = "Regional interface endpoint service short names to create when enable_private_vpc_endpoints is true."
266
+ type = list(string)
267
+ default = ["ecr.api", "ecr.dkr", "logs", "secretsmanager"]
268
+
269
+ validation {
270
+ condition = alltrue([
271
+ for service in var.interface_vpc_endpoint_services : contains(["ecr.api", "ecr.dkr", "logs", "secretsmanager", "sts", "ssm", "kms"], service)
272
+ ])
273
+ error_message = "interface_vpc_endpoint_services must contain only approved AWS service short names."
274
+ }
275
+ }
276
+
277
+ variable "additional_vpc_endpoint_source_security_group_ids" {
278
+ description = "Additional source security groups allowed to use Open Uptime interface VPC endpoints in a shared VPC. Keep empty for dedicated Open Uptime subnets."
279
+ type = list(string)
280
+ default = []
281
+ }
282
+
283
+ variable "gateway_vpc_endpoint_services" {
284
+ description = "Regional gateway endpoint service short names to create when enable_private_vpc_endpoints is true."
285
+ type = list(string)
286
+ default = ["s3"]
287
+
288
+ validation {
289
+ condition = alltrue([for service in var.gateway_vpc_endpoint_services : contains(["s3"], service)])
290
+ error_message = "gateway_vpc_endpoint_services currently supports only s3."
291
+ }
292
+ }
293
+
294
+ variable "private_route_table_ids" {
295
+ description = "Private route table ids for gateway VPC endpoints such as S3. Leave empty to skip gateway endpoint creation."
296
+ type = list(string)
297
+ default = []
298
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/uptime",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Local-first uptime and downtime monitoring service with CLI, MCP, SDK, SQLite persistence, and a dashboard.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",