github.com/minio/console@v1.4.1/api/client.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"path"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/minio/minio-go/v7/pkg/replication"
    29  	"github.com/minio/minio-go/v7/pkg/sse"
    30  	xnet "github.com/minio/pkg/v3/net"
    31  
    32  	"github.com/minio/console/models"
    33  	"github.com/minio/console/pkg"
    34  	"github.com/minio/console/pkg/auth"
    35  	"github.com/minio/console/pkg/auth/ldap"
    36  	xjwt "github.com/minio/console/pkg/auth/token"
    37  	mc "github.com/minio/mc/cmd"
    38  	"github.com/minio/mc/pkg/probe"
    39  	"github.com/minio/minio-go/v7"
    40  	"github.com/minio/minio-go/v7/pkg/credentials"
    41  	"github.com/minio/minio-go/v7/pkg/lifecycle"
    42  	"github.com/minio/minio-go/v7/pkg/notification"
    43  	"github.com/minio/minio-go/v7/pkg/tags"
    44  )
    45  
    46  func init() {
    47  	// All minio-go API operations shall be performed only once,
    48  	// another way to look at this is we are turning off retries.
    49  	minio.MaxRetry = 1
    50  }
    51  
    52  // MinioClient interface with all functions to be implemented
    53  // by mock when testing, it should include all MinioClient respective api calls
    54  // that are used within this project.
    55  type MinioClient interface {
    56  	listBucketsWithContext(ctx context.Context) ([]minio.BucketInfo, error)
    57  	makeBucketWithContext(ctx context.Context, bucketName, location string, objectLocking bool) error
    58  	setBucketPolicyWithContext(ctx context.Context, bucketName, policy string) error
    59  	removeBucket(ctx context.Context, bucketName string) error
    60  	getBucketNotification(ctx context.Context, bucketName string) (config notification.Configuration, err error)
    61  	getBucketPolicy(ctx context.Context, bucketName string) (string, error)
    62  	listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
    63  	getObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error)
    64  	getObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error)
    65  	putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error)
    66  	putObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error
    67  	putObjectRetention(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error
    68  	statObject(ctx context.Context, bucketName, prefix string, opts minio.GetObjectOptions) (objectInfo minio.ObjectInfo, err error)
    69  	setBucketEncryption(ctx context.Context, bucketName string, config *sse.Configuration) error
    70  	removeBucketEncryption(ctx context.Context, bucketName string) error
    71  	getBucketEncryption(ctx context.Context, bucketName string) (*sse.Configuration, error)
    72  	putObjectTagging(ctx context.Context, bucketName, objectName string, otags *tags.Tags, opts minio.PutObjectTaggingOptions) error
    73  	getObjectTagging(ctx context.Context, bucketName, objectName string, opts minio.GetObjectTaggingOptions) (*tags.Tags, error)
    74  	setObjectLockConfig(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error
    75  	getBucketObjectLockConfig(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error)
    76  	getObjectLockConfig(ctx context.Context, bucketName string) (lock string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error)
    77  	getLifecycleRules(ctx context.Context, bucketName string) (lifecycle *lifecycle.Configuration, err error)
    78  	setBucketLifecycle(ctx context.Context, bucketName string, config *lifecycle.Configuration) error
    79  	copyObject(ctx context.Context, dst minio.CopyDestOptions, src minio.CopySrcOptions) (minio.UploadInfo, error)
    80  	GetBucketTagging(ctx context.Context, bucketName string) (*tags.Tags, error)
    81  	SetBucketTagging(ctx context.Context, bucketName string, tags *tags.Tags) error
    82  	RemoveBucketTagging(ctx context.Context, bucketName string) error
    83  }
    84  
    85  // Interface implementation
    86  //
    87  // Define the structure of a minIO Client and define the functions that are actually used
    88  // from minIO api.
    89  type minioClient struct {
    90  	client *minio.Client
    91  }
    92  
    93  func (c minioClient) GetBucketTagging(ctx context.Context, bucketName string) (*tags.Tags, error) {
    94  	return c.client.GetBucketTagging(ctx, bucketName)
    95  }
    96  
    97  func (c minioClient) SetBucketTagging(ctx context.Context, bucketName string, tags *tags.Tags) error {
    98  	return c.client.SetBucketTagging(ctx, bucketName, tags)
    99  }
   100  
   101  func (c minioClient) RemoveBucketTagging(ctx context.Context, bucketName string) error {
   102  	return c.client.RemoveBucketTagging(ctx, bucketName)
   103  }
   104  
   105  // implements minio.ListBuckets(ctx)
   106  func (c minioClient) listBucketsWithContext(ctx context.Context) ([]minio.BucketInfo, error) {
   107  	return c.client.ListBuckets(ctx)
   108  }
   109  
   110  // implements minio.MakeBucketWithContext(ctx, bucketName, location, objectLocking)
   111  func (c minioClient) makeBucketWithContext(ctx context.Context, bucketName, location string, objectLocking bool) error {
   112  	return c.client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{
   113  		Region:        location,
   114  		ObjectLocking: objectLocking,
   115  	})
   116  }
   117  
   118  // implements minio.SetBucketPolicyWithContext(ctx, bucketName, policy)
   119  func (c minioClient) setBucketPolicyWithContext(ctx context.Context, bucketName, policy string) error {
   120  	return c.client.SetBucketPolicy(ctx, bucketName, policy)
   121  }
   122  
   123  // implements minio.RemoveBucket(bucketName)
   124  func (c minioClient) removeBucket(ctx context.Context, bucketName string) error {
   125  	return c.client.RemoveBucket(ctx, bucketName)
   126  }
   127  
   128  // implements minio.GetBucketNotification(bucketName)
   129  func (c minioClient) getBucketNotification(ctx context.Context, bucketName string) (config notification.Configuration, err error) {
   130  	return c.client.GetBucketNotification(ctx, bucketName)
   131  }
   132  
   133  // implements minio.GetBucketPolicy(bucketName)
   134  func (c minioClient) getBucketPolicy(ctx context.Context, bucketName string) (string, error) {
   135  	return c.client.GetBucketPolicy(ctx, bucketName)
   136  }
   137  
   138  // implements minio.getBucketVersioning(ctx, bucketName)
   139  func (c minioClient) getBucketVersioning(ctx context.Context, bucketName string) (minio.BucketVersioningConfiguration, error) {
   140  	return c.client.GetBucketVersioning(ctx, bucketName)
   141  }
   142  
   143  // implements minio.getBucketVersioning(ctx, bucketName)
   144  func (c minioClient) getBucketReplication(ctx context.Context, bucketName string) (replication.Config, error) {
   145  	return c.client.GetBucketReplication(ctx, bucketName)
   146  }
   147  
   148  // implements minio.listObjects(ctx)
   149  func (c minioClient) listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
   150  	return c.client.ListObjects(ctx, bucket, opts)
   151  }
   152  
   153  func (c minioClient) getObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) {
   154  	return c.client.GetObjectRetention(ctx, bucketName, objectName, versionID)
   155  }
   156  
   157  func (c minioClient) getObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) {
   158  	return c.client.GetObjectLegalHold(ctx, bucketName, objectName, opts)
   159  }
   160  
   161  func (c minioClient) putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) {
   162  	return c.client.PutObject(ctx, bucketName, objectName, reader, objectSize, opts)
   163  }
   164  
   165  func (c minioClient) putObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error {
   166  	return c.client.PutObjectLegalHold(ctx, bucketName, objectName, opts)
   167  }
   168  
   169  func (c minioClient) putObjectRetention(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error {
   170  	return c.client.PutObjectRetention(ctx, bucketName, objectName, opts)
   171  }
   172  
   173  func (c minioClient) statObject(ctx context.Context, bucketName, prefix string, opts minio.GetObjectOptions) (objectInfo minio.ObjectInfo, err error) {
   174  	return c.client.StatObject(ctx, bucketName, prefix, opts)
   175  }
   176  
   177  // implements minio.SetBucketEncryption(ctx, bucketName, config)
   178  func (c minioClient) setBucketEncryption(ctx context.Context, bucketName string, config *sse.Configuration) error {
   179  	return c.client.SetBucketEncryption(ctx, bucketName, config)
   180  }
   181  
   182  // implements minio.RemoveBucketEncryption(ctx, bucketName)
   183  func (c minioClient) removeBucketEncryption(ctx context.Context, bucketName string) error {
   184  	return c.client.RemoveBucketEncryption(ctx, bucketName)
   185  }
   186  
   187  // implements minio.GetBucketEncryption(ctx, bucketName, config)
   188  func (c minioClient) getBucketEncryption(ctx context.Context, bucketName string) (*sse.Configuration, error) {
   189  	return c.client.GetBucketEncryption(ctx, bucketName)
   190  }
   191  
   192  func (c minioClient) putObjectTagging(ctx context.Context, bucketName, objectName string, otags *tags.Tags, opts minio.PutObjectTaggingOptions) error {
   193  	return c.client.PutObjectTagging(ctx, bucketName, objectName, otags, opts)
   194  }
   195  
   196  func (c minioClient) getObjectTagging(ctx context.Context, bucketName, objectName string, opts minio.GetObjectTaggingOptions) (*tags.Tags, error) {
   197  	return c.client.GetObjectTagging(ctx, bucketName, objectName, opts)
   198  }
   199  
   200  func (c minioClient) setObjectLockConfig(ctx context.Context, bucketName string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit) error {
   201  	return c.client.SetObjectLockConfig(ctx, bucketName, mode, validity, unit)
   202  }
   203  
   204  func (c minioClient) getBucketObjectLockConfig(ctx context.Context, bucketName string) (mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) {
   205  	return c.client.GetBucketObjectLockConfig(ctx, bucketName)
   206  }
   207  
   208  func (c minioClient) getObjectLockConfig(ctx context.Context, bucketName string) (lock string, mode *minio.RetentionMode, validity *uint, unit *minio.ValidityUnit, err error) {
   209  	return c.client.GetObjectLockConfig(ctx, bucketName)
   210  }
   211  
   212  func (c minioClient) getLifecycleRules(ctx context.Context, bucketName string) (lifecycle *lifecycle.Configuration, err error) {
   213  	return c.client.GetBucketLifecycle(ctx, bucketName)
   214  }
   215  
   216  func (c minioClient) setBucketLifecycle(ctx context.Context, bucketName string, config *lifecycle.Configuration) error {
   217  	return c.client.SetBucketLifecycle(ctx, bucketName, config)
   218  }
   219  
   220  func (c minioClient) copyObject(ctx context.Context, dst minio.CopyDestOptions, src minio.CopySrcOptions) (minio.UploadInfo, error) {
   221  	return c.client.CopyObject(ctx, dst, src)
   222  }
   223  
   224  // MCClient interface with all functions to be implemented
   225  // by mock when testing, it should include all mc/S3Client respective api calls
   226  // that are used within this project.
   227  type MCClient interface {
   228  	addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error
   229  	removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error
   230  	watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error)
   231  	remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult
   232  	list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent
   233  	get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error)
   234  	shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error)
   235  	setVersioning(ctx context.Context, status string, excludePrefix []string, excludeFolders bool) *probe.Error
   236  }
   237  
   238  // Interface implementation
   239  //
   240  // Define the structure of a mc S3Client and define the functions that are actually used
   241  // from mcS3client api.
   242  type mcClient struct {
   243  	client *mc.S3Client
   244  }
   245  
   246  // implements S3Client.AddNotificationConfig()
   247  func (c mcClient) addNotificationConfig(ctx context.Context, arn string, events []string, prefix, suffix string, ignoreExisting bool) *probe.Error {
   248  	return c.client.AddNotificationConfig(ctx, arn, events, prefix, suffix, ignoreExisting)
   249  }
   250  
   251  // implements S3Client.RemoveNotificationConfig()
   252  func (c mcClient) removeNotificationConfig(ctx context.Context, arn string, event string, prefix string, suffix string) *probe.Error {
   253  	return c.client.RemoveNotificationConfig(ctx, arn, event, prefix, suffix)
   254  }
   255  
   256  func (c mcClient) watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
   257  	return c.client.Watch(ctx, options)
   258  }
   259  
   260  func (c mcClient) setReplication(ctx context.Context, cfg *replication.Config, opts replication.Options) *probe.Error {
   261  	return c.client.SetReplication(ctx, cfg, opts)
   262  }
   263  
   264  func (c mcClient) deleteAllReplicationRules(ctx context.Context) *probe.Error {
   265  	return c.client.RemoveReplication(ctx)
   266  }
   267  
   268  func (c mcClient) setVersioning(ctx context.Context, status string, excludePrefix []string, excludeFolders bool) *probe.Error {
   269  	return c.client.SetVersion(ctx, status, excludePrefix, excludeFolders)
   270  }
   271  
   272  func (c mcClient) remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult {
   273  	return c.client.Remove(ctx, isIncomplete, isRemoveBucket, isBypass, forceDelete, contentCh)
   274  }
   275  
   276  func (c mcClient) list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent {
   277  	return c.client.List(ctx, opts)
   278  }
   279  
   280  func (c mcClient) get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error) {
   281  	rd, _, err := c.client.Get(ctx, opts)
   282  	return rd, err
   283  }
   284  
   285  func (c mcClient) shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) {
   286  	return c.client.ShareDownload(ctx, versionID, expires)
   287  }
   288  
   289  // ConsoleCredentialsI interface with all functions to be implemented
   290  // by mock when testing, it should include all needed consoleCredentials.Login api calls
   291  // that are used within this project.
   292  type ConsoleCredentialsI interface {
   293  	Get() (credentials.Value, error)
   294  	Expire()
   295  	GetAccountAccessKey() string
   296  }
   297  
   298  // Interface implementation
   299  type ConsoleCredentials struct {
   300  	ConsoleCredentials *credentials.Credentials
   301  	AccountAccessKey   string
   302  }
   303  
   304  func (c ConsoleCredentials) GetAccountAccessKey() string {
   305  	return c.AccountAccessKey
   306  }
   307  
   308  // Get implements *Login.Get()
   309  func (c ConsoleCredentials) Get() (credentials.Value, error) {
   310  	return c.ConsoleCredentials.Get()
   311  }
   312  
   313  // Expire implements *Login.Expire()
   314  func (c ConsoleCredentials) Expire() {
   315  	c.ConsoleCredentials.Expire()
   316  }
   317  
   318  // consoleSTSAssumeRole it's a STSAssumeRole wrapper, in general
   319  // there's no need to use this struct anywhere else in the project, it's only required
   320  // for passing a custom *http.Client to *credentials.STSAssumeRole
   321  type consoleSTSAssumeRole struct {
   322  	stsAssumeRole *credentials.STSAssumeRole
   323  }
   324  
   325  func (s consoleSTSAssumeRole) Retrieve() (credentials.Value, error) {
   326  	return s.stsAssumeRole.Retrieve()
   327  }
   328  
   329  func (s consoleSTSAssumeRole) IsExpired() bool {
   330  	return s.stsAssumeRole.IsExpired()
   331  }
   332  
   333  func stsCredentials(minioURL, accessKey, secretKey, location, clientIP string) (*credentials.Credentials, error) {
   334  	if accessKey == "" || secretKey == "" {
   335  		return nil, errors.New("credentials endpoint, access and secret key are mandatory for AssumeRoleSTS")
   336  	}
   337  	opts := credentials.STSAssumeRoleOptions{
   338  		AccessKey:       accessKey,
   339  		SecretKey:       secretKey,
   340  		Location:        location,
   341  		DurationSeconds: int(xjwt.GetConsoleSTSDuration().Seconds()),
   342  	}
   343  	stsAssumeRole := &credentials.STSAssumeRole{
   344  		Client:      GetConsoleHTTPClient(clientIP),
   345  		STSEndpoint: minioURL,
   346  		Options:     opts,
   347  	}
   348  	consoleSTSWrapper := consoleSTSAssumeRole{stsAssumeRole: stsAssumeRole}
   349  	return credentials.New(consoleSTSWrapper), nil
   350  }
   351  
   352  func NewConsoleCredentials(accessKey, secretKey, location, clientIP string) (*credentials.Credentials, error) {
   353  	minioURL := getMinIOServer()
   354  
   355  	// Future authentication methods can be added under this switch statement
   356  	switch {
   357  	// LDAP authentication for Console
   358  	case ldap.GetLDAPEnabled():
   359  		{
   360  			creds, err := auth.GetCredentialsFromLDAP(GetConsoleHTTPClient(clientIP), minioURL, accessKey, secretKey)
   361  			if err != nil {
   362  				return nil, err
   363  			}
   364  
   365  			// We verify if LDAP credentials are correct and no error is returned
   366  			_, err = creds.Get()
   367  
   368  			if err != nil && strings.Contains(strings.ToLower(err.Error()), "not found") {
   369  				// We try to use STS Credentials in case LDAP credentials are incorrect.
   370  				stsCreds, errSTS := stsCredentials(minioURL, accessKey, secretKey, location, clientIP)
   371  
   372  				// If there is an error with STS too, then we return the original LDAP error
   373  				if errSTS != nil {
   374  					LogError("error in STS credentials for LDAP case: %v ", errSTS)
   375  
   376  					// We return LDAP result
   377  					return creds, nil
   378  				}
   379  
   380  				_, err := stsCreds.Get()
   381  				// There is an error with STS credentials, We return the result of LDAP as STS is not a priority in this case.
   382  				if err != nil {
   383  					return creds, nil
   384  				}
   385  
   386  				return stsCreds, nil
   387  			}
   388  
   389  			return creds, nil
   390  		}
   391  	// default authentication for Console is via STS (Security Token Service) against MinIO
   392  	default:
   393  		{
   394  			return stsCredentials(minioURL, accessKey, secretKey, location, clientIP)
   395  		}
   396  	}
   397  }
   398  
   399  // getConsoleCredentialsFromSession returns the *consoleCredentials.Login associated to the
   400  // provided session token, this is useful for running the Expire() or IsExpired() operations
   401  func getConsoleCredentialsFromSession(claims *models.Principal) *credentials.Credentials {
   402  	if claims == nil {
   403  		return credentials.NewStaticV4("", "", "")
   404  	}
   405  	return credentials.NewStaticV4(claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken)
   406  }
   407  
   408  // newMinioClient creates a new MinIO client based on the ConsoleCredentials extracted
   409  // from the provided session token
   410  func newMinioClient(claims *models.Principal, clientIP string) (*minio.Client, error) {
   411  	creds := getConsoleCredentialsFromSession(claims)
   412  	endpoint := getMinIOEndpoint()
   413  	secure := getMinIOEndpointIsSecure()
   414  	minioClient, err := minio.New(endpoint, &minio.Options{
   415  		Creds:     creds,
   416  		Secure:    secure,
   417  		Transport: GetConsoleHTTPClient(clientIP).Transport,
   418  	})
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  	// set user-agent to differentiate Console UI requests for auditing.
   423  	minioClient.SetAppInfo("MinIO Console", pkg.Version)
   424  	return minioClient, nil
   425  }
   426  
   427  // computeObjectURLWithoutEncode returns a MinIO url containing the object filename without encoding
   428  func computeObjectURLWithoutEncode(bucketName, prefix string) (string, error) {
   429  	u, err := xnet.ParseHTTPURL(getMinIOServer())
   430  	if err != nil {
   431  		return "", fmt.Errorf("the provided endpoint: '%s' is invalid", getMinIOServer())
   432  	}
   433  	var p string
   434  	if strings.TrimSpace(bucketName) != "" {
   435  		p = path.Join(p, bucketName)
   436  	}
   437  	if strings.TrimSpace(prefix) != "" {
   438  		p = pathJoinFinalSlash(p, prefix)
   439  	}
   440  	return u.String() + "/" + p, nil
   441  }
   442  
   443  // newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
   444  func newS3BucketClient(claims *models.Principal, bucketName string, prefix string, clientIP string) (*mc.S3Client, error) {
   445  	if claims == nil {
   446  		return nil, fmt.Errorf("the provided credentials are invalid")
   447  	}
   448  	// It's very important to avoid encoding the prefix since the minio client will encode the path itself
   449  	objectURL, err := computeObjectURLWithoutEncode(bucketName, prefix)
   450  	if err != nil {
   451  		return nil, fmt.Errorf("the provided endpoint is invalid")
   452  	}
   453  	s3Config := newS3Config(objectURL, claims.STSAccessKeyID, claims.STSSecretAccessKey, claims.STSSessionToken, clientIP)
   454  	client, pErr := mc.S3New(s3Config)
   455  	if pErr != nil {
   456  		return nil, pErr.Cause
   457  	}
   458  	s3Client, ok := client.(*mc.S3Client)
   459  	if !ok {
   460  		return nil, fmt.Errorf("the provided url doesn't point to a S3 server")
   461  	}
   462  	return s3Client, nil
   463  }
   464  
   465  // pathJoinFinalSlash - like path.Join() but retains trailing slashSeparator of the last element
   466  func pathJoinFinalSlash(elem ...string) string {
   467  	if len(elem) > 0 {
   468  		if strings.HasSuffix(elem[len(elem)-1], SlashSeparator) {
   469  			return path.Join(elem...) + SlashSeparator
   470  		}
   471  	}
   472  	return path.Join(elem...)
   473  }
   474  
   475  // Deprecated
   476  // newS3Config simply creates a new Config struct using the passed
   477  // parameters.
   478  func newS3Config(endpoint, accessKey, secretKey, sessionToken string, clientIP string) *mc.Config {
   479  	// We have a valid alias and hostConfig. We populate the/
   480  	// consoleCredentials from the match found in the config file.
   481  	return &mc.Config{
   482  		HostURL:      endpoint,
   483  		AccessKey:    accessKey,
   484  		SecretKey:    secretKey,
   485  		SessionToken: sessionToken,
   486  		Signature:    "S3v4",
   487  		AppName:      globalAppName,
   488  		AppVersion:   pkg.Version,
   489  		Insecure:     isLocalIPEndpoint(endpoint),
   490  		Transport: &ConsoleTransport{
   491  			ClientIP:  clientIP,
   492  			Transport: GlobalTransport,
   493  		},
   494  	}
   495  }