forked from TrueCloudLab/frostfs-s3-gw
Compare commits
13 commits
Author | SHA1 | Date | |
---|---|---|---|
da9703ab63 | |||
a53e50b324 | |||
5e9c562683 | |||
49bf3c1bce | |||
a4d9658fbb | |||
bec63026bd | |||
0064e7ab07 | |||
41e1f1ad7a | |||
4d2e6f8650 | |||
3d3dd00211 | |||
510b0a1005 | |||
da77e426b6 | |||
e7a8d4bdaf |
29 changed files with 1273 additions and 221 deletions
|
@ -16,7 +16,7 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.23.5'
|
||||||
|
|
||||||
- name: Install govulncheck
|
- name: Install govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -4,6 +4,22 @@ This document outlines major changes between releases.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.32.4] - 2025-02-03
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Possible deadlock in tree pool component (#617)
|
||||||
|
- Possible memory leak in gRPC client (#617)
|
||||||
|
|
||||||
|
## [0.32.3] - 2025-01-29
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Use `UNSIGNED_PAYLOAD` as content hash to check signature if `x-amz-content-sha256` isn't signed header (#616)
|
||||||
|
|
||||||
|
## [0.32.2] - 2025-01-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix panic when payload discard (#605)
|
||||||
|
|
||||||
## [0.32.1] - 2025-01-17
|
## [0.32.1] - 2025-01-17
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -400,4 +416,7 @@ To see CHANGELOG for older versions, refer to https://github.com/nspcc-dev/neofs
|
||||||
[0.31.3]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.31.2...v0.31.3
|
[0.31.3]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.31.2...v0.31.3
|
||||||
[0.32.0]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.31.3...v0.32.0
|
[0.32.0]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.31.3...v0.32.0
|
||||||
[0.32.1]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.0...v0.32.1
|
[0.32.1]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.0...v0.32.1
|
||||||
[Unreleased]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.1...master
|
[0.32.2]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.1...v0.32.2
|
||||||
|
[0.32.3]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.2...v0.32.3
|
||||||
|
[0.32.4]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.3...v0.32.4
|
||||||
|
[Unreleased]: https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/compare/v0.32.4...master
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
v0.32.1
|
v0.32.4
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -396,6 +397,10 @@ func cloneRequest(r *http.Request, authHeader *AuthHeader) *http.Request {
|
||||||
func (c *Center) checkSign(ctx context.Context, authHeader *AuthHeader, box *accessbox.Box, request *http.Request, signatureDateTime time.Time) error {
|
func (c *Center) checkSign(ctx context.Context, authHeader *AuthHeader, box *accessbox.Box, request *http.Request, signatureDateTime time.Time) error {
|
||||||
var signature string
|
var signature string
|
||||||
|
|
||||||
|
if !slices.Contains(authHeader.SignedFields, "x-amz-content-sha256") && authHeader.PayloadHash == "" {
|
||||||
|
authHeader.PayloadHash = UnsignedPayload
|
||||||
|
}
|
||||||
|
|
||||||
switch authHeader.Preamble {
|
switch authHeader.Preamble {
|
||||||
case signaturePreambleSigV4:
|
case signaturePreambleSigV4:
|
||||||
creds := aws.Credentials{
|
creds := aws.Credentials{
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -26,6 +27,9 @@ import (
|
||||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
smithyauth "github.com/aws/smithy-go/auth"
|
||||||
|
"github.com/aws/smithy-go/logging"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
|
@ -298,6 +302,7 @@ func TestAuthenticate(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
|
region string
|
||||||
prefixes []string
|
prefixes []string
|
||||||
request *http.Request
|
request *http.Request
|
||||||
err bool
|
err bool
|
||||||
|
@ -308,10 +313,23 @@ func TestAuthenticate(t *testing.T) {
|
||||||
prefixes: []string{addr.Container().String()},
|
prefixes: []string{addr.Container().String()},
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now())
|
||||||
|
require.NoError(t, err)
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
region: region,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid sign with hash",
|
||||||
|
prefixes: []string{addr.Container().String()},
|
||||||
|
request: func() *http.Request {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
r.Header.Set(AmzContentSHA256, "")
|
||||||
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
err = defaultSigner.SignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
region: region,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no authorization header",
|
name: "no authorization header",
|
||||||
|
@ -418,12 +436,27 @@ func TestAuthenticate(t *testing.T) {
|
||||||
request: func() *http.Request {
|
request: func() *http.Request {
|
||||||
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
r.Header.Set(AmzExpires, "60")
|
r.Header.Set(AmzExpires, "60")
|
||||||
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, UnsignedPayload, service, region, time.Now())
|
||||||
|
require.NoError(t, err)
|
||||||
|
r.URL, err = url.ParseRequestURI(signedURI)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
region: region,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid presign with hash",
|
||||||
|
request: func() *http.Request {
|
||||||
|
r := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
r.Header.Set(AmzExpires, "60")
|
||||||
|
r.Header.Set(AmzContentSHA256, "")
|
||||||
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
signedURI, _, err := defaultSigner.PresignHTTP(ctx, awsCreds, r, "", service, region, time.Now())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
r.URL, err = url.ParseRequestURI(signedURI)
|
r.URL, err = url.ParseRequestURI(signedURI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return r
|
return r
|
||||||
}(),
|
}(),
|
||||||
|
region: region,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "presign, bad X-Amz-Credential",
|
name: "presign, bad X-Amz-Credential",
|
||||||
|
@ -480,6 +513,56 @@ func TestAuthenticate(t *testing.T) {
|
||||||
err: true,
|
err: true,
|
||||||
errCode: errors.ErrBadRequest,
|
errCode: errors.ErrBadRequest,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "presign using original aws sdk",
|
||||||
|
request: func() *http.Request {
|
||||||
|
cli := s3.NewPresignClient(s3.New(s3.Options{
|
||||||
|
Credentials: credentials.NewStaticCredentialsProvider(awsCreds.AccessKeyID, awsCreds.SecretAccessKey, ""),
|
||||||
|
UsePathStyle: true,
|
||||||
|
BaseEndpoint: aws.String("http://localhost"),
|
||||||
|
Region: region,
|
||||||
|
Logger: logging.NewStandardLogger(os.Stdout),
|
||||||
|
ClientLogMode: aws.LogSigning,
|
||||||
|
}))
|
||||||
|
|
||||||
|
res, err := cli.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
Key: aws.String("object"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||||
|
r.URL, err = url.ParseRequestURI(res.URL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
region: region,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "presign sigv4a using original aws sdk",
|
||||||
|
request: func() *http.Request {
|
||||||
|
cli := s3.NewPresignClient(s3.New(s3.Options{
|
||||||
|
Credentials: credentials.NewStaticCredentialsProvider(awsCreds.AccessKeyID, awsCreds.SecretAccessKey, ""),
|
||||||
|
UsePathStyle: true,
|
||||||
|
BaseEndpoint: aws.String("http://localhost"),
|
||||||
|
Region: region,
|
||||||
|
Logger: logging.NewStandardLogger(os.Stdout),
|
||||||
|
ClientLogMode: aws.LogSigning,
|
||||||
|
AuthSchemeResolver: resolver{},
|
||||||
|
}))
|
||||||
|
|
||||||
|
res, err := cli.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String("bucket"),
|
||||||
|
Key: aws.String("object"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
r := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
|
||||||
|
r.URL, err = url.ParseRequestURI(res.URL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
creds := tokens.New(bigConfig)
|
creds := tokens.New(bigConfig)
|
||||||
|
@ -495,13 +578,19 @@ func TestAuthenticate(t *testing.T) {
|
||||||
} else {
|
} else {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, accessKeyID, box.AuthHeaders.AccessKeyID)
|
require.Equal(t, accessKeyID, box.AuthHeaders.AccessKeyID)
|
||||||
require.Equal(t, region, box.AuthHeaders.Region)
|
require.Equal(t, tc.region, box.AuthHeaders.Region)
|
||||||
require.Equal(t, secret.SecretKey, box.AccessBox.Gate.SecretKey)
|
require.Equal(t, secret.SecretKey, box.AccessBox.Gate.SecretKey)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type resolver struct{}
|
||||||
|
|
||||||
|
func (r resolver) ResolveAuthSchemes(context.Context, *s3.AuthResolverParameters) ([]*smithyauth.Option, error) {
|
||||||
|
return []*smithyauth.Option{{SchemeID: smithyauth.SchemeIDSigV4A}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestHTTPPostAuthenticate(t *testing.T) {
|
func TestHTTPPostAuthenticate(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
policyBase64 = "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXQpdfQ=="
|
policyBase64 = "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXQpdfQ=="
|
||||||
|
|
|
@ -52,7 +52,12 @@ func PresignRequest(ctx context.Context, creds aws.Credentials, reqData RequestD
|
||||||
options.Logger = log
|
options.Logger = log
|
||||||
})
|
})
|
||||||
|
|
||||||
signedURI, _, err := signer.PresignHTTP(ctx, creds, req, presignData.Headers[AmzContentSHA256], presignData.Service, presignData.Region, presignData.SignTime)
|
payloadHash := presignData.Headers[AmzContentSHA256]
|
||||||
|
if payloadHash == "" {
|
||||||
|
payloadHash = UnsignedPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
signedURI, _, err := signer.PresignHTTP(ctx, creds, req, payloadHash, presignData.Service, presignData.Region, presignData.SignTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("presign: %w", err)
|
return nil, fmt.Errorf("presign: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -93,7 +98,13 @@ func PresignRequestV4a(cred aws.Credentials, reqData RequestData, presignData Pr
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to derive assymetric key from credentials: %w", err)
|
return nil, fmt.Errorf("failed to derive assymetric key from credentials: %w", err)
|
||||||
}
|
}
|
||||||
presignedURL, _, err := signer.PresignHTTP(req.Context(), creds, req, presignData.Headers[AmzContentSHA256], presignData.Service, []string{presignData.Region}, presignData.SignTime)
|
|
||||||
|
payloadHash := presignData.Headers[AmzContentSHA256]
|
||||||
|
if payloadHash == "" {
|
||||||
|
payloadHash = UnsignedPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
presignedURL, _, err := signer.PresignHTTP(req.Context(), creds, req, payloadHash, presignData.Service, []string{presignData.Region}, presignData.SignTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("presign: %w", err)
|
return nil, fmt.Errorf("presign: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,6 @@ func TestCheckSign(t *testing.T) {
|
||||||
SignTime: time.Now().UTC(),
|
SignTime: time.Now().UTC(),
|
||||||
Headers: map[string]string{
|
Headers: map[string]string{
|
||||||
ContentTypeHdr: "text/plain",
|
ContentTypeHdr: "text/plain",
|
||||||
AmzContentSHA256: UnsignedPayload,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// This file is adopting https://github.com/aws/aws-sdk-go-v2/blob/a2b751d1ba71f59175a41f9cae5f159f1044360f/aws/signer/v4/stream.go for sigv4a.
|
// This file is adopting https://github.com/aws/aws-sdk-go-v2/blob/a2b751d1ba71f59175a41f9cae5f159f1044360f/aws/signer/v4/stream.go for sigv4a.
|
||||||
|
// with changes
|
||||||
|
// * add VerifyTrailerSignature
|
||||||
|
|
||||||
package v4a
|
package v4a
|
||||||
|
|
||||||
|
@ -88,6 +90,39 @@ func (s *StreamSigner) buildEventStreamStringToSign(headers, payload, previousSi
|
||||||
}, "\n")
|
}, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StreamSigner) VerifyTrailerSignature(payload []byte, signingTime time.Time, signature []byte) error {
|
||||||
|
prevSignature := s.prevSignature
|
||||||
|
|
||||||
|
st := v4Internal.NewSigningTime(signingTime)
|
||||||
|
|
||||||
|
scope := buildCredentialScope(st, s.service)
|
||||||
|
|
||||||
|
stringToSign := s.buildEventStreamStringToSignTrailer(payload, prevSignature, scope, &st)
|
||||||
|
|
||||||
|
ok, err := signerCrypto.VerifySignature(&s.credentials.PrivateKey.PublicKey, makeHash(sha256.New(), []byte(stringToSign)), signature)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("v4a: invalid signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.prevSignature = signature
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StreamSigner) buildEventStreamStringToSignTrailer(payload, previousSignature []byte, credentialScope string, signingTime *v4Internal.SigningTime) string {
|
||||||
|
hash := sha256.New()
|
||||||
|
return strings.Join([]string{
|
||||||
|
"AWS4-ECDSA-P256-SHA256-TRAILER",
|
||||||
|
signingTime.TimeFormat(),
|
||||||
|
credentialScope,
|
||||||
|
hex.EncodeToString(previousSignature),
|
||||||
|
hex.EncodeToString(makeHash(hash, payload)),
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
func buildCredentialScope(st v4Internal.SigningTime, service string) string {
|
func buildCredentialScope(st v4Internal.SigningTime, service string) string {
|
||||||
return strings.Join([]string{
|
return strings.Join([]string{
|
||||||
st.Format(shortTimeFormat),
|
st.Format(shortTimeFormat),
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
// This is https://github.com/aws/aws-sdk-go-v2/blob/a2b751d1ba71f59175a41f9cae5f159f1044360f/aws/signer/v4/stream.go
|
// This is https://github.com/aws/aws-sdk-go-v2/blob/a2b751d1ba71f59175a41f9cae5f159f1044360f/aws/signer/v4/stream.go
|
||||||
|
// with changes
|
||||||
|
// * add GetTrailingSignature
|
||||||
|
|
||||||
package v4
|
package v4
|
||||||
|
|
||||||
|
@ -87,3 +89,32 @@ func (s *StreamSigner) buildEventStreamStringToSign(headers, payload, previousSi
|
||||||
hex.EncodeToString(makeHash(hash, payload)),
|
hex.EncodeToString(makeHash(hash, payload)),
|
||||||
}, "\n")
|
}, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTrailerSignature signs the provided header and payload bytes.
|
||||||
|
func (s *StreamSigner) GetTrailerSignature(payload []byte, signingTime time.Time) ([]byte, error) {
|
||||||
|
prevSignature := s.prevSignature
|
||||||
|
|
||||||
|
st := v4Internal.NewSigningTime(signingTime)
|
||||||
|
|
||||||
|
sigKey := s.signingKeyDeriver.DeriveKey(s.credentials, s.service, s.region, st)
|
||||||
|
|
||||||
|
scope := v4Internal.BuildCredentialScope(st, s.region, s.service)
|
||||||
|
|
||||||
|
stringToSign := s.buildEventStreamStringToSignTrailer(payload, prevSignature, scope, &st)
|
||||||
|
|
||||||
|
signature := v4Internal.HMACSHA256(sigKey, []byte(stringToSign))
|
||||||
|
s.prevSignature = signature
|
||||||
|
|
||||||
|
return signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StreamSigner) buildEventStreamStringToSignTrailer(payload, previousSignature []byte, credentialScope string, signingTime *v4Internal.SigningTime) string {
|
||||||
|
hash := sha256.New()
|
||||||
|
return strings.Join([]string{
|
||||||
|
"AWS4-HMAC-SHA256-TRAILER",
|
||||||
|
signingTime.TimeFormat(),
|
||||||
|
credentialScope,
|
||||||
|
hex.EncodeToString(previousSignature),
|
||||||
|
hex.EncodeToString(makeHash(hash, payload)),
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
|
@ -109,7 +109,6 @@ type MultipartInfo struct {
|
||||||
Owner user.ID
|
Owner user.ID
|
||||||
Created time.Time
|
Created time.Time
|
||||||
Meta map[string]string
|
Meta map[string]string
|
||||||
CopiesNumbers []uint32
|
|
||||||
Finished bool
|
Finished bool
|
||||||
CreationEpoch uint64
|
CreationEpoch uint64
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,12 +152,6 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
p.Header[api.ContentLanguage] = contentLanguage
|
p.Header[api.ContentLanguage] = contentLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(p.Header, reqInfo.Namespace, bktInfo.LocationConstraint)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err, additional...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = h.obj.CreateMultipartUpload(ctx, p); err != nil {
|
if err = h.obj.CreateMultipartUpload(ctx, p); err != nil {
|
||||||
h.logAndSendError(ctx, w, "could create multipart upload", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could create multipart upload", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
|
@ -229,6 +223,12 @@ func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err, additional...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
hash, err := h.obj.UploadPart(ctx, p)
|
hash, err := h.obj.UploadPart(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "could not upload a part", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not upload a part", reqInfo, err, additional...)
|
||||||
|
@ -354,6 +354,12 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err, additional...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
info, err := h.obj.UploadPartCopy(ctx, p)
|
info, err := h.obj.UploadPartCopy(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "could not upload part copy", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "could not upload part copy", reqInfo, err, additional...)
|
||||||
|
@ -416,6 +422,12 @@ func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.
|
||||||
Parts: reqBody.Parts,
|
Parts: reqBody.Parts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(ctx, w, "invalid copies number", reqInfo, err, additional...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Start complete multipart upload which may take some time to fetch object
|
// Start complete multipart upload which may take some time to fetch object
|
||||||
// and re-upload it part by part.
|
// and re-upload it part by part.
|
||||||
objInfo, err := h.completeMultipartUpload(r, c, bktInfo)
|
objInfo, err := h.completeMultipartUpload(r, c, bktInfo)
|
||||||
|
|
|
@ -81,6 +81,26 @@ func TestDeleteMultipartAllParts(t *testing.T) {
|
||||||
require.Empty(t, hc.tp.Objects())
|
require.Empty(t, hc.tp.Objects())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultipartCopiesNumber(t *testing.T) {
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName, objName := "bucket", "object"
|
||||||
|
|
||||||
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
|
copies := []uint32{2, 0}
|
||||||
|
|
||||||
|
hc.config.copiesNumbers = map[string][]uint32{"default": copies}
|
||||||
|
|
||||||
|
multipartInfo := createMultipartUpload(hc, bktName, objName, nil)
|
||||||
|
uploadPart(hc, bktName, objName, multipartInfo.UploadID, 1, layer.UploadMinSize)
|
||||||
|
|
||||||
|
objs := hc.tp.Objects()
|
||||||
|
require.Len(t, objs, 1)
|
||||||
|
|
||||||
|
require.EqualValues(t, copies, hc.tp.CopiesNumbers(addrFromObject(objs[0]).EncodeToString()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestSpecialMultipartName(t *testing.T) {
|
func TestSpecialMultipartName(t *testing.T) {
|
||||||
hc := prepareHandlerContextWithMinCache(t)
|
hc := prepareHandlerContextWithMinCache(t)
|
||||||
|
|
||||||
|
@ -792,3 +812,14 @@ func listPartsBase(hc *handlerContext, bktName, objName string, encrypted bool,
|
||||||
|
|
||||||
return listPartsResponse
|
return listPartsResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addrFromObject(obj *object.Object) oid.Address {
|
||||||
|
var addr oid.Address
|
||||||
|
cnrID, _ := obj.ContainerID()
|
||||||
|
objID, _ := obj.ID()
|
||||||
|
|
||||||
|
addr.SetContainer(cnrID)
|
||||||
|
addr.SetObject(objID)
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
|
@ -311,10 +311,23 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) getBodyReader(r *http.Request) (io.ReadCloser, error) {
|
type BodyReader interface {
|
||||||
|
io.ReadCloser
|
||||||
|
TrailerHeaders() map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type noTrailerBodyReader struct {
|
||||||
|
io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *noTrailerBodyReader) TrailerHeaders() map[string]string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) getBodyReader(r *http.Request) (BodyReader, error) {
|
||||||
shaType, streaming := api.IsSignedStreamingV4(r)
|
shaType, streaming := api.IsSignedStreamingV4(r)
|
||||||
if !streaming {
|
if !streaming {
|
||||||
return r.Body, nil
|
return &noTrailerBodyReader{r.Body}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
encodings := r.Header.Values(api.ContentEncoding)
|
encodings := r.Header.Values(api.ContentEncoding)
|
||||||
|
@ -350,12 +363,15 @@ func (h *handler) getBodyReader(r *http.Request) (io.ReadCloser, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
chunkReader io.ReadCloser
|
chunkReader BodyReader
|
||||||
)
|
)
|
||||||
if shaType == api.StreamingContentV4aSHA256 {
|
switch shaType {
|
||||||
chunkReader, err = newSignV4aChunkedReader(r)
|
case api.StreamingContentSHA256, api.StreamingContentSHA256Trailer:
|
||||||
} else {
|
|
||||||
chunkReader, err = newSignV4ChunkedReader(r)
|
chunkReader, err = newSignV4ChunkedReader(r)
|
||||||
|
case api.StreamingContentV4aSHA256, api.StreamingContentV4aSHA256Trailer:
|
||||||
|
chunkReader, err = newSignV4aChunkedReader(r)
|
||||||
|
default:
|
||||||
|
chunkReader, err = newUnsignedChunkedReader(r.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -377,6 +377,77 @@ func TestPutObjectCheckContentSHA256(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPutObjectWithStreamUnsignedBodySmall(t *testing.T) {
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName, objName := "test2", "tmp.txt"
|
||||||
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
|
w, req, chunk := getChunkedRequestUnsignedTrailingSmall(hc.context, t, bktName, objName)
|
||||||
|
hc.Handler().PutObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
|
||||||
|
w, req = prepareTestRequest(hc, bktName, objName, nil)
|
||||||
|
hc.Handler().HeadObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
require.Equal(t, "5", w.Header().Get(api.ContentLength))
|
||||||
|
|
||||||
|
data := getObjectRange(t, hc, bktName, objName, 0, 5)
|
||||||
|
for i := range chunk {
|
||||||
|
require.Equal(t, chunk[i], data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutObjectWithStreamUnsignedBody(t *testing.T) {
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName, objName := "examplebucket", "chunkObject.txt"
|
||||||
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
|
w, req, chunk := getChunkedRequestUnsignedTrailing(hc.context, t, bktName, objName)
|
||||||
|
hc.Handler().PutObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
|
||||||
|
w, req = prepareTestRequest(hc, bktName, objName, nil)
|
||||||
|
hc.Handler().HeadObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
require.Equal(t, strconv.Itoa(awsChunkedRequestExampleDecodedContentLength), w.Header().Get(api.ContentLength))
|
||||||
|
|
||||||
|
data := getObjectRange(t, hc, bktName, objName, 0, awsChunkedRequestExampleDecodedContentLength)
|
||||||
|
for i := range chunk {
|
||||||
|
require.Equal(t, chunk[i], data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutObjectWithStreamBodyAWSExampleTrailing(t *testing.T) {
|
||||||
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
bktName, objName := "examplebucket", "chunkObject.txt"
|
||||||
|
createTestBucket(hc, bktName)
|
||||||
|
|
||||||
|
t.Run("valid trailer signature", func(t *testing.T) {
|
||||||
|
w, req, chunk := getChunkedRequestTrailing(hc.context, t, bktName, objName)
|
||||||
|
hc.Handler().PutObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
|
||||||
|
w, req = prepareTestRequest(hc, bktName, objName, nil)
|
||||||
|
hc.Handler().HeadObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusOK)
|
||||||
|
require.Equal(t, strconv.Itoa(awsChunkedRequestExampleDecodedContentLength), w.Header().Get(api.ContentLength))
|
||||||
|
|
||||||
|
data := getObjectRange(t, hc, bktName, objName, 0, awsChunkedRequestExampleDecodedContentLength)
|
||||||
|
equalDataSlices(t, chunk, data)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid trailer signature", func(t *testing.T) {
|
||||||
|
w, req, _ := getChunkedRequestTrailing(hc.context, t, bktName, objName)
|
||||||
|
body := req.Body.(*customNopCloser)
|
||||||
|
body.Bytes()[body.Len()-2] = 'a'
|
||||||
|
hc.Handler().PutObjectHandler(w, req)
|
||||||
|
assertStatus(t, w, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestPutObjectWithStreamBodyAWSExample(t *testing.T) {
|
func TestPutObjectWithStreamBodyAWSExample(t *testing.T) {
|
||||||
hc := prepareHandlerContext(t)
|
hc := prepareHandlerContext(t)
|
||||||
|
|
||||||
|
@ -476,9 +547,9 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
|
||||||
|
|
||||||
req, err := http.NewRequest("PUT", "https://s3.amazonaws.com/"+bktName+"/"+objName, nil)
|
req, err := http.NewRequest("PUT", "https://s3.amazonaws.com/"+bktName+"/"+objName, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req.Header.Set("content-encoding", "aws-chunked")
|
req.Header.Set("content-encoding", api.AwsChunked)
|
||||||
req.Header.Set("content-length", strconv.Itoa(awsChunkedRequestExampleContentLength))
|
req.Header.Set("content-length", strconv.Itoa(awsChunkedRequestExampleContentLength))
|
||||||
req.Header.Set("x-amz-content-sha256", "STREAMING-AWS4-HMAC-SHA256-PAYLOAD")
|
req.Header.Set("x-amz-content-sha256", api.StreamingContentSHA256)
|
||||||
req.Header.Set("x-amz-decoded-content-length", strconv.Itoa(awsChunkedRequestExampleDecodedContentLength))
|
req.Header.Set("x-amz-decoded-content-length", strconv.Itoa(awsChunkedRequestExampleDecodedContentLength))
|
||||||
req.Header.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
|
req.Header.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
|
||||||
|
|
||||||
|
@ -510,6 +581,202 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
|
||||||
return w, req, chunk
|
return w, req, chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type customNopCloser struct {
|
||||||
|
*bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *customNopCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getChunkedRequestTrailing implements request example from
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming-trailers.html
|
||||||
|
func getChunkedRequestTrailing(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request, []byte) {
|
||||||
|
chunk := make([]byte, 65*1024)
|
||||||
|
for i := range chunk {
|
||||||
|
chunk[i] = 'a'
|
||||||
|
}
|
||||||
|
chunk1 := chunk[:64*1024]
|
||||||
|
chunk2 := chunk[64*1024:]
|
||||||
|
|
||||||
|
AWSAccessKeyID := "AKIAIOSFODNN7EXAMPLE"
|
||||||
|
AWSSecretAccessKey := "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
||||||
|
|
||||||
|
awsCreds := aws.Credentials{AccessKeyID: AWSAccessKeyID, SecretAccessKey: AWSSecretAccessKey}
|
||||||
|
signer := v4.NewSigner()
|
||||||
|
|
||||||
|
reqBody := bytes.NewBufferString("10000;chunk-signature=b474d8862b1487a5145d686f57f013e54db672cee1c953b3010fb58501ef5aa2\r\n")
|
||||||
|
_, err := reqBody.Write(chunk1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = reqBody.WriteString("\r\n400;chunk-signature=1c1344b170168f8e65b41376b44b20fe354e373826ccbbe2c1d40a8cae51e5c7\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = reqBody.Write(chunk2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = reqBody.WriteString("\r\n0;chunk-signature=2ca2aba2005185cf7159c6277faf83795951dd77a3a99e6e65d5c9f85863f992\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = reqBody.WriteString("x-amz-checksum-crc32c:sOO8/Q==\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// original signature is 63bddb248ad2590c92712055f51b8e78ab024eead08276b24f010b0efd74843f,
|
||||||
|
// but we use d81f82fc3505edab99d459891051a732e8730629a2e4a59689829ca17fe2e435
|
||||||
|
// because original signature is incorrect
|
||||||
|
// it was calculated using the`AWS4-HMAC-SHA256-PAYLOAD` constant in canonical string instead of
|
||||||
|
// `AWS4-HMAC-SHA256-TRAILER` that actually must be used by spec
|
||||||
|
// (java sdk use correct `AWS4-HMAC-SHA256-TRAILER` string).
|
||||||
|
_, err = reqBody.WriteString("x-amz-trailer-signature:d81f82fc3505edab99d459891051a732e8730629a2e4a59689829ca17fe2e435")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", "https://s3.amazonaws.com/"+bktName+"/"+objName, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("content-encoding", api.AwsChunked)
|
||||||
|
req.Header.Set("content-length", strconv.Itoa(awsChunkedRequestExampleContentLength))
|
||||||
|
req.Header.Set("x-amz-content-sha256", api.StreamingContentSHA256Trailer)
|
||||||
|
req.Header.Set("x-amz-decoded-content-length", strconv.Itoa(awsChunkedRequestExampleDecodedContentLength))
|
||||||
|
req.Header.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
|
||||||
|
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32c")
|
||||||
|
|
||||||
|
signTime, err := time.Parse("20060102T150405Z", "20130524T000000Z")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = signer.SignHTTP(ctx, awsCreds, req, api.StreamingContentSHA256Trailer, "s3", "us-east-1", signTime)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req.Body = &customNopCloser{Buffer: reqBody}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
|
||||||
|
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
|
||||||
|
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
|
||||||
|
ClientTime: signTime,
|
||||||
|
AuthHeaders: &middleware.AuthHeader{
|
||||||
|
AccessKeyID: AWSAccessKeyID,
|
||||||
|
SignatureV4: "106e2a8a18243abcf37539882f36619c00e2dfc72633413f02d3b74544bfeb8e",
|
||||||
|
Region: "us-east-1",
|
||||||
|
},
|
||||||
|
AccessBox: &accessbox.Box{
|
||||||
|
Gate: &accessbox.GateData{
|
||||||
|
SecretKey: AWSSecretAccessKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
return w, req, chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChunkedRequestUnsignedTrailing(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request, []byte) {
|
||||||
|
chunk := make([]byte, 65*1024)
|
||||||
|
for i := range chunk {
|
||||||
|
chunk[i] = 'a'
|
||||||
|
}
|
||||||
|
//chunk1 := chunk[:64*1024]
|
||||||
|
//chunk2 := chunk[64*1024:]
|
||||||
|
|
||||||
|
AWSAccessKeyID := "9uEm8zMrGWsEDWiPCnVuQLKTiGtCEXpYXt8eBG7agupw0JDySJZMFuej7PTcPzRqBUyPtFowNu1RtvHULU8XHjie6"
|
||||||
|
AWSSecretAccessKey := "9f546428957ed7e189b7be928906ce7d1d9cb3042dd4d2d5194e28ce8c4c3b8e"
|
||||||
|
|
||||||
|
awsCreds := aws.Credentials{AccessKeyID: AWSAccessKeyID, SecretAccessKey: AWSSecretAccessKey}
|
||||||
|
signer := v4.NewSigner()
|
||||||
|
|
||||||
|
reqBody := bytes.NewBufferString("10400\r\n")
|
||||||
|
_, err := reqBody.Write(chunk)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = reqBody.WriteString("\r\n0\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = reqBody.WriteString("\r\nx-amz-checksum-crc64nvme:pRf+emrnL+A=\r\n\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", "https://localhost:8184/"+bktName+"/"+objName, nil)
|
||||||
|
//req, err := http.NewRequest("PUT", "https://localhost:8184/test2/body", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("x-amz-sdk-checksum-algorithm", "CRC64NVME")
|
||||||
|
req.Header.Set("content-encoding", api.AwsChunked)
|
||||||
|
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc64nvme")
|
||||||
|
req.Header.Set("x-amz-content-sha256", api.StreamingUnsignedPayloadTrailer)
|
||||||
|
req.Header.Set("x-amz-decoded-content-length", strconv.Itoa(awsChunkedRequestExampleDecodedContentLength))
|
||||||
|
req.Header.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
|
||||||
|
|
||||||
|
signTime, err := time.Parse("20060102T150405Z", "20250131T140527Z")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = signer.SignHTTP(ctx, awsCreds, req, api.StreamingContentSHA256Trailer, "s3", "ru", signTime)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req.Body = io.NopCloser(reqBody)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
|
||||||
|
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
|
||||||
|
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
|
||||||
|
ClientTime: signTime,
|
||||||
|
AuthHeaders: &middleware.AuthHeader{
|
||||||
|
AccessKeyID: AWSAccessKeyID,
|
||||||
|
SignatureV4: "a075c83779d1c3c02254fbe4c9eff0a21556d15556fc6a25db69147c4838226b",
|
||||||
|
Region: "ru",
|
||||||
|
},
|
||||||
|
AccessBox: &accessbox.Box{
|
||||||
|
Gate: &accessbox.GateData{
|
||||||
|
SecretKey: AWSSecretAccessKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
return w, req, chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChunkedRequestUnsignedTrailingSmall(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request, []byte) {
|
||||||
|
AWSAccessKeyID := "9uEm8zMrGWsEDWiPCnVuQLKTiGtCEXpYXt8eBG7agupw0JDySJZMFuej7PTcPzRqBUyPtFowNu1RtvHULU8XHjie6"
|
||||||
|
AWSSecretAccessKey := "9f546428957ed7e189b7be928906ce7d1d9cb3042dd4d2d5194e28ce8c4c3b8e"
|
||||||
|
|
||||||
|
awsCreds := aws.Credentials{AccessKeyID: AWSAccessKeyID, SecretAccessKey: AWSSecretAccessKey}
|
||||||
|
signer := v4.NewSigner()
|
||||||
|
|
||||||
|
chunk := "tmp2\n"
|
||||||
|
|
||||||
|
reqBody := bytes.NewBufferString("5\r\n")
|
||||||
|
_, err := reqBody.WriteString(chunk)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = reqBody.WriteString("\r\n0\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = reqBody.WriteString("x-amz-checksum-crc64nvme:q1EYl4rI0TU=\r\n\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", "https://localhost:8184/"+bktName+"/"+objName, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("x-amz-sdk-checksum-algorithm", "CRC64NVME")
|
||||||
|
req.Header.Set("content-encoding", api.AwsChunked)
|
||||||
|
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc64nvme")
|
||||||
|
req.Header.Set("x-amz-content-sha256", api.StreamingUnsignedPayloadTrailer)
|
||||||
|
req.Header.Set("x-amz-decoded-content-length", "5")
|
||||||
|
req.Header.Set("x-amz-storage-class", "REDUCED_REDUNDANCY")
|
||||||
|
|
||||||
|
signTime, err := time.Parse("20060102T150405Z", "20250203T063745Z")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = signer.SignHTTP(ctx, awsCreds, req, api.StreamingContentSHA256Trailer, "s3", "ru", signTime)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req.Body = io.NopCloser(reqBody)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
|
||||||
|
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
|
||||||
|
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
|
||||||
|
ClientTime: signTime,
|
||||||
|
AuthHeaders: &middleware.AuthHeader{
|
||||||
|
AccessKeyID: AWSAccessKeyID,
|
||||||
|
SignatureV4: "a075c83779d1c3c02254fbe4c9eff0a21556d15556fc6a25db69147c4838226b",
|
||||||
|
Region: "ru",
|
||||||
|
},
|
||||||
|
AccessBox: &accessbox.Box{
|
||||||
|
Gate: &accessbox.GateData{
|
||||||
|
SecretKey: AWSSecretAccessKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
return w, req, []byte(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
func getEmptyChunkedRequest(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
|
func getEmptyChunkedRequest(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
|
||||||
AWSAccessKeyID := "48c1K4PLVb7SvmV3PjDKEuXaMh8yZMXZ8Wx9msrkKcYw06dZeaxeiPe8vyFm2WsoeVaNt7UWEjNsVkagDs8oX4XXh"
|
AWSAccessKeyID := "48c1K4PLVb7SvmV3PjDKEuXaMh8yZMXZ8Wx9msrkKcYw06dZeaxeiPe8vyFm2WsoeVaNt7UWEjNsVkagDs8oX4XXh"
|
||||||
AWSSecretAccessKey := "09260955b4eb0279dc017ba20a1ddac909cbd226c86cbb2d868e55534c8e64b0"
|
AWSSecretAccessKey := "09260955b4eb0279dc017ba20a1ddac909cbd226c86cbb2d868e55534c8e64b0"
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/v4"
|
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4sdk2/signer/v4"
|
||||||
|
@ -27,6 +29,8 @@ type (
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
streamSigner *v4.StreamSigner
|
streamSigner *v4.StreamSigner
|
||||||
|
|
||||||
|
trailerHeaders []string
|
||||||
|
trailers map[string]string
|
||||||
requestTime time.Time
|
requestTime time.Time
|
||||||
buffer []byte
|
buffer []byte
|
||||||
offset int
|
offset int
|
||||||
|
@ -37,6 +41,7 @@ type (
|
||||||
var (
|
var (
|
||||||
errGiantChunk = errors.New("chunk too big: choose chunk size <= 16MiB")
|
errGiantChunk = errors.New("chunk too big: choose chunk size <= 16MiB")
|
||||||
errMalformedChunkedEncoding = errors.New("malformed chunked encoding")
|
errMalformedChunkedEncoding = errors.New("malformed chunked encoding")
|
||||||
|
errMalformedTrailerHeaders = errors.New("malformed trailer headers")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *s3ChunkReader) Close() (err error) {
|
func (c *s3ChunkReader) Close() (err error) {
|
||||||
|
@ -107,29 +112,9 @@ func (c *s3ChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
c.err = errMalformedChunkedEncoding
|
c.err = errMalformedChunkedEncoding
|
||||||
return num, c.err
|
return num, c.err
|
||||||
}
|
}
|
||||||
b, err := c.reader.ReadByte()
|
|
||||||
if err == io.EOF {
|
if err = c.readCRLF(); err != nil {
|
||||||
err = io.ErrUnexpectedEOF
|
return num, err
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
c.err = err
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
if b != '\r' {
|
|
||||||
c.err = errMalformedChunkedEncoding
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
b, err = c.reader.ReadByte()
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
c.err = err
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
if b != '\n' {
|
|
||||||
c.err = errMalformedChunkedEncoding
|
|
||||||
return num, c.err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cap(c.buffer) < size {
|
if cap(c.buffer) < size {
|
||||||
|
@ -147,23 +132,6 @@ func (c *s3ChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
c.err = err
|
c.err = err
|
||||||
return num, c.err
|
return num, c.err
|
||||||
}
|
}
|
||||||
b, err = c.reader.ReadByte()
|
|
||||||
if b != '\r' || err != nil {
|
|
||||||
c.err = errMalformedChunkedEncoding
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
b, err = c.reader.ReadByte()
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
c.err = err
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
if b != '\n' {
|
|
||||||
c.err = errMalformedChunkedEncoding
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we have read the entire chunk successfully, we verify
|
// Once we have read the entire chunk successfully, we verify
|
||||||
// that the received signature matches our computed signature.
|
// that the received signature matches our computed signature.
|
||||||
|
@ -181,16 +149,99 @@ func (c *s3ChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
// If the chunk size is zero we return io.EOF. As specified by AWS,
|
// If the chunk size is zero we return io.EOF. As specified by AWS,
|
||||||
// only the last chunk is zero-sized.
|
// only the last chunk is zero-sized.
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
|
if len(c.trailerHeaders) != 0 {
|
||||||
|
if err = c.readTrailers(); err != nil {
|
||||||
|
c.err = err
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
} else if err = c.readCRLF(); err != nil {
|
||||||
|
return num, err
|
||||||
|
}
|
||||||
|
|
||||||
c.err = io.EOF
|
c.err = io.EOF
|
||||||
return num, c.err
|
return num, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = c.readCRLF(); err != nil {
|
||||||
|
return num, err
|
||||||
|
}
|
||||||
|
|
||||||
c.offset = copy(buf, c.buffer)
|
c.offset = copy(buf, c.buffer)
|
||||||
num += c.offset
|
num += c.offset
|
||||||
return num, err
|
return num, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSignV4ChunkedReader(req *http.Request) (io.ReadCloser, error) {
|
func (c *s3ChunkReader) readCRLF() error {
|
||||||
|
for _, ch := range [2]byte{'\r', '\n'} {
|
||||||
|
b, err := c.reader.ReadByte()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.err = err
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
if b != ch {
|
||||||
|
c.err = errMalformedChunkedEncoding
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *s3ChunkReader) readTrailers() error {
|
||||||
|
var k, v []byte
|
||||||
|
var err error
|
||||||
|
for err == nil {
|
||||||
|
k, err = c.reader.ReadBytes(':')
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.err = errMalformedTrailerHeaders
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
v, err = c.reader.ReadBytes('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
c.err = errMalformedTrailerHeaders
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
if len(v) >= 2 && v[len(v)-2] == '\r' {
|
||||||
|
v[len(v)-2] = '\n'
|
||||||
|
v = v[:len(v)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case slices.Contains(c.trailerHeaders, string(k[:len(k)-1])):
|
||||||
|
c.buffer = append(append(c.buffer, k...), v...) // todo use copy
|
||||||
|
case string(k) == "x-amz-trailer-signature:":
|
||||||
|
calculatedSignature, err := c.streamSigner.GetTrailerSignature(c.buffer, c.requestTime)
|
||||||
|
if err != nil {
|
||||||
|
c.err = err
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
if string(v[:64]) != hex.EncodeToString(calculatedSignature) {
|
||||||
|
c.err = errs.GetAPIError(errs.ErrSignatureDoesNotMatch)
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c.err = errMalformedTrailerHeaders
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.trailers[string(k[:len(k)-1])] = string(v[:len(v)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *s3ChunkReader) TrailerHeaders() map[string]string {
|
||||||
|
return c.trailers
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSignV4ChunkedReader(req *http.Request) (*s3ChunkReader, error) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
box, err := middleware.GetBoxData(ctx)
|
box, err := middleware.GetBoxData(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -214,11 +265,19 @@ func newSignV4ChunkedReader(req *http.Request) (io.ReadCloser, error) {
|
||||||
}
|
}
|
||||||
newStreamSigner := v4.NewStreamSigner(currentCredentials, "s3", authHeaders.Region, seed)
|
newStreamSigner := v4.NewStreamSigner(currentCredentials, "s3", authHeaders.Region, seed)
|
||||||
|
|
||||||
|
var trailerHeaders []string
|
||||||
|
trailer := req.Header.Get("x-amz-trailer")
|
||||||
|
if trailer != "" {
|
||||||
|
trailerHeaders = strings.Split(trailer, ";")
|
||||||
|
}
|
||||||
|
|
||||||
return &s3ChunkReader{
|
return &s3ChunkReader{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
reader: bufio.NewReader(req.Body),
|
reader: bufio.NewReader(req.Body),
|
||||||
streamSigner: newStreamSigner,
|
streamSigner: newStreamSigner,
|
||||||
requestTime: reqTime,
|
requestTime: reqTime,
|
||||||
buffer: make([]byte, 64*1024),
|
buffer: make([]byte, 64*1024),
|
||||||
|
trailerHeaders: trailerHeaders,
|
||||||
|
trailers: make(map[string]string, len(trailerHeaders)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -12,7 +16,53 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSigV4AStreaming(t *testing.T) {
|
func TestSigV4AChunkedReader(t *testing.T) {
|
||||||
|
t.Run("with trailers", func(t *testing.T) {
|
||||||
|
accessKeyID := "9uEm8zMrGWsEDWiPCnVuQLKTiGtCEXpYXt8eBG7agupw0JDySJZMFuej7PTcPzRqBUyPtFowNu1RtvHULU8XHjie6"
|
||||||
|
secretKey := "9f546428957ed7e189b7be928906ce7d1d9cb3042dd4d2d5194e28ce8c4c3b8e"
|
||||||
|
|
||||||
|
chunk1 := "Testing with the {sdk-java}"
|
||||||
|
body := "1b;chunk-signature=3045022100956ca03d2166100b455b532de542892f73925fbcea2f6498674a39a61bb4860902202977c1d47aea548d434540f89640ce97e605d18353cbbd75a619874f02e3dd22**\r\n" +
|
||||||
|
chunk1 +
|
||||||
|
"\r\n0;chunk-signature=304502210097dcc1721675469910ef8712fc2af0678eb90c12216dd6228c6b621fb6f805a0022047d27d21ae2af8a8172f2ef83c81ce9d4746aa88fc9ee0ca783eaa5e71aaef6c**\r\n" +
|
||||||
|
"x-amz-checksum-crc32:Np6zMg==\r\n" +
|
||||||
|
"x-amz-trailer-signature:304502200ecacd9aa2c432af5a2327c22a2ff9b32f44ab8559de00309219aef105eaaac102210092cbc0e78c4bcd56490a73da8ceed1934be80f3affeffb14d8c743fc292dda4f**\r\n\r\n"
|
||||||
|
|
||||||
|
reqBody := bytes.NewBufferString(body)
|
||||||
|
req, err := http.NewRequest("PUT", "https://localhost:8184/test2/tmp", reqBody)
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32")
|
||||||
|
|
||||||
|
signature := "3045022100ddbc6ab11785d7f23d299de7db97379116f543377a44e38170a4e43b38b0d62b02201d8dca13c67f04f45491345152db4b704768eb8bb89b5215fd59bb4a4d9d7b61"
|
||||||
|
signingTime, err := time.Parse("20060102T150405Z", "20250203T144621Z")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
accessBox, err := newTestAccessBox(key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
accessBox.Gate.SecretKey = secretKey
|
||||||
|
|
||||||
|
ctx := middleware.SetBox(req.Context(), &middleware.Box{
|
||||||
|
AccessBox: accessBox,
|
||||||
|
AuthHeaders: &middleware.AuthHeader{
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
|
SignatureV4: signature,
|
||||||
|
},
|
||||||
|
ClientTime: signingTime,
|
||||||
|
})
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
r, err := newSignV4aChunkedReader(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, chunk1, string(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without trailers", func(t *testing.T) {
|
||||||
accessKeyID := "2XEbqH4M3ym7a3E3esxfZ2gRLnMwDXrCN4y1SkQg5fHa09sThVmVL3EE6xeKsyMzaqu5jPi41YCaVbnwbwCTF3bx1"
|
accessKeyID := "2XEbqH4M3ym7a3E3esxfZ2gRLnMwDXrCN4y1SkQg5fHa09sThVmVL3EE6xeKsyMzaqu5jPi41YCaVbnwbwCTF3bx1"
|
||||||
secretKey := "00637f53f842573aaa06c2164c598973cd986880987111416cf71f1619def537"
|
secretKey := "00637f53f842573aaa06c2164c598973cd986880987111416cf71f1619def537"
|
||||||
|
|
||||||
|
@ -52,6 +102,136 @@ func TestSigV4AStreaming(t *testing.T) {
|
||||||
|
|
||||||
data, err := io.ReadAll(r)
|
data, err := io.ReadAll(r)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, chunk1, string(data))
|
require.Equal(t, chunk1, string(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSigV4ChunkedReader(t *testing.T) {
|
||||||
|
accessKeyID := "9uEm8zMrGWsEDWiPCnVuQLKTiGtCEXpYXt8eBG7agupw0JDySJZMFuej7PTcPzRqBUyPtFowNu1RtvHULU8XHjie6"
|
||||||
|
secretKey := "9f546428957ed7e189b7be928906ce7d1d9cb3042dd4d2d5194e28ce8c4c3b8e"
|
||||||
|
|
||||||
|
signature := "b740b3b2a08c541c3fc4bd155a448e25408b509a29af98a86356b894930b93e8"
|
||||||
|
signingTime, err := time.Parse("20060102T150405Z", "20250203T134442Z")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
accessBox, err := newTestAccessBox(key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
accessBox.Gate.SecretKey = secretKey
|
||||||
|
|
||||||
|
setBoxFn := func(ctx context.Context) context.Context {
|
||||||
|
return middleware.SetBox(ctx, &middleware.Box{
|
||||||
|
AccessBox: accessBox,
|
||||||
|
AuthHeaders: &middleware.AuthHeader{
|
||||||
|
AccessKeyID: accessKeyID,
|
||||||
|
SignatureV4: signature,
|
||||||
|
Region: "us-east-1",
|
||||||
|
},
|
||||||
|
ClientTime: signingTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk1 := "Testing with the {sdk-java}"
|
||||||
|
|
||||||
|
t.Run("with trailers", func(t *testing.T) {
|
||||||
|
body := "1b;chunk-signature=a6a9be5fff05db0b542aedb2203d892b4162250885d06b1422b173ee0ea92ba5\r\n" +
|
||||||
|
chunk1 +
|
||||||
|
"\r\n0;chunk-signature=31afd083a57c416c46afaf101649d7f0c6c0627cfa60c0f93d1f7ea84396ee42\r\n" +
|
||||||
|
"x-amz-checksum-crc32:Np6zMg==\r\n" +
|
||||||
|
"x-amz-trailer-signature:40ec0046ac730fa27a1451d00d849056c49553ee753f5d158306d05671a42125\r\n\r\n"
|
||||||
|
|
||||||
|
reqBody := bytes.NewBufferString(body)
|
||||||
|
req, err := http.NewRequest("PUT", "https://localhost:8184/test2/tmp", reqBody)
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32")
|
||||||
|
|
||||||
|
req = req.WithContext(setBoxFn(req.Context()))
|
||||||
|
|
||||||
|
r, err := newSignV4ChunkedReader(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, chunk1, string(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without trailers", func(t *testing.T) {
|
||||||
|
body := "1b;chunk-signature=a6a9be5fff05db0b542aedb2203d892b4162250885d06b1422b173ee0ea92ba5\r\n" +
|
||||||
|
chunk1 +
|
||||||
|
"\r\n0;chunk-signature=31afd083a57c416c46afaf101649d7f0c6c0627cfa60c0f93d1f7ea84396ee42\r\n\r\n"
|
||||||
|
reqBody := bytes.NewBufferString(body)
|
||||||
|
req, err := http.NewRequest("PUT", "https://localhost:8184/test2/tmp", reqBody)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req = req.WithContext(setBoxFn(req.Context()))
|
||||||
|
|
||||||
|
r, err := newSignV4ChunkedReader(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, chunk1, string(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnsignedChunkReader(t *testing.T) {
|
||||||
|
chunk1 := "chunk1"
|
||||||
|
chunk2 := "chunk2"
|
||||||
|
|
||||||
|
t.Run("with trailer", func(t *testing.T) {
|
||||||
|
chunks := []string{chunk1, chunk2}
|
||||||
|
trailer := map[string]string{"x-amz-checksum-crc64nvme": "q1EYl4rI0TU="}
|
||||||
|
body, expected := getChunkedBody(t, chunks, trailer)
|
||||||
|
|
||||||
|
r, err := newUnsignedChunkedReader(body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(data))
|
||||||
|
|
||||||
|
require.EqualValues(t, trailer, r.TrailerHeaders())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without trailer", func(t *testing.T) {
|
||||||
|
chunks := []string{chunk1, chunk2}
|
||||||
|
body, expected := getChunkedBody(t, chunks, nil)
|
||||||
|
|
||||||
|
r, err := newUnsignedChunkedReader(body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChunkedBody(t *testing.T, chunks []string, trailers map[string]string) (*bytes.Buffer, string) {
|
||||||
|
res := bytes.NewBufferString("")
|
||||||
|
|
||||||
|
for i, chunk := range chunks {
|
||||||
|
meta := strconv.FormatInt(int64(len(chunk)), 16) + "\r\n"
|
||||||
|
if i != 0 {
|
||||||
|
meta = "\r\n" + meta
|
||||||
|
}
|
||||||
|
_, err := res.WriteString(meta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = res.WriteString(chunk)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := res.WriteString("\r\n0\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for k, v := range trailers {
|
||||||
|
_, err := res.WriteString(fmt.Sprintf("%s:%s\n", k, v))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = res.WriteString("\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return res, strings.Join(chunks, "")
|
||||||
}
|
}
|
||||||
|
|
161
api/handler/s3unsignedreader.go
Normal file
161
api/handler/s3unsignedreader.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
s3UnsignedChunkReader struct {
|
||||||
|
reader *bufio.Reader
|
||||||
|
|
||||||
|
trailers map[string]string
|
||||||
|
buffer []byte
|
||||||
|
offset int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *s3UnsignedChunkReader) Close() (err error) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *s3UnsignedChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
|
if c.offset > 0 {
|
||||||
|
num = copy(buf, c.buffer[c.offset:])
|
||||||
|
if num == len(buf) {
|
||||||
|
c.offset += num
|
||||||
|
return num, nil
|
||||||
|
}
|
||||||
|
c.offset = 0
|
||||||
|
buf = buf[num:]
|
||||||
|
}
|
||||||
|
|
||||||
|
var size int
|
||||||
|
var b byte
|
||||||
|
for {
|
||||||
|
b, err = c.reader.ReadByte()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.err = err
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
if b == '\r' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually deserialize the size since AWS specified
|
||||||
|
// the chunk size to be of variable width. In particular,
|
||||||
|
// a size of 16 is encoded as `10` while a size of 64 KB
|
||||||
|
// is `10000`.
|
||||||
|
switch {
|
||||||
|
case b >= '0' && b <= '9':
|
||||||
|
size = size<<4 | int(b-'0')
|
||||||
|
case b >= 'a' && b <= 'f':
|
||||||
|
size = size<<4 | int(b-('a'-10))
|
||||||
|
case b >= 'A' && b <= 'F':
|
||||||
|
size = size<<4 | int(b-('A'-10))
|
||||||
|
default:
|
||||||
|
c.err = errMalformedChunkedEncoding
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
if size > maxChunkSize {
|
||||||
|
c.err = errGiantChunk
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b != '\r' {
|
||||||
|
c.err = errMalformedChunkedEncoding
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
b, err = c.reader.ReadByte()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.err = err
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
if b != '\n' {
|
||||||
|
c.err = errMalformedChunkedEncoding
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(c.buffer) < size {
|
||||||
|
c.buffer = make([]byte, size)
|
||||||
|
} else {
|
||||||
|
c.buffer = c.buffer[:size]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, we read the payload and compute its SHA-256 hash.
|
||||||
|
_, err = io.ReadFull(c.reader, c.buffer)
|
||||||
|
if err == io.EOF && size != 0 {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
c.err = err
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the chunk size is zero we return io.EOF. As specified by AWS,
|
||||||
|
// only the last chunk is zero-sized.
|
||||||
|
if size == 0 {
|
||||||
|
var k, v string
|
||||||
|
for err == nil {
|
||||||
|
k, err = c.reader.ReadString(':')
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.err = errMalformedTrailerHeaders
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
v, err = c.reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
c.err = errMalformedTrailerHeaders
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
c.trailers[k[:len(k)-1]] = v[:len(v)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
c.err = io.EOF
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = c.reader.ReadByte()
|
||||||
|
if b != '\r' || err != nil {
|
||||||
|
c.err = errMalformedChunkedEncoding
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
b, err = c.reader.ReadByte()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.err = err
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
if b != '\n' {
|
||||||
|
c.err = errMalformedChunkedEncoding
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.offset = copy(buf, c.buffer)
|
||||||
|
num += c.offset
|
||||||
|
return num, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *s3UnsignedChunkReader) TrailerHeaders() map[string]string {
|
||||||
|
return c.trailers
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnsignedChunkedReader(body io.Reader) (*s3UnsignedChunkReader, error) {
|
||||||
|
return &s3UnsignedChunkReader{
|
||||||
|
reader: bufio.NewReader(body),
|
||||||
|
trailers: map[string]string{},
|
||||||
|
buffer: make([]byte, 64*1024),
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
v4a "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4asdk2"
|
||||||
|
@ -20,6 +22,8 @@ type (
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
streamSigner *v4a.StreamSigner
|
streamSigner *v4a.StreamSigner
|
||||||
|
|
||||||
|
trailerHeaders []string
|
||||||
|
trailers map[string]string
|
||||||
requestTime time.Time
|
requestTime time.Time
|
||||||
buffer []byte
|
buffer []byte
|
||||||
offset int
|
offset int
|
||||||
|
@ -87,21 +91,9 @@ func (c *s3v4aChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
c.err = errMalformedChunkedEncoding
|
c.err = errMalformedChunkedEncoding
|
||||||
return num, c.err
|
return num, c.err
|
||||||
}
|
}
|
||||||
b, err := c.reader.ReadByte()
|
|
||||||
if err != nil {
|
if err = c.readCRLF(); err != nil {
|
||||||
return c.handleErr(num, err)
|
return num, err
|
||||||
}
|
|
||||||
if b != '\r' {
|
|
||||||
c.err = errMalformedChunkedEncoding
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
b, err = c.reader.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return c.handleErr(num, err)
|
|
||||||
}
|
|
||||||
if b != '\n' {
|
|
||||||
c.err = errMalformedChunkedEncoding
|
|
||||||
return num, c.err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cap(c.buffer) < size {
|
if cap(c.buffer) < size {
|
||||||
|
@ -119,19 +111,6 @@ func (c *s3v4aChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
c.err = err
|
c.err = err
|
||||||
return num, c.err
|
return num, c.err
|
||||||
}
|
}
|
||||||
b, err = c.reader.ReadByte()
|
|
||||||
if b != '\r' || err != nil {
|
|
||||||
c.err = errMalformedChunkedEncoding
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
b, err = c.reader.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return c.handleErr(num, err)
|
|
||||||
}
|
|
||||||
if b != '\n' {
|
|
||||||
c.err = errMalformedChunkedEncoding
|
|
||||||
return num, c.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once we have read the entire chunk successfully, we verify
|
// Once we have read the entire chunk successfully, we verify
|
||||||
// that the received signature is valid.
|
// that the received signature is valid.
|
||||||
|
@ -150,10 +129,23 @@ func (c *s3v4aChunkReader) Read(buf []byte) (num int, err error) {
|
||||||
// If the chunk size is zero we return io.EOF. As specified by AWS,
|
// If the chunk size is zero we return io.EOF. As specified by AWS,
|
||||||
// only the last chunk is zero-sized.
|
// only the last chunk is zero-sized.
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
|
if len(c.trailerHeaders) != 0 {
|
||||||
|
if err = c.readTrailers(); err != nil {
|
||||||
|
c.err = err
|
||||||
|
return num, c.err
|
||||||
|
}
|
||||||
|
} else if err = c.readCRLF(); err != nil {
|
||||||
|
return num, err
|
||||||
|
}
|
||||||
|
|
||||||
c.err = io.EOF
|
c.err = io.EOF
|
||||||
return num, c.err
|
return num, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = c.readCRLF(); err != nil {
|
||||||
|
return num, err
|
||||||
|
}
|
||||||
|
|
||||||
c.offset = copy(buf, c.buffer)
|
c.offset = copy(buf, c.buffer)
|
||||||
num += c.offset
|
num += c.offset
|
||||||
return num, err
|
return num, err
|
||||||
|
@ -168,7 +160,78 @@ func (c *s3v4aChunkReader) handleErr(num int, err error) (int, error) {
|
||||||
return num, c.err
|
return num, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSignV4aChunkedReader(req *http.Request) (io.ReadCloser, error) {
|
func (c *s3v4aChunkReader) readCRLF() error {
|
||||||
|
for _, ch := range [2]byte{'\r', '\n'} {
|
||||||
|
b, err := c.reader.ReadByte()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.err = err
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
if b != ch {
|
||||||
|
c.err = errMalformedChunkedEncoding
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *s3v4aChunkReader) readTrailers() error {
|
||||||
|
var k, v []byte
|
||||||
|
var err error
|
||||||
|
for err == nil {
|
||||||
|
k, err = c.reader.ReadBytes(':')
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.err = errMalformedTrailerHeaders
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
v, err = c.reader.ReadBytes('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
c.err = errMalformedTrailerHeaders
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
if len(v) >= 2 && v[len(v)-2] == '\r' {
|
||||||
|
v[len(v)-2] = '\n'
|
||||||
|
v = v[:len(v)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case slices.Contains(c.trailerHeaders, string(k[:len(k)-1])):
|
||||||
|
c.buffer = append(append(c.buffer, k...), v...) // todo use copy
|
||||||
|
case string(k) == "x-amz-trailer-signature:":
|
||||||
|
n, err := hex.Decode(v[:], bytes.TrimRight(v[:], "*\n"))
|
||||||
|
if err != nil {
|
||||||
|
c.err = errMalformedChunkedEncoding
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.streamSigner.VerifyTrailerSignature(c.buffer, c.requestTime, v[:n]); err != nil {
|
||||||
|
c.err = fmt.Errorf("%w: %s", errs.GetAPIError(errs.ErrSignatureDoesNotMatch), err.Error())
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c.err = errMalformedTrailerHeaders
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.trailers[string(k[:len(k)-1])] = string(v[:len(v)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *s3v4aChunkReader) TrailerHeaders() map[string]string {
|
||||||
|
return c.trailers
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSignV4aChunkedReader(req *http.Request) (*s3v4aChunkReader, error) {
|
||||||
box, err := middleware.GetBoxData(req.Context())
|
box, err := middleware.GetBoxData(req.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.GetAPIError(errs.ErrAuthorizationHeaderMalformed)
|
return nil, errs.GetAPIError(errs.ErrAuthorizationHeaderMalformed)
|
||||||
|
@ -200,10 +263,18 @@ func newSignV4aChunkedReader(req *http.Request) (io.ReadCloser, error) {
|
||||||
|
|
||||||
newStreamSigner := v4a.NewStreamSigner(creds, "s3", seed)
|
newStreamSigner := v4a.NewStreamSigner(creds, "s3", seed)
|
||||||
|
|
||||||
|
var trailerHeaders []string
|
||||||
|
trailer := req.Header.Get("x-amz-trailer")
|
||||||
|
if trailer != "" {
|
||||||
|
trailerHeaders = strings.Split(trailer, ";")
|
||||||
|
}
|
||||||
|
|
||||||
return &s3v4aChunkReader{
|
return &s3v4aChunkReader{
|
||||||
reader: bufio.NewReader(req.Body),
|
reader: bufio.NewReader(req.Body),
|
||||||
streamSigner: newStreamSigner,
|
streamSigner: newStreamSigner,
|
||||||
requestTime: reqTime,
|
requestTime: reqTime,
|
||||||
buffer: make([]byte, 64*1024),
|
buffer: make([]byte, 64*1024),
|
||||||
|
trailerHeaders: trailerHeaders,
|
||||||
|
trailers: make(map[string]string, len(trailerHeaders)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,10 @@ const (
|
||||||
DefaultLocationConstraint = "default"
|
DefaultLocationConstraint = "default"
|
||||||
|
|
||||||
StreamingContentSHA256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
|
StreamingContentSHA256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
|
||||||
|
StreamingContentSHA256Trailer = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER"
|
||||||
StreamingContentV4aSHA256 = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD"
|
StreamingContentV4aSHA256 = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD"
|
||||||
|
StreamingContentV4aSHA256Trailer = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER"
|
||||||
|
StreamingUnsignedPayloadTrailer = "STREAMING-UNSIGNED-PAYLOAD-TRAILER"
|
||||||
|
|
||||||
DefaultStorageClass = "STANDARD"
|
DefaultStorageClass = "STANDARD"
|
||||||
)
|
)
|
||||||
|
@ -129,6 +132,8 @@ var SystemMetadata = map[string]struct{}{
|
||||||
func IsSignedStreamingV4(r *http.Request) (string, bool) {
|
func IsSignedStreamingV4(r *http.Request) (string, bool) {
|
||||||
shaHeader := r.Header.Get(AmzContentSha256)
|
shaHeader := r.Header.Get(AmzContentSha256)
|
||||||
return shaHeader,
|
return shaHeader,
|
||||||
(shaHeader == StreamingContentSHA256 || shaHeader == StreamingContentV4aSHA256) &&
|
(shaHeader == StreamingContentSHA256 || shaHeader == StreamingContentSHA256Trailer ||
|
||||||
|
shaHeader == StreamingContentV4aSHA256 || shaHeader == StreamingContentV4aSHA256Trailer ||
|
||||||
|
shaHeader == StreamingUnsignedPayloadTrailer) &&
|
||||||
r.Method == http.MethodPut
|
r.Method == http.MethodPut
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ var _ frostfs.FrostFS = (*TestFrostFS)(nil)
|
||||||
|
|
||||||
type TestFrostFS struct {
|
type TestFrostFS struct {
|
||||||
objects map[string]*object.Object
|
objects map[string]*object.Object
|
||||||
|
copiesNumbers map[string][]uint32
|
||||||
objectErrors map[string]error
|
objectErrors map[string]error
|
||||||
objectPutErrors map[string]error
|
objectPutErrors map[string]error
|
||||||
containers map[string]*container.Container
|
containers map[string]*container.Container
|
||||||
|
@ -88,6 +89,7 @@ type TestFrostFS struct {
|
||||||
func NewTestFrostFS(key *keys.PrivateKey) *TestFrostFS {
|
func NewTestFrostFS(key *keys.PrivateKey) *TestFrostFS {
|
||||||
return &TestFrostFS{
|
return &TestFrostFS{
|
||||||
objects: make(map[string]*object.Object),
|
objects: make(map[string]*object.Object),
|
||||||
|
copiesNumbers: make(map[string][]uint32),
|
||||||
objectErrors: make(map[string]error),
|
objectErrors: make(map[string]error),
|
||||||
objectPutErrors: make(map[string]error),
|
objectPutErrors: make(map[string]error),
|
||||||
containers: make(map[string]*container.Container),
|
containers: make(map[string]*container.Container),
|
||||||
|
@ -126,6 +128,10 @@ func (t *TestFrostFS) Objects() []*object.Object {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TestFrostFS) CopiesNumbers(addr string) []uint32 {
|
||||||
|
return t.copiesNumbers[addr]
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TestFrostFS) ObjectExists(objID oid.ID) bool {
|
func (t *TestFrostFS) ObjectExists(objID oid.ID) bool {
|
||||||
for _, obj := range t.objects {
|
for _, obj := range t.objects {
|
||||||
if id, _ := obj.ID(); id.Equals(objID) {
|
if id, _ := obj.ID(); id.Equals(objID) {
|
||||||
|
@ -346,6 +352,8 @@ func (t *TestFrostFS) CreateObject(ctx context.Context, prm frostfs.PrmObjectCre
|
||||||
|
|
||||||
addr := newAddress(cnrID, objID)
|
addr := newAddress(cnrID, objID)
|
||||||
t.objects[addr.EncodeToString()] = obj
|
t.objects[addr.EncodeToString()] = obj
|
||||||
|
t.copiesNumbers[addr.EncodeToString()] = prm.CopiesNumber
|
||||||
|
|
||||||
return &frostfs.CreateObjectResult{
|
return &frostfs.CreateObjectResult{
|
||||||
ObjectID: objID,
|
ObjectID: objID,
|
||||||
CreationEpoch: t.currentEpoch - 1,
|
CreationEpoch: t.currentEpoch - 1,
|
||||||
|
|
|
@ -61,7 +61,6 @@ type (
|
||||||
Info *UploadInfoParams
|
Info *UploadInfoParams
|
||||||
Header map[string]string
|
Header map[string]string
|
||||||
Data *UploadData
|
Data *UploadData
|
||||||
CopiesNumbers []uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadData struct {
|
UploadData struct {
|
||||||
|
@ -75,6 +74,7 @@ type (
|
||||||
Reader io.Reader
|
Reader io.Reader
|
||||||
ContentMD5 string
|
ContentMD5 string
|
||||||
ContentSHA256Hash string
|
ContentSHA256Hash string
|
||||||
|
CopiesNumbers []uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadCopyParams struct {
|
UploadCopyParams struct {
|
||||||
|
@ -85,11 +85,13 @@ type (
|
||||||
SrcEncryption encryption.Params
|
SrcEncryption encryption.Params
|
||||||
PartNumber int
|
PartNumber int
|
||||||
Range *RangeParams
|
Range *RangeParams
|
||||||
|
CopiesNumbers []uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
CompleteMultipartParams struct {
|
CompleteMultipartParams struct {
|
||||||
Info *UploadInfoParams
|
Info *UploadInfoParams
|
||||||
Parts []*CompletedPart
|
Parts []*CompletedPart
|
||||||
|
CopiesNumbers []uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletedPart struct {
|
CompletedPart struct {
|
||||||
|
@ -165,7 +167,6 @@ func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartPar
|
||||||
Owner: n.gateOwner,
|
Owner: n.gateOwner,
|
||||||
Created: TimeNow(ctx),
|
Created: TimeNow(ctx),
|
||||||
Meta: make(map[string]string, metaSize),
|
Meta: make(map[string]string, metaSize),
|
||||||
CopiesNumbers: p.CopiesNumbers,
|
|
||||||
CreationEpoch: networkInfo.CurrentEpoch(),
|
CreationEpoch: networkInfo.CurrentEpoch(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +223,7 @@ func (n *Layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf
|
||||||
Attributes: make([][2]string, 2),
|
Attributes: make([][2]string, 2),
|
||||||
Payload: p.Reader,
|
Payload: p.Reader,
|
||||||
CreationTime: TimeNow(ctx),
|
CreationTime: TimeNow(ctx),
|
||||||
CopiesNumber: multipartInfo.CopiesNumbers,
|
CopiesNumber: p.CopiesNumbers,
|
||||||
}
|
}
|
||||||
|
|
||||||
decSize := p.Size
|
decSize := p.Size
|
||||||
|
@ -376,6 +377,7 @@ func (n *Layer) UploadPartCopy(ctx context.Context, p *UploadCopyParams) (*data.
|
||||||
PartNumber: p.PartNumber,
|
PartNumber: p.PartNumber,
|
||||||
Size: size,
|
Size: size,
|
||||||
Reader: objPayload,
|
Reader: objPayload,
|
||||||
|
CopiesNumbers: p.CopiesNumbers,
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.uploadPart(ctx, multipartInfo, params)
|
return n.uploadPart(ctx, multipartInfo, params)
|
||||||
|
@ -472,7 +474,7 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
||||||
Header: initMetadata,
|
Header: initMetadata,
|
||||||
Size: &multipartObjetSize,
|
Size: &multipartObjetSize,
|
||||||
Encryption: p.Info.Encryption,
|
Encryption: p.Info.Encryption,
|
||||||
CopiesNumbers: multipartInfo.CopiesNumbers,
|
CopiesNumbers: p.CopiesNumbers,
|
||||||
CompleteMD5Hash: hex.EncodeToString(md5Hash.Sum(nil)) + "-" + strconv.Itoa(len(p.Parts)),
|
CompleteMD5Hash: hex.EncodeToString(md5Hash.Sum(nil)) + "-" + strconv.Itoa(len(p.Parts)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -525,10 +525,7 @@ func (n *Layer) objectPutAndHash(ctx context.Context, prm frostfs.PrmObjectCreat
|
||||||
})
|
})
|
||||||
res, err := n.frostFS.CreateObject(ctx, prm)
|
res, err := n.frostFS.CreateObject(ctx, prm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, errDiscard := io.Copy(io.Discard, prm.Payload); errDiscard != nil {
|
n.payloadDiscard(ctx, prm.Payload)
|
||||||
n.reqLogger(ctx).Warn(logs.FailedToDiscardPutPayloadProbablyGoroutineLeaks, zap.Error(errDiscard))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &data.CreatedObjectInfo{
|
return &data.CreatedObjectInfo{
|
||||||
|
@ -540,6 +537,14 @@ func (n *Layer) objectPutAndHash(ctx context.Context, prm frostfs.PrmObjectCreat
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Layer) payloadDiscard(ctx context.Context, payload io.Reader) {
|
||||||
|
if payload != nil {
|
||||||
|
if _, errDiscard := io.Copy(io.Discard, payload); errDiscard != nil {
|
||||||
|
n.reqLogger(ctx).Warn(logs.FailedToDiscardPutPayloadProbablyGoroutineLeaks, zap.Error(errDiscard))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type logWrapper struct {
|
type logWrapper struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,3 +49,16 @@ func TestGoroutinesDontLeakInPutAndHash(t *testing.T) {
|
||||||
require.ErrorIs(t, err, expErr)
|
require.ErrorIs(t, err, expErr)
|
||||||
require.Empty(t, payload.Len(), "body must be read out otherwise goroutines can leak in wrapReader")
|
require.Empty(t, payload.Len(), "body must be read out otherwise goroutines can leak in wrapReader")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNilPayloadPutAndHash(t *testing.T) {
|
||||||
|
tc := prepareContext(t)
|
||||||
|
prm := frostfs.PrmObjectCreate{
|
||||||
|
Filepath: tc.obj,
|
||||||
|
Payload: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
expErr := errors.New("some error")
|
||||||
|
tc.testFrostFS.SetObjectPutError(tc.obj, expErr)
|
||||||
|
_, err := tc.layer.objectPutAndHash(tc.ctx, prm, tc.bktInfo)
|
||||||
|
require.ErrorIs(t, err, expErr)
|
||||||
|
}
|
||||||
|
|
|
@ -1053,8 +1053,8 @@ func (a *App) updateServers() error {
|
||||||
if err := ser.UpdateCert(serverInfo.TLS.CertFile, serverInfo.TLS.KeyFile); err != nil {
|
if err := ser.UpdateCert(serverInfo.TLS.CertFile, serverInfo.TLS.KeyFile); err != nil {
|
||||||
return fmt.Errorf("failed to update tls certs: %w", err)
|
return fmt.Errorf("failed to update tls certs: %w", err)
|
||||||
}
|
}
|
||||||
found = true
|
|
||||||
}
|
}
|
||||||
|
found = true
|
||||||
} else if unbind := a.updateUnbindServerInfo(serverInfo); unbind {
|
} else if unbind := a.updateUnbindServerInfo(serverInfo); unbind {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -931,13 +931,17 @@ func newViper(flags *pflag.FlagSet) (*viper.Viper, error) {
|
||||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
v.AllowEmptyEnv(true)
|
v.AllowEmptyEnv(true)
|
||||||
|
|
||||||
|
if err := bindFlags(v, flags); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
setDefaults(v, flags)
|
setDefaults(v, flags)
|
||||||
|
|
||||||
if v.IsSet(cfgServer+".0."+cfgTLSKeyFile) && v.IsSet(cfgServer+".0."+cfgTLSCertFile) {
|
if v.IsSet(cfgServer+".0."+cfgTLSKeyFile) && v.IsSet(cfgServer+".0."+cfgTLSCertFile) {
|
||||||
v.Set(cfgServer+".0."+cfgTLSEnabled, true)
|
v.Set(cfgServer+".0."+cfgTLSEnabled, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, bindFlags(v, flags)
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSettings() *appCfg {
|
func newSettings() *appCfg {
|
||||||
|
|
|
@ -48,3 +48,12 @@ resolve_order:
|
||||||
require.Equal(t, []string{resolver.DNSResolver}, cfg.config().GetStringSlice(cfgResolveOrder))
|
require.Equal(t, []string{resolver.DNSResolver}, cfg.config().GetStringSlice(cfgResolveOrder))
|
||||||
require.Equal(t, 10, cfg.config().GetInt(cfgMaxClientsCount))
|
require.Equal(t, 10, cfg.config().GetInt(cfgMaxClientsCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetTLSEnabled(t *testing.T) {
|
||||||
|
cfg := newSettings()
|
||||||
|
|
||||||
|
require.NoError(t, cfg.flags.Parse([]string{"--" + cfgTLSCertFile, "tls.crt", "--" + cfgTLSKeyFile, "tls.key"}))
|
||||||
|
require.NoError(t, cfg.reload())
|
||||||
|
|
||||||
|
require.True(t, cfg.config().GetBool(cfgServer+".0."+cfgTLSEnabled))
|
||||||
|
}
|
||||||
|
|
|
@ -64,6 +64,8 @@ $ frostfs-s3-gw --listen_address 192.168.130.130:443 \
|
||||||
|
|
||||||
Using these flag you can configure only one address. To set multiple addresses use yaml config.
|
Using these flag you can configure only one address. To set multiple addresses use yaml config.
|
||||||
|
|
||||||
|
**Note:** It's not recommended to configure addresses via flags and yaml config at the same time.
|
||||||
|
|
||||||
### RPC endpoint and resolving of bucket names
|
### RPC endpoint and resolving of bucket names
|
||||||
|
|
||||||
To set RPC endpoint specify a value of parameter `-r` or `--rpc_endpoint`. The parameter is **required if** another
|
To set RPC endpoint specify a value of parameter `-r` or `--rpc_endpoint`. The parameter is **required if** another
|
||||||
|
|
47
go.mod
47
go.mod
|
@ -5,13 +5,15 @@ go 1.22
|
||||||
require (
|
require (
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.20.1-0.20241022094040-5f956751d48b
|
git.frostfs.info/TrueCloudLab/frostfs-contract v0.20.1-0.20241022094040-5f956751d48b
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88
|
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241218062344-42a0fc8c13ae
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250130095343-593dd77d841a
|
||||||
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972
|
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972
|
||||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b
|
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240822104152-a3bc3099bd5b
|
||||||
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02
|
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20240124114243-cb2e66427d02
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.5
|
github.com/aws/aws-sdk-go-v2 v1.34.0
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.32
|
github.com/aws/aws-sdk-go-v2/config v1.27.32
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.31
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.31
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1
|
||||||
|
github.com/aws/smithy-go v1.22.2
|
||||||
github.com/bluele/gcache v0.0.2
|
github.com/bluele/gcache v0.0.2
|
||||||
github.com/go-chi/chi/v5 v5.0.8
|
github.com/go-chi/chi/v5 v5.0.8
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
@ -28,16 +30,16 @@ require (
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4
|
github.com/trailofbits/go-fuzz-utils v0.0.0-20230413173806-58c38daa3cb4
|
||||||
github.com/urfave/cli/v2 v2.27.2
|
github.com/urfave/cli/v2 v2.27.2
|
||||||
go.opentelemetry.io/otel v1.28.0
|
go.opentelemetry.io/otel v1.31.0
|
||||||
go.opentelemetry.io/otel/trace v1.28.0
|
go.opentelemetry.io/otel/trace v1.31.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/crypto v0.24.0
|
golang.org/x/crypto v0.31.0
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
golang.org/x/net v0.26.0
|
golang.org/x/net v0.30.0
|
||||||
golang.org/x/sys v0.22.0
|
golang.org/x/sys v0.28.0
|
||||||
golang.org/x/text v0.16.0
|
golang.org/x/text v0.21.0
|
||||||
google.golang.org/grpc v1.66.2
|
google.golang.org/grpc v1.69.2
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.36.1
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,16 +50,19 @@ require (
|
||||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
||||||
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
github.com/VictoriaMetrics/easyproto v0.1.4 // indirect
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.29 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6 // indirect
|
||||||
github.com/aws/smithy-go v1.20.4 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
@ -108,14 +113,14 @@ require (
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.31.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/term v0.21.0 // indirect
|
golang.org/x/term v0.27.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.2.1 // indirect
|
lukechampine.com/blake3 v1.2.1 // indirect
|
||||||
|
|
98
go.sum
98
go.sum
|
@ -42,8 +42,8 @@ git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSV
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
|
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88 h1:9bvBDLApbbO5sXBKdODpE9tzy3HV99nXxkDWNn22rdI=
|
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88 h1:9bvBDLApbbO5sXBKdODpE9tzy3HV99nXxkDWNn22rdI=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88/go.mod h1:kbwB4v2o6RyOfCo9kEFeUDZIX3LKhmS0yXPrtvzkQ1g=
|
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20241112082307-f17779933e88/go.mod h1:kbwB4v2o6RyOfCo9kEFeUDZIX3LKhmS0yXPrtvzkQ1g=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241218062344-42a0fc8c13ae h1:7gvuOTmS3oaOM79JkHWWlsvGqIRqsum5KnOI1TYqfn0=
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250130095343-593dd77d841a h1:Ud+3zz4WP9HPxEQxDPJZPpiPdm30nDNSKucsWP9L54M=
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20241218062344-42a0fc8c13ae/go.mod h1:dbWUc5jOBTXVvssCLCYxkkSTL9jgLr1KruGP2FMAfiM=
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20250130095343-593dd77d841a/go.mod h1:aQpPWfG8oyfJ2X+FenPTJpSRWZjwcP5/RAtkW+/VEX8=
|
||||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
|
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
|
||||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
|
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
|
||||||
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 h1:/960fWeyn2AFHwQUwDsWB3sbP6lTEnFnMzLMM6tx6N8=
|
git.frostfs.info/TrueCloudLab/multinet v0.0.0-20241015075604-6cb0d80e0972 h1:/960fWeyn2AFHwQUwDsWB3sbP6lTEnFnMzLMM6tx6N8=
|
||||||
|
@ -62,32 +62,42 @@ github.com/VictoriaMetrics/easyproto v0.1.4 h1:r8cNvo8o6sR4QShBXQd1bKw/VVLSQma/V
|
||||||
github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710=
|
github.com/VictoriaMetrics/easyproto v0.1.4/go.mod h1:QlGlzaJnDfFd8Lk6Ci/fuLxfTo3/GThPs2KH23mv710=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g=
|
github.com/aws/aws-sdk-go-v2 v1.34.0 h1:9iyL+cjifckRGEVpRKZP3eIxVlL06Qk1Tk13vreaVQU=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0=
|
github.com/aws/aws-sdk-go-v2 v1.34.0/go.mod h1:JgstGg0JjWU1KpVJjD5H0y0yyAIpSdKEq556EI6yOOM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 h1:zAxi9p3wsZMIaVCdoiQp2uZ9k1LsZvmAnoTBeZPXom0=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8/go.mod h1:3XkePX5dSaxveLAYY7nsbsZZrKxCyEuE5pM4ziFxyGg=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.32 h1:jnAMVTJTpAQlePCUUlnXnllHEMGVWmvUJOiGjgtS9S0=
|
github.com/aws/aws-sdk-go-v2/config v1.27.32 h1:jnAMVTJTpAQlePCUUlnXnllHEMGVWmvUJOiGjgtS9S0=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.27.32/go.mod h1:JibtzKJoXT0M/MhoYL6qfCk7nm/MppwukDFZtdgVRoY=
|
github.com/aws/aws-sdk-go-v2/config v1.27.32/go.mod h1:JibtzKJoXT0M/MhoYL6qfCk7nm/MppwukDFZtdgVRoY=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.31 h1:jtyfcOfgoqWA2hW/E8sFbwdfgwD3APnF9CLCKE8dTyw=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.31 h1:jtyfcOfgoqWA2hW/E8sFbwdfgwD3APnF9CLCKE8dTyw=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.31/go.mod h1:RSgY5lfCfw+FoyKWtOpLolPlfQVdDBQWTUniAaE+NKY=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.31/go.mod h1:RSgY5lfCfw+FoyKWtOpLolPlfQVdDBQWTUniAaE+NKY=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29 h1:Ej0Rf3GMv50Qh4G4852j2djtoDb7AzQ7MuQeFHa3D70=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.29/go.mod h1:oeNTC7PwJNoM5AznVr23wxhLnuJv0ZDe5v7w0wqIs9M=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29 h1:6e8a71X+9GfghragVevC5bZqvATtc3mAMgxpSNbgzF0=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.29/go.mod h1:c4jkZiQ+BWpNqq7VtrxjwISrLrt/VvPq3XiopkUIolI=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.29 h1:g9OUETuxA8i/Www5Cby0R3WSTe7ppFTZXHVLNskNS4w=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.29/go.mod h1:CQk+koLR1QeY1+vm7lqNfFii07DEderKq6T3F1L2pyc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.3 h1:EP1ITDgYVPM2dL1bBBntJ7AW5yTjuWGz9XO+CZwpALU=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.3/go.mod h1:5lWNWeAgWenJ/BZ/CP9k9DjLbC0pjnM045WjXRPPi14=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10 h1:hN4yJBGswmFTOVYqmbz1GBs9ZMtQe8SrYxPwrkrlRv8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.10/go.mod h1:TsxON4fEZXyrKY+D+3d2gSTyJkGORexIYab9PTf56DA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.10 h1:fXoWC2gi7tdJYNTPnnlSGzEVwewUchOi8xVq/dkg8Qs=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.10/go.mod h1:cvzBApD5dVazHU8C2rbBQzzzsKc8m5+wNJ9mCRZLKPc=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1 h1:9LawY3cDJ3HE+v2GMd5SOkNLDwgN4K7TsCjyVBYu/L4=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1/go.mod h1:hHnELVnIHltd8EOF3YzahVX6F6y2C6dNqpRj1IMkS5I=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6 h1:o++HUDXlbrTl4PSal3YHtdErQxB8mDGAtkKNXBWPfIU=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6 h1:o++HUDXlbrTl4PSal3YHtdErQxB8mDGAtkKNXBWPfIU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.22.6/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6 h1:yCHcQCOwTfIsc8DoEhM3qXPxD+j8CbI6t1K3dNzsWV0=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6 h1:yCHcQCOwTfIsc8DoEhM3qXPxD+j8CbI6t1K3dNzsWV0=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.6/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6 h1:TrQadF7GcqvQ63kgwEcjlrVc2Fa0wpgLT0xtc73uAd8=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6 h1:TrQadF7GcqvQ63kgwEcjlrVc2Fa0wpgLT0xtc73uAd8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.30.6/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
|
||||||
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
|
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||||
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
|
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
|
||||||
|
@ -164,6 +174,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
@ -365,20 +377,22 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ=
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ=
|
||||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y=
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y=
|
||||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||||
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
|
||||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
|
||||||
|
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||||
|
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
@ -396,8 +410,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-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-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.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -469,8 +483,8 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -490,8 +504,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.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-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -535,19 +549,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/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.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -668,10 +682,10 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
@ -688,8 +702,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
@ -700,8 +714,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|
Loading…
Add table
Reference in a new issue