mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2024-01-19 02:48:24 +00:00
Merge branch 'master' of https://github.com/seaweedfs/seaweedfs
This commit is contained in:
commit
b388957340
|
@ -18,10 +18,10 @@ bucket prefix = yournamehere-{random}-
|
||||||
|
|
||||||
[s3 main]
|
[s3 main]
|
||||||
# main display_name set in vstart.sh
|
# main display_name set in vstart.sh
|
||||||
display_name = M. Tester
|
display_name = s3_tests
|
||||||
|
|
||||||
# main user_idname set in vstart.sh
|
# main user_idname set in vstart.sh
|
||||||
user_id = testid
|
user_id = s3_tests
|
||||||
|
|
||||||
# main email set in vstart.sh
|
# main email set in vstart.sh
|
||||||
email = tester@ceph.com
|
email = tester@ceph.com
|
||||||
|
|
|
@ -18,8 +18,6 @@ import (
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||||
)
|
)
|
||||||
|
|
||||||
var IdentityAnonymous *Identity
|
|
||||||
|
|
||||||
type Action string
|
type Action string
|
||||||
|
|
||||||
type Iam interface {
|
type Iam interface {
|
||||||
|
@ -30,11 +28,13 @@ type IdentityAccessManagement struct {
|
||||||
m sync.RWMutex
|
m sync.RWMutex
|
||||||
|
|
||||||
identities []*Identity
|
identities []*Identity
|
||||||
isAuthEnabled bool
|
accessKeyIdent map[string]*Identity
|
||||||
domain string
|
|
||||||
hashes map[string]*sync.Pool
|
hashes map[string]*sync.Pool
|
||||||
hashCounters map[string]*int32
|
hashCounters map[string]*int32
|
||||||
|
identityAnonymous *Identity
|
||||||
hashMu sync.RWMutex
|
hashMu sync.RWMutex
|
||||||
|
domain string
|
||||||
|
isAuthEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
|
@ -136,6 +136,8 @@ func (iam *IdentityAccessManagement) LoadS3ApiConfigurationFromBytes(content []b
|
||||||
|
|
||||||
func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3ApiConfiguration) error {
|
func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3ApiConfiguration) error {
|
||||||
var identities []*Identity
|
var identities []*Identity
|
||||||
|
var identityAnonymous *Identity
|
||||||
|
accessKeyIdent := make(map[string]*Identity)
|
||||||
for _, ident := range config.Identities {
|
for _, ident := range config.Identities {
|
||||||
t := &Identity{
|
t := &Identity{
|
||||||
Name: ident.Name,
|
Name: ident.Name,
|
||||||
|
@ -149,7 +151,7 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
|
||||||
glog.Warningf("anonymous identity is associated with a non-anonymous account ID, the association is invalid")
|
glog.Warningf("anonymous identity is associated with a non-anonymous account ID, the association is invalid")
|
||||||
}
|
}
|
||||||
t.AccountId = s3account.AccountAnonymous.Id
|
t.AccountId = s3account.AccountAnonymous.Id
|
||||||
IdentityAnonymous = t
|
identityAnonymous = t
|
||||||
} else {
|
} else {
|
||||||
if len(ident.AccountId) > 0 {
|
if len(ident.AccountId) > 0 {
|
||||||
t.AccountId = ident.AccountId
|
t.AccountId = ident.AccountId
|
||||||
|
@ -164,19 +166,15 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
|
||||||
AccessKey: cred.AccessKey,
|
AccessKey: cred.AccessKey,
|
||||||
SecretKey: cred.SecretKey,
|
SecretKey: cred.SecretKey,
|
||||||
})
|
})
|
||||||
|
accessKeyIdent[cred.AccessKey] = t
|
||||||
}
|
}
|
||||||
identities = append(identities, t)
|
identities = append(identities, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
if IdentityAnonymous == nil {
|
|
||||||
IdentityAnonymous = &Identity{
|
|
||||||
Name: s3account.AccountAnonymous.Name,
|
|
||||||
AccountId: s3account.AccountAnonymous.Id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iam.m.Lock()
|
iam.m.Lock()
|
||||||
// atomically switch
|
// atomically switch
|
||||||
iam.identities = identities
|
iam.identities = identities
|
||||||
|
iam.identityAnonymous = identityAnonymous
|
||||||
|
iam.accessKeyIdent = accessKeyIdent
|
||||||
if !iam.isAuthEnabled { // one-directional, no toggling
|
if !iam.isAuthEnabled { // one-directional, no toggling
|
||||||
iam.isAuthEnabled = len(identities) > 0
|
iam.isAuthEnabled = len(identities) > 0
|
||||||
}
|
}
|
||||||
|
@ -189,14 +187,12 @@ func (iam *IdentityAccessManagement) isEnabled() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {
|
func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {
|
||||||
|
|
||||||
iam.m.RLock()
|
iam.m.RLock()
|
||||||
defer iam.m.RUnlock()
|
defer iam.m.RUnlock()
|
||||||
for _, ident := range iam.identities {
|
if ident, ok := iam.accessKeyIdent[accessKey]; ok {
|
||||||
for _, cred := range ident.Credentials {
|
for _, credential := range ident.Credentials {
|
||||||
// println("checking", ident.Name, cred.AccessKey)
|
if credential.AccessKey == accessKey {
|
||||||
if cred.AccessKey == accessKey {
|
return ident, credential, true
|
||||||
return ident, cred, true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,10 +203,8 @@ func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identi
|
||||||
func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, found bool) {
|
func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, found bool) {
|
||||||
iam.m.RLock()
|
iam.m.RLock()
|
||||||
defer iam.m.RUnlock()
|
defer iam.m.RUnlock()
|
||||||
for _, ident := range iam.identities {
|
if iam.identityAnonymous != nil {
|
||||||
if ident.isAnonymous() {
|
return iam.identityAnonymous, true
|
||||||
return ident, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -270,8 +264,7 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action)
|
||||||
return identity, s3err.ErrNotImplemented
|
return identity, s3err.ErrNotImplemented
|
||||||
case authTypeAnonymous:
|
case authTypeAnonymous:
|
||||||
authType = "Anonymous"
|
authType = "Anonymous"
|
||||||
identity, found = iam.lookupAnonymous()
|
if identity, found = iam.lookupAnonymous(); !found {
|
||||||
if !found {
|
|
||||||
r.Header.Set(s3_constants.AmzAuthType, authType)
|
r.Header.Set(s3_constants.AmzAuthType, authType)
|
||||||
return identity, s3err.ErrAccessDenied
|
return identity, s3err.ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,15 @@ func TestCanDo(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(t, true, ident5.canDo(ACTION_READ, "special_bucket", "/a/b/c/d.txt"))
|
assert.Equal(t, true, ident5.canDo(ACTION_READ, "special_bucket", "/a/b/c/d.txt"))
|
||||||
assert.Equal(t, true, ident5.canDo(ACTION_WRITE, "special_bucket", "/a/b/c/d.txt"))
|
assert.Equal(t, true, ident5.canDo(ACTION_WRITE, "special_bucket", "/a/b/c/d.txt"))
|
||||||
|
|
||||||
|
// anonymous buckets
|
||||||
|
ident6 := &Identity{
|
||||||
|
Name: "anonymous",
|
||||||
|
Actions: []Action{
|
||||||
|
"Read",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, true, ident6.canDo(ACTION_READ, "anything_bucket", "/a/b/c/d.txt"))
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoadS3ApiConfigurationTestCase struct {
|
type LoadS3ApiConfigurationTestCase struct {
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"google.golang.org/grpc"
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -60,22 +60,24 @@ func TestIsRequestPresignedSignatureV4(t *testing.T) {
|
||||||
|
|
||||||
// Tests is requested authenticated function, tests replies for s3 errors.
|
// Tests is requested authenticated function, tests replies for s3 errors.
|
||||||
func TestIsReqAuthenticated(t *testing.T) {
|
func TestIsReqAuthenticated(t *testing.T) {
|
||||||
option := S3ApiServerOption{
|
iam := &IdentityAccessManagement{
|
||||||
GrpcDialOption: grpc.WithTransportCredentials(insecure.NewCredentials()),
|
hashes: make(map[string]*sync.Pool),
|
||||||
|
hashCounters: make(map[string]*int32),
|
||||||
}
|
}
|
||||||
iam := NewIdentityAccessManagement(&option)
|
_ = iam.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{
|
||||||
iam.identities = []*Identity{
|
Identities: []*iam_pb.Identity{
|
||||||
{
|
{
|
||||||
Name: "someone",
|
Name: "someone",
|
||||||
Credentials: []*Credential{
|
Credentials: []*iam_pb.Credential{
|
||||||
{
|
{
|
||||||
AccessKey: "access_key_1",
|
AccessKey: "access_key_1",
|
||||||
SecretKey: "secret_key_1",
|
SecretKey: "secret_key_1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: nil,
|
Actions: []string{},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// List of test cases for validating http request authentication.
|
// List of test cases for validating http request authentication.
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
@ -97,24 +99,58 @@ func TestIsReqAuthenticated(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckAdminRequestAuthType(t *testing.T) {
|
func TestCheckaAnonymousRequestAuthType(t *testing.T) {
|
||||||
option := S3ApiServerOption{
|
iam := &IdentityAccessManagement{
|
||||||
GrpcDialOption: grpc.WithTransportCredentials(insecure.NewCredentials()),
|
hashes: make(map[string]*sync.Pool),
|
||||||
|
hashCounters: make(map[string]*int32),
|
||||||
}
|
}
|
||||||
iam := NewIdentityAccessManagement(&option)
|
_ = iam.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{
|
||||||
iam.identities = []*Identity{
|
Identities: []*iam_pb.Identity{
|
||||||
|
{
|
||||||
|
Name: "anonymous",
|
||||||
|
Actions: []string{s3_constants.ACTION_READ},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testCases := []struct {
|
||||||
|
Request *http.Request
|
||||||
|
ErrCode s3err.ErrorCode
|
||||||
|
Action Action
|
||||||
|
}{
|
||||||
|
{Request: mustNewRequest("GET", "http://127.0.0.1:9000/bucket", 0, nil, t), ErrCode: s3err.ErrNone, Action: s3_constants.ACTION_READ},
|
||||||
|
{Request: mustNewRequest("PUT", "http://127.0.0.1:9000/bucket", 0, nil, t), ErrCode: s3err.ErrAccessDenied, Action: s3_constants.ACTION_WRITE},
|
||||||
|
}
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
_, s3Error := iam.authRequest(testCase.Request, testCase.Action)
|
||||||
|
if s3Error != testCase.ErrCode {
|
||||||
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||||
|
}
|
||||||
|
if testCase.Request.Header.Get(s3_constants.AmzAuthType) != "Anonymous" {
|
||||||
|
t.Errorf("Test %d: Unexpected AuthType returned wanted %s, got %s", i, "Anonymous", testCase.Request.Header.Get(s3_constants.AmzAuthType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckAdminRequestAuthType(t *testing.T) {
|
||||||
|
iam := &IdentityAccessManagement{
|
||||||
|
hashes: make(map[string]*sync.Pool),
|
||||||
|
hashCounters: make(map[string]*int32),
|
||||||
|
}
|
||||||
|
_ = iam.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{
|
||||||
|
Identities: []*iam_pb.Identity{
|
||||||
{
|
{
|
||||||
Name: "someone",
|
Name: "someone",
|
||||||
Credentials: []*Credential{
|
Credentials: []*iam_pb.Credential{
|
||||||
{
|
{
|
||||||
AccessKey: "access_key_1",
|
AccessKey: "access_key_1",
|
||||||
SecretKey: "secret_key_1",
|
SecretKey: "secret_key_1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: nil,
|
Actions: []string{},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
})
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
ErrCode s3err.ErrorCode
|
ErrCode s3err.ErrorCode
|
||||||
|
|
|
@ -259,34 +259,56 @@ func (s3a *S3ApiServer) GetBucketAclHandler(w http.ResponseWriter, r *http.Reque
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := AccessControlPolicy{}
|
identityId := r.Header.Get(s3_constants.AmzIdentityId)
|
||||||
for _, ident := range s3a.iam.identities {
|
response := AccessControlPolicy{
|
||||||
if len(ident.Credentials) == 0 {
|
Owner: CanonicalUser{
|
||||||
continue
|
ID: identityId,
|
||||||
}
|
DisplayName: identityId,
|
||||||
for _, action := range ident.Actions {
|
},
|
||||||
if !action.overBucket(bucket) || action.getPermission() == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
id := ident.Credentials[0].AccessKey
|
|
||||||
if response.Owner.DisplayName == "" && action.isOwner(bucket) && len(ident.Credentials) > 0 {
|
|
||||||
response.Owner.DisplayName = ident.Name
|
|
||||||
response.Owner.ID = id
|
|
||||||
}
|
}
|
||||||
response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
|
response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
|
||||||
Grantee: Grantee{
|
Grantee: Grantee{
|
||||||
ID: id,
|
ID: identityId,
|
||||||
DisplayName: ident.Name,
|
DisplayName: identityId,
|
||||||
Type: "CanonicalUser",
|
Type: "CanonicalUser",
|
||||||
XMLXSI: "CanonicalUser",
|
XMLXSI: "CanonicalUser",
|
||||||
XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},
|
XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},
|
||||||
Permission: action.getPermission(),
|
Permission: s3.PermissionFullControl,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
writeSuccessResponseXML(w, r, response)
|
writeSuccessResponseXML(w, r, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutBucketAclHandler Put bucket ACL only responds success if the ACL is private.
|
||||||
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html //
|
||||||
|
func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// collect parameters
|
||||||
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
||||||
|
glog.V(3).Infof("PutBucketAclHandler %s", bucket)
|
||||||
|
|
||||||
|
if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
|
||||||
|
s3err.WriteErrorResponse(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cannedAcl := r.Header.Get(s3_constants.AmzCannedAcl)
|
||||||
|
switch {
|
||||||
|
case cannedAcl == "":
|
||||||
|
acl := &s3.AccessControlPolicy{}
|
||||||
|
if err := xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
||||||
|
glog.Errorf("PutBucketAclHandler: %s", err)
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(acl.Grants) == 1 && acl.Grants[0].Permission != nil && *acl.Grants[0].Permission == s3_constants.PermissionFullControl {
|
||||||
|
writeSuccessResponseEmpty(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case cannedAcl == s3_constants.CannedAclPrivate:
|
||||||
|
writeSuccessResponseEmpty(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBucketLifecycleConfigurationHandler Get Bucket Lifecycle configuration
|
// GetBucketLifecycleConfigurationHandler Get Bucket Lifecycle configuration
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html
|
||||||
func (s3a *S3ApiServer) GetBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
|
func (s3a *S3ApiServer) GetBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -41,9 +41,3 @@ func (s3a *S3ApiServer) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Re
|
||||||
func (s3a *S3ApiServer) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
func (s3a *S3ApiServer) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
s3err.WriteErrorResponse(w, r, http.StatusNoContent)
|
s3err.WriteErrorResponse(w, r, http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutBucketAclHandler Put bucket ACL
|
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html
|
|
||||||
func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue