Skip to content

Commit 6c8b74b

Browse files
fix: added gcs name validation (#10426) (#17858)
[upstream:8e02735411039856ec1dc1f0f36471c268ec857f] Signed-off-by: Modular Magician <[email protected]>
1 parent c7dcee5 commit 6c8b74b

File tree

6 files changed

+97
-74
lines changed

6 files changed

+97
-74
lines changed

.changelog/10426.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note: bug
2+
storage: added validation to `name` field in `google_storage_bucket` resource
3+
```

google/services/storage/resource_storage_bucket.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
1919
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
20+
"github.com/hashicorp/terraform-provider-google/google/verify"
2021

2122
"github.com/gammazero/workerpool"
2223
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
@@ -59,10 +60,11 @@ func ResourceStorageBucket() *schema.Resource {
5960

6061
Schema: map[string]*schema.Schema{
6162
"name": {
62-
Type: schema.TypeString,
63-
Required: true,
64-
ForceNew: true,
65-
Description: `The name of the bucket.`,
63+
Type: schema.TypeString,
64+
Required: true,
65+
ForceNew: true,
66+
Description: `The name of the bucket.`,
67+
ValidateFunc: verify.ValidateGCSName,
6668
},
6769

6870
"encryption": {
@@ -579,9 +581,6 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error
579581

580582
// Get the bucket and location
581583
bucket := d.Get("name").(string)
582-
if err := tpgresource.CheckGCSName(bucket); err != nil {
583-
return err
584-
}
585584
location := d.Get("location").(string)
586585

587586
// Create a bucket, setting the labels, location and name.

google/tpgresource/utils.go

-25
Original file line numberDiff line numberDiff line change
@@ -617,31 +617,6 @@ func Fake404(reasonResourceType, resourceName string) *googleapi.Error {
617617
}
618618
}
619619

620-
// validate name of the gcs bucket. Guidelines are located at https://cloud.google.com/storage/docs/naming-buckets
621-
// this does not attempt to check for IP addresses or close misspellings of "google"
622-
func CheckGCSName(name string) error {
623-
if strings.HasPrefix(name, "goog") {
624-
return fmt.Errorf("error: bucket name %s cannot start with %q", name, "goog")
625-
}
626-
627-
if strings.Contains(name, "google") {
628-
return fmt.Errorf("error: bucket name %s cannot contain %q", name, "google")
629-
}
630-
631-
valid, _ := regexp.MatchString("^[a-z0-9][a-z0-9_.-]{1,220}[a-z0-9]$", name)
632-
if !valid {
633-
return fmt.Errorf("error: bucket name validation failed %v. See https://cloud.google.com/storage/docs/naming-buckets", name)
634-
}
635-
636-
for _, str := range strings.Split(name, ".") {
637-
valid, _ := regexp.MatchString("^[a-z0-9_-]{1,63}$", str)
638-
if !valid {
639-
return fmt.Errorf("error: bucket name validation failed %v", str)
640-
}
641-
}
642-
return nil
643-
}
644-
645620
// CheckGoogleIamPolicy makes assertions about the contents of a google_iam_policy data source's policy_data attribute
646621
func CheckGoogleIamPolicy(value string) error {
647622
if strings.Contains(value, "\"description\":\"\"") {

google/tpgresource/utils_test.go

-42
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/hashicorp/errwrap"
1212
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1313

14-
"github.com/hashicorp/terraform-provider-google/google/acctest"
1514
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
1615
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
1716

@@ -1256,44 +1255,3 @@ func TestReplaceVars(t *testing.T) {
12561255
})
12571256
}
12581257
}
1259-
1260-
func TestCheckGCSName(t *testing.T) {
1261-
valid63 := acctest.RandString(t, 63)
1262-
cases := map[string]bool{
1263-
// Valid
1264-
"foobar": true,
1265-
"foobar1": true,
1266-
"12345": true,
1267-
"foo_bar_baz": true,
1268-
"foo-bar-baz": true,
1269-
"foo-bar_baz1": true,
1270-
"foo--bar": true,
1271-
"foo__bar": true,
1272-
"foo-goog": true,
1273-
"foo.goog": true,
1274-
valid63: true,
1275-
fmt.Sprintf("%s.%s.%s", valid63, valid63, valid63): true,
1276-
1277-
// Invalid
1278-
"goog-foobar": false,
1279-
"foobar-google": false,
1280-
"-foobar": false,
1281-
"foobar-": false,
1282-
"_foobar": false,
1283-
"foobar_": false,
1284-
"fo": false,
1285-
"foo$bar": false,
1286-
"foo..bar": false,
1287-
acctest.RandString(t, 64): false,
1288-
fmt.Sprintf("%s.%s.%s.%s", valid63, valid63, valid63, valid63): false,
1289-
}
1290-
1291-
for bucketName, valid := range cases {
1292-
err := tpgresource.CheckGCSName(bucketName)
1293-
if valid && err != nil {
1294-
t.Errorf("The bucket name %s was expected to pass validation and did not pass.", bucketName)
1295-
} else if !valid && err == nil {
1296-
t.Errorf("The bucket name %s was NOT expected to pass validation and passed.", bucketName)
1297-
}
1298-
}
1299-
}

google/verify/validation.go

+48
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ var (
7676
Rfc6996Asn32BitMin = int64(4200000000)
7777
Rfc6996Asn32BitMax = int64(4294967294)
7878
GcpRouterPartnerAsn = int64(16550)
79+
80+
// Format of GCS Bucket Name
81+
// https://cloud.google.com/storage/docs/naming-buckets
82+
GCSNameValidChars = "^[a-z0-9_.-]*$"
83+
GCSNameStartEndChars = "^[a-z|0-9].*[a-z|0-9]$"
84+
GCSNameLength = "^.{3,222}"
85+
GCSNameLengthSplit = "^.{1,63}$"
86+
GCSNameCidr = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$"
87+
GCSNameGoogPrefix = "^goog.*$"
88+
GCSNameContainsGoogle = "^.*google.*$"
7989
)
8090

8191
var Rfc1918Networks = []string{
@@ -91,6 +101,44 @@ func ValidateGCEName(v interface{}, k string) (ws []string, errors []error) {
91101
return ValidateRegexp(re)(v, k)
92102
}
93103

104+
// validateGCSName ensures the name of a gcs bucket matches the requirements for GCS Buckets
105+
// https://cloud.google.com/storage/docs/naming-buckets
106+
func ValidateGCSName(v interface{}, k string) (ws []string, errors []error) {
107+
value := v.(string)
108+
109+
if !regexp.MustCompile(GCSNameValidChars).MatchString(value) {
110+
errors = append(errors, fmt.Errorf("%q name value can only contain lowercase letters, numeric characters, dashes (-), underscores (_), and dots (.)", value))
111+
}
112+
113+
if !regexp.MustCompile(GCSNameStartEndChars).MatchString(value) {
114+
errors = append(errors, fmt.Errorf("%q name value must start and end with a number or letter", value))
115+
}
116+
117+
if !regexp.MustCompile(GCSNameLength).MatchString(value) {
118+
errors = append(errors, fmt.Errorf("%q name value must contain 3-63 characters. Names containing dots can contain up to 222 characters, but each dot-separated component can be no longer than 63 characters", value))
119+
}
120+
121+
for _, str := range strings.Split(value, ".") {
122+
if !regexp.MustCompile(GCSNameLengthSplit).MatchString(str) {
123+
errors = append(errors, fmt.Errorf("%q name value must contain 3-63 characters. Names containing dots can contain up to 222 characters, but each dot-separated component can be no longer than 63 characters", value))
124+
}
125+
}
126+
127+
if regexp.MustCompile(GCSNameCidr).MatchString(value) {
128+
errors = append(errors, fmt.Errorf("%q name value cannot be represented as an IP address in dotted-decimal notation (for example, 192.168.5.4)", value))
129+
}
130+
131+
if regexp.MustCompile(GCSNameGoogPrefix).MatchString(value) {
132+
errors = append(errors, fmt.Errorf("%q name value cannot begin with the \"goog\" prefix", value))
133+
}
134+
135+
if regexp.MustCompile(GCSNameContainsGoogle).MatchString(strings.ReplaceAll(value, "0", "o")) {
136+
errors = append(errors, fmt.Errorf("%q name value cannot contain \"google\" or close misspellings, such as \"g00gle\"", value))
137+
}
138+
139+
return
140+
}
141+
94142
// Ensure that the BGP ASN value of Cloud Router is a valid value as per RFC6996 or a value of 16550
95143
func ValidateRFC6996Asn(v interface{}, k string) (ws []string, errors []error) {
96144
value := int64(v.(int))

google/verify/validation_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,43 @@ func TestValidateIAMCustomRoleIDRegex(t *testing.T) {
319319
t.Errorf("Failed to validate IAMCustomRole IDs: %v", es)
320320
}
321321
}
322+
323+
func TestValidateGCSName(t *testing.T) {
324+
x := []StringValidationTestCase{
325+
// No errors
326+
{TestName: "basic", Value: "foobar"},
327+
{TestName: "has number", Value: "foobar1"},
328+
{TestName: "all numbers", Value: "12345"},
329+
{TestName: "all _", Value: "foo_bar_baz"},
330+
{TestName: "all -", Value: "foo-bar-baz"},
331+
{TestName: "begins with number", Value: "1foo-bar_baz"},
332+
{TestName: "ends with number", Value: "foo-bar_baz1"},
333+
{TestName: "almost an ip", Value: "192.168.5.foo"},
334+
{TestName: "has _", Value: "foo-bar_baz"},
335+
{TestName: "--", Value: "foo--bar"},
336+
{TestName: "__", Value: "foo__bar"},
337+
{TestName: "-goog", Value: "foo-goog"},
338+
{TestName: ".goog", Value: "foo.goog"},
339+
340+
// With errors
341+
{TestName: "invalid char $", Value: "foo$bar", ExpectError: true},
342+
{TestName: "has uppercase", Value: "fooBar", ExpectError: true},
343+
{TestName: "begins with -", Value: "-foobar", ExpectError: true},
344+
{TestName: "ends with -", Value: "foobar-", ExpectError: true},
345+
{TestName: "begins with _", Value: "_foobar", ExpectError: true},
346+
{TestName: "ends with _", Value: "foobar_", ExpectError: true},
347+
{TestName: "less than 3 chars", Value: "fo", ExpectError: true},
348+
{TestName: "..", Value: "foo..bar", ExpectError: true},
349+
{TestName: "greater than 63 chars with no .", Value: "my-really-long-bucket-name-with-invalid-that-does-not-contain-a-period", ExpectError: true},
350+
{TestName: "greater than 63 chars between .", Value: "my.really-long-bucket-name-with-invalid-that-does-contain-a-period-but.is-too-long", ExpectError: true},
351+
{TestName: "has goog prefix", Value: "goog-foobar", ExpectError: true},
352+
{TestName: "almost an ip", Value: "192.168.5.1", ExpectError: true},
353+
{TestName: "contains google", Value: "foobar-google", ExpectError: true},
354+
{TestName: "contains close misspelling of google", Value: "foo-go0gle-bar", ExpectError: true},
355+
}
356+
357+
es := TestStringValidationCases(x, ValidateGCSName)
358+
if len(es) > 0 {
359+
t.Errorf("Failed to validate GCS names: %v", es)
360+
}
361+
}

0 commit comments

Comments
 (0)