Skip to content

Commit cd6b5d5

Browse files
committed
feat(aws-privatelink): simplify lab config to TF_VAR env vars with auto-derived AZs
All three labs now share a consistent variable interface: - Required: owner_email, falcon_client_id, falcon_client_secret, falcon_cloud - Optional: region, environment, instance_type, ami_id - Removed: name_prefix, availability_zones, vpc_cidr, subnet_cidrs AZs auto-derive from the region. VPC CIDRs are hardcoded in locals. Environment variable is used as the resource name prefix (default: dev). Adds PrivateLink readiness check in user_data to avoid 4-min sensor fallback timeout on first boot.
1 parent ff252d9 commit cd6b5d5

17 files changed

Lines changed: 234 additions & 286 deletions

File tree

aws-privatelink/docs/architecture-01-per-vpc.md

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ interface endpoints, its own `cloudsink.net` private hosted zone, its own S3
77
gateway endpoint, and one private Amazon Linux 2023 test host. There is no
88
shared network infrastructure between VPCs.
99

10-
This Terraform example deploys in one consumer Region, `us-east-2` by
11-
default, with two Availability Zones. For a US-2 Falcon CID, the VPC endpoints
12-
are created in `us-east-2` and connect to the CrowdStrike endpoint service in
10+
This Terraform example deploys in any supported consumer Region. For a US-2
11+
Falcon CID, the VPC endpoints connect to the CrowdStrike endpoint service in
1312
`us-west-2` over cross-region PrivateLink. The example composes the same
1413
`endpoint-vpc` and `sensor-host` modules used by the multi-account examples
1514
(02, 03), wired to a single AWS account.
@@ -23,7 +22,6 @@ are created in `us-east-2` and connect to the CrowdStrike endpoint service in
2322
- [Deployment](#deployment)
2423
- [Export credentials](#export-credentials)
2524
- [Apply](#apply)
26-
- [Pick a different consumer Region](#pick-a-different-consumer-region)
2725
- [Teardown](#teardown)
2826
- [Operational notes](#operational-notes)
2927
- [Verification](#verification)
@@ -133,12 +131,23 @@ lab S3 bucket so the test host can install without internet egress.
133131
### Export credentials
134132

135133
```bash
136-
export AWS_PROFILE=... # or any other AWS auth method
137-
export TF_VAR_falcon_client_id='...' # CrowdStrike API client ID
138-
export TF_VAR_falcon_client_secret='...' # CrowdStrike API secret
139-
export TF_VAR_owner_email='you@example.com' # required owner tag
134+
# Required
135+
export AWS_PROFILE=... # AWS credentials for the target account
136+
export TF_VAR_falcon_client_id='...' # Falcon API client ID (Sensor Download: Read)
137+
export TF_VAR_falcon_client_secret='...' # Falcon API client secret
138+
export TF_VAR_falcon_cloud='us-2' # Falcon cloud for this CID (us-1, us-2, eu-1)
139+
export TF_VAR_owner_email='you@example.com' # Owner tag for resource accountability
140+
141+
# Optional
142+
export TF_VAR_region='us-east-2' # Region where the lab is deployed
143+
export TF_VAR_environment='dev' # Resource name prefix and environment tag
144+
export TF_VAR_instance_type='t3.small' # EC2 instance size for sensor hosts
145+
export TF_VAR_ami_id='ami-0abcdef1234567890' # Only set if the default is deprecated; must be Amazon Linux 2023
140146
```
141147

148+
Avoid deploying to Falcon home Regions (`us-west-1`, `us-west-2`, `eu-central-1`)
149+
since the lab is designed to demonstrate cross-region PrivateLink connectivity.
150+
142151
### Apply
143152

144153
```bash
@@ -162,21 +171,6 @@ On first apply, Terraform will:
162171
Expect about 3-5 minutes from `apply complete` to the host appearing in the
163172
Falcon console.
164173

165-
### Pick a different consumer Region
166-
167-
The lab defaults to `us-east-2` because it demonstrates cross-region
168-
connectivity without deploying the consumer VPC in a Falcon home Region.
169-
170-
To change the consumer Region, set:
171-
172-
```bash
173-
export TF_VAR_region='eu-west-1'
174-
export TF_VAR_availability_zones='["eu-west-1a", "eu-west-1b"]'
175-
export TF_VAR_subnet_cidrs='["10.50.1.0/24", "10.50.2.0/24"]'
176-
```
177-
178-
Avoid the unsupported Regions listed in the root README.
179-
180174
## Teardown
181175

182176
```bash
Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,35 @@
11
# Single-account, single-region stack. Creates one VPC with CrowdStrike
22
# PrivateLink endpoints, S3 bucket, PHZ, and a private sensor host. Uses
3-
# the same endpoint-vpc + sensor-host modules as 02 and 03 — the only
4-
# difference is that both modules share one provider and no RAM / TGW is
5-
# needed.
3+
# the same endpoint-vpc + sensor-host modules as 02 and 03.
64
#
75
# The RPM and CID come from fetch.tf (root-level), so the Falcon API is
86
# hit once per apply.
97

8+
data "aws_availability_zones" "available" {
9+
state = "available"
10+
}
11+
1012
locals {
11-
name_prefix = "${var.environment}-${var.name_prefix}"
13+
azs = slice(data.aws_availability_zones.available.names, 0, 2)
14+
vpc_cidr = "10.50.0.0/16"
15+
subnet_cidrs = [cidrsubnet(local.vpc_cidr, 8, 1), cidrsubnet(local.vpc_cidr, 8, 2)]
1216
}
1317

1418
module "endpoint_vpc" {
1519
source = "../../modules/endpoint-vpc"
1620

1721
region = var.region
18-
availability_zones = var.availability_zones
19-
name_prefix = local.name_prefix
22+
availability_zones = local.azs
23+
name_prefix = var.environment
2024

21-
vpc_cidr = var.vpc_cidr
22-
subnet_cidrs = var.subnet_cidrs
25+
vpc_cidr = local.vpc_cidr
26+
subnet_cidrs = local.subnet_cidrs
2327

2428
falcon_cloud = var.falcon_cloud
2529
sensor_rpm_path = local.fetched_rpm_path
2630

27-
# Single-account: no RAM subnet sharing needed.
2831
ram_principals = []
2932

30-
# Wire instance SG into the endpoints SG ingress.
3133
consumer_sg_ids = {
3234
sensor-host = module.sensor_host.instance_sg_id
3335
}
@@ -37,7 +39,7 @@ module "sensor_host" {
3739
source = "../../modules/sensor-host"
3840

3941
region = var.region
40-
name_prefix = local.name_prefix
42+
name_prefix = var.environment
4143

4244
vpc_id = module.endpoint_vpc.vpc_id
4345
subnet_ids = module.endpoint_vpc.subnet_ids_list
@@ -48,6 +50,8 @@ module "sensor_host" {
4850
sensor_bucket_name = module.endpoint_vpc.sensor_bucket_name
4951
sensor_bucket_rpm_key = module.endpoint_vpc.sensor_bucket_rpm_key
5052

51-
falcon_cloud = var.falcon_cloud
52-
falcon_cid = local.fetched_cid
53+
falcon_cloud = var.falcon_cloud
54+
falcon_cid = local.fetched_cid
55+
instance_type = var.instance_type
56+
ami_id = var.ami_id
5357
}

aws-privatelink/examples/01-per-vpc/outputs.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ output "deployment" {
22
description = "Everything you need to SSM into, verify, and operate the stack."
33
value = {
44
region = var.region
5+
environment = var.environment
56
instance_ids = module.sensor_host.instance_ids
67
ami_id = module.sensor_host.ami_id
78
sensor_bucket = module.endpoint_vpc.sensor_bucket_name

aws-privatelink/examples/01-per-vpc/providers.tf

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ locals {
33
Environment = var.environment
44
OwnerEmail = var.owner_email
55
ManagedBy = "terraform"
6-
Project = "aws-privatelink-reference"
7-
Example = "01-per-vpc"
6+
Project = "aws-privatelink"
87
}
98
}
109

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,59 @@
1-
variable "region" {
2-
description = "Consumer region where the VPC and endpoints are created."
3-
type = string
4-
default = "us-east-2"
5-
}
1+
### Required — credentials & identity ########################################
62

7-
variable "availability_zones" {
8-
description = "Two AZs in the consumer region. One private subnet (and one endpoint ENI) is placed per AZ."
9-
type = list(string)
10-
default = ["us-east-2a", "us-east-2b"]
11-
}
12-
13-
variable "vpc_cidr" {
14-
description = "VPC CIDR for the consumer VPC."
3+
variable "owner_email" {
4+
description = "OwnerEmail tag value applied to every resource."
155
type = string
16-
default = "10.50.0.0/16"
17-
}
186

19-
variable "subnet_cidrs" {
20-
description = "Private subnet CIDRs, one per AZ in availability_zones (order-aligned)."
21-
type = list(string)
22-
default = ["10.50.1.0/24", "10.50.2.0/24"]
7+
validation {
8+
condition = can(regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", var.owner_email))
9+
error_message = "owner_email must be a valid email address (e.g. you@example.com)."
10+
}
2311
}
2412

25-
variable "name_prefix" {
26-
description = "Base name prefix. Combined with var.environment to produce the effective prefix applied to all resources."
13+
variable "falcon_client_id" {
14+
description = "CrowdStrike Falcon API client ID with Sensor Download: Read scope."
2715
type = string
28-
default = "cs-privatelink"
16+
sensitive = true
2917
}
3018

31-
variable "environment" {
32-
description = "Environment tag value applied to every resource."
19+
variable "falcon_client_secret" {
20+
description = "CrowdStrike Falcon API client secret."
3321
type = string
34-
default = "demo"
22+
sensitive = true
3523
}
3624

37-
variable "owner_email" {
38-
description = "OwnerEmail tag value applied to every resource. Required — every deployment is tied to an accountable owner."
25+
variable "falcon_cloud" {
26+
description = "CrowdStrike Falcon cloud (us-1, us-2, or eu-1)."
3927
type = string
4028

4129
validation {
42-
condition = can(regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", var.owner_email))
43-
error_message = "owner_email must be a valid email address (e.g. you@example.com)."
30+
condition = contains(["us-1", "us-2", "eu-1"], var.falcon_cloud)
31+
error_message = "falcon_cloud must be one of us-1, us-2, eu-1."
4432
}
4533
}
4634

47-
variable "falcon_client_id" {
48-
description = "CrowdStrike Falcon API client ID with Sensor Download: Read scope. Export as TF_VAR_falcon_client_id."
35+
### Optional — deployment configuration #####################################
36+
37+
variable "region" {
38+
description = "AWS region to deploy into. AZs are auto-derived."
4939
type = string
50-
sensitive = true
40+
default = "us-east-2"
5141
}
5242

53-
variable "falcon_client_secret" {
54-
description = "CrowdStrike Falcon API client secret. Export as TF_VAR_falcon_client_secret."
43+
variable "environment" {
44+
description = "Environment name used as the resource prefix and tag value."
5545
type = string
56-
sensitive = true
46+
default = "dev"
5747
}
5848

59-
variable "falcon_cloud" {
60-
description = "CrowdStrike Falcon cloud (us-1, us-2, or eu-1)."
49+
variable "instance_type" {
50+
description = "EC2 instance type for sensor hosts."
51+
type = string
52+
default = "t3.small"
53+
}
54+
55+
variable "ami_id" {
56+
description = "Amazon Linux 2023 AMI ID. Only set if the default becomes unavailable. Must be AL2023."
6157
type = string
62-
default = "us-2"
58+
default = null
6359
}

aws-privatelink/examples/02-shared-vpc/main.tf

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
# Two-account shared VPC. The owner provisions a single VPC with all the
22
# PrivateLink plumbing and RAM-shares its subnets to the workload account.
3-
# The workload launches a sensor host directly into those shared subnets —
4-
# no workload VPC, no cross-account R53, no cross-account SSM params.
3+
# The workload launches a sensor host directly into those shared subnets.
54
#
6-
# Cross-cutting wiring:
7-
# * endpoint_vpc.consumer_sg_ids <- sensor_host.instance_sg_id (the SG
8-
# reference breaks out of the workload account into the endpoints SG
9-
# because both SGs live in the owner's VPC)
10-
# * endpoint_vpc.authorized_role_arns <- sensor_host.instance_role_arn
11-
# (grants the workload instance role s3:GetObject on the sensor bucket)
12-
#
13-
# for_each on the consumer_sg_ids uses a literal string key ("workload-host"),
14-
# so it's plan-time known; the SG ID value is apply-time computed. That's
15-
# what lets this compose without a resource cycle or depends_on hack.
5+
# The RPM and CID come from fetch.tf (root-level), so the Falcon API is
6+
# hit once per apply.
7+
8+
data "aws_availability_zones" "available" {
9+
provider = aws.owner
10+
state = "available"
11+
}
1612

1713
locals {
18-
name_prefix = "${var.environment}-${var.name_prefix}"
14+
azs = slice(data.aws_availability_zones.available.names, 0, 2)
15+
vpc_cidr = "10.60.0.0/16"
16+
subnet_cidrs = [cidrsubnet(local.vpc_cidr, 8, 1), cidrsubnet(local.vpc_cidr, 8, 2)]
1917
}
2018

2119
module "endpoint_vpc" {
@@ -26,11 +24,11 @@ module "endpoint_vpc" {
2624
}
2725

2826
region = var.region
29-
availability_zones = var.availability_zones
30-
name_prefix = local.name_prefix
27+
availability_zones = local.azs
28+
name_prefix = var.environment
3129

32-
vpc_cidr = var.vpc_cidr
33-
subnet_cidrs = var.subnet_cidrs
30+
vpc_cidr = local.vpc_cidr
31+
subnet_cidrs = local.subnet_cidrs
3432

3533
falcon_cloud = var.falcon_cloud
3634
sensor_rpm_path = local.fetched_rpm_path
@@ -54,7 +52,7 @@ module "sensor_host" {
5452
}
5553

5654
region = var.region
57-
name_prefix = local.name_prefix
55+
name_prefix = var.environment
5856

5957
vpc_id = module.endpoint_vpc.vpc_id
6058
subnet_ids = module.endpoint_vpc.subnet_ids_list
@@ -65,6 +63,8 @@ module "sensor_host" {
6563
sensor_bucket_name = module.endpoint_vpc.sensor_bucket_name
6664
sensor_bucket_rpm_key = module.endpoint_vpc.sensor_bucket_rpm_key
6765

68-
falcon_cloud = var.falcon_cloud
69-
falcon_cid = local.fetched_cid
66+
falcon_cloud = var.falcon_cloud
67+
falcon_cid = local.fetched_cid
68+
instance_type = var.instance_type
69+
ami_id = var.ami_id
7070
}

aws-privatelink/examples/02-shared-vpc/outputs.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ output "deployment" {
22
description = "Everything you need to SSM into, verify, and operate the stack. SSM commands target the workload account — prepend --profile $workload_profile on the workstation."
33
value = {
44
region = var.region
5+
environment = var.environment
56
owner_profile = var.owner_profile
67
workload_profile = var.workload_profile
78
workload_account_id = var.workload_account_id

aws-privatelink/examples/02-shared-vpc/providers.tf

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@ locals {
33
Environment = var.environment
44
OwnerEmail = var.owner_email
55
ManagedBy = "terraform"
6-
Project = "aws-privatelink-reference"
7-
Example = "02-shared-vpc"
6+
Project = "aws-privatelink"
87
}
98
}
109

11-
# Owner account — hosts the VPC, endpoints, PHZ, sensor bucket, RAM share.
12-
# One AWS allowlist ticket is filed against this account.
1310
provider "aws" {
1411
alias = "owner"
1512
region = var.region
@@ -20,9 +17,6 @@ provider "aws" {
2017
}
2118
}
2219

23-
# Workload account — launches EC2 into the RAM-shared subnets. No VPC of its
24-
# own. Any number of workload accounts share this shape; the module just gets
25-
# called once per account with a matching provider alias.
2620
provider "aws" {
2721
alias = "workload"
2822
region = var.region

0 commit comments

Comments
 (0)