mirror of
https://github.com/versity/versitygw.git
synced 2026-07-02 16:54:25 +00:00
3e26175265
The latest version of the go sdk has deprecate the s3 manager in favor of the new transfermanager. This updates to the new transfermanager functionality for the two places we were using the manager, iam s3 and integration tests. This also removes the pinning of nats nkeys since they have fixed the tags in their repo now. And general cleanup of the go.mod.
306 lines
7.1 KiB
Go
306 lines
7.1 KiB
Go
// 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"
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/config"
|
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
|
"github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/aws/smithy-go"
|
|
"github.com/versity/versitygw/debuglogger"
|
|
)
|
|
|
|
// IAMServiceS3 stores user accounts in an S3 object
|
|
// The endpoint, credentials, bucket, and region are provided
|
|
// from cli configuration.
|
|
// The object format and name is the same as the internal IAM service:
|
|
// coming from iAMConfig and iamFile in iam_internal.
|
|
|
|
type IAMServiceS3 struct {
|
|
// This mutex will help with racing updates to the IAM data
|
|
// from multiple requests to this gateway instance, but
|
|
// will not help with racing updates to multiple load balanced
|
|
// gateway instances. This is a limitation of the internal
|
|
// IAM service. All account updates should be sent to a single
|
|
// gateway instance if possible.
|
|
sync.RWMutex
|
|
|
|
access string
|
|
secret string
|
|
region string
|
|
bucket string
|
|
endpoint string
|
|
sslSkipVerify bool
|
|
rootAcc Account
|
|
client *s3.Client
|
|
}
|
|
|
|
var _ IAMService = &IAMServiceS3{}
|
|
|
|
func NewS3(rootAcc Account, access, secret, region, bucket, endpoint string, sslSkipVerify bool) (*IAMServiceS3, error) {
|
|
if access == "" {
|
|
return nil, fmt.Errorf("must provide s3 IAM service access key")
|
|
}
|
|
if secret == "" {
|
|
return nil, fmt.Errorf("must provide s3 IAM service secret key")
|
|
}
|
|
if region == "" {
|
|
return nil, fmt.Errorf("must provide s3 IAM service region")
|
|
}
|
|
if bucket == "" {
|
|
return nil, fmt.Errorf("must provide s3 IAM service bucket")
|
|
}
|
|
if endpoint == "" {
|
|
return nil, fmt.Errorf("must provide s3 IAM service endpoint")
|
|
}
|
|
|
|
i := &IAMServiceS3{
|
|
access: access,
|
|
secret: secret,
|
|
region: region,
|
|
bucket: bucket,
|
|
endpoint: endpoint,
|
|
sslSkipVerify: sslSkipVerify,
|
|
rootAcc: rootAcc,
|
|
}
|
|
|
|
cfg, err := i.getConfig()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("init s3 IAM: %v", err)
|
|
}
|
|
|
|
if endpoint != "" {
|
|
i.client = s3.NewFromConfig(cfg, func(o *s3.Options) {
|
|
o.BaseEndpoint = &endpoint
|
|
})
|
|
return i, nil
|
|
}
|
|
|
|
i.client = s3.NewFromConfig(cfg)
|
|
return i, nil
|
|
}
|
|
|
|
func (s *IAMServiceS3) CreateAccount(account Account) error {
|
|
if s.rootAcc.Access == account.Access {
|
|
return ErrUserExists
|
|
}
|
|
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
conf, err := s.getAccounts()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, ok := conf.AccessAccounts[account.Access]
|
|
if ok {
|
|
return ErrUserExists
|
|
}
|
|
conf.AccessAccounts[account.Access] = account
|
|
|
|
return s.storeAccts(conf)
|
|
}
|
|
|
|
func (s *IAMServiceS3) GetUserAccount(access string) (Account, error) {
|
|
if access == s.rootAcc.Access {
|
|
return s.rootAcc, nil
|
|
}
|
|
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
|
|
conf, err := s.getAccounts()
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
|
|
acct, ok := conf.AccessAccounts[access]
|
|
if !ok {
|
|
return Account{}, ErrNoSuchUser
|
|
}
|
|
|
|
return acct, nil
|
|
}
|
|
|
|
func (s *IAMServiceS3) UpdateUserAccount(access string, props MutableProps) error {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
conf, err := s.getAccounts()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
acc, ok := conf.AccessAccounts[access]
|
|
if !ok {
|
|
return ErrNoSuchUser
|
|
}
|
|
|
|
updateAcc(&acc, props)
|
|
conf.AccessAccounts[access] = acc
|
|
|
|
return s.storeAccts(conf)
|
|
}
|
|
|
|
func (s *IAMServiceS3) DeleteUserAccount(access string) error {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
conf, err := s.getAccounts()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, ok := conf.AccessAccounts[access]
|
|
if !ok {
|
|
return fmt.Errorf("account does not exist")
|
|
}
|
|
delete(conf.AccessAccounts, access)
|
|
|
|
return s.storeAccts(conf)
|
|
}
|
|
|
|
func (s *IAMServiceS3) ListUserAccounts() ([]Account, error) {
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
|
|
conf, err := s.getAccounts()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keys := make([]string, 0, len(conf.AccessAccounts))
|
|
for k := range conf.AccessAccounts {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
var accs []Account
|
|
for _, k := range keys {
|
|
accs = append(accs, Account{
|
|
Access: k,
|
|
Secret: conf.AccessAccounts[k].Secret,
|
|
Role: conf.AccessAccounts[k].Role,
|
|
UserID: conf.AccessAccounts[k].UserID,
|
|
GroupID: conf.AccessAccounts[k].GroupID,
|
|
ProjectID: conf.AccessAccounts[k].ProjectID,
|
|
})
|
|
}
|
|
|
|
return accs, nil
|
|
}
|
|
|
|
func (s *IAMServiceS3) Shutdown() error {
|
|
return nil
|
|
}
|
|
|
|
func (s *IAMServiceS3) getConfig() (aws.Config, error) {
|
|
creds := credentials.NewStaticCredentialsProvider(s.access, s.secret, "")
|
|
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: s.sslSkipVerify},
|
|
}
|
|
client := &http.Client{Transport: tr}
|
|
|
|
opts := []func(*config.LoadOptions) error{
|
|
config.WithRegion(s.region),
|
|
config.WithCredentialsProvider(creds),
|
|
config.WithHTTPClient(client),
|
|
}
|
|
|
|
if debuglogger.IsIAMDebugEnabled() {
|
|
opts = append(opts,
|
|
config.WithClientLogMode(aws.LogSigning|aws.LogRetries|aws.LogRequest|aws.LogResponse|aws.LogRequestEventMessage|aws.LogResponseEventMessage))
|
|
}
|
|
|
|
return config.LoadDefaultConfig(context.Background(), opts...)
|
|
}
|
|
|
|
func (s *IAMServiceS3) getAccounts() (iAMConfig, error) {
|
|
obj := iamFile
|
|
|
|
out, err := s.client.GetObject(context.Background(), &s3.GetObjectInput{
|
|
Bucket: &s.bucket,
|
|
Key: &obj,
|
|
})
|
|
if err != nil {
|
|
// if the error is object not exists,
|
|
// init empty accounts struct and return that
|
|
var nsk *types.NoSuchKey
|
|
if errors.As(err, &nsk) {
|
|
return iAMConfig{AccessAccounts: map[string]Account{}}, nil
|
|
}
|
|
var apiErr smithy.APIError
|
|
if errors.As(err, &apiErr) {
|
|
if apiErr.ErrorCode() == "NotFound" {
|
|
return iAMConfig{AccessAccounts: map[string]Account{}}, nil
|
|
}
|
|
}
|
|
|
|
// all other errors, return the error
|
|
return iAMConfig{}, fmt.Errorf("get %v: %w", obj, err)
|
|
}
|
|
|
|
defer out.Body.Close()
|
|
|
|
b, err := io.ReadAll(out.Body)
|
|
if err != nil {
|
|
return iAMConfig{}, fmt.Errorf("read %v: %w", obj, err)
|
|
}
|
|
|
|
conf, err := parseIAM(b)
|
|
if err != nil {
|
|
return iAMConfig{}, fmt.Errorf("parse iam data: %w", err)
|
|
}
|
|
|
|
return conf, nil
|
|
}
|
|
|
|
func (s *IAMServiceS3) storeAccts(conf iAMConfig) error {
|
|
b, err := json.Marshal(conf)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to serialize iam: %w", err)
|
|
}
|
|
|
|
obj := iamFile
|
|
uploader := transfermanager.New(s.client)
|
|
upinfo := &transfermanager.UploadObjectInput{
|
|
Body: bytes.NewReader(b),
|
|
Bucket: &s.bucket,
|
|
Key: &obj,
|
|
}
|
|
_, err = uploader.UploadObject(context.Background(), upinfo)
|
|
if err != nil {
|
|
return fmt.Errorf("store accounts in %v: %w", iamFile, err)
|
|
}
|
|
|
|
return nil
|
|
}
|