data "aws_ssm_parameter" "al2023_arm" { name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64" } data "aws_acm_certificate" "grafana" { domain = var.grafana_domain statuses = ["ISSUED"] most_recent = true } resource "aws_security_group" "alb" { name = "apm-wo-analysis-grafana-alb" description = "apm-wo grafana ALB" vpc_id = aws_vpc.grafana.id dynamic "ingress" { for_each = var.office_cidrs content { description = "HTTPS from office ${ingress.value}" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = [ingress.value] } } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "apm-wo-analysis-grafana-alb" } } resource "aws_security_group" "instance" { name = "apm-wo-analysis-grafana-instance" description = "apm-wo grafana instance" vpc_id = aws_vpc.grafana.id ingress { description = "Grafana HTTP from the ALB only" from_port = 3000 to_port = 3000 protocol = "tcp" security_groups = [aws_security_group.alb.id] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "apm-wo-analysis-grafana-instance" } } data "aws_iam_policy_document" "grafana_assume" { statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } } } resource "aws_iam_role" "grafana" { name = "apm-wo-analysis-grafana" path = "/tf-managed/" description = "Grafana instance role: Athena, Glue, S3, SSM" assume_role_policy = data.aws_iam_policy_document.grafana_assume.json permissions_boundary = aws_iam_policy.exec_boundary.arn } data "aws_iam_policy_document" "grafana" { statement { sid = "AthenaQuery" effect = "Allow" actions = [ "athena:StartQueryExecution", "athena:StopQueryExecution", "athena:GetQueryExecution", "athena:GetQueryResults", "athena:GetWorkGroup", ] resources = [ "arn:aws:athena:${var.aws_region}:${local.account_id}:workgroup/${local.athena_workgroup}", ] } statement { sid = "AthenaList" effect = "Allow" actions = ["athena:ListWorkGroups"] resources = ["*"] } statement { sid = "GlueReadOnly" effect = "Allow" actions = [ "glue:GetDatabase", "glue:GetDatabases", "glue:GetTable", "glue:GetTables", "glue:GetPartition", "glue:GetPartitions", ] resources = [ "arn:aws:glue:${var.aws_region}:${local.account_id}:catalog", "arn:aws:glue:${var.aws_region}:${local.account_id}:database/${local.glue_database}", "arn:aws:glue:${var.aws_region}:${local.account_id}:table/${local.glue_database}/*", ] } statement { sid = "ReadAnalytics" effect = "Allow" actions = [ "s3:GetObject", ] resources = [ "${aws_s3_bucket.exports.arn}/analytics/*", "${aws_s3_bucket.exports.arn}/${local.grafana_config_prefix}/*", ] } statement { sid = "AthenaResults" effect = "Allow" actions = [ "s3:GetObject", "s3:PutObject", "s3:AbortMultipartUpload", ] resources = [ "${aws_s3_bucket.exports.arn}/athena-results/*", ] } statement { sid = "ListExports" effect = "Allow" actions = ["s3:ListBucket", "s3:GetBucketLocation"] resources = [aws_s3_bucket.exports.arn] } } resource "aws_iam_role_policy" "grafana" { name = "grafana-athena-s3" role = aws_iam_role.grafana.id policy = data.aws_iam_policy_document.grafana.json } resource "aws_iam_role_policy_attachment" "grafana_ssm" { role = aws_iam_role.grafana.name policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" } resource "aws_iam_instance_profile" "grafana" { name = "apm-wo-analysis-grafana" path = "/tf-managed/" role = aws_iam_role.grafana.name } resource "aws_instance" "grafana" { ami = data.aws_ssm_parameter.al2023_arm.value instance_type = "t4g.small" subnet_id = aws_subnet.private["${var.aws_region}a"].id vpc_security_group_ids = [aws_security_group.instance.id] iam_instance_profile = aws_iam_instance_profile.grafana.name user_data = templatefile("${path.module}/templates/grafana_userdata.sh.tftpl", { config_bucket = aws_s3_bucket.exports.bucket config_prefix = local.grafana_config_prefix plugin_version = var.athena_plugin_version grafana_domain = var.grafana_domain }) metadata_options { http_endpoint = "enabled" http_tokens = "required" } root_block_device { volume_type = "gp3" volume_size = 20 encrypted = true delete_on_termination = false } tags = { Name = "apm-wo-analysis-grafana" (local.grafana_backup_tag) = "true" } lifecycle { ignore_changes = [ami, user_data] } } resource "aws_lb" "grafana" { name = "apm-wo-analysis-grafana" internal = false load_balancer_type = "application" security_groups = [aws_security_group.alb.id] subnets = [for s in aws_subnet.public : s.id] } resource "aws_lb_target_group" "grafana" { name = "apm-wo-analysis-grafana" port = 3000 protocol = "HTTP" vpc_id = aws_vpc.grafana.id target_type = "instance" health_check { path = "/api/health" matcher = "200" healthy_threshold = 2 unhealthy_threshold = 3 } } resource "aws_lb_target_group_attachment" "grafana" { target_group_arn = aws_lb_target_group.grafana.arn target_id = aws_instance.grafana.id port = 3000 } resource "aws_lb_listener" "grafana_https" { load_balancer_arn = aws_lb.grafana.arn port = 443 protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" certificate_arn = data.aws_acm_certificate.grafana.arn default_action { type = "forward" target_group_arn = aws_lb_target_group.grafana.arn } }