Skip to main content

Overview

This guide provides Terraform configuration examples for setting up the AWS infrastructure required for Meter’s SIEM integration. You’ll create a Kinesis Data Stream and IAM role that allows Meter to securely write security events to your AWS account.

Prerequisites

  • Terraform installed (version 1.0 or later recommended)
  • AWS account with permissions to create IAM roles, policies, and Kinesis streams
  • AWS credentials configured for Terraform (via environment variables, AWS CLI, or IAM role)
  • Basic familiarity with Terraform and AWS IAM concepts

Use cases

  • Automate SIEM integration infrastructure deployment across multiple AWS accounts
  • Maintain infrastructure-as-code for audit and compliance requirements
  • Quickly replicate SIEM setup for dev, staging, and production environments
  • Version control your security integration configuration

Minimal configuration

This minimal example creates the required AWS resources with secure defaults.

Variables

Define these variables in your Terraform configuration:
variable "aws_region" {
  description = "AWS region for Kinesis stream"
  type        = string
  default     = "us-east-1"
}

variable "meter_service_role_arn" {
  description = "ARN of Meter's IAM service role that will assume your role"
  type        = string
  default     = "arn:aws:iam::458553032353:role/Meter-data-export"
}

variable "external_id" {
  description = "Secret external ID for secure role assumption"
  type        = string
  sensitive   = true
}

variable "kinesis_stream_name" {
  description = "Name of the Kinesis stream for SIEM events"
  type        = string
  default     = "meter-siem-events"
}

variable "kinesis_shard_count" {
  description = "Number of shards for the Kinesis stream"
  type        = number
  default     = 1
}

variable "kinesis_retention_hours" {
  description = "Data retention period in hours (24-8760)"
  type        = number
  default     = 24
}

Provider configuration

terraform {
  required_version = ">= 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

Kinesis Data Stream

Create a Kinesis stream to receive events from Meter:
resource "aws_kinesis_stream" "meter_siem" {
  name             = var.kinesis_stream_name
  shard_count      = var.kinesis_shard_count
  retention_period = var.kinesis_retention_hours

  shard_level_metrics = [
    "IncomingBytes",
    "IncomingRecords",
    "OutgoingBytes",
    "OutgoingRecords",
  ]

  stream_mode_details {
    stream_mode = "PROVISIONED"
  }

  tags = {
    Name        = "Meter SIEM Integration"
    ManagedBy   = "Terraform"
    Purpose     = "Security Event Ingestion"
  }
}

IAM role for Meter

Create an IAM role that Meter can assume to write to your Kinesis stream:
resource "aws_iam_role" "meter_siem_publisher" {
  name        = "MeterSIEMIntegrationRole"
  description = "Allows Meter to publish security events to Kinesis"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          AWS = var.meter_service_role_arn
        }
        Action = "sts:AssumeRole"
        Condition = {
          StringEquals = {
            "sts:ExternalId" = var.external_id
          }
        }
      }
    ]
  })

  tags = {
    Name      = "Meter SIEM Integration"
    ManagedBy = "Terraform"
  }
}

IAM policy for Kinesis write access

Create a policy that grants write permissions to the Kinesis stream:
resource "aws_iam_policy" "meter_kinesis_write" {
  name        = "MeterSIEMKinesisWritePolicy"
  description = "Allows writing records to Meter SIEM Kinesis stream"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "kinesis:PutRecord",
          "kinesis:PutRecords"
        ]
        Resource = aws_kinesis_stream.meter_siem.arn
      }
    ]
  })
}

Attach policy to role

resource "aws_iam_role_policy_attachment" "meter_kinesis_attach" {
  role       = aws_iam_role.meter_siem_publisher.name
  policy_arn = aws_iam_policy.meter_kinesis_write.arn
}

Outputs

Export the values needed for Dashboard configuration:
output "kinesis_stream_arn" {
  description = "ARN of the Kinesis stream - use this in Dashboard"
  value       = aws_kinesis_stream.meter_siem.arn
}

output "iam_role_arn" {
  description = "ARN of the IAM role - use this in Dashboard"
  value       = aws_iam_role.meter_siem_publisher.arn
}

output "external_id" {
  description = "External ID for role assumption - use this in Dashboard"
  value       = var.external_id
  sensitive   = true
}

output "kinesis_stream_name" {
  description = "Name of the Kinesis stream"
  value       = aws_kinesis_stream.meter_siem.name
}

Configuration options

Kinesis stream settings

