Skip to content

Commit f27e23d

Browse files
authored
s3_bucket_name: add all documented naming rules (#571)
1 parent c2e96d4 commit f27e23d

File tree

3 files changed

+265
-8
lines changed

3 files changed

+265
-8
lines changed

integration/rule-config/result.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,26 @@
1919
}
2020
},
2121
"callers": []
22+
},
23+
{
24+
"rule": {
25+
"name": "aws_s3_bucket_name",
26+
"severity": "error",
27+
"link": "https://github.com/terraform-linters/tflint-ruleset-aws/blob/v0.27.0/docs/rules/aws_s3_bucket_name.md"
28+
},
29+
"message": "Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-). (name: \"foo_bar\", regex: \"[^a-z0-9\\\\.\\\\-]\")",
30+
"range": {
31+
"filename": "template.tf",
32+
"start": {
33+
"line": 2,
34+
"column": 12
35+
},
36+
"end": {
37+
"line": 2,
38+
"column": 21
39+
}
40+
},
41+
"callers": []
2242
}
2343
],
2444
"errors": []

rules/aws_s3_bucket_name.go

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package rules
22

33
import (
44
"fmt"
5+
"net"
56
"regexp"
67
"strings"
78

@@ -73,6 +74,53 @@ func (r *AwsS3BucketNameRule) Check(runner tflint.Runner) error {
7374
bucketNameMinLength := 3
7475
bucketNameMaxLength := 63
7576

77+
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html
78+
regexRules := []struct {
79+
Regexp regexp.Regexp
80+
Description string
81+
}{
82+
{
83+
Regexp: *regexp.MustCompile(fmt.Sprintf(
84+
"^.{0,%d}$|.{%d,}$",
85+
bucketNameMinLength-1,
86+
bucketNameMaxLength+1,
87+
)),
88+
Description: fmt.Sprintf(
89+
"Bucket names must be between %d (min) and %d (max) characters long.",
90+
bucketNameMinLength,
91+
bucketNameMaxLength,
92+
),
93+
},
94+
{
95+
Regexp: *regexp.MustCompile("[^a-z0-9\\.\\-]"),
96+
Description: "Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-).",
97+
},
98+
{
99+
Regexp: *regexp.MustCompile("^[^a-z0-9]|[^a-z0-9]$"),
100+
Description: "Bucket names must begin and end with a lowercase letter or number.",
101+
},
102+
{
103+
Regexp: *regexp.MustCompile("\\.\\."),
104+
Description: "Bucket names must not contain two adjacent periods.",
105+
},
106+
{
107+
Regexp: *regexp.MustCompile("^xn--"),
108+
Description: "Bucket names must not start with the prefix 'xn--'.",
109+
},
110+
{
111+
Regexp: *regexp.MustCompile("^(sthree-|sthree-configurator)"),
112+
Description: "Bucket names must not start with the prefix 'sthree-' and the prefix 'sthree-configurator'.",
113+
},
114+
{
115+
Regexp: *regexp.MustCompile("-s3alias$"),
116+
Description: "Bucket names must not end with the suffix '-s3alias'.",
117+
},
118+
{
119+
Regexp: *regexp.MustCompile("--ol-s3$"),
120+
Description: "Bucket names must not end with the suffix '--ol-s3'.",
121+
},
122+
}
123+
76124
for _, resource := range resources.Blocks {
77125
attribute, exists := resource.Body.Attributes[r.attributeName]
78126
if !exists {
@@ -100,12 +148,23 @@ func (r *AwsS3BucketNameRule) Check(runner tflint.Runner) error {
100148
}
101149
}
102150

103-
if len(name) < bucketNameMinLength || len(name) > bucketNameMaxLength {
104-
runner.EmitIssue(
105-
r,
106-
fmt.Sprintf("Bucket name %q must be between %d and %d characters", name, bucketNameMinLength, bucketNameMaxLength),
107-
attribute.Expr.Range(),
108-
)
151+
nameAsIP := net.ParseIP(name)
152+
if nameAsIP != nil && net.IP.To4(nameAsIP) != nil {
153+
runner.EmitIssue(
154+
r,
155+
fmt.Sprintf(`Bucket names must not be formatted as an IP address. (name: %q)`, name),
156+
attribute.Expr.Range(),
157+
)
158+
}
159+
160+
for _, rule := range regexRules {
161+
if rule.Regexp.MatchString(name) {
162+
runner.EmitIssue(
163+
r,
164+
fmt.Sprintf(`%s (name: %q, regex: %q)`, rule.Description, name, rule.Regexp.String()),
165+
attribute.Expr.Range(),
166+
)
167+
}
109168
}
110169
return nil
111170
}, nil)

rules/aws_s3_bucket_name_test.go

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,20 +120,198 @@ rule "aws_s3_bucket_name" {
120120
{
121121
Name: "length",
122122
Content: `
123-
resource "aws_s3_bucket" "too_long" {
123+
resource "aws_s3_bucket" "max_length" {
124124
bucket = "a-really-ultra-hiper-super-long-foo-bar-baz-bucket-name.domain.test"
125125
}
126+
127+
resource "aws_s3_bucket" "min_length" {
128+
bucket = "xy"
129+
}
126130
`,
127131
Expected: helper.Issues{
128132
{
129133
Rule: NewAwsS3BucketNameRule(),
130-
Message: `Bucket name "a-really-ultra-hiper-super-long-foo-bar-baz-bucket-name.domain.test" must be between 3 and 63 characters`,
134+
Message: `Bucket names must be between 3 (min) and 63 (max) characters long. (name: "a-really-ultra-hiper-super-long-foo-bar-baz-bucket-name.domain.test", regex: "^.{0,2}$|.{64,}$")`,
131135
Range: hcl.Range{
132136
Filename: "resource.tf",
133137
Start: hcl.Pos{Line: 3, Column: 12},
134138
End: hcl.Pos{Line: 3, Column: 81},
135139
},
136140
},
141+
{
142+
Rule: NewAwsS3BucketNameRule(),
143+
Message: `Bucket names must be between 3 (min) and 63 (max) characters long. (name: "xy", regex: "^.{0,2}$|.{64,}$")`,
144+
Range: hcl.Range{
145+
Filename: "resource.tf",
146+
Start: hcl.Pos{Line: 7, Column: 12},
147+
End: hcl.Pos{Line: 7, Column: 16},
148+
},
149+
},
150+
},
151+
},
152+
{
153+
Name: "invalid_characters",
154+
Content: `
155+
resource "aws_s3_bucket" "invalid_characters" {
156+
bucket = "who_am_I?.com"
157+
}
158+
`,
159+
Expected: helper.Issues{
160+
{
161+
Rule: NewAwsS3BucketNameRule(),
162+
Message: `Bucket names can consist only of lowercase letters, numbers, dots (.), and hyphens (-). (name: "who_am_I?.com", regex: "[^a-z0-9\\.\\-]")`,
163+
Range: hcl.Range{
164+
Filename: "resource.tf",
165+
Start: hcl.Pos{Line: 3, Column: 12},
166+
End: hcl.Pos{Line: 3, Column: 27},
167+
},
168+
},
169+
},
170+
},
171+
{
172+
Name: "invalid_begin_end",
173+
Content: `
174+
resource "aws_s3_bucket" "invalid_begin" {
175+
bucket = ".domain.com"
176+
}
177+
178+
resource "aws_s3_bucket" "invalid_end" {
179+
bucket = "domain.com."
180+
}
181+
`,
182+
Expected: helper.Issues{
183+
{
184+
Rule: NewAwsS3BucketNameRule(),
185+
Message: `Bucket names must begin and end with a lowercase letter or number. (name: ".domain.com", regex: "^[^a-z0-9]|[^a-z0-9]$")`,
186+
Range: hcl.Range{
187+
Filename: "resource.tf",
188+
Start: hcl.Pos{Line: 3, Column: 12},
189+
End: hcl.Pos{Line: 3, Column: 25},
190+
},
191+
},
192+
{
193+
Rule: NewAwsS3BucketNameRule(),
194+
Message: `Bucket names must begin and end with a lowercase letter or number. (name: "domain.com.", regex: "^[^a-z0-9]|[^a-z0-9]$")`,
195+
Range: hcl.Range{
196+
Filename: "resource.tf",
197+
Start: hcl.Pos{Line: 7, Column: 12},
198+
End: hcl.Pos{Line: 7, Column: 25},
199+
},
200+
},
201+
},
202+
},
203+
{
204+
Name: "adjacent_periods",
205+
Content: `
206+
resource "aws_s3_bucket" "adjacent_periods" {
207+
bucket = "domain..com"
208+
}
209+
`,
210+
Expected: helper.Issues{
211+
{
212+
Rule: NewAwsS3BucketNameRule(),
213+
Message: `Bucket names must not contain two adjacent periods. (name: "domain..com", regex: "\\.\\.")`,
214+
Range: hcl.Range{
215+
Filename: "resource.tf",
216+
Start: hcl.Pos{Line: 3, Column: 12},
217+
End: hcl.Pos{Line: 3, Column: 25},
218+
},
219+
},
220+
},
221+
},
222+
{
223+
Name: "ipv4",
224+
Content: `
225+
resource "aws_s3_bucket" "ipv4" {
226+
bucket = "192.168.0.254"
227+
}
228+
`,
229+
Expected: helper.Issues{
230+
{
231+
Rule: NewAwsS3BucketNameRule(),
232+
Message: `Bucket names must not be formatted as an IP address. (name: "192.168.0.254")`,
233+
Range: hcl.Range{
234+
Filename: "resource.tf",
235+
Start: hcl.Pos{Line: 3, Column: 12},
236+
End: hcl.Pos{Line: 3, Column: 27},
237+
},
238+
},
239+
},
240+
},
241+
{
242+
Name: "invalid_prefix_xn",
243+
Content: `
244+
resource "aws_s3_bucket" "invalid_prefix_xn" {
245+
bucket = "xn--domain.com"
246+
}
247+
`,
248+
Expected: helper.Issues{
249+
{
250+
Rule: NewAwsS3BucketNameRule(),
251+
Message: `Bucket names must not start with the prefix 'xn--'. (name: "xn--domain.com", regex: "^xn--")`,
252+
Range: hcl.Range{
253+
Filename: "resource.tf",
254+
Start: hcl.Pos{Line: 3, Column: 12},
255+
End: hcl.Pos{Line: 3, Column: 28},
256+
},
257+
},
258+
},
259+
},
260+
{
261+
Name: "invalid_prefix_sthree",
262+
Content: `
263+
resource "aws_s3_bucket" "invalid_prefix_sthree" {
264+
bucket = "sthree-domain.com"
265+
}
266+
`,
267+
Expected: helper.Issues{
268+
{
269+
Rule: NewAwsS3BucketNameRule(),
270+
Message: `Bucket names must not start with the prefix 'sthree-' and the prefix 'sthree-configurator'. (name: "sthree-domain.com", regex: "^(sthree-|sthree-configurator)")`,
271+
Range: hcl.Range{
272+
Filename: "resource.tf",
273+
Start: hcl.Pos{Line: 3, Column: 12},
274+
End: hcl.Pos{Line: 3, Column: 31},
275+
},
276+
},
277+
},
278+
},
279+
{
280+
Name: "invalid_suffix_s3alias",
281+
Content: `
282+
resource "aws_s3_bucket" "invalid_suffix_s3alias" {
283+
bucket = "domain-s3alias"
284+
}
285+
`,
286+
Expected: helper.Issues{
287+
{
288+
Rule: NewAwsS3BucketNameRule(),
289+
Message: `Bucket names must not end with the suffix '-s3alias'. (name: "domain-s3alias", regex: "-s3alias$")`,
290+
Range: hcl.Range{
291+
Filename: "resource.tf",
292+
Start: hcl.Pos{Line: 3, Column: 12},
293+
End: hcl.Pos{Line: 3, Column: 28},
294+
},
295+
},
296+
},
297+
},
298+
{
299+
Name: "invalid_suffix_ols3",
300+
Content: `
301+
resource "aws_s3_bucket" "invalid_suffix_ols3" {
302+
bucket = "domain--ol-s3"
303+
}
304+
`,
305+
Expected: helper.Issues{
306+
{
307+
Rule: NewAwsS3BucketNameRule(),
308+
Message: `Bucket names must not end with the suffix '--ol-s3'. (name: "domain--ol-s3", regex: "--ol-s3$")`,
309+
Range: hcl.Range{
310+
Filename: "resource.tf",
311+
Start: hcl.Pos{Line: 3, Column: 12},
312+
End: hcl.Pos{Line: 3, Column: 27},
313+
},
314+
},
137315
},
138316
},
139317
}

0 commit comments

Comments
 (0)