← Back to DevBytes

Neptune Security: IAM Policies and Network Security

Understanding Amazon Neptune Security

Amazon Neptune is a fully managed graph database service that supports both Property Graph (Gremlin, openCypher) and RDF/SPARQL query languages. Securing your Neptune deployment requires a layered approach that combines AWS Identity and Access Management (IAM) policies for authentication and authorization with network-level controls such as VPC placement, security groups, and encryption. This tutorial walks you through both dimensions in detail, providing practical code examples and best practices you can apply immediately.

What Are Neptune IAM Policies?

IAM policies in Neptune govern who can access the database and what actions they can perform. Unlike traditional databases that rely on username/password combinations stored inside the engine, Neptune integrates with AWS IAM for authentication. Every request to the Neptune data plane must be signed with AWS Signature Version 4 using valid IAM credentials. This means your applications, Lambda functions, and EC2 instances all use IAM roles rather than embedded credentials.

An IAM policy for Neptune typically consists of:

IAM Policy Structure for Neptune Data Plane Access

Neptune separates its API operations into two planes. The control plane includes management operations like CreateDBCluster, DeleteDBInstance, and ModifyDBClusterParameterGroup. The data plane covers the actual query endpoints: connect, read, write, delete, and manage actions that map to Gremlin, SPARQL, or openCypher operations.

Here is a complete IAM policy that grants read-only access to a specific Neptune cluster's data plane:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "neptune-db:Read",
        "neptune-db:GetQueryStatus",
        "neptune-db:GetQueryResults",
        "neptune-db:GetEngineStatus"
      ],
      "Resource": [
        "arn:aws:neptune-db:us-east-1:123456789012:cluster-abcdefgh/*",
        "arn:aws:neptune-db:us-east-1:123456789012:cluster-abcdefgh"
      ]
    }
  ]
}

