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  }