ParameterTypeDefaultDescription
namestringRequiredName of the Kinesis stream
shard_countnumber1Number of shards (each shard: 1 MB/sec in, 2 MB/sec out)
retention_periodnumber24Data retention in hours (24-8760)
stream_modestring”PROVISIONED”Use “PROVISIONED” for predictable costs, “ON_DEMAND” for variable traffic
shard_level_metricslistSee exampleCloudWatch metrics to enable for monitoring

IAM role configuration

ParameterTypeRequiredDescription
meter_service_role_arnstringYesARN of Meter’s service role (provided by Meter)
external_idstringYesRandom secret string (64+ characters recommended)

Advanced configuration

On-demand Kinesis stream

For unpredictable event volumes, use on-demand mode:
resource "aws_kinesis_stream" "meter_siem" {
  name             = var.kinesis_stream_name
  retention_period = var.kinesis_retention_hours

  stream_mode_details {
    stream_mode = "ON_DEMAND"
  }

  shard_level_metrics = [
    "IncomingBytes",
    "IncomingRecords",
  ]

  tags = {
    Name = "Meter SIEM Integration (On-Demand)"
  }
}

Enhanced monitoring with CloudWatch alarms

Add CloudWatch alarms to monitor integration health:
resource "aws_cloudwatch_metric_alarm" "kinesis_no_incoming_records" {
  alarm_name          = "meter-siem-no-events"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = 2
  metric_name         = "IncomingRecords"
  namespace           = "AWS/Kinesis"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  alarm_description   = "Alert if no SIEM events received for 10 minutes"
  treat_missing_data  = "breaching"

  dimensions = {
    StreamName = aws_kinesis_stream.meter_siem.name
  }

  alarm_actions = [var.sns_topic_arn]
}

resource "aws_cloudwatch_metric_alarm" "kinesis_write_throttled" {
  alarm_name          = "meter-siem-write-throttled"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 1
  metric_name         = "WriteProvisionedThroughputExceeded"
  namespace           = "AWS/Kinesis"
  period              = 60
  statistic           = "Sum"
  threshold           = 0
  alarm_description   = "Alert if Kinesis writes are being throttled"

  dimensions = {
    StreamName = aws_kinesis_stream.meter_siem.name
  }

  alarm_actions = [var.sns_topic_arn]
}

Server-side encryption

Enable encryption at rest for compliance:
resource "aws_kinesis_stream" "meter_siem" {
  name             = var.kinesis_stream_name
  shard_count      = var.kinesis_shard_count
  retention_period = var.kinesis_retention_hours

  encryption_type = "KMS"
  kms_key_id      = aws_kms_key.kinesis_encryption.id

  stream_mode_details {
    stream_mode = "PROVISIONED"
  }
}

resource "aws_kms_key" "kinesis_encryption" {
  description             = "KMS key for Meter SIEM Kinesis stream encryption"
  deletion_window_in_days = 10
  enable_key_rotation     = true

  tags = {
    Name = "Meter SIEM Kinesis Encryption"
  }
}

resource "aws_kms_alias" "kinesis_encryption" {
  name          = "alias/meter-siem-kinesis"
  target_key_id = aws_kms_key.kinesis_encryption.key_id
}

# Update IAM policy to allow KMS operations
resource "aws_iam_policy" "meter_kinesis_write_encrypted" {
  name        = "MeterSIEMKinesisWritePolicy"
  description = "Allows writing records to encrypted Meter SIEM Kinesis stream"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "kinesis:PutRecord",
          "kinesis:PutRecords"
        ]
        Resource = aws_kinesis_stream.meter_siem.arn
      },
      {
        Effect = "Allow"
        Action = [
          "kms:Decrypt",
          "kms:GenerateDataKey"
        ]
        Resource = aws_kms_key.kinesis_encryption.arn
      }
    ]
  })
}

VPC endpoints for private connectivity

Route Kinesis traffic through your VPC:
resource "aws_vpc_endpoint" "kinesis_streams" {
  vpc_id            = var.vpc_id
  service_name      = "com.amazonaws.${var.aws_region}.kinesis-streams"
  vpc_endpoint_type = "Interface"

  subnet_ids = var.private_subnet_ids

  security_group_ids = [
    aws_security_group.kinesis_endpoint.id
  ]

  private_dns_enabled = true

  tags = {
    Name = "Meter SIEM Kinesis VPC Endpoint"
  }
}

