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