S3: add support for session tokens

Fixes #736

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-02-25 15:30:04 +01:00
parent 4e9dae6fa4
commit e18ad55067
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
12 changed files with 48 additions and 8 deletions

View file

@ -42,6 +42,7 @@ var (
portableS3Region string
portableS3AccessKey string
portableS3AccessSecret string
portableS3SessionToken string
portableS3Endpoint string
portableS3StorageClass string
portableS3ACL string
@ -172,6 +173,7 @@ Please take a look at the usage below to customize the serving parameters`,
Bucket: portableS3Bucket,
Region: portableS3Region,
AccessKey: portableS3AccessKey,
SessionToken: portableS3SessionToken,
Endpoint: portableS3Endpoint,
StorageClass: portableS3StorageClass,
ACL: portableS3ACL,
@ -294,6 +296,7 @@ sftpfs => SFTP (legacy: 5)`)
portableCmd.Flags().StringVar(&portableS3Region, "s3-region", "", "")
portableCmd.Flags().StringVar(&portableS3AccessKey, "s3-access-key", "", "")
portableCmd.Flags().StringVar(&portableS3AccessSecret, "s3-access-secret", "", "")
portableCmd.Flags().StringVar(&portableS3SessionToken, "s3-session-token", "", "")
portableCmd.Flags().StringVar(&portableS3Endpoint, "s3-endpoint", "", "")
portableCmd.Flags().StringVar(&portableS3StorageClass, "s3-storage-class", "", "")
portableCmd.Flags().StringVar(&portableS3ACL, "s3-acl", "", "")

View file

@ -93,6 +93,7 @@ Flags:
virtual folder identified by this
prefix and its contents
--s3-region string
--s3-session-token string
--s3-storage-class string
--s3-upload-concurrency int How many parts are uploaded in
parallel (default 2)

2
go.mod
View file

