Skip to content

Commit bd047d1

Browse files
feat: (storage) update signed_url to virtual style (#15832)
1 parent 8d105ed commit bd047d1

File tree

3 files changed

+99
-18
lines changed

3 files changed

+99
-18
lines changed

mmv1/third_party/terraform/services/storage/data_source_storage_object_signed_url.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,12 @@ func dataSourceGoogleSignedUrlRead(d *schema.ResourceData, meta interface{}) err
141141
}
142142
}
143143

144-
urlData.Path = fmt.Sprintf("/%s/%s", d.Get("bucket").(string), d.Get("path").(string))
144+
bucketName := d.Get("bucket").(string)
145+
objectPath := d.Get("path").(string)
146+
baseUrl := getGcsHostUrl(urlData, bucketName, objectPath)
147+
148+
// sign path should be same in both cases as we are using v2 signature
149+
urlData.SignPath = fmt.Sprintf("/%s/%s", bucketName, objectPath)
145150

146151
// Load JWT Config from Google Credentials
147152
jwtConfig, err := loadJwtConfig(d, config)
@@ -151,7 +156,7 @@ func dataSourceGoogleSignedUrlRead(d *schema.ResourceData, meta interface{}) err
151156
urlData.JwtConfig = jwtConfig
152157

153158
// Construct URL
154-
signedUrl, err := urlData.SignedUrl()
159+
signedUrl, err := urlData.SignedUrl(baseUrl)
155160
if err != nil {
156161
return err
157162
}
@@ -208,6 +213,25 @@ func loadJwtConfig(d *schema.ResourceData, meta interface{}) (*jwt.Config, error
208213
return nil, errors.New("Credentials not found in datasource, provider configuration or GOOGLE_APPLICATION_CREDENTIALS environment variable.")
209214
}
210215

216+
func getGcsHostUrl(urlData *UrlData, bucketName, objectPath string) string {
217+
var baseUrl string
218+
if strings.Contains(bucketName, ".") {
219+
// Use path-style URL as "." in the bucket name create invalid virtual hostnames
220+
// Signed URL format https://storage.googleapis.com/tf-test-bucket-6159205297736845881/path/to/object
221+
// Path format is bucket_name/object_path
222+
urlData.Path = fmt.Sprintf("/%s/%s", bucketName, objectPath)
223+
baseUrl = gcsBaseUrl
224+
} else {
225+
// default to always virtual style URL
226+
// URL format https://tf-test-bucket-6159205297736845881.storage.googleapis.com//path/to/object
227+
// Path format is object_path
228+
urlData.Path = fmt.Sprintf("/%s", objectPath)
229+
gcsUrl := strings.Split(gcsBaseUrl, "://")
230+
baseUrl = fmt.Sprintf("%s://%s.%s", gcsUrl[0], bucketName, gcsUrl[1])
231+
}
232+
return baseUrl
233+
}
234+
211235
// parsePrivateKey converts the binary contents of a private key file
212236
// to an *rsa.PrivateKey. It detects whether the private key is in a
213237
// PEM container or not. If so, it extracts the the private key
@@ -241,7 +265,9 @@ type UrlData struct {
241265
HttpMethod string
242266
Expires int
243267
HttpHeaders map[string]string
244-
Path string
268+
SignPath string
269+
// Internally used field derived for virtual-host or path-style.
270+
Path string
245271
}
246272

247273
// SigningString creates a string representation of the UrlData in a form ready for signing:
@@ -285,7 +311,7 @@ func (u *UrlData) SigningString() []byte {
285311
}
286312

287313
// Storage Object path (includes bucketname)
288-
buf.WriteString(u.Path)
314+
buf.WriteString(u.SignPath)
289315

290316
return buf.Bytes()
291317
}
@@ -317,7 +343,7 @@ func (u *UrlData) EncodedSignature() (string, error) {
317343
}
318344

