Browse Source

[s3acl] Step1: move s3account.AccountManager into to iam.S3ApiConfiguration (#4859)

* move s3account.AccountManager into to iam.S3ApiConfiguration and switch to Interface

https://github.com/seaweedfs/seaweedfs/issues/4519

* fix: test bucket acl default and
adjust the variable names

* fix: s3 api config test

---------

Co-authored-by: Konstantin Lebedev <9497591+kmlebedev@users.noreply.github.co>
Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
pull/4868/head
Konstantin Lebedev 1 year ago
committed by GitHub
parent
commit
f8b94cac0e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      Makefile
  2. 14
      docker/compose/s3.json
  3. 4
      docker/compose/s3tests.conf
  4. 9
      weed/pb/iam.proto
  5. 168
      weed/pb/iam_pb/iam.pb.go
  6. 6
      weed/pb/iam_pb/iam_grpc.pb.go
  7. 137
      weed/s3api/auth_credentials.go
  8. 37
      weed/s3api/auth_credentials_test.go
  9. 15
      weed/s3api/bucket_metadata.go
  10. 42
      weed/s3api/bucket_metadata_test.go
  11. 6
      weed/s3api/s3_constants/s3_acp.go
  12. 70
      weed/s3api/s3account/s3_account.go
  13. 28
      weed/s3api/s3api_acl_helper.go
  14. 75
      weed/s3api/s3api_acl_helper_test.go
  15. 5
      weed/s3api/s3api_acp.go
  16. 11
      weed/s3api/s3api_bucket_handlers.go
  17. 3
      weed/s3api/s3api_server.go

5
Makefile

@ -10,5 +10,8 @@ install:
full_install:
cd weed; go install -tags "elastic gocdk sqlite ydb tikv"
tests:
server: install
weed -v 4 server -s3 -filer -volume.max=0 -master.volumeSizeLimitMB=1024 -volume.preStopSeconds=1 -s3.port=8000 -s3.allowEmptyFolder=false -s3.allowDeleteBucketNotEmpty=false -s3.config=./docker/compose/s3.json
test:
cd weed; go test -tags "elastic gocdk sqlite ydb tikv" -v ./...

14
docker/compose/s3.json

@ -40,7 +40,10 @@
"List",
"Tagging",
"Write"
]
],
"account": {
"id": "testid"
}
},
{
"name": "s3_tests_alt",
@ -101,5 +104,12 @@
"Write"
]
}
]
],
"accounts": [
{
"id" : "testid",
"displayName": "M. Tester",
"emailAddress": "tester@ceph.com"
}
]
}

4
docker/compose/s3tests.conf

@ -18,10 +18,10 @@ bucket prefix = yournamehere-{random}-
[s3 main]
# main display_name set in vstart.sh
display_name = s3_tests
display_name = M. Tester
# main user_idname set in vstart.sh
user_id = s3_tests
user_id = testid
# main email set in vstart.sh
email = tester@ceph.com

9
weed/pb/iam.proto

