mirror of
https://github.com/versity/versitygw.git
synced 2026-01-25 04:22:02 +00:00
Compare commits
93 Commits
feat/bette
...
v1.0.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c9da2629d | ||
|
|
fd4bb8ffbc | ||
|
|
f35cdfb20c | ||
|
|
888fdd3688 | ||
|
|
646dc674b0 | ||
|
|
86fe01ede0 | ||
|
|
4add647501 | ||
|
|
748912fb3d | ||
|
|
30ad7111a6 | ||
|
|
f2c2f18ac7 | ||
|
|
c72ab8ee27 | ||
|
|
22cfacb987 | ||
|
|
da3c6211bd | ||
|
|
d14c1141f9 | ||
|
|
94d399775e | ||
|
|
c29fc6a261 | ||
|
|
7f3302ba9e | ||
|
|
ca2c8d2f48 | ||
|
|
da721b77f0 | ||
|
|
5dc3cd6889 | ||
|
|
fad19579ad | ||
|
|
a0f67936d0 | ||
|
|
c094086d83 | ||
|
|
cca9fee532 | ||
|
|
fcd50d2d1f | ||
|
|
ac6e17fd70 | ||
|
|
c37a22ffe1 | ||
|
|
9f78b94464 | ||
|
|
fe7d5d58cc | ||
|
|
cdd12dab5a | ||
|
|
fe584a8f63 | ||
|
|
04e71c44e9 | ||
|
|
5e35f89aa2 | ||
|
|
4025172897 | ||
|
|
ee315276f6 | ||
|
|
cc7ea685d9 | ||
|
|
2d75ef2d55 | ||
|
|
b983dadce9 | ||
|
|
5e83419e2c | ||
|
|
34cadaeca0 | ||
|
|
4e5586d729 | ||
|
|
a1eb66db91 | ||
|
|
1aaf99dafd | ||
|
|
02b907bc90 | ||
|
|
f487d2602c | ||
|
|
5529796ccd | ||
|
|
66ed32baca | ||
|
|
ea2184077a | ||
|
|
0a72c65b76 | ||
|
|
5932fd5e4e | ||
|
|
ba561f55ab | ||
|
|
b0c310aca6 | ||
|
|
47cf2a69c1 | ||
|
|
7c5258e6e9 | ||
|
|
59a4eab6ae | ||
|
|
1bfe331edf | ||
|
|
f471e39f4b | ||
|
|
8d4a8fc5e0 | ||
|
|
60700e3fa7 | ||
|
|
847993a514 | ||
|
|
10decc0d2c | ||
|
|
c17db864cd | ||
|
|
7e530ee8ae | ||
|
|
94d23cce9a | ||
|
|
3e99c0de0c | ||
|
|
2a61489e4c | ||
|
|
5358abca3f | ||
|
|
0704069262 | ||
|
|
70b700d577 | ||
|
|
47a6152e84 | ||
|
|
94fc70f5a7 | ||
|
|
c77604b545 | ||
|
|
adb69ed041 | ||
|
|
c65a355bd9 | ||
|
|
a43eec0ae7 | ||
|
|
57d344a8f0 | ||
|
|
7218926ac5 | ||
|
|
d9591f694e | ||
|
|
0f2c727990 | ||
|
|
80b316fccf | ||
|
|
a1aef5d559 | ||
|
|
1e5c1780c9 | ||
|
|
3f2de08549 | ||
|
|
568f8346bf | ||
|
|
a6d61e1dde | ||
|
|
3e9c5b883f | ||
|
|
b9e464bbd0 | ||
|
|
86440ec7da | ||
|
|
db305142f1 | ||
|
|
a26f069c53 | ||
|
|
8fb6227e31 | ||
|
|
6cf73eaabb | ||
|
|
0312a1e3dc |
1
.github/workflows/docker-bats.yml
vendored
1
.github/workflows/docker-bats.yml
vendored
@@ -14,6 +14,7 @@ jobs:
|
||||
run: |
|
||||
cp tests/.env.docker.default tests/.env.docker
|
||||
cp tests/.secrets.default tests/.secrets
|
||||
# see https://github.com/versity/versitygw/issues/1034
|
||||
docker build \
|
||||
--build-arg="GO_LIBRARY=go1.23.1.linux-amd64.tar.gz" \
|
||||
--build-arg="AWS_CLI=awscli-exe-linux-x86_64.zip" \
|
||||
|
||||
30
.github/workflows/system.yml
vendored
30
.github/workflows/system.yml
vendored
@@ -73,16 +73,17 @@ jobs:
|
||||
RUN_SET: "s3api-user"
|
||||
RECREATE_BUCKETS: "false"
|
||||
BACKEND: "posix"
|
||||
- set: "s3api, s3, multipart|object, non-static, folder IAM"
|
||||
IAM_TYPE: folder
|
||||
RUN_SET: "s3api-bucket,s3api-object,s3api-multipart"
|
||||
RECREATE_BUCKETS: "true"
|
||||
BACKEND: "s3"
|
||||
- set: "s3api, s3, policy|user, non-static, folder IAM"
|
||||
IAM_TYPE: folder
|
||||
RUN_SET: "s3api-policy,s3api-user"
|
||||
RECREATE_BUCKETS: "true"
|
||||
BACKEND: "s3"
|
||||
# TODO fix/debug s3 gateway
|
||||
#- set: "s3api, s3, multipart|object, non-static, folder IAM"
|
||||
# IAM_TYPE: folder
|
||||
# RUN_SET: "s3api-bucket,s3api-object,s3api-multipart"
|
||||
# RECREATE_BUCKETS: "true"
|
||||
# BACKEND: "s3"
|
||||
#- set: "s3api, s3, policy|user, non-static, folder IAM"
|
||||
# IAM_TYPE: folder
|
||||
# RUN_SET: "s3api-policy,s3api-user"
|
||||
# RECREATE_BUCKETS: "true"
|
||||
# BACKEND: "s3"
|
||||
- set: "s3cmd, posix, file count, non-static, folder IAM"
|
||||
IAM_TYPE: folder
|
||||
RUN_SET: "s3cmd-file-count"
|
||||
@@ -132,6 +133,14 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get install libxml2-utils
|
||||
|
||||
# see https://github.com/versity/versitygw/issues/1034
|
||||
- name: Install AWS cli
|
||||
run: |
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.22.35.zip" -o "awscliv2.zip"
|
||||
unzip -o awscliv2.zip
|
||||
./aws/install -i ${{ github.workspace }}/aws-cli -b ${{ github.workspace }}/bin
|
||||
echo "${{ github.workspace }}/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build and run
|
||||
env:
|
||||
IAM_TYPE: ${{ matrix.IAM_TYPE }}
|
||||
@@ -170,6 +179,7 @@ jobs:
|
||||
export AWS_REGION=us-east-1
|
||||
export AWS_ACCESS_KEY_ID_TWO=user
|
||||
export AWS_SECRET_ACCESS_KEY_TWO=pass
|
||||
export AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED
|
||||
aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID --profile versity
|
||||
aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY --profile versity
|
||||
aws configure set aws_region $AWS_REGION --profile versity
|
||||
|
||||
159
auth/acl.go
159
auth/acl.go
@@ -17,6 +17,7 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -33,12 +34,13 @@ type ACL struct {
|
||||
}
|
||||
|
||||
type Grantee struct {
|
||||
Permission types.Permission
|
||||
Permission Permission
|
||||
Access string
|
||||
Type types.Type
|
||||
}
|
||||
|
||||
type GetBucketAclOutput struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ AccessControlPolicy"`
|
||||
Owner *types.Owner
|
||||
AccessControlList AccessControlList
|
||||
}
|
||||
@@ -59,20 +61,124 @@ type AccessControlPolicy struct {
|
||||
Owner *types.Owner
|
||||
}
|
||||
|
||||
func (acp *AccessControlPolicy) Validate() error {
|
||||
if !acp.AccessControlList.isValid() {
|
||||
return s3err.GetAPIError(s3err.ErrMalformedACL)
|
||||
}
|
||||
|
||||
// The Owner can't be nil
|
||||
if acp.Owner == nil {
|
||||
return s3err.GetAPIError(s3err.ErrMalformedACL)
|
||||
}
|
||||
|
||||
// The Owner ID can't be empty
|
||||
if acp.Owner.ID == nil || *acp.Owner.ID == "" {
|
||||
return s3err.GetAPIError(s3err.ErrMalformedACL)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type AccessControlList struct {
|
||||
Grants []Grant `xml:"Grant"`
|
||||
}
|
||||
|
||||
// Validates the AccessControlList
|
||||
func (acl *AccessControlList) isValid() bool {
|
||||
for _, el := range acl.Grants {
|
||||
if !el.isValid() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type Permission string
|
||||
|
||||
const (
|
||||
PermissionFullControl Permission = "FULL_CONTROL"
|
||||
PermissionWrite Permission = "WRITE"
|
||||
PermissionWriteAcp Permission = "WRITE_ACP"
|
||||
PermissionRead Permission = "READ"
|
||||
PermissionReadAcp Permission = "READ_ACP"
|
||||
)
|
||||
|
||||
// Check if the permission is valid
|
||||
func (p Permission) isValid() bool {
|
||||
return p == PermissionFullControl ||
|
||||
p == PermissionRead ||
|
||||
p == PermissionReadAcp ||
|
||||
p == PermissionWrite ||
|
||||
p == PermissionWriteAcp
|
||||
}
|
||||
|
||||
type Grant struct {
|
||||
Grantee *Grt
|
||||
Permission types.Permission
|
||||
Grantee *Grt `xml:"Grantee"`
|
||||
Permission Permission `xml:"Permission"`
|
||||
}
|
||||
|
||||
// Checks if Grant is valid
|
||||
func (g *Grant) isValid() bool {
|
||||
return g.Permission.isValid() && g.Grantee.isValid()
|
||||
}
|
||||
|
||||
type Grt struct {
|
||||
XMLNS string `xml:"xmlns:xsi,attr"`
|
||||
XMLXSI types.Type `xml:"xsi:type,attr"`
|
||||
Type types.Type `xml:"Type"`
|
||||
ID string `xml:"ID"`
|
||||
XMLNS string `xml:"xmlns:xsi,attr"`
|
||||
Type types.Type `xml:"xsi:type,attr"`
|
||||
ID string `xml:"ID"`
|
||||
}
|
||||
|
||||
// Custom Unmarshalling for Grt to parse xsi:type properly
|
||||
func (g *Grt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
// Iterate through the XML tokens to process the attributes
|
||||
for _, attr := range start.Attr {
|
||||
// Check if the attribute is xsi:type and belongs to the xsi namespace
|
||||
if attr.Name.Space == "http://www.w3.org/2001/XMLSchema-instance" && attr.Name.Local == "type" {
|
||||
g.Type = types.Type(attr.Value)
|
||||
}
|
||||
// Handle xmlns:xsi
|
||||
if attr.Name.Local == "xmlns:xsi" {
|
||||
g.XMLNS = attr.Value
|
||||
}
|
||||
}
|
||||
|
||||
// Decode the inner XML elements like ID
|
||||
for {
|
||||
t, err := d.Token()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch se := t.(type) {
|
||||
case xml.StartElement:
|
||||
if se.Name.Local == "ID" {
|
||||
if err := d.DecodeElement(&g.ID, &se); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case xml.EndElement:
|
||||
if se.Name.Local == start.Name.Local {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validates Grt
|
||||
func (g *Grt) isValid() bool {
|
||||
// Validate the Type
|
||||
// Only these 2 types are supported in the gateway
|
||||
if g.Type != types.TypeCanonicalUser && g.Type != types.TypeGroup {
|
||||
return false
|
||||
}
|
||||
|
||||
// The ID prop shouldn't be empty
|
||||
if g.ID == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func ParseACL(data []byte) (ACL, error) {
|
||||
@@ -99,10 +205,9 @@ func ParseACLOutput(data []byte) (GetBucketAclOutput, error) {
|
||||
acs := elem.Access
|
||||
grants = append(grants, Grant{
|
||||
Grantee: &Grt{
|
||||
XMLNS: "http://www.w3.org/2001/XMLSchema-instance",
|
||||
XMLXSI: elem.Type,
|
||||
ID: acs,
|
||||
Type: elem.Type,
|
||||
XMLNS: "http://www.w3.org/2001/XMLSchema-instance",
|
||||
ID: acs,
|
||||
Type: elem.Type,
|
||||
},
|
||||
Permission: elem.Permission,
|
||||
})
|
||||
@@ -125,7 +230,7 @@ func UpdateACL(input *PutBucketAclInput, acl ACL, iam IAMService, isAdmin bool)
|
||||
|
||||
defaultGrantees := []Grantee{
|
||||
{
|
||||
Permission: types.PermissionFullControl,
|
||||
Permission: PermissionFullControl,
|
||||
Access: acl.Owner,
|
||||
Type: types.TypeCanonicalUser,
|
||||
},
|
||||
@@ -136,19 +241,19 @@ func UpdateACL(input *PutBucketAclInput, acl ACL, iam IAMService, isAdmin bool)
|
||||
switch input.ACL {
|
||||
case types.BucketCannedACLPublicRead:
|
||||
defaultGrantees = append(defaultGrantees, Grantee{
|
||||
Permission: types.PermissionRead,
|
||||
Permission: PermissionRead,
|
||||
Access: "all-users",
|
||||
Type: types.TypeGroup,
|
||||
})
|
||||
case types.BucketCannedACLPublicReadWrite:
|
||||
defaultGrantees = append(defaultGrantees, []Grantee{
|
||||
{
|
||||
Permission: types.PermissionRead,
|
||||
Permission: PermissionRead,
|
||||
Access: "all-users",
|
||||
Type: types.TypeGroup,
|
||||
},
|
||||
{
|
||||
Permission: types.PermissionWrite,
|
||||
Permission: PermissionWrite,
|
||||
Access: "all-users",
|
||||
Type: types.TypeGroup,
|
||||
},
|
||||
@@ -165,7 +270,7 @@ func UpdateACL(input *PutBucketAclInput, acl ACL, iam IAMService, isAdmin bool)
|
||||
for _, str := range fullControlList {
|
||||
defaultGrantees = append(defaultGrantees, Grantee{
|
||||
Access: str,
|
||||
Permission: types.PermissionFullControl,
|
||||
Permission: PermissionFullControl,
|
||||
Type: types.TypeCanonicalUser,
|
||||
})
|
||||
}
|
||||
@@ -175,7 +280,7 @@ func UpdateACL(input *PutBucketAclInput, acl ACL, iam IAMService, isAdmin bool)
|
||||
for _, str := range readList {
|
||||
defaultGrantees = append(defaultGrantees, Grantee{
|
||||
Access: str,
|
||||
Permission: types.PermissionRead,
|
||||
Permission: PermissionRead,
|
||||
Type: types.TypeCanonicalUser,
|
||||
})
|
||||
}
|
||||
@@ -185,7 +290,7 @@ func UpdateACL(input *PutBucketAclInput, acl ACL, iam IAMService, isAdmin bool)
|
||||
for _, str := range readACPList {
|
||||
defaultGrantees = append(defaultGrantees, Grantee{
|
||||
Access: str,
|
||||
Permission: types.PermissionReadAcp,
|
||||
Permission: PermissionReadAcp,
|
||||
Type: types.TypeCanonicalUser,
|
||||
})
|
||||
}
|
||||
@@ -195,7 +300,7 @@ func UpdateACL(input *PutBucketAclInput, acl ACL, iam IAMService, isAdmin bool)
|
||||
for _, str := range writeList {
|
||||
defaultGrantees = append(defaultGrantees, Grantee{
|
||||
Access: str,
|
||||
Permission: types.PermissionWrite,
|
||||
Permission: PermissionWrite,
|
||||
Type: types.TypeCanonicalUser,
|
||||
})
|
||||
}
|
||||
@@ -205,7 +310,7 @@ func UpdateACL(input *PutBucketAclInput, acl ACL, iam IAMService, isAdmin bool)
|
||||
for _, str := range writeACPList {
|
||||
defaultGrantees = append(defaultGrantees, Grantee{
|
||||
Access: str,
|
||||
Permission: types.PermissionWriteAcp,
|
||||
Permission: PermissionWriteAcp,
|
||||
Type: types.TypeCanonicalUser,
|
||||
})
|
||||
}
|
||||
@@ -262,8 +367,8 @@ func CheckIfAccountsExist(accs []string, iam IAMService) ([]string, error) {
|
||||
result = append(result, acc)
|
||||
continue
|
||||
}
|
||||
if err == ErrNotSupported {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
if errors.Is(err, s3err.GetAPIError(s3err.ErrAdminMethodNotSupported)) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("check user account: %w", err)
|
||||
}
|
||||
@@ -286,7 +391,7 @@ func splitUnique(s, divider string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
func verifyACL(acl ACL, access string, permission types.Permission) error {
|
||||
func verifyACL(acl ACL, access string, permission Permission) error {
|
||||
grantee := Grantee{
|
||||
Access: access,
|
||||
Permission: permission,
|
||||
@@ -294,7 +399,7 @@ func verifyACL(acl ACL, access string, permission types.Permission) error {
|
||||
}
|
||||
granteeFullCtrl := Grantee{
|
||||
Access: access,
|
||||
Permission: types.PermissionFullControl,
|
||||
Permission: PermissionFullControl,
|
||||
Type: types.TypeCanonicalUser,
|
||||
}
|
||||
granteeAllUsers := Grantee{
|
||||
@@ -353,7 +458,7 @@ func IsAdminOrOwner(acct Account, isRoot bool, acl ACL) error {
|
||||
|
||||
type AccessOptions struct {
|
||||
Acl ACL
|
||||
AclPermission types.Permission
|
||||
AclPermission Permission
|
||||
IsRoot bool
|
||||
Acc Account
|
||||
Bucket string
|
||||
@@ -364,7 +469,7 @@ type AccessOptions struct {
|
||||
|
||||
func VerifyAccess(ctx context.Context, be backend.Backend, opts AccessOptions) error {
|
||||
if opts.Readonly {
|
||||
if opts.AclPermission == types.PermissionWrite || opts.AclPermission == types.PermissionWriteAcp {
|
||||
if opts.AclPermission == PermissionWrite || opts.AclPermission == PermissionWriteAcp {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
}
|
||||
@@ -422,7 +527,7 @@ func VerifyObjectCopyAccess(ctx context.Context, be backend.Backend, copySource
|
||||
|
||||
if err := VerifyAccess(ctx, be, AccessOptions{
|
||||
Acl: srcBucketAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: PermissionRead,
|
||||
IsRoot: opts.IsRoot,
|
||||
Acc: opts.Acc,
|
||||
Bucket: srcBucket,
|
||||
|
||||
@@ -124,6 +124,12 @@ type Opts struct {
|
||||
CacheDisable bool
|
||||
CacheTTL int
|
||||
CachePrune int
|
||||
IpaHost string
|
||||
IpaVaultName string
|
||||
IpaUser string
|
||||
IpaPassword string
|
||||
IpaInsecure bool
|
||||
IpaDebug bool
|
||||
}
|
||||
|
||||
func New(o *Opts) (IAMService, error) {
|
||||
@@ -149,6 +155,9 @@ func New(o *Opts) (IAMService, error) {
|
||||
o.VaultMountPath, o.VaultRootToken, o.VaultRoleId, o.VaultRoleSecret,
|
||||
o.VaultServerCert, o.VaultClientCert, o.VaultClientCertKey)
|
||||
fmt.Printf("initializing Vault IAM with %q\n", o.VaultEndpointURL)
|
||||
case o.IpaHost != "":
|
||||
svc, err = NewIpaIAMService(o.RootAccount, o.IpaHost, o.IpaVaultName, o.IpaUser, o.IpaPassword, o.IpaInsecure, o.IpaDebug)
|
||||
fmt.Printf("initializing IPA IAM with %q\n", o.IpaHost)
|
||||
default:
|
||||
// if no iam options selected, default to the single user mode
|
||||
fmt.Println("No IAM service configured, enabling single account mode")
|
||||
|
||||
446
auth/iam_ipa.go
Normal file
446
auth/iam_ipa.go
Normal file
@@ -0,0 +1,446 @@
|
||||
// Copyright 2025 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const IpaVersion = "2.254"
|
||||
|
||||
type IpaIAMService struct {
|
||||
client http.Client
|
||||
id int
|
||||
version string
|
||||
host string
|
||||
vaultName string
|
||||
username string
|
||||
password string
|
||||
kraTransportKey *rsa.PublicKey
|
||||
debug bool
|
||||
rootAcc Account
|
||||
}
|
||||
|
||||
var _ IAMService = &IpaIAMService{}
|
||||
|
||||
func NewIpaIAMService(rootAcc Account, host, vaultName, username, password string, isInsecure, debug bool) (*IpaIAMService, error) {
|
||||
|
||||
ipa := IpaIAMService{
|
||||
id: 0,
|
||||
version: IpaVersion,
|
||||
host: host,
|
||||
vaultName: vaultName,
|
||||
username: username,
|
||||
password: password,
|
||||
debug: debug,
|
||||
rootAcc: rootAcc,
|
||||
}
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
// this should never happen
|
||||
return nil, fmt.Errorf("cookie jar creation: %w", err)
|
||||
}
|
||||
|
||||
mTLSConfig := &tls.Config{InsecureSkipVerify: isInsecure}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: mTLSConfig,
|
||||
}
|
||||
ipa.client = http.Client{Jar: jar, Transport: tr}
|
||||
|
||||
err = ipa.login()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ipa login failed: %w", err)
|
||||
}
|
||||
|
||||
req, err := ipa.newRequest("vaultconfig_show/1", []string{}, map[string]any{"all": true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ipa vaultconfig_show: %w", err)
|
||||
}
|
||||
vaultConfig := struct {
|
||||
Kra_Server_Server []string
|
||||
Transport_Cert Base64EncodedWrapped
|
||||
Wrapping_default_algorithm string
|
||||
Wrapping_supported_algorithms []string
|
||||
}{}
|
||||
err = ipa.rpc(req, &vaultConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ipa vault config: %w", err)
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(vaultConfig.Transport_Cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ipa cannot parse vault certificate: %w", err)
|
||||
}
|
||||
|
||||
ipa.kraTransportKey = cert.PublicKey.(*rsa.PublicKey)
|
||||
|
||||
isSupported := false
|
||||
for _, algo := range vaultConfig.Wrapping_supported_algorithms {
|
||||
if algo == "aes-128-cbc" {
|
||||
isSupported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isSupported {
|
||||
return nil,
|
||||
fmt.Errorf("IPA vault does not support aes-128-cbc. Only %v supported",
|
||||
vaultConfig.Wrapping_supported_algorithms)
|
||||
}
|
||||
return &ipa, nil
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) CreateAccount(account Account) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) GetUserAccount(access string) (Account, error) {
|
||||
if access == ipa.rootAcc.Access {
|
||||
return ipa.rootAcc, nil
|
||||
}
|
||||
|
||||
req, err := ipa.newRequest("user_show/1", []string{access}, map[string]any{})
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("ipa user_show: %w", err)
|
||||
}
|
||||
|
||||
userResult := struct {
|
||||
Gidnumber []string
|
||||
Uidnumber []string
|
||||
}{}
|
||||
|
||||
err = ipa.rpc(req, &userResult)
|
||||
if err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
|
||||
uid, err := strconv.Atoi(userResult.Uidnumber[0])
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("ipa uid invalid: %w", err)
|
||||
}
|
||||
gid, err := strconv.Atoi(userResult.Gidnumber[0])
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("ipa gid invalid: %w", err)
|
||||
}
|
||||
|
||||
account := Account{
|
||||
Access: access,
|
||||
Role: RoleUser,
|
||||
UserID: uid,
|
||||
GroupID: gid,
|
||||
}
|
||||
|
||||
session_key := make([]byte, 16)
|
||||
|
||||
_, err = rand.Read(session_key)
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("ipa cannot generate session key: %w", err)
|
||||
}
|
||||
|
||||
encryptedKey, err := rsa.EncryptPKCS1v15(rand.Reader, ipa.kraTransportKey, session_key)
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("ipa vault secret retrieval: %w", err)
|
||||
}
|
||||
|
||||
req, err = ipa.newRequest("vault_retrieve_internal/1", []string{ipa.vaultName},
|
||||
map[string]any{"username": access,
|
||||
"session_key": Base64EncodedWrapped(encryptedKey),
|
||||
"wrapping_algo": "aes-128-cbc"})
|
||||
if err != nil {
|
||||
return Account{}, fmt.Errorf("ipa vault_retrieve_internal: %w", err)
|
||||
}
|
||||
|
||||
data := struct {
|
||||
Vault_data Base64EncodedWrapped
|
||||
Nonce Base64EncodedWrapped
|
||||
}{}
|
||||
|
||||
err = ipa.rpc(req, &data)
|
||||
if err != nil {
|
||||
return account, err
|
||||
}
|
||||
|
||||
aes, err := aes.NewCipher(session_key)
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("ipa cannot create AES cipher: %w", err)
|
||||
}
|
||||
cbc := cipher.NewCBCDecrypter(aes, data.Nonce)
|
||||
cbc.CryptBlocks(data.Vault_data, data.Vault_data)
|
||||
secretUnpaddedJson, err := pkcs7Unpad(data.Vault_data, 16)
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("ipa cannot unpad decrypted result: %w", err)
|
||||
}
|
||||
|
||||
secret := struct {
|
||||
Data Base64Encoded
|
||||
}{}
|
||||
json.Unmarshal(secretUnpaddedJson, &secret)
|
||||
account.Secret = string(secret.Data)
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) UpdateUserAccount(access string, props MutableProps) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) DeleteUserAccount(access string) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) ListUserAccounts() ([]Account, error) {
|
||||
return []Account{}, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implementation
|
||||
|
||||
func (ipa *IpaIAMService) login() error {
|
||||
form := url.Values{}
|
||||
form.Set("user", ipa.username)
|
||||
form.Set("password", ipa.password)
|
||||
|
||||
req, err := http.NewRequest(
|
||||
"POST",
|
||||
fmt.Sprintf("%s/ipa/session/login_password", ipa.host),
|
||||
strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("referer", fmt.Sprintf("%s/ipa", ipa.host))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := ipa.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode == 401 {
|
||||
return errors.New("cannot login to FreeIPA: invalid credentials")
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("cannot login to FreeIPA: status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type rpcRequest = string
|
||||
|
||||
type rpcResponse struct {
|
||||
Result json.RawMessage
|
||||
Principal string
|
||||
Id int
|
||||
Version string
|
||||
}
|
||||
|
||||
func (p rpcResponse) String() string {
|
||||
return string(p.Result)
|
||||
}
|
||||
|
||||
var errRpc = errors.New("IPA RPC error")
|
||||
|
||||
func (ipa *IpaIAMService) rpc(req rpcRequest, value any) error {
|
||||
err := ipa.login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := ipa.rpcInternal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(res.Result, value)
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) rpcInternal(req rpcRequest) (rpcResponse, error) {
|
||||
httpReq, err := http.NewRequest("POST",
|
||||
fmt.Sprintf("%s/ipa/session/json", ipa.host),
|
||||
strings.NewReader(req))
|
||||
if err != nil {
|
||||
return rpcResponse{}, err
|
||||
}
|
||||
|
||||
ipa.log(fmt.Sprintf("%v", req))
|
||||
httpReq.Header.Set("referer", fmt.Sprintf("%s/ipa", ipa.host))
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
httpResp, err := ipa.client.Do(httpReq)
|
||||
if err != nil {
|
||||
return rpcResponse{}, err
|
||||
}
|
||||
|
||||
bytes, err := io.ReadAll(httpResp.Body)
|
||||
ipa.log(string(bytes))
|
||||
if err != nil {
|
||||
return rpcResponse{}, err
|
||||
}
|
||||
|
||||
result := struct {
|
||||
Result struct {
|
||||
Json json.RawMessage `json:"result"`
|
||||
Value string `json:"value"`
|
||||
Summary any `json:"summary"`
|
||||
} `json:"result"`
|
||||
Error json.RawMessage `json:"error"`
|
||||
Id int `json:"id"`
|
||||
Principal string `json:"principal"`
|
||||
Version string `json:"version"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(bytes, &result)
|
||||
if err != nil {
|
||||
return rpcResponse{}, err
|
||||
}
|
||||
if string(result.Error) != "null" {
|
||||
return rpcResponse{}, fmt.Errorf("%s: %w", string(result.Error), errRpc)
|
||||
}
|
||||
|
||||
return rpcResponse{
|
||||
Result: result.Result.Json,
|
||||
Principal: result.Principal,
|
||||
Id: result.Id,
|
||||
Version: result.Version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) newRequest(method string, args []string, dict map[string]any) (rpcRequest, error) {
|
||||
|
||||
id := ipa.id
|
||||
ipa.id++
|
||||
|
||||
dict["version"] = ipa.version
|
||||
|
||||
jmethod, errMethod := json.Marshal(method)
|
||||
jargs, errArgs := json.Marshal(args)
|
||||
jdict, errDict := json.Marshal(dict)
|
||||
|
||||
err := errors.Join(errMethod, errArgs, errDict)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ipa request invalid: %w", err)
|
||||
}
|
||||
|
||||
request := map[string]interface{}{
|
||||
"id": id,
|
||||
"method": json.RawMessage(jmethod),
|
||||
"params": []json.RawMessage{json.RawMessage(jargs), json.RawMessage(jdict)},
|
||||
}
|
||||
|
||||
requestJSON, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
return string(requestJSON), nil
|
||||
}
|
||||
|
||||
// pkcs7Unpad validates and unpads data from the given bytes slice.
|
||||
// The returned value will be 1 to n bytes smaller depending on the
|
||||
// amount of padding, where n is the block size.
|
||||
func pkcs7Unpad(b []byte, blocksize int) ([]byte, error) {
|
||||
if blocksize <= 0 {
|
||||
return nil, errors.New("invalid blocksize")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil, errors.New("invalid PKCS7 data (empty or not padded)")
|
||||
}
|
||||
if len(b)%blocksize != 0 {
|
||||
return nil, errors.New("invalid padding on input")
|
||||
}
|
||||
c := b[len(b)-1]
|
||||
n := int(c)
|
||||
if n == 0 || n > len(b) {
|
||||
return nil, errors.New("invalid padding on input")
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if b[len(b)-n+i] != c {
|
||||
return nil, errors.New("invalid padding on input")
|
||||
}
|
||||
}
|
||||
return b[:len(b)-n], nil
|
||||
}
|
||||
|
||||
/*
|
||||
e.g.
|
||||
|
||||
"value" {
|
||||
"__base64__": "aGVsbG93b3JsZAo="
|
||||
}
|
||||
*/
|
||||
type Base64EncodedWrapped []byte
|
||||
|
||||
func (b *Base64EncodedWrapped) UnmarshalJSON(data []byte) error {
|
||||
intermediate := struct {
|
||||
Base64 string `json:"__base64__"`
|
||||
}{}
|
||||
err := json.Unmarshal(data, &intermediate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*b, err = base64.StdEncoding.DecodeString(intermediate.Base64)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Base64EncodedWrapped) MarshalJSON() ([]byte, error) {
|
||||
intermediate := struct {
|
||||
Base64 string `json:"__base64__"`
|
||||
}{Base64: base64.StdEncoding.EncodeToString(*b)}
|
||||
return json.Marshal(intermediate)
|
||||
}
|
||||
|
||||
/*
|
||||
e.g.
|
||||
|
||||
"value": "aGVsbG93b3JsZAo="
|
||||
*/
|
||||
type Base64Encoded []byte
|
||||
|
||||
func (b *Base64Encoded) UnmarshalJSON(data []byte) error {
|
||||
var intermediate string
|
||||
err := json.Unmarshal(data, &intermediate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*b, err = base64.StdEncoding.DecodeString(intermediate)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ipa *IpaIAMService) log(msg string) {
|
||||
if ipa.debug {
|
||||
log.Println(msg)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
// IAMServiceSingle manages the single tenant (root-only) IAM service
|
||||
@@ -23,31 +23,29 @@ type IAMServiceSingle struct{}
|
||||
|
||||
var _ IAMService = &IAMServiceSingle{}
|
||||
|
||||
var ErrNotSupported = errors.New("method is not supported")
|
||||
|
||||
// CreateAccount not valid in single tenant mode
|
||||
func (IAMServiceSingle) CreateAccount(account Account) error {
|
||||
return ErrNotSupported
|
||||
return s3err.GetAPIError(s3err.ErrAdminMethodNotSupported)
|
||||
}
|
||||
|
||||
// GetUserAccount no accounts in single tenant mode
|
||||
func (IAMServiceSingle) GetUserAccount(access string) (Account, error) {
|
||||
return Account{}, ErrNoSuchUser
|
||||
return Account{}, s3err.GetAPIError(s3err.ErrAdminMethodNotSupported)
|
||||
}
|
||||
|
||||
// UpdateUserAccount no accounts in single tenant mode
|
||||
func (IAMServiceSingle) UpdateUserAccount(access string, props MutableProps) error {
|
||||
return ErrNotSupported
|
||||
return s3err.GetAPIError(s3err.ErrAdminMethodNotSupported)
|
||||
}
|
||||
|
||||
// DeleteUserAccount no accounts in single tenant mode
|
||||
func (IAMServiceSingle) DeleteUserAccount(access string) error {
|
||||
return ErrNotSupported
|
||||
return s3err.GetAPIError(s3err.ErrAdminMethodNotSupported)
|
||||
}
|
||||
|
||||
// ListUserAccounts no accounts in single tenant mode
|
||||
func (IAMServiceSingle) ListUserAccounts() ([]Account, error) {
|
||||
return []Account{}, nil
|
||||
return []Account{}, s3err.GetAPIError(s3err.ErrAdminMethodNotSupported)
|
||||
}
|
||||
|
||||
// Shutdown graceful termination of service
|
||||
|
||||
@@ -47,7 +47,7 @@ func NewVaultIAMService(rootAcc Account, endpoint, secretStoragePath, mountPath,
|
||||
tls.ServerCertificate.FromBytes = []byte(serverCert)
|
||||
if clientCert != "" {
|
||||
if clientCertKey == "" {
|
||||
return nil, fmt.Errorf("client certificate and client certificate should both be specified")
|
||||
return nil, fmt.Errorf("client certificate and client certificate key should both be specified")
|
||||
}
|
||||
|
||||
tls.ClientCertificate.FromBytes = []byte(clientCert)
|
||||
|
||||
@@ -196,7 +196,6 @@ func (az *Azure) CreateBucket(ctx context.Context, input *s3.CreateBucketInput,
|
||||
}
|
||||
|
||||
func (az *Azure) ListBuckets(ctx context.Context, input s3response.ListBucketsInput) (s3response.ListAllMyBucketsResult, error) {
|
||||
fmt.Printf("%+v\n", input)
|
||||
pager := az.client.NewListContainersPager(
|
||||
&service.ListContainersOptions{
|
||||
Include: service.ListContainersInclude{
|
||||
@@ -1459,7 +1458,10 @@ func (az *Azure) ChangeBucketOwner(ctx context.Context, bucket string, acl []byt
|
||||
// The action actually returns the containers owned by the user, who initialized the gateway
|
||||
// TODO: Not sure if there's a way to list all the containers and owners?
|
||||
func (az *Azure) ListBucketsAndOwners(ctx context.Context) (buckets []s3response.Bucket, err error) {
|
||||
pager := az.client.NewListContainersPager(nil)
|
||||
opts := &service.ListContainersOptions{
|
||||
Include: service.ListContainersInclude{Metadata: true},
|
||||
}
|
||||
pager := az.client.NewListContainersPager(opts)
|
||||
|
||||
for pager.More() {
|
||||
resp, err := pager.NextPage(ctx)
|
||||
@@ -1735,9 +1737,11 @@ func (az *Azure) deleteContainerMetaData(ctx context.Context, bucket, key string
|
||||
}
|
||||
|
||||
func getAclFromMetadata(meta map[string]*string, key key) (*auth.ACL, error) {
|
||||
var acl auth.ACL
|
||||
|
||||
data, ok := meta[string(key)]
|
||||
if !ok {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInternalError)
|
||||
return &acl, nil
|
||||
}
|
||||
|
||||
value, err := decodeString(*data)
|
||||
@@ -1745,7 +1749,6 @@ func getAclFromMetadata(meta map[string]*string, key key) (*auth.ACL, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var acl auth.ACL
|
||||
if len(value) == 0 {
|
||||
return &acl, nil
|
||||
}
|
||||
|
||||
54
backend/meta/none.go
Normal file
54
backend/meta/none.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2025 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package meta
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// NoMeta is a metadata storer that does not store metadata.
|
||||
// This can be useful for read only mounts where attempting to store metadata
|
||||
// would fail.
|
||||
type NoMeta struct{}
|
||||
|
||||
// RetrieveAttribute retrieves the value of a specific attribute for an object or a bucket.
|
||||
// always returns ErrNoSuchKey
|
||||
func (NoMeta) RetrieveAttribute(_ *os.File, _, _, _ string) ([]byte, error) {
|
||||
return nil, ErrNoSuchKey
|
||||
}
|
||||
|
||||
// StoreAttribute stores the value of a specific attribute for an object or a bucket.
|
||||
// always returns nil without storing the attribute
|
||||
func (NoMeta) StoreAttribute(_ *os.File, _, _, _ string, _ []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAttribute removes the value of a specific attribute for an object or a bucket.
|
||||
// always returns nil without deleting the attribute
|
||||
func (NoMeta) DeleteAttribute(_, _, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAttributes lists all attributes for an object or a bucket.
|
||||
// always returns an empty list of attributes
|
||||
func (NoMeta) ListAttributes(_, _ string) ([]string, error) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// DeleteAttributes removes all attributes for an object or a bucket.
|
||||
// always returns nil without deleting any attributes
|
||||
func (NoMeta) DeleteAttributes(bucket, object string) error {
|
||||
return nil
|
||||
}
|
||||
139
backend/meta/sidecar.go
Normal file
139
backend/meta/sidecar.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2025 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package meta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// SideCar is a metadata storer that uses sidecar files to store metadata.
|
||||
type SideCar struct {
|
||||
dir string
|
||||
}
|
||||
|
||||
const (
|
||||
sidecarmeta = "meta"
|
||||
)
|
||||
|
||||
// NewSideCar creates a new SideCar metadata storer.
|
||||
func NewSideCar(dir string) (SideCar, error) {
|
||||
fi, err := os.Lstat(dir)
|
||||
if err != nil {
|
||||
return SideCar{}, fmt.Errorf("failed to stat directory: %v", err)
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return SideCar{}, fmt.Errorf("not a directory")
|
||||
}
|
||||
|
||||
return SideCar{dir: dir}, nil
|
||||
}
|
||||
|
||||
// RetrieveAttribute retrieves the value of a specific attribute for an object or a bucket.
|
||||
func (s SideCar) RetrieveAttribute(_ *os.File, bucket, object, attribute string) ([]byte, error) {
|
||||
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
|
||||
if object == "" {
|
||||
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
|
||||
}
|
||||
attr := filepath.Join(metadir, attribute)
|
||||
|
||||
value, err := os.ReadFile(attr)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, ErrNoSuchKey
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read attribute: %v", err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// StoreAttribute stores the value of a specific attribute for an object or a bucket.
|
||||
func (s SideCar) StoreAttribute(_ *os.File, bucket, object, attribute string, value []byte) error {
|
||||
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
|
||||
if object == "" {
|
||||
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
|
||||
}
|
||||
err := os.MkdirAll(metadir, 0777)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create metadata directory: %v", err)
|
||||
}
|
||||
|
||||
attr := filepath.Join(metadir, attribute)
|
||||
err = os.WriteFile(attr, value, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write attribute: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAttribute removes the value of a specific attribute for an object or a bucket.
|
||||
func (s SideCar) DeleteAttribute(bucket, object, attribute string) error {
|
||||
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
|
||||
if object == "" {
|
||||
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
|
||||
}
|
||||
attr := filepath.Join(metadir, attribute)
|
||||
|
||||
err := os.Remove(attr)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return ErrNoSuchKey
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove attribute: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAttributes lists all attributes for an object or a bucket.
|
||||
func (s SideCar) ListAttributes(bucket, object string) ([]string, error) {
|
||||
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
|
||||
if object == "" {
|
||||
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
|
||||
}
|
||||
|
||||
ents, err := os.ReadDir(metadir)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return []string{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list attributes: %v", err)
|
||||
}
|
||||
|
||||
var attrs []string
|
||||
for _, ent := range ents {
|
||||
attrs = append(attrs, ent.Name())
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
// DeleteAttributes removes all attributes for an object or a bucket.
|
||||
func (s SideCar) DeleteAttributes(bucket, object string) error {
|
||||
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
|
||||
if object == "" {
|
||||
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
|
||||
}
|
||||
|
||||
err := os.RemoveAll(metadir)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("failed to remove attributes: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -107,9 +107,14 @@ type PosixOpts struct {
|
||||
BucketLinks bool
|
||||
VersioningDir string
|
||||
NewDirPerm fs.FileMode
|
||||
SideCarDir string
|
||||
}
|
||||
|
||||
func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, error) {
|
||||
if opts.SideCarDir != "" && strings.HasPrefix(opts.SideCarDir, rootdir) {
|
||||
return nil, fmt.Errorf("sidecar directory cannot be inside the gateway root directory")
|
||||
}
|
||||
|
||||
err := os.Chdir(rootdir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("chdir %v: %w", rootdir, err)
|
||||
@@ -120,46 +125,36 @@ func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, erro
|
||||
return nil, fmt.Errorf("open %v: %w", rootdir, err)
|
||||
}
|
||||
|
||||
var verioningdirAbs string
|
||||
rootdirAbs, err := filepath.Abs(rootdir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get absolute path of %v: %w", rootdir, err)
|
||||
}
|
||||
|
||||
var verioningdirAbs string
|
||||
// Ensure the versioning directory isn't within the root directory
|
||||
if opts.VersioningDir != "" {
|
||||
rootdirAbs, err := filepath.Abs(rootdir)
|
||||
verioningdirAbs, err = validateSubDir(rootdirAbs, opts.VersioningDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get absolute path of %v: %w", rootdir, err)
|
||||
}
|
||||
|
||||
verioningdirAbs, err = filepath.Abs(opts.VersioningDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get absolute path of %v: %w", opts.VersioningDir, err)
|
||||
}
|
||||
|
||||
// Ensure the paths end with a separator
|
||||
if !strings.HasSuffix(rootdirAbs, string(filepath.Separator)) {
|
||||
rootdirAbs += string(filepath.Separator)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(verioningdirAbs, string(filepath.Separator)) {
|
||||
verioningdirAbs += string(filepath.Separator)
|
||||
}
|
||||
|
||||
// Ensure the posix root directory doesn't contain the versioning directory
|
||||
if strings.HasPrefix(verioningdirAbs, rootdirAbs) {
|
||||
return nil, fmt.Errorf("the root directory %v contains the versioning directory %v", rootdir, opts.VersioningDir)
|
||||
}
|
||||
|
||||
vDir, err := os.Stat(verioningdirAbs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat versioning dir: %w", err)
|
||||
}
|
||||
|
||||
// Check the versioning path to be a directory
|
||||
if !vDir.IsDir() {
|
||||
return nil, fmt.Errorf("versioning path should be a directory")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Bucket versioning enabled with directory: %v\n", verioningdirAbs)
|
||||
var sidecardirAbs string
|
||||
// Ensure the sidecar directory isn't within the root directory
|
||||
if opts.SideCarDir != "" {
|
||||
sidecardirAbs, err = validateSubDir(rootdirAbs, opts.SideCarDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if verioningdirAbs != "" {
|
||||
fmt.Println("Bucket versioning enabled with directory:", verioningdirAbs)
|
||||
}
|
||||
|
||||
if sidecardirAbs != "" {
|
||||
fmt.Println("Using sidecar directory for metadata:", sidecardirAbs)
|
||||
}
|
||||
|
||||
return &Posix{
|
||||
meta: meta,
|
||||
@@ -175,6 +170,48 @@ func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateSubDir(root, dir string) (string, error) {
|
||||
absDir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get absolute path of %v: %w",
|
||||
dir, err)
|
||||
}
|
||||
|
||||
if isDirBelowRoot(root, absDir) {
|
||||
return "", fmt.Errorf("the root directory %v contains the directory %v",
|
||||
root, dir)
|
||||
}
|
||||
|
||||
vDir, err := os.Stat(absDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("stat %q: %w", absDir, err)
|
||||
}
|
||||
|
||||
if !vDir.IsDir() {
|
||||
return "", fmt.Errorf("path %q is not a directory", absDir)
|
||||
}
|
||||
|
||||
return absDir, nil
|
||||
}
|
||||
|
||||
func isDirBelowRoot(root, dir string) bool {
|
||||
// Ensure the paths ends with a separator
|
||||
if !strings.HasSuffix(root, string(filepath.Separator)) {
|
||||
root += string(filepath.Separator)
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(dir, string(filepath.Separator)) {
|
||||
dir += string(filepath.Separator)
|
||||
}
|
||||
|
||||
// Ensure the root directory doesn't contain the directory
|
||||
if strings.HasPrefix(dir, root) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Posix) Shutdown() {
|
||||
p.rootfd.Close()
|
||||
}
|
||||
@@ -198,7 +235,7 @@ func (p *Posix) doesBucketAndObjectExist(bucket, object string) error {
|
||||
}
|
||||
|
||||
_, err = os.Stat(filepath.Join(bucket, object))
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -761,7 +798,7 @@ func getBoolPtr(b bool) *bool {
|
||||
// Check if the given object is a delete marker
|
||||
func (p *Posix) isObjDeleteMarker(bucket, object string) (bool, error) {
|
||||
_, err := p.meta.RetrieveAttribute(nil, bucket, object, deleteMarkerKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return false, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -2434,7 +2471,7 @@ func (p *Posix) DeleteObject(ctx context.Context, input *s3.DeleteObjectInput) (
|
||||
if getString(input.VersionId) == "" {
|
||||
// if the versionId is not specified, make the current version a delete marker
|
||||
fi, err := os.Stat(objpath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
// AWS returns success if the object does not exist
|
||||
return &s3.DeleteObjectOutput{}, nil
|
||||
}
|
||||
@@ -2601,7 +2638,7 @@ func (p *Posix) DeleteObject(ctx context.Context, input *s3.DeleteObjectInput) (
|
||||
if errors.Is(err, syscall.ENAMETOOLONG) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrKeyTooLong)
|
||||
}
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -2621,7 +2658,7 @@ func (p *Posix) DeleteObject(ctx context.Context, input *s3.DeleteObjectInput) (
|
||||
if errors.Is(err, syscall.ENAMETOOLONG) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrKeyTooLong)
|
||||
}
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
// AWS returns success if the object does not exist
|
||||
return &s3.DeleteObjectOutput{}, nil
|
||||
}
|
||||
@@ -2646,6 +2683,9 @@ func (p *Posix) DeleteObject(ctx context.Context, input *s3.DeleteObjectInput) (
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, syscall.ENOTEMPTY) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrDirectoryNotEmpty)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("delete object: %w", err)
|
||||
}
|
||||
@@ -2768,7 +2808,7 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput) (*s3.GetO
|
||||
object := *input.Key
|
||||
if versionId != "" {
|
||||
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -2787,7 +2827,7 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput) (*s3.GetO
|
||||
objPath := filepath.Join(bucket, object)
|
||||
|
||||
fi, err := os.Stat(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
if versionId != "" {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
@@ -3025,7 +3065,7 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
|
||||
if versionId != "" {
|
||||
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -3045,7 +3085,7 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
objPath := filepath.Join(bucket, object)
|
||||
|
||||
fi, err := os.Stat(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
if versionId != "" {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
@@ -3206,7 +3246,7 @@ func (p *Posix) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
vId, err := p.meta.RetrieveAttribute(nil, srcBucket, srcObject, versionIdKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -3229,7 +3269,7 @@ func (p *Posix) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.
|
||||
|
||||
objPath := filepath.Join(srcBucket, srcObject)
|
||||
f, err := os.Open(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
if p.versioningEnabled() && vEnabled {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchVersion)
|
||||
}
|
||||
@@ -3284,7 +3324,7 @@ func (p *Posix) CopyObject(ctx context.Context, input *s3.CopyObjectInput) (*s3.
|
||||
b, _ := p.meta.RetrieveAttribute(nil, dstBucket, dstObject, etagkey)
|
||||
etag = string(b)
|
||||
vId, _ := p.meta.RetrieveAttribute(nil, dstBucket, dstObject, versionIdKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
version = backend.GetPtrFromString(string(vId))
|
||||
@@ -3612,7 +3652,7 @@ func (p *Posix) GetObjectTagging(_ context.Context, bucket, object string) (map[
|
||||
func (p *Posix) getAttrTags(bucket, object string) (map[string]string, error) {
|
||||
tags := make(map[string]string)
|
||||
b, err := p.meta.RetrieveAttribute(nil, bucket, object, tagHdr)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -3641,7 +3681,7 @@ func (p *Posix) PutObjectTagging(_ context.Context, bucket, object string, tags
|
||||
|
||||
if tags == nil {
|
||||
err = p.meta.DeleteAttribute(bucket, object, tagHdr)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -3659,7 +3699,7 @@ func (p *Posix) PutObjectTagging(_ context.Context, bucket, object string, tags
|
||||
}
|
||||
|
||||
err = p.meta.StoreAttribute(nil, bucket, object, tagHdr, b)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -3831,7 +3871,7 @@ func (p *Posix) PutObjectLegalHold(_ context.Context, bucket, object, versionId
|
||||
return s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -3845,7 +3885,7 @@ func (p *Posix) PutObjectLegalHold(_ context.Context, bucket, object, versionId
|
||||
}
|
||||
|
||||
err = p.meta.StoreAttribute(nil, bucket, object, objectLegalHoldKey, statusData)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
if versionId != "" {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
@@ -3874,7 +3914,7 @@ func (p *Posix) GetObjectLegalHold(_ context.Context, bucket, object, versionId
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -3888,7 +3928,7 @@ func (p *Posix) GetObjectLegalHold(_ context.Context, bucket, object, versionId
|
||||
}
|
||||
|
||||
data, err := p.meta.RetrieveAttribute(nil, bucket, object, objectLegalHoldKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
if versionId != "" {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
@@ -3922,7 +3962,7 @@ func (p *Posix) PutObjectRetention(_ context.Context, bucket, object, versionId
|
||||
return s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -3936,7 +3976,7 @@ func (p *Posix) PutObjectRetention(_ context.Context, bucket, object, versionId
|
||||
}
|
||||
|
||||
objectLockCfg, err := p.meta.RetrieveAttribute(nil, bucket, object, objectRetentionKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
if versionId != "" {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
@@ -3994,7 +4034,7 @@ func (p *Posix) GetObjectRetention(_ context.Context, bucket, object, versionId
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
vId, err := p.meta.RetrieveAttribute(nil, bucket, object, versionIdKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -4008,7 +4048,7 @@ func (p *Posix) GetObjectRetention(_ context.Context, bucket, object, versionId
|
||||
}
|
||||
|
||||
data, err := p.meta.RetrieveAttribute(nil, bucket, object, objectRetentionKey)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
if versionId != "" {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidVersionId)
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ func (s *ScoutFS) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s
|
||||
objPath := filepath.Join(bucket, object)
|
||||
|
||||
fi, err := os.Stat(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, syscall.ENAMETOOLONG) {
|
||||
@@ -614,7 +614,7 @@ func (s *ScoutFS) GetObject(_ context.Context, input *s3.GetObjectInput) (*s3.Ge
|
||||
objPath := filepath.Join(bucket, object)
|
||||
|
||||
fi, err := os.Stat(objPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, syscall.ENAMETOOLONG) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"io/fs"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/versity/versitygw/s3response"
|
||||
@@ -231,7 +232,7 @@ func Walk(ctx context.Context, fileSystem fs.FS, prefix, delimiter, marker strin
|
||||
})
|
||||
if err != nil {
|
||||
// suppress file not found caused by user's prefix
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) {
|
||||
return WalkResults{}, nil
|
||||
}
|
||||
return WalkResults{}, err
|
||||
|
||||
@@ -74,6 +74,9 @@ var (
|
||||
metricsService string
|
||||
statsdServers string
|
||||
dogstatsServers string
|
||||
ipaHost, ipaVaultName string
|
||||
ipaUser, ipaPassword string
|
||||
ipaInsecure, ipaDebug bool
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -206,6 +209,7 @@ func initFlags() []cli.Flag {
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug output",
|
||||
Value: false,
|
||||
EnvVars: []string{"VGW_DEBUG"},
|
||||
Destination: &debug,
|
||||
},
|
||||
@@ -506,6 +510,42 @@ func initFlags() []cli.Flag {
|
||||
Aliases: []string{"mds"},
|
||||
Destination: &dogstatsServers,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ipa-host",
|
||||
Usage: "FreeIPA server url e.g. https://ipa.example.test",
|
||||
EnvVars: []string{"VGW_IPA_HOST"},
|
||||
Destination: &ipaHost,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ipa-vault-name",
|
||||
Usage: "A name of the user vault containing their secret",
|
||||
EnvVars: []string{"VGW_IPA_VAULT_NAME"},
|
||||
Destination: &ipaVaultName,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ipa-user",
|
||||
Usage: "Username used to connect to FreeIPA. Needs permissions to read user vault contents",
|
||||
EnvVars: []string{"VGW_IPA_USER"},
|
||||
Destination: &ipaUser,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ipa-password",
|
||||
Usage: "Password of the user used to connect to FreeIPA.",
|
||||
EnvVars: []string{"VGW_IPA_PASSWORD"},
|
||||
Destination: &ipaPassword,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "ipa-insecure",
|
||||
Usage: "Verify TLS certificate of FreeIPA server. Default is 'true'.",
|
||||
EnvVars: []string{"VGW_IPA_INSECURE"},
|
||||
Destination: &ipaInsecure,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "ipa-debug",
|
||||
Usage: "FreeIPA IAM debug output",
|
||||
EnvVars: []string{"VGW_IPA_DEBUG"},
|
||||
Destination: &ipaDebug,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,6 +663,12 @@ func runGateway(ctx context.Context, be backend.Backend) error {
|
||||
CacheDisable: iamCacheDisable,
|
||||
CacheTTL: iamCacheTTL,
|
||||
CachePrune: iamCachePrune,
|
||||
IpaHost: ipaHost,
|
||||
IpaVaultName: ipaVaultName,
|
||||
IpaUser: ipaUser,
|
||||
IpaPassword: ipaPassword,
|
||||
IpaInsecure: ipaInsecure,
|
||||
IpaDebug: ipaDebug,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("setup iam: %w", err)
|
||||
|
||||
@@ -29,6 +29,8 @@ var (
|
||||
bucketlinks bool
|
||||
versioningDir string
|
||||
dirPerms uint
|
||||
sidecar string
|
||||
nometa bool
|
||||
)
|
||||
|
||||
func posixCommand() *cli.Command {
|
||||
@@ -79,6 +81,18 @@ will be translated into the file /mnt/fs/gwroot/mybucket/a/b/c/myobject`,
|
||||
DefaultText: "0755",
|
||||
Value: 0755,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "sidecar",
|
||||
Usage: "use provided sidecar directory to store metadata",
|
||||
EnvVars: []string{"VGW_META_SIDECAR"},
|
||||
Destination: &sidecar,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "nometa",
|
||||
Usage: "disable metadata storage",
|
||||
EnvVars: []string{"VGW_META_NONE"},
|
||||
Destination: &nometa,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -89,24 +103,45 @@ func runPosix(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
gwroot := (ctx.Args().Get(0))
|
||||
err := meta.XattrMeta{}.Test(gwroot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("posix xattr check: %v", err)
|
||||
}
|
||||
|
||||
if dirPerms > math.MaxUint32 {
|
||||
return fmt.Errorf("invalid directory permissions: %d", dirPerms)
|
||||
}
|
||||
|
||||
be, err := posix.New(gwroot, meta.XattrMeta{}, posix.PosixOpts{
|
||||
if nometa && sidecar != "" {
|
||||
return fmt.Errorf("cannot use both nometa and sidecar metadata")
|
||||
}
|
||||
|
||||
opts := posix.PosixOpts{
|
||||
ChownUID: chownuid,
|
||||
ChownGID: chowngid,
|
||||
BucketLinks: bucketlinks,
|
||||
VersioningDir: versioningDir,
|
||||
NewDirPerm: fs.FileMode(dirPerms),
|
||||
})
|
||||
}
|
||||
|
||||
var ms meta.MetadataStorer
|
||||
switch {
|
||||
case sidecar != "":
|
||||
sc, err := meta.NewSideCar(sidecar)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to init sidecar metadata: %w", err)
|
||||
}
|
||||
ms = sc
|
||||
opts.SideCarDir = sidecar
|
||||
case nometa:
|
||||
ms = meta.NoMeta{}
|
||||
default:
|
||||
ms = meta.XattrMeta{}
|
||||
err := meta.XattrMeta{}.Test(gwroot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("xattr check failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
be, err := posix.New(gwroot, ms, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init posix: %v", err)
|
||||
return fmt.Errorf("failed to init posix backend: %w", err)
|
||||
}
|
||||
|
||||
return runGateway(ctx.Context, be)
|
||||
|
||||
@@ -38,6 +38,7 @@ var (
|
||||
checksumDisable bool
|
||||
versioningEnabled bool
|
||||
azureTests bool
|
||||
tlsStatus bool
|
||||
)
|
||||
|
||||
func testCommand() *cli.Command {
|
||||
@@ -79,6 +80,12 @@ func initTestFlags() []cli.Flag {
|
||||
Aliases: []string{"d"},
|
||||
Destination: &debug,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "allow-insecure",
|
||||
Usage: "skip tls verification",
|
||||
Aliases: []string{"ai"},
|
||||
Destination: &tlsStatus,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +218,7 @@ func initTestCommands() []*cli.Command {
|
||||
integration.WithEndpoint(endpoint),
|
||||
integration.WithConcurrency(concurrency),
|
||||
integration.WithPartSize(partSize),
|
||||
integration.WithTLSStatus(tlsStatus),
|
||||
}
|
||||
if debug {
|
||||
opts = append(opts, integration.WithDebug())
|
||||
@@ -271,6 +279,7 @@ func initTestCommands() []*cli.Command {
|
||||
integration.WithRegion(region),
|
||||
integration.WithEndpoint(endpoint),
|
||||
integration.WithConcurrency(concurrency),
|
||||
integration.WithTLSStatus(tlsStatus),
|
||||
}
|
||||
if debug {
|
||||
opts = append(opts, integration.WithDebug())
|
||||
@@ -296,6 +305,7 @@ func getAction(tf testFunc) func(*cli.Context) error {
|
||||
integration.WithSecret(awsSecret),
|
||||
integration.WithRegion(region),
|
||||
integration.WithEndpoint(endpoint),
|
||||
integration.WithTLSStatus(tlsStatus),
|
||||
}
|
||||
if debug {
|
||||
opts = append(opts, integration.WithDebug())
|
||||
@@ -333,6 +343,7 @@ func extractIntTests() (commands []*cli.Command) {
|
||||
integration.WithSecret(awsSecret),
|
||||
integration.WithRegion(region),
|
||||
integration.WithEndpoint(endpoint),
|
||||
integration.WithTLSStatus(tlsStatus),
|
||||
}
|
||||
if debug {
|
||||
opts = append(opts, integration.WithDebug())
|
||||
|
||||
76
go.mod
76
go.mod
@@ -3,40 +3,40 @@ module github.com/versity/versitygw
|
||||
go 1.21.0
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||
github.com/DataDog/datadog-go/v5 v5.5.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.3
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2
|
||||
github.com/aws/smithy-go v1.22.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.8
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
|
||||
github.com/DataDog/datadog-go/v5 v5.6.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.34.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.74.1
|
||||
github.com/aws/smithy-go v1.22.2
|
||||
github.com/go-ldap/ldap/v3 v3.4.10
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/vault-client-go v0.4.3
|
||||
github.com/nats-io/nats.go v1.37.0
|
||||
github.com/nats-io/nats.go v1.38.0
|
||||
github.com/oklog/ulid/v2 v2.1.0
|
||||
github.com/pkg/xattr v0.4.10
|
||||
github.com/segmentio/kafka-go v0.4.47
|
||||
github.com/smira/go-statsd v1.3.4
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
github.com/valyala/fasthttp v1.57.0
|
||||
github.com/valyala/fasthttp v1.58.0
|
||||
github.com/versity/scoutfs-go v0.0.0-20240325223134-38eb2f5f7d44
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.26.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sys v0.29.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.3 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.10 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
@@ -45,33 +45,33 @@ require (
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
github.com/nats-io/nkeys v0.4.9 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.1
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.42
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.35
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.2
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.55
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.54
|
||||
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.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.29 // 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/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
|
||||
193
go.sum
193
go.sum
@@ -1,23 +1,23 @@
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.3 h1:6LyjnnaLpcOKK0fbYisI+mb8CE7iNe7i89nMNQxFxs8=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/DataDog/datadog-go/v5 v5.5.0 h1:G5KHeB8pWBNXT4Jtw0zAkhdxEAWSpWH00geHI6LDrKU=
|
||||
github.com/DataDog/datadog-go/v5 v5.5.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/DataDog/datadog-go/v5 v5.6.0 h1:2oCLxjF/4htd55piM75baflj/KoE6VYS7alEUqFvRDw=
|
||||
github.com/DataDog/datadog-go/v5 v5.6.0/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw=
|
||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
@@ -25,48 +25,48 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.1 h1:oxIvOUXy8x0U3fR//0eq+RdCKimWI900+SV+10xsCBw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.1/go.mod h1:bRQcttQJiARbd5JZxw6wG0yIK3eLeSCPdg6uqmmlIiI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.42/go.mod h1:FwZBfU530dJ26rv9saAbxa9Ej3eF/AK0OAY86k13n4M=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 h1:68jFVtt3NulEzojFesM/WVarlFpCaXLKaBxDpzkQ9OQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18/go.mod h1:Fjnn5jQVIo6VyedMc0/EhPpfNlPl7dHV916O6B+49aE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.35 h1:ihPPdcCVSN0IvBByXwqVp28/l4VosBZ6sDulcvU2J7w=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.35/go.mod h1:JkgEhs3SVF51Dj3m1Bj+yL8IznpxzkwlA3jLg3x7Kls=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ=
|
||||
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/v4a v1.3.22 h1:yV+hCAHZZYJQcwAaszoBNwLbPItHvApxT0kVIw6jRgs=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22/go.mod h1:kbR1TL8llqB1eGnVbybcA4/wgScxdylOdyAd51yxPdw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 h1:kT6BcZsmMtNkP/iYMcRG+mIEA/IbeiUimXtGmqF39y0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3/go.mod h1:Z8uGua2k4PPaGOYn66pK02rhMrot3Xk3tpBuUFPomZU=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 h1:ZC7Y/XgKUxwqcdhO5LE8P6oGP1eh6xlQReWNKfhvJno=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3/go.mod h1:WqfO7M9l9yUAw0HcHaikwRd/H6gzYdz7vjejCA5e2oY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 h1:p9TNFL8bFUMd+38YIpTAXpoxyz0MxC7FlbFEH4P4E1U=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2/go.mod h1:fNjyo0Coen9QTwQLWeV6WO2Nytwiu+cCcWaTdKCAqqE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 h1:UTpsIf0loCIWEbrqdLb+0RxnTXfWh2vhw4nQmFi4nPc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.3/go.mod h1:FZ9j3PFHHAR+w0BSEjK955w5YD2UwB/l/H0yAK3MJvI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 h1:2YCmIXv3tmiItw0LlYf6v7gEHebLY45kBEnPezbUKyU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3/go.mod h1:u19stRyNPxGhj6dRm+Cdgu6N75qnbW7+QN0q0dsAk58=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 h1:wVnQ6tigGsRqSWDEEyH6lSAJ9OyFUsSnbaUWChuSGzs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.3/go.mod h1:VZa9yTFyj4o10YGsmDO4gbQJUvvhY72fhumT8W4LqsE=
|
||||
github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM=
|
||||
github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/aws/aws-sdk-go-v2 v1.34.0 h1:9iyL+cjifckRGEVpRKZP3eIxVlL06Qk1Tk13vreaVQU=
|
||||
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.29.2 h1:JuIxOEPcSKpMB0J+khMjznG9LIhIBdmqNiEcPclnwqc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.2/go.mod h1:HktTHregOZwNSM/e7WTfVSu9RCX+3eOv+6ij27PtaYs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.55 h1:CDhKnDEaGkLA5ZszV/qw5uwN5M8rbv9Cl0JRN+PRsaM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.55/go.mod h1:kPD/vj+RB5MREDUky376+zdnjZpR+WgdBBvwrmnlmKE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.25 h1:kU7tmXNaJ07LsyN3BUgGqAmVmQtq0w6duVIHAKfp0/w=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.25/go.mod h1:OiC8+OiqrURb1wrwmr/UbOVLFSWEGxjinj5C299VQdo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.54 h1:6BWOAho3Cgdy4cmNJ4HWY8VZgqODEU7Gw78XXireNZI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.54/go.mod h1:n+t/oyYErOV3jf/GxNTVlizSM9RMV1yH7jvcIvld3Do=
|
||||
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.29/go.mod h1:oeNTC7PwJNoM5AznVr23wxhLnuJv0ZDe5v7w0wqIs9M=
|
||||
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.29/go.mod h1:c4jkZiQ+BWpNqq7VtrxjwISrLrt/VvPq3XiopkUIolI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.29 h1:g9OUETuxA8i/Www5Cby0R3WSTe7ppFTZXHVLNskNS4w=
|
||||
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/accept-encoding v1.12.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA=
|
||||
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.24.12 h1:kznaW4f81mNMlREkU9w3jUuJvU5g/KsqDV43ab7Rp6s=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.12/go.mod h1:bZy9r8e0/s0P7BSDHgMLXK2KvdyRRBIQ2blKlvLt0IU=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.11 h1:mUwIpAvILeKFnRx4h1dEgGEFGuV8KJ3pEScZWVFYuZA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.11/go.mod h1:JDJtD+b8HNVv71axz8+S5492KM8wTzHRFpMKQbPlYxw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.10 h1:g9d+TOsu3ac7SgmY2dUf1qMgu/uJVTlQ4VCbH6hRxSw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.10/go.mod h1:WZfNmntu92HO44MVZAubQaz3qCuIdeOdog2sADfU6hU=
|
||||
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -74,13 +74,12 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.10 h1:ot/iwPOhfpNVgB1o+AVXljizWZ9JTp7YF5oeyONmcJU=
|
||||
github.com/go-ldap/ldap/v3 v3.4.10/go.mod h1:JXh4Uxgi40P6E9rdsYqpUtbW46D9UTjJ9QSwGRznplY=
|
||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
@@ -124,27 +123,26 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
|
||||
github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||
github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA=
|
||||
github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw=
|
||||
github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0=
|
||||
github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
||||
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -152,8 +150,8 @@ github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
|
||||
github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
||||
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
|
||||
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@@ -175,14 +173,14 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg=
|
||||
github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE=
|
||||
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
||||
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/versity/scoutfs-go v0.0.0-20240325223134-38eb2f5f7d44 h1:Wx1o3pNrCzsHIIDyZ2MLRr6tF/1FhAr7HNDn80QqDWE=
|
||||
@@ -203,14 +201,19 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -220,17 +223,22 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -242,23 +250,27 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@@ -267,15 +279,18 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
89
runtests.sh
89
runtests.sh
@@ -10,6 +10,14 @@ mkdir /tmp/versioning.covdata
|
||||
rm -rf /tmp/versioningdir
|
||||
mkdir /tmp/versioningdir
|
||||
|
||||
# setup tls certificate and key
|
||||
ECHO "Generating TLS certificate and key in the cert.pem and key.pem files"
|
||||
|
||||
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
|
||||
openssl req -new -x509 -key key.pem -out cert.pem -days 365 -subj "/C=US/ST=California/L=San Francisco/O=Versity/OU=Software/CN=versity.com"
|
||||
|
||||
|
||||
ECHO "Running the sdk test over http"
|
||||
# run server in background not versioning-enabled
|
||||
# port: 7070(default)
|
||||
GOCOVERDIR=/tmp/covdata ./versitygw -a user -s pass --iam-dir /tmp/gw posix /tmp/gw &
|
||||
@@ -17,7 +25,7 @@ GW_PID=$!
|
||||
|
||||
sleep 1
|
||||
|
||||
# check if versioning-enabled gateway process is still running
|
||||
# check if gateway process is still running
|
||||
if ! kill -0 $GW_PID; then
|
||||
echo "server no longer running"
|
||||
exit 1
|
||||
@@ -45,9 +53,48 @@ fi
|
||||
|
||||
kill $GW_PID
|
||||
|
||||
ECHO "Running the sdk test over https"
|
||||
|
||||
# run server in background with TLS certificate
|
||||
# port: 7071(default)
|
||||
GOCOVERDIR=/tmp/https.covdata ./versitygw --cert "$PWD/cert.pem" --key "$PWD/key.pem" -p :7071 -a user -s pass --iam-dir /tmp/gw posix /tmp/gw &
|
||||
GW_HTTPS_PID=$!
|
||||
|
||||
sleep 1
|
||||
|
||||
# check if https gateway process is still running
|
||||
if ! kill -0 $GW_HTTPS_PID; then
|
||||
echo "server no longer running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# run tests
|
||||
# full flow tests
|
||||
if ! ./versitygw test --allow-insecure -a user -s pass -e https://127.0.0.1:7071 full-flow; then
|
||||
echo "full flow tests failed"
|
||||
kill $GW_HTTPS_PID
|
||||
exit 1
|
||||
fi
|
||||
# posix tests
|
||||
if ! ./versitygw test --allow-insecure -a user -s pass -e https://127.0.0.1:7071 posix; then
|
||||
echo "posix tests failed"
|
||||
kill $GW_HTTPS_PID
|
||||
exit 1
|
||||
fi
|
||||
# iam tests
|
||||
if ! ./versitygw test --allow-insecure -a user -s pass -e https://127.0.0.1:7071 iam; then
|
||||
echo "iam tests failed"
|
||||
kill $GW_HTTPS_PID
|
||||
exit 1
|
||||
fi
|
||||
|
||||
kill $GW_HTTPS_PID
|
||||
|
||||
|
||||
ECHO "Running the sdk test over http against the versioning-enabled gateway"
|
||||
# run server in background versioning-enabled
|
||||
# port: 7071
|
||||
GOCOVERDIR=/tmp/versioning.covdata ./versitygw -p :7071 -a user -s pass --iam-dir /tmp/gw posix --versioning-dir /tmp/versioningdir /tmp/gw &
|
||||
# port: 7072
|
||||
GOCOVERDIR=/tmp/versioning.covdata ./versitygw -p :7072 -a user -s pass --iam-dir /tmp/gw posix --versioning-dir /tmp/versioningdir /tmp/gw &
|
||||
GW_VS_PID=$!
|
||||
|
||||
# wait a second for server to start up
|
||||
@@ -61,13 +108,13 @@ fi
|
||||
|
||||
# run tests
|
||||
# full flow tests
|
||||
if ! ./versitygw test -a user -s pass -e http://127.0.0.1:7071 full-flow -vs; then
|
||||
if ! ./versitygw test -a user -s pass -e http://127.0.0.1:7072 full-flow -vs; then
|
||||
echo "versioning-enabled full-flow tests failed"
|
||||
kill $GW_VS_PID
|
||||
exit 1
|
||||
fi
|
||||
# posix tests
|
||||
if ! ./versitygw test -a user -s pass -e http://127.0.0.1:7071 posix -vs; then
|
||||
if ! ./versitygw test -a user -s pass -e http://127.0.0.1:7072 posix -vs; then
|
||||
echo "versiongin-enabled posix tests failed"
|
||||
kill $GW_VS_PID
|
||||
exit 1
|
||||
@@ -76,6 +123,38 @@ fi
|
||||
# kill off server
|
||||
kill $GW_VS_PID
|
||||
|
||||
ECHO "Running the sdk test over https against the versioning-enabled gateway"
|
||||
# run server in background versioning-enabled
|
||||
# port: 7073
|
||||
GOCOVERDIR=/tmp/versioning.https.covdata ./versitygw --cert "$PWD/cert.pem" --key "$PWD/key.pem" -p :7073 -a user -s pass --iam-dir /tmp/gw posix --versioning-dir /tmp/versioningdir /tmp/gw &
|
||||
GW_VS_HTTPS_PID=$!
|
||||
|
||||
# wait a second for server to start up
|
||||
sleep 1
|
||||
|
||||
# check if versioning-enabled gateway process is still running
|
||||
if ! kill -0 $GW_VS_HTTPS_PID; then
|
||||
echo "versioning-enabled server no longer running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# run tests
|
||||
# full flow tests
|
||||
if ! ./versitygw test --allow-insecure -a user -s pass -e https://127.0.0.1:7073 full-flow -vs; then
|
||||
echo "versioning-enabled full-flow tests failed"
|
||||
kill $GW_VS_HTTPS_PID
|
||||
exit 1
|
||||
fi
|
||||
# posix tests
|
||||
if ! ./versitygw test --allow-insecure -a user -s pass -e https://127.0.0.1:7073 posix -vs; then
|
||||
echo "versiongin-enabled posix tests failed"
|
||||
kill $GW_VS_HTTPS_PID
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# kill off server
|
||||
kill $GW_VS_HTTPS_PID
|
||||
|
||||
exit 0
|
||||
|
||||
# if the above binary was built with -cover enabled (make testbin),
|
||||
|
||||
@@ -167,7 +167,7 @@ func (c AdminController) ChangeBucketOwner(ctx *fiber.Ctx) error {
|
||||
Owner: owner,
|
||||
Grantees: []auth.Grantee{
|
||||
{
|
||||
Permission: types.PermissionFullControl,
|
||||
Permission: auth.PermissionFullControl,
|
||||
Access: owner,
|
||||
Type: types.TypeCanonicalUser,
|
||||
},
|
||||
|
||||
@@ -126,7 +126,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -175,7 +175,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -217,7 +217,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -282,7 +282,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -319,7 +319,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionReadAcp,
|
||||
AclPermission: auth.PermissionReadAcp,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -352,7 +352,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -468,7 +468,7 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -589,7 +589,16 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
ctx.Response().SetBodyStream(res.Body, int(getint64(res.ContentLength)))
|
||||
err := utils.StreamResponseBody(ctx, res.Body)
|
||||
if err != nil {
|
||||
SendResponse(ctx, nil,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
MetricsMng: c.mm,
|
||||
Action: metrics.ActionGetObject,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return SendResponse(ctx, nil,
|
||||
@@ -637,7 +646,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -685,7 +694,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -722,7 +731,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -762,7 +771,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -792,7 +801,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -845,7 +854,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -886,7 +895,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionReadAcp,
|
||||
AclPermission: auth.PermissionReadAcp,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -926,7 +935,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -977,7 +986,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1027,7 +1036,7 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1127,7 +1136,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1180,7 +1189,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1210,7 +1219,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1272,7 +1281,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1313,7 +1322,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1384,7 +1393,7 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWriteAcp,
|
||||
AclPermission: auth.PermissionWriteAcp,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1415,13 +1424,12 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if accessControlPolicy.Owner == nil ||
|
||||
accessControlPolicy.Owner.ID == nil ||
|
||||
*accessControlPolicy.Owner.ID == "" {
|
||||
err = accessControlPolicy.Validate()
|
||||
if err != nil {
|
||||
if c.debug {
|
||||
log.Println("empty access control policy owner")
|
||||
log.Printf("invalid access control policy: %v\n", err)
|
||||
}
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrMalformedACL),
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: metrics.ActionPutBucketAcl,
|
||||
@@ -1733,7 +1741,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
err = auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1766,7 +1774,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1839,7 +1847,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1903,7 +1911,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
err = auth.VerifyObjectCopyAccess(ctx.Context(), c.be, copySource,
|
||||
auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -1967,7 +1975,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2073,7 +2081,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
ID: &grt.Grantee.ID,
|
||||
Type: grt.Grantee.Type,
|
||||
},
|
||||
Permission: grt.Permission,
|
||||
Permission: types.Permission(grt.Permission),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2174,7 +2182,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
err = auth.VerifyObjectCopyAccess(ctx.Context(), c.be, copySource,
|
||||
auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2306,7 +2314,7 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2437,7 +2445,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2469,7 +2477,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2501,7 +2509,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2532,7 +2540,7 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2585,7 +2593,7 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2658,7 +2666,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2696,7 +2704,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2737,7 +2745,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2826,7 +2834,7 @@ func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -2909,7 +2917,7 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -3080,7 +3088,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -3135,7 +3143,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
AclPermission: auth.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -3189,11 +3197,25 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if len(data.Parts) == 0 {
|
||||
if c.debug {
|
||||
log.Println("empty parts provided for complete multipart upload")
|
||||
}
|
||||
return SendXMLResponse(ctx, nil,
|
||||
s3err.GetAPIError(s3err.ErrEmptyParts),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
MetricsMng: c.mm,
|
||||
Action: metrics.ActionCompleteMultipartUpload,
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
err = auth.VerifyAccess(ctx.Context(), c.be,
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
@@ -3253,7 +3275,7 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
|
||||
auth.AccessOptions{
|
||||
Readonly: c.readonly,
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
AclPermission: auth.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
|
||||
@@ -941,12 +941,12 @@ func TestS3ApiController_PutActions(t *testing.T) {
|
||||
</Tagging>
|
||||
`
|
||||
|
||||
retentionBody := `
|
||||
<Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Mode>GOVERNANCE</Mode>
|
||||
<RetainUntilDate>2025-01-01T00:00:00Z</RetainUntilDate>
|
||||
</Retention>
|
||||
`
|
||||
//retentionBody := `
|
||||
//<Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
// <Mode>GOVERNANCE</Mode>
|
||||
// <RetainUntilDate>2025-01-01T00:00:00Z</RetainUntilDate>
|
||||
//</Retention>
|
||||
//`
|
||||
|
||||
legalHoldBody := `
|
||||
<LegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
@@ -1076,15 +1076,15 @@ func TestS3ApiController_PutActions(t *testing.T) {
|
||||
wantErr: false,
|
||||
statusCode: 400,
|
||||
},
|
||||
{
|
||||
name: "put-object-retention-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?retention", strings.NewReader(retentionBody)),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
//{
|
||||
// name: "put-object-retention-success",
|
||||
// app: app,
|
||||
// args: args{
|
||||
// req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?retention", strings.NewReader(retentionBody)),
|
||||
// },
|
||||
// wantErr: false,
|
||||
// statusCode: 200,
|
||||
//},
|
||||
{
|
||||
name: "put-legal-hold-invalid-request",
|
||||
app: app,
|
||||
@@ -1713,6 +1713,19 @@ func TestS3ApiController_CreateActions(t *testing.T) {
|
||||
</SelectObjectContentRequest>
|
||||
`
|
||||
|
||||
completMpBody := `
|
||||
<CompleteMultipartUpload xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Part>
|
||||
<ETag>etag</ETag>
|
||||
<PartNumber>1</PartNumber>
|
||||
</Part>
|
||||
</CompleteMultipartUpload>
|
||||
`
|
||||
|
||||
completMpEmptyBody := `
|
||||
<CompleteMultipartUpload xmlns="http://s3.amazonaws.com/doc/2006-03-01/"></CompleteMultipartUpload>
|
||||
`
|
||||
|
||||
app.Use(func(ctx *fiber.Ctx) error {
|
||||
ctx.Locals("account", auth.Account{Access: "valid access"})
|
||||
ctx.Locals("isRoot", true)
|
||||
@@ -1765,11 +1778,20 @@ func TestS3ApiController_CreateActions(t *testing.T) {
|
||||
wantErr: false,
|
||||
statusCode: 400,
|
||||
},
|
||||
{
|
||||
name: "Complete-multipart-upload-empty-parts",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?uploadId=23423", strings.NewReader(completMpEmptyBody)),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 400,
|
||||
},
|
||||
{
|
||||
name: "Complete-multipart-upload-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?uploadId=23423", strings.NewReader(`<root><key>body</key></root>`)),
|
||||
req: httptest.NewRequest(http.MethodPost, "/my-bucket/my-key?uploadId=23423", strings.NewReader(completMpBody)),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
|
||||
@@ -47,7 +47,7 @@ func ProcessChunkedBody(root RootUserConfig, iam auth.IAMService, logger s3log.A
|
||||
if utils.IsBigDataAction(ctx) {
|
||||
var err error
|
||||
wrapBodyReader(ctx, func(r io.Reader) io.Reader {
|
||||
var cr *utils.ChunkReader
|
||||
var cr io.Reader
|
||||
cr, err = utils.NewChunkReader(ctx, r, authData, region, acct.Secret, date)
|
||||
return cr
|
||||
})
|
||||
|
||||
@@ -260,19 +260,3 @@ func removeSpace(str string) string {
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
var (
|
||||
specialValues = map[string]bool{
|
||||
"UNSIGNED-PAYLOAD": true,
|
||||
"STREAMING-UNSIGNED-PAYLOAD-TRAILER": true,
|
||||
"STREAMING-AWS4-HMAC-SHA256-PAYLOAD": true,
|
||||
"STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER": true,
|
||||
"STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD": true,
|
||||
"STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER": true,
|
||||
}
|
||||
)
|
||||
|
||||
// IsSpecialPayload checks for streaming/unsigned authorization types
|
||||
func IsSpecialPayload(str string) bool {
|
||||
return specialValues[str]
|
||||
}
|
||||
|
||||
@@ -15,260 +15,85 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
// chunked uploads described in:
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
|
||||
type payloadType string
|
||||
|
||||
const (
|
||||
chunkHdrStr = ";chunk-signature="
|
||||
chunkHdrDelim = "\r\n"
|
||||
zeroLenSig = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
awsV4 = "AWS4"
|
||||
awsS3Service = "s3"
|
||||
awsV4Request = "aws4_request"
|
||||
streamPayloadAlgo = "AWS4-HMAC-SHA256-PAYLOAD"
|
||||
payloadTypeUnsigned payloadType = "UNSIGNED-PAYLOAD"
|
||||
payloadTypeStreamingUnsignedTrailer payloadType = "STREAMING-UNSIGNED-PAYLOAD-TRAILER"
|
||||
payloadTypeStreamingSigned payloadType = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
|
||||
payloadTypeStreamingSignedTrailer payloadType = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER"
|
||||
payloadTypeStreamingEcdsa payloadType = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD"
|
||||
payloadTypeStreamingEcdsaTrailer payloadType = "STREAMING-AWS4-ECDSA-P256-SHA256-PAYLOAD-TRAILER"
|
||||
)
|
||||
|
||||
// ChunkReader reads from chunked upload request body, and returns
|
||||
// object data stream
|
||||
type ChunkReader struct {
|
||||
r io.Reader
|
||||
signingKey []byte
|
||||
prevSig string
|
||||
parsedSig string
|
||||
currentChunkSize int64
|
||||
chunkDataLeft int64
|
||||
trailerExpected int
|
||||
stash []byte
|
||||
chunkHash hash.Hash
|
||||
strToSignPrefix string
|
||||
skipcheck bool
|
||||
}
|
||||
|
||||
// NewChunkReader reads from request body io.Reader and parses out the
|
||||
// chunk metadata in stream. The headers are validated for proper signatures.
|
||||
// Reading from the chunk reader will read only the object data stream
|
||||
// without the chunk headers/trailers.
|
||||
func NewChunkReader(ctx *fiber.Ctx, r io.Reader, authdata AuthData, region, secret string, date time.Time) (*ChunkReader, error) {
|
||||
return &ChunkReader{
|
||||
r: r,
|
||||
signingKey: getSigningKey(secret, region, date),
|
||||
// the authdata.Signature is validated in the auth-reader,
|
||||
// so we can use that here without any other checks
|
||||
prevSig: authdata.Signature,
|
||||
chunkHash: sha256.New(),
|
||||
strToSignPrefix: getStringToSignPrefix(date, region),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read satisfies the io.Reader for this type
|
||||
func (cr *ChunkReader) Read(p []byte) (int, error) {
|
||||
n, err := cr.r.Read(p)
|
||||
if err != nil && err != io.EOF {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if cr.chunkDataLeft < int64(n) {
|
||||
chunkSize := cr.chunkDataLeft
|
||||
if chunkSize > 0 {
|
||||
cr.chunkHash.Write(p[:chunkSize])
|
||||
}
|
||||
n, err := cr.parseAndRemoveChunkInfo(p[chunkSize:n])
|
||||
n += int(chunkSize)
|
||||
return n, err
|
||||
}
|
||||
|
||||
cr.chunkDataLeft -= int64(n)
|
||||
cr.chunkHash.Write(p[:n])
|
||||
return n, err
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html#sigv4-chunked-body-definition
|
||||
// This part is the same for all chunks,
|
||||
// only the previous signature and hash of current chunk changes
|
||||
func getStringToSignPrefix(date time.Time, region string) string {
|
||||
credentialScope := fmt.Sprintf("%s/%s/%s/%s",
|
||||
date.Format("20060102"),
|
||||
region,
|
||||
awsS3Service,
|
||||
awsV4Request)
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s",
|
||||
streamPayloadAlgo,
|
||||
date.Format("20060102T150405Z"),
|
||||
credentialScope)
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html#sigv4-chunked-body-definition
|
||||
// signature For each chunk, you calculate the signature using the following
|
||||
// string to sign. For the first chunk, you use the seed-signature as the
|
||||
// previous signature.
|
||||
func getChunkStringToSign(prefix, prevSig string, chunkHash []byte) string {
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s",
|
||||
prefix,
|
||||
prevSig,
|
||||
zeroLenSig,
|
||||
hex.EncodeToString(chunkHash))
|
||||
}
|
||||
|
||||
// The provided p should have all of the previous chunk data and trailer
|
||||
// consumed already. The positioning here is expected that p[0] starts the
|
||||
// new chunk size with the ";chunk-signature=" following. The only exception
|
||||
// is if we started consuming the trailer, but hit the end of the read buffer.
|
||||
// In this case, parseAndRemoveChunkInfo is called with skipcheck=true to
|
||||
// finish consuming the final trailer bytes.
|
||||
// This parses the chunk metadata in situ without allocating an extra buffer.
|
||||
// It will just read and validate the chunk metadata and then move the
|
||||
// following chunk data to overwrite the metadata in the provided buffer.
|
||||
func (cr *ChunkReader) parseAndRemoveChunkInfo(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
|
||||
if !cr.skipcheck && cr.parsedSig != "" {
|
||||
chunkhash := cr.chunkHash.Sum(nil)
|
||||
cr.chunkHash.Reset()
|
||||
|
||||
sigstr := getChunkStringToSign(cr.strToSignPrefix, cr.prevSig, chunkhash)
|
||||
cr.prevSig = hex.EncodeToString(hmac256(cr.signingKey, []byte(sigstr)))
|
||||
|
||||
if cr.currentChunkSize != 0 && cr.prevSig != cr.parsedSig {
|
||||
return 0, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)
|
||||
}
|
||||
}
|
||||
|
||||
if cr.trailerExpected != 0 {
|
||||
if len(p) < len(chunkHdrDelim) {
|
||||
// This is the special case where we need to consume the
|
||||
// trailer, but instead hit the end of the buffer. The
|
||||
// subsequent call will finish consuming the trailer.
|
||||
cr.chunkDataLeft = 0
|
||||
cr.trailerExpected -= len(p)
|
||||
cr.skipcheck = true
|
||||
return 0, nil
|
||||
}
|
||||
// move data up to remove trailer
|
||||
copy(p, p[cr.trailerExpected:])
|
||||
n -= cr.trailerExpected
|
||||
}
|
||||
|
||||
cr.skipcheck = false
|
||||
|
||||
chunkSize, sig, bufOffset, err := cr.parseChunkHeaderBytes(p[:n])
|
||||
cr.currentChunkSize = chunkSize
|
||||
cr.parsedSig = sig
|
||||
if err == errskipHeader {
|
||||
cr.chunkDataLeft = 0
|
||||
return 0, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if chunkSize == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
cr.trailerExpected = len(chunkHdrDelim)
|
||||
|
||||
// move data up to remove chunk header
|
||||
copy(p, p[bufOffset:n])
|
||||
n -= bufOffset
|
||||
|
||||
// if remaining buffer larger than chunk data,
|
||||
// parse next header in buffer
|
||||
if int64(n) > chunkSize {
|
||||
cr.chunkDataLeft = 0
|
||||
cr.chunkHash.Write(p[:chunkSize])
|
||||
n, err := cr.parseAndRemoveChunkInfo(p[chunkSize:n])
|
||||
if (chunkSize + int64(n)) > math.MaxInt {
|
||||
return 0, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)
|
||||
}
|
||||
return n + int(chunkSize), err
|
||||
}
|
||||
|
||||
cr.chunkDataLeft = chunkSize - int64(n)
|
||||
cr.chunkHash.Write(p[:n])
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
|
||||
// Task 3: Calculate Signature
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#signing-request-intro
|
||||
func getSigningKey(secret, region string, date time.Time) []byte {
|
||||
dateKey := hmac256([]byte(awsV4+secret), []byte(date.Format(yyyymmdd)))
|
||||
dateRegionKey := hmac256(dateKey, []byte(region))
|
||||
dateRegionServiceKey := hmac256(dateRegionKey, []byte(awsS3Service))
|
||||
signingKey := hmac256(dateRegionServiceKey, []byte(awsV4Request))
|
||||
return signingKey
|
||||
}
|
||||
|
||||
func hmac256(key []byte, data []byte) []byte {
|
||||
hash := hmac.New(sha256.New, key)
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidChunkFormat = errors.New("invalid chunk header format")
|
||||
errskipHeader = errors.New("skip to next header")
|
||||
specialValues = map[payloadType]bool{
|
||||
payloadTypeUnsigned: true,
|
||||
payloadTypeStreamingUnsignedTrailer: true,
|
||||
payloadTypeStreamingSigned: true,
|
||||
payloadTypeStreamingSignedTrailer: true,
|
||||
payloadTypeStreamingEcdsa: true,
|
||||
payloadTypeStreamingEcdsaTrailer: true,
|
||||
}
|
||||
)
|
||||
|
||||
func (pt payloadType) isValid() bool {
|
||||
return pt == payloadTypeUnsigned ||
|
||||
pt == payloadTypeStreamingUnsignedTrailer ||
|
||||
pt == payloadTypeStreamingSigned ||
|
||||
pt == payloadTypeStreamingSignedTrailer ||
|
||||
pt == payloadTypeStreamingEcdsa ||
|
||||
pt == payloadTypeStreamingEcdsaTrailer
|
||||
}
|
||||
|
||||
type checksumType string
|
||||
|
||||
const (
|
||||
maxHeaderSize = 1024
|
||||
checksumTypeCrc32 checksumType = "x-amz-checksum-crc32"
|
||||
checksumTypeCrc32c checksumType = "x-amz-checksum-crc32c"
|
||||
checksumTypeSha1 checksumType = "x-amz-checksum-sha1"
|
||||
checksumTypeSha256 checksumType = "x-amz-checksum-sha256"
|
||||
checksumTypeCrc64nvme checksumType = "x-amz-checksum-crc64nvme"
|
||||
)
|
||||
|
||||
// Theis returns the chunk payload size, signature, data start offset, and
|
||||
// error if any. See the AWS documentation for the chunk header format. The
|
||||
// header[0] byte is expected to be the first byte of the chunk size here.
|
||||
func (cr *ChunkReader) parseChunkHeaderBytes(header []byte) (int64, string, int, error) {
|
||||
stashLen := len(cr.stash)
|
||||
if cr.stash != nil {
|
||||
tmp := make([]byte, maxHeaderSize)
|
||||
copy(tmp, cr.stash)
|
||||
copy(tmp[len(cr.stash):], header)
|
||||
header = tmp
|
||||
cr.stash = nil
|
||||
}
|
||||
|
||||
semicolonIndex := bytes.Index(header, []byte(chunkHdrStr))
|
||||
if semicolonIndex == -1 {
|
||||
cr.stash = make([]byte, len(header))
|
||||
copy(cr.stash, header)
|
||||
cr.trailerExpected = 0
|
||||
return 0, "", 0, errskipHeader
|
||||
}
|
||||
|
||||
sigIndex := semicolonIndex + len(chunkHdrStr)
|
||||
sigEndIndex := bytes.Index(header[sigIndex:], []byte(chunkHdrDelim))
|
||||
if sigEndIndex == -1 {
|
||||
cr.stash = make([]byte, len(header))
|
||||
copy(cr.stash, header)
|
||||
cr.trailerExpected = 0
|
||||
return 0, "", 0, errskipHeader
|
||||
}
|
||||
|
||||
chunkSizeBytes := header[:semicolonIndex]
|
||||
chunkSize, err := strconv.ParseInt(string(chunkSizeBytes), 16, 64)
|
||||
if err != nil {
|
||||
return 0, "", 0, errInvalidChunkFormat
|
||||
}
|
||||
|
||||
signature := string(header[sigIndex:(sigIndex + sigEndIndex)])
|
||||
dataStartOffset := sigIndex + sigEndIndex + len(chunkHdrDelim)
|
||||
|
||||
return chunkSize, signature, dataStartOffset - stashLen, nil
|
||||
func (c checksumType) isValid() bool {
|
||||
return c == checksumTypeCrc32 ||
|
||||
c == checksumTypeCrc32c ||
|
||||
c == checksumTypeSha1 ||
|
||||
c == checksumTypeSha256 ||
|
||||
c == checksumTypeCrc64nvme
|
||||
}
|
||||
|
||||
// IsSpecialPayload checks for streaming/unsigned authorization types
|
||||
func IsSpecialPayload(str string) bool {
|
||||
return specialValues[payloadType(str)]
|
||||
}
|
||||
|
||||
func NewChunkReader(ctx *fiber.Ctx, r io.Reader, authdata AuthData, region, secret string, date time.Time) (io.Reader, error) {
|
||||
contentSha256 := payloadType(ctx.Get("X-Amz-Content-Sha256"))
|
||||
if !contentSha256.isValid() {
|
||||
//TODO: Add proper APIError
|
||||
return nil, fmt.Errorf("invalid x-amz-content-sha256: %v", string(contentSha256))
|
||||
}
|
||||
|
||||
checksumType := checksumType(ctx.Get("X-Amz-Trailer"))
|
||||
if checksumType != "" && !checksumType.isValid() {
|
||||
//TODO: Add proper APIError
|
||||
return nil, fmt.Errorf("invalid X-Amz-Trailer: %v", checksumType)
|
||||
}
|
||||
|
||||
switch contentSha256 {
|
||||
case payloadTypeStreamingUnsignedTrailer:
|
||||
return NewUnsignedChunkReader(r, checksumType)
|
||||
//TODO: Add other chunk readers
|
||||
}
|
||||
|
||||
return NewSignedChunkReader(r, authdata, region, secret, date)
|
||||
}
|
||||
|
||||
276
s3api/utils/signed-chunk-reader.go
Normal file
276
s3api/utils/signed-chunk-reader.go
Normal file
@@ -0,0 +1,276 @@
|
||||
// Copyright 2024 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
// chunked uploads described in:
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
|
||||
|
||||
const (
|
||||
chunkHdrStr = ";chunk-signature="
|
||||
chunkHdrDelim = "\r\n"
|
||||
zeroLenSig = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
awsV4 = "AWS4"
|
||||
awsS3Service = "s3"
|
||||
awsV4Request = "aws4_request"
|
||||
streamPayloadAlgo = "AWS4-HMAC-SHA256-PAYLOAD"
|
||||
)
|
||||
|
||||
// ChunkReader reads from chunked upload request body, and returns
|
||||
// object data stream
|
||||
type ChunkReader struct {
|
||||
r io.Reader
|
||||
signingKey []byte
|
||||
prevSig string
|
||||
parsedSig string
|
||||
currentChunkSize int64
|
||||
chunkDataLeft int64
|
||||
trailerExpected int
|
||||
stash []byte
|
||||
chunkHash hash.Hash
|
||||
strToSignPrefix string
|
||||
skipcheck bool
|
||||
}
|
||||
|
||||
// NewChunkReader reads from request body io.Reader and parses out the
|
||||
// chunk metadata in stream. The headers are validated for proper signatures.
|
||||
// Reading from the chunk reader will read only the object data stream
|
||||
// without the chunk headers/trailers.
|
||||
func NewSignedChunkReader(r io.Reader, authdata AuthData, region, secret string, date time.Time) (io.Reader, error) {
|
||||
return &ChunkReader{
|
||||
r: r,
|
||||
signingKey: getSigningKey(secret, region, date),
|
||||
// the authdata.Signature is validated in the auth-reader,
|
||||
// so we can use that here without any other checks
|
||||
prevSig: authdata.Signature,
|
||||
chunkHash: sha256.New(),
|
||||
strToSignPrefix: getStringToSignPrefix(date, region),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read satisfies the io.Reader for this type
|
||||
func (cr *ChunkReader) Read(p []byte) (int, error) {
|
||||
n, err := cr.r.Read(p)
|
||||
if err != nil && err != io.EOF {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if cr.chunkDataLeft < int64(n) {
|
||||
chunkSize := cr.chunkDataLeft
|
||||
if chunkSize > 0 {
|
||||
cr.chunkHash.Write(p[:chunkSize])
|
||||
}
|
||||
n, err := cr.parseAndRemoveChunkInfo(p[chunkSize:n])
|
||||
n += int(chunkSize)
|
||||
return n, err
|
||||
}
|
||||
|
||||
cr.chunkDataLeft -= int64(n)
|
||||
cr.chunkHash.Write(p[:n])
|
||||
return n, err
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html#sigv4-chunked-body-definition
|
||||
// This part is the same for all chunks,
|
||||
// only the previous signature and hash of current chunk changes
|
||||
func getStringToSignPrefix(date time.Time, region string) string {
|
||||
credentialScope := fmt.Sprintf("%s/%s/%s/%s",
|
||||
date.Format("20060102"),
|
||||
region,
|
||||
awsS3Service,
|
||||
awsV4Request)
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s",
|
||||
streamPayloadAlgo,
|
||||
date.Format("20060102T150405Z"),
|
||||
credentialScope)
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html#sigv4-chunked-body-definition
|
||||
// signature For each chunk, you calculate the signature using the following
|
||||
// string to sign. For the first chunk, you use the seed-signature as the
|
||||
// previous signature.
|
||||
func getChunkStringToSign(prefix, prevSig string, chunkHash []byte) string {
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s",
|
||||
prefix,
|
||||
prevSig,
|
||||
zeroLenSig,
|
||||
hex.EncodeToString(chunkHash))
|
||||
}
|
||||
|
||||
// The provided p should have all of the previous chunk data and trailer
|
||||
// consumed already. The positioning here is expected that p[0] starts the
|
||||
// new chunk size with the ";chunk-signature=" following. The only exception
|
||||
// is if we started consuming the trailer, but hit the end of the read buffer.
|
||||
// In this case, parseAndRemoveChunkInfo is called with skipcheck=true to
|
||||
// finish consuming the final trailer bytes.
|
||||
// This parses the chunk metadata in situ without allocating an extra buffer.
|
||||
// It will just read and validate the chunk metadata and then move the
|
||||
// following chunk data to overwrite the metadata in the provided buffer.
|
||||
func (cr *ChunkReader) parseAndRemoveChunkInfo(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
|
||||
if !cr.skipcheck && cr.parsedSig != "" {
|
||||
chunkhash := cr.chunkHash.Sum(nil)
|
||||
cr.chunkHash.Reset()
|
||||
|
||||
sigstr := getChunkStringToSign(cr.strToSignPrefix, cr.prevSig, chunkhash)
|
||||
cr.prevSig = hex.EncodeToString(hmac256(cr.signingKey, []byte(sigstr)))
|
||||
|
||||
if cr.currentChunkSize != 0 && cr.prevSig != cr.parsedSig {
|
||||
return 0, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)
|
||||
}
|
||||
}
|
||||
|
||||
if cr.trailerExpected != 0 {
|
||||
if len(p) < len(chunkHdrDelim) {
|
||||
// This is the special case where we need to consume the
|
||||
// trailer, but instead hit the end of the buffer. The
|
||||
// subsequent call will finish consuming the trailer.
|
||||
cr.chunkDataLeft = 0
|
||||
cr.trailerExpected -= len(p)
|
||||
cr.skipcheck = true
|
||||
return 0, nil
|
||||
}
|
||||
// move data up to remove trailer
|
||||
copy(p, p[cr.trailerExpected:])
|
||||
n -= cr.trailerExpected
|
||||
}
|
||||
|
||||
cr.skipcheck = false
|
||||
|
||||
chunkSize, sig, bufOffset, err := cr.parseChunkHeaderBytes(p[:n])
|
||||
cr.currentChunkSize = chunkSize
|
||||
cr.parsedSig = sig
|
||||
if err == errskipHeader {
|
||||
cr.chunkDataLeft = 0
|
||||
return 0, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if chunkSize == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
cr.trailerExpected = len(chunkHdrDelim)
|
||||
|
||||
// move data up to remove chunk header
|
||||
copy(p, p[bufOffset:n])
|
||||
n -= bufOffset
|
||||
|
||||
// if remaining buffer larger than chunk data,
|
||||
// parse next header in buffer
|
||||
if int64(n) > chunkSize {
|
||||
cr.chunkDataLeft = 0
|
||||
cr.chunkHash.Write(p[:chunkSize])
|
||||
n, err := cr.parseAndRemoveChunkInfo(p[chunkSize:n])
|
||||
if (chunkSize + int64(n)) > math.MaxInt {
|
||||
return 0, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)
|
||||
}
|
||||
return n + int(chunkSize), err
|
||||
}
|
||||
|
||||
cr.chunkDataLeft = chunkSize - int64(n)
|
||||
cr.chunkHash.Write(p[:n])
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
|
||||
// Task 3: Calculate Signature
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#signing-request-intro
|
||||
func getSigningKey(secret, region string, date time.Time) []byte {
|
||||
dateKey := hmac256([]byte(awsV4+secret), []byte(date.Format(yyyymmdd)))
|
||||
dateRegionKey := hmac256(dateKey, []byte(region))
|
||||
dateRegionServiceKey := hmac256(dateRegionKey, []byte(awsS3Service))
|
||||
signingKey := hmac256(dateRegionServiceKey, []byte(awsV4Request))
|
||||
return signingKey
|
||||
}
|
||||
|
||||
func hmac256(key []byte, data []byte) []byte {
|
||||
hash := hmac.New(sha256.New, key)
|
||||
hash.Write(data)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidChunkFormat = errors.New("invalid chunk header format")
|
||||
errskipHeader = errors.New("skip to next header")
|
||||
)
|
||||
|
||||
const (
|
||||
maxHeaderSize = 1024
|
||||
)
|
||||
|
||||
// This returns the chunk payload size, signature, data start offset, and
|
||||
// error if any. See the AWS documentation for the chunk header format. The
|
||||
// header[0] byte is expected to be the first byte of the chunk size here.
|
||||
func (cr *ChunkReader) parseChunkHeaderBytes(header []byte) (int64, string, int, error) {
|
||||
stashLen := len(cr.stash)
|
||||
if stashLen > maxHeaderSize {
|
||||
return 0, "", 0, errInvalidChunkFormat
|
||||
}
|
||||
if cr.stash != nil {
|
||||
tmp := make([]byte, maxHeaderSize)
|
||||
copy(tmp, cr.stash)
|
||||
copy(tmp[len(cr.stash):], header)
|
||||
header = tmp
|
||||
cr.stash = nil
|
||||
}
|
||||
|
||||
semicolonIndex := bytes.Index(header, []byte(chunkHdrStr))
|
||||
if semicolonIndex == -1 {
|
||||
cr.stash = make([]byte, len(header))
|
||||
copy(cr.stash, header)
|
||||
cr.trailerExpected = 0
|
||||
return 0, "", 0, errskipHeader
|
||||
}
|
||||
|
||||
sigIndex := semicolonIndex + len(chunkHdrStr)
|
||||
sigEndIndex := bytes.Index(header[sigIndex:], []byte(chunkHdrDelim))
|
||||
if sigEndIndex == -1 {
|
||||
cr.stash = make([]byte, len(header))
|
||||
copy(cr.stash, header)
|
||||
cr.trailerExpected = 0
|
||||
return 0, "", 0, errskipHeader
|
||||
}
|
||||
|
||||
chunkSizeBytes := header[:semicolonIndex]
|
||||
chunkSize, err := strconv.ParseInt(string(chunkSizeBytes), 16, 64)
|
||||
if err != nil {
|
||||
return 0, "", 0, errInvalidChunkFormat
|
||||
}
|
||||
|
||||
signature := string(header[sigIndex:(sigIndex + sigEndIndex)])
|
||||
dataStartOffset := sigIndex + sigEndIndex + len(chunkHdrDelim)
|
||||
|
||||
return chunkSize, signature, dataStartOffset - stashLen, nil
|
||||
}
|
||||
235
s3api/utils/unsigned-chunk-reader.go
Normal file
235
s3api/utils/unsigned-chunk-reader.go
Normal file
@@ -0,0 +1,235 @@
|
||||
// Copyright 2024 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/crc32"
|
||||
"hash/crc64"
|
||||
"io"
|
||||
"math/bits"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
trailerDelim = []byte{'\n', '\r', '\n'}
|
||||
errMalformedEncoding = errors.New("malformed chunk encoding")
|
||||
)
|
||||
|
||||
type UnsignedChunkReader struct {
|
||||
reader *bufio.Reader
|
||||
checksumType checksumType
|
||||
expectedChecksum string
|
||||
hasher hash.Hash
|
||||
stash []byte
|
||||
chunkCounter int
|
||||
offset int
|
||||
}
|
||||
|
||||
func NewUnsignedChunkReader(r io.Reader, ct checksumType) (*UnsignedChunkReader, error) {
|
||||
hasher, err := getHasher(ct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UnsignedChunkReader{
|
||||
reader: bufio.NewReader(r),
|
||||
checksumType: ct,
|
||||
stash: make([]byte, 0),
|
||||
hasher: hasher,
|
||||
chunkCounter: 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ucr *UnsignedChunkReader) Read(p []byte) (int, error) {
|
||||
// First read any stashed data
|
||||
if len(ucr.stash) != 0 {
|
||||
n := copy(p, ucr.stash)
|
||||
ucr.offset += n
|
||||
|
||||
if n < len(ucr.stash) {
|
||||
ucr.stash = ucr.stash[n:]
|
||||
ucr.offset = 0
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
// Read the chunk size
|
||||
chunkSize, err := ucr.extractChunkSize()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if chunkSize == 0 {
|
||||
// Stop reading parsing payloads as 0 sized chunk is reached
|
||||
break
|
||||
}
|
||||
rdr := io.TeeReader(ucr.reader, ucr.hasher)
|
||||
payload := make([]byte, chunkSize)
|
||||
// Read and cache the payload
|
||||
_, err = io.ReadFull(rdr, payload)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Skip the trailing "\r\n"
|
||||
if err := ucr.readAndSkip('\r', '\n'); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Copy the payload into the io.Reader buffer
|
||||
n := copy(p[ucr.offset:], payload)
|
||||
ucr.offset += n
|
||||
ucr.chunkCounter++
|
||||
|
||||
if int64(n) < chunkSize {
|
||||
// stash the remaining data
|
||||
ucr.stash = payload[n:]
|
||||
dataRead := ucr.offset
|
||||
ucr.offset = 0
|
||||
return dataRead, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Read and validate trailers
|
||||
if err := ucr.readTrailer(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ucr.offset, io.EOF
|
||||
}
|
||||
|
||||
// Reads and validates the bytes provided from the underlying io.Reader
|
||||
func (ucr *UnsignedChunkReader) readAndSkip(data ...byte) error {
|
||||
for _, d := range data {
|
||||
b, err := ucr.reader.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if b != d {
|
||||
return errMalformedEncoding
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extracts the chunk size from the payload
|
||||
func (ucr *UnsignedChunkReader) extractChunkSize() (int64, error) {
|
||||
line, err := ucr.reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return 0, errMalformedEncoding
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
chunkSize, err := strconv.ParseInt(line, 16, 64)
|
||||
if err != nil {
|
||||
return 0, errMalformedEncoding
|
||||
}
|
||||
|
||||
return chunkSize, nil
|
||||
}
|
||||
|
||||
// Reads and validates the trailer at the end
|
||||
func (ucr *UnsignedChunkReader) readTrailer() error {
|
||||
var trailerBuffer bytes.Buffer
|
||||
|
||||
for {
|
||||
v, err := ucr.reader.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
if v != '\r' {
|
||||
trailerBuffer.WriteByte(v)
|
||||
continue
|
||||
}
|
||||
var tmp [3]byte
|
||||
_, err = io.ReadFull(ucr.reader, tmp[:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(tmp[:], trailerDelim) {
|
||||
return errMalformedEncoding
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Parse the trailer
|
||||
trailerHeader := trailerBuffer.String()
|
||||
trailerHeader = strings.TrimSpace(trailerHeader)
|
||||
trailerHeaderParts := strings.Split(trailerHeader, ":")
|
||||
if len(trailerHeaderParts) != 2 {
|
||||
return errMalformedEncoding
|
||||
}
|
||||
|
||||
if trailerHeaderParts[0] != string(ucr.checksumType) {
|
||||
//TODO: handle the error
|
||||
return errMalformedEncoding
|
||||
}
|
||||
|
||||
ucr.expectedChecksum = trailerHeaderParts[1]
|
||||
|
||||
// Validate checksum
|
||||
return ucr.validateChecksum()
|
||||
}
|
||||
|
||||
// Validates the trailing checksum sent at the end
|
||||
func (ucr *UnsignedChunkReader) validateChecksum() error {
|
||||
csum := ucr.hasher.Sum(nil)
|
||||
checksum := base64.StdEncoding.EncodeToString(csum)
|
||||
|
||||
if checksum != ucr.expectedChecksum {
|
||||
return fmt.Errorf("actual checksum: %v, expected checksum: %v", checksum, ucr.expectedChecksum)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retruns the hash calculator based on the hash type provided
|
||||
func getHasher(ct checksumType) (hash.Hash, error) {
|
||||
switch ct {
|
||||
case checksumTypeCrc32:
|
||||
return crc32.NewIEEE(), nil
|
||||
case checksumTypeCrc32c:
|
||||
return crc32.New(crc32.MakeTable(crc32.Castagnoli)), nil
|
||||
case checksumTypeCrc64nvme:
|
||||
table := crc64.MakeTable(bits.Reverse64(0xad93d23594c93659))
|
||||
return crc64.New(table), nil
|
||||
case checksumTypeSha1:
|
||||
return sha1.New(), nil
|
||||
case checksumTypeSha256:
|
||||
return sha256.New(), nil
|
||||
default:
|
||||
return nil, errors.New("unsupported checksum type")
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,30 @@ func SetResponseHeaders(ctx *fiber.Ctx, headers []CustomHeader) {
|
||||
}
|
||||
}
|
||||
|
||||
// Streams the response body by chunks
|
||||
func StreamResponseBody(ctx *fiber.Ctx, rdr io.ReadCloser) error {
|
||||
buf := make([]byte, 4096) // 4KB chunks
|
||||
defer rdr.Close()
|
||||
for {
|
||||
n, err := rdr.Read(buf)
|
||||
if n > 0 {
|
||||
_, writeErr := ctx.Write(buf[:n])
|
||||
if writeErr != nil {
|
||||
return fmt.Errorf("write chunk: %w", writeErr)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return fmt.Errorf("read chunk: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsValidBucketName(bucket string) bool {
|
||||
if len(bucket) < 3 || len(bucket) > 63 {
|
||||
return false
|
||||
|
||||
@@ -72,6 +72,7 @@ const (
|
||||
ErrInvalidPartNumberMarker
|
||||
ErrInvalidObjectAttributes
|
||||
ErrInvalidPart
|
||||
ErrEmptyParts
|
||||
ErrInvalidPartNumber
|
||||
ErrInternalError
|
||||
ErrInvalidCopyDest
|
||||
@@ -144,6 +145,7 @@ const (
|
||||
ErrExistingObjectIsDirectory
|
||||
ErrObjectParentIsFile
|
||||
ErrDirectoryObjectContainsData
|
||||
ErrDirectoryNotEmpty
|
||||
ErrQuotaExceeded
|
||||
ErrVersioningNotConfigured
|
||||
|
||||
@@ -153,6 +155,7 @@ const (
|
||||
ErrAdminUserExists
|
||||
ErrAdminInvalidUserRole
|
||||
ErrAdminMissingUserAcess
|
||||
ErrAdminMethodNotSupported
|
||||
)
|
||||
|
||||
var errorCodeResponse = map[ErrorCode]APIError{
|
||||
@@ -251,6 +254,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrEmptyParts: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "You must specify at least one part",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidPartNumber: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Part number must be an integer between 1 and 10000, inclusive.",
|
||||
@@ -593,6 +601,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "Directory object contains data payload.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrDirectoryNotEmpty: {
|
||||
Code: "ErrDirectoryNotEmpty",
|
||||
Description: "Directory object not empty.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrQuotaExceeded: {
|
||||
Code: "QuotaExceeded",
|
||||
Description: "Your request was denied due to quota exceeded.",
|
||||
@@ -630,6 +643,11 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "User access key ID is missing.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrAdminMethodNotSupported: {
|
||||
Code: "XAdminMethodNotSupported",
|
||||
Description: "The method is not supported in single root user mode.",
|
||||
HTTPStatusCode: http.StatusNotImplemented,
|
||||
},
|
||||
}
|
||||
|
||||
// GetAPIError provides API Error for input API error code.
|
||||
|
||||
@@ -25,4 +25,5 @@ USERNAME_TWO=HIJKLMN
|
||||
PASSWORD_TWO=OPQRSTU
|
||||
TEST_FILE_FOLDER=$PWD/versity-gwtest-files
|
||||
RECREATE_BUCKETS=true
|
||||
REMOVE_TEST_FILE_FOLDER=true
|
||||
REMOVE_TEST_FILE_FOLDER=true
|
||||
VERSIONING_DIR=/tmp/versioning
|
||||
@@ -3,8 +3,9 @@ FROM ubuntu:latest
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG SECRETS_FILE=tests/.secrets
|
||||
ARG CONFIG_FILE=tests/.env.docker
|
||||
ARG GO_LIBRARY=go1.23.1.linux-arm64.tar.gz
|
||||
ARG AWS_CLI=awscli-exe-linux-aarch64.zip
|
||||
ARG GO_LIBRARY=go1.21.13.linux-arm64.tar.gz
|
||||
# see https://github.com/versity/versitygw/issues/1034
|
||||
ARG AWS_CLI=awscli-exe-linux-aarch64-2.22.35.zip
|
||||
ARG MC_FOLDER=linux-arm64
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
@@ -85,6 +86,7 @@ RUN openssl genpkey -algorithm RSA -out versitygw-docker.pem -pkeyopt rsa_keygen
|
||||
|
||||
ENV WORKSPACE=.
|
||||
ENV VERSITYGW_TEST_ENV=$CONFIG_FILE
|
||||
#ENV AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED
|
||||
|
||||
ENTRYPOINT ["tests/run.sh"]
|
||||
CMD ["s3api,s3,s3cmd,mc,rest"]
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# Command-Line Tests
|
||||
|
||||
## Table of Contents
|
||||
|
||||
[Instructions - Running Locally](#instructions---running-locally)<br>
|
||||
[* Posix Backend](#posix-backend)<br>
|
||||
[* Static Bucket Mode](#static-bucket-mode)<br>
|
||||
[* S3 Backend](#s3-backend)<br>
|
||||
[* Direct Mode](#direct-mode)<br>
|
||||
[Instructions - Running With Docker](#instructions---running-with-docker)<br>
|
||||
[Instructions - Running With Docker-Compose](#instructions---running-with-docker-compose)<br>
|
||||
[Environment Parameters](#environment-parameters)<br>
|
||||
[* Secret](#secret)<br>
|
||||
[* Non-Secret](#non-secret)<br>
|
||||
[REST Scripts](#rest-scripts)<br>
|
||||
|
||||
## Instructions - Running Locally
|
||||
|
||||
### Posix Backend
|
||||
@@ -61,10 +75,11 @@ To communicate directly with s3, in order to compare the gateway results to dire
|
||||
1. Copy `.secrets.default` to `.secrets` in the `tests` folder and change the parameters and add the additional s3 fields explained in the **S3 Backend** section above if running with the s3 backend.
|
||||
2. By default, the dockerfile uses the **arm** architecture (usually modern Mac). If using **amd** (usually earlier Mac or Linux), you can either replace the corresponding `ARG` values directly, or with `arg="<param>=<amd library or folder>"` Also, you can determine which is used by your OS with `uname -a`.
|
||||
3. Build and run the `Dockerfile_test_bats` file. Change the `SECRETS_FILE` and `CONFIG_FILE` parameters to point to your secrets and config file, respectively, if not using the defaults. Example: `docker build -t <tag> -f Dockerfile_test_bats --build-arg="SECRETS_FILE=<file>" --build-arg="CONFIG_FILE=<file>" .`.
|
||||
4. To run the entire suite, run `docker run -it <image name>`. To run an individual suite, pass in the name of the suite as defined in `tests/run.sh` (e.g. REST tests -> `docker run -it <image name> rest`). Also, multiple specific suites can be run, if separated by comma.
|
||||
|
||||
## Instructions - Running with docker-compose
|
||||
|
||||
A file named `docker-compose-bats.yml` is provided in the root folder. Four configurations are provided:
|
||||
A file named `docker-compose-bats.yml` is provided in the root folder. A few configurations are provided, and you can also create your own provided you have a secrets and config file:
|
||||
* insecure (without certificates), with creation/removal of buckets
|
||||
* secure, posix backend, with static buckets
|
||||
* secure, posix backend, with creation/removal of buckets
|
||||
@@ -85,8 +100,18 @@ A single instance can be run with `docker-compose -f docker-compose-bats.yml up
|
||||
|
||||
## Environment Parameters
|
||||
|
||||
### Secret
|
||||
|
||||
**AWS_PROFILE**, **AWS_ENDPOINT_URL**, **AWS_REGION**, **AWS_ACCESS_KEY_ID**, **AWS_SECRET_ACCESS_KEY**: identical to the same parameters in **s3**.
|
||||
|
||||
**AWS_CANONICAL_ID**: for direct mode, the canonical ID for the main user (owner)
|
||||
|
||||
**ACL_AWS_CANONICAL_ID**: for direct mode, the canonical ID for the user to test ACL changes and access by non-owners
|
||||
|
||||
**ACL_AWS_ACCESS_KEY_ID**, **ACL_AWS_ACCESS_SECRET_KEY**: for direct mode, the ID and key for the S3 user in the **ACL_AWS_CANONICAL_ID** account.
|
||||
|
||||
### Non-Secret
|
||||
|
||||
**VERSITY_EXE**: location of the versity executable relative to test folder.
|
||||
|
||||
**RUN_VERSITYGW**: whether to run the versitygw executable, should be set to **false** when running tests directly against **s3**.
|
||||
@@ -134,3 +159,21 @@ A single instance can be run with `docker-compose -f docker-compose-bats.yml up
|
||||
**VERSIONING_DIR**: where to put gateway file versioning info.
|
||||
|
||||
**COMMAND_LOG**: where to store list of client commands, which if using will be reported during test failures.
|
||||
|
||||
**TIME_LOG**: optional log to show duration of individual tests
|
||||
|
||||
**DIRECT_S3_ROOT_ACCOUNT_NAME**: for direct mode, S3 username
|
||||
|
||||
**DELETE_BUCKETS_AFTER_TEST**: whether or not to delete buckets after individual tests, useful for debugging if the post-test bucket state needs to be checked
|
||||
|
||||
## REST Scripts
|
||||
|
||||
REST scripts are included for calls to S3's REST API in the `./tests/rest_scripts/` folder. To call a script, the following parameters are needed:
|
||||
* **AWS_ACCESS_KEY_ID**, **AWS_SECRET_ACCESS_KEY**, etc.
|
||||
* **AWS_ENDPOINT_URL** (default: `https://localhost:7070`)
|
||||
* **OUTPUT_FILE**: file where the command's response data is written
|
||||
* Any other parameters specified at the top of the script file, such as payloads and variables. Sometimes, defaults are included.
|
||||
|
||||
Upon success, the script will return a response code, and write the data to the **OUTPUT_FILE** location.
|
||||
|
||||
Example: `AWS_ACCESS_KEY_ID={id} AWS_SECRET_ACCESS_KEY={key} AWS_ENDPOINT_URL=https://s3.amazonaws.com OUTPUT_FILE=./output_file.xml ./tests/rest_scripts/list_buckets.sh`
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
copy_object() {
|
||||
if [ $# -ne 4 ]; then
|
||||
echo "copy object command requires command type, source, bucket, key"
|
||||
log 2 "copy object command requires command type, source, bucket, key"
|
||||
return 1
|
||||
fi
|
||||
local exit_code=0
|
||||
@@ -24,7 +24,7 @@ copy_object() {
|
||||
record_command "copy-object" "client:$1"
|
||||
if [[ $1 == 's3' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3 cp "$2" s3://"$3/$4" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3api' ]] || [[ $1 == 'aws' ]]; then
|
||||
elif [[ $1 == 's3api' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3api copy-object --copy-source "$2" --bucket "$3" --key "$4" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
log 5 "s3cmd ${S3CMD_OPTS[*]} --no-check-certificate cp s3://$2 s3://$3/$4"
|
||||
@@ -32,12 +32,12 @@ copy_object() {
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
error=$(send_command mc --insecure cp "$MC_ALIAS/$2" "$MC_ALIAS/$3/$4" 2>&1) || exit_code=$?
|
||||
else
|
||||
echo "'copy-object' not implemented for '$1'"
|
||||
log 2 "'copy-object' not implemented for '$1'"
|
||||
return 1
|
||||
fi
|
||||
log 5 "copy object exit code: $exit_code"
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo "error copying object to bucket: $error"
|
||||
log 2 "error copying object to bucket: $error"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
|
||||
@@ -31,7 +31,7 @@ create_bucket() {
|
||||
log 6 "create bucket"
|
||||
if [[ $1 == 's3' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3 mb s3://"$2" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == "aws" ]] || [[ $1 == 's3api' ]]; then
|
||||
elif [[ $1 == 's3api' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3api create-bucket --bucket "$2" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == "s3cmd" ]]; then
|
||||
log 5 "s3cmd ${S3CMD_OPTS[*]} --no-check-certificate mb s3://$2"
|
||||
|
||||
@@ -54,6 +54,7 @@ create_multipart_upload_with_user() {
|
||||
return 1
|
||||
fi
|
||||
upload_id="${upload_id//\"/}"
|
||||
echo "$upload_id"
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ create_presigned_url() {
|
||||
fi
|
||||
|
||||
local presign_result=0
|
||||
if [[ $1 == 'aws' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
presigned_url=$(send_command aws s3 presign "s3://$2/$3" --expires-in 900) || presign_result=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
presigned_url=$(send_command s3cmd --no-check-certificate "${S3CMD_OPTS[@]}" signurl "s3://$2/$3" "$(echo "$(date +%s)" + 900 | bc)") || presign_result=$?
|
||||
|
||||
@@ -32,7 +32,7 @@ delete_bucket() {
|
||||
exit_code=0
|
||||
if [[ $1 == 's3' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3 rb s3://"$2") || exit_code=$?
|
||||
elif [[ $1 == 'aws' ]] || [[ $1 == 's3api' ]]; then
|
||||
elif [[ $1 == 's3api' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3api delete-bucket --bucket "$2" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate rb s3://"$2" 2>&1) || exit_code=$?
|
||||
|
||||
@@ -21,7 +21,7 @@ delete_bucket_policy() {
|
||||
return 1
|
||||
fi
|
||||
local delete_result=0
|
||||
if [[ $1 == 'aws' ]] || [[ $1 == 's3api' ]] || [[ $1 == 's3' ]]; then
|
||||
if [[ $1 == 's3api' ]] || [[ $1 == 's3' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3api delete-bucket-policy --bucket "$2" 2>&1) || delete_result=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate delpolicy "s3://$2" 2>&1) || delete_result=$?
|
||||
|
||||
@@ -21,7 +21,7 @@ delete_bucket_tagging() {
|
||||
return 1
|
||||
fi
|
||||
local result
|
||||
if [[ $1 == 'aws' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
tags=$(send_command aws --no-verify-ssl s3api delete-bucket-tagging --bucket "$2" 2>&1) || result=$?
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
tags=$(send_command mc --insecure tag remove "$MC_ALIAS"/"$2" 2>&1) || result=$?
|
||||
|
||||
@@ -25,7 +25,7 @@ delete_object() {
|
||||
local exit_code=0
|
||||
if [[ $1 == 's3' ]]; then
|
||||
delete_object_error=$(send_command aws --no-verify-ssl s3 rm "s3://$2/$3" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3api' ]] || [[ $1 == 'aws' ]]; then
|
||||
elif [[ $1 == 's3api' ]]; then
|
||||
delete_object_error=$(send_command aws --no-verify-ssl s3api delete-object --bucket "$2" --key "$3" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
delete_object_error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate rm "s3://$2/$3" 2>&1) || exit_code=$?
|
||||
|
||||
@@ -17,22 +17,22 @@
|
||||
delete_object_tagging() {
|
||||
record_command "delete-object-tagging" "client:$1"
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "delete object tagging command missing command type, bucket, key"
|
||||
log 2 "delete object tagging command missing command type, bucket, key"
|
||||
return 1
|
||||
fi
|
||||
delete_result=0
|
||||
if [[ $1 == 'aws' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3api delete-object-tagging --bucket "$2" --key "$3" 2>&1) || delete_result=$?
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
error=$(send_command mc --insecure tag remove "$MC_ALIAS/$2/$3") || delete_result=$?
|
||||
elif [ "$1" == 'rest' ]; then
|
||||
delete_object_tagging_rest "$2" "$3" || delete_result=$?
|
||||
else
|
||||
echo "delete-object-tagging command not implemented for '$1'"
|
||||
log 2 "delete-object-tagging command not implemented for '$1'"
|
||||
return 1
|
||||
fi
|
||||
if [[ $delete_result -ne 0 ]]; then
|
||||
echo "error deleting object tagging: $error"
|
||||
log 2 "error deleting object tagging: $error"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
@@ -43,38 +43,12 @@ delete_object_tagging_rest() {
|
||||
log 2 "'delete_object_tagging' requires bucket, key"
|
||||
return 1
|
||||
fi
|
||||
|
||||
generate_hash_for_payload ""
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
aws_endpoint_url_address=${AWS_ENDPOINT_URL#*//}
|
||||
header=$(echo "$AWS_ENDPOINT_URL" | awk -F: '{print $1}')
|
||||
# shellcheck disable=SC2154
|
||||
canonical_request="DELETE
|
||||
/$1/$2
|
||||
tagging=
|
||||
host:$aws_endpoint_url_address
|
||||
x-amz-content-sha256:$payload_hash
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
$payload_hash"
|
||||
|
||||
if ! generate_sts_string "$current_date_time" "$canonical_request"; then
|
||||
log 2 "error generating sts string"
|
||||
if ! result=$(BUCKET_NAME="$1" OBJECT_KEY="$2" OUTPUT_FILE="$TEST_FILE_FOLDER/response.txt" ./tests/rest_scripts/delete_object_tagging.sh); then
|
||||
log 2 "error sending delete object tagging REST command: $result"
|
||||
return 1
|
||||
fi
|
||||
get_signature
|
||||
# shellcheck disable=SC2154
|
||||
reply=$(send_command curl -ks -w "%{http_code}" -X DELETE "$header://$aws_endpoint_url_address/$1/$2?tagging" \
|
||||
-H "Authorization: AWS4-HMAC-SHA256 Credential=$AWS_ACCESS_KEY_ID/$ymd/$AWS_REGION/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature" \
|
||||
-H "x-amz-content-sha256: $payload_hash" \
|
||||
-H "x-amz-date: $current_date_time" \
|
||||
-d "$tagging" -o "$TEST_FILE_FOLDER"/delete_tagging_error.txt 2>&1)
|
||||
log 5 "reply status code: $reply"
|
||||
if [[ "$reply" != "204" ]]; then
|
||||
log 2 "reply error: $reply"
|
||||
log 2 "put object tagging command returned error: $(cat "$TEST_FILE_FOLDER"/delete_tagging_error.txt)"
|
||||
if [ "$result" != "204" ]; then
|
||||
log 2 "delete-object-tagging returned code $result (response: $(cat "$TEST_FILE_FOLDER/response.txt"))"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
|
||||
@@ -21,7 +21,7 @@ get_bucket_acl() {
|
||||
return 1
|
||||
fi
|
||||
local exit_code=0
|
||||
if [[ $1 == 'aws' ]] || [[ $1 == 's3api' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
acl=$(send_command aws --no-verify-ssl s3api get-bucket-acl --bucket "$2" 2>&1) || exit_code="$?"
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
acl=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate info "s3://$2" 2>&1) || exit_code="$?"
|
||||
|
||||
@@ -17,17 +17,18 @@
|
||||
get_bucket_location() {
|
||||
record_command "get-bucket-location" "client:$1"
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "get bucket location command requires command type, bucket name"
|
||||
log 2 "get bucket location command requires command type, bucket name"
|
||||
return 1
|
||||
fi
|
||||
if [[ $1 == 'aws' ]]; then
|
||||
get_result=0
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
get_bucket_location_aws "$2" || get_result=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
get_bucket_location_s3cmd "$2" || get_result=$?
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
get_bucket_location_mc "$2" || get_result=$?
|
||||
else
|
||||
echo "command type '$1' not implemented for get_bucket_location"
|
||||
log 2 "command type '$1' not implemented for get_bucket_location"
|
||||
return 1
|
||||
fi
|
||||
if [[ $get_result -ne 0 ]]; then
|
||||
@@ -39,7 +40,7 @@ get_bucket_location() {
|
||||
get_bucket_location_aws() {
|
||||
record_command "get-bucket-location" "client:s3api"
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "get bucket location (aws) requires bucket name"
|
||||
log 2 "get bucket location (aws) requires bucket name"
|
||||
return 1
|
||||
fi
|
||||
location_json=$(send_command aws --no-verify-ssl s3api get-bucket-location --bucket "$1") || location_result=$?
|
||||
@@ -59,7 +60,7 @@ get_bucket_location_s3cmd() {
|
||||
fi
|
||||
info=$(send_command s3cmd --no-check-certificate info "s3://$1") || results=$?
|
||||
if [[ $results -ne 0 ]]; then
|
||||
echo "error getting s3cmd info: $info"
|
||||
log 2 "error getting bucket location: $location"
|
||||
return 1
|
||||
fi
|
||||
bucket_location=$(echo "$info" | grep -o 'Location:.*' | awk '{print $2}')
|
||||
@@ -69,12 +70,12 @@ get_bucket_location_s3cmd() {
|
||||
get_bucket_location_mc() {
|
||||
record_command "get-bucket-location" "client:mc"
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "get bucket location (mc) requires bucket name"
|
||||
log 2 "get bucket location (mc) requires bucket name"
|
||||
return 1
|
||||
fi
|
||||
info=$(send_command mc --insecure stat "$MC_ALIAS/$1") || results=$?
|
||||
if [[ $results -ne 0 ]]; then
|
||||
echo "error getting s3cmd info: $info"
|
||||
log 2 "error getting s3cmd info: $info"
|
||||
return 1
|
||||
fi
|
||||
# shellcheck disable=SC2034
|
||||
|
||||
@@ -21,7 +21,7 @@ get_bucket_policy() {
|
||||
return 1
|
||||
fi
|
||||
local get_bucket_policy_result=0
|
||||
if [[ $1 == 'aws' ]] || [[ $1 == 's3api' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
get_bucket_policy_aws "$2" || get_bucket_policy_result=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
get_bucket_policy_s3cmd "$2" || get_bucket_policy_result=$?
|
||||
@@ -97,41 +97,57 @@ get_bucket_policy_s3cmd() {
|
||||
policy_brackets=false
|
||||
# NOTE: versitygw sends policies back in multiple lines here, direct in single line
|
||||
while IFS= read -r line; do
|
||||
if [[ $policy_brackets == false ]]; then
|
||||
policy_line=$(echo "$line" | grep 'Policy: ')
|
||||
if [[ $policy_line != "" ]]; then
|
||||
if [[ $policy_line != *'{'* ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ $policy_line == *'}'* ]]; then
|
||||
log 5 "policy on single line"
|
||||
bucket_policy=${policy_line//Policy:/}
|
||||
break
|
||||
else
|
||||
policy_brackets=true
|
||||
bucket_policy+="{"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
bucket_policy+=$line
|
||||
if [[ $line == "" ]]; then
|
||||
break
|
||||
fi
|
||||
if check_and_load_policy_info; then
|
||||
break
|
||||
fi
|
||||
done <<< "$info"
|
||||
log 5 "bucket policy: $bucket_policy"
|
||||
return 0
|
||||
}
|
||||
|
||||
# return 0 for no policy, single-line policy, or loading complete, 1 for still searching or loading
|
||||
check_and_load_policy_info() {
|
||||
if [[ $policy_brackets == false ]]; then
|
||||
if search_for_first_policy_line_or_full_policy; then
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
bucket_policy+=$line
|
||||
if [[ $line == "}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# return 0 for empty or single-line policy, 1 for other cases
|
||||
search_for_first_policy_line_or_full_policy() {
|
||||
policy_line=$(echo "$line" | grep 'Policy: ')
|
||||
if [[ $policy_line != "" ]]; then
|
||||
if [[ $policy_line != *'{'* ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ $policy_line == *'}'* ]]; then
|
||||
log 5 "policy on single line"
|
||||
bucket_policy=${policy_line//Policy:/}
|
||||
return 0
|
||||
else
|
||||
policy_brackets=true
|
||||
bucket_policy+="{"
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
get_bucket_policy_mc() {
|
||||
record_command "get-bucket-policy" "client:mc"
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "aws 'get bucket policy' command requires bucket"
|
||||
log 2 "aws 'get bucket policy' command requires bucket"
|
||||
return 1
|
||||
fi
|
||||
bucket_policy=$(send_command mc --insecure anonymous get-json "$MC_ALIAS/$1") || get_result=$?
|
||||
if [[ $get_result -ne 0 ]]; then
|
||||
echo "error getting policy: $bucket_policy"
|
||||
log 2 "error getting policy: $bucket_policy"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
|
||||
@@ -21,7 +21,7 @@ get_bucket_tagging() {
|
||||
assert [ $# -eq 2 ]
|
||||
record_command "get-bucket-tagging" "client:$1"
|
||||
local result
|
||||
if [[ $1 == 'aws' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
tags=$(send_command aws --no-verify-ssl s3api get-bucket-tagging --bucket "$2" 2>&1) || result=$?
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
tags=$(send_command mc --insecure tag list "$MC_ALIAS"/"$2" 2>&1) || result=$?
|
||||
@@ -35,7 +35,7 @@ get_bucket_tagging() {
|
||||
export tags=
|
||||
return 0
|
||||
fi
|
||||
echo "error getting bucket tags: $tags"
|
||||
log 2 "error getting bucket tags: $tags"
|
||||
return 1
|
||||
fi
|
||||
export tags
|
||||
|
||||
@@ -24,7 +24,7 @@ get_object() {
|
||||
local exit_code=0
|
||||
if [[ $1 == 's3' ]]; then
|
||||
get_object_error=$(send_command aws --no-verify-ssl s3 mv "s3://$2/$3" "$4" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3api' ]] || [[ $1 == 'aws' ]]; then
|
||||
elif [[ $1 == 's3api' ]]; then
|
||||
get_object_error=$(send_command aws --no-verify-ssl s3api get-object --bucket "$2" --key "$3" "$4" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
get_object_error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate get "s3://$2/$3" "$4" 2>&1) || exit_code=$?
|
||||
|
||||
@@ -21,7 +21,7 @@ get_object_tagging() {
|
||||
return 1
|
||||
fi
|
||||
local result
|
||||
if [[ "$1" == 'aws' ]] || [[ $1 == 's3api' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
tags=$(send_command aws --no-verify-ssl s3api get-object-tagging --bucket "$2" --key "$3" 2>&1) || result=$?
|
||||
elif [[ "$1" == 'mc' ]]; then
|
||||
tags=$(send_command mc --insecure tag list "$MC_ALIAS"/"$2"/"$3" 2>&1) || result=$?
|
||||
|
||||
@@ -29,21 +29,22 @@ head_bucket() {
|
||||
return 1
|
||||
fi
|
||||
local exit_code=0
|
||||
if [[ $1 == "aws" ]] || [[ $1 == 's3api' ]] || [[ $1 == 's3' ]]; then
|
||||
if [[ $1 == 's3api' ]] || [[ $1 == 's3' ]]; then
|
||||
bucket_info=$(send_command aws --no-verify-ssl s3api head-bucket --bucket "$2" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == "s3cmd" ]]; then
|
||||
bucket_info=$(send_command s3cmd --no-check-certificate info "s3://$2" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
bucket_info=$(send_command mc --insecure stat "$MC_ALIAS"/"$2" 2>&1) || exit_code=$?
|
||||
else
|
||||
fail "invalid command type $1"
|
||||
log 2 "invalid command type $1"
|
||||
fi
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
log 2 "error getting bucket info: $bucket_info"
|
||||
if [[ "$bucket_info" == *"404"* ]] || [[ "$bucket_info" == *"does not exist"* ]]; then
|
||||
return 1
|
||||
fi
|
||||
log 2 "error getting bucket info: $bucket_info"
|
||||
return 2
|
||||
fi
|
||||
echo "$bucket_info"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ head_object() {
|
||||
return 2
|
||||
fi
|
||||
local exit_code=0
|
||||
if [[ $1 == 'aws' ]] || [[ $1 == 's3api' ]] || [[ $1 == 's3' ]]; then
|
||||
if [[ $1 == 's3api' ]] || [[ $1 == 's3' ]]; then
|
||||
metadata=$(send_command aws --no-verify-ssl s3api head-object --bucket "$2" --key "$3" 2>&1) || exit_code="$?"
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
metadata=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate info s3://"$2/$3" 2>&1) || exit_code="$?"
|
||||
|
||||
@@ -18,14 +18,14 @@ list_buckets() {
|
||||
log 6 "list_buckets"
|
||||
record_command "list-buckets" "client:$1"
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "list buckets command missing command type"
|
||||
log 2 "list buckets command missing command type"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local exit_code=0
|
||||
if [[ $1 == 's3' ]]; then
|
||||
buckets=$(send_command aws --no-verify-ssl s3 ls 2>&1 s3://) || exit_code=$?
|
||||
elif [[ $1 == 's3api' ]] || [[ $1 == 'aws' ]]; then
|
||||
elif [[ $1 == 's3api' ]]; then
|
||||
list_buckets_s3api "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" || exit_code=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
buckets=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate ls s3:// 2>&1) || exit_code=$?
|
||||
@@ -34,11 +34,11 @@ list_buckets() {
|
||||
elif [[ $1 == 'rest' ]]; then
|
||||
list_buckets_rest || exit_code=$?
|
||||
else
|
||||
echo "list buckets command not implemented for '$1'"
|
||||
log 2 "list buckets command not implemented for '$1'"
|
||||
return 1
|
||||
fi
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo "error listing buckets: $buckets"
|
||||
log 2 "error listing buckets: $buckets"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -57,7 +57,7 @@ list_buckets() {
|
||||
list_buckets_with_user() {
|
||||
record_command "list-buckets" "client:$1"
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "'list buckets as user' command missing command type, username, password"
|
||||
log 2 "'list buckets as user' command missing command type, username, password"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -71,11 +71,11 @@ list_buckets_with_user() {
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
buckets=$(send_command mc --insecure ls "$MC_ALIAS" 2>&1) || exit_code=$?
|
||||
else
|
||||
echo "list buckets command not implemented for '$1'"
|
||||
log 2 "list buckets command not implemented for '$1'"
|
||||
return 1
|
||||
fi
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo "error listing buckets: $buckets"
|
||||
log 2 "error listing buckets: $buckets"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -97,7 +97,7 @@ list_buckets_s3api() {
|
||||
return 1
|
||||
fi
|
||||
if ! output=$(AWS_ACCESS_KEY_ID="$1" AWS_SECRET_ACCESS_KEY="$2" send_command aws --no-verify-ssl s3api list-buckets 2>&1); then
|
||||
echo "error listing buckets: $output"
|
||||
log 2 "error listing buckets: $output"
|
||||
return 1
|
||||
fi
|
||||
log 5 "bucket data: $output"
|
||||
|
||||
@@ -34,30 +34,10 @@ list_object_versions_rest() {
|
||||
log 2 "'list_object_versions_rest' requires bucket name"
|
||||
return 1
|
||||
fi
|
||||
generate_hash_for_payload ""
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
# shellcheck disable=SC2154
|
||||
canonical_request="GET
|
||||
/$1
|
||||
versions=
|
||||
host:${AWS_ENDPOINT_URL#*//}
|
||||
x-amz-content-sha256:$payload_hash
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
$payload_hash"
|
||||
|
||||
if ! generate_sts_string "$current_date_time" "$canonical_request"; then
|
||||
log 2 "error generating sts string"
|
||||
log 5 "list object versions REST"
|
||||
if ! result=$(BUCKET_NAME="$1" OUTPUT_FILE="$TEST_FILE_FOLDER/object_versions.txt" ./tests/rest_scripts/list_object_versions.sh); then
|
||||
log 2 "error listing object versions: $result"
|
||||
return 1
|
||||
fi
|
||||
|
||||
get_signature
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
reply=$(send_command curl -ks "$AWS_ENDPOINT_URL/$1?versions" \
|
||||
-H "Authorization: AWS4-HMAC-SHA256 Credential=$AWS_ACCESS_KEY_ID/$ymd/$AWS_REGION/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature" \
|
||||
-H "x-amz-content-sha256: $payload_hash" \
|
||||
-H "x-amz-date: $current_date_time" \
|
||||
-o "$TEST_FILE_FOLDER/object_versions.txt" 2>&1)
|
||||
return 0
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ./tests/util_list_objects.sh
|
||||
source ./tests/util/util_list_objects.sh
|
||||
source ./tests/commands/command.sh
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
@@ -29,7 +29,7 @@ list_objects() {
|
||||
|
||||
local output
|
||||
local result=0
|
||||
if [[ $1 == "aws" ]] || [[ $1 == 's3' ]]; then
|
||||
if [[ $1 == 's3' ]]; then
|
||||
output=$(send_command aws --no-verify-ssl s3 ls s3://"$2" 2>&1) || result=$?
|
||||
elif [[ $1 == 's3api' ]]; then
|
||||
list_objects_s3api "$2" || result=$?
|
||||
@@ -91,7 +91,7 @@ list_objects_s3api() {
|
||||
# export objects on success, return 1 for failure
|
||||
list_objects_s3api_v1() {
|
||||
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
|
||||
echo "list objects command requires bucket, (optional) delimiter"
|
||||
log 2 "list objects command requires bucket, (optional) delimiter"
|
||||
return 1
|
||||
fi
|
||||
if [ "$2" == "" ]; then
|
||||
@@ -100,7 +100,7 @@ list_objects_s3api_v1() {
|
||||
objects=$(send_command aws --no-verify-ssl s3api list-objects --bucket "$1" --delimiter "$2") || local result=$?
|
||||
fi
|
||||
if [[ $result -ne 0 ]]; then
|
||||
echo "error listing objects: $objects"
|
||||
log 2 "error listing objects: $objects"
|
||||
return 1
|
||||
fi
|
||||
export objects
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
# export objects on success, return 1 for failure
|
||||
list_objects_v2() {
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "list objects command missing bucket and/or path"
|
||||
log 2 "list objects command missing bucket and/or path"
|
||||
return 1
|
||||
fi
|
||||
record_command "list-objects-v2 client:s3api"
|
||||
objects=$(send_command aws --no-verify-ssl s3api list-objects-v2 --bucket "$1") || local result=$?
|
||||
if [[ $result -ne 0 ]]; then
|
||||
echo "error listing objects: $objects"
|
||||
log 2 "error listing objects: $objects"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/util_file.sh
|
||||
source ./tests/util/util_file.sh
|
||||
source ./tests/commands/command.sh
|
||||
|
||||
put_bucket_acl_s3api() {
|
||||
@@ -58,22 +58,15 @@ reset_bucket_acl() {
|
||||
return 1
|
||||
fi
|
||||
# shellcheck disable=SC2154
|
||||
cat <<EOF > "$TEST_FILE_FOLDER/$acl_file"
|
||||
{
|
||||
"Grants": [
|
||||
{
|
||||
"Grantee": {
|
||||
"ID": "$AWS_ACCESS_KEY_ID",
|
||||
"Type": "CanonicalUser"
|
||||
},
|
||||
"Permission": "FULL_CONTROL"
|
||||
}
|
||||
],
|
||||
"Owner": {
|
||||
"ID": "$AWS_ACCESS_KEY_ID"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
if ! setup_acl_json "$TEST_FILE_FOLDER/$acl_file" "CanonicalUser" "$AWS_ACCESS_KEY_ID" "FULL_CONTROL" "$AWS_ACCESS_KEY_ID"; then
|
||||
log 2 "error resetting versitygw ACL"
|
||||
return 1
|
||||
fi
|
||||
elif ! setup_acl_json "$TEST_FILE_FOLDER/$acl_file" "CanonicalUser" "$AWS_CANONICAL_ID" "FULL_CONTROL" "$AWS_CANONICAL_ID"; then
|
||||
log 2 "error resetting direct ACL"
|
||||
return 1
|
||||
fi
|
||||
if ! put_bucket_acl_s3api "$BUCKET_ONE_NAME" "$TEST_FILE_FOLDER/$acl_file"; then
|
||||
log 2 "error putting bucket acl (s3api)"
|
||||
return 1
|
||||
|
||||
@@ -21,7 +21,7 @@ put_bucket_policy() {
|
||||
return 1
|
||||
fi
|
||||
local put_policy_result=0
|
||||
if [[ $1 == 'aws' ]] || [[ $1 == 's3api' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
policy=$(send_command aws --no-verify-ssl s3api put-bucket-policy --bucket "$2" --policy "file://$3" 2>&1) || put_policy_result=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
policy=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate setpolicy "$3" "s3://$2" 2>&1) || put_policy_result=$?
|
||||
|
||||
@@ -23,7 +23,7 @@ put_bucket_tagging() {
|
||||
local error
|
||||
local result=0
|
||||
record_command "put-bucket-tagging" "client:$1"
|
||||
if [[ $1 == 'aws' ]] || [[ $1 == 's3api' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3api put-bucket-tagging --bucket "$2" --tagging "TagSet=[{Key=$3,Value=$4}]") || result=$?
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
error=$(send_command mc --insecure tag set "$MC_ALIAS"/"$2" "$3=$4" 2>&1) || result=$?
|
||||
|
||||
@@ -27,7 +27,7 @@ put_object() {
|
||||
local error
|
||||
if [[ $1 == 's3' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3 mv "$2" s3://"$3/$4" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3api' ]] || [[ $1 == 'aws' ]]; then
|
||||
elif [[ $1 == 's3api' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3api put-object --body "$2" --bucket "$3" --key "$4" 2>&1) || exit_code=$?
|
||||
elif [[ $1 == 's3cmd' ]]; then
|
||||
error=$(send_command s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate put "$2" s3://"$3/$4" 2>&1) || exit_code=$?
|
||||
|
||||
@@ -22,7 +22,7 @@ put_object_tagging() {
|
||||
local error
|
||||
local result
|
||||
record_command "put-object-tagging" "client:$1"
|
||||
if [[ $1 == 'aws' ]]; then
|
||||
if [[ $1 == 's3api' ]]; then
|
||||
error=$(send_command aws --no-verify-ssl s3api put-object-tagging --bucket "$2" --key "$3" --tagging "TagSet=[{Key=$4,Value=$5}]" 2>&1) || result=$?
|
||||
elif [[ $1 == 'mc' ]]; then
|
||||
error=$(send_command mc --insecure tag set "$MC_ALIAS"/"$2"/"$3" "$4=$5" 2>&1) || result=$?
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
upload_part_copy() {
|
||||
record_command "upload-part-copy" "client:s3api"
|
||||
if [ $# -ne 5 ]; then
|
||||
echo "upload multipart part copy function must have bucket, key, upload ID, file name, part number"
|
||||
log 2 "upload multipart part copy function must have bucket, key, upload ID, file name, part number"
|
||||
return 1
|
||||
fi
|
||||
local etag_json
|
||||
echo "$1 $2 $3 $4 $5"
|
||||
log 5 "parameters: $1 $2 $3 $4 $5"
|
||||
etag_json=$(send_command aws --no-verify-ssl s3api upload-part-copy --bucket "$1" --key "$2" --upload-id "$3" --part-number "$5" --copy-source "$1/$4-$(($5-1))") || local uploaded=$?
|
||||
if [[ $uploaded -ne 0 ]]; then
|
||||
echo "Error uploading part $5: $etag_json"
|
||||
log 2 "Error uploading part $5: $etag_json"
|
||||
return 1
|
||||
fi
|
||||
etag=$(echo "$etag_json" | jq '.CopyPartResult.ETag')
|
||||
|
||||
42
tests/env.sh
42
tests/env.sh
@@ -63,13 +63,7 @@ check_universal_vars() {
|
||||
source_config_file
|
||||
fi
|
||||
if [ -n "$COMMAND_LOG" ]; then
|
||||
if [ -e "$COMMAND_LOG" ]; then
|
||||
if ! error=$(rm "$COMMAND_LOG"); then
|
||||
log 3 "error removing command log: $error"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
echo "******** $(date +"%Y-%m-%d %H:%M:%S") $BATS_TEST_NAME COMMANDS ********" >> "$COMMAND_LOG"
|
||||
init_command_log
|
||||
fi
|
||||
|
||||
if [ "$GITHUB_ACTIONS" != "true" ] && [ -r "$SECRETS_FILE" ]; then
|
||||
@@ -80,6 +74,10 @@ check_universal_vars() {
|
||||
fi
|
||||
|
||||
if [[ -n "$LOG_LEVEL" ]]; then
|
||||
if [[ $LOG_LEVEL -lt 2 ]]; then
|
||||
log 1 "log level must be 2 or greater"
|
||||
exit 1
|
||||
fi
|
||||
export LOG_LEVEL_INT=$LOG_LEVEL
|
||||
fi
|
||||
|
||||
@@ -125,18 +123,46 @@ check_universal_vars() {
|
||||
log 1 "RECREATE_BUCKETS must be 'true' or 'false'"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$RECREATE_BUCKETS" != "true" ] && [ "$RECREATE_BUCKETS" != "false" ]; then
|
||||
log 1 "RECREATE_BUCKETS must be 'true' or 'false'"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$RECREATE_BUCKETS" == "false" ] && [ "$DELETE_BUCKETS_AFTER_TEST" == "true" ]; then
|
||||
log 1 "cannot set DELETE_BUCKETS_AFTER_TEST to 'true' if RECREATE_BUCKETS is 'false'"
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$TEST_FILE_FOLDER" ]; then
|
||||
log 1 "TEST_FILE_FOLDER missing"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$TEST_FILE_FOLDER" ]; then
|
||||
if ! error=$(mkdir -p "$TEST_FILE_FOLDER"); then
|
||||
if ! error=$(mkdir -p "$TEST_FILE_FOLDER" 2>&1); then
|
||||
log 2 "error creating test folder: $error"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
# exporting these since they're needed for subshells
|
||||
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION AWS_PROFILE AWS_ENDPOINT_URL
|
||||
if [ -n "$AWS_CANONICAL_ID" ]; then
|
||||
log 5 "canonical ID: $AWS_CANONICAL_ID"
|
||||
export AWS_CANONICAL_ID
|
||||
fi
|
||||
}
|
||||
|
||||
delete_command_log() {
|
||||
if [ -e "$COMMAND_LOG" ]; then
|
||||
if ! error=$(rm "$COMMAND_LOG"); then
|
||||
log 2 "error removing command log: $error"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
init_command_log() {
|
||||
if ! delete_command_log; then
|
||||
exit 1
|
||||
fi
|
||||
echo "******** $(date +"%Y-%m-%d %H:%M:%S") $BATS_TEST_NAME COMMANDS ********" >> "$COMMAND_LOG"
|
||||
}
|
||||
|
||||
check_versity_vars() {
|
||||
|
||||
@@ -86,6 +86,7 @@ func TestListBuckets(s *S3Conf) {
|
||||
ListBuckets_invalid_max_buckets(s)
|
||||
ListBuckets_truncated(s)
|
||||
ListBuckets_success(s)
|
||||
ListBuckets_empty_success(s)
|
||||
}
|
||||
|
||||
func TestDeleteBucket(s *S3Conf) {
|
||||
@@ -152,6 +153,7 @@ func TestHeadObject(s *S3Conf) {
|
||||
HeadObject_directory_object_noslash(s)
|
||||
HeadObject_non_existing_dir_object(s)
|
||||
HeadObject_with_contenttype(s)
|
||||
HeadObject_invalid_parent_dir(s)
|
||||
HeadObject_success(s)
|
||||
}
|
||||
|
||||
@@ -159,6 +161,7 @@ func TestGetObjectAttributes(s *S3Conf) {
|
||||
GetObjectAttributes_non_existing_bucket(s)
|
||||
GetObjectAttributes_non_existing_object(s)
|
||||
GetObjectAttributes_invalid_attrs(s)
|
||||
GetObjectAttributes_invalid_parent(s)
|
||||
GetObjectAttributes_empty_attrs(s)
|
||||
GetObjectAttributes_existing_object(s)
|
||||
}
|
||||
@@ -167,7 +170,9 @@ func TestGetObject(s *S3Conf) {
|
||||
GetObject_non_existing_key(s)
|
||||
GetObject_directory_object_noslash(s)
|
||||
GetObject_invalid_ranges(s)
|
||||
GetObject_invalid_parent(s)
|
||||
GetObject_with_meta(s)
|
||||
GetObject_large_object(s)
|
||||
GetObject_success(s)
|
||||
GetObject_directory_success(s)
|
||||
GetObject_by_range_success(s)
|
||||
@@ -198,6 +203,7 @@ func TestListObjectsV2(s *S3Conf) {
|
||||
ListObjectsV2_truncated_common_prefixes(s)
|
||||
ListObjectsV2_all_objs_max_keys(s)
|
||||
ListObjectsV2_list_all_objs(s)
|
||||
ListObjectsV2_invalid_parent_prefix(s)
|
||||
}
|
||||
|
||||
// VD stands for Versioning Disabled
|
||||
@@ -239,6 +245,7 @@ func TestPutObjectTagging(s *S3Conf) {
|
||||
func TestGetObjectTagging(s *S3Conf) {
|
||||
GetObjectTagging_non_existing_object(s)
|
||||
GetObjectTagging_unset_tags(s)
|
||||
GetObjectTagging_invalid_parent(s)
|
||||
GetObjectTagging_success(s)
|
||||
}
|
||||
|
||||
@@ -314,6 +321,7 @@ func TestCompleteMultipartUpload(s *S3Conf) {
|
||||
CompletedMultipartUpload_non_existing_bucket(s)
|
||||
CompleteMultipartUpload_invalid_part_number(s)
|
||||
CompleteMultipartUpload_invalid_ETag(s)
|
||||
CompleteMultipartUpload_empty_parts(s)
|
||||
CompleteMultipartUpload_success(s)
|
||||
if !s.azureTests {
|
||||
CompleteMultipartUpload_racey_success(s)
|
||||
@@ -329,6 +337,10 @@ func TestPutBucketAcl(s *S3Conf) {
|
||||
PutBucketAcl_invalid_acl_acp_and_grants(s)
|
||||
PutBucketAcl_invalid_owner(s)
|
||||
PutBucketAcl_invalid_owner_not_in_body(s)
|
||||
PutBucketAcl_invalid_empty_owner_id_in_body(s)
|
||||
PutBucketAcl_invalid_permission_in_body(s)
|
||||
PutBucketAcl_invalid_grantee_type_in_body(s)
|
||||
PutBucketAcl_empty_grantee_ID_in_body(s)
|
||||
PutBucketAcl_success_access_denied(s)
|
||||
PutBucketAcl_success_grants(s)
|
||||
PutBucketAcl_success_canned_acl(s)
|
||||
@@ -441,8 +453,8 @@ func TestGetObjectLegalHold(s *S3Conf) {
|
||||
func TestWORMProtection(s *S3Conf) {
|
||||
WORMProtection_bucket_object_lock_configuration_compliance_mode(s)
|
||||
WORMProtection_bucket_object_lock_configuration_governance_mode(s)
|
||||
// WORMProtection_bucket_object_lock_governance_bypass_delete(s)
|
||||
// WORMProtection_bucket_object_lock_governance_bypass_delete_multiple
|
||||
WORMProtection_bucket_object_lock_governance_bypass_delete(s)
|
||||
WORMProtection_bucket_object_lock_governance_bypass_delete_multiple(s)
|
||||
WORMProtection_object_lock_retention_compliance_locked(s)
|
||||
WORMProtection_object_lock_retention_governance_locked(s)
|
||||
WORMProtection_object_lock_retention_governance_bypass_overwrite(s)
|
||||
@@ -515,6 +527,7 @@ func TestPosix(s *S3Conf) {
|
||||
PutObject_name_too_long(s)
|
||||
HeadObject_name_too_long(s)
|
||||
DeleteObject_name_too_long(s)
|
||||
DeleteObject_directory_not_empty(s)
|
||||
// posix specific versioning tests
|
||||
if !s.versioningEnabled {
|
||||
TestVersioningDisabled(s)
|
||||
@@ -527,6 +540,7 @@ func TestIAM(s *S3Conf) {
|
||||
IAM_userplus_CreateBucket(s)
|
||||
IAM_admin_ChangeBucketOwner(s)
|
||||
IAM_ChangeBucketOwner_back_to_root(s)
|
||||
IAM_ListBuckets(s)
|
||||
}
|
||||
|
||||
func TestAccessControl(s *S3Conf) {
|
||||
@@ -567,6 +581,7 @@ func TestVersioning(s *S3Conf) {
|
||||
Versioning_CopyObject_special_chars(s)
|
||||
// HeadObject action
|
||||
Versioning_HeadObject_invalid_versionId(s)
|
||||
Versioning_HeadObject_invalid_parent(s)
|
||||
Versioning_HeadObject_success(s)
|
||||
Versioning_HeadObject_without_versionId(s)
|
||||
Versioning_HeadObject_delete_marker(s)
|
||||
@@ -690,6 +705,7 @@ func GetIntTests() IntTests {
|
||||
"ListBuckets_invalid_max_buckets": ListBuckets_invalid_max_buckets,
|
||||
"ListBuckets_truncated": ListBuckets_truncated,
|
||||
"ListBuckets_success": ListBuckets_success,
|
||||
"ListBuckets_empty_success": ListBuckets_empty_success,
|
||||
"DeleteBucket_non_existing_bucket": DeleteBucket_non_existing_bucket,
|
||||
"DeleteBucket_non_empty_bucket": DeleteBucket_non_empty_bucket,
|
||||
"DeleteBucket_success_status_code": DeleteBucket_success_status_code,
|
||||
@@ -725,16 +741,20 @@ func GetIntTests() IntTests {
|
||||
"HeadObject_non_existing_dir_object": HeadObject_non_existing_dir_object,
|
||||
"HeadObject_name_too_long": HeadObject_name_too_long,
|
||||
"HeadObject_with_contenttype": HeadObject_with_contenttype,
|
||||
"HeadObject_invalid_parent_dir": HeadObject_invalid_parent_dir,
|
||||
"HeadObject_success": HeadObject_success,
|
||||
"GetObjectAttributes_non_existing_bucket": GetObjectAttributes_non_existing_bucket,
|
||||
"GetObjectAttributes_non_existing_object": GetObjectAttributes_non_existing_object,
|
||||
"GetObjectAttributes_invalid_attrs": GetObjectAttributes_invalid_attrs,
|
||||
"GetObjectAttributes_invalid_parent": GetObjectAttributes_invalid_parent,
|
||||
"GetObjectAttributes_empty_attrs": GetObjectAttributes_empty_attrs,
|
||||
"GetObjectAttributes_existing_object": GetObjectAttributes_existing_object,
|
||||
"GetObject_non_existing_key": GetObject_non_existing_key,
|
||||
"GetObject_directory_object_noslash": GetObject_directory_object_noslash,
|
||||
"GetObject_invalid_ranges": GetObject_invalid_ranges,
|
||||
"GetObject_invalid_parent": GetObject_invalid_parent,
|
||||
"GetObject_with_meta": GetObject_with_meta,
|
||||
"GetObject_large_object": GetObject_large_object,
|
||||
"GetObject_success": GetObject_success,
|
||||
"GetObject_directory_success": GetObject_directory_success,
|
||||
"GetObject_by_range_success": GetObject_by_range_success,
|
||||
@@ -759,9 +779,11 @@ func GetIntTests() IntTests {
|
||||
"ListObjectsV2_truncated_common_prefixes": ListObjectsV2_truncated_common_prefixes,
|
||||
"ListObjectsV2_all_objs_max_keys": ListObjectsV2_all_objs_max_keys,
|
||||
"ListObjectsV2_list_all_objs": ListObjectsV2_list_all_objs,
|
||||
"ListObjectsV2_invalid_parent_prefix": ListObjectsV2_invalid_parent_prefix,
|
||||
"ListObjectVersions_VD_success": ListObjectVersions_VD_success,
|
||||
"DeleteObject_non_existing_object": DeleteObject_non_existing_object,
|
||||
"DeleteObject_directory_object_noslash": DeleteObject_directory_object_noslash,
|
||||
"DeleteObject_directory_not_empty": DeleteObject_directory_not_empty,
|
||||
"DeleteObject_name_too_long": DeleteObject_name_too_long,
|
||||
"DeleteObject_non_existing_dir_object": DeleteObject_non_existing_dir_object,
|
||||
"DeleteObject_success": DeleteObject_success,
|
||||
@@ -782,6 +804,7 @@ func GetIntTests() IntTests {
|
||||
"PutObjectTagging_success": PutObjectTagging_success,
|
||||
"GetObjectTagging_non_existing_object": GetObjectTagging_non_existing_object,
|
||||
"GetObjectTagging_unset_tags": GetObjectTagging_unset_tags,
|
||||
"GetObjectTagging_invalid_parent": GetObjectTagging_invalid_parent,
|
||||
"GetObjectTagging_success": GetObjectTagging_success,
|
||||
"DeleteObjectTagging_non_existing_object": DeleteObjectTagging_non_existing_object,
|
||||
"DeleteObjectTagging_success_status": DeleteObjectTagging_success_status,
|
||||
@@ -833,6 +856,7 @@ func GetIntTests() IntTests {
|
||||
"CompletedMultipartUpload_non_existing_bucket": CompletedMultipartUpload_non_existing_bucket,
|
||||
"CompleteMultipartUpload_invalid_part_number": CompleteMultipartUpload_invalid_part_number,
|
||||
"CompleteMultipartUpload_invalid_ETag": CompleteMultipartUpload_invalid_ETag,
|
||||
"CompleteMultipartUpload_empty_parts": CompleteMultipartUpload_empty_parts,
|
||||
"CompleteMultipartUpload_success": CompleteMultipartUpload_success,
|
||||
"CompleteMultipartUpload_racey_success": CompleteMultipartUpload_racey_success,
|
||||
"PutBucketAcl_non_existing_bucket": PutBucketAcl_non_existing_bucket,
|
||||
@@ -843,6 +867,10 @@ func GetIntTests() IntTests {
|
||||
"PutBucketAcl_invalid_acl_acp_and_grants": PutBucketAcl_invalid_acl_acp_and_grants,
|
||||
"PutBucketAcl_invalid_owner": PutBucketAcl_invalid_owner,
|
||||
"PutBucketAcl_invalid_owner_not_in_body": PutBucketAcl_invalid_owner_not_in_body,
|
||||
"PutBucketAcl_invalid_empty_owner_id_in_body": PutBucketAcl_invalid_empty_owner_id_in_body,
|
||||
"PutBucketAcl_invalid_permission_in_body": PutBucketAcl_invalid_permission_in_body,
|
||||
"PutBucketAcl_invalid_grantee_type_in_body": PutBucketAcl_invalid_grantee_type_in_body,
|
||||
"PutBucketAcl_empty_grantee_ID_in_body": PutBucketAcl_empty_grantee_ID_in_body,
|
||||
"PutBucketAcl_success_access_denied": PutBucketAcl_success_access_denied,
|
||||
"PutBucketAcl_success_grants": PutBucketAcl_success_grants,
|
||||
"PutBucketAcl_success_canned_acl": PutBucketAcl_success_canned_acl,
|
||||
@@ -941,6 +969,7 @@ func GetIntTests() IntTests {
|
||||
"IAM_userplus_CreateBucket": IAM_userplus_CreateBucket,
|
||||
"IAM_admin_ChangeBucketOwner": IAM_admin_ChangeBucketOwner,
|
||||
"IAM_ChangeBucketOwner_back_to_root": IAM_ChangeBucketOwner_back_to_root,
|
||||
"IAM_ListBuckets": IAM_ListBuckets,
|
||||
"AccessControl_default_ACL_user_access_denied": AccessControl_default_ACL_user_access_denied,
|
||||
"AccessControl_default_ACL_userplus_access_denied": AccessControl_default_ACL_userplus_access_denied,
|
||||
"AccessControl_default_ACL_admin_successful_access": AccessControl_default_ACL_admin_successful_access,
|
||||
@@ -969,6 +998,7 @@ func GetIntTests() IntTests {
|
||||
"Versioning_CopyObject_from_an_object_version": Versioning_CopyObject_from_an_object_version,
|
||||
"Versioning_CopyObject_special_chars": Versioning_CopyObject_special_chars,
|
||||
"Versioning_HeadObject_invalid_versionId": Versioning_HeadObject_invalid_versionId,
|
||||
"Versioning_HeadObject_invalid_parent": Versioning_HeadObject_invalid_parent,
|
||||
"Versioning_HeadObject_success": Versioning_HeadObject_success,
|
||||
"Versioning_HeadObject_without_versionId": Versioning_HeadObject_without_versionId,
|
||||
"Versioning_HeadObject_delete_marker": Versioning_HeadObject_delete_marker,
|
||||
|
||||
@@ -16,6 +16,7 @@ package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -42,6 +43,8 @@ type S3Conf struct {
|
||||
debug bool
|
||||
versioningEnabled bool
|
||||
azureTests bool
|
||||
tlsStatus bool
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewS3Conf(opts ...Option) *S3Conf {
|
||||
@@ -50,6 +53,20 @@ func NewS3Conf(opts ...Option) *S3Conf {
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
}
|
||||
|
||||
customTransport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: s.tlsStatus,
|
||||
},
|
||||
}
|
||||
|
||||
customHTTPClient := &http.Client{
|
||||
Transport: customTransport,
|
||||
Timeout: shortTimeout,
|
||||
}
|
||||
|
||||
s.httpClient = customHTTPClient
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -88,6 +105,9 @@ func WithVersioningEnabled() Option {
|
||||
func WithAzureMode() Option {
|
||||
return func(s *S3Conf) { s.azureTests = true }
|
||||
}
|
||||
func WithTLSStatus(ts bool) Option {
|
||||
return func(s *S3Conf) { s.tlsStatus = ts }
|
||||
}
|
||||
|
||||
func (c *S3Conf) getCreds() credentials.StaticCredentialsProvider {
|
||||
// TODO support token/IAM
|
||||
@@ -118,6 +138,8 @@ func (c *S3Conf) Config() aws.Config {
|
||||
config.WithRetryMaxAttempts(1),
|
||||
}
|
||||
|
||||
opts = append(opts, config.WithHTTPClient(c.httpClient))
|
||||
|
||||
if c.checksumDisable {
|
||||
opts = append(opts,
|
||||
config.WithAPIOptions([]func(*middleware.Stack) error{v4.SwapComputePayloadSHA256ForUnsignedPayloadMiddleware}))
|
||||
@@ -168,3 +190,11 @@ func (c *S3Conf) DownloadData(w io.WriterAt, bucket, object string) (int64, erro
|
||||
|
||||
return downloader.Download(context.Background(), w, downinfo)
|
||||
}
|
||||
|
||||
func (c *S3Conf) getAdminCommand(args ...string) []string {
|
||||
if c.tlsStatus {
|
||||
return append([]string{"admin", "--allow-insecure"}, args...)
|
||||
}
|
||||
|
||||
return append([]string{"admin"}, args...)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -213,6 +213,21 @@ func actionHandler(s *S3Conf, testName string, handler func(s3client *s3.Client,
|
||||
return handlerErr
|
||||
}
|
||||
|
||||
func actionHandlerNoSetup(s *S3Conf, testName string, handler func(s3client *s3.Client, bucket string) error, _ ...setupOpt) error {
|
||||
runF(testName)
|
||||
client := s3.NewFromConfig(s.Config())
|
||||
handlerErr := handler(client, "")
|
||||
if handlerErr != nil {
|
||||
failF("%v: %v", testName, handlerErr)
|
||||
}
|
||||
|
||||
if handlerErr == nil {
|
||||
passF(testName)
|
||||
}
|
||||
|
||||
return handlerErr
|
||||
}
|
||||
|
||||
type authConfig struct {
|
||||
testName string
|
||||
path string
|
||||
@@ -751,7 +766,7 @@ func createUsers(s *S3Conf, users []user) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := execCommand("admin", "-a", s.awsID, "-s", s.awsSecret, "-er", s.endpoint, "create-user", "-a", usr.access, "-s", usr.secret, "-r", usr.role)
|
||||
out, err := execCommand(s.getAdminCommand("-a", s.awsID, "-s", s.awsSecret, "-er", s.endpoint, "create-user", "-a", usr.access, "-s", usr.secret, "-r", usr.role)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -763,7 +778,7 @@ func createUsers(s *S3Conf, users []user) error {
|
||||
}
|
||||
|
||||
func deleteUser(s *S3Conf, access string) error {
|
||||
out, err := execCommand("admin", "-a", s.awsID, "-s", s.awsSecret, "-er", s.endpoint, "delete-user", "-a", access)
|
||||
out, err := execCommand(s.getAdminCommand("-a", s.awsID, "-s", s.awsSecret, "-er", s.endpoint, "delete-user", "-a", access)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -776,7 +791,7 @@ func deleteUser(s *S3Conf, access string) error {
|
||||
|
||||
func changeBucketsOwner(s *S3Conf, buckets []string, owner string) error {
|
||||
for _, bucket := range buckets {
|
||||
out, err := execCommand("admin", "-a", s.awsID, "-s", s.awsSecret, "-er", s.endpoint, "change-bucket-owner", "-b", bucket, "-o", owner)
|
||||
out, err := execCommand(s.getAdminCommand("-a", s.awsID, "-s", s.awsSecret, "-er", s.endpoint, "change-bucket-owner", "-b", bucket, "-o", owner)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -788,6 +803,18 @@ func changeBucketsOwner(s *S3Conf, buckets []string, owner string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func listBuckets(s *S3Conf) error {
|
||||
out, err := execCommand(s.getAdminCommand("-a", s.awsID, "-s", s.awsSecret, "-er", s.endpoint, "list-buckets")...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(string(out), adminErrorPrefix) {
|
||||
return fmt.Errorf("failed to list buckets, %s", out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
func genRandString(length int) string {
|
||||
@@ -895,9 +922,15 @@ func checkWORMProtection(client *s3.Client, bucket, object string) error {
|
||||
Key: &object,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
||||
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
|
||||
return err
|
||||
}
|
||||
// client sdk regression issue prevents getting full error message,
|
||||
// change back to below once this is fixed:
|
||||
// https://github.com/aws/aws-sdk-go-v2/issues/2921
|
||||
// if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = client.DeleteObject(ctx, &s3.DeleteObjectInput{
|
||||
@@ -905,9 +938,15 @@ func checkWORMProtection(client *s3.Client, bucket, object string) error {
|
||||
Key: &object,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
||||
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
|
||||
return err
|
||||
}
|
||||
// client sdk regression issue prevents getting full error message,
|
||||
// change back to below once this is fixed:
|
||||
// https://github.com/aws/aws-sdk-go-v2/issues/2921
|
||||
// if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
|
||||
@@ -921,9 +960,15 @@ func checkWORMProtection(client *s3.Client, bucket, object string) error {
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
||||
if err := checkSdkApiErr(err, "InvalidRequest"); err != nil {
|
||||
return err
|
||||
}
|
||||
// client sdk regression issue prevents getting full error message,
|
||||
// change back to below once this is fixed:
|
||||
// https://github.com/aws/aws-sdk-go-v2/issues/2921
|
||||
// if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrObjectLocked)); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ log_mask() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_message "$log_level" "${masked_args[*]}"
|
||||
log_message "$log_level" "$masked_data"
|
||||
}
|
||||
|
||||
mask_args() {
|
||||
@@ -63,23 +63,38 @@ mask_args() {
|
||||
echo "'mask_args' requires string"
|
||||
return 1
|
||||
fi
|
||||
IFS=' ' read -r -a array <<< "$1"
|
||||
unmasked_array=()
|
||||
masked_data=""
|
||||
while IFS= read -r line; do
|
||||
unmasked_array+=("$line")
|
||||
done <<< "$1"
|
||||
|
||||
if ! mask_arg_array "${array[@]}"; then
|
||||
echo "error masking arg array"
|
||||
return 1
|
||||
fi
|
||||
# shellcheck disable=SC2068
|
||||
first_line=true
|
||||
for line in "${unmasked_array[@]}"; do
|
||||
if ! mask_arg_array "$line"; then
|
||||
echo "error masking arg array"
|
||||
return 1
|
||||
fi
|
||||
if [ "$first_line" == "true" ]; then
|
||||
masked_data="${masked_args[*]}"
|
||||
first_line="false"
|
||||
else
|
||||
masked_data+=$(printf "\n%s" "${masked_args[*]}")
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
mask_arg_array() {
|
||||
masked_args=() # Initialize an array to hold the masked arguments
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "'mask_arg_array' requires parameters"
|
||||
return 1
|
||||
fi
|
||||
mask_next=false
|
||||
is_access=false
|
||||
for arg in "$@"; do
|
||||
masked_args=() # Initialize an array to hold the masked arguments
|
||||
# shellcheck disable=SC2068
|
||||
for arg in $@; do
|
||||
if ! check_arg_for_mask "$arg"; then
|
||||
echo "error checking arg for mask"
|
||||
return 1
|
||||
@@ -129,8 +144,11 @@ log_message() {
|
||||
return 1
|
||||
fi
|
||||
now="$(date "+%Y-%m-%d %H:%M:%S")"
|
||||
echo "$now $1 $2"
|
||||
if [[ -n "$TEST_LOG_FILE" ]]; then
|
||||
echo "$now $1 $2" >> "$TEST_LOG_FILE"
|
||||
if [[ ( "$1" == "CRIT" ) || ( "$1" == "ERROR" ) ]]; then
|
||||
echo "$now $1 $2" >&2
|
||||
fi
|
||||
if [[ -n "$TEST_LOG_FILE" ]]; then
|
||||
echo "$now $1 $2" >> "$TEST_LOG_FILE.tmp"
|
||||
fi
|
||||
sync
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# under the License.
|
||||
|
||||
source ./tests/setup.sh
|
||||
source ./tests/util.sh
|
||||
source ./tests/util/util_object.sh
|
||||
|
||||
delete_bucket_if_exists() {
|
||||
if [[ $# -ne 2 ]]; then
|
||||
@@ -28,26 +28,23 @@ delete_bucket_if_exists() {
|
||||
return 1
|
||||
fi
|
||||
if [[ $exists_result -eq 1 ]]; then
|
||||
log 5 "bucket '$2' doesn't exist, skipping"
|
||||
echo "bucket '$2' doesn't exist, skipping"
|
||||
return 0
|
||||
fi
|
||||
if ! delete_bucket_recursive "$1" "$2"; then
|
||||
log 2 "error deleting bucket"
|
||||
return 1
|
||||
fi
|
||||
log 5 "bucket '$2' successfully deleted"
|
||||
echo "bucket '$2' successfully deleted"
|
||||
return 0
|
||||
}
|
||||
|
||||
if ! setup; then
|
||||
log 2 "error starting versity to set up static buckets"
|
||||
exit 1
|
||||
fi
|
||||
if ! delete_bucket_if_exists "s3api" "$BUCKET_ONE_NAME"; then
|
||||
base_setup
|
||||
if ! RECREATE_BUCKETS=true delete_bucket_if_exists "s3api" "$BUCKET_ONE_NAME"; then
|
||||
log 2 "error deleting static bucket one"
|
||||
elif ! delete_bucket_if_exists "s3api" "$BUCKET_TWO_NAME"; then
|
||||
elif ! RECREATE_BUCKETS=true delete_bucket_if_exists "s3api" "$BUCKET_TWO_NAME"; then
|
||||
log 2 "error deleting static bucket two"
|
||||
fi
|
||||
if ! teardown; then
|
||||
if ! stop_versity; then
|
||||
log 2 "error stopping versity"
|
||||
fi
|
||||
50
tests/rest_scripts/copy_object.sh
Executable file
50
tests/rest_scripts/copy_object.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
# shellcheck disable=SC2153
|
||||
key="$OBJECT_KEY"
|
||||
# shellcheck disable=SC2153
|
||||
copy_source="$COPY_SOURCE"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="PUT
|
||||
/$bucket_name/$key
|
||||
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-copy-source:$copy_source
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-copy-source;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" -X PUT "$AWS_ENDPOINT_URL/$bucket_name/$key")
|
||||
curl_command+=(-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-copy-source;x-amz-date,Signature=$signature\"")
|
||||
curl_command+=(-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-copy-source: $copy_source\""
|
||||
-H "\"x-amz-date: $current_date_time\"")
|
||||
curl_command+=(-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
47
tests/rest_scripts/delete_object_tagging.sh
Executable file
47
tests/rest_scripts/delete_object_tagging.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
# shellcheck disable=SC2153
|
||||
key="$OBJECT_KEY"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="DELETE
|
||||
/$bucket_name/$key
|
||||
tagging=
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" -X DELETE "$AWS_ENDPOINT_URL/$bucket_name/$key?tagging"
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
70
tests/rest_scripts/delete_objects.sh
Executable file
70
tests/rest_scripts/delete_objects.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153,SC2154
|
||||
payload="$PAYLOAD"
|
||||
# shellcheck disable=SC2153,SC2154
|
||||
bucket_name="$BUCKET_NAME"
|
||||
has_content_md5="${HAS_CONTENT_MD5:="true"}"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
payload_hash="$(echo -n "$payload" | sha256sum | awk '{print $1}')"
|
||||
if [ "$has_content_md5" == "true" ]; then
|
||||
content_md5=$(echo -n "$payload" | openssl dgst -binary -md5 | openssl base64)
|
||||
fi
|
||||
|
||||
canonical_request="POST
|
||||
/$bucket_name
|
||||
delete=
|
||||
"
|
||||
if [ "$has_content_md5" == "true" ]; then
|
||||
canonical_request+="content-md5:$content_md5
|
||||
"
|
||||
fi
|
||||
canonical_request+="host:$host
|
||||
x-amz-content-sha256:$payload_hash
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
"
|
||||
if [ "$has_content_md5" == "true" ]; then
|
||||
canonical_request+="content-md5;"
|
||||
fi
|
||||
canonical_request+="host;x-amz-content-sha256;x-amz-date
|
||||
$payload_hash"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" -X POST "$AWS_ENDPOINT_URL/$bucket_name?delete")
|
||||
signed_headers=""
|
||||
if [ "$has_content_md5" == "true" ]; then
|
||||
signed_headers+="content-md5;"
|
||||
fi
|
||||
signed_headers+="host;x-amz-content-sha256;x-amz-date"
|
||||
curl_command+=(-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=$signed_headers,Signature=$signature\"")
|
||||
curl_command+=(-H "\"Content-Type: application/xml\"")
|
||||
if [ "$has_content_md5" == "true" ]; then
|
||||
curl_command+=(-H "\"content-md5: $content_md5\"")
|
||||
fi
|
||||
curl_command+=(-H "\"x-amz-content-sha256: $payload_hash\""
|
||||
-H "\"x-amz-date: $current_date_time\"")
|
||||
curl_command+=(-o "$OUTPUT_FILE")
|
||||
curl_command+=(-d "\"${payload//\"/\\\"}\"")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
43
tests/rest_scripts/get_bucket_acl.sh
Executable file
43
tests/rest_scripts/get_bucket_acl.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="GET
|
||||
/$bucket_name
|
||||
acl=
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" "$AWS_ENDPOINT_URL/$bucket_name?acl="
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
43
tests/rest_scripts/get_bucket_ownership_controls.sh
Executable file
43
tests/rest_scripts/get_bucket_ownership_controls.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="GET
|
||||
/$bucket_name
|
||||
ownershipControls=
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" "$AWS_ENDPOINT_URL/$bucket_name?ownershipControls="
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
43
tests/rest_scripts/get_bucket_policy.sh
Executable file
43
tests/rest_scripts/get_bucket_policy.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="GET
|
||||
/$bucket_name
|
||||
policy=
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" "$AWS_ENDPOINT_URL/$bucket_name?policy="
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
47
tests/rest_scripts/get_object.sh
Executable file
47
tests/rest_scripts/get_object.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
# shellcheck disable=SC2154
|
||||
key="$OBJECT_KEY"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
#x-amz-object-attributes:ETag
|
||||
canonical_request="GET
|
||||
/$bucket_name/$key
|
||||
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" "$AWS_ENDPOINT_URL/$bucket_name/$key"
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
44
tests/rest_scripts/get_public_access_block.sh
Executable file
44
tests/rest_scripts/get_public_access_block.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="GET
|
||||
/$bucket_name
|
||||
publicAccessBlock=
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" "$AWS_ENDPOINT_URL/$bucket_name?publicAccessBlock="
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
47
tests/rest_scripts/head_object.sh
Executable file
47
tests/rest_scripts/head_object.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
# shellcheck disable=SC2154
|
||||
key="$OBJECT_KEY"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
#x-amz-object-attributes:ETag
|
||||
canonical_request="HEAD
|
||||
/$bucket_name/$key
|
||||
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ksI -w "\"%{http_code}\"" "$AWS_ENDPOINT_URL/$bucket_name/$key"
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
45
tests/rest_scripts/list_object_versions.sh
Executable file
45
tests/rest_scripts/list_object_versions.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
canonical_request="GET
|
||||
/$bucket_name
|
||||
versions=
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" "https://$host/$bucket_name?versions"
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
74
tests/rest_scripts/list_objects.sh
Executable file
74
tests/rest_scripts/list_objects.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
version_two="${VERSION_TWO:-FALSE}"
|
||||
max_keys="${MAX_KEYS:-0}"
|
||||
# shellcheck disable=SC2153
|
||||
marker="$MARKER"
|
||||
# shellcheck disable=SC2153
|
||||
if [ "$CONTINUATION_TOKEN" != "" ]; then
|
||||
continuation_token=$(jq -rn --arg token "$CONTINUATION_TOKEN" '$token | @uri')
|
||||
fi
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
#x-amz-object-attributes:ETag
|
||||
canonical_request="GET
|
||||
/$bucket_name
|
||||
"
|
||||
|
||||
queries=""
|
||||
if [ "$MARKER" != "" ]; then
|
||||
queries=$(add_parameter "$queries" "marker=$marker")
|
||||
fi
|
||||
if [ "$CONTINUATION_TOKEN" != "" ]; then
|
||||
queries=$(add_parameter "$queries" "continuation-token=$continuation_token")
|
||||
fi
|
||||
if [ "$version_two" != "FALSE" ]; then
|
||||
queries=$(add_parameter "$queries" "list-type=2")
|
||||
fi
|
||||
if [ "$max_keys" -ne 0 ]; then
|
||||
queries=$(add_parameter "$queries" "max-keys=$max_keys")
|
||||
fi
|
||||
|
||||
canonical_request+="
|
||||
host:$host
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"")
|
||||
url="'$AWS_ENDPOINT_URL/$bucket_name"
|
||||
if [ "$queries" != "" ]; then
|
||||
url+="?$queries"
|
||||
fi
|
||||
url+="'"
|
||||
curl_command+=("$url")
|
||||
curl_command+=(-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
77
tests/rest_scripts/put_bucket_acl.sh
Executable file
77
tests/rest_scripts/put_bucket_acl.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
# shellcheck disable=SC2153
|
||||
acl_file="$ACL_FILE"
|
||||
# shellcheck disable=SC2153
|
||||
canned_acl="$CANNED_ACL"
|
||||
|
||||
if [ -n "$ACL_FILE" ]; then
|
||||
payload="$(cat "$acl_file")"
|
||||
else
|
||||
payload=""
|
||||
fi
|
||||
|
||||
payload_hash="$(echo -n "$payload" | sha256sum | awk '{print $1}')"
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="PUT
|
||||
/$bucket_name
|
||||
acl=
|
||||
host:$host
|
||||
"
|
||||
if [ -n "$CANNED_ACL" ]; then
|
||||
canonical_request+="x-amz-acl:$canned_acl
|
||||
"
|
||||
fi
|
||||
canonical_request+="x-amz-content-sha256:$payload_hash
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
"
|
||||
canonical_request+="host;"
|
||||
if [ -n "$CANNED_ACL" ]; then
|
||||
canonical_request+="x-amz-acl;"
|
||||
fi
|
||||
canonical_request+="x-amz-content-sha256;x-amz-date
|
||||
$payload_hash"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" -X PUT "$AWS_ENDPOINT_URL/$bucket_name?acl=")
|
||||
if [ -n "$CANNED_ACL" ]; then
|
||||
acl_header="x-amz-acl;"
|
||||
else
|
||||
acl_header=""
|
||||
fi
|
||||
curl_command+=(-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;${acl_header}x-amz-content-sha256;x-amz-date,Signature=$signature\"")
|
||||
if [ -n "$CANNED_ACL" ]; then
|
||||
curl_command+=(-H "\"x-amz-acl: $canned_acl\"")
|
||||
fi
|
||||
curl_command+=(-H "\"x-amz-content-sha256: $payload_hash\""
|
||||
-H "\"x-amz-date: $current_date_time\"")
|
||||
if [ -n "$ACL_FILE" ]; then
|
||||
curl_command+=(-d "\"${payload//\"/\\\"}\"")
|
||||
fi
|
||||
curl_command+=(-o "$OUTPUT_FILE")
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
59
tests/rest_scripts/put_bucket_ownership_controls.sh
Executable file
59
tests/rest_scripts/put_bucket_ownership_controls.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
# shellcheck disable=SC2153
|
||||
ownership="$OWNERSHIP"
|
||||
|
||||
payload="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||
<OwnershipControls xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">
|
||||
<Rule>
|
||||
<ObjectOwnership>$ownership</ObjectOwnership>
|
||||
</Rule>
|
||||
</OwnershipControls>"
|
||||
|
||||
content_md5=$(echo -n "$payload" | openssl dgst -binary -md5 | openssl base64)
|
||||
payload_hash="$(echo -n "$payload" | sha256sum | awk '{print $1}')"
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="PUT
|
||||
/$bucket_name
|
||||
ownershipControls=
|
||||
content-md5:$content_md5
|
||||
host:$host
|
||||
x-amz-content-sha256:$payload_hash
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
content-md5;host;x-amz-content-sha256;x-amz-date
|
||||
$payload_hash"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" -X PUT "$AWS_ENDPOINT_URL/$bucket_name?ownershipControls="
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=content-md5;host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"Content-MD5: $content_md5\""
|
||||
-H "\"x-amz-content-sha256: $payload_hash\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-d "\"${payload//\"/\\\"}\""
|
||||
-o "$OUTPUT_FILE")
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
51
tests/rest_scripts/put_bucket_policy.sh
Executable file
51
tests/rest_scripts/put_bucket_policy.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
# shellcheck disable=SC2153
|
||||
policy_file="$POLICY_FILE"
|
||||
|
||||
payload="$(cat "$policy_file")"
|
||||
|
||||
payload_hash="$(echo -n "$payload" | sha256sum | awk '{print $1}')"
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="PUT
|
||||
/$bucket_name
|
||||
policy=
|
||||
host:$host
|
||||
x-amz-content-sha256:$payload_hash
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
$payload_hash"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" -X PUT "$AWS_ENDPOINT_URL/$bucket_name?policy="
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: $payload_hash\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-d "\"${payload//\"/\\\"}\""
|
||||
-o "$OUTPUT_FILE")
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
58
tests/rest_scripts/put_public_access_block.sh
Executable file
58
tests/rest_scripts/put_public_access_block.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# Fields
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
block_public_acls="${BLOCK_PUBLIC_ACLS:-TRUE}"
|
||||
ignore_public_acls="${IGNORE_PUBLIC_ACLS:-TRUE}"
|
||||
block_public_policy="${BLOCK_PUBLIC_POLICY:-TRUE}"
|
||||
restrict_public_buckets="${RESTRICT_PUBLIC_BUCKETS:-TRUE}"
|
||||
|
||||
payload="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||
<PublicAccessBlockConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">
|
||||
<BlockPublicAcls>$block_public_acls</BlockPublicAcls>
|
||||
<IgnorePublicAcls>$ignore_public_acls</IgnorePublicAcls>
|
||||
<BlockPublicPolicy>$block_public_policy</BlockPublicPolicy>
|
||||
<RestrictPublicBuckets>$restrict_public_buckets</RestrictPublicBuckets>
|
||||
</PublicAccessBlockConfiguration>"
|
||||
|
||||
payload_hash="$(echo -n "$payload" | sha256sum | awk '{print $1}')"
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
|
||||
canonical_request="PUT
|
||||
/$bucket_name
|
||||
publicAccessBlock=
|
||||
host:$host
|
||||
x-amz-content-sha256:$payload_hash
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-date
|
||||
$payload_hash"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" -X PUT "$AWS_ENDPOINT_URL/$bucket_name?publicAccessBlock="
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: $payload_hash\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-d "\"${payload//\"/\\\"}\""
|
||||
-o "$OUTPUT_FILE")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
@@ -65,3 +65,15 @@ $canonical_request_hash"
|
||||
curl_command=()
|
||||
add_command_recording_if_enabled
|
||||
}
|
||||
|
||||
add_parameter() {
|
||||
if [ "$#" -ne 2 ]; then
|
||||
return
|
||||
fi
|
||||
current_string="$1"
|
||||
if [ "$current_string" != "" ]; then
|
||||
current_string+="&"
|
||||
fi
|
||||
current_string+="$2"
|
||||
echo "$current_string"
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ $payload_hash"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
sleep 5
|
||||
|
||||
curl_command+=(curl -isk -w "\"%{http_code}\"" "\"$AWS_ENDPOINT_URL/$bucket_name/$key?partNumber=$part_number&uploadId=$upload_id\""
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: $payload_hash\""
|
||||
|
||||
55
tests/rest_scripts/upload_part_copy.sh
Executable file
55
tests/rest_scripts/upload_part_copy.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/rest_scripts/rest.sh
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
bucket_name="$BUCKET_NAME"
|
||||
# shellcheck disable=SC2153
|
||||
key="$OBJECT_KEY"
|
||||
# shellcheck disable=SC2153
|
||||
part_number="$PART_NUMBER"
|
||||
# shellcheck disable=SC2153
|
||||
upload_id="$UPLOAD_ID"
|
||||
# shellcheck disable=SC2153
|
||||
part_location=$PART_LOCATION
|
||||
|
||||
current_date_time=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||
aws_endpoint_url_address=${AWS_ENDPOINT_URL#*//}
|
||||
# shellcheck disable=SC2034
|
||||
header=$(echo "$AWS_ENDPOINT_URL" | awk -F: '{print $1}')
|
||||
# shellcheck disable=SC2154
|
||||
canonical_request="PUT
|
||||
/$bucket_name/$key
|
||||
partNumber=$part_number&uploadId=$upload_id
|
||||
host:$aws_endpoint_url_address
|
||||
x-amz-content-sha256:UNSIGNED-PAYLOAD
|
||||
x-amz-copy-source:$part_location
|
||||
x-amz-date:$current_date_time
|
||||
|
||||
host;x-amz-content-sha256;x-amz-copy-source;x-amz-date
|
||||
UNSIGNED-PAYLOAD"
|
||||
|
||||
create_canonical_hash_sts_and_signature
|
||||
|
||||
curl_command+=(curl -ks -w "\"%{http_code}\"" -X PUT "\"$AWS_ENDPOINT_URL/$bucket_name/$key?partNumber=$part_number&uploadId=$upload_id\""
|
||||
-H "\"Authorization: AWS4-HMAC-SHA256 Credential=$aws_access_key_id/$year_month_day/$aws_region/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-copy-source;x-amz-date,Signature=$signature\""
|
||||
-H "\"x-amz-content-sha256: UNSIGNED-PAYLOAD\""
|
||||
-H "\"x-amz-copy-source: $part_location\""
|
||||
-H "\"x-amz-date: $current_date_time\""
|
||||
-o "\"$OUTPUT_FILE\"")
|
||||
# shellcheck disable=SC2154
|
||||
eval "${curl_command[*]}" 2>&1
|
||||
@@ -141,7 +141,11 @@ run_suite() {
|
||||
;;
|
||||
rest)
|
||||
echo "Running rest tests ..."
|
||||
"$HOME"/bin/bats ./tests/test_rest.sh || exit_code=$?
|
||||
if "$HOME"/bin/bats ./tests/test_rest.sh; then
|
||||
"$HOME"/bin/bats ./tests/test_rest_acl.sh || exit_code=$?
|
||||
else
|
||||
exit_code=1
|
||||
fi
|
||||
;;
|
||||
s3api-user)
|
||||
echo "Running s3api user tests ..."
|
||||
|
||||
@@ -14,19 +14,23 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/env.sh
|
||||
source ./tests/report.sh
|
||||
source ./tests/setup_mc.sh
|
||||
source ./tests/util.sh
|
||||
source ./tests/util/util_object.sh
|
||||
source ./tests/versity.sh
|
||||
|
||||
# bats setup function
|
||||
setup() {
|
||||
base_setup
|
||||
|
||||
if [ -n "$TEST_LOG_FILE" ]; then
|
||||
if ! error=$(touch "$TEST_LOG_FILE.tmp" 2>&1); then
|
||||
log 2 "error creating log file: $error"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log 4 "Running test $BATS_TEST_NAME"
|
||||
if [[ $LOG_LEVEL -ge 5 ]] || [[ -n "$TIME_LOG" ]]; then
|
||||
start_time=$(date +%s)
|
||||
@@ -46,27 +50,37 @@ setup() {
|
||||
fi
|
||||
|
||||
export AWS_PROFILE
|
||||
log 4 "********** END SETUP **********"
|
||||
}
|
||||
|
||||
# fail a test
|
||||
# param: error message
|
||||
#fail() {
|
||||
# log 1 "$1"
|
||||
# exit 1
|
||||
#}
|
||||
delete_temp_log_if_exists() {
|
||||
if [ -e "$TEST_LOG_FILE.tmp" ]; then
|
||||
if ! error=$(rm "$TEST_LOG_FILE.tmp" 2>&1); then
|
||||
log 2 "error deleting temp log: $error"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# bats teardown function
|
||||
teardown() {
|
||||
if [[ ( "$BATS_TEST_COMPLETED" -ne 1 ) && ( -e "$COMMAND_LOG" ) ]]; then
|
||||
cat "$COMMAND_LOG"
|
||||
echo "**********************************************************************************"
|
||||
fi
|
||||
# shellcheck disable=SC2154
|
||||
if ! bucket_cleanup_if_bucket_exists "s3api" "$BUCKET_ONE_NAME"; then
|
||||
log 3 "error deleting bucket $BUCKET_ONE_NAME or contents"
|
||||
log 4 "********** BEGIN TEARDOWN **********"
|
||||
if [ "$DELETE_BUCKETS_AFTER_TEST" != "false" ]; then
|
||||
log 5 "deleting or clearing buckets"
|
||||
if ! bucket_cleanup_if_bucket_exists "s3api" "$BUCKET_ONE_NAME"; then
|
||||
log 3 "error deleting bucket $BUCKET_ONE_NAME or contents"
|
||||
fi
|
||||
if ! bucket_cleanup_if_bucket_exists "s3api" "$BUCKET_TWO_NAME"; then
|
||||
log 3 "error deleting bucket $BUCKET_TWO_NAME or contents"
|
||||
fi
|
||||
fi
|
||||
if ! bucket_cleanup_if_bucket_exists "s3api" "$BUCKET_TWO_NAME"; then
|
||||
log 3 "error deleting bucket $BUCKET_TWO_NAME or contents"
|
||||
if user_exists "$USERNAME_ONE" && ! delete_user "$USERNAME_ONE"; then
|
||||
log 3 "error deleting user $USERNAME_ONE"
|
||||
fi
|
||||
if user_exists "$USERNAME_TWO" && ! delete_user "$USERNAME_TWO"; then
|
||||
log 3 "error deleting user $USERNAME_TWO"
|
||||
fi
|
||||
if [ "$REMOVE_TEST_FILE_FOLDER" == "true" ]; then
|
||||
log 6 "removing test file folder"
|
||||
@@ -86,4 +100,26 @@ teardown() {
|
||||
if [[ -n "$COVERAGE_DB" ]]; then
|
||||
record_result
|
||||
fi
|
||||
if [[ "$BATS_TEST_COMPLETED" -ne 1 ]]; then
|
||||
if [[ -e "$COMMAND_LOG" ]]; then
|
||||
cat "$COMMAND_LOG"
|
||||
echo "**********************************************************************************"
|
||||
fi
|
||||
if [[ -e "$TEST_LOG_FILE.tmp" ]]; then
|
||||
echo "********************************** LOG *******************************************"
|
||||
cat "$TEST_LOG_FILE.tmp"
|
||||
echo "**********************************************************************************"
|
||||
fi
|
||||
fi
|
||||
if ! delete_command_log; then
|
||||
log 3 "error deleting command log"
|
||||
fi
|
||||
if [ -e "$TEST_LOG_FILE.tmp" ]; then
|
||||
if ! error=$(cat "$TEST_LOG_FILE.tmp" >> "$TEST_LOG_FILE" 2>&1); then
|
||||
log 2 "error appending temp log to main log: $error"
|
||||
fi
|
||||
if ! delete_temp_log_if_exists; then
|
||||
log 2 "error deleting temp log"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ check_for_alias() {
|
||||
local alias_result
|
||||
aliases=$(mc alias list)
|
||||
if [[ $alias_result -ne 0 ]]; then
|
||||
echo "error checking for aliases: $aliases"
|
||||
log 2 "error checking for aliases: $aliases"
|
||||
return 2
|
||||
fi
|
||||
while IFS= read -r line; do
|
||||
@@ -32,7 +32,7 @@ check_for_alias() {
|
||||
check_add_mc_alias() {
|
||||
check_for_alias || alias_result=$?
|
||||
if [[ $alias_result -eq 2 ]]; then
|
||||
echo "error checking for aliases"
|
||||
log 2 "error checking for aliases"
|
||||
return 1
|
||||
fi
|
||||
if [[ $alias_result -eq 0 ]]; then
|
||||
@@ -41,7 +41,7 @@ check_add_mc_alias() {
|
||||
local set_result
|
||||
error=$(mc alias set --insecure "$MC_ALIAS" "$AWS_ENDPOINT_URL" "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY") || set_result=$?
|
||||
if [[ $set_result -ne 0 ]]; then
|
||||
echo "error setting alias: $error"
|
||||
log 2 "error setting alias: $error"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# under the License.
|
||||
|
||||
source ./tests/env.sh
|
||||
source ./tests/util.sh
|
||||
source ./tests/util/util_object.sh
|
||||
source ./tests/commands/create_bucket.sh
|
||||
|
||||
create_bucket_if_not_exists() {
|
||||
@@ -29,14 +29,14 @@ create_bucket_if_not_exists() {
|
||||
return 1
|
||||
fi
|
||||
if [[ $exists_result -eq 0 ]]; then
|
||||
log 5 "bucket '$2' already exists, skipping"
|
||||
echo "bucket '$2' already exists, skipping"
|
||||
return 0
|
||||
fi
|
||||
if ! create_bucket_object_lock_enabled "$2"; then
|
||||
log 2 "error creating bucket"
|
||||
return 1
|
||||
fi
|
||||
log 5 "bucket '$2' successfully created"
|
||||
echo "bucket '$2' successfully created"
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
source ./tests/setup.sh
|
||||
source ./tests/util.sh
|
||||
|
||||
if ! base_setup; then
|
||||
log 2 "error starting versity to set up static buckets"
|
||||
exit 1
|
||||
fi
|
||||
if ! delete_bucket_recursive "s3" "$BUCKET_ONE_NAME"; then
|
||||
log 2 "error creating static bucket one"
|
||||
elif ! delete_bucket_recursive "s3" "$BUCKET_TWO_NAME"; then
|
||||
log 2 "error creating static bucket two"
|
||||
fi
|
||||
log 4 "buckets deleted successfully"
|
||||
if ! stop_versity; then
|
||||
log 2 "error stopping versity"
|
||||
fi
|
||||
@@ -15,13 +15,13 @@
|
||||
# under the License.
|
||||
|
||||
source ./tests/setup.sh
|
||||
source ./tests/util.sh
|
||||
source ./tests/util_acl.sh
|
||||
source ./tests/util_bucket_location.sh
|
||||
source ./tests/util_file.sh
|
||||
source ./tests/util_list_buckets.sh
|
||||
source ./tests/util_policy.sh
|
||||
source ./tests/util_presigned_url.sh
|
||||
source ./tests/util/util_acl.sh
|
||||
source ./tests/util/util_bucket_location.sh
|
||||
source ./tests/util/util_file.sh
|
||||
source ./tests/util/util_list_buckets.sh
|
||||
source ./tests/util/util_object.sh
|
||||
source ./tests/util/util_policy.sh
|
||||
source ./tests/util/util_presigned_url.sh
|
||||
source ./tests/commands/copy_object.sh
|
||||
source ./tests/commands/delete_bucket_tagging.sh
|
||||
source ./tests/commands/delete_object_tagging.sh
|
||||
@@ -218,7 +218,7 @@ test_common_list_buckets() {
|
||||
|
||||
test_common_list_objects() {
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "common test function for listing objects requires command type"
|
||||
log 2 "common test function for listing objects requires command type"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -294,7 +294,7 @@ test_common_set_get_object_tags() {
|
||||
|
||||
test_common_presigned_url_utf8_chars() {
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "presigned url command missing command type"
|
||||
log 2 "presigned url command missing command type"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -303,7 +303,8 @@ test_common_presigned_url_utf8_chars() {
|
||||
|
||||
run create_test_file "$bucket_file"
|
||||
assert_success
|
||||
dd if=/dev/urandom of="$TEST_FILE_FOLDER/$bucket_file" bs=5M count=1 || fail "error creating test file"
|
||||
run dd if=/dev/urandom of="$TEST_FILE_FOLDER/$bucket_file" bs=5M count=1
|
||||
assert_success
|
||||
|
||||
run setup_bucket "$1" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
@@ -382,12 +383,7 @@ test_common_get_put_delete_bucket_policy() {
|
||||
assert_success
|
||||
|
||||
effect="Allow"
|
||||
#principal="*"
|
||||
if [[ $DIRECT == "true" ]]; then
|
||||
principal="{\"AWS\": \"arn:aws:iam::$DIRECT_AWS_USER_ID:user/s3user\"}"
|
||||
else
|
||||
principal="\"*\""
|
||||
fi
|
||||
principal="*"
|
||||
action="s3:GetObject"
|
||||
resource="arn:aws:s3:::$BUCKET_ONE_NAME/*"
|
||||
|
||||
|
||||
@@ -15,13 +15,11 @@
|
||||
# under the License.
|
||||
|
||||
test_put_bucket_acl_s3cmd() {
|
||||
if [[ $DIRECT != "true" ]]; then
|
||||
skip "https://github.com/versity/versitygw/issues/695"
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/963"
|
||||
fi
|
||||
run setup_bucket "s3cmd" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run put_bucket_ownership_controls "$BUCKET_ONE_NAME" "BucketOwnerPreferred"
|
||||
run setup_bucket "s3cmd" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
username=$USERNAME_ONE
|
||||
@@ -46,9 +44,6 @@ test_put_bucket_acl_s3cmd() {
|
||||
}
|
||||
|
||||
test_common_put_bucket_acl() {
|
||||
if [[ $RECREATE_BUCKETS == "false" ]]; then
|
||||
skip "https://github.com/versity/versitygw/issues/716"
|
||||
fi
|
||||
assert [ $# -eq 1 ]
|
||||
|
||||
run setup_bucket "$1" "$BUCKET_ONE_NAME"
|
||||
@@ -57,8 +52,7 @@ test_common_put_bucket_acl() {
|
||||
run put_bucket_ownership_controls "$BUCKET_ONE_NAME" "BucketOwnerPreferred"
|
||||
assert_success
|
||||
|
||||
username=$USERNAME_ONE
|
||||
run setup_user "$username" "HIJKLMN" "user"
|
||||
run setup_user "$USERNAME_ONE" "$PASSWORD_ONE" "user"
|
||||
assert_success
|
||||
|
||||
run get_check_acl_id "$1" "$BUCKET_ONE_NAME"
|
||||
@@ -69,47 +63,24 @@ test_common_put_bucket_acl() {
|
||||
assert_success
|
||||
|
||||
if [[ $DIRECT == "true" ]]; then
|
||||
grantee="{\"Type\": \"Group\", \"URI\": \"http://acs.amazonaws.com/groups/global/AllUsers\"}"
|
||||
grantee_type="Group"
|
||||
grantee_id="http://acs.amazonaws.com/groups/global/AllUsers"
|
||||
else
|
||||
grantee="{\"ID\": \"$username\", \"Type\": \"CanonicalUser\"}"
|
||||
grantee_type="CanonicalUser"
|
||||
grantee_id="$USERNAME_ONE"
|
||||
fi
|
||||
run setup_acl_json "$TEST_FILE_FOLDER/$acl_file" "$grantee_type" "$grantee_id" "READ" "$AWS_ACCESS_KEY_ID"
|
||||
assert_success
|
||||
|
||||
cat <<EOF > "$TEST_FILE_FOLDER"/"$acl_file"
|
||||
{
|
||||
"Grants": [
|
||||
{
|
||||
"Grantee": $grantee,
|
||||
"Permission": "READ"
|
||||
}
|
||||
],
|
||||
"Owner": {
|
||||
"ID": "$AWS_ACCESS_KEY_ID"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
log 5 "acl: $(cat "$TEST_FILE_FOLDER/$acl_file")"
|
||||
run put_bucket_acl_s3api "$BUCKET_ONE_NAME" "$TEST_FILE_FOLDER"/"$acl_file"
|
||||
assert_success
|
||||
|
||||
run get_check_acl_after_first_put "$1" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
cat <<EOF > "$TEST_FILE_FOLDER"/"$acl_file"
|
||||
{
|
||||
"Grants": [
|
||||
{
|
||||
"Grantee": {
|
||||
"ID": "$username",
|
||||
"Type": "CanonicalUser"
|
||||
},
|
||||
"Permission": "FULL_CONTROL"
|
||||
}
|
||||
],
|
||||
"Owner": {
|
||||
"ID": "$AWS_ACCESS_KEY_ID"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
run setup_acl_json "$TEST_FILE_FOLDER/$acl_file" "CanonicalUser" "$USERNAME_ONE" "FULL_CONTROL" "$AWS_ACCESS_KEY_ID"
|
||||
assert_success
|
||||
|
||||
run put_bucket_acl_s3api "$BUCKET_ONE_NAME" "$TEST_FILE_FOLDER"/"$acl_file"
|
||||
assert_success
|
||||
|
||||
@@ -14,11 +14,14 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/test_common.sh
|
||||
source ./tests/setup.sh
|
||||
source ./tests/util_create_bucket.sh
|
||||
source ./tests/util_head_bucket.sh
|
||||
source ./tests/util_tags.sh
|
||||
source ./tests/util/util_create_bucket.sh
|
||||
source ./tests/util/util_head_bucket.sh
|
||||
source ./tests/util/util_tags.sh
|
||||
source ./tests/commands/delete_bucket_policy.sh
|
||||
source ./tests/commands/get_bucket_policy.sh
|
||||
source ./tests/commands/put_bucket_policy.sh
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/test_common.sh
|
||||
|
||||
export RUN_MC=true
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/commands/create_multipart_upload.sh
|
||||
source ./tests/commands/delete_object_tagging.sh
|
||||
source ./tests/commands/get_bucket_versioning.sh
|
||||
@@ -28,17 +31,27 @@ source ./tests/commands/put_object_retention.sh
|
||||
source ./tests/commands/put_object_tagging.sh
|
||||
source ./tests/logger.sh
|
||||
source ./tests/setup.sh
|
||||
source ./tests/util.sh
|
||||
source ./tests/util_attributes.sh
|
||||
source ./tests/util_legal_hold.sh
|
||||
source ./tests/util_list_buckets.sh
|
||||
source ./tests/util_list_objects.sh
|
||||
source ./tests/util_list_parts.sh
|
||||
source ./tests/util_lock_config.sh
|
||||
source ./tests/util_rest.sh
|
||||
source ./tests/util_tags.sh
|
||||
source ./tests/util_time.sh
|
||||
source ./tests/util_versioning.sh
|
||||
source ./tests/util/util_acl.sh
|
||||
source ./tests/util/util_attributes.sh
|
||||
source ./tests/util/util_delete_object.sh
|
||||
source ./tests/util/util_head_object.sh
|
||||
source ./tests/util/util_legal_hold.sh
|
||||
source ./tests/util/util_list_buckets.sh
|
||||
source ./tests/util/util_list_objects.sh
|
||||
source ./tests/util/util_list_parts.sh
|
||||
source ./tests/util/util_lock_config.sh
|
||||
source ./tests/util/util_multipart_before_completion.sh
|
||||
source ./tests/util/util_object.sh
|
||||
source ./tests/util/util_ownership.sh
|
||||
source ./tests/util/util_policy.sh
|
||||
source ./tests/util/util_public_access_block.sh
|
||||
source ./tests/util/util_rest.sh
|
||||
source ./tests/util/util_tags.sh
|
||||
source ./tests/util/util_time.sh
|
||||
source ./tests/util/util_versioning.sh
|
||||
source ./tests/util/util_xml.sh
|
||||
|
||||
export RUN_USERS=true
|
||||
|
||||
@test "test_rest_list_objects" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
@@ -185,7 +198,6 @@ source ./tests/util_versioning.sh
|
||||
}
|
||||
|
||||
@test "test_rest_versioning" {
|
||||
skip "https://github.com/versity/versitygw/issues/864"
|
||||
test_file="test_file"
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
@@ -218,7 +230,6 @@ source ./tests/util_versioning.sh
|
||||
}
|
||||
|
||||
@test "versioning - add version, then delete and check for marker" {
|
||||
skip "https://github.com/versity/versitygw/issues/864"
|
||||
test_file="test_file"
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
@@ -241,9 +252,6 @@ source ./tests/util_versioning.sh
|
||||
}
|
||||
|
||||
@test "versioning - retrieve after delete" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/888"
|
||||
fi
|
||||
test_file="test_file"
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
@@ -266,9 +274,6 @@ source ./tests/util_versioning.sh
|
||||
}
|
||||
|
||||
@test "REST - legal hold, get without config" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/883"
|
||||
fi
|
||||
test_file="test_file"
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
@@ -319,7 +324,7 @@ source ./tests/util_versioning.sh
|
||||
|
||||
@test "REST - get object attributes" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/916"
|
||||
skip "https://github.com/versity/versitygw/issues/1000"
|
||||
fi
|
||||
test_file="test_file"
|
||||
|
||||
@@ -341,7 +346,7 @@ source ./tests/util_versioning.sh
|
||||
|
||||
@test "REST - attributes - invalid param" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/917"
|
||||
skip "https://github.com/versity/versitygw/issues/1001"
|
||||
fi
|
||||
test_file="test_file"
|
||||
|
||||
@@ -360,7 +365,7 @@ source ./tests/util_versioning.sh
|
||||
|
||||
@test "REST - attributes - checksum" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/928"
|
||||
skip "https://github.com/versity/versitygw/issues/1006"
|
||||
fi
|
||||
test_file="test_file"
|
||||
|
||||
@@ -383,9 +388,6 @@ source ./tests/util_versioning.sh
|
||||
}
|
||||
|
||||
@test "REST - bucket tagging - tags" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/932"
|
||||
fi
|
||||
test_key="testKey"
|
||||
test_value="testValue"
|
||||
|
||||
@@ -394,4 +396,203 @@ source ./tests/util_versioning.sh
|
||||
|
||||
run add_verify_bucket_tags_rest "$BUCKET_ONE_NAME" "$test_key" "$test_value"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - get, put bucket ownership controls" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run get_and_check_ownership_controls "$BUCKET_ONE_NAME" "BucketOwnerEnforced"
|
||||
assert_success
|
||||
|
||||
run put_bucket_ownership_controls_rest "$BUCKET_ONE_NAME" "BucketOwnerPreferred"
|
||||
assert_success
|
||||
|
||||
run get_and_check_ownership_controls "$BUCKET_ONE_NAME" "BucketOwnerPreferred"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - get policy w/o policy" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/959"
|
||||
fi
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run get_and_check_no_policy_error "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - put policy" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run setup_user_versitygw_or_direct "$USERNAME_ONE" "$PASSWORD_ONE" "user" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
log 5 "username: ${lines[0]}"
|
||||
log 5 "password: ${lines[1]}"
|
||||
|
||||
sleep 5
|
||||
|
||||
run setup_policy_with_single_statement "$TEST_FILE_FOLDER/policy_file.txt" "2012-10-17" "Allow" "$USERNAME_ONE" "s3:PutBucketTagging" "arn:aws:s3:::$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run put_and_check_policy_rest "$BUCKET_ONE_NAME" "$TEST_FILE_FOLDER/policy_file.txt" "Allow" "$USERNAME_ONE" "s3:PutBucketTagging" "arn:aws:s3:::$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - list objects v2 - invalid continuation token" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/993"
|
||||
fi
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file="test_file"
|
||||
test_file_two="test_file_2"
|
||||
test_file_three="test_file_3"
|
||||
run create_test_files "$test_file" "$test_file_two" "$test_file_three"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file_two" "$BUCKET_ONE_NAME" "$test_file_two"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file_three" "$BUCKET_ONE_NAME" "$test_file_three"
|
||||
assert_success
|
||||
|
||||
run list_objects_check_params_get_token "$BUCKET_ONE_NAME" "$test_file" "$test_file_two" "TRUE"
|
||||
assert_success
|
||||
continuation_token=$output
|
||||
|
||||
# interestingly, AWS appears to accept continuation tokens that are a few characters off, so have to remove three chars
|
||||
run list_objects_check_continuation_error "$BUCKET_ONE_NAME" "${continuation_token:0:${#continuation_token}-3}"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - list objects v1 - no NextMarker without delimiter" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/999"
|
||||
fi
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file="test_file"
|
||||
test_file_two="test_file_2"
|
||||
run create_test_files "$test_file" "$test_file_two"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file_two" "$BUCKET_ONE_NAME" "$test_file_two"
|
||||
assert_success
|
||||
|
||||
run list_objects_v1_check_nextmarker_empty "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - complete upload - invalid part" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/1008"
|
||||
fi
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file="test_file"
|
||||
run create_large_file "$test_file"
|
||||
assert_success
|
||||
|
||||
run create_upload_finish_wrong_etag "$BUCKET_ONE_NAME" "$test_file"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - upload part copy" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file="test_file"
|
||||
run create_large_file "$test_file"
|
||||
assert_success
|
||||
|
||||
run create_upload_part_copy_rest "$BUCKET_ONE_NAME" "$test_file" "$TEST_FILE_FOLDER/$test_file"
|
||||
assert_success
|
||||
|
||||
run download_and_compare_file "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file" "$TEST_FILE_FOLDER/$test_file-copy"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - head object" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/1018"
|
||||
fi
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file="test_file"
|
||||
run create_test_file "$test_file"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file"
|
||||
assert_success
|
||||
|
||||
run get_etag_rest "$BUCKET_ONE_NAME" "$test_file"
|
||||
assert_success
|
||||
expected_etag=$output
|
||||
|
||||
run get_etag_attribute_rest "$BUCKET_ONE_NAME" "$test_file" "$expected_etag"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - POST call on root endpoint" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/1036"
|
||||
fi
|
||||
run delete_object_empty_bucket_check_error
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - delete objects - no content-md5 header" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/1040"
|
||||
fi
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run delete_objects_no_content_md5_header "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - delete objects command" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file_one="test_file"
|
||||
test_file_two="test_file_two"
|
||||
run create_test_files "$test_file_one" "$test_file_two"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file_one" "$BUCKET_ONE_NAME" "$test_file_one"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file_two" "$BUCKET_ONE_NAME" "$test_file_two"
|
||||
assert_success
|
||||
|
||||
run verify_object_exists "$BUCKET_ONE_NAME" "$test_file_one"
|
||||
assert_success
|
||||
|
||||
run verify_object_exists "$BUCKET_ONE_NAME" "$test_file_two"
|
||||
assert_success
|
||||
|
||||
run delete_objects_verify_success "$BUCKET_ONE_NAME" "$test_file_one" "$test_file_two"
|
||||
assert_success
|
||||
|
||||
run verify_object_not_found "$BUCKET_ONE_NAME" "$test_file_one"
|
||||
assert_success
|
||||
|
||||
run verify_object_not_found "$BUCKET_ONE_NAME" "$test_file_two"
|
||||
assert_success
|
||||
}
|
||||
167
tests/test_rest_acl.sh
Executable file
167
tests/test_rest_acl.sh
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
# Copyright 2024 Versity Software
|
||||
# This file is licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http:#www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/commands/put_object.sh
|
||||
source ./tests/logger.sh
|
||||
source ./tests/setup.sh
|
||||
source ./tests/util/util_acl.sh
|
||||
source ./tests/util/util_object.sh
|
||||
|
||||
export RUN_USERS=true
|
||||
|
||||
@test "REST - get ACL" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run get_and_check_acl_rest "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - put ACL" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file="test_file"
|
||||
run create_test_files "$test_file"
|
||||
assert_success
|
||||
|
||||
run put_bucket_ownership_controls "$BUCKET_ONE_NAME" "BucketOwnerPreferred"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file"
|
||||
assert_success
|
||||
|
||||
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
|
||||
assert_success
|
||||
canonical_id=${lines[0]}
|
||||
user_canonical_id=${lines[1]}
|
||||
username=${lines[2]}
|
||||
password=${lines[3]}
|
||||
|
||||
run setup_acl "$TEST_FILE_FOLDER/acl-file.txt" "$user_canonical_id" "READ" "$canonical_id"
|
||||
assert_success
|
||||
|
||||
run list_objects_with_user_rest_verify_access_denied "$BUCKET_ONE_NAME" "$username" "$password"
|
||||
assert_success
|
||||
|
||||
run put_acl_rest "$BUCKET_ONE_NAME" "$TEST_FILE_FOLDER/acl-file.txt"
|
||||
assert_success
|
||||
|
||||
if [ "$DIRECT" == "true" ]; then
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
run list_objects_with_user_rest_verify_success "$BUCKET_ONE_NAME" "$username" "$password" "$test_file"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - put public-read canned acl" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file="test_file"
|
||||
run create_test_files "$test_file"
|
||||
assert_success
|
||||
|
||||
run put_bucket_ownership_controls "$BUCKET_ONE_NAME" "BucketOwnerPreferred"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file"
|
||||
assert_success
|
||||
|
||||
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
|
||||
assert_success
|
||||
canonical_id=${lines[0]}
|
||||
user_canonical_id=${lines[1]}
|
||||
username=${lines[2]}
|
||||
password=${lines[3]}
|
||||
|
||||
run list_objects_with_user_rest_verify_access_denied "$BUCKET_ONE_NAME" "$username" "$password"
|
||||
assert_success
|
||||
|
||||
if [ "$DIRECT" == "true" ]; then
|
||||
run allow_public_access "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
fi
|
||||
run put_canned_acl_rest "$BUCKET_ONE_NAME" "public-read"
|
||||
assert_success
|
||||
|
||||
run list_objects_with_user_rest_verify_success "$BUCKET_ONE_NAME" "$username" "$password" "$test_file"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - put invalid ACL" {
|
||||
if [ "$DIRECT" != "true" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/986"
|
||||
fi
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run put_bucket_ownership_controls "$BUCKET_ONE_NAME" "BucketOwnerPreferred"
|
||||
assert_success
|
||||
|
||||
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
|
||||
assert_success
|
||||
canonical_id=${lines[0]}
|
||||
user_canonical_id=${lines[1]}
|
||||
username=${lines[2]}
|
||||
password=${lines[3]}
|
||||
|
||||
run setup_acl "$TEST_FILE_FOLDER/acl-file.txt" "$user_canonical_id" "READD" "$canonical_id"
|
||||
assert_success
|
||||
|
||||
if [ "$DIRECT" == "true" ]; then
|
||||
run allow_public_access "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
fi
|
||||
run put_invalid_acl_rest_verify_failure "$BUCKET_ONE_NAME" "$TEST_FILE_FOLDER/acl-file.txt"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "REST - put public-read-write canned acl" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
test_file="test_file"
|
||||
run create_test_files "$test_file"
|
||||
assert_success
|
||||
|
||||
run put_bucket_ownership_controls "$BUCKET_ONE_NAME" "BucketOwnerPreferred"
|
||||
assert_success
|
||||
|
||||
run create_versitygw_acl_user_or_get_direct_user "$USERNAME_ONE" "$PASSWORD_ONE"
|
||||
assert_success
|
||||
canonical_id=${lines[0]}
|
||||
user_canonical_id=${lines[1]}
|
||||
username=${lines[2]}
|
||||
password=${lines[3]}
|
||||
|
||||
run put_object_with_user "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file" "$username" "$password"
|
||||
assert_failure
|
||||
|
||||
if [ "$DIRECT" == "true" ]; then
|
||||
run allow_public_access "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
fi
|
||||
run put_canned_acl_rest "$BUCKET_ONE_NAME" "public-read-write"
|
||||
assert_success
|
||||
|
||||
run put_object_with_user "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file" "$username" "$password"
|
||||
assert_success
|
||||
}
|
||||
@@ -14,8 +14,11 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/test_common.sh
|
||||
source ./tests/util_file.sh
|
||||
source ./tests/util/util_file.sh
|
||||
|
||||
# complete-multipart-upload
|
||||
@test "test_complete_multipart_upload" {
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/test_common.sh
|
||||
source ./tests/util_file.sh
|
||||
source ./tests/util/util_file.sh
|
||||
|
||||
@test "test_list_objects_file_count" {
|
||||
test_common_list_objects_file_count "s3"
|
||||
|
||||
@@ -14,15 +14,18 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/setup.sh
|
||||
source ./tests/util.sh
|
||||
source ./tests/util_aws.sh
|
||||
source ./tests/util_create_bucket.sh
|
||||
source ./tests/util_file.sh
|
||||
source ./tests/util_lock_config.sh
|
||||
source ./tests/util_tags.sh
|
||||
source ./tests/util_users.sh
|
||||
source ./tests/test_aws_root_inner.sh
|
||||
source ./tests/util/util_create_bucket.sh
|
||||
source ./tests/util/util_file.sh
|
||||
source ./tests/util/util_head_bucket.sh
|
||||
source ./tests/util/util_lock_config.sh
|
||||
source ./tests/util/util_object.sh
|
||||
source ./tests/util/util_tags.sh
|
||||
source ./tests/util/util_users.sh
|
||||
source ./tests/test_s3api_root_inner.sh
|
||||
source ./tests/test_common.sh
|
||||
source ./tests/test_common_acl.sh
|
||||
source ./tests/commands/copy_object.sh
|
||||
@@ -50,46 +53,67 @@ source ./tests/commands/select_object_content.sh
|
||||
|
||||
export RUN_USERS=true
|
||||
|
||||
# create-bucket
|
||||
@test "test_create_delete_bucket_aws" {
|
||||
test_common_create_delete_bucket "aws"
|
||||
}
|
||||
|
||||
@test "test_create_bucket_invalid_name" {
|
||||
test_create_bucket_invalid_name_aws_root
|
||||
if [[ $RECREATE_BUCKETS != "true" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
run create_and_check_bucket_invalid_name "s3api"
|
||||
assert_success
|
||||
}
|
||||
|
||||
# delete-bucket - test_create_delete_bucket_aws
|
||||
# create-bucket
|
||||
@test "test_create_delete_bucket_s3api" {
|
||||
test_common_create_delete_bucket "s3api"
|
||||
}
|
||||
|
||||
# delete-bucket - test_create_delete_bucket_s3api
|
||||
|
||||
# delete-bucket-policy
|
||||
@test "test_get_put_delete_bucket_policy" {
|
||||
if [[ -n $SKIP_POLICY ]]; then
|
||||
skip "will not test policy actions with SKIP_POLICY set"
|
||||
fi
|
||||
test_common_get_put_delete_bucket_policy "aws"
|
||||
test_common_get_put_delete_bucket_policy "s3api"
|
||||
}
|
||||
|
||||
# delete-bucket-tagging
|
||||
@test "test-set-get-delete-bucket-tags" {
|
||||
test_common_set_get_delete_bucket_tags "aws"
|
||||
}
|
||||
|
||||
# get-bucket-acl
|
||||
@test "test_get_bucket_acl" {
|
||||
test_get_bucket_acl_aws_root
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run get_bucket_acl_and_check_owner "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
}
|
||||
|
||||
# get-bucket-location
|
||||
@test "test_get_bucket_location" {
|
||||
test_common_get_bucket_location "aws"
|
||||
test_common_get_bucket_location "s3api"
|
||||
}
|
||||
|
||||
# get-bucket-policy - test_get_put_delete_bucket_policy
|
||||
|
||||
# get-bucket-tagging - test_set_get_delete_bucket_tags
|
||||
|
||||
@test "test_head_bucket" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run check_for_empty_region "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "test_head_bucket_doesnt_exist" {
|
||||
if [ "$RECREATE_BUCKETS" == "false" ]; then
|
||||
skip "skip test for static buckets"
|
||||
fi
|
||||
run bucket_info_without_bucket
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "test_head_bucket_invalid_name" {
|
||||
if head_bucket "aws" ""; then
|
||||
if head_bucket "s3api" ""; then
|
||||
fail "able to get bucket info for invalid name"
|
||||
fi
|
||||
}
|
||||
@@ -103,23 +127,7 @@ export RUN_USERS=true
|
||||
test_common_put_bucket_acl "s3api"
|
||||
}
|
||||
|
||||
@test "test_head_bucket" {
|
||||
run setup_bucket "aws" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
head_bucket "aws" "$BUCKET_ONE_NAME" || fail "error getting bucket info"
|
||||
log 5 "INFO: $bucket_info"
|
||||
region=$(echo "$bucket_info" | grep -v "InsecureRequestWarning" | jq -r ".BucketRegion" 2>&1) || fail "error getting bucket region: $region"
|
||||
[[ $region != "" ]] || fail "empty bucket region"
|
||||
bucket_cleanup "aws" "$BUCKET_ONE_NAME"
|
||||
}
|
||||
|
||||
@test "test_head_bucket_doesnt_exist" {
|
||||
run setup_bucket "aws" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
head_bucket "aws" "$BUCKET_ONE_NAME"a || local info_result=$?
|
||||
[[ $info_result -eq 1 ]] || fail "bucket info for non-existent bucket returned"
|
||||
[[ $bucket_info == *"404"* ]] || fail "404 not returned for non-existent bucket info"
|
||||
bucket_cleanup "aws" "$BUCKET_ONE_NAME"
|
||||
# delete-bucket-tagging
|
||||
@test "test-set-get-delete-bucket-tags" {
|
||||
test_common_set_get_delete_bucket_tags "s3api"
|
||||
}
|
||||
|
||||
@@ -14,52 +14,98 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/setup.sh
|
||||
source ./tests/test_aws_root_inner.sh
|
||||
source ./tests/util_file.sh
|
||||
source ./tests/util_multipart.sh
|
||||
source ./tests/util_tags.sh
|
||||
source ./tests/test_s3api_root_inner.sh
|
||||
source ./tests/util/util_file.sh
|
||||
source ./tests/util/util_multipart.sh
|
||||
source ./tests/util/util_multipart_abort.sh
|
||||
source ./tests/util/util_multipart_before_completion.sh
|
||||
source ./tests/util/util_tags.sh
|
||||
source ./tests/commands/get_object.sh
|
||||
source ./tests/commands/put_object.sh
|
||||
source ./tests/commands/list_multipart_uploads.sh
|
||||
|
||||
# abort-multipart-upload
|
||||
@test "test_abort_multipart_upload" {
|
||||
test_abort_multipart_upload_aws_root
|
||||
local bucket_file="bucket-file"
|
||||
|
||||
run create_test_file "$bucket_file"
|
||||
assert_success
|
||||
# shellcheck disable=SC2154
|
||||
run dd if=/dev/urandom of="$TEST_FILE_FOLDER/$bucket_file" bs=5M count=1
|
||||
assert_success
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run run_then_abort_multipart_upload "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER"/"$bucket_file" 4
|
||||
assert_success
|
||||
|
||||
run object_exists "s3api" "$BUCKET_ONE_NAME" "$bucket_file"
|
||||
assert_failure 1
|
||||
}
|
||||
|
||||
# complete-multipart-upload
|
||||
@test "test_complete_multipart_upload" {
|
||||
test_complete_multipart_upload_aws_root
|
||||
local bucket_file="bucket-file"
|
||||
run create_test_files "$bucket_file"
|
||||
assert_success
|
||||
|
||||
run dd if=/dev/urandom of="$TEST_FILE_FOLDER/$bucket_file" bs=5M count=1
|
||||
assert_success
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run multipart_upload "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER"/"$bucket_file" 4
|
||||
assert_success
|
||||
|
||||
run download_and_compare_file "s3api" "$TEST_FILE_FOLDER/$bucket_file" "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER/$bucket_file-copy"
|
||||
assert_success
|
||||
}
|
||||
|
||||
# create-multipart-upload
|
||||
@test "test_create_multipart_upload_properties" {
|
||||
test_create_multipart_upload_properties_aws_root
|
||||
}
|
||||
local bucket_file="bucket-file"
|
||||
|
||||
# test multi-part upload list parts command
|
||||
@test "test-multipart-upload-list-parts" {
|
||||
test_multipart_upload_list_parts_aws_root
|
||||
}
|
||||
local expected_content_type="application/zip"
|
||||
local expected_meta_key="testKey"
|
||||
local expected_meta_val="testValue"
|
||||
local expected_hold_status="ON"
|
||||
local expected_retention_mode="GOVERNANCE"
|
||||
local expected_tag_key="TestTag"
|
||||
local expected_tag_val="TestTagVal"
|
||||
|
||||
# test listing of active uploads
|
||||
@test "test-multipart-upload-list-uploads" {
|
||||
local bucket_file_one="bucket-file-one"
|
||||
local bucket_file_two="bucket-file-two"
|
||||
run setup_multipart_upload_with_params "$BUCKET_ONE_NAME" "$bucket_file"
|
||||
assert_success
|
||||
later=${lines[${#lines[@]}-1]}
|
||||
|
||||
if [[ $RECREATE_BUCKETS == false ]]; then
|
||||
run abort_all_multipart_uploads "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
fi
|
||||
|
||||
run create_test_files "$bucket_file_one" "$bucket_file_two"
|
||||
run multipart_upload_with_params "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER"/"$bucket_file" 4 \
|
||||
"$expected_content_type" \
|
||||
"{\"$expected_meta_key\": \"$expected_meta_val\"}" \
|
||||
"$expected_hold_status" \
|
||||
"$expected_retention_mode" \
|
||||
"$later" \
|
||||
"$expected_tag_key=$expected_tag_val"
|
||||
assert_success
|
||||
|
||||
run setup_bucket "aws" "$BUCKET_ONE_NAME"
|
||||
run get_and_verify_metadata "$bucket_file" "$expected_content_type" "$expected_meta_key" "$expected_meta_val" \
|
||||
"$expected_hold_status" "$expected_retention_mode" "$later"
|
||||
assert_success
|
||||
|
||||
run create_list_check_multipart_uploads "$BUCKET_ONE_NAME" "$bucket_file_one" "$bucket_file_two"
|
||||
run check_verify_object_tags "s3api" "$BUCKET_ONE_NAME" "$bucket_file" "$expected_tag_key" "$expected_tag_val"
|
||||
assert_success
|
||||
|
||||
run put_object_legal_hold "$BUCKET_ONE_NAME" "$bucket_file" "OFF"
|
||||
assert_success
|
||||
|
||||
run get_and_check_legal_hold "s3api" "$BUCKET_ONE_NAME" "$bucket_file" "OFF"
|
||||
assert_success
|
||||
|
||||
run download_and_compare_file "s3api" "$TEST_FILE_FOLDER/$bucket_file" "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER/$bucket_file-copy"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@@ -72,7 +118,7 @@ source ./tests/commands/list_multipart_uploads.sh
|
||||
run dd if=/dev/urandom of="$TEST_FILE_FOLDER/$bucket_file" bs=5M count=1
|
||||
assert_success
|
||||
|
||||
run setup_bucket "aws" "$BUCKET_ONE_NAME"
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run multipart_upload_from_bucket "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER"/"$bucket_file" 4
|
||||
@@ -90,7 +136,7 @@ source ./tests/commands/list_multipart_uploads.sh
|
||||
run create_large_file "$bucket_file"
|
||||
assert_success
|
||||
|
||||
run setup_bucket "aws" "$BUCKET_ONE_NAME"
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run multipart_upload_range_too_large "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER"/"$bucket_file"
|
||||
@@ -102,9 +148,49 @@ source ./tests/commands/list_multipart_uploads.sh
|
||||
run create_large_file "$bucket_file"
|
||||
assert_success
|
||||
|
||||
run setup_bucket "aws" "$BUCKET_ONE_NAME"
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run run_and_verify_multipart_upload_with_valid_range "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER/$bucket_file"
|
||||
assert_success
|
||||
}
|
||||
|
||||
# test multi-part upload list parts command
|
||||
@test "test-multipart-upload-list-parts" {
|
||||
local bucket_file="bucket-file"
|
||||
|
||||
run create_test_file "$bucket_file" 0
|
||||
assert_success
|
||||
run dd if=/dev/urandom of="$TEST_FILE_FOLDER/$bucket_file" bs=5M count=1
|
||||
assert_success
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run start_multipart_upload_list_check_parts "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER"/"$bucket_file"
|
||||
assert_success
|
||||
|
||||
run run_then_abort_multipart_upload "$BUCKET_ONE_NAME" "$bucket_file" "$TEST_FILE_FOLDER/$bucket_file" 4
|
||||
assert_success
|
||||
}
|
||||
|
||||
# test listing of active uploads
|
||||
@test "test-multipart-upload-list-uploads" {
|
||||
local bucket_file_one="bucket-file-one"
|
||||
local bucket_file_two="bucket-file-two"
|
||||
|
||||
if [[ $RECREATE_BUCKETS == false ]]; then
|
||||
run abort_all_multipart_uploads "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
fi
|
||||
|
||||
run create_test_files "$bucket_file_one" "$bucket_file_two"
|
||||
assert_success
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run create_list_check_multipart_uploads "$BUCKET_ONE_NAME" "$bucket_file_one" "$bucket_file_two"
|
||||
assert_success
|
||||
}
|
||||
|
||||
|
||||
@@ -14,15 +14,17 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
load ./bats-support/load
|
||||
load ./bats-assert/load
|
||||
|
||||
source ./tests/setup.sh
|
||||
source ./tests/util.sh
|
||||
source ./tests/util_aws.sh
|
||||
source ./tests/util_create_bucket.sh
|
||||
source ./tests/util_file.sh
|
||||
source ./tests/util_lock_config.sh
|
||||
source ./tests/util_tags.sh
|
||||
source ./tests/util_users.sh
|
||||
source ./tests/test_aws_root_inner.sh
|
||||
source ./tests/util/util_create_bucket.sh
|
||||
source ./tests/util/util_file.sh
|
||||
source ./tests/util/util_lock_config.sh
|
||||
source ./tests/util/util_object.sh
|
||||
source ./tests/util/util_tags.sh
|
||||
source ./tests/util/util_users.sh
|
||||
source ./tests/test_s3api_root_inner.sh
|
||||
source ./tests/test_common.sh
|
||||
source ./tests/test_common_acl.sh
|
||||
source ./tests/commands/copy_object.sh
|
||||
@@ -56,87 +58,50 @@ export RUN_USERS=true
|
||||
}
|
||||
|
||||
@test "test_copy_object_empty" {
|
||||
copy_object_empty || fail "copy objects with no parameters test failure"
|
||||
run copy_object_empty
|
||||
assert_success
|
||||
}
|
||||
|
||||
# delete-object - tested with bucket cleanup before or after tests
|
||||
|
||||
# delete-object-tagging
|
||||
@test "test_delete_object_tagging" {
|
||||
test_common_delete_object_tagging "aws"
|
||||
test_common_delete_object_tagging "s3api"
|
||||
}
|
||||
|
||||
# delete-objects
|
||||
@test "test_delete_objects" {
|
||||
if [ "$RECREATE_BUCKETS" == "false" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/888"
|
||||
skip "https://github.com/versity/versitygw/issues/1029"
|
||||
fi
|
||||
test_delete_objects_aws_root
|
||||
test_delete_objects_s3api_root
|
||||
}
|
||||
|
||||
# get-object
|
||||
@test "test_get_object_full_range" {
|
||||
test_get_object_full_range_aws_root
|
||||
test_get_object_full_range_s3api_root
|
||||
}
|
||||
|
||||
@test "test_get_object_invalid_range" {
|
||||
test_get_object_invalid_range_aws_root
|
||||
test_get_object_invalid_range_s3api_root
|
||||
}
|
||||
|
||||
# get-object-attributes
|
||||
@test "test_get_object_attributes" {
|
||||
test_get_object_attributes_aws_root
|
||||
test_get_object_attributes_s3api_root
|
||||
}
|
||||
|
||||
@test "test_put_object" {
|
||||
test_put_object_aws_root
|
||||
@test "test_get_put_object_legal_hold" {
|
||||
test_get_put_object_legal_hold_s3api_root
|
||||
}
|
||||
|
||||
# test adding and removing an object on versitygw
|
||||
@test "test_put_object_with_data" {
|
||||
if [ "$RECREATE_BUCKETS" == "false" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/888"
|
||||
fi
|
||||
test_common_put_object_with_data "aws"
|
||||
}
|
||||
|
||||
@test "test_put_object_no_data" {
|
||||
if [ "$RECREATE_BUCKETS" == "false" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/888"
|
||||
fi
|
||||
test_common_put_object_no_data "aws"
|
||||
@test "test_get_put_object_retention" {
|
||||
test_get_put_object_retention_s3api_root
|
||||
}
|
||||
|
||||
# test listing a bucket's objects on versitygw
|
||||
@test "test_list_objects" {
|
||||
test_common_list_objects "aws"
|
||||
}
|
||||
|
||||
@test "test_get_put_object_legal_hold" {
|
||||
test_get_put_object_legal_hold_aws_root
|
||||
}
|
||||
|
||||
@test "test_get_put_object_retention" {
|
||||
test_get_put_object_retention_aws_root
|
||||
}
|
||||
|
||||
# test v1 s3api list objects command
|
||||
@test "test-s3api-list-objects-v1" {
|
||||
test_s3api_list_objects_v1_aws_root
|
||||
}
|
||||
|
||||
# test v2 s3api list objects command
|
||||
@test "test-s3api-list-objects-v2" {
|
||||
test_s3api_list_objects_v2_aws_root
|
||||
}
|
||||
|
||||
# test abilty to set and retrieve object tags
|
||||
@test "test-set-get-object-tags" {
|
||||
test_common_set_get_object_tags "aws"
|
||||
}
|
||||
|
||||
@test "test-presigned-url-utf8-chars" {
|
||||
test_common_presigned_url_utf8_chars "aws"
|
||||
test_common_list_objects "s3api"
|
||||
}
|
||||
|
||||
@test "test-list-objects-delimiter" {
|
||||
@@ -149,67 +114,37 @@ export RUN_USERS=true
|
||||
run create_test_file "$folder_name"/"$object_name"
|
||||
assert_success
|
||||
|
||||
run setup_bucket "aws" "$BUCKET_ONE_NAME"
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run put_object "aws" "$TEST_FILE_FOLDER/$folder_name/$object_name" "$BUCKET_ONE_NAME" "$folder_name/$object_name"
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$folder_name/$object_name" "$BUCKET_ONE_NAME" "$folder_name/$object_name"
|
||||
assert_success
|
||||
|
||||
run check_object_listing_with_prefixes "$BUCKET_ONE_NAME" "$folder_name" "$object_name"
|
||||
assert_success
|
||||
}
|
||||
|
||||
# ensure that lists of files greater than a size of 1000 (pagination) are returned properly
|
||||
#@test "test_list_objects_file_count" {
|
||||
# test_common_list_objects_file_count "aws"
|
||||
#}
|
||||
|
||||
# ensure that lists of files greater than a size of 1000 (pagination) are returned properly
|
||||
#@test "test_list_objects_file_count" {
|
||||
# test_common_list_objects_file_count "aws"
|
||||
#}
|
||||
|
||||
#@test "test_filename_length" {
|
||||
# file_name=$(printf "%0.sa" $(seq 1 1025))
|
||||
# echo "$file_name"
|
||||
|
||||
# create_test_files "$file_name" || created=$?
|
||||
# [[ $created -eq 0 ]] || fail "error creating file"
|
||||
|
||||
# setup_bucket "aws" "$BUCKET_ONE_NAME" || local setup_result=$?
|
||||
# [[ $setup_result -eq 0 ]] || fail "error setting up bucket"
|
||||
|
||||
# put_object "aws" "$TEST_FILE_FOLDER"/"$file_name" "$BUCKET_ONE_NAME"/"$file_name" || local put_object=$?
|
||||
# [[ $put_object -eq 0 ]] || fail "Failed to add object to bucket"
|
||||
#}
|
||||
|
||||
@test "test_retention_bypass" {
|
||||
test_retention_bypass_aws_root
|
||||
@test "test_put_object" {
|
||||
test_put_object_s3api_root
|
||||
}
|
||||
|
||||
@test "test_add_object_metadata" {
|
||||
object_one="object-one"
|
||||
test_key="x-test-data"
|
||||
test_value="test-value"
|
||||
# test adding and removing an object on versitygw
|
||||
@test "test_put_object_with_data" {
|
||||
if [ "$RECREATE_BUCKETS" == "false" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/1029"
|
||||
fi
|
||||
test_common_put_object_with_data "s3api"
|
||||
}
|
||||
|
||||
run create_test_files "$object_one"
|
||||
assert_success
|
||||
@test "test_put_object_no_data" {
|
||||
if [ "$RECREATE_BUCKETS" == "false" ]; then
|
||||
skip "https://github.com/versity/versitygw/issues/1029"
|
||||
fi
|
||||
test_common_put_object_no_data "s3api"
|
||||
}
|
||||
|
||||
run setup_bucket "aws" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
object="$TEST_FILE_FOLDER"/"$object_one"
|
||||
put_object_with_metadata "aws" "$object" "$BUCKET_ONE_NAME" "$object_one" "$test_key" "$test_value" || fail "failed to add object to bucket"
|
||||
object_exists "aws" "$BUCKET_ONE_NAME" "$object_one" || fail "object not found after being added to bucket"
|
||||
|
||||
get_object_metadata "aws" "$BUCKET_ONE_NAME" "$object_one" || fail "error getting object metadata"
|
||||
key=$(echo "$metadata" | jq -r 'keys[]' 2>&1) || fail "error getting key from metadata: $key"
|
||||
value=$(echo "$metadata" | jq -r '.[]' 2>&1) || fail "error getting value from metadata: $value"
|
||||
[[ $key == "$test_key" ]] || fail "keys doesn't match (expected $key, actual \"$test_key\")"
|
||||
[[ $value == "$test_value" ]] || fail "values doesn't match (expected $value, actual \"$test_value\")"
|
||||
|
||||
bucket_cleanup "aws" "$BUCKET_ONE_NAME"
|
||||
delete_test_files "$object_one"
|
||||
@test "test-presigned-url-utf8-chars" {
|
||||
test_common_presigned_url_utf8_chars "s3api"
|
||||
}
|
||||
|
||||
@test "test_put_object_lock_configuration" {
|
||||
@@ -229,11 +164,126 @@ export RUN_USERS=true
|
||||
|
||||
run get_and_check_object_lock_config "$bucket_name" "$enabled" "$governance" "$days"
|
||||
assert_success "error getting and checking object lock config"
|
||||
|
||||
bucket_cleanup "aws" "$bucket_name"
|
||||
}
|
||||
|
||||
@test "test_put_object_metadata" {
|
||||
object_one="object-one"
|
||||
test_key="x-test-data"
|
||||
test_value="test-value"
|
||||
|
||||
run create_test_files "$object_one"
|
||||
assert_success
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
object="$TEST_FILE_FOLDER"/"$object_one"
|
||||
run put_object_with_metadata "s3api" "$object" "$BUCKET_ONE_NAME" "$object_one" "$test_key" "$test_value"
|
||||
assert_success
|
||||
|
||||
run object_exists "s3api" "$BUCKET_ONE_NAME" "$object_one"
|
||||
assert_success
|
||||
|
||||
run get_object_metadata_and_check_keys "$BUCKET_ONE_NAME" "$object_one" "$test_key" "$test_value"
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "test_retention_bypass" {
|
||||
test_retention_bypass_s3api_root
|
||||
}
|
||||
|
||||
# test v1 s3api list objects command
|
||||
@test "test-s3api-list-objects-v1" {
|
||||
test_s3api_list_objects_v1_s3api_root
|
||||
}
|
||||
|
||||
# test v2 s3api list objects command
|
||||
@test "test-s3api-list-objects-v2" {
|
||||
test_s3api_list_objects_v2_s3api_root
|
||||
}
|
||||
|
||||
# test abilty to set and retrieve object tags
|
||||
@test "test-set-get-object-tags" {
|
||||
test_common_set_get_object_tags "s3api"
|
||||
}
|
||||
|
||||
# ensure that lists of files greater than a size of 1000 (pagination) are returned properly
|
||||
#@test "test_list_objects_file_count" {
|
||||
# test_common_list_objects_file_count "s3api"
|
||||
#}
|
||||
|
||||
# ensure that lists of files greater than a size of 1000 (pagination) are returned properly
|
||||
#@test "test_list_objects_file_count" {
|
||||
# test_common_list_objects_file_count "s3api"
|
||||
#}
|
||||
|
||||
#@test "test_filename_length" {
|
||||
# file_name=$(printf "%0.sa" $(seq 1 1025))
|
||||
# echo "$file_name"
|
||||
|
||||
# create_test_files "$file_name" || created=$?
|
||||
# [[ $created -eq 0 ]] || fail "error creating file"
|
||||
|
||||
# setup_bucket "s3api" "$BUCKET_ONE_NAME" || local setup_result=$?
|
||||
# [[ $setup_result -eq 0 ]] || fail "error setting up bucket"
|
||||
|
||||
# put_object "s3api" "$TEST_FILE_FOLDER"/"$file_name" "$BUCKET_ONE_NAME"/"$file_name" || local put_object=$?
|
||||
# [[ $put_object -eq 0 ]] || fail "Failed to add object to bucket"
|
||||
#}
|
||||
|
||||
|
||||
@test "test_ls_directory_object" {
|
||||
test_common_ls_directory_object "s3api"
|
||||
}
|
||||
|
||||
@test "directory objects can't contain data" {
|
||||
if [ "$DIRECT" == "true" ]; then
|
||||
skip
|
||||
fi
|
||||
test_file="a"
|
||||
|
||||
run create_test_file "$test_file"
|
||||
assert_success
|
||||
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file/"
|
||||
assert_failure
|
||||
assert_output -p "Directory object contains data payload"
|
||||
}
|
||||
|
||||
#@test "objects containing data can't be copied to directory objects" {
|
||||
# # TODO finish test after https://github.com/versity/versitygw/issues/1021
|
||||
# skip "https://github.com/versity/versitygw/issues/1021"
|
||||
# test_file="a"
|
||||
#
|
||||
# run create_test_file "$test_file" 0
|
||||
# assert_success
|
||||
#
|
||||
# run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
# assert_success
|
||||
#
|
||||
# run put_object "s3api" "$TEST_FILE_FOLDER/$test_file" "$BUCKET_ONE_NAME" "$test_file"
|
||||
# assert_success
|
||||
#
|
||||
# if ! result=$(COMMAND_LOG="$COMMAND_LOG" BUCKET_NAME="$BUCKET_ONE_NAME" OBJECT_KEY="$test_file/" COPY_SOURCE="$BUCKET_ONE_NAME/$test_file" OUTPUT_FILE="$TEST_FILE_FOLDER/result.txt" ./tests/rest_scripts/copy_object.sh); then
|
||||
# log 2 "error listing multipart upload parts: $result"
|
||||
# return 1
|
||||
# fi
|
||||
# if [ "$result" != "400" ]; then
|
||||
# log 2 "response code '$result': $(cat "$TEST_FILE_FOLDER/result.txt")"
|
||||
# return 1
|
||||
# fi
|
||||
# return 0
|
||||
#}
|
||||
|
||||
@test "directory object - create multipart upload" {
|
||||
run setup_bucket "s3api" "$BUCKET_ONE_NAME"
|
||||
assert_success
|
||||
|
||||
run create_multipart_upload "$BUCKET_ONE_NAME" "test_file/"
|
||||
assert_failure
|
||||
assert_output -p "Directory object contains data payload"
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user