319345
// SignedUrl constructs the final signed URL a client can use to retrieve storage object
320-
func (u *UrlData) SignedUrl() (string, error) {
346+
func (u *UrlData) SignedUrl(baseUrl string) (string, error) {
321347

322348
encodedSig, err := u.EncodedSignature()
323349
if err != nil {
@@ -327,7 +353,7 @@ func (u *UrlData) SignedUrl() (string, error) {
327353
// build url
328354
// https://cloud.google.com/storage/docs/access-control/create-signed-urls-program
329355
var urlBuffer bytes.Buffer
330-
urlBuffer.WriteString(gcsBaseUrl)
356+
urlBuffer.WriteString(baseUrl)
331357
urlBuffer.WriteString(u.Path)
332358
urlBuffer.WriteString("?GoogleAccessId=")
333359
urlBuffer.WriteString(u.JwtConfig.Email)

mmv1/third_party/terraform/services/storage/data_source_storage_object_signed_url_internal_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@ const fakeCredentials = `{
3030
// URL HTTP Method Expiration Signed URL
3131
// gs://tf-test-bucket-6159205297736845881/path/to/file GET 2016-08-12 14:03:30 https://storage.googleapis.com/tf-test-bucket-6159205297736845881/path/to/[email protected]&Expires=1470967410&Signature=JJvE2Jc%2BeoagyS1qRACKBGUkgLkKjw7cGymHhtB4IzzN3nbXDqr0acRWGy0%2BEpZ3HYNDalEYsK0lR9Q0WCgty5I0JKmPIuo9hOYa1xTNH%2B22xiWsekxGV%2FcA9FXgWpi%2BFt7fBmMk4dhDe%2BuuYc7N79hd0FYuSBNW1Wp32Bluoe4SNkNAB%2BuIDd9KqPzqs09UAbBoz2y4WxXOQnRyR8GAfb8B%2FDtv62gYjtmp%2F6%2Fyr6xj7byWKZdQt8kEftQLTQmP%2F17Efjp6p%2BXo71Q0F9IhAFiqWfp3Ij8hHDSebLcVb2ULXyHNNQpHBOhFgALrFW3I6Uc3WciLEOsBS9Ej3EGdTg%3D%3D
3232

33-
const testUrlPath = "/tf-test-bucket-6159205297736845881/path/to/file"
33+
const testUrlPath = "path/to/file"
34+
const testSignUrlPath = "/tf-test-bucket-6159205297736845881/path/to/file"
3435
const testUrlExpires = 1470967410
36+
const testBucketName = "tf-test-bucket-6159205297736845881"
3537
const testUrlExpectedSignatureBase64Encoded = "JJvE2Jc%2BeoagyS1qRACKBGUkgLkKjw7cGymHhtB4IzzN3nbXDqr0acRWGy0%2BEpZ3HYNDalEYsK0lR9Q0WCgty5I0JKmPIuo9hOYa1xTNH%2B22xiWsekxGV%2FcA9FXgWpi%2BFt7fBmMk4dhDe%2BuuYc7N79hd0FYuSBNW1Wp32Bluoe4SNkNAB%2BuIDd9KqPzqs09UAbBoz2y4WxXOQnRyR8GAfb8B%2FDtv62gYjtmp%2F6%2Fyr6xj7byWKZdQt8kEftQLTQmP%2F17Efjp6p%2BXo71Q0F9IhAFiqWfp3Ij8hHDSebLcVb2ULXyHNNQpHBOhFgALrFW3I6Uc3WciLEOsBS9Ej3EGdTg%3D%3D"
36-
const testUrlExpectedUrl = "https://storage.googleapis.com/tf-test-bucket-6159205297736845881/path/to/[email protected]&Expires=1470967410&Signature=JJvE2Jc%2BeoagyS1qRACKBGUkgLkKjw7cGymHhtB4IzzN3nbXDqr0acRWGy0%2BEpZ3HYNDalEYsK0lR9Q0WCgty5I0JKmPIuo9hOYa1xTNH%2B22xiWsekxGV%2FcA9FXgWpi%2BFt7fBmMk4dhDe%2BuuYc7N79hd0FYuSBNW1Wp32Bluoe4SNkNAB%2BuIDd9KqPzqs09UAbBoz2y4WxXOQnRyR8GAfb8B%2FDtv62gYjtmp%2F6%2Fyr6xj7byWKZdQt8kEftQLTQmP%2F17Efjp6p%2BXo71Q0F9IhAFiqWfp3Ij8hHDSebLcVb2ULXyHNNQpHBOhFgALrFW3I6Uc3WciLEOsBS9Ej3EGdTg%3D%3D"
38+
const testUrlExpectedUrl = "https://tf-test-bucket-6159205297736845881.storage.googleapis.com/path/to/[email protected]&Expires=1470967410&Signature=JJvE2Jc%2BeoagyS1qRACKBGUkgLkKjw7cGymHhtB4IzzN3nbXDqr0acRWGy0%2BEpZ3HYNDalEYsK0lR9Q0WCgty5I0JKmPIuo9hOYa1xTNH%2B22xiWsekxGV%2FcA9FXgWpi%2BFt7fBmMk4dhDe%2BuuYc7N79hd0FYuSBNW1Wp32Bluoe4SNkNAB%2BuIDd9KqPzqs09UAbBoz2y4WxXOQnRyR8GAfb8B%2FDtv62gYjtmp%2F6%2Fyr6xj7byWKZdQt8kEftQLTQmP%2F17Efjp6p%2BXo71Q0F9IhAFiqWfp3Ij8hHDSebLcVb2ULXyHNNQpHBOhFgALrFW3I6Uc3WciLEOsBS9Ej3EGdTg%3D%3D"
3739

3840
func TestUrlData_Signing(t *testing.T) {
3941
urlData := &UrlData{
4042
HttpMethod: "GET",
4143
Expires: testUrlExpires,
4244
Path: testUrlPath,
45+
SignPath: testSignUrlPath,
4346
}
4447
// unescape and decode the expected signature
4548
expectedSig, err := url.QueryUnescape(testUrlExpectedSignatureBase64Encoded)
@@ -83,8 +86,10 @@ func TestUrlData_SignedUrl(t *testing.T) {
8386
Expires: testUrlExpires,
8487
Path: testUrlPath,
8588
JwtConfig: cfg,
89+
SignPath: testSignUrlPath,
8690
}
87-
result, err := urlData.SignedUrl()
91+
baseUrl := getGcsHostUrl(urlData, testBucketName, testUrlPath)
92+
result, err := urlData.SignedUrl(baseUrl)
8893
if err != nil {
8994
t.Errorf("Could not generated signed url: %+v", err)
9095
}

mmv1/third_party/terraform/services/storage/data_source_storage_object_signed_url_test.go

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,49 @@ import (
66
"fmt"
77
"io/ioutil"
88
"net/http"
9+
"strings"
910

1011
"github.com/hashicorp/go-cleanhttp"
1112
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
1213
"github.com/hashicorp/terraform-plugin-testing/terraform"
1314
"github.com/hashicorp/terraform-provider-google/google/acctest"
1415
)
1516

16-
func TestAccStorageSignedUrl_basic(t *testing.T) {
17+
const objectPath = "object/objname"
18+
const stoargeApiHost = "storage.googleapis.com"
19+
20+
func TestAccStorageSignedUrl_basicVirtualStyle(t *testing.T) {
1721
t.Parallel()
1822

23+
bucketName := acctest.TestBucketName(t)
24+
1925
acctest.VcrTest(t, resource.TestCase{
2026
PreCheck: func() { acctest.AccTestPreCheck(t) },
2127
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
2228
Steps: []resource.TestStep{
2329
{
24-
Config: testGoogleSignedUrlConfig,
30+
Config: testGoogleSignedUrlConfig(bucketName),
2531
Check: resource.ComposeTestCheckFunc(
26-
testAccSignedUrlExists(t, "data.google_storage_object_signed_url.blerg"),
32+
testAccSignedUrlVirtualStyleExists(t, "data.google_storage_object_signed_url.blerg", bucketName),
33+
),
34+
},
35+
},
36+
})
37+
}
38+
39+
func TestAccStorageSignedUrl_basicPathStyle(t *testing.T) {
40+
t.Parallel()
41+
42+
bucketName := acctest.TestBucketName(t) + ".com"
43+
44+
acctest.VcrTest(t, resource.TestCase{
45+
PreCheck: func() { acctest.AccTestPreCheck(t) },
46+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
47+
Steps: []resource.TestStep{
48+
{
49+
Config: testGoogleSignedUrlConfig(bucketName),
50+
Check: resource.ComposeTestCheckFunc(
51+
testAccSignedUrlPathStyleExists(t, "data.google_storage_object_signed_url.blerg", bucketName),
2752
),
2853
},
2954
},
@@ -59,7 +84,7 @@ func TestAccStorageSignedUrl_accTest(t *testing.T) {
5984
})
6085
}
6186

62-
func testAccSignedUrlExists(t *testing.T, n string) resource.TestCheckFunc {
87+
func testAccSignedUrlVirtualStyleExists(t *testing.T, n, bucketName string) resource.TestCheckFunc {
6388
return func(s *terraform.State) error {
6489

6590
r := s.RootModule().Resources[n]
@@ -69,6 +94,30 @@ func testAccSignedUrlExists(t *testing.T, n string) resource.TestCheckFunc {
6994
return fmt.Errorf("signed_url is empty: %v", a)
7095
}
7196

97+
splitUrl := strings.Split(a["signed_url"], "/")
98+
if splitUrl[2] != fmt.Sprintf("%s.%s", bucketName, stoargeApiHost) {
99+
return fmt.Errorf("invalid virtual style URL")
100+
}
101+
102+
return nil
103+
}
104+
}
105+
106+
func testAccSignedUrlPathStyleExists(t *testing.T, n, bucketName string) resource.TestCheckFunc {
107+
return func(s *terraform.State) error {
108+
109+
r := s.RootModule().Resources[n]
110+
a := r.Primary.Attributes
111+
112+
if a["signed_url"] == "" {
113+
return fmt.Errorf("signed_url is empty: %v", a)
114+
}
115+
116+
urlPrefix := fmt.Sprintf("%s://%s/%s/%s", "https", stoargeApiHost, bucketName, objectPath)
117+
if !strings.HasPrefix(a["signed_url"], urlPrefix) {
118+
return fmt.Errorf("invalid path style URL")
119+
}
120+
72121
return nil
73122
}
74123
}
@@ -131,13 +180,14 @@ func testAccSignedUrlRetrieval(n string, headers map[string]string) resource.Tes
131180
}
132181
}
133182

134-
const testGoogleSignedUrlConfig = `
183+
func testGoogleSignedUrlConfig(bucket string) string {
184+
return fmt.Sprintf(`
135185
data "google_storage_object_signed_url" "blerg" {
136-
bucket = "friedchicken"
137-
path = "path/to/file"
138-
186+
bucket = "%s"
187+
path = "%s"
188+
}
189+
`, bucket, objectPath)
139190
}
140-
`
141191

142192
func testAccTestGoogleStorageObjectSignedURL(bucketName string) string {
143193
return fmt.Sprintf(`

0 commit comments

Comments
 (0)