github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/asset/backend_gcs.go (about) 1 // Copyright 2022 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package asset 5 6 import ( 7 "fmt" 8 "io" 9 "net/url" 10 "regexp" 11 "strings" 12 13 "github.com/google/syzkaller/pkg/debugtracer" 14 "github.com/google/syzkaller/pkg/gcs" 15 ) 16 17 type cloudStorageBackend struct { 18 client *gcs.Client 19 bucket string 20 tracer debugtracer.DebugTracer 21 } 22 23 func makeCloudStorageBackend(bucket string, tracer debugtracer.DebugTracer) (*cloudStorageBackend, error) { 24 tracer.Log("created gcs backend for bucket '%s'", bucket) 25 client, err := gcs.NewClient() 26 if err != nil { 27 return nil, fmt.Errorf("the call to NewClient failed: %w", err) 28 } 29 return &cloudStorageBackend{ 30 client: client, 31 bucket: bucket, 32 tracer: tracer, 33 }, nil 34 } 35 36 // Actual write errors might be hidden, so we wrap the writer here 37 // to ensure that they get logged. 38 type writeErrorLogger struct { 39 writeCloser io.WriteCloser 40 tracer debugtracer.DebugTracer 41 } 42 43 func (wel *writeErrorLogger) Write(p []byte) (n int, err error) { 44 n, err = wel.writeCloser.Write(p) 45 if err != nil { 46 wel.tracer.Log("cloud storage write error: %s", err) 47 } 48 return 49 } 50 51 func (wel *writeErrorLogger) Close() error { 52 err := wel.writeCloser.Close() 53 if err != nil { 54 wel.tracer.Log("cloud storage writer close error: %s", err) 55 } 56 return err 57 } 58 59 func (csb *cloudStorageBackend) upload(req *uploadRequest) (*uploadResponse, error) { 60 path := fmt.Sprintf("%s/%s", csb.bucket, req.savePath) 61 // Best-effort check only. In the worst case we'll just overwite the file. 62 // The alternative would be to add an If-precondition, but it'd require 63 // complicated error-during-write handling. 64 exists, err := csb.client.FileExists(path) 65 if err != nil { 66 return nil, err 67 } 68 if exists { 69 return nil, &FileExistsError{req.savePath} 70 } 71 w, err := csb.client.FileWriterExt(path, req.contentType, req.contentEncoding) 72 csb.tracer.Log("gcs upload: obtained a writer for %s, error %s", path, err) 73 if err != nil { 74 return nil, err 75 } 76 return &uploadResponse{ 77 writer: &writeErrorLogger{ 78 writeCloser: w, 79 tracer: csb.tracer, 80 }, 81 path: req.savePath, 82 }, nil 83 } 84 85 func (csb *cloudStorageBackend) downloadURL(path string, publicURL bool) (string, error) { 86 return csb.client.GetDownloadURL(fmt.Sprintf("%s/%s", csb.bucket, path), publicURL), nil 87 } 88 89 var allowedDomainsRe = regexp.MustCompile(`^storage\.googleapis\.com|storage\.cloud\.google\.com$`) 90 91 func (csb *cloudStorageBackend) getPath(downloadURL string) (string, error) { 92 u, err := url.Parse(downloadURL) 93 if err != nil { 94 return "", fmt.Errorf("failed to parse the URL: %w", err) 95 } 96 if !allowedDomainsRe.MatchString(u.Host) { 97 return "", fmt.Errorf("not allowed host: %s", u.Host) 98 } 99 prefix := "/" + csb.bucket + "/" 100 if !strings.HasPrefix(u.Path, prefix) { 101 return "", ErrUnknownBucket 102 } 103 return u.Path[len(prefix):], nil 104 } 105 106 func (csb *cloudStorageBackend) list() ([]storedObject, error) { 107 list, err := csb.client.ListObjects(csb.bucket) 108 if err != nil { 109 return nil, err 110 } 111 ret := []storedObject{} 112 for _, obj := range list { 113 ret = append(ret, storedObject{ 114 path: obj.Path, 115 createdAt: obj.CreatedAt, 116 }) 117 } 118 return ret, nil 119 } 120 121 func (csb *cloudStorageBackend) remove(path string) error { 122 path = fmt.Sprintf("%s/%s", csb.bucket, path) 123 err := csb.client.DeleteFile(path) 124 if err == gcs.ErrFileNotFound { 125 return ErrAssetDoesNotExist 126 } 127 return err 128 }