@hasna/uptime 0.1.7 → 0.1.9
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 +27 -3
- package/README.md +11 -5
- package/dist/api.d.ts +2 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +43 -4
- package/dist/cli/index.js +80 -31
- package/dist/cloud-plan.d.ts +11 -7
- package/dist/cloud-plan.d.ts.map +1 -1
- package/dist/cloud-plan.js +30 -22
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -26
- package/docs/aws-deployment-runbook.md +29 -11
- package/infra/aws/README.md +22 -1
- package/infra/aws/main.tf +138 -10
- package/infra/aws/outputs.tf +8 -0
- package/infra/aws/terraform.tfvars.example +12 -3
- package/infra/aws/variables.tf +72 -3
- package/package.json +1 -1
package/infra/aws/main.tf
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
terraform {
|
|
2
|
-
required_version = ">= 1.
|
|
2
|
+
required_version = ">= 1.9.0"
|
|
3
3
|
|
|
4
4
|
required_providers {
|
|
5
5
|
aws = {
|
|
@@ -23,6 +23,8 @@ locals {
|
|
|
23
23
|
efs_gid = 10001
|
|
24
24
|
hosted_sqlite_db_path = "/data/uptime/uptime.db"
|
|
25
25
|
efs_enabled_services = toset(["web"])
|
|
26
|
+
use_alb_https = var.protected_access_mode == "alb_https_cert"
|
|
27
|
+
use_cloudfront = var.protected_access_mode == "cloudfront_default_domain"
|
|
26
28
|
services = {
|
|
27
29
|
web = {
|
|
28
30
|
desired_count = lookup(var.desired_counts, "web", 0)
|
|
@@ -51,10 +53,15 @@ locals {
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
tags = {
|
|
54
|
-
ManagedBy
|
|
55
|
-
Service
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
ManagedBy = "terraform"
|
|
57
|
+
Service = var.service_name
|
|
58
|
+
Project = var.project_name
|
|
59
|
+
Stage = var.stage
|
|
60
|
+
Environment = var.environment
|
|
61
|
+
Account = var.account_name
|
|
62
|
+
Owner = var.owner
|
|
63
|
+
AppType = var.app_type
|
|
64
|
+
CostCenter = var.cost_center
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
|
|
@@ -62,6 +69,11 @@ data "aws_vpc" "target" {
|
|
|
62
69
|
id = var.vpc_id
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
data "aws_ec2_managed_prefix_list" "cloudfront_origin_facing" {
|
|
73
|
+
count = local.use_cloudfront ? 1 : 0
|
|
74
|
+
name = "com.amazonaws.global.cloudfront.origin-facing"
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
resource "aws_ecr_repository" "open_uptime" {
|
|
66
78
|
name = var.ecr_repository_name
|
|
67
79
|
image_tag_mutability = "IMMUTABLE"
|
|
@@ -290,7 +302,7 @@ resource "aws_security_group" "alb" {
|
|
|
290
302
|
}
|
|
291
303
|
|
|
292
304
|
resource "aws_security_group_rule" "alb_https_ingress" {
|
|
293
|
-
count = length(var.alb_ingress_cidr_blocks) > 0 ? 1 : 0
|
|
305
|
+
count = local.use_alb_https && length(var.alb_ingress_cidr_blocks) > 0 ? 1 : 0
|
|
294
306
|
type = "ingress"
|
|
295
307
|
description = "HTTPS"
|
|
296
308
|
security_group_id = aws_security_group.alb.id
|
|
@@ -300,6 +312,17 @@ resource "aws_security_group_rule" "alb_https_ingress" {
|
|
|
300
312
|
cidr_blocks = var.alb_ingress_cidr_blocks
|
|
301
313
|
}
|
|
302
314
|
|
|
315
|
+
resource "aws_security_group_rule" "alb_http_from_cloudfront" {
|
|
316
|
+
count = local.use_cloudfront ? 1 : 0
|
|
317
|
+
type = "ingress"
|
|
318
|
+
description = "HTTP from CloudFront origin-facing ranges"
|
|
319
|
+
security_group_id = aws_security_group.alb.id
|
|
320
|
+
from_port = 80
|
|
321
|
+
to_port = 80
|
|
322
|
+
protocol = "tcp"
|
|
323
|
+
prefix_list_ids = [data.aws_ec2_managed_prefix_list.cloudfront_origin_facing[0].id]
|
|
324
|
+
}
|
|
325
|
+
|
|
303
326
|
resource "aws_security_group_rule" "alb_to_web" {
|
|
304
327
|
type = "egress"
|
|
305
328
|
description = "To Open Uptime web"
|
|
@@ -418,7 +441,7 @@ resource "aws_efs_access_point" "uptime" {
|
|
|
418
441
|
}
|
|
419
442
|
|
|
420
443
|
resource "aws_efs_mount_target" "data" {
|
|
421
|
-
for_each =
|
|
444
|
+
for_each = { for index, subnet_id in var.private_subnet_ids : tostring(index) => subnet_id }
|
|
422
445
|
file_system_id = aws_efs_file_system.data.id
|
|
423
446
|
subnet_id = each.value
|
|
424
447
|
security_groups = [aws_security_group.efs.id]
|
|
@@ -506,10 +529,25 @@ resource "aws_lb_target_group" "web" {
|
|
|
506
529
|
}
|
|
507
530
|
|
|
508
531
|
resource "aws_lb_listener" "https" {
|
|
532
|
+
count = local.use_alb_https ? 1 : 0
|
|
509
533
|
load_balancer_arn = aws_lb.open_uptime.arn
|
|
510
534
|
port = 443
|
|
511
535
|
protocol = "HTTPS"
|
|
512
536
|
certificate_arn = var.certificate_arn
|
|
537
|
+
tags = local.tags
|
|
538
|
+
|
|
539
|
+
default_action {
|
|
540
|
+
type = "forward"
|
|
541
|
+
target_group_arn = aws_lb_target_group.web.arn
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
resource "aws_lb_listener" "http_cloudfront" {
|
|
546
|
+
count = local.use_cloudfront ? 1 : 0
|
|
547
|
+
load_balancer_arn = aws_lb.open_uptime.arn
|
|
548
|
+
port = 80
|
|
549
|
+
protocol = "HTTP"
|
|
550
|
+
tags = local.tags
|
|
513
551
|
|
|
514
552
|
default_action {
|
|
515
553
|
type = "forward"
|
|
@@ -517,8 +555,61 @@ resource "aws_lb_listener" "https" {
|
|
|
517
555
|
}
|
|
518
556
|
}
|
|
519
557
|
|
|
558
|
+
resource "aws_cloudfront_distribution" "open_uptime" {
|
|
559
|
+
count = local.use_cloudfront ? 1 : 0
|
|
560
|
+
enabled = true
|
|
561
|
+
is_ipv6_enabled = true
|
|
562
|
+
comment = "Open Uptime ${local.prefix} protected HTTPS edge"
|
|
563
|
+
price_class = "PriceClass_100"
|
|
564
|
+
tags = local.tags
|
|
565
|
+
|
|
566
|
+
origin {
|
|
567
|
+
domain_name = aws_lb.open_uptime.dns_name
|
|
568
|
+
origin_id = "${local.prefix}-alb"
|
|
569
|
+
|
|
570
|
+
custom_origin_config {
|
|
571
|
+
http_port = 80
|
|
572
|
+
https_port = 443
|
|
573
|
+
origin_protocol_policy = "http-only"
|
|
574
|
+
origin_ssl_protocols = ["TLSv1.2"]
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
default_cache_behavior {
|
|
579
|
+
target_origin_id = "${local.prefix}-alb"
|
|
580
|
+
viewer_protocol_policy = "redirect-to-https"
|
|
581
|
+
compress = true
|
|
582
|
+
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
|
|
583
|
+
cached_methods = ["GET", "HEAD"]
|
|
584
|
+
default_ttl = 0
|
|
585
|
+
max_ttl = 0
|
|
586
|
+
min_ttl = 0
|
|
587
|
+
|
|
588
|
+
forwarded_values {
|
|
589
|
+
query_string = true
|
|
590
|
+
headers = ["Authorization", "Content-Type", "Origin", "X-Uptime-Hosted-Token"]
|
|
591
|
+
|
|
592
|
+
cookies {
|
|
593
|
+
forward = "all"
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
restrictions {
|
|
599
|
+
geo_restriction {
|
|
600
|
+
restriction_type = "none"
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
viewer_certificate {
|
|
605
|
+
cloudfront_default_certificate = true
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
depends_on = [aws_lb_listener.http_cloudfront]
|
|
609
|
+
}
|
|
610
|
+
|
|
520
611
|
resource "aws_route53_record" "open_uptime" {
|
|
521
|
-
count = var.hosted_zone_id == null ? 0 : 1
|
|
612
|
+
count = var.hosted_zone_id == null || !local.use_alb_https ? 0 : 1
|
|
522
613
|
zone_id = var.hosted_zone_id
|
|
523
614
|
name = var.hostname
|
|
524
615
|
type = "A"
|
|
@@ -670,7 +761,12 @@ resource "aws_ecs_task_definition" "service" {
|
|
|
670
761
|
{ name = "HASNA_UPTIME_WORKSPACE_ID", value = var.workspace_id },
|
|
671
762
|
{ name = "HASNA_UPTIME_COMPONENT", value = each.key },
|
|
672
763
|
{ name = "HASNA_UPTIME_HOSTNAME", value = var.hostname },
|
|
673
|
-
],
|
|
764
|
+
], each.key == "web" ? [
|
|
765
|
+
{
|
|
766
|
+
name = "HASNA_UPTIME_ALLOWED_ORIGINS"
|
|
767
|
+
value = local.use_cloudfront ? "https://${aws_cloudfront_distribution.open_uptime[0].domain_name}" : "https://${var.hostname}"
|
|
768
|
+
},
|
|
769
|
+
] : [], contains(local.efs_enabled_services, each.key) ? [
|
|
674
770
|
{ name = "HASNA_UPTIME_HOSTED_SQLITE_DB", value = local.hosted_sqlite_db_path },
|
|
675
771
|
] : [])
|
|
676
772
|
mountPoints = contains(local.efs_enabled_services, each.key) ? [
|
|
@@ -725,7 +821,7 @@ resource "aws_ecs_service" "web" {
|
|
|
725
821
|
container_port = local.container_port
|
|
726
822
|
}
|
|
727
823
|
|
|
728
|
-
depends_on = [aws_lb_listener.https, aws_efs_mount_target.data]
|
|
824
|
+
depends_on = [aws_lb_listener.https, aws_lb_listener.http_cloudfront, aws_efs_mount_target.data]
|
|
729
825
|
}
|
|
730
826
|
|
|
731
827
|
resource "aws_ecs_service" "worker" {
|
|
@@ -793,3 +889,35 @@ resource "aws_cloudwatch_metric_alarm" "web_unhealthy" {
|
|
|
793
889
|
TargetGroup = aws_lb_target_group.web.arn_suffix
|
|
794
890
|
}
|
|
795
891
|
}
|
|
892
|
+
|
|
893
|
+
resource "aws_budgets_budget" "monthly" {
|
|
894
|
+
count = var.monthly_budget_limit_usd > 0 && length(var.budget_alert_email_addresses) > 0 ? 1 : 0
|
|
895
|
+
name = "${local.prefix}-monthly-budget"
|
|
896
|
+
budget_type = "COST"
|
|
897
|
+
limit_amount = format("%.2f", var.monthly_budget_limit_usd)
|
|
898
|
+
limit_unit = "USD"
|
|
899
|
+
time_unit = "MONTHLY"
|
|
900
|
+
|
|
901
|
+
cost_filter {
|
|
902
|
+
name = "TagKeyValue"
|
|
903
|
+
values = [format("user:Service$%s", var.service_name)]
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
notification {
|
|
907
|
+
comparison_operator = "GREATER_THAN"
|
|
908
|
+
notification_type = "FORECASTED"
|
|
909
|
+
threshold = 80
|
|
910
|
+
threshold_type = "PERCENTAGE"
|
|
911
|
+
subscriber_email_addresses = var.budget_alert_email_addresses
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
notification {
|
|
915
|
+
comparison_operator = "GREATER_THAN"
|
|
916
|
+
notification_type = "ACTUAL"
|
|
917
|
+
threshold = 100
|
|
918
|
+
threshold_type = "PERCENTAGE"
|
|
919
|
+
subscriber_email_addresses = var.budget_alert_email_addresses
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
tags = local.tags
|
|
923
|
+
}
|
package/infra/aws/outputs.tf
CHANGED
|
@@ -14,6 +14,14 @@ output "alb_dns_name" {
|
|
|
14
14
|
value = aws_lb.open_uptime.dns_name
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
output "cloudfront_domain_name" {
|
|
18
|
+
value = try(aws_cloudfront_distribution.open_uptime[0].domain_name, null)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
output "protected_access_url" {
|
|
22
|
+
value = var.protected_access_mode == "cloudfront_default_domain" ? "https://${aws_cloudfront_distribution.open_uptime[0].domain_name}" : "https://${var.hostname}"
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
output "evidence_bucket" {
|
|
18
26
|
value = aws_s3_bucket.evidence.bucket
|
|
19
27
|
}
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
region = "us-east-1"
|
|
2
2
|
stage = "prod"
|
|
3
3
|
service_name = "open-uptime"
|
|
4
|
+
project_name = "open-uptime"
|
|
5
|
+
owner = "hasna"
|
|
6
|
+
app_type = "opensource"
|
|
7
|
+
environment = "prod"
|
|
8
|
+
cost_center = "opensource"
|
|
4
9
|
hostname = "uptime.example.com"
|
|
5
10
|
workspace_id = "workspace-id"
|
|
6
11
|
vpc_id = "vpc-xxxxxxxx"
|
|
7
12
|
ecr_repository_name = "open-uptime"
|
|
13
|
+
protected_access_mode = "cloudfront_default_domain"
|
|
8
14
|
public_subnet_ids = ["subnet-replace-public-a", "subnet-replace-public-b"]
|
|
9
15
|
alb_ingress_cidr_blocks = []
|
|
10
16
|
private_subnet_ids = ["subnet-replace-private-a", "subnet-replace-private-b"]
|
|
11
17
|
container_image = "123456789012.dkr.ecr.us-east-1.amazonaws.com/open-uptime@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
12
|
-
runtime_package_version = "0.1.
|
|
13
|
-
certificate_arn =
|
|
14
|
-
hosted_zone_id =
|
|
18
|
+
runtime_package_version = "0.1.9"
|
|
19
|
+
certificate_arn = null
|
|
20
|
+
hosted_zone_id = null
|
|
15
21
|
app_env_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:open-uptime/prod/app/env"
|
|
16
22
|
hosted_token_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:open-uptime/prod/hosted-token"
|
|
17
23
|
public_probe_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:open-uptime/prod/probe/public"
|
|
18
24
|
reporting_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:open-uptime/prod/reporting"
|
|
19
25
|
kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/00000000-0000-0000-0000-000000000000"
|
|
20
26
|
alarm_actions = []
|
|
27
|
+
monthly_budget_limit_usd = 0
|
|
21
28
|
|
|
22
29
|
desired_counts = {
|
|
23
30
|
web = 0
|
|
@@ -26,3 +33,5 @@ desired_counts = {
|
|
|
26
33
|
reporter = 0
|
|
27
34
|
migration = 0
|
|
28
35
|
}
|
|
36
|
+
|
|
37
|
+
budget_alert_email_addresses = []
|
package/infra/aws/variables.tf
CHANGED
|
@@ -22,6 +22,36 @@ variable "service_name" {
|
|
|
22
22
|
default = "open-uptime"
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
variable "project_name" {
|
|
26
|
+
description = "Project tag value for cost allocation."
|
|
27
|
+
type = string
|
|
28
|
+
default = "open-uptime"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
variable "owner" {
|
|
32
|
+
description = "Owner tag value for cost allocation and operations."
|
|
33
|
+
type = string
|
|
34
|
+
default = "hasna"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
variable "app_type" {
|
|
38
|
+
description = "AppType tag value."
|
|
39
|
+
type = string
|
|
40
|
+
default = "opensource"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
variable "environment" {
|
|
44
|
+
description = "Environment tag value."
|
|
45
|
+
type = string
|
|
46
|
+
default = "prod"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
variable "cost_center" {
|
|
50
|
+
description = "CostCenter tag value."
|
|
51
|
+
type = string
|
|
52
|
+
default = "opensource"
|
|
53
|
+
}
|
|
54
|
+
|
|
25
55
|
variable "hostname" {
|
|
26
56
|
description = "Public/internal hostname for Open Uptime."
|
|
27
57
|
type = string
|
|
@@ -46,13 +76,24 @@ variable "ecr_repository_name" {
|
|
|
46
76
|
default = "open-uptime"
|
|
47
77
|
}
|
|
48
78
|
|
|
79
|
+
variable "protected_access_mode" {
|
|
80
|
+
description = "Protected web access mode. cloudfront_default_domain uses the CloudFront HTTPS default domain and restricts ALB HTTP to CloudFront origin-facing ranges. alb_https_cert uses an ALB HTTPS listener with certificate_arn."
|
|
81
|
+
type = string
|
|
82
|
+
default = "cloudfront_default_domain"
|
|
83
|
+
|
|
84
|
+
validation {
|
|
85
|
+
condition = contains(["cloudfront_default_domain", "alb_https_cert"], var.protected_access_mode)
|
|
86
|
+
error_message = "protected_access_mode must be cloudfront_default_domain or alb_https_cert."
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
49
90
|
variable "public_subnet_ids" {
|
|
50
91
|
description = "Public subnets for the ALB."
|
|
51
92
|
type = list(string)
|
|
52
93
|
}
|
|
53
94
|
|
|
54
95
|
variable "alb_ingress_cidr_blocks" {
|
|
55
|
-
description = "Approved HTTPS source CIDR blocks for
|
|
96
|
+
description = "Approved HTTPS source CIDR blocks for ALB HTTPS mode. Keep empty until edge/source policy is approved."
|
|
56
97
|
type = list(string)
|
|
57
98
|
default = []
|
|
58
99
|
}
|
|
@@ -75,7 +116,7 @@ variable "container_image" {
|
|
|
75
116
|
variable "runtime_package_version" {
|
|
76
117
|
description = "Published @hasna/uptime package version that CodeBuild should build into the ECR image."
|
|
77
118
|
type = string
|
|
78
|
-
default = "0.1.
|
|
119
|
+
default = "0.1.9"
|
|
79
120
|
|
|
80
121
|
validation {
|
|
81
122
|
condition = can(regex("^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$", var.runtime_package_version))
|
|
@@ -84,8 +125,19 @@ variable "runtime_package_version" {
|
|
|
84
125
|
}
|
|
85
126
|
|
|
86
127
|
variable "certificate_arn" {
|
|
87
|
-
description = "ACM certificate ARN for HTTPS
|
|
128
|
+
description = "ACM certificate ARN for ALB HTTPS mode. Leave null when protected_access_mode is cloudfront_default_domain."
|
|
88
129
|
type = string
|
|
130
|
+
default = null
|
|
131
|
+
|
|
132
|
+
validation {
|
|
133
|
+
condition = var.certificate_arn == null || can(regex("^arn:aws:acm:", var.certificate_arn))
|
|
134
|
+
error_message = "certificate_arn must be null or an ACM certificate ARN."
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
validation {
|
|
138
|
+
condition = var.protected_access_mode != "alb_https_cert" || var.certificate_arn != null
|
|
139
|
+
error_message = "certificate_arn is required when protected_access_mode is alb_https_cert."
|
|
140
|
+
}
|
|
89
141
|
}
|
|
90
142
|
|
|
91
143
|
variable "hosted_zone_id" {
|
|
@@ -168,3 +220,20 @@ variable "alarm_actions" {
|
|
|
168
220
|
type = list(string)
|
|
169
221
|
default = []
|
|
170
222
|
}
|
|
223
|
+
|
|
224
|
+
variable "monthly_budget_limit_usd" {
|
|
225
|
+
description = "Optional monthly AWS Budgets limit in USD. Set with budget_alert_email_addresses to create a budget alert."
|
|
226
|
+
type = number
|
|
227
|
+
default = 0
|
|
228
|
+
|
|
229
|
+
validation {
|
|
230
|
+
condition = var.monthly_budget_limit_usd >= 0
|
|
231
|
+
error_message = "monthly_budget_limit_usd must be non-negative."
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
variable "budget_alert_email_addresses" {
|
|
236
|
+
description = "Email recipients for AWS Budgets forecasted and actual alerts. Leave empty to skip budget creation."
|
|
237
|
+
type = list(string)
|
|
238
|
+
default = []
|
|
239
|
+
}
|
package/package.json
CHANGED