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  }