Note the use of neptune-db: prefix for data plane actions versus neptune: for control plane. The resource ARN includes both the cluster identifier and a trailing /* wildcard to cover all query-specific sub-resources like /sparql, /gremlin, or /openCypher endpoints.

Granular Query-Specific IAM Policies

You can restrict access even further by targeting specific query languages or query types. Neptune exposes distinct resource paths for each query engine:

Below is an IAM policy that allows write operations exclusively via the Gremlin endpoint, while permitting read-only access through SPARQL:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "neptune-db:Read",
        "neptune-db:Write",
        "neptune-db:Delete",
        "neptune-db:Manage"
      ],
      "Resource": "arn:aws:neptune-db:us-east-1:123456789012:cluster-mycluster/gremlin"
    },
    {
      "Effect": "Allow",
      "Action": [
        "neptune-db:Read"
      ],
      "Resource": "arn:aws:neptune-db:us-east-1:123456789012:cluster-mycluster/sparql"
    },
    {
      "Effect": "Allow",
      "Action": [
        "neptune-db:GetQueryStatus",
        "neptune-db:GetQueryResults",
        "neptune-db:GetEngineStatus"
      ],
      "Resource": "arn:aws:neptune-db:us-east-1:123456789012:cluster-mycluster/*"
    }
  ]
}

This pattern lets you enforce the principle of least privilege at the query engine level. A developer working on a Gremlin-based recommendation service gets full CRUD on the Gremlin endpoint, while an analytics team running SPARQL queries is limited to reads only.

Control Plane IAM Policies

Control plane policies govern administrative operations on Neptune resources. A typical DevOps engineer needs permissions to create snapshots, modify parameter groups, and monitor the cluster without being able to delete it. Here is a policy that balances operational access with safety:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "neptune:DescribeDBClusters",
        "neptune:DescribeDBInstances",
        "neptune:DescribeDBSubnetGroups",
        "neptune:DescribeDBParameterGroups",
        "neptune:DescribeDBParameters",
        "neptune:DescribeEventCategories",
        "neptune:DescribeEvents",
        "neptune:ListTagsForResource",
        "neptune:CreateDBSnapshot",
        "neptune:CopyDBSnapshot",
        "neptune:ModifyDBInstance",
        "neptune:ModifyDBParameterGroup",
        "neptune:RebootDBInstance"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "Action": [
        "neptune:DeleteDBCluster",
        "neptune:DeleteDBInstance",
        "neptune:DeleteDBSnapshot"
      ],
      "Resource": "*"
    }
  ]
}

The explicit Deny clause for destructive actions overrides any broader Allow statements that might exist in other attached policies, providing a strong safety net against accidental deletions.

Condition-Based Access Control

IAM condition keys add context-aware restrictions. For Neptune, commonly used conditions include aws:SourceIp for IP-based restrictions, aws:SourceVpc or aws:SourceVpce for VPC endpoint enforcement, and aws:RequestedRegion for regional lock-in.

Here is an example that restricts Neptune data plane access to requests originating from within a specific VPC endpoint and a specific CIDR range:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "neptune-db:Read",
        "neptune-db:Write",
        "neptune-db:Delete",
        "neptune-db:GetQueryStatus",
        "neptune-db:GetQueryResults"
      ],
      "Resource": "arn:aws:neptune-db:us-east-1:123456789012:cluster-prod-cluster/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceVpce": "vpce-0a1b2c3d4e5f67890"
        },
        "IpAddress": {
          "aws:SourceIp": [
            "10.0.0.0/8",
            "172.16.0.0/12"
          ]
        },
        "Bool": {
          "aws:SecureTransport": "true"
        }
      }
    }
  ]
}

The aws:SecureTransport condition ensures all requests are made over TLS, preventing accidental plaintext communication with the database endpoint.

Network Security for Neptune

VPC-Only Deployment Model

Neptune operates exclusively within a Virtual Private Cloud (VPC). You cannot create a Neptune cluster that listens on the public internet. This architectural constraint is a fundamental security control — it forces you to place Neptune inside private subnets and route all traffic through your defined network paths. When you launch a Neptune cluster, you specify:

Security Groups: The Primary Firewall

Neptune security groups act as stateful firewalls. You define inbound rules that specify allowed protocols, port ranges, and source traffic. Neptune's default port for database connections is 8182 for Gremlin and 8182 for SPARQL (the same port handles both via content negotiation). Outbound rules are typically left open since Neptune does not initiate connections to external resources.

Below is a CloudFormation snippet that creates a security group for Neptune with strict ingress rules:

{
  "Resources": {
    "NeptuneSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Security group for Neptune cluster - restricts inbound to application tier",
        "VpcId": "vpc-0abc123def456789",
        "SecurityGroupIngress": [
          {
            "Description": "Allow Gremlin/SPARQL from application servers only",
            "FromPort": 8182,
            "ToPort": 8182,
            "IpProtocol": "tcp",
            "SourceSecurityGroupId": "sg-appserver-xyz789"
          },
          {
            "Description": "Allow HTTPS for Neptune HTTPS endpoint from load balancer security group",
            "FromPort": 8183,
            "ToPort": 8183,
            "IpProtocol": "tcp",
            "SourceSecurityGroupId": "sg-loadbalancer-abc123"
          }
        ],
        "SecurityGroupEgress": [
          {
            "Description": "Allow all outbound for VPC internal communication",
            "FromPort": 0,
            "ToPort": 65535,
            "IpProtocol": "tcp",
            "CidrIp": "10.0.0.0/8"
          }
        ],
        "Tags": [
          {
            "Key": "Name",
            "Value": "neptune-production-sg"
          },
          {
            "Key": "Environment",
            "Value": "production"
          }
        ]
      }
    }
  }
}

Key security principles demonstrated here: source restriction by security group reference rather than raw CIDR blocks, separate rules for different protocols (8182 for WebSocket-based Gremlin, 8183 for HTTPS), and explicit tagging for audit trail purposes.

Subnet Architecture and Network Isolation

A robust Neptune network architecture uses at least two tiers of subnets:

For organizations requiring strict isolation, Neptune supports deployment in private subnets with no NAT gateway, relying entirely on VPC endpoints for any AWS service communication (like writing logs to CloudWatch or pulling from S3 for bulk loading).

VPC Endpoints for Neptune

While Neptune itself lives in your VPC, you can create an AWS PrivateLink endpoint (interface VPC endpoint) for Neptune in a different VPC to allow cross-VPC or cross-account access without exposing traffic to the internet. The endpoint appears as an elastic network interface in the consumer VPC's subnet.

Here is the Terraform configuration for a Neptune VPC endpoint:

resource "aws_vpc_endpoint" "neptune_endpoint" {
  vpc_id              = "vpc-consumer-0123456789"
  service_name        = "com.amazonaws.us-east-1.neptune-db"
  vpc_endpoint_type   = "Interface"
  private_dns_enabled = true
  subnet_ids          = ["subnet-consumer-a", "subnet-consumer-b"]
  
  security_group_ids = [
    aws_security_group.neptune_endpoint_sg.id
  ]

  tags = {
    Name        = "neptune-cross-account-endpoint"
    Environment = "production"
  }
}

resource "aws_security_group" "neptune_endpoint_sg" {
  name        = "neptune-endpoint-sg"
  description = "Controls traffic to Neptune via PrivateLink endpoint"
  vpc_id      = "vpc-consumer-0123456789"

  ingress {
    description = "Allow from application security group"
    from_port   = 8182
    to_port     = 8182
    protocol    = "tcp"
    security_groups = ["sg-app-consumer-xyz"]
  }
}

The private_dns_enabled = true flag is critical — it allows consumer VPC resources to resolve the Neptune cluster's DNS name to the PrivateLink endpoint IP addresses automatically, requiring no code changes in your application.

Encryption in Transit and at Rest

Enforcing TLS Connections

Neptune supports TLS 1.2 and TLS 1.3 for all client connections. You can enforce TLS at two levels:

  1. IAM policy level – Using the aws:SecureTransport condition key as shown earlier
  2. Application connection level – Configuring your Neptune driver or client library to require TLS

Here is a Python example using the Gremlin Python client with mandatory TLS:

from gremlin_python.driver import client, serializer
import ssl
import os

# Neptune cluster endpoint and port
NEPTUNE_ENDPOINT = "wss://your-cluster.neptune.amazonaws.com:8182/gremlin"

# Create a custom SSL context that requires TLS 1.2+
ssl_context = ssl.create_default_context()
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
ssl_context.check_hostname = True

# Initialize the Gremlin client with TLS enforcement
gremlin_client = client.Client(
    NEPTUNE_ENDPOINT,
    'g',
    message_serializer=serializer.GraphSONSerializersV3d0(),
    ssl_context=ssl_context,
    headers={
        'Host': 'your-cluster.neptune.amazonaws.com'
    }
)

# All subsequent queries are now TLS-encrypted
try:
    result = gremlin_client.submit("g.V().limit(1)")
    print("Secure connection established and query executed successfully.")
finally:
    gremlin_client.close()

Encryption at Rest with KMS

Neptune encrypts data at rest using AWS Key Management Service (KMS) keys. You specify the KMS key during cluster creation. Once enabled, encryption at rest covers the cluster volume, snapshots, and replicas. The encryption is transparent to your application — Neptune handles decryption on read operations automatically.

CloudFormation snippet for enabling encryption at rest during cluster creation:

{
  "Resources": {
    "NeptuneDBCluster": {
      "Type": "AWS::Neptune::DBCluster",
      "Properties": {
        "DBClusterIdentifier": "encrypted-neptune-cluster",
        "StorageEncrypted": true,
        "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/abcd-1234-efgh-5678",
        "DBSubnetGroupName": { "Ref": "NeptuneSubnetGroup" },
        "VpcSecurityGroupIds": [{ "Ref": "NeptuneSecurityGroup" }],
        "EnableIAMDatabaseAuthentication": true,
        "CopyTagsToSnapshot": true
      }
    }
  }
}

The EnableIAMDatabaseAuthentication property, when set to true, requires all data plane connections to use IAM authentication rather than the built-in Neptune user/password mechanism.

IAM Database Authentication in Practice

How IAM Auth Works for Neptune Connections

When IAM database authentication is enabled, your application must generate an AWS Signature Version 4 signed HTTP request or WebSocket upgrade request. Neptune validates the signature against IAM before allowing the connection. This eliminates static credentials and ties every database session to an IAM principal that can be rotated, revoked, or assumed temporarily.

Here is a complete Node.js example demonstrating how to connect to Neptune using IAM authentication and the Gremlin WebSocket endpoint:

const AWS = require('aws-sdk');
const WebSocket = require('ws');
const gremlin = require('gremlin');

// Neptune configuration
const NEPTUNE_ENDPOINT = 'your-cluster.neptune.amazonaws.com';
const NEPTUNE_PORT = 8182;
const REGION = 'us-east-1';

async function createIamSignedWebSocketConnection() {
  // Step 1: Get credentials from the environment or IAM role
  const credentials = new AWS.EnvironmentCredentials('AWS');
  
  // Step 2: Construct the request details for Signature V4 signing
  const requestParams = {
    host: `${NEPTUNE_ENDPOINT}:${NEPTUNE_PORT}`,
    path: '/gremlin',
    service: 'neptune-db',
    region: REGION,
    method: 'GET',
    headers: {
      'host': `${NEPTUNE_ENDPOINT}:${NEPTUNE_PORT}`,
      'Content-Type': 'application/json'
    },
    body: ''
  };

  // Step 3: Sign the request using Signature V4
  const signer = new AWS.Signers.V4(requestParams, 'neptune-db', {
    defaultExpires: 300 // 5 minutes
  });
  
  const signedRequest = signer.addAuthorization(
    credentials,
    new Date()
  );

  // Step 4: Extract the signed URL with query parameters
  const signedUrl = `wss://${NEPTUNE_ENDPOINT}:${NEPTUNE_PORT}/gremlin?` +
    Object.keys(signedRequest.query)
      .map(key => `${key}=${encodeURIComponent(signedRequest.query[key])}`)
      .join('&');

  // Step 5: Establish the WebSocket connection
  const ws = new WebSocket(signedUrl, {
    headers: {
      'Host': NEPTUNE_ENDPOINT
    }
  });

  return new Promise((resolve, reject) => {
    ws.on('open', () => {
      console.log('IAM-authenticated WebSocket connection established');
      
      // Create Gremlin client over the authenticated WebSocket
      const client = new gremlin.driver.Client(
        signedUrl,
        gremlin.driver.processor.GraphTraversalSource
      );
      resolve(client);
    });
    
    ws.on('error', (error) => {
      console.error('WebSocket connection failed:', error);
      reject(error);
    });
  });
}

// Usage example
(async () => {
  try {
    const client = await createIamSignedWebSocketConnection();
    const result = await client.submit('g.V().hasLabel("user").valueMap()');
    console.log('Query result:', result);
    client.close();
  } catch (err) {
    console.error('IAM auth connection failed:', err);
  }
})();

This approach is far more secure than embedding database credentials in configuration files. The credentials used for signing come from the EC2 instance role, ECS task role, or Lambda execution role — all of which AWS rotates automatically and can be scoped tightly.

Monitoring and Auditing Neptune Security

CloudTrail Integration

All Neptune control plane API calls are logged to AWS CloudTrail. Data plane events, however, are not automatically logged to CloudTrail. To capture data plane activity, you need to enable Neptune audit logs via the cluster parameter group. The relevant parameter is neptune_enable_audit_log — set it to 1 to capture all queries, or 2 for a sampled subset.

Here is the AWS CLI command to modify the parameter group and enable full audit logging:

aws neptune modify-db-cluster-parameter-group \
    --db-cluster-parameter-group-name my-neptune-params \
    --parameters ParameterName=neptune_enable_audit_log,ParameterValue=1,ApplyMethod=immediate \
    --region us-east-1

Audit logs are written to CloudWatch Logs. You can then create metric filters and alarms for suspicious patterns like excessive failed authentication attempts, unusual query volumes, or access from unexpected IP ranges (by correlating with VPC Flow Logs).

VPC Flow Logs for Network Visibility

Enabling VPC Flow Logs on the subnets hosting Neptune gives you detailed metadata about every IP flow. This is invaluable for detecting unauthorized access attempts, verifying that only approved security groups are communicating with Neptune, and troubleshooting connectivity issues.

CloudFormation snippet for enabling flow logs on Neptune subnets:

{
  "Resources": {
    "NeptuneFlowLog": {
      "Type": "AWS::EC2::FlowLog",
      "Properties": {
        "ResourceId": "subnet-neptune-private-a",
        "ResourceType": "Subnet",
        "TrafficType": "ALL",
        "LogDestinationType": "cloud-watch-logs",
        "LogGroupName": "/vpc/flow-logs/neptune-subnets",
        "DeliverLogsPermissionArn": "arn:aws:iam::123456789012:role/FlowLogsToCloudWatchRole"
      }
    }
  }
}

Combine flow logs with Neptune audit logs to build a complete picture: flow logs tell you which IPs and ports connected to Neptune, while audit logs reveal what queries those connections executed.

Best Practices for Neptune IAM and Network Security

1. Enable IAM Database Authentication from Day One

Never use Neptune's built-in username/password mechanism in production. Enable EnableIAMDatabaseAuthentication at cluster creation. This forces all connections through IAM, eliminating static credential management and enabling credential rotation via IAM role assumption.

2. Apply the Principle of Least Privilege Relentlessly

Create separate IAM roles for different workloads: a read-only role for analytics dashboards, a read-write role for ingestion services, and a restricted role for developers running ad-hoc queries. Use query-endpoint-specific resource paths (/gremlin, /sparql, /openCypher) in your IAM policies to limit each role to exactly the query language it needs.

3. Layer Security Group Rules

Rather than opening Neptune to broad CIDR ranges, reference security group IDs in your inbound rules. This creates a logical boundary where only resources in specific application security groups can connect. For cross-VPC access, use PrivateLink endpoints instead of peering with open security groups.

4. Encrypt Everything, Everywhere

Enable encryption at rest with a customer-managed KMS key (not the default AWS-managed key) so you retain control over key rotation policies. Enforce TLS in transit both at the IAM condition level (aws:SecureTransport) and in your client configuration. Never allow plaintext Neptune connections.

5. Use Temporary Credentials Exclusively

For applications running on EC2, ECS, or Lambda, rely on instance roles, task roles, and execution roles — never hard-code long-lived IAM access keys. For local development, use aws sso login or aws sts assume-role to generate temporary credentials that expire within hours, not years.

6. Implement Network Segmentation

Place Neptune in dedicated private subnets that are separate from your application subnets. Use distinct route tables: Neptune subnets should have routes only to the VPC's internal CIDR and to VPC endpoints for S3/CloudWatch (if needed). Remove any route to a NAT gateway or internet gateway from Neptune's subnets.

7. Audit Proactively, Not Retroactively

Enable Neptune audit logging and VPC Flow Logs from the moment the cluster is created. Set up CloudWatch alarms for anomalies: connection attempts from unknown IPs, spikes in failed IAM auth signatures, or queries that attempt to traverse sensitive vertex labels. Treat security monitoring as a continuous process, not a periodic review.

8. Rotate KMS Keys and Review Policies Regularly

Schedule quarterly reviews of IAM policies attached to Neptune-access roles. Use IAM Access Analyzer to identify policies that grant overly broad permissions. Rotate your KMS customer-managed keys annually (or more frequently for high-compliance environments) and verify that old snapshots encrypted with previous keys are either re-encrypted or securely deleted.

9. Test Security Controls with Chaos Engineering

Periodically simulate scenarios where a compromised IAM role attempts to call neptune-db:Delete on the production cluster. Verify that your Deny policies and security group configurations block the action. Test PrivateLink endpoint connectivity during failover events to ensure cross-VPC consumers can still reach Neptune through the endpoint when the primary instance changes.

10. Document Your Security Model as Code

Define all Neptune security controls — IAM policies, security groups, KMS keys, subnet configurations, and audit log settings — in infrastructure-as-code templates (CloudFormation, Terraform, or CDK). Store these in version control. This ensures reproducibility, allows peer review of security changes through pull requests, and provides an audit trail of who changed what security setting and when.

Complete Neptune Security Terraform Module Example

Below is a consolidated Terraform module that implements the security patterns discussed throughout this tutorial. It provisions a Neptune cluster with IAM authentication, encryption at rest, strict security groups, private subnets, and audit logging — all in one deployable unit:

# neptune-security-module/main.tf

variable "vpc_id" {
  description = "VPC ID where Neptune will be deployed"
  type        = string
}

variable "private_subnet_ids" {
  description = "List of private subnet IDs for Neptune placement"
  type        = list(string)
}

variable "app_security_group_id" {
  description = "Security group ID of the application tier that connects to Neptune"
  type        = string
}

variable "kms_key_arn" {
  description = "ARN of customer-managed KMS key for encryption at rest"
  type        = string
}

# --- Security Group ---
resource "aws_security_group" "neptune" {
  name        = "neptune-cluster-sg"
  description = "Neptune cluster security group - ingress only from app tier"
  vpc_id      = var.vpc_id

  ingress {
    description     = "Gremlin/SPARQL WebSocket from application tier"
    from_port       = 8182
    to_port         = 8182
    protocol        = "tcp"
    security_groups = [var.app_security_group_id]
  }

  ingress {
    description     = "Neptune HTTPS endpoint from application tier"
    from_port       = 8183
    to_port         = 8183
    protocol        = "tcp"
    security_groups = [var.app_security_group_id]
  }

  egress {
    description = "Allow outbound to VPC CIDR for internal service communication"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["10.0.0.0/8"]
  }

  tags = {
    Name        = "neptune-production-sg"
    Environment = "production"
    ManagedBy   = "terraform"
  }

  lifecycle {
    create_before_destroy = true
  }
}

# --- Subnet Group ---
resource "aws_neptune_subnet_group" "neptune" {
  name        = "neptune-private-subnet-group"
  subnet_ids  = var.private_subnet_ids
  description = "Subnet group for Neptune in private subnets"

  tags = {
    Name        = "neptune-subnet-group"
    Environment = "production"
  }
}

# --- Cluster Parameter Group with Audit Logging ---
resource "aws_neptune_cluster_parameter_group" "neptune" {
  name        = "neptune-audit-enabled-params"
  family      = "neptune1.3"
  description = "Parameter group enabling audit logs and IAM auth enforcement"

  parameter {
    name         = "neptune_enable_audit_log"
    value        = "1"
    apply_method = "pending-reboot"
  }

  parameter {
    name         = "neptune_enforce_user_authentication"
    value        = "IAM"
    apply_method = "pending-reboot"
  }

  tags = {
    Environment = "production"
  }
}

# --- Neptune Cluster ---
resource "aws_neptune_cluster" "neptune" {
  cluster_identifier                  = "secure-neptune-cluster"
  engine                              = "neptune"
  engine_version                      = "1.3.2"
  db_subnet_group_name                = aws_neptune_subnet_group.neptune.name
  vpc_security_group_ids              = [aws_security_group.neptune.id]
  neptune_cluster_parameter_group_name = aws_neptune_cluster_parameter_group.neptune.name
  
  storage_encrypted                   = true
  kms_key_arn                         = var.kms_key_arn
  
  enable_iam_database_authentication  = true
  copy_tags_to_snapshot               = true
  backup_retention_period             = 7
  preferred_backup_window             = "03:00-04:00"
  preferred_maintenance_window        = "sun:04:00-sun:05:00"

  tags = {
    Name        = "secure-neptune-cluster"
    Environment = "production"
  }
}

# --- Neptune Instance ---
resource "aws_neptune_cluster_instance" "neptune" {
  count                  = 1
  cluster_identifier     = aws_neptune_cluster.neptune.id
  instance_class         = "db.r6g.large"
  neptune_subnet_group_name = aws_neptune_subnet_group.neptune.name
  publicly_accessible    = false
  apply_immediately      = true

  tags = {
    Name        = "neptune-instance-1"
    Environment = "production"
  }
}

# --- IAM Policy for Application Access ---
resource "aws_iam_policy" "neptune_app_access" {
  name        = "NeptuneApplicationDataAccess"
  description = "Grants application tier read/write access to Neptune data plane via IAM auth"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "neptune-db:Read",
          "neptune-db:Write",
          "neptune-db:Delete",
          "neptune-db:GetQueryStatus",
          "neptune-db:GetQueryResults",
          "neptune-db:GetEngineStatus"
        ]
        Resource = [
          "${aws_neptune_cluster.neptune.arn}/*",
          aws_neptune_cluster.neptune.arn
        ]
        Condition = {
          Bool = {
            "aws:SecureTransport" = "true"
          }
          StringEquals = {
            "aws:SourceVpc" = var.vpc_id
          }
        }
      }
    ]
  })
}

# --- VPC Flow Logs for Neptune Subnets ---
resource "aws_flow_log" "neptune_subnets" {
  for_each             = toset(var.private_subnet_ids)
  
  log_destination_type = "cloud-watch-logs"
  log_group_name       = "/vpc/flow-logs/neptune"
  traffic_type         = "ALL"
  resource_id          = each.value
  resource_type        = "Subnet"

  depends_on = [aws_neptune_subnet_group.neptune]
}

# --- Outputs ---
output "neptune_endpoint" {
  value = aws_neptune_cluster.neptune.endpoint
}

output "neptune_read_endpoint" {
  value = aws_neptune_cluster.neptune.reader_endpoint
}

output "iam_policy_arn" {
  value = aws_iam_policy.neptune_app_access.arn
}

This module encapsulates every security layer: network isolation via private subnets and strict security groups, encryption at rest with a customer-managed KMS key, IAM authentication enforcement, audit logging, and condition-based IAM policies that require TLS and VPC origin. You can instantiate it once per environment (dev, staging, prod) with environment-specific variables.

Conclusion

Securing Amazon Neptune requires a deliberate, multi-layered approach that treats IAM policies and network controls as complementary defenses rather than alternatives. IAM policies provide fine-grained, identity-based access control that governs who can connect and what queries they can execute — down to the query language and operation type. Network security through VPC placement, security groups, and PrivateLink endpoints ensures that how traffic reaches Neptune is strictly controlled, never traversing the public internet. Encryption at rest and in transit completes the picture by protecting data regardless of access path. By implementing the patterns and practices outlined in this tutorial — enabling IAM database authentication, layering security group references, enforcing TLS conditions in IAM policies, enabling audit logging from day one, and codifying all security controls as infrastructure-as-code — you build a Neptune deployment that is resilient against both

— Ad —

Google AdSense will appear here after approval

← Back to all articles