From 1c3b6b1cb6bf497a7e71d2611e772555480fea4e Mon Sep 17 00:00:00 2001 From: bhagya05 Date: Tue, 9 Apr 2024 13:20:57 +0530 Subject: [PATCH 1/3] add ability to generate sign url in gcp bucket Signed-off-by: bhagya05 --- bindings/gcp/bucket/bucket.go | 68 +++++++++++++++++++++++++++++- bindings/gcp/bucket/bucket_test.go | 4 +- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/bindings/gcp/bucket/bucket.go b/bindings/gcp/bucket/bucket.go index 5fc9e7dec0..df08fb3828 100644 --- a/bindings/gcp/bucket/bucket.go +++ b/bindings/gcp/bucket/bucket.go @@ -25,6 +25,7 @@ import ( "net/url" "reflect" "strconv" + "time" "cloud.google.com/go/storage" "github.com/google/uuid" @@ -43,11 +44,13 @@ const ( objectURLBase = "https://storage.googleapis.com/%s/%s" metadataDecodeBase64 = "decodeBase64" metadataEncodeBase64 = "encodeBase64" + metadataSignTTL = "signTTL" metadataKey = "key" maxResults = 1000 metadataKeyBC = "name" + signOperation = "sign" ) // GCPStorage allows saving data to GCP bucket storage. @@ -73,6 +76,7 @@ type gcpMetadata struct { Bucket string `json:"bucket" mapstructure:"bucket"` DecodeBase64 bool `json:"decodeBase64,string" mapstructure:"decodeBase64"` EncodeBase64 bool `json:"encodeBase64,string" mapstructure:"encodeBase64"` + SignTTL string `json:"signTTL" mapstructure:"signTTL" mdignore:"true"` } type listPayload struct { @@ -80,6 +84,9 @@ type listPayload struct { MaxResults int32 `json:"maxResults"` Delimiter string `json:"delimiter"` } +type signResponse struct { + SignURL string `json:"signURL"` +} type createResponse struct { ObjectURL string `json:"objectURL"` @@ -130,6 +137,7 @@ func (g *GCPStorage) Operations() []bindings.OperationKind { bindings.GetOperation, bindings.DeleteOperation, bindings.ListOperation, + signOperation, } } @@ -145,6 +153,8 @@ func (g *GCPStorage) Invoke(ctx context.Context, req *bindings.InvokeRequest) (* return g.delete(ctx, req) case bindings.ListOperation: return g.list(ctx, req) + case signOperation: + return g.sign(ctx, req) default: return nil, fmt.Errorf("unsupported operation %s", req.Operation) } @@ -312,7 +322,9 @@ func (metadata gcpMetadata) mergeWithRequestMetadata(req *bindings.InvokeRequest if val, ok := req.Metadata[metadataEncodeBase64]; ok && val != "" { merged.EncodeBase64 = utils.IsTruthy(val) } - + if val, ok := req.Metadata[metadataSignTTL]; ok && val != "" { + merged.SignTTL = val + } return merged, nil } @@ -332,3 +344,57 @@ func (g *GCPStorage) GetComponentMetadata() (metadataInfo metadata.MetadataMap) metadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo, metadata.BindingType) return } + +func (g *GCPStorage) sign(ctx context.Context, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) { + metadata, err := g.metadata.mergeWithRequestMetadata(req) + if err != nil { + return nil, fmt.Errorf("gcp binding error. error merge metadata : %w", err) + } + + var key string + if val, ok := req.Metadata[metadataKey]; ok && val != "" { + key = val + } else { + return nil, fmt.Errorf("gcp bucket binding error: can't read key value") + } + + if metadata.SignTTL == "" { + return nil, fmt.Errorf("gcp bucket binding error: required metadata '%s' missing", metadataSignTTL) + } + + signURL, err := g.signObject(metadata.Bucket, key, metadata.SignTTL) + if err != nil { + return nil, fmt.Errorf("gcp bucket binding error: %w", err) + + } + + jsonResponse, err := json.Marshal(signResponse{ + SignURL: signURL, + }) + if err != nil { + return nil, fmt.Errorf("gcp bucket binding error: error marshalling sign response: %w", err) + } + return &bindings.InvokeResponse{ + Data: jsonResponse, + }, nil + +} + +func (g *GCPStorage) signObject(bucket, object, ttl string) (string, error) { + + d, err := time.ParseDuration(ttl) + if err != nil { + return "", fmt.Errorf("gcp bucket binding error: error parsing signTTL: %w", err) + } + opts := &storage.SignedURLOptions{ + Scheme: storage.SigningSchemeV4, + Method: "GET", + Expires: time.Now().Add(d), + } + + u, err := g.client.Bucket(g.metadata.Bucket).SignedURL(object, opts) + if err != nil { + return "", fmt.Errorf("Bucket(%q).SignedURL: %w", bucket, err) + } + return u, nil +} diff --git a/bindings/gcp/bucket/bucket_test.go b/bindings/gcp/bucket/bucket_test.go index 20fb87a9a2..ef102f42d0 100644 --- a/bindings/gcp/bucket/bucket_test.go +++ b/bindings/gcp/bucket/bucket_test.go @@ -40,6 +40,7 @@ func TestParseMetadata(t *testing.T) { "projectID": "my_project_id", "tokenURI": "my_token_uri", "type": "my_type", + "signTTL": "15s", } gs := GCPStorage{logger: logger.NewLogger("test")} meta, err := gs.parseMetadata(m) @@ -57,6 +58,7 @@ func TestParseMetadata(t *testing.T) { assert.Equal(t, "my_project_id", meta.ProjectID) assert.Equal(t, "my_token_uri", meta.TokenURI) assert.Equal(t, "my_type", meta.Type) + assert.Equal(t,"15s",meta.SignTTL) }) t.Run("Metadata is correctly marshalled to JSON", func(t *testing.T) { @@ -67,7 +69,7 @@ func TestParseMetadata(t *testing.T) { "\"private_key\":\"my_private_key\",\"client_email\":\"my_email@mail.dapr\",\"client_id\":\"my_client_id\","+ "\"auth_uri\":\"my_auth_uri\",\"token_uri\":\"my_token_uri\",\"auth_provider_x509_cert_url\":\"my_auth_provider_x509\","+ "\"client_x509_cert_url\":\"my_client_x509\",\"bucket\":\"my_bucket\",\"decodeBase64\":\"false\","+ - "\"encodeBase64\":\"false\"}", string(json)) + "\"encodeBase64\":\"false\",\"signTTL\":\"15s\"}", string(json)) }) }) From b1e94341fe325e039770d68835d00b248d07aca6 Mon Sep 17 00:00:00 2001 From: bhagya05 Date: Tue, 9 Apr 2024 20:07:15 +0530 Subject: [PATCH 2/3] fix linting Signed-off-by: bhagya05 --- bindings/gcp/bucket/bucket.go | 3 --- bindings/gcp/bucket/bucket_test.go | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bindings/gcp/bucket/bucket.go b/bindings/gcp/bucket/bucket.go index df08fb3828..7fa929ae40 100644 --- a/bindings/gcp/bucket/bucket.go +++ b/bindings/gcp/bucket/bucket.go @@ -365,7 +365,6 @@ func (g *GCPStorage) sign(ctx context.Context, req *bindings.InvokeRequest) (*bi signURL, err := g.signObject(metadata.Bucket, key, metadata.SignTTL) if err != nil { return nil, fmt.Errorf("gcp bucket binding error: %w", err) - } jsonResponse, err := json.Marshal(signResponse{ @@ -377,11 +376,9 @@ func (g *GCPStorage) sign(ctx context.Context, req *bindings.InvokeRequest) (*bi return &bindings.InvokeResponse{ Data: jsonResponse, }, nil - } func (g *GCPStorage) signObject(bucket, object, ttl string) (string, error) { - d, err := time.ParseDuration(ttl) if err != nil { return "", fmt.Errorf("gcp bucket binding error: error parsing signTTL: %w", err) diff --git a/bindings/gcp/bucket/bucket_test.go b/bindings/gcp/bucket/bucket_test.go index ef102f42d0..6922050acb 100644 --- a/bindings/gcp/bucket/bucket_test.go +++ b/bindings/gcp/bucket/bucket_test.go @@ -40,7 +40,7 @@ func TestParseMetadata(t *testing.T) { "projectID": "my_project_id", "tokenURI": "my_token_uri", "type": "my_type", - "signTTL": "15s", + "signTTL": "15s", } gs := GCPStorage{logger: logger.NewLogger("test")} meta, err := gs.parseMetadata(m) @@ -58,7 +58,7 @@ func TestParseMetadata(t *testing.T) { assert.Equal(t, "my_project_id", meta.ProjectID) assert.Equal(t, "my_token_uri", meta.TokenURI) assert.Equal(t, "my_type", meta.Type) - assert.Equal(t,"15s",meta.SignTTL) + assert.Equal(t, "15s", meta.SignTTL) }) t.Run("Metadata is correctly marshalled to JSON", func(t *testing.T) { From 1f7acbcd99f8b0e8ca494dee3ba1d272bc56755d Mon Sep 17 00:00:00 2001 From: bhagya05 Date: Sat, 18 May 2024 18:19:13 +0530 Subject: [PATCH 3/3] add signTTL in metadata.yaml Signed-off-by: bhagya05 --- bindings/gcp/bucket/metadata.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bindings/gcp/bucket/metadata.yaml b/bindings/gcp/bucket/metadata.yaml index e45a072a21..6e3448744e 100644 --- a/bindings/gcp/bucket/metadata.yaml +++ b/bindings/gcp/bucket/metadata.yaml @@ -23,6 +23,12 @@ metadata: The bucket name. example: '"mybucket"' type: string + - name: signTTL + required: false + description: | + Specifies the duration that the signed URL should be valid. + example: '"15m, 1h"' + type: string - name: decodeBase64 type: bool required: false