@ -16,13 +16,14 @@ service SeaweedIdentityAccessManagement {
message S3ApiConfiguration {
repeated Identity identities = 1;
repeated Account accounts = 2;
}
message Identity {
string name = 1;
repeated Credential credentials = 2;
repeated string actions = 3;
string accountId = 4;
Account account = 4;
}
message Credential {
@ -32,6 +33,12 @@ message Credential {
// bool is_disabled = 4;
}
message Account {
string id = 1;
string display_name = 2;
string email_address = 3;
}
/*
message Policy {
repeated Statement statements = 1;

168
weed/pb/iam_pb/iam.pb.go

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.30.0
// protoc v4.23.3
// protoc v4.23.2
// source: iam.proto
package iam_pb
@ -26,6 +26,7 @@ type S3ApiConfiguration struct {
unknownFields protoimpl.UnknownFields
Identities []*Identity `protobuf:"bytes,1,rep,name=identities,proto3" json:"identities,omitempty"`
Accounts []*Account `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty"`
}
func (x *S3ApiConfiguration) Reset() {
@ -67,6 +68,13 @@ func (x *S3ApiConfiguration) GetIdentities() []*Identity {
return nil
}
func (x *S3ApiConfiguration) GetAccounts() []*Account {
if x != nil {
return x.Accounts
}
return nil
}
type Identity struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -75,7 +83,7 @@ type Identity struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Credentials []*Credential `protobuf:"bytes,2,rep,name=credentials,proto3" json:"credentials,omitempty"`
Actions []string `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"`
AccountId string `protobuf:"bytes,4,opt,name=accountId,proto3" json:"accountId,omitempty"`
Account *Account `protobuf:"bytes,4,opt,name=account,proto3" json:"account,omitempty"`
}
func (x *Identity) Reset() {
@ -131,11 +139,11 @@ func (x *Identity) GetActions() []string {
return nil
}
func (x *Identity) GetAccountId() string {
func (x *Identity) GetAccount() *Account {
if x != nil {
return x.AccountId
return x.Account
}
return ""
return nil
}
type Credential struct {
@ -193,36 +201,109 @@ func (x *Credential) GetSecretKey() string {
return ""
}
type Account struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"`
EmailAddress string `protobuf:"bytes,3,opt,name=email_address,json=emailAddress,proto3" json:"email_address,omitempty"`
}
func (x *Account) Reset() {
*x = Account{}
if protoimpl.UnsafeEnabled {
mi := &file_iam_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Account) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Account) ProtoMessage() {}
func (x *Account) ProtoReflect() protoreflect.Message {
mi := &file_iam_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Account.ProtoReflect.Descriptor instead.
func (*Account) Descriptor() ([]byte, []int) {
return file_iam_proto_rawDescGZIP(), []int{3}
}
func (x *Account) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Account) GetDisplayName() string {
if x != nil {
return x.DisplayName
}
return ""
}
func (x *Account) GetEmailAddress() string {
if x != nil {
return x.EmailAddress
}
return ""
}
var File_iam_proto protoreflect.FileDescriptor
var file_iam_proto_rawDesc = []byte{
0x0a, 0x09, 0x69, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x69, 0x61, 0x6d,
0x5f, 0x70, 0x62, 0x22, 0x46, 0x0a, 0x12, 0x53, 0x33, 0x41, 0x70, 0x69, 0x43, 0x6f, 0x6e, 0x66,
0x5f, 0x70, 0x62, 0x22, 0x73, 0x0a, 0x12, 0x53, 0x33, 0x41, 0x70, 0x69, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x0a, 0x69, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e,
0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52,
0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x08,
0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x0b,
0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x12, 0x2e, 0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
0x6c, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09,
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x4a, 0x0a, 0x0a, 0x43, 0x72,
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x65,
0x73, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63,
0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65,
0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x63,
0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x32, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x61, 0x77, 0x65, 0x65,
0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4d,
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x4b, 0x0a, 0x10, 0x73, 0x65, 0x61,
0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x08, 0x49,
0x61, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x61, 0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x73, 0x65,
0x61, 0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70, 0x62, 0x2f,
0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x61,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08,
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x08, 0x49, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x0b, 0x63, 0x72, 0x65,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12,
0x2e, 0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12,
0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x61, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x61, 0x6d,
0x5f, 0x70, 0x62, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4a, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65,
0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79,
0x22, 0x61, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64,
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23,
0x0a, 0x0d, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x41, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x32, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x61, 0x77, 0x65, 0x65, 0x64, 0x49, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x4b, 0x0a, 0x10, 0x73, 0x65, 0x61, 0x77, 0x65, 0x65,
0x64, 0x66, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x08, 0x49, 0x61, 0x6d, 0x50,
0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x73, 0x65, 0x61, 0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x77, 0x65,
0x65, 0x64, 0x66, 0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70, 0x62, 0x2f, 0x69, 0x61, 0x6d,
0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -237,20 +318,23 @@ func file_iam_proto_rawDescGZIP() []byte {
return file_iam_proto_rawDescData
}
var file_iam_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_iam_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_iam_proto_goTypes = []interface{}{
(*S3ApiConfiguration)(nil), // 0: iam_pb.S3ApiConfiguration
(*Identity)(nil), // 1: iam_pb.Identity
(*Credential)(nil), // 2: iam_pb.Credential
(*Account)(nil), // 3: iam_pb.Account
}
var file_iam_proto_depIdxs = []int32{
1, // 0: iam_pb.S3ApiConfiguration.identities:type_name -> iam_pb.Identity
2, // 1: iam_pb.Identity.credentials:type_name -> iam_pb.Credential
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
3, // 1: iam_pb.S3ApiConfiguration.accounts:type_name -> iam_pb.Account
2, // 2: iam_pb.Identity.credentials:type_name -> iam_pb.Credential
3, // 3: iam_pb.Identity.account:type_name -> iam_pb.Account
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_iam_proto_init() }
@ -295,6 +379,18 @@ func file_iam_proto_init() {
return nil
}
}
file_iam_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Account); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -302,7 +398,7 @@ func file_iam_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_iam_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},

6
weed/pb/iam_pb/iam_grpc.pb.go

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v4.23.3
// - protoc-gen-go-grpc v1.3.0
// - protoc v4.23.2
// source: iam.proto
package iam_pb
@ -15,6 +15,8 @@ import (
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const ()
// SeaweedIdentityAccessManagementClient is the client API for SeaweedIdentityAccessManagement service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.

137
weed/s3api/auth_credentials.go

@ -7,8 +7,6 @@ import (
"strings"
"sync"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"github.com/seaweedfs/seaweedfs/weed/filer"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb"
@ -29,6 +27,8 @@ type IdentityAccessManagement struct {
identities []*Identity
accessKeyIdent map[string]*Identity
accounts map[string]*Account
emailAccount map[string]*Account
hashes map[string]*sync.Pool
hashCounters map[string]*int32
identityAnonymous *Identity
@ -39,20 +39,50 @@ type IdentityAccessManagement struct {
type Identity struct {
Name string
AccountId string
Account *Account
Credentials []*Credential
Actions []Action
}
func (i *Identity) isAnonymous() bool {
return i.Name == s3account.AccountAnonymous.Name
// Account represents a system user, a system user can
// configure multiple IAM-Users, IAM-Users can configure
// permissions respectively, and each IAM-User can
// configure multiple security credentials
type Account struct {
//Name is also used to display the "DisplayName" as the owner of the bucket or object
DisplayName string
EmailAddress string
//Id is used to identify an Account when granting cross-account access(ACLs) to buckets and objects
Id string
}
// Predefined Accounts
var (
// AccountAdmin is used as the default account for IAM-Credentials access without Account configured
AccountAdmin = Account{
DisplayName: "admin",
EmailAddress: "admin@example.com",
Id: s3_constants.AccountAdminId,
}
// AccountAnonymous is used to represent the account for anonymous access
AccountAnonymous = Account{
DisplayName: "anonymous",
EmailAddress: "anonymous@example.com",
Id: s3_constants.AccountAnonymousId,
}
)
type Credential struct {
AccessKey string
SecretKey string
}
func (i *Identity) isAnonymous() bool {
return i.Account.Id == s3_constants.AccountAnonymousId
}
func (action Action) isAdmin() bool {
return strings.HasPrefix(string(action), s3_constants.ACTION_ADMIN)
}
@ -65,14 +95,19 @@ func (action Action) overBucket(bucket string) bool {
return strings.HasSuffix(string(action), ":"+bucket) || strings.HasSuffix(string(action), ":*")
}
// "Permission": "FULL_CONTROL"|"WRITE"|"WRITE_ACP"|"READ"|"READ_ACP"
func (action Action) getPermission() Permission {
switch act := strings.Split(string(action), ":")[0]; act {
case s3_constants.ACTION_ADMIN:
return Permission("FULL_CONTROL")
case s3_constants.ACTION_WRITE:
return Permission("WRITE")
case s3_constants.ACTION_WRITE_ACP:
return Permission("WRITE_ACP")
case s3_constants.ACTION_READ:
return Permission("READ")
case s3_constants.ACTION_READ_ACP:
return Permission("READ_ACP")
default:
return Permission("")
}
@ -138,26 +173,69 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
var identities []*Identity
var identityAnonymous *Identity
accessKeyIdent := make(map[string]*Identity)
accounts := make(map[string]*Account)
emailAccount := make(map[string]*Account)
foundAccountAdmin := false
foundAccountAnonymous := false
for _, account := range config.Accounts {
switch account.Id {
case AccountAdmin.Id:
AccountAdmin = Account{
Id: account.Id,
DisplayName: account.DisplayName,
EmailAddress: account.EmailAddress,
}
accounts[account.Id] = &AccountAdmin
foundAccountAdmin = true
case AccountAnonymous.Id:
AccountAnonymous = Account{
Id: account.Id,
DisplayName: account.DisplayName,
EmailAddress: account.EmailAddress,
}
accounts[account.Id] = &AccountAnonymous
foundAccountAnonymous = true
default:
t := Account{
Id: account.Id,
DisplayName: account.DisplayName,
EmailAddress: account.EmailAddress,
}
accounts[account.Id] = &t
}
if account.EmailAddress != "" {
emailAccount[account.EmailAddress] = accounts[account.Id]
}
}
if !foundAccountAdmin {
accounts[AccountAdmin.Id] = &AccountAdmin
emailAccount[AccountAdmin.EmailAddress] = &AccountAdmin
}
if !foundAccountAnonymous {
accounts[AccountAnonymous.Id] = &AccountAnonymous
emailAccount[AccountAnonymous.EmailAddress] = &AccountAnonymous
}
for _, ident := range config.Identities {
t := &Identity{
Name: ident.Name,
AccountId: s3account.AccountAdmin.Id,
Credentials: nil,
Actions: nil,
}
if ident.Name == s3account.AccountAnonymous.Name {
if ident.AccountId != "" && ident.AccountId != s3account.AccountAnonymous.Id {
glog.Warningf("anonymous identity is associated with a non-anonymous account ID, the association is invalid")
}
t.AccountId = s3account.AccountAnonymous.Id
switch {
case ident.Name == AccountAnonymous.Id:
t.Account = &AccountAnonymous
identityAnonymous = t
} else {
if len(ident.AccountId) > 0 {
t.AccountId = ident.AccountId
case ident.Account == nil:
t.Account = &AccountAdmin
default:
if account, ok := accounts[ident.Account.Id]; ok {
t.Account = account
} else {
t.Account = &AccountAdmin
glog.Warningf("identity %s is associated with a non exist account ID, the association is invalid", ident.Name)
}
}
for _, action := range ident.Actions {
t.Actions = append(t.Actions, Action(action))
}
@ -170,15 +248,19 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
}
identities = append(identities, t)
}
iam.m.Lock()
// atomically switch
iam.identities = identities
iam.identityAnonymous = identityAnonymous
iam.accounts = accounts
iam.emailAccount = emailAccount
iam.accessKeyIdent = accessKeyIdent
if !iam.isAuthEnabled { // one-directional, no toggling
iam.isAuthEnabled = len(identities) > 0
}
iam.m.Unlock()
return nil
}
@ -209,6 +291,24 @@ func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, foun
return nil, false
}
func (iam *IdentityAccessManagement) GetAccountNameById(canonicalId string) string {
iam.m.RLock()
defer iam.m.RUnlock()
if account, ok := iam.accounts[canonicalId]; ok {
return account.DisplayName
}
return ""
}
func (iam *IdentityAccessManagement) GetAccountIdByEmail(email string) string {
iam.m.RLock()
defer iam.m.RUnlock()
if account, ok := iam.emailAccount[email]; ok {
return account.Id
}
return ""
}
func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !iam.isEnabled() {
@ -287,9 +387,8 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action)
return identity, s3err.ErrAccessDenied
}
if !identity.isAnonymous() {
r.Header.Set(s3_constants.AmzAccountId, identity.AccountId)
}
r.Header.Set(s3_constants.AmzAccountId, identity.Account.Id)
return identity, s3err.ErrNone
}

37
weed/s3api/auth_credentials_test.go

@ -2,14 +2,12 @@ package s3api
import (
. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"github.com/stretchr/testify/assert"
"reflect"
"testing"
jsonpb "google.golang.org/protobuf/encoding/protojson"
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
jsonpb "google.golang.org/protobuf/encoding/protojson"
)
func TestIdentityListFileFormat(t *testing.T) {
@ -146,11 +144,22 @@ func TestCanDo(t *testing.T) {
}
type LoadS3ApiConfigurationTestCase struct {
pbAccount *iam_pb.Account
pbIdent *iam_pb.Identity
expectIdent *Identity
}
func TestLoadS3ApiConfiguration(t *testing.T) {
specifiedAccount := Account{
Id: "specifiedAccountID",
DisplayName: "specifiedAccountName",
EmailAddress: "specifiedAccounEmail@example.com",
}
pbSpecifiedAccount := iam_pb.Account{
Id: "specifiedAccountID",
DisplayName: "specifiedAccountName",
EmailAddress: "specifiedAccounEmail@example.com",
}
testCases := map[string]*LoadS3ApiConfigurationTestCase{
"notSpecifyAccountId": {
pbIdent: &iam_pb.Identity{
@ -167,8 +176,8 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
},
},
expectIdent: &Identity{
Name: "notSpecifyAccountId",
AccountId: s3account.AccountAdmin.Id,
Name: "notSpecifyAccountId",
Account: &AccountAdmin,
Actions: []Action{
"Read",
"Write",
@ -182,17 +191,18 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
},
},
"specifiedAccountID": {
pbAccount: &pbSpecifiedAccount,
pbIdent: &iam_pb.Identity{
Name: "specifiedAccountID",
AccountId: "specifiedAccountID",
Name: "specifiedAccountID",
Account: &pbSpecifiedAccount,
Actions: []string{
"Read",
"Write",
},
},
expectIdent: &Identity{
Name: "specifiedAccountID",
AccountId: "specifiedAccountID",
Name: "specifiedAccountID",
Account: &specifiedAccount,
Actions: []Action{
"Read",
"Write",
@ -208,8 +218,8 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
},
},
expectIdent: &Identity{
Name: "anonymous",
AccountId: "anonymous",
Name: "anonymous",
Account: &AccountAnonymous,
Actions: []Action{
"Read",
"Write",
@ -223,6 +233,9 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
}
for _, v := range testCases {
config.Identities = append(config.Identities, v.pbIdent)
if v.pbAccount != nil {
config.Accounts = append(config.Accounts, v.pbAccount)
}
}
iam := IdentityAccessManagement{}
@ -234,7 +247,7 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
for _, ident := range iam.identities {
tc := testCases[ident.Name]
if !reflect.DeepEqual(ident, tc.expectIdent) {
t.Error("not expect")
t.Errorf("not expect for ident name %s", ident.Name)
}
}
}

15
weed/s3api/bucket_metadata.go

@ -6,7 +6,6 @@ import (
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"github.com/seaweedfs/seaweedfs/weed/util"
"math"
@ -19,7 +18,7 @@ var loadBucketMetadataFromFiler = func(r *BucketRegistry, bucketName string) (*B
return nil, err
}
return buildBucketMetadata(r.s3a.accountManager, entry), nil
return buildBucketMetadata(r.s3a.iam, entry), nil
}
type BucketMetaData struct {
@ -73,13 +72,13 @@ func (r *BucketRegistry) init() error {
}
func (r *BucketRegistry) LoadBucketMetadata(entry *filer_pb.Entry) {
bucketMetadata := buildBucketMetadata(r.s3a.accountManager, entry)
bucketMetadata := buildBucketMetadata(r.s3a.iam, entry)
r.metadataCacheLock.Lock()
defer r.metadataCacheLock.Unlock()
r.metadataCache[entry.Name] = bucketMetadata
}
func buildBucketMetadata(accountManager *s3account.AccountManager, entry *filer_pb.Entry) *BucketMetaData {
func buildBucketMetadata(accountManager AccountManager, entry *filer_pb.Entry) *BucketMetaData {
entryJson, _ := json.Marshal(entry)
glog.V(3).Infof("build bucket metadata,entry=%s", entryJson)
bucketMetadata := &BucketMetaData{
@ -90,8 +89,8 @@ func buildBucketMetadata(accountManager *s3account.AccountManager, entry *filer_
// Default owner: `AccountAdmin`
Owner: &s3.Owner{
ID: &s3account.AccountAdmin.Id,
DisplayName: &s3account.AccountAdmin.Name,
ID: &AccountAdmin.Id,
DisplayName: &AccountAdmin.DisplayName,
},
}
if entry.Extended != nil {
@ -112,8 +111,8 @@ func buildBucketMetadata(accountManager *s3account.AccountManager, entry *filer_
acpOwnerBytes, ok := entry.Extended[s3_constants.ExtAmzOwnerKey]
if ok && len(acpOwnerBytes) > 0 {
ownerAccountId := string(acpOwnerBytes)
ownerAccountName, exists := accountManager.IdNameMapping[ownerAccountId]
if !exists {
ownerAccountName := accountManager.GetAccountNameById(ownerAccountId)
if ownerAccountName == "" {
glog.Warningf("owner[id=%s] is invalid, bucket: %s", ownerAccountId, bucketMetadata.Name)
} else {
bucketMetadata.Owner = &s3.Owner{

42
weed/s3api/bucket_metadata_test.go

@ -5,8 +5,8 @@ import (
"fmt"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"reflect"
"sync"
@ -31,7 +31,7 @@ var (
Name: "entryWithValidAcp",
Extended: map[string][]byte{
s3_constants.ExtOwnershipKey: []byte(s3_constants.OwnershipBucketOwnerEnforced),
s3_constants.ExtAmzOwnerKey: []byte(s3account.AccountAdmin.Name),
s3_constants.ExtAmzOwnerKey: []byte(AccountAdmin.DisplayName),
s3_constants.ExtAmzAclKey: goodEntryAcl,
},
}
@ -88,8 +88,8 @@ var tcs = []*BucketMetadataTestCase{
Name: badEntry.Name,
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
Owner: &s3.Owner{
DisplayName: &s3account.AccountAdmin.Name,
ID: &s3account.AccountAdmin.Id,
DisplayName: &AccountAdmin.DisplayName,
ID: &AccountAdmin.Id,
},
Acl: nil,
},
@ -99,8 +99,8 @@ var tcs = []*BucketMetadataTestCase{
Name: goodEntry.Name,
ObjectOwnership: s3_constants.OwnershipBucketOwnerEnforced,
Owner: &s3.Owner{
DisplayName: &s3account.AccountAdmin.Name,
ID: &s3account.AccountAdmin.Id,
DisplayName: &AccountAdmin.DisplayName,
ID: &AccountAdmin.Id,
},
Acl: s3_constants.PublicRead,
},
@ -110,8 +110,8 @@ var tcs = []*BucketMetadataTestCase{
Name: ownershipEmptyStr.Name,
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
Owner: &s3.Owner{
DisplayName: &s3account.AccountAdmin.Name,
ID: &s3account.AccountAdmin.Id,
DisplayName: &AccountAdmin.DisplayName,
ID: &AccountAdmin.Id,
},
Acl: nil,
},
@ -121,8 +121,8 @@ var tcs = []*BucketMetadataTestCase{
Name: ownershipValid.Name,
ObjectOwnership: s3_constants.OwnershipBucketOwnerEnforced,
Owner: &s3.Owner{
DisplayName: &s3account.AccountAdmin.Name,
ID: &s3account.AccountAdmin.Id,
DisplayName: &AccountAdmin.DisplayName,
ID: &AccountAdmin.Id,
},
Acl: nil,
},
@ -132,8 +132,8 @@ var tcs = []*BucketMetadataTestCase{
Name: acpEmptyStr.Name,
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
Owner: &s3.Owner{
DisplayName: &s3account.AccountAdmin.Name,
ID: &s3account.AccountAdmin.Id,
DisplayName: &AccountAdmin.DisplayName,
ID: &AccountAdmin.Id,
},
Acl: nil,
},
@ -143,8 +143,8 @@ var tcs = []*BucketMetadataTestCase{
Name: acpEmptyObject.Name,
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
Owner: &s3.Owner{
DisplayName: &s3account.AccountAdmin.Name,
ID: &s3account.AccountAdmin.Id,
DisplayName: &AccountAdmin.DisplayName,
ID: &AccountAdmin.Id,
},
Acl: nil,
},
@ -154,8 +154,8 @@ var tcs = []*BucketMetadataTestCase{
Name: acpOwnerNil.Name,
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
Owner: &s3.Owner{
DisplayName: &s3account.AccountAdmin.Name,
ID: &s3account.AccountAdmin.Id,
DisplayName: &AccountAdmin.DisplayName,
ID: &AccountAdmin.Id,
},
Acl: make([]*s3.Grant, 0),
},
@ -163,14 +163,10 @@ var tcs = []*BucketMetadataTestCase{
}
func TestBuildBucketMetadata(t *testing.T) {
accountManager := &s3account.AccountManager{
IdNameMapping: map[string]string{
s3account.AccountAdmin.Id: s3account.AccountAdmin.Name,
s3account.AccountAnonymous.Id: s3account.AccountAnonymous.Name,
},
}
iam := &IdentityAccessManagement{}
_ = iam.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{})
for _, tc := range tcs {
resultBucketMetadata := buildBucketMetadata(accountManager, tc.filerEntry)
resultBucketMetadata := buildBucketMetadata(iam, tc.filerEntry)
if !reflect.DeepEqual(resultBucketMetadata, tc.expectBucketMetadata) {
t.Fatalf("result is unexpect: \nresult: %v, \nexpect: %v", resultBucketMetadata, tc.expectBucketMetadata)
}

6
weed/s3api/s3_constants/s3_acp.go

@ -0,0 +1,6 @@
package s3_constants
const (
AccountAnonymousId = "anonymous"
AccountAdminId = "admin"
)

70
weed/s3api/s3account/s3_account.go

@ -1,70 +0,0 @@
package s3account
import (
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"sync"
)
//Predefined Accounts
var (
// AccountAdmin is used as the default account for IAM-Credentials access without Account configured
AccountAdmin = Account{
Name: "admin",
EmailAddress: "admin@example.com",
Id: "admin",
}
// AccountAnonymous is used to represent the account for anonymous access
AccountAnonymous = Account{
Name: "anonymous",
EmailAddress: "anonymous@example.com",
Id: "anonymous",
}
)
//Account represents a system user, a system user can
//configure multiple IAM-Users, IAM-Users can configure
//permissions respectively, and each IAM-User can
//configure multiple security credentials
type Account struct {
//Name is also used to display the "DisplayName" as the owner of the bucket or object
Name string
EmailAddress string
//Id is used to identify an Account when granting cross-account access(ACLs) to buckets and objects
Id string
}
type AccountManager struct {
sync.Mutex
filerClient filer_pb.FilerClient
IdNameMapping map[string]string
EmailIdMapping map[string]string
}
func NewAccountManager(filerClient filer_pb.FilerClient) *AccountManager {
am := &AccountManager{
filerClient: filerClient,
IdNameMapping: make(map[string]string),
EmailIdMapping: make(map[string]string),
}
am.initialize()
return am
}
func (am *AccountManager) GetAccountNameById(canonicalId string) string {
return am.IdNameMapping[canonicalId]
}
func (am *AccountManager) GetAccountIdByEmail(email string) string {
return am.EmailIdMapping[email]
}
func (am *AccountManager) initialize() {
// load predefined Accounts
for _, account := range []Account{AccountAdmin, AccountAnonymous} {
am.IdNameMapping[account.Id] = account.Name
am.EmailIdMapping[account.EmailAddress] = account.Id
}
}

28
weed/s3api/s3acl/acl_helper.go → weed/s3api/s3api_acl_helper.go

@ -1,4 +1,4 @@
package s3acl
package s3api
import (
"encoding/json"
@ -8,25 +8,29 @@ import (
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"github.com/seaweedfs/seaweedfs/weed/util"
"net/http"
"strings"
)
type AccountManager interface {
GetAccountNameById(canonicalId string) string
GetAccountIdByEmail(email string) string
}
// GetAccountId get AccountId from request headers, AccountAnonymousId will be return if not presen
func GetAccountId(r *http.Request) string {
id := r.Header.Get(s3_constants.AmzAccountId)
if len(id) == 0 {
return s3account.AccountAnonymous.Id
return s3_constants.AccountAnonymousId
} else {
return id
}
}
// ExtractAcl extracts the acl from the request body, or from the header if request body is empty
func ExtractAcl(r *http.Request, accountManager *s3account.AccountManager, ownership, bucketOwnerId, ownerId, accountId string) (grants []*s3.Grant, errCode s3err.ErrorCode) {
func ExtractAcl(r *http.Request, accountManager AccountManager, ownership, bucketOwnerId, ownerId, accountId string) (grants []*s3.Grant, errCode s3err.ErrorCode) {
if r.Body != nil && r.Body != http.NoBody {
defer util.CloseRequest(r)
@ -50,7 +54,7 @@ func ExtractAcl(r *http.Request, accountManager *s3account.AccountManager, owner
}
// ParseAndValidateAclHeadersOrElseDefault will callParseAndValidateAclHeaders to get Grants, if empty, it will return Grant that grant `accountId` with `FullControl` permission
func ParseAndValidateAclHeadersOrElseDefault(r *http.Request, accountManager *s3account.AccountManager, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) {
func ParseAndValidateAclHeadersOrElseDefault(r *http.Request, accountManager AccountManager, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) {
ownerId, grants, errCode = ParseAndValidateAclHeaders(r, accountManager, ownership, bucketOwnerId, accountId, putAcl)
if errCode != s3err.ErrNone {
return
@ -69,7 +73,7 @@ func ParseAndValidateAclHeadersOrElseDefault(r *http.Request, accountManager *s3
}
// ParseAndValidateAclHeaders parse and validate acl from header
func ParseAndValidateAclHeaders(r *http.Request, accountManager *s3account.AccountManager, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) {
func ParseAndValidateAclHeaders(r *http.Request, accountManager AccountManager, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) {
ownerId, grants, errCode = ParseAclHeaders(r, ownership, bucketOwnerId, accountId, putAcl)
if errCode != s3err.ErrNone {
return
@ -257,7 +261,7 @@ func ParseCannedAclHeader(bucketOwnership, bucketOwnerId, accountId, cannedAcl s
}
// ValidateAndTransferGrants validate grant & transfer Email-Grant to Id-Grant
func ValidateAndTransferGrants(accountManager *s3account.AccountManager, grants []*s3.Grant) ([]*s3.Grant, s3err.ErrorCode) {
func ValidateAndTransferGrants(accountManager AccountManager, grants []*s3.Grant) ([]*s3.Grant, s3err.ErrorCode) {
var result []*s3.Grant
for _, grant := range grants {
grantee := grant.Grantee
@ -283,8 +287,8 @@ func ValidateAndTransferGrants(accountManager *s3account.AccountManager, grants
glog.Warning("invalid canonical grantee! account id is nil")
return nil, s3err.ErrInvalidRequest
}
_, ok := accountManager.IdNameMapping[*grantee.ID]
if !ok {
name := accountManager.GetAccountNameById(*grantee.ID)
if len(name) == 0 {
glog.Warningf("invalid canonical grantee! account id[%s] is not exists", *grantee.ID)
return nil, s3err.ErrInvalidRequest
}
@ -294,8 +298,8 @@ func ValidateAndTransferGrants(accountManager *s3account.AccountManager, grants
glog.Warning("invalid email grantee! email address is nil")
return nil, s3err.ErrInvalidRequest
}
accountId, ok := accountManager.EmailIdMapping[*grantee.EmailAddress]
if !ok {
accountId := accountManager.GetAccountIdByEmail(*grantee.EmailAddress)
if len(accountId) == 0 {
glog.Warningf("invalid email grantee! email address[%s] is not exists", *grantee.EmailAddress)
return nil, s3err.ErrInvalidRequest
}
@ -348,7 +352,7 @@ func DetermineReqGrants(accountId, aclAction string) (grants []*s3.Grant) {
})
// group grantee (AuthenticateUsers)
if accountId != s3account.AccountAnonymous.Id {
if accountId != s3_constants.AccountAnonymousId {
grants = append(grants, &s3.Grant{
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeGroup,

75
weed/s3api/s3acl/acl_helper_test.go → weed/s3api/s3api_acl_helper_test.go

@ -1,34 +1,38 @@
package s3acl
package s3api
import (
"bytes"
"encoding/json"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"io"
"net/http"
"testing"
)
var (
accountManager = &s3account.AccountManager{
IdNameMapping: map[string]string{
s3account.AccountAdmin.Id: s3account.AccountAdmin.Name,
s3account.AccountAnonymous.Id: s3account.AccountAnonymous.Name,
"accountA": "accountA",
"accountB": "accountB",
},
EmailIdMapping: map[string]string{
s3account.AccountAdmin.EmailAddress: s3account.AccountAdmin.Id,
s3account.AccountAnonymous.EmailAddress: s3account.AccountAnonymous.Id,
"accountA@example.com": "accountA",
"accountBexample.com": "accountB",
var accountManager *IdentityAccessManagement
func init() {
accountManager = &IdentityAccessManagement{}
_ = accountManager.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{
Accounts: []*iam_pb.Account{
{
Id: "accountA",
DisplayName: "accountAName",
EmailAddress: "accountA@example.com",
},
{
Id: "accountB",
DisplayName: "accountBName",
EmailAddress: "accountB@example.com",
},
},
}
)
})
}
func TestGetAccountId(t *testing.T) {
req := &http.Request{
@ -36,22 +40,22 @@ func TestGetAccountId(t *testing.T) {
}
//case1
//accountId: "admin"
req.Header.Set(s3_constants.AmzAccountId, s3account.AccountAdmin.Id)
if GetAccountId(req) != s3account.AccountAdmin.Id {
req.Header.Set(s3_constants.AmzAccountId, s3_constants.AccountAdminId)
if GetAccountId(req) != s3_constants.AccountAdminId {
t.Fatal("expect accountId: admin")
}
//case2
//accountId: "anoymous"
req.Header.Set(s3_constants.AmzAccountId, s3account.AccountAnonymous.Id)
if GetAccountId(req) != s3account.AccountAnonymous.Id {
req.Header.Set(s3_constants.AmzAccountId, s3_constants.AccountAnonymousId)
if GetAccountId(req) != s3_constants.AccountAnonymousId {
t.Fatal("expect accountId: anonymous")
}
//case3
//accountId is nil => "anonymous"
req.Header.Del(s3_constants.AmzAccountId)
if GetAccountId(req) != s3account.AccountAnonymous.Id {
if GetAccountId(req) != s3_constants.AccountAnonymousId {
t.Fatal("expect accountId: anonymous")
}
}
@ -64,7 +68,6 @@ func TestExtractAcl(t *testing.T) {
}
testCases := make([]*Case, 0)
accountAdminId := "admin"
{
//case1 (good case)
//parse acp from request body
@ -249,14 +252,14 @@ func TestParseAndValidateAclHeaders(t *testing.T) {
{
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeCanonicalUser,
ID: &s3account.AccountAnonymous.Id,
ID: aws.String(s3_constants.AccountAnonymousId),
},
Permission: &s3_constants.PermissionFullControl,
},
{
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeCanonicalUser,
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
},
Permission: &s3_constants.PermissionFullControl,
},
@ -391,7 +394,7 @@ func grantsEquals(a, b []*s3.Grant) bool {
func TestDetermineReqGrants(t *testing.T) {
{
//case1: request account is anonymous
accountId := s3account.AccountAnonymous.Id
accountId := s3_constants.AccountAnonymousId
reqPermission := s3_constants.PermissionRead
resultGrants := DetermineReqGrants(accountId, reqPermission)
@ -496,7 +499,7 @@ func TestAssembleEntryWithAcp(t *testing.T) {
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeGroup,
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
URI: &s3_constants.GranteeGroupAllUsers,
},
},
@ -569,13 +572,13 @@ func TestGrantEquals(t *testing.T) {
GrantEquals(&s3.Grant{
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
ID: &s3account.AccountAdmin.Id,
EmailAddress: &s3account.AccountAdmin.EmailAddress,
ID: aws.String(s3_constants.AccountAdminId),
//EmailAddress: &s3account.AccountAdmin.EmailAddress,
},
}, &s3.Grant{
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
},
}): true,
@ -623,13 +626,13 @@ func TestGrantEquals(t *testing.T) {
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeGroup,
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
},
}, &s3.Grant{
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeGroup,
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
},
}): true,
@ -637,14 +640,14 @@ func TestGrantEquals(t *testing.T) {
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeGroup,
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
URI: &s3_constants.GranteeGroupAllUsers,
},
}, &s3.Grant{
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeGroup,
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
},
}): false,
@ -652,7 +655,7 @@ func TestGrantEquals(t *testing.T) {
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeGroup,
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
URI: &s3_constants.GranteeGroupAllUsers,
},
}, &s3.Grant{
@ -692,7 +695,7 @@ func TestSetAcpGrantsHeader(t *testing.T) {
Permission: &s3_constants.PermissionRead,
Grantee: &s3.Grantee{
Type: &s3_constants.GrantTypeGroup,
ID: &s3account.AccountAdmin.Id,
ID: aws.String(s3_constants.AccountAdminId),
URI: &s3_constants.GranteeGroupAllUsers,
},
},

5
weed/s3api/s3api_acp.go

@ -2,7 +2,6 @@ package s3api
import (
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"net/http"
)
@ -10,7 +9,7 @@ import (
func getAccountId(r *http.Request) string {
id := r.Header.Get(s3_constants.AmzAccountId)
if len(id) == 0 {
return s3account.AccountAnonymous.Id
return AccountAnonymous.Id
} else {
return id
}
@ -22,7 +21,7 @@ func (s3a *S3ApiServer) checkAccessByOwnership(r *http.Request, bucket string) s
return errCode
}
accountId := getAccountId(r)
if accountId == s3account.AccountAdmin.Id || accountId == *metadata.Owner.ID {
if accountId == AccountAdmin.Id || accountId == *metadata.Owner.ID {
return s3err.ErrNone
}
return s3err.ErrAccessDenied

11
weed/s3api/s3api_bucket_handlers.go

@ -259,17 +259,18 @@ func (s3a *S3ApiServer) GetBucketAclHandler(w http.ResponseWriter, r *http.Reque
return
}
identityId := r.Header.Get(s3_constants.AmzIdentityId)
amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
amzDisplayName := s3a.iam.GetAccountNameById(amzAccountId)
response := AccessControlPolicy{
Owner: CanonicalUser{
ID: identityId,
DisplayName: identityId,
ID: amzAccountId,
DisplayName: amzDisplayName,
},
}
response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
Grantee: Grantee{
ID: identityId,
DisplayName: identityId,
ID: amzAccountId,
DisplayName: amzDisplayName,
Type: "CanonicalUser",
XMLXSI: "CanonicalUser",
XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},

3
weed/s3api/s3api_server.go

@ -5,7 +5,6 @@ import (
"fmt"
"github.com/seaweedfs/seaweedfs/weed/filer"
"github.com/seaweedfs/seaweedfs/weed/pb/s3_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
"net"
"net/http"
"strings"
@ -42,7 +41,6 @@ type S3ApiServer struct {
randomClientId int32
filerGuard *security.Guard
client *http.Client
accountManager *s3account.AccountManager
bucketRegistry *BucketRegistry
}
@ -63,7 +61,6 @@ func NewS3ApiServer(router *mux.Router, option *S3ApiServerOption) (s3ApiServer
filerGuard: security.NewGuard([]string{}, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec),
cb: NewCircuitBreaker(option),
}
s3ApiServer.accountManager = s3account.NewAccountManager(s3ApiServer)
s3ApiServer.bucketRegistry = NewBucketRegistry(s3ApiServer)
if option.LocalFilerSocket == "" {
s3ApiServer.client = &http.Client{Transport: &http.Transport{

Loading…
Cancel
Save