Syncing Data to AWS S3 | Twingate

Syncing Data to AWS S3

Business & Enterprise only

Note that syncing data to S3 is limited to the Business and Enterprise product plans. See our pricing page for more information.

Overview

Admins can configure Twingate to send audit logs, network events, and DNS filtering logs to their AWS S3 buckets in JSON format every 5 minutes. This data can be programmatically consumed or sent to a SIEM, providing real-time visibility for troubleshooting or investigating Admin actions within Twingate or end user connections across the network.

Payload Examples

Depending on selected event streams, the following event types will be pushed as separate objects in a single JSON file (filename format: 2025-07-25T18:20:00+00:00.json) about every 5 minutes.

network_access

For ZTNA tunneling events - refer to Network Events Schema for more info.

{
  "event_type": "network_access",
  "event": {
    "version": 1,
    "time": "2025-07-25T18:19:41.236597+00:00",
    "status": "closed_connection",
    "connection": {
      "id": "8ffefb3db7bd8446-20",
      "client_ip": "1.2.3.4",
      "protocol": "tcp",
      "bytes_received": 338,
      "bytes_transferred": 404
    },
    "connector": {
      "id": "123456",
      "name": "faithful-pigeon"
    },
    "remote_network": {
      "id": "789012",
      "name": "aws-us-east-1-dev"
    },
    "resource": {
      "address": "argocd.eks.twindemo.int",
      "applied_rule": "argocd.eks.twindemo.int",
      "id": "1234567",
      "ip": "10.11.12.13",
      "port": 80
    },
    "relays": [
      {
        "ip": "1.2.3.4",
        "name": "relaybalancer+https://relays-prm.twingate.com",
        "port": 30501
      }
    ],
    "device": {
      "id": "1234567"
    },
    "user": {
      "id": "890123",
      "email": "janedoe@twindemo.com"
    },
    "location": {
      "lat": 39.84,
      "lon": -100.04
    }
  }
}

dns_filtering

For DNS filtering events - refer to DNS Filtering Logs Schema for more info.

{
  "event_type": "dns_filtering",
  "event": {
    "version": 1,
    "time": "2025-07-25T18:23:37.774557+00:00",
    "domain": "global.help-panel.docs.aws.a2z.com",
    "root": "a2z.com",
    "device": {
      "id": "RGV2aABCDEFGHIJKL=",
      "name": "Jane's MacBook Pro",
      "model": {
        "hostname": "jd-mbpro.local",
        "local_name": "MacBook Pro (16-inch, M1 Pro, Late 2021)",
        "serial_number": "XYZABC1"
      }
    },
    "connection": {
      "client_ip": "12.34.56.78",
      "protocol": "DNS-over-HTTPS"
    },
    "status": "default",
    "reasons": []
  }
}

audit_log

For admin actions - refer to Audit Logs Schema for more info.

{
  "event_type": "audit_log",
  "event": {
    "version": 1,
    "time": "2025-07-25T18:24:05.979179+00:00",
    "action": "create",
    "targets": [
      {
        "version": "1.0",
        "type": "PublicAPIKey",
        "name": "TerraformKey",
        "id": "UHViABCDEFGHIJKLMNOP",
        "permission": "read only",
        "allowedIpRanges": ["0.0.0.0/0", "::/0"]
      }
    ],
    "actor": {
      "type": "User",
      "id": "VXNlabcdefghi=",
      "info": {
        "email": "janedoe@twindemo.com",
        "name": "Jane Doe"
      }
    }
  }
}

data_loss_prevention

For app control policies.

{
  "event_type": "data_loss_prevention",
  "event": {
    "version": 1,
    "time": "2025-07-26T00:43:25.104518+00:00",
    "action": "block_upload",
    "status": "blocked",
    "data_loss_prevention_policies": [
      {
        "id": "RGF0YUxabcd123efghijklmn456",
        "name": "DLP-AllActions-AllContent"
      }
    ],
    "device": {
      "id": "1234567",
      "name": "jd-mbpro"
    },
    "user": {
      "id": "8901234",
      "email": "janedoe@twindemo.com"
    },
    "resource": {
      "address": "chatgpt.com",
      "applied_rule": "*chatgpt.com",
      "id": "123456"
    }
  }
}

Authentication Options

Twingate offers two ways to authenticate for syncing data to AWS S3:

Option 1: OIDC IAM Role (Recommended for Most Teams)

This is the recommended approach for securely syncing data to S3. It uses short-lived, automatically rotated credentials via an IAM role that Twingate assumes through OpenID Connect (OIDC). This setup reduces risk, simplifies credential management, and aligns with AWS security best practices.

✅ We strongly recommend this method for all production environments.

Option 2: IAM User Credentials (Supported for Simpler Setups)

For smaller teams or simpler environments without an identity provider, you can also configure S3 sync using static IAM user credentials. While still supported, this method requires careful handling due to its reliance on long-lived access keys and manual rotation.