resource "aws_security_group" "kinesis_endpoint" {
  name        = "meter-siem-kinesis-endpoint"
  description = "Security group for Kinesis VPC endpoint"
  vpc_id      = var.vpc_id

  ingress {
    description = "HTTPS from VPC"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.vpc_cidr]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Integration with Kinesis Firehose

Forward events to S3 for long-term storage:
resource "aws_kinesis_firehose_delivery_stream" "meter_siem_to_s3" {
  name        = "meter-siem-to-s3"
  destination = "extended_s3"

  kinesis_source_configuration {
    kinesis_stream_arn = aws_kinesis_stream.meter_siem.arn
    role_arn           = aws_iam_role.firehose_role.arn
  }

  extended_s3_configuration {
    role_arn   = aws_iam_role.firehose_role.arn
    bucket_arn = aws_s3_bucket.siem_events.arn
    prefix     = "siem-events/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/"

    compression_format = "GZIP"

    buffer_size     = 5
    buffer_interval = 300
  }
}

resource "aws_s3_bucket" "siem_events" {
  bucket = "your-org-meter-siem-events"
}

resource "aws_s3_bucket_lifecycle_configuration" "siem_events_lifecycle" {
  bucket = aws_s3_bucket.siem_events.id

  rule {
    id     = "archive-old-events"
    status = "Enabled"

    transition {
      days          = 90
      storage_class = "GLACIER"
    }

    expiration {
      days = 365
    }
  }
}

Deployment steps

  1. Generate an external ID:
    openssl rand -hex 32
    
    Save this value securely (e.g., AWS Secrets Manager, 1Password).
  2. Create a terraform.tfvars file:
    aws_region              = "us-east-1"
    meter_service_role_arn  = "arn:aws:iam::458553032353:role/Meter-data-export"
    external_id             = "YOUR_GENERATED_EXTERNAL_ID"
    kinesis_stream_name     = "meter-siem-events"
    kinesis_shard_count     = 1
    kinesis_retention_hours = 24
    
  3. Initialize Terraform:
    terraform init
    
  4. Review the plan:
    terraform plan
    
  5. Apply the configuration:
    terraform apply
    
  6. Capture the outputs:
    terraform output kinesis_stream_arn
    terraform output iam_role_arn
    terraform output -raw external_id
    
  7. Configure in Meter Dashboard:

Best practices

Security

  • Store external ID securely: Never commit the external ID to version control. Use Terraform variables with sensitive = true and store the value in AWS Secrets Manager or a secure secrets management tool.
  • Enable encryption: Use KMS encryption for streams containing sensitive security data.
  • Least privilege: Only grant PutRecord and PutRecords permissions, not full Kinesis access.
  • Enable CloudTrail: Log all API calls to your Kinesis stream and IAM role for audit purposes.

Cost optimization

  • Right-size shards: Start with 1 shard and monitor IncomingBytes and WriteProvisionedThroughputExceeded metrics. Add shards only when needed.
  • Optimize retention: The default 24-hour retention is sufficient if you’re consuming events in real-time. Longer retention increases costs.
  • Use on-demand carefully: On-demand mode is convenient but can be more expensive for consistent, high-volume streams.

Operational

  • Tag resources: Add consistent tags for cost tracking and resource management.
  • Monitor metrics: Enable shard-level metrics and set up CloudWatch alarms for proactive monitoring.
  • Test before production: Deploy to a test environment first and send test events to validate the configuration.
  • Document your setup: Keep a record of your Terraform module version, external ID rotation schedule, and any customizations.

Troubleshooting

Terraform apply fails with “AccessDenied”

Cause: Your AWS credentials lack permissions to create IAM roles or Kinesis streams. Solution: Ensure your Terraform execution role has permissions for iam:CreateRole, iam:CreatePolicy, kinesis:CreateStream, etc.

Integration shows “Unauthorized” in Dashboard

Cause: The IAM role trust policy or external ID is misconfigured. Solution:
  • Verify var.meter_service_role_arn is the correct ARN provided by Meter
  • Ensure the external_id in Terraform exactly matches what you entered in Dashboard
  • Check the IAM role was created successfully: aws iam get-role --role-name MeterSIEMIntegrationRole

Kinesis stream throttling errors

Cause: Event rate exceeds your provisioned shard capacity. Solution:
  • Increase kinesis_shard_count (each shard adds 1 MB/sec write capacity)
  • Or switch to on-demand mode: stream_mode = "ON_DEMAND"
  • Monitor WriteProvisionedThroughputExceeded metric

High AWS costs

Cause: Over-provisioned shards or long retention periods. Solution:
  • Review your actual event volume in CloudWatch metrics
  • Reduce kinesis_shard_count if utilization is low
  • Decrease kinesis_retention_hours if you don’t need long-term buffering
  • Consider archiving to S3 with Firehose instead of extended Kinesis retention

Need help?

If you run into any issues or have questions, please reach out to our Support Engineering team by opening a ticket via the Dashboard: https://dashboard.meter.com/support Last updated by Meter Support Engineering on 09/29/2025
I