AWS PrivateLink
This guide walks you through setting up AWS PrivateLink to allow Infracost to securely connect to your private GitHub Enterprise Server or GitLab instance.
Email hello@infracost.io if you would like to test this integration.
Architecture Overview
AWS PrivateLink creates a private, one-way network tunnel from Infracost's AWS account into your VPC, so that Infracost can reach your VCS without any traffic crossing the public internet.
How it works:
- Infracost Runner initiates API calls (repo process, PR comments) through a VPC Endpoint in the Infracost AWS account.
- The VPC Endpoint connects over the AWS backbone to the VPC Endpoint Service in your account — no traffic traverses the public internet.
- The Network Load Balancer forwards traffic to your GitHub Enterprise or GitLab instance.
- Your VCS sends webhook events (push, pull request) back to Infracost — either over the public internet (Hybrid mode) or through a second PrivateLink tunnel (Fully Private mode).
See Data Flow Overview below for detailed diagrams of each connectivity option.
Prerequisites
- Your VCS (GitHub Enterprise or GitLab) is running in an AWS VPC
- You have the ability to create AWS resources (NLB, VPC Endpoint Service) in your account
Step 1: Gather Your Environment Details
Before starting, collect the following information from your AWS environment:
| Information Needed | Example |
|---|---|
| VPC ID where your VCS runs | vpc-0abc123def456 |
| Subnet IDs in that VPC (at least one) | subnet-111, subnet-222, subnet-333 |
| EC2 Instance ID of your VCS | i-0abc123def456 |
| Security Group IDs for the NLB | sg-0abc123 |
| Your AWS region | us-east-1 |
| Internal URL used to reach your VCS | https://github.mycompany.internal |
Step 2: Note Infracost's Connection Details
Infracost will connect from:
- AWS Account ID:
237144093413 - Region:
us-east-2
Important: If your VCS is in a region other than
us-east-2, you'll be using cross-region PrivateLink. Your VPC Endpoint Service configuration must includeus-east-2in thesupported_regionssetting.
Step 3: Deploy the VPC Endpoint Service Infrastructure
Using either the resource list in Appendix A or the Terraform configuration in Appendix B, deploy the necessary resources to your account to become a PrivateLink endpoint provider for Infracost.
Step 4: Provide Information to Infracost
After deploying the infrastructure, send Infracost the following:
| Information | How to Find It |
|---|---|
| Your AWS region | e.g., us-east-1 |
| VPC Endpoint Service name | Find in AWS Console or Terraform output: com.amazonaws.vpce.REGION.vpce-svc-XXXX |
| Internal VCS URL | The URL your teams use to access the VCS |
| TLS certificate rotation schedule | Only needed if you use self-signed certificates (not needed for AWS ACM certificates) |
Step 5: Confirm Connectivity
Once Infracost confirms the connection is configured on their side, they will verify connectivity to your VCS through the PrivateLink tunnel.
Data Flow Overview
You can choose between two connectivity scenarios based on your security and network requirements:
Option A: Hybrid Connectivity (Public Internet for Webhooks)
This is the simpler option where outbound webhooks use the public internet while incoming API calls to your VCS use PrivateLink.
Data Flow Summary:
1. WEBHOOKS (VCS → Infracost)
- Triggered by: Push events, Merge/Pull Requests
- Path: Public Internet (HTTPS)
- Direction: Your VCS → Infracost webhook endpoint
2. VCS API CALLS (Infracost → VCS)
- Purpose: process repos, fetch configs, post PR comments
- Path: AWS PrivateLink (private, no public internet)
- Direction: Infracost → VPC Endpoint → Your VPC Endpoint Service → VCS
Requirements:
- Your VCS must have outbound internet access to reach Infracost's webhook endpoint
- Simpler configuration with no additional DNS setup required
Option B: Fully Private Connectivity (PrivateLink for Everything)
This option keeps all traffic on PrivateLink but requires bi-directional routing configuration.
Data Flow Summary:
1. WEBHOOKS (VCS → Infracost)
- Triggered by: Push events, Merge/Pull Requests
- Path: AWS PrivateLink (fully private)
- Direction: Your VCS → Your VPC Endpoint → Infracost VPC Endpoint Service → Infracost webhook endpoint
2. VCS API CALLS (Infracost → VCS)
- Purpose: Process repos, fetch configs, post PR comments
- Path: AWS PrivateLink (fully private)
- Direction: Infracost → VPC Endpoint → Your VPC Endpoint Service → VCS
Requirements:
- Bi-directional PrivateLink setup: You must also create a VPC Endpoint in your account that connects to an Infracost-provided VPC Endpoint Service
- DNS Configuration: The Infracost webhook endpoint configured in your VCS must be set to that of the VPC Endpoint you create, which is known after creating it (as opposed to using the default, public-internet hostname for Infracost webhooks)
When to Choose This Option:
- Your VCS has no outbound internet access
- Your security policies require all traffic to remain on private networks
- You need complete network isolation from the public internet
Appendix A: Required AWS Resources Overview
The Terraform configuration in Appendix B creates the following AWS resources. If you do not use Terraform, please create the equivalent resources:
1. Network Load Balancer
- Purpose: Acts as the entry point for incoming PrivateLink connections
- Type: Internal Network Load Balancer
- Configuration:
- Deployed across your specified subnets
- Cross-zone load balancing enabled
- Security group rules on PrivateLink traffic disabled (allows Infracost connections)
2. Target Group
- Purpose: Defines where the NLB forwards traffic
- Configuration:
- Protocol: TCP
- Port: 443
- Target type: EC2 instance
- Health check: TCP on port 443, every 30 seconds
3. Target Group Attachment
- Purpose: Registers your VCS EC2 instance with the target group
- Configuration:
- Links the target group to your VCS instance on port 443
4. NLB Listener
- Purpose: Listens for incoming connections and forwards them
- Configuration:
- Protocol: TCP
- Port: 443
- Action: Forward to the target group
5. VPC Endpoint Service
- Purpose: Exposes your NLB as a PrivateLink service that Infracost can connect to
- Configuration:
- Auto-accepts connections (no manual approval required)
- Attached to the NLB
- Supported regions: Your region +
us-east-2(required for cross-region connectivity) - Allowed principals: Your account + Infracost's account (
237144093413)
Appendix B: Terraform Configuration
- Replace
githubwithgitlabif connecting a GitLab instance - Update the
localsblock with your environment-specific values - Run Terraform to create:
- A Network Load Balancer pointing to your VCS instance
- A Target Group with health checks on port 443
- A VPC Endpoint Service that allows connections from Infracost
locals {
infracost_account_id = "237144093413" # Infracost prod account ID
github_app_vpc_id = "<VPC ID of your VCS instance>"
github_app_subnet_ids = ["<subnet-1>", "<subnet-2>", "<subnet-3>"]
github_app_instance_id = "<EC2 instance ID where your VCS runs>"
github_nlb_security_groups = ["<security-group-id>"]
}
# NETWORK LOAD BALANCER
resource "aws_lb" "github_nlb" {
name = "infracost-github-nlb"
load_balancer_type = "network"
subnets = local.github_app_subnet_ids
internal = true
enable_deletion_protection = true
enable_cross_zone_load_balancing = true
security_groups = local.github_nlb_security_groups
enforce_security_group_inbound_rules_on_private_link_traffic = "off"
}
# TARGET GROUP
resource "aws_lb_target_group" "github_tg" {
name = "infracost-github-tg"
protocol = "TCP"
port = 443
vpc_id = local.github_app_vpc_id
target_type = "instance"
health_check {
port = 443
protocol = "TCP"
interval = 30
unhealthy_threshold = 2
}
}
resource "aws_lb_target_group_attachment" "github_app_attachment" {
target_group_arn = aws_lb_target_group.github_tg.arn
target_id = local.github_app_instance_id
port = 443
}
# LISTENER
resource "aws_lb_listener" "github_listener" {
load_balancer_arn = aws_lb.github_nlb.arn
protocol = "TCP"
port = 443
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.github_tg.id
}
}
# VPC ENDPOINT SERVICE
resource "aws_vpc_endpoint_service" "github" {
acceptance_required = false
network_load_balancer_arns = [aws_lb.github_nlb.arn]
supported_regions = ["<your-region>", "us-east-2"] # MUST include us-east-2
allowed_principals = [
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:root",
"arn:aws:iam::${local.infracost_account_id}:root"
]
}