From bcaee6b4998478552acad97b9f0f8079f9518281 Mon Sep 17 00:00:00 2001 From: Nikita Zinkevich Date: Wed, 24 Jul 2024 14:23:17 +0300 Subject: [PATCH] [#369] Request reproducer Signed-off-by: Nikita Zinkevich --- cmd/s3-playback/config/playback.yaml | 5 + cmd/s3-playback/main.go | 20 ++++ cmd/s3-playback/modules/root.go | 49 ++++++++++ cmd/s3-playback/modules/run.go | 123 +++++++++++++++++++++++++ cmd/s3-playback/request/credentials.go | 6 ++ cmd/s3-playback/request/multipart.go | 52 +++++++++++ cmd/s3-playback/request/request.go | 104 +++++++++++++++++++++ go.mod | 19 ++-- go.sum | 47 +++++----- 9 files changed, 391 insertions(+), 34 deletions(-) create mode 100644 cmd/s3-playback/config/playback.yaml create mode 100644 cmd/s3-playback/main.go create mode 100644 cmd/s3-playback/modules/root.go create mode 100644 cmd/s3-playback/modules/run.go create mode 100644 cmd/s3-playback/request/credentials.go create mode 100644 cmd/s3-playback/request/multipart.go create mode 100644 cmd/s3-playback/request/request.go diff --git a/cmd/s3-playback/config/playback.yaml b/cmd/s3-playback/config/playback.yaml new file mode 100644 index 0000000..13034c0 --- /dev/null +++ b/cmd/s3-playback/config/playback.yaml @@ -0,0 +1,5 @@ +credentials: + access_key: CAtUxDSSFtuVyVCjHTMhwx3eP3YSPo5ffwbPcnKfcdrD06WwUSn72T5EBNe3jcgjL54rmxFM6u3nUAoNBps8qJ1PD + secret_key: 560027d81c277de7378f71cbf12a32e4f7f541de724be59bcfdbfdc925425f30 +endpoint: http://localhost:8084 +log: ./log/request.log \ No newline at end of file diff --git a/cmd/s3-playback/main.go b/cmd/s3-playback/main.go new file mode 100644 index 0000000..fc3f25b --- /dev/null +++ b/cmd/s3-playback/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/cmd/s3-playback/modules" +) + +func main() { + ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) + + if cmd, err := modules.Execute(ctx); err != nil { + cmd.PrintErrln("Error:", err.Error()) + cmd.PrintErrf("Run '%v --help' for usage.\n", cmd.CommandPath()) + os.Exit(1) + } +} diff --git a/cmd/s3-playback/modules/root.go b/cmd/s3-playback/modules/root.go new file mode 100644 index 0000000..575287c --- /dev/null +++ b/cmd/s3-playback/modules/root.go @@ -0,0 +1,49 @@ +package modules + +import ( + "context" + "strings" + + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const configFlag = "config" + +var rootCmd = &cobra.Command{ + Use: "frostfs-s3-playback", + Version: version.Version, + Short: "FrostFS S3 Traffic Playback", + Long: "Helps to reproduce s3 commands from log files", + Example: "frostfs-s3-playback --version", + SilenceErrors: true, + SilenceUsage: true, + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + viper.AutomaticEnv() + viper.SetEnvPrefix("PLAYBACK") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AllowEmptyEnv(true) + + if err := viper.BindPFlags(cmd.Flags()); err != nil { + return err + } + + viper.SetConfigFile(viper.GetString(configFlag)) + + return viper.ReadInConfig() + }, + RunE: func(cmd *cobra.Command, _ []string) error { + return cmd.Help() + }, +} + +func Execute(ctx context.Context) (*cobra.Command, error) { + return rootCmd.ExecuteContextC(ctx) +} + +func init() { + rootCmd.PersistentFlags().String(configFlag, "", "Configuration file") + _ = rootCmd.MarkFlagRequired(configFlag) + rootCmd.AddCommand(runCmd) +} diff --git a/cmd/s3-playback/modules/run.go b/cmd/s3-playback/modules/run.go new file mode 100644 index 0000000..305713a --- /dev/null +++ b/cmd/s3-playback/modules/run.go @@ -0,0 +1,123 @@ +package modules + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strconv" + + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/cmd/s3-playback/request" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + logPathFlag = "log" + endpointFlag = "endpoint" +) + +var runCmd = &cobra.Command{ + Use: "run", + Short: "Reads network log file and executes requests to specified URL", + SilenceUsage: true, + RunE: run, +} + +func init() { + runCmd.Flags().String(logPathFlag, "", "Log file path") + runCmd.Flags().String(endpointFlag, "", "Endpoint URL") +} + +var requestID = 1 + +func logResponse(method, url, status string, body []byte, values ...any) { + fmt.Println(strconv.Itoa(requestID)+")", method, url, status) + requestID++ + if len(body) > 0 { + fmt.Println(string(body)) + } + if len(values) > 0 { + fmt.Println(values...) + } + fmt.Println() +} + +func run(_ *cobra.Command, _ []string) error { + var logdata request.LoggedRequest + var creds = request.Credentials{ + AccessKey: viper.GetString("credentials.access_key"), + SecretKey: viper.GetString("credentials.secret_key"), + } + ctx := context.TODO() + ctx = context.WithValue(ctx, request.ContextKey("creds"), creds) + + info, err := os.Stat(viper.GetString(logPathFlag)) + if err != nil { + return fmt.Errorf("could not stat log file: %w", err) + } + bufSize := int(info.Size()) + buf := make([]byte, bufSize) + + logfile, err := os.Open(viper.GetString(logPathFlag)) + if err != nil { + return err + } + defer logfile.Close() + + sc := bufio.NewScanner(logfile) + sc.Buffer(buf, bufSize) + + for sc.Scan() { + if err = json.Unmarshal(sc.Bytes(), &logdata); err != nil { + fmt.Println("Error parsing log file:", err) + continue + } + playback(ctx, logdata) + } + + return nil +} + +// playback creates http.Request from LoggedRequest and sends it to specified endpoint. +func playback(ctx context.Context, logContent request.LoggedRequest) { + r, err := prepareRequest(ctx, logContent) + if err != nil { + logResponse(logContent.Method, logContent.URI, "", nil, "failed to prepare response:", err) + return + } + resp, err := http.DefaultClient.Do(r) + if err != nil { + logResponse(r.Method, r.URL.String(), "", nil, "failed to send request:", err) + return + } + + respBody, err := io.ReadAll(resp.Body) + if err == nil { + _ = request.RegisterMultipart(r, respBody, logContent.Response) + } + logResponse(r.Method, r.URL.String(), resp.Status, respBody) +} + +// prepareRequest creates request from logs and modifies its signature and uploadId (if presents). +func prepareRequest(ctx context.Context, data request.LoggedRequest) (*http.Request, error) { + var r *http.Request + var err error + + r, err = request.NewRequest(viper.GetString(endpointFlag), data) + if err != nil { + return nil, err + } + err = request.SwapUploadID(r) + if err != nil { + return nil, err + } + err = request.Sign(ctx, r) + if err != nil { + return nil, err + } + return r, nil +} diff --git a/cmd/s3-playback/request/credentials.go b/cmd/s3-playback/request/credentials.go new file mode 100644 index 0000000..0acb309 --- /dev/null +++ b/cmd/s3-playback/request/credentials.go @@ -0,0 +1,6 @@ +package request + +type Credentials struct { + AccessKey string + SecretKey string +} diff --git a/cmd/s3-playback/request/multipart.go b/cmd/s3-playback/request/multipart.go new file mode 100644 index 0000000..70e7bb8 --- /dev/null +++ b/cmd/s3-playback/request/multipart.go @@ -0,0 +1,52 @@ +package request + +import ( + "encoding/xml" + "errors" + "fmt" + "net/http" +) + +var ( + multiparts = make(map[string]MultipartUpload) +) + +type MultipartUpload struct { + XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ InitiateMultipartUploadResult" json:"-"` + Bucket string `json:"bucket" xml:"Bucket"` + Key string `json:"key" xml:"Key"` + UploadID string `json:"uploadId" xml:"UploadId"` +} + +func RegisterMultipart(r *http.Request, resp []byte, loggedResponse []byte) error { + var mpart, mpartOld MultipartUpload + if r.Method == "POST" && r.URL.Query().Has("uploads") { + err1 := xml.Unmarshal(resp, &mpart) + err2 := xml.Unmarshal(loggedResponse, &mpartOld) + if err1 != nil || err2 != nil { + return fmt.Errorf("xml unmarshal error") + } + if mpartOld.UploadID != "" { + multiparts[mpartOld.UploadID] = mpart + } + } + + return nil +} + +func SwapUploadID(r *http.Request) error { + var uploadID string + query := r.URL.Query() + + if query.Has("uploadId") { + uploadID = query.Get("uploadId") + mpart, ok := multiparts[uploadID] + if !ok { + return errors.New("multipart not found") + } + query.Set("uploadId", mpart.UploadID) + r.URL.RawQuery = query.Encode() + } + + return nil +} diff --git a/cmd/s3-playback/request/request.go b/cmd/s3-playback/request/request.go new file mode 100644 index 0000000..4c2a2d0 --- /dev/null +++ b/cmd/s3-playback/request/request.go @@ -0,0 +1,104 @@ +package request + +import ( + "bytes" + "context" + "errors" + "net/http" + "net/url" + "regexp" + "strings" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/aws/aws-sdk-go-v2/credentials" +) + +const ( + EmptyStringHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +) + +// authorizationFieldRegexp -- is regexp for credentials with Base58 encoded cid and oid and '0' (zero) as delimiter. +var authorizationFieldRegexp = regexp.MustCompile(`AWS4-HMAC-SHA256 Credential=(?P[^/]+)/(?P[^/]+)/(?P[^/]*)/(?P[^/]+)/aws4_request,\s*SignedHeaders=(?P.+),\s*Signature=(?P.+)`) + +type ( + LoggedRequest struct { + From string `json:"from"` + URI string `json:"URI"` + Method string `json:"method"` + Query url.Values `json:"query"` + Body []byte `json:"body"` + Header http.Header `json:"headers"` + Response []byte `json:"response"` + } + ContextKey string +) + +// NewRequest creates new http.Request from parsed data. +func NewRequest(endpoint string, req LoggedRequest) (*http.Request, error) { + r, err := http.NewRequest(req.Method, endpoint+req.URI, bytes.NewReader(req.Body)) + if err != nil { + return nil, err + } + r.Header = req.Header + + return r, nil +} + +// Sign replace Authorization header with new Access key id and Signature values. +func Sign(ctx context.Context, r *http.Request) error { + creds, ok := ctx.Value(ContextKey("creds")).(Credentials) + if !ok { + return errors.New("failed to get credentials") + } + credsProvider, err := credentials.NewStaticCredentialsProvider(creds.AccessKey, creds.SecretKey, "").Retrieve(ctx) + if err != nil { + return err + } + + authinfo, err := parseAuthHeader(r) + if err != nil { + return err + } + // replace old access key id with a new one + r.Header[auth.AuthorizationHdr][0] = strings.Replace(r.Header[auth.AuthorizationHdr][0], authinfo["access_key_id"], creds.AccessKey, 1) + + signer := v4.NewSigner() + signatureDateTimeStr := r.Header.Get(api.AmzDate) + signatureDateTime, err := time.Parse("20060102T150405Z", signatureDateTimeStr) + if err != nil { + return err + } + + err = signer.SignHTTP(ctx, credsProvider, r, r.Header.Get(api.AmzContentSha256), authinfo["service"], authinfo["region"], signatureDateTime) + if err != nil { + return err + } + + return nil +} + +func parseAuthHeader(r *http.Request) (map[string]string, error) { + matcher := auth.NewRegexpMatcher(authorizationFieldRegexp) + authHeader := r.Header[auth.AuthorizationHdr] + if len(authHeader) != 1 { + return nil, errors.New("invalid authorization header") + } + authinfo := matcher.GetSubmatches(authHeader[0]) + return authinfo, nil +} + +/*func getHexSHA256(r *http.Request) (string, error) { + body, err := io.ReadAll(r.Body) + if err != nil { + return "", err + } + defer r.Body.Close() + r.Body = io.NopCloser(bytes.NewBuffer(body)) + sh := sha256.New() + sh.Write(body) + + return hex.EncodeToString(sh.Sum(nil)), nil +}*/ diff --git a/go.mod b/go.mod index 84b4588..d99668b 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,9 @@ require ( git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240705093617-560cbbd1f1e4 git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240611102930-ac965e8d176a git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02 - github.com/aws/aws-sdk-go v1.44.6 - github.com/aws/aws-sdk-go-v2 v1.18.1 + github.com/aws/aws-sdk-go v1.54.20 + github.com/aws/aws-sdk-go-v2 v1.30.3 + github.com/aws/aws-sdk-go-v2/credentials v1.17.27 github.com/bluele/gcache v0.0.2 github.com/go-chi/chi/v5 v5.0.8 github.com/google/uuid v1.3.1 @@ -28,10 +29,10 @@ require ( go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/trace v1.16.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.21.0 + golang.org/x/crypto v0.25.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 - golang.org/x/net v0.23.0 - golang.org/x/text v0.14.0 + golang.org/x/net v0.27.0 + golang.org/x/text v0.16.0 google.golang.org/grpc v1.59.0 google.golang.org/protobuf v1.33.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 @@ -43,7 +44,7 @@ require ( git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/aws/smithy-go v1.13.5 // indirect + github.com/aws/smithy-go v1.20.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -92,9 +93,9 @@ require ( go.opentelemetry.io/otel/sdk v1.16.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect diff --git a/go.sum b/go.sum index d79d47b..9b076e6 100644 --- a/go.sum +++ b/go.sum @@ -62,12 +62,14 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/aws/aws-sdk-go v1.44.6 h1:Y+uHxmZfhRTLX2X3khkdxCoTZAyGEX21aOUHe1U6geg= -github.com/aws/aws-sdk-go v1.44.6/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo= -github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go v1.54.20 h1:FZ2UcXya7bUkvkpf7TaPmiL7EubK0go1nlXGLRwEsoo= +github.com/aws/aws-sdk-go v1.54.20/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= @@ -172,7 +174,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -366,8 +367,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -403,7 +404,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -439,9 +440,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -463,8 +463,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -507,15 +507,13 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -523,9 +521,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -576,7 +573,7 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=