Skip to content

Commit 86ffdb3

Browse files
committed
feat(rule): check values of service principals
Signed-off-by: Fred Myerscough <oniice@gmail.com>
1 parent 12b874a commit 86ffdb3

File tree

10 files changed

+643
-20
lines changed

10 files changed

+643
-20
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,16 @@ plugin "aws-meta" {
2828

2929
## Rules
3030

31-
|Name|Description|Severity|Enabled|Link|
31+
|Name|Description|Severity|Enabled By Default|Link|
3232
| --- | --- | --- | --- | --- |
33-
|aws_meta_hardcoded|Validates that there are no hardcoded AWS regions or partitions in ARN values across all resource types|WARNING||[docs](docs/rules.md#aws_meta_hardcoded)|
34-
|aws_iam_role_policy_hardcoded_region|Validates that there are no hardcoded AWS regions in IAM role policy documents|WARNING||[docs](docs/rules.md#aws_iam_role_policy_hardcoded_region)|
35-
|aws_iam_role_policy_hardcoded_partition|Validates that there are no hardcoded AWS partitions in IAM role policy documents|WARNING||[docs](docs/rules.md#aws_iam_role_policy_hardcoded_partition)|
36-
|aws_iam_policy_hardcoded_region|Validates that there are no hardcoded AWS regions in IAM policy documents|WARNING||[docs](docs/rules.md#aws_iam_policy_hardcoded_region)|
37-
|aws_iam_policy_hardcoded_partition|Validates that there are no hardcoded AWS partitions in IAM policy documents|WARNING||[docs](docs/rules.md#aws_iam_policy_hardcoded_partition)|
38-
|aws_provider_hardcoded_region|Validates that there are no hardcoded AWS regions in provider configuration|WARNING||[docs](docs/rules.md#aws_provider_hardcoded_region)|
33+
|aws_meta_hardcoded|Validates that there are no hardcoded AWS regions or partitions in ARN values across all resource types|WARNING||[docs](docs/rules.md#aws_meta_hardcoded)|
34+
|aws_iam_role_policy_hardcoded_region|Validates that there are no hardcoded AWS regions in IAM role policy documents|WARNING||[docs](docs/rules.md#aws_iam_role_policy_hardcoded_region)|
35+
|aws_iam_role_policy_hardcoded_partition|Validates that there are no hardcoded AWS partitions in IAM role policy documents|WARNING||[docs](docs/rules.md#aws_iam_role_policy_hardcoded_partition)|
36+
|aws_iam_policy_hardcoded_region|Validates that there are no hardcoded AWS regions in IAM policy documents|WARNING||[docs](docs/rules.md#aws_iam_policy_hardcoded_region)|
37+
|aws_iam_policy_hardcoded_partition|Validates that there are no hardcoded AWS partitions in IAM policy documents|WARNING||[docs](docs/rules.md#aws_iam_policy_hardcoded_partition)|
38+
|aws_provider_hardcoded_region|Validates that there are no hardcoded AWS regions in provider configuration|WARNING||[docs](docs/rules.md#aws_provider_hardcoded_region)|
39+
|aws_service_principal_hardcoded|Validates that service principals don't use hardcoded DNS suffixes (e.g., amazonaws.com)|WARNING||[docs](docs/rules.md#aws_service_principal_hardcoded)|
40+
|aws_service_principal_dns_suffix|Validates that service principals don't use dns_suffix interpolation|WARNING||[docs](docs/rules.md#aws_service_principal_dns_suffix)|
3941

4042
For detailed examples and usage information, see the [Rule Details documentation](docs/rules.md).
4143

examples/failing/.tflint.hcl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,13 @@ rule "aws_provider_hardcoded_region" {
2424

2525
rule "aws_meta_hardcoded" {
2626
enabled = true
27-
}
27+
}
28+
29+
30+
rule "aws_service_principal_hardcoded" {
31+
enabled = true
32+
}
33+
34+
rule "aws_service_principal_dns_suffix" {
35+
enabled = true
36+
}

examples/failing/main.tf

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,94 @@ resource "aws_cloudwatch_event_target" "example" {
9090
rule = "my-rule"
9191
arn = "arn:aws-cn:lambda:cn-north-1:123456789012:function:my-function"
9292
}
93+
94+
# Get partition data
95+
data "aws_partition" "current" {}
96+
97+
# IAM role with hardcoded service principal DNS suffix (potential future rule)
98+
resource "aws_iam_role" "service_principal_bad_hardcoded" {
99+
name = "service-principal-bad-hardcoded"
100+
101+
assume_role_policy = jsonencode({
102+
Version = "2012-10-17"
103+
Statement = [{
104+
Action = "sts:AssumeRole"
105+
Effect = "Allow"
106+
Principal = {
107+
Service = "s3.amazonaws.com" # ❌ Hardcoded DNS suffix (should use data.aws_service_principal)
108+
}
109+
}]
110+
})
111+
}
112+
113+
# IAM role using dns_suffix (not best practice - potential future rule)
114+
resource "aws_iam_role" "service_principal_bad_dns_suffix" {
115+
name = "service-principal-bad-dns-suffix"
116+
117+
assume_role_policy = jsonencode({
118+
Version = "2012-10-17"
119+
Statement = [{
120+
Action = "sts:AssumeRole"
121+
Effect = "Allow"
122+
Principal = {
123+
Service = "s3.${data.aws_partition.current.dns_suffix}"
124+
}
125+
}]
126+
})
127+
}
128+
129+
# IAM role with multiple hardcoded service principals (potential future rule)
130+
resource "aws_iam_role" "multi_service_principal_bad_hardcoded" {
131+
name = "multi-service-bad-hardcoded"
132+
133+
assume_role_policy = jsonencode({
134+
Version = "2012-10-17"
135+
Statement = [{
136+
Action = "sts:AssumeRole"
137+
Effect = "Allow"
138+
Principal = {
139+
Service = [
140+
"lambda.amazonaws.com",
141+
"ec2.amazonaws.com",
142+
"ecs-tasks.amazonaws.com"
143+
]
144+
}
145+
}]
146+
})
147+
}
148+
149+
# IAM role with multiple service principals using dns_suffix (not best practice)
150+
resource "aws_iam_role" "multi_service_principal_bad_dns_suffix" {
151+
name = "multi-service-bad-dns-suffix"
152+
153+
assume_role_policy = jsonencode({
154+
Version = "2012-10-17"
155+
Statement = [{
156+
Action = "sts:AssumeRole"
157+
Effect = "Allow"
158+
Principal = {
159+
Service = [
160+
"lambda.${data.aws_partition.current.dns_suffix}",
161+
"ec2.${data.aws_partition.current.dns_suffix}",
162+
"ecs-tasks.${data.aws_partition.current.dns_suffix}"
163+
]
164+
}
165+
}]
166+
})
167+
}
168+
169+
# IAM role with China partition hardcoded (potential future rule)
170+
resource "aws_iam_role" "china_service_principal_bad" {
171+
name = "china-service-bad"
172+
173+
assume_role_policy = jsonencode({
174+
Version = "2012-10-17"
175+
Statement = [{
176+
Action = "sts:AssumeRole"
177+
Effect = "Allow"
178+
Principal = {
179+
Service = "lambda.amazonaws.com.cn"
180+
}
181+
}]
182+
})
183+
}

examples/passing/.tflint.hcl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,11 @@ rule "aws_provider_hardcoded_region" {
2525
rule "aws_meta_hardcoded" {
2626
enabled = true
2727
}
28+
29+
rule "aws_service_principal_hardcoded" {
30+
enabled = true
31+
}
32+
33+
rule "aws_service_principal_dns_suffix" {
34+
enabled = true
35+
}

examples/passing/main.tf

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ terraform {
1414
}
1515
}
1616

17-
# Use variables for region configuration
18-
variable "aws_region" {
19-
description = "AWS region"
20-
type = string
21-
# No default value to avoid hardcoding
22-
}
23-
2417
# Use data sources to get current region and partition
2518
data "aws_region" "current" {}
2619
data "aws_partition" "current" {}
@@ -40,11 +33,25 @@ resource "random_id" "bucket_suffix" {
4033
byte_length = 4
4134
}
4235

43-
# IAM role with dynamic ARN construction
36+
# Get service principal for EC2
37+
data "aws_service_principal" "ec2_for_role" {
38+
service_name = "ec2"
39+
}
40+
41+
# IAM role with dynamic service principal (best practice)
4442
resource "aws_iam_role" "example" {
4543
name = "example-role"
46-
47-
assume_role_policy = "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Action\": \"sts:AssumeRole\", \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"ec2.amazonaws.com\"}}]}"
44+
45+
assume_role_policy = jsonencode({
46+
Version = "2012-10-17"
47+
Statement = [{
48+
Action = "sts:AssumeRole"
49+
Effect = "Allow"
50+
Principal = {
51+
Service = data.aws_service_principal.ec2_for_role.name
52+
}
53+
}]
54+
})
4855
}
4956

5057
# IAM role policy with dynamic ARNs
@@ -65,12 +72,17 @@ resource "aws_iam_policy" "example" {
6572
data "aws_caller_identity" "current" {}
6673

6774

68-
# Lambda permission with dynamic ARN (best practice)
75+
# Get service principal for S3 (for Lambda permission)
76+
data "aws_service_principal" "s3_for_lambda" {
77+
service_name = "s3"
78+
}
79+
80+
# Lambda permission with dynamic ARN and service principal (best practice)
6981
resource "aws_lambda_permission" "example" {
7082
statement_id = "AllowS3Invoke"
7183
action = "lambda:InvokeFunction"
7284
function_name = "my-function"
73-
principal = "s3.amazonaws.com"
85+
principal = data.aws_service_principal.s3_for_lambda.name
7486
source_arn = "arn:${data.aws_partition.current.partition}:s3:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:bucket/my-bucket"
7587
}
7688

@@ -84,6 +96,7 @@ resource "aws_sns_topic_subscription" "example" {
8496
# KMS grant with dynamic ARN (best practice)
8597
resource "aws_kms_grant" "example" {
8698
name = "my-grant"
99+
operations = ["Encrypt", "Decrypt", "GenerateDataKey"]
87100
key_id = "arn:${data.aws_partition.current.partition}:kms:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:key/12345678-1234-1234-1234-123456789012"
88101
grantee_principal = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/my-role"
89102
}
@@ -93,3 +106,56 @@ resource "aws_cloudwatch_event_target" "example" {
93106
rule = "my-rule"
94107
arn = "arn:${data.aws_partition.current.partition}:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:my-function"
95108
}
109+
110+
# Get service principal names dynamically (best practice)
111+
data "aws_service_principal" "s3" {
112+
service_name = "s3"
113+
}
114+
115+
data "aws_service_principal" "lambda" {
116+
service_name = "lambda"
117+
}
118+
119+
data "aws_service_principal" "ec2" {
120+
service_name = "ec2"
121+
}
122+
123+
data "aws_service_principal" "ecs_tasks" {
124+
service_name = "ecs-tasks"
125+
}
126+
127+
# IAM role with service principal using data source (best practice)
128+
resource "aws_iam_role" "service_principal_example" {
129+
name = "service-principal-role"
130+
131+
assume_role_policy = jsonencode({
132+
Version = "2012-10-17"
133+
Statement = [{
134+
Action = "sts:AssumeRole"
135+
Effect = "Allow"
136+
Principal = {
137+
Service = data.aws_service_principal.s3.name
138+
}
139+
}]
140+
})
141+
}
142+
143+
# IAM role with multiple service principals using data sources (best practice)
144+
resource "aws_iam_role" "multi_service_principal" {
145+
name = "multi-service-role"
146+
147+
assume_role_policy = jsonencode({
148+
Version = "2012-10-17"
149+
Statement = [{
150+
Action = "sts:AssumeRole"
151+
Effect = "Allow"
152+
Principal = {
153+
Service = [
154+
data.aws_service_principal.lambda.name,
155+
data.aws_service_principal.ec2.name,
156+
data.aws_service_principal.ecs_tasks.name
157+
]
158+
}
159+
}]
160+
})
161+
}

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ func main() {
1818
rules.NewAwsIamPolicyHardcodedRegionRule(),
1919
rules.NewAwsIamPolicyHardcodedPartitionRule(),
2020
rules.NewAwsProviderHardcodedRegionRule(),
21+
rules.NewAwsServicePrincipalHardcodedRule(),
22+
rules.NewAwsServicePrincipalDNSSuffixRule(),
2123
},
2224
},
2325
})

0 commit comments

Comments
 (0)