From ef5a94420c832e7b2a1667ee9c4f40b4323d1166 Mon Sep 17 00:00:00 2001 From: jonaustin09 Date: Tue, 5 Dec 2023 13:47:48 -0500 Subject: [PATCH 1/2] feat: Created admin CLI actions in s3 proxy, Created iam proxy for proxy server --- auth/iam_proxy.go | 173 ++++++++++++++++++++++++++++++++++++++++++ backend/s3proxy/s3.go | 82 ++++++++++++++++++++ cmd/versitygw/main.go | 39 ++++++---- 3 files changed, 278 insertions(+), 16 deletions(-) create mode 100644 auth/iam_proxy.go diff --git a/auth/iam_proxy.go b/auth/iam_proxy.go new file mode 100644 index 0000000..cdb427a --- /dev/null +++ b/auth/iam_proxy.go @@ -0,0 +1,173 @@ +// Copyright 2023 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 ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" +) + +type IAMServiceProxy struct { + access string + secret string + region string + endpoint string +} + +var _ IAMService = &IAMServiceProxy{} + +func NewProxy(access, secret, region, endpoint string) IAMService { + return &IAMServiceProxy{ + access: access, + secret: secret, + region: region, + endpoint: endpoint, + } +} + +func (s *IAMServiceProxy) CreateAccount(account Account) error { + accJson, err := json.Marshal(account) + if err != nil { + return fmt.Errorf("failed to parse user data: %w", err) + } + + req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%v/create-user", s.endpoint), bytes.NewBuffer(accJson)) + if err != nil { + return fmt.Errorf("failed to send the request: %w", err) + } + + signer := v4.NewSigner() + + hashedPayload := sha256.Sum256(accJson) + hexPayload := hex.EncodeToString(hashedPayload[:]) + + req.Header.Set("X-Amz-Content-Sha256", hexPayload) + + signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.access, SecretAccessKey: s.secret}, req, hexPayload, "s3", s.region, time.Now()) + if signErr != nil { + return fmt.Errorf("failed to sign the request: %w", err) + } + + client := http.Client{} + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send the request: %w", err) + } + + if resp.StatusCode > 300 { + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + return fmt.Errorf(string(body)) + } + + return nil +} + +func (IAMServiceProxy) GetUserAccount(access string) (Account, error) { + return Account{}, nil +} + +func (s *IAMServiceProxy) DeleteUserAccount(access string) error { + req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%v/delete-user?access=%v", s.endpoint, access), nil) + if err != nil { + return fmt.Errorf("failed to send the request: %w", err) + } + + signer := v4.NewSigner() + + hashedPayload := sha256.Sum256([]byte{}) + hexPayload := hex.EncodeToString(hashedPayload[:]) + + req.Header.Set("X-Amz-Content-Sha256", hexPayload) + + signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.access, SecretAccessKey: s.secret}, req, hexPayload, "s3", s.region, time.Now()) + if signErr != nil { + return fmt.Errorf("failed to sign the request: %w", err) + } + + client := http.Client{} + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send the request: %w", err) + } + + if resp.StatusCode > 300 { + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + return fmt.Errorf(string(body)) + } + + return nil +} + +func (s *IAMServiceProxy) ListUserAccounts() ([]Account, error) { + req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%v/list-users", s.endpoint), nil) + if err != nil { + return []Account{}, fmt.Errorf("failed to send the request: %w", err) + } + + signer := v4.NewSigner() + + hashedPayload := sha256.Sum256([]byte{}) + hexPayload := hex.EncodeToString(hashedPayload[:]) + + req.Header.Set("X-Amz-Content-Sha256", hexPayload) + + signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.access, SecretAccessKey: s.secret}, req, hexPayload, "s3", s.region, time.Now()) + if signErr != nil { + return []Account{}, fmt.Errorf("failed to sign the request: %w", err) + } + + client := http.Client{} + + resp, err := client.Do(req) + if err != nil { + return []Account{}, fmt.Errorf("failed to send the request: %w", err) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return []Account{}, err + } + defer resp.Body.Close() + + var accs []Account + if err := json.Unmarshal(body, &accs); err != nil { + return []Account{}, err + } + + return accs, nil +} + +func (IAMServiceProxy) Shutdown() error { + return nil +} diff --git a/backend/s3proxy/s3.go b/backend/s3proxy/s3.go index f2ed7bc..30df572 100644 --- a/backend/s3proxy/s3.go +++ b/backend/s3proxy/s3.go @@ -16,12 +16,18 @@ package s3proxy import ( "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" "io" + "net/http" "strconv" + "time" + "github.com/aws/aws-sdk-go-v2/aws" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" @@ -535,6 +541,82 @@ func (s *S3be) DeleteObjectTagging(ctx context.Context, bucket, object string) e return handleError(err) } +func (s *S3be) ChangeBucketOwner(ctx context.Context, bucket, newOwner string) error { + req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%v/change-bucket-owner/?bucket=%v&owner=%v", s.endpoint, bucket, newOwner), nil) + if err != nil { + return fmt.Errorf("failed to send the request: %w", err) + } + + signer := v4.NewSigner() + + hashedPayload := sha256.Sum256([]byte{}) + hexPayload := hex.EncodeToString(hashedPayload[:]) + + req.Header.Set("X-Amz-Content-Sha256", hexPayload) + + signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.access, SecretAccessKey: s.secret}, req, hexPayload, "s3", s.awsRegion, time.Now()) + if signErr != nil { + return fmt.Errorf("failed to sign the request: %w", err) + } + + client := http.Client{} + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to send the request: %w", err) + } + + if resp.StatusCode > 300 { + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + return fmt.Errorf(string(body)) + } + + return nil +} + +func (s *S3be) ListBucketsAndOwners(ctx context.Context) ([]s3response.Bucket, error) { + req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%v/list-buckets", s.endpoint), nil) + if err != nil { + return []s3response.Bucket{}, fmt.Errorf("failed to send the request: %w", err) + } + + signer := v4.NewSigner() + + hashedPayload := sha256.Sum256([]byte{}) + hexPayload := hex.EncodeToString(hashedPayload[:]) + + req.Header.Set("X-Amz-Content-Sha256", hexPayload) + + signErr := signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.access, SecretAccessKey: s.secret}, req, hexPayload, "s3", s.awsRegion, time.Now()) + if signErr != nil { + return []s3response.Bucket{}, fmt.Errorf("failed to sign the request: %w", err) + } + + client := http.Client{} + + resp, err := client.Do(req) + if err != nil { + return []s3response.Bucket{}, fmt.Errorf("failed to send the request: %w", err) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return []s3response.Bucket{}, err + } + defer resp.Body.Close() + + var buckets []s3response.Bucket + if err := json.Unmarshal(body, &buckets); err != nil { + return []s3response.Bucket{}, err + } + + return buckets, nil +} + func handleError(err error) error { if err == nil { return nil diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index 0021609..34617bb 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -339,22 +339,29 @@ func runGateway(ctx context.Context, be backend.Backend) error { admOpts = append(admOpts, s3api.WithAdminSrvTLS(cert)) } - iam, err := auth.New(&auth.Opts{ - Dir: iamDir, - LDAPServerURL: ldapURL, - LDAPBindDN: ldapBindDN, - LDAPPassword: ldapPassword, - LDAPQueryBase: ldapQueryBase, - LDAPObjClasses: ldapObjClasses, - LDAPAccessAtr: ldapAccessAtr, - LDAPSecretAtr: ldapSecAtr, - LDAPRoleAtr: ldapRoleAtr, - CacheDisable: iamCacheDisable, - CacheTTL: iamCacheTTL, - CachePrune: iamCachePrune, - }) - if err != nil { - return fmt.Errorf("setup iam: %w", err) + var iam auth.IAMService + + if s3proxyEndpoint != "" { + iam = auth.NewProxy(s3proxyAccess, s3proxySecret, s3proxyRegion, s3proxyEndpoint) + } else { + var err error + iam, err = auth.New(&auth.Opts{ + Dir: iamDir, + LDAPServerURL: ldapURL, + LDAPBindDN: ldapBindDN, + LDAPPassword: ldapPassword, + LDAPQueryBase: ldapQueryBase, + LDAPObjClasses: ldapObjClasses, + LDAPAccessAtr: ldapAccessAtr, + LDAPSecretAtr: ldapSecAtr, + LDAPRoleAtr: ldapRoleAtr, + CacheDisable: iamCacheDisable, + CacheTTL: iamCacheTTL, + CachePrune: iamCachePrune, + }) + if err != nil { + return fmt.Errorf("setup iam: %w", err) + } } logger, err := s3log.InitLogger(&s3log.LogConfig{ From 056c905a6542c8d87f7fe360d46283a65e631922 Mon Sep 17 00:00:00 2001 From: jonaustin09 Date: Fri, 8 Dec 2023 11:55:33 -0500 Subject: [PATCH 2/2] fix: Closes #323, fixed s3 proxy single user PutBucketAcl issue --- auth/iam_proxy.go | 8 ++++++-- cmd/versitygw/main.go | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/auth/iam_proxy.go b/auth/iam_proxy.go index cdb427a..83ca8a1 100644 --- a/auth/iam_proxy.go +++ b/auth/iam_proxy.go @@ -88,8 +88,12 @@ func (s *IAMServiceProxy) CreateAccount(account Account) error { return nil } -func (IAMServiceProxy) GetUserAccount(access string) (Account, error) { - return Account{}, nil +func (s *IAMServiceProxy) GetUserAccount(access string) (Account, error) { + return Account{ + Access: s.access, + Secret: s.secret, + Role: "admin", + }, nil } func (s *IAMServiceProxy) DeleteUserAccount(access string) error { diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index 34617bb..79d0371 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -342,6 +342,8 @@ func runGateway(ctx context.Context, be backend.Backend) error { var iam auth.IAMService if s3proxyEndpoint != "" { + rootUserAccess = s3proxyAccess + rootUserSecret = s3proxySecret iam = auth.NewProxy(s3proxyAccess, s3proxySecret, s3proxyRegion, s3proxyEndpoint) } else { var err error