⚠️ If you use this method, ensure your access keys are tightly controlled and monitored. Consider transitioning to the OIDC method when possible.

Setup Methods

You can configure either authentication option using:

Step 1: Create or Identify Your AWS S3 Bucket

Follow the AWS S3 User Guide to create a bucket. Twingate will write logs to this bucket in JSON format.

Step 2: Configure AWS Permissions

Required IAM Policy

Ensure the IAM Role includes an inline or attached policy that grants s3:PutObject on your S3 bucket. If you’re using SSE-KMS encryption, you’ll also need permissions for kms:GenerateDataKey* and kms:Decrypt.

S3 Sync Permissions

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "TwingateS3Sync",
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::<S3BucketName>/*"
    }
  ]
}

S3 Sync Permissions with SSE-KMS Encryption

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "TwingateS3Sync",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "kms:GenerateDataKey*", "kms:Decrypt"],
      "Resource": "arn:aws:s3:::<S3BucketName>/*"
    }
  ]
}

Option 1: OIDC IAM Role (Recommended)

v2 opt-in regions

AWS opt-in regions (such as ap-southeast-3 Jakarta, ap-east-1 Hong Kong, and me-south-1 Bahrain) require V2 session tokens from the global STS endpoint. AWS accounts issue V1 tokens by default, which are not accepted in opt-in regions and will cause the sync to fail. If you’re unsure which version your account is configured for, see Sync fails in opt-in AWS regions in the troubleshooting section.

Option 2: IAM User Credentials

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowTwingateUserToPutObject",
      "Effect": "Allow",
      "Principal": { "AWS": "aws_iam_user.twingate_s3_user.arn" },
      "Action": ["s3:PutObject"],
      "Resource": "${local.bucket_arn}/*"
    }
  ]
}

Step 3: Configure your AWS S3 sync in the Twingate Admin Console

Option Required Info
OIDC Role Bucket name & IAM Role ARN
IAM Credentials Bucket name, Access Key & Secret

💡 Do not include the arn:aws:s3::: prefix when entering the bucket name.

(Optional) Configure Using Terraform

If you’re managing infrastructure with Terraform, below are example configurations or you can take a look at our Terraform Examples.

Terraform Compatibility

These examples were last tested with:

Be sure to validate these versions with your environment or pin them using the required_providers block in your Terraform configuration.

OIDC Example

tf {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0.0"
    }
  }
  required_version = ">= 1.12.2"
}

# The Twingate network slug, used for OIDC URL and audience
variable "tenant_slug" {
  type        = string
  description = "Twingate network slug (e.g., acme)"
  default     = "<tenant>"
}

# Name of the S3 bucket to receive logs
variable "bucket_name" {
  type        = string
  description = "S3 bucket name for Twingate sync"
  default     = "<bucket-name>"
}

# Defining reusable values
locals {
  oidc_url    = "https://${var.tenant_slug}.twingate.com/oidc"
  oidc_prefix = "${var.tenant_slug}.twingate.com/oidc"
  bucket_arn  = "arn:aws:s3:::${var.bucket_name}"
}

# OIDC provider: allows AWS to validate tokens issued by Twingate
resource "aws_iam_openid_connect_provider" "twingate" {
  url            = local.oidc_url
  client_id_list = [var.tenant_slug]
}

# IAM policy assigned to the role: grants Twingate permission to upload logs
resource "aws_iam_policy" "twingate_s3_sync" {
  name        = "${var.tenant_slug}-twingate-s3-sync-2"
  description = "Allows Twingate to upload logs to S3"

policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "TwingateS3Sync"
        Effect = "Allow"
        Action = "s3:PutObject"
        Resource = [
          "${local.bucket_arn}/*"
        ]
      }
    ]
  })
}

# IAM role assumed by Twingate via OIDC for secure log sync
resource "aws_iam_role" "twingate_s3_sync" {
  name = "${var.tenant_slug}-twingate-s3-sync-role"

assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Federated = aws_iam_openid_connect_provider.twingate.arn }
      Action    = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringEquals = {
          "${local.oidc_prefix}:aud" = var.tenant_slug
          "${local.oidc_prefix}:sub" = "events_sync"
        }
      }
    }]
  })
}

# Attach above policy to the IAM role
resource "aws_iam_role_policy_attachment" "twingate_attach" {
  role       = aws_iam_role.twingate_s3_sync.name
  policy_arn = aws_iam_policy.twingate_s3_sync.arn
}

# Create or manage the S3 bucket for log storage
resource "aws_s3_bucket" "logs" {
  bucket = var.bucket_name
}

# Bucket policy: grants only the sync role permission to put objects
resource "aws_s3_bucket_policy" "logs" {
  bucket = aws_s3_bucket.logs.id

policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Sid    = "TwingateS3Sync"
      Effect = "Allow"
      Principal = {
        AWS = aws_iam_role.twingate_s3_sync.arn
      }
      Action = ["s3:PutObject"]
      Resource = [
        "${local.bucket_arn}/*"
      ]
    }]
  })
}

# Output to grab the aws_iam_role arn => to be used in Twingate
output "twingate_s3_sync_role_arn" {
  description = "IAM Role ARN that Twingate uses to upload logs"
  value       = aws_iam_role.twingate_s3_sync.arn
}

IAM User Example

tf {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0.0"
    }
  }
  required_version = ">= 1.12.2"
}

# The Twingate network slug (used for tagging and naming)
variable "tenant_slug" {
  type        = string
  description = "Twingate network slug (e.g., acme)"
  default     = "twindemogb"
}

# Name of the S3 bucket to receive logs
variable "bucket_name" {
  type        = string
  description = "S3 bucket name for Twingate sync"
  default     = "test-twindemogb-logs"
}

# Defining reusable values
locals {
  bucket_arn = "arn:aws:s3:::${var.bucket_name}"
}

# IAM user for static access
resource "aws_iam_user" "twingate_s3_user" {
  name = "${var.tenant_slug}-s3-sync-user"
}

# IAM policy allowing S3 PutObject
resource "aws_iam_policy" "twingate_s3_policy" {
  name        = "${var.tenant_slug}-s3-sync-policy"
  description = "Allows PutObject to Twingate log bucket"

policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = ["s3:PutObject"]
        Resource = [
          local.bucket_arn,
          "${local.bucket_arn}/*"
        ]
      }
    ]
  })
}

# Attach the policy to the user
resource "aws_iam_user_policy_attachment" "twingate_user_attach" {
  user       = aws_iam_user.twingate_s3_user.name
  policy_arn = aws_iam_policy.twingate_s3_policy.arn
}

# Create access keys for the user
resource "aws_iam_access_key" "twingate_s3_access_key" {
  user = aws_iam_user.twingate_s3_user.name
}

# Create or manage the S3 bucket
resource "aws_s3_bucket" "logs" {
  bucket = var.bucket_name
}

# Optional: S3 bucket policy (if you want to restrict writes to this IAM user specifically)
resource "aws_s3_bucket_policy" "logs" {
  bucket = aws_s3_bucket.logs.id

policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "AllowTwingateUserToPutObject",
        Effect    = "Allow",
        Principal = { AWS = aws_iam_user.twingate_s3_user.arn },
        Action    = ["s3:PutObject"],
        Resource  = "${local.bucket_arn}/*"
      }
    ]
  })
}

# Outputs for Twingate Admin Console
output "access_key_id" {
  value       = aws_iam_access_key.twingate_s3_access_key.id
  description = "Access Key ID for Twingate S3 sync"
}

output "secret_access_key" {
  value       = aws_iam_access_key.twingate_s3_access_key.secret
  description = "Secret Access Key for Twingate S3 sync"
  sensitive   = true
}

output "bucket_name" {
  value       = var.bucket_name
  description = "S3 bucket name (no ARN prefix)"
}

Troubleshooting

Why is my S3 sync failing?

If you’ve just configured your S3 sync and it immediately fails, you could be running into a configuration issue. Please check that your bucket name, access key, secret access key, and AWS user policies are correct. The AWS user must have a policy granting s3:PutObject access to the target bucket.

Sync fails in opt-in AWS regions (OIDC only)

AWS opt-in regions require V2 session tokens. By default, the AWS global STS endpoint issues V1 tokens, which are not valid in opt-in regions such as ap-southeast-3 (Jakarta), ap-east-1 (Hong Kong), and me-south-1 (Bahrain).

To resolve this, update your AWS account to issue V2 tokens from the global STS endpoint:

What happens if there are no events to sync?

In the case that there are no events to sync, Twingate will not send files to the S3 bucket. You can confirm that the sync works in the Admin Console by navigating to Settings, then Reports. The S3 sync status will be displayed on the upper right corner as seen below.

I just performed an action that should be reflected in my data. Why am I not seeing it?

Events can take up to 10 minutes to be reflected in the sync.

What permissions does my AWS Admin user need to do these actions (Console, API, Terraform)?

Admin IAM User Permissions

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AdditionalAllow",
      "Effect": "Allow",
      "Action": [
        "iam:CreateRole",
        "iam:AttachRolePolicy",
        "iam:DeleteRole",
        "iam:DetachRolePolicy",
        "iam:PassRole",
        "iam:CreateOpenIDConnectProvider",
        "iam:DeleteOpenIDConnectProvider",
        "iam:CreatePolicy",
        "iam:DeletePolicy",
        "iam:CreatePolicyVersion",
        "iam:DeletePolicyVersion",
        "S3:PutObject",
        "iam:CreateUser",
        "iam:DeleteUser",
        "iam:UpdateUser",
        "iam:AttachUserPolicy",
        "iam:DetachUserPolicy",
        "iam:CreateAccessKey",
        "iam:DeleteAccessKey",
        "iam:UpdateAccessKey",
        "iam:ListAccessKeys"
      ],
      "Resource": ["*"]
    }
  ]
}