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
    }
  }
}

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.

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