Skip to content

Commit

Permalink
fix(storage): retry SignBlob call for URL signing (#11154)
Browse files Browse the repository at this point in the history
* fix(storage): retry SignBlob call for URL signing

Adds a retry to the SignBlob call made by the default SignBytes
function from BucketHandle.SignedURL().

This is an idempotent call so fully safe to retry.

Signed URL integration tests pass locally.

* fmt

* add test with mock transport
  • Loading branch information
tritone authored Nov 20, 2024
1 parent a75c8b0 commit f198452
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 5 deletions.
13 changes: 8 additions & 5 deletions storage/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,14 @@ func (b *BucketHandle) defaultSignBytesFunc(email string) func([]byte) ([]byte,
if err != nil {
return nil, fmt.Errorf("unable to create iamcredentials client: %w", err)
}

resp, err := svc.Projects.ServiceAccounts.SignBlob(fmt.Sprintf("projects/-/serviceAccounts/%s", email), &iamcredentials.SignBlobRequest{
Payload: base64.StdEncoding.EncodeToString(in),
}).Do()
if err != nil {
// Do the SignBlob call with a retry for transient errors.
var resp *iamcredentials.SignBlobResponse
if err := run(ctx, func(ctx context.Context) error {
resp, err = svc.Projects.ServiceAccounts.SignBlob(fmt.Sprintf("projects/-/serviceAccounts/%s", email), &iamcredentials.SignBlobRequest{
Payload: base64.StdEncoding.EncodeToString(in),
}).Do()
return err
}, b.retry, true); err != nil {
return nil, fmt.Errorf("unable to sign bytes: %w", err)
}
out, err := base64.StdEncoding.DecodeString(resp.SignedBlob)
Expand Down
28 changes: 28 additions & 0 deletions storage/bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package storage
import (
"context"
"fmt"
"net/http"
"testing"
"time"

Expand Down Expand Up @@ -1640,3 +1641,30 @@ func TestBucketSignedURL_Endpoint_Emulator_Host(t *testing.T) {
})
}
}

// Test retry logic for default SignBlob function used by BucketHandle.SignedURL.
// This cannot be tested via the emulator so we use a mock.
func TestDefaultSignBlobRetry(t *testing.T) {
ctx := context.Background()

// Use mock transport. Return 2 503 responses before succeeding.
mt := mockTransport{}
mt.addResult(&http.Response{StatusCode: 503, Body: bodyReader("")}, nil)
mt.addResult(&http.Response{StatusCode: 503, Body: bodyReader("")}, nil)
mt.addResult(&http.Response{StatusCode: 200, Body: bodyReader("{}")}, nil)

client, err := NewClient(ctx, option.WithHTTPClient(&http.Client{Transport: &mt}))
if err != nil {
t.Fatalf("NewClient: %v", err)
}

b := client.Bucket("fakebucket")

if _, err := b.SignedURL("fakeobj", &SignedURLOptions{
Method: "GET",
Expires: time.Now().Add(time.Hour),
SignBytes: b.defaultSignBytesFunc("example@example.com"),
}); err != nil {
t.Fatalf("BucketHandle.SignedURL: %v", err)
}
}

0 comments on commit f198452

Please sign in to comment.