@ -41,7 +41,7 @@ require (
github.com/rs/cors v1.8.2
github.com/rs/xid v1.3.0
github.com/rs/zerolog v1.26.2-0.20220203140311-fc26014bd4e1
github.com/sftpgo/sdk v0.1.1-0.20220225104414-9e485ac5bc94
github.com/sftpgo/sdk v0.1.1-0.20220225141305-cca7ba31466c
github.com/shirou/gopsutil/v3 v3.22.1
github.com/spf13/afero v1.8.1
github.com/spf13/cobra v1.3.0

4
go.sum
View file

@ -698,8 +698,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=
github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
github.com/sftpgo/sdk v0.1.1-0.20220225104414-9e485ac5bc94 h1:IllQqdyqETJdbik04oorF/oGwSkeY35RTPQjn/eQhO0=
github.com/sftpgo/sdk v0.1.1-0.20220225104414-9e485ac5bc94/go.mod h1:zqCRMcwS28IViwekJHNkFu4GqSfyVmOQTlh8h3icAXE=
github.com/sftpgo/sdk v0.1.1-0.20220225141305-cca7ba31466c h1:aSWi1VB6DXmPmscawueKEhoyMTZjsMTiRaWFfhHmB4Y=
github.com/sftpgo/sdk v0.1.1-0.20220225141305-cca7ba31466c/go.mod h1:zqCRMcwS28IViwekJHNkFu4GqSfyVmOQTlh8h3icAXE=
github.com/shirou/gopsutil/v3 v3.22.1 h1:33y31Q8J32+KstqPfscvFwBlNJ6xLaBy4xqBXzlYV5w=
github.com/shirou/gopsutil/v3 v3.22.1/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=

View file

@ -1787,6 +1787,7 @@ func TestUserRedactedPassword(t *testing.T) {
u.FsConfig.S3Config.Bucket = "b"
u.FsConfig.S3Config.Region = "eu-west-1"
u.FsConfig.S3Config.AccessKey = "access-key"
u.FsConfig.S3Config.SessionToken = "session token"
u.FsConfig.S3Config.AccessSecret = kms.NewSecret(sdkkms.SecretStatusRedacted, "access-secret", "", "")
u.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?k=m"
u.FsConfig.S3Config.StorageClass = "Standard"
@ -2564,6 +2565,7 @@ func TestUserS3Config(t *testing.T) {
user.FsConfig.S3Config.Region = "us-east-1" //nolint:goconst
user.FsConfig.S3Config.AccessKey = "Server-Access-Key"
user.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("Server-Access-Secret")
user.FsConfig.S3Config.SessionToken = "Session token"
user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000"
user.FsConfig.S3Config.UploadPartSize = 8
user.FsConfig.S3Config.DownloadPartMaxTime = 60
@ -15097,6 +15099,7 @@ func TestWebUserS3Mock(t *testing.T) {
user.FsConfig.S3Config.Region = "eu-west-1"
user.FsConfig.S3Config.AccessKey = "access-key"
user.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("access-secret")
user.FsConfig.S3Config.SessionToken = "new session token"
user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b"
user.FsConfig.S3Config.StorageClass = "Standard"
user.FsConfig.S3Config.KeyPrefix = "somedir/subdir/"
@ -15135,6 +15138,7 @@ func TestWebUserS3Mock(t *testing.T) {
form.Set("s3_region", user.FsConfig.S3Config.Region)
form.Set("s3_access_key", user.FsConfig.S3Config.AccessKey)
form.Set("s3_access_secret", user.FsConfig.S3Config.AccessSecret.GetPayload())
form.Set("s3_session_token", user.FsConfig.S3Config.SessionToken)
form.Set("s3_storage_class", user.FsConfig.S3Config.StorageClass)
form.Set("s3_acl", user.FsConfig.S3Config.ACL)
form.Set("s3_endpoint", user.FsConfig.S3Config.Endpoint)
@ -15224,6 +15228,7 @@ func TestWebUserS3Mock(t *testing.T) {
assert.Equal(t, updateUser.FsConfig.S3Config.Bucket, user.FsConfig.S3Config.Bucket)
assert.Equal(t, updateUser.FsConfig.S3Config.Region, user.FsConfig.S3Config.Region)
assert.Equal(t, updateUser.FsConfig.S3Config.AccessKey, user.FsConfig.S3Config.AccessKey)
assert.Equal(t, updateUser.FsConfig.S3Config.SessionToken, user.FsConfig.S3Config.SessionToken)
assert.Equal(t, updateUser.FsConfig.S3Config.StorageClass, user.FsConfig.S3Config.StorageClass)
assert.Equal(t, updateUser.FsConfig.S3Config.ACL, user.FsConfig.S3Config.ACL)
assert.Equal(t, updateUser.FsConfig.S3Config.Endpoint, user.FsConfig.S3Config.Endpoint)
@ -15924,6 +15929,7 @@ func TestS3WebFolderMock(t *testing.T) {
S3Region := "eu-west-1"
S3AccessKey := "access-key"
S3AccessSecret := kms.NewPlainSecret("folder-access-secret")
S3SessionToken := "fake session token"
S3Endpoint := "http://127.0.0.1:9000/path?b=c"
S3StorageClass := "Standard"
S3ACL := "public-read-write"
@ -15943,6 +15949,7 @@ func TestS3WebFolderMock(t *testing.T) {
form.Set("s3_region", S3Region)
form.Set("s3_access_key", S3AccessKey)
form.Set("s3_access_secret", S3AccessSecret.GetPayload())
form.Set("s3_session_token", S3SessionToken)
form.Set("s3_storage_class", S3StorageClass)
form.Set("s3_acl", S3ACL)
form.Set("s3_endpoint", S3Endpoint)
@ -15987,6 +15994,7 @@ func TestS3WebFolderMock(t *testing.T) {
assert.Equal(t, S3Bucket, folder.FsConfig.S3Config.Bucket)
assert.Equal(t, S3Region, folder.FsConfig.S3Config.Region)
assert.Equal(t, S3AccessKey, folder.FsConfig.S3Config.AccessKey)
assert.Equal(t, S3SessionToken, folder.FsConfig.S3Config.SessionToken)
assert.NotEmpty(t, folder.FsConfig.S3Config.AccessSecret.GetPayload())
assert.Equal(t, S3Endpoint, folder.FsConfig.S3Config.Endpoint)
assert.Equal(t, S3StorageClass, folder.FsConfig.S3Config.StorageClass)
@ -16035,6 +16043,7 @@ func TestS3WebFolderMock(t *testing.T) {
assert.Equal(t, S3Bucket, folder.FsConfig.S3Config.Bucket)
assert.Equal(t, S3Region, folder.FsConfig.S3Config.Region)
assert.Equal(t, S3AccessKey, folder.FsConfig.S3Config.AccessKey)
assert.Equal(t, S3SessionToken, folder.FsConfig.S3Config.SessionToken)
assert.NotEmpty(t, folder.FsConfig.S3Config.AccessSecret.GetPayload())
assert.Equal(t, S3Endpoint, folder.FsConfig.S3Config.Endpoint)
assert.Equal(t, S3StorageClass, folder.FsConfig.S3Config.StorageClass)

View file

@ -964,6 +964,7 @@ func getS3Config(r *http.Request) (vfs.S3FsConfig, error) {
config.Bucket = r.Form.Get("s3_bucket")
config.Region = r.Form.Get("s3_region")
config.AccessKey = r.Form.Get("s3_access_key")
config.SessionToken = strings.TrimSpace(r.Form.Get("s3_session_token"))
config.AccessSecret = getSecretFromFormField(r, "s3_access_secret")
config.Endpoint = r.Form.Get("s3_endpoint")
config.StorageClass = r.Form.Get("s3_storage_class")

View file

@ -1275,6 +1275,9 @@ func compareS3Config(expected *vfs.Filesystem, actual *vfs.Filesystem) error { /
if expected.S3Config.AccessKey != actual.S3Config.AccessKey {
return errors.New("fs S3 access key mismatch")
}
if expected.S3Config.SessionToken != actual.S3Config.SessionToken {
return errors.New("fs S3 session token mismatch")
}
if err := checkEncryptedSecret(expected.S3Config.AccessSecret, actual.S3Config.AccessSecret); err != nil {
return fmt.Errorf("fs S3 access secret mismatch: %v", err)
}

View file

@ -4701,6 +4701,8 @@ components:
type: string
access_secret:
$ref: '#/components/schemas/Secret'
session_token:
type: string
endpoint:
type: string
description: optional endpoint

View file

@ -166,6 +166,14 @@
</div>
</div>
<div class="form-group row fsconfig fsconfig-s3fs">
<label for="idS3SessionToken" class="col-sm-2 col-form-label">Session token</label>
<div class="col-sm-10">
<textarea class="form-control" id="idS3SessionToken" name="s3_session_token"
rows="3">{{.S3Config.SessionToken}}</textarea>
</div>
</div>
<div class="form-group fsconfig fsconfig-s3fs">
<div class="form-check">

View file

@ -244,6 +244,7 @@ func (f *Filesystem) GetACopy() Filesystem {
Bucket: f.S3Config.Bucket,
Region: f.S3Config.Region,
AccessKey: f.S3Config.AccessKey,
SessionToken: f.S3Config.SessionToken,
Endpoint: f.S3Config.Endpoint,
StorageClass: f.S3Config.StorageClass,
ACL: f.S3Config.ACL,

View file

@ -81,7 +81,8 @@ func NewS3Fs(connectionID, localTempDir, mountPath string, config S3FsConfig) (F
if err := fs.config.AccessSecret.TryDecrypt(); err != nil {
return fs, err
}
awsConfig.Credentials = credentials.NewStaticCredentials(fs.config.AccessKey, fs.config.AccessSecret.GetPayload(), "")
awsConfig.Credentials = credentials.NewStaticCredentials(fs.config.AccessKey,
fs.config.AccessSecret.GetPayload(), fs.config.SessionToken)
}
if fs.config.Endpoint != "" {

View file

@ -173,6 +173,9 @@ func (c *S3FsConfig) isEqual(other *S3FsConfig) bool {
if c.AccessKey != other.AccessKey {
return false
}
if c.SessionToken != other.SessionToken {
return false
}
if c.Endpoint != other.Endpoint {
return false
}
@ -182,6 +185,17 @@ func (c *S3FsConfig) isEqual(other *S3FsConfig) bool {
if c.ACL != other.ACL {
return false
}
if !c.areMultipartFieldsEqual(other) {
return false
}
if c.ForcePathStyle != other.ForcePathStyle {
return false
}
return c.isSecretEqual(other)
}
func (c *S3FsConfig) areMultipartFieldsEqual(other *S3FsConfig) bool {
if c.UploadPartSize != other.UploadPartSize {
return false
}
@ -200,10 +214,7 @@ func (c *S3FsConfig) isEqual(other *S3FsConfig) bool {
if c.UploadPartMaxTime != other.UploadPartMaxTime {
return false
}
if c.ForcePathStyle != other.ForcePathStyle {
return false
}
return c.isSecretEqual(other)
return true
}
func (c *S3FsConfig) isSecretEqual(other *S3FsConfig) bool {