diff --git a/Makefile b/Makefile index 315a62428..6ca6cae0d 100644 --- a/Makefile +++ b/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 ./... diff --git a/docker/compose/s3.json b/docker/compose/s3.json index 64dedb681..ce230863b 100644 --- a/docker/compose/s3.json +++ b/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" + } + ] } \ No newline at end of file diff --git a/docker/compose/s3tests.conf b/docker/compose/s3tests.conf index 2bffe20d4..68d9ddeb7 100644 --- a/docker/compose/s3tests.conf +++ b/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 diff --git a/weed/pb/iam.proto b/weed/pb/iam.proto index 1a6027292..99bb65ef2 100644 --- a/weed/pb/iam.proto +++ b/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; diff --git a/weed/pb/iam_pb/iam.pb.go b/weed/pb/iam_pb/iam.pb.go index 3c5578833..074e255e6 100644 --- a/weed/pb/iam_pb/iam.pb.go +++ b/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, }, diff --git a/weed/pb/iam_pb/iam_grpc.pb.go b/weed/pb/iam_pb/iam_grpc.pb.go index d3aee785d..c4fe7becc 100644 --- a/weed/pb/iam_pb/iam_grpc.pb.go +++ b/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. diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go index 38ff2b5ca..a2b1fd90f 100644 --- a/weed/s3api/auth_credentials.go +++ b/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 } diff --git a/weed/s3api/auth_credentials_test.go b/weed/s3api/auth_credentials_test.go index 0bbcce706..f9f87fc54 100644 --- a/weed/s3api/auth_credentials_test.go +++ b/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) } } } diff --git a/weed/s3api/bucket_metadata.go b/weed/s3api/bucket_metadata.go index f4088e6b3..d1f487104 100644 --- a/weed/s3api/bucket_metadata.go +++ b/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{ diff --git a/weed/s3api/bucket_metadata_test.go b/weed/s3api/bucket_metadata_test.go index f852a272a..16d20b8fe 100644 --- a/weed/s3api/bucket_metadata_test.go +++ b/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) } diff --git a/weed/s3api/s3_constants/s3_acp.go b/weed/s3api/s3_constants/s3_acp.go new file mode 100644 index 000000000..d24e07e24 --- /dev/null +++ b/weed/s3api/s3_constants/s3_acp.go @@ -0,0 +1,6 @@ +package s3_constants + +const ( + AccountAnonymousId = "anonymous" + AccountAdminId = "admin" +) diff --git a/weed/s3api/s3account/s3_account.go b/weed/s3api/s3account/s3_account.go deleted file mode 100644 index 9b1b01123..000000000 --- a/weed/s3api/s3account/s3_account.go +++ /dev/null @@ -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 - } -} diff --git a/weed/s3api/s3acl/acl_helper.go b/weed/s3api/s3api_acl_helper.go similarity index 93% rename from weed/s3api/s3acl/acl_helper.go rename to weed/s3api/s3api_acl_helper.go index bb956368e..0332b6a39 100644 --- a/weed/s3api/s3acl/acl_helper.go +++ b/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, diff --git a/weed/s3api/s3acl/acl_helper_test.go b/weed/s3api/s3api_acl_helper_test.go similarity index 92% rename from weed/s3api/s3acl/acl_helper_test.go rename to weed/s3api/s3api_acl_helper_test.go index ce177595b..81ce25575 100644 --- a/weed/s3api/s3acl/acl_helper_test.go +++ b/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", +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", + }, }, - EmailIdMapping: map[string]string{ - s3account.AccountAdmin.EmailAddress: s3account.AccountAdmin.Id, - s3account.AccountAnonymous.EmailAddress: s3account.AccountAnonymous.Id, - "accountA@example.com": "accountA", - "accountBexample.com": "accountB", - }, - } -) + }) +} 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, }, }, diff --git a/weed/s3api/s3api_acp.go b/weed/s3api/s3api_acp.go index 7a76c2a67..0a79990f5 100644 --- a/weed/s3api/s3api_acp.go +++ b/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 diff --git a/weed/s3api/s3api_bucket_handlers.go b/weed/s3api/s3api_bucket_handlers.go index d2e987a25..3bf5a598b 100644 --- a/weed/s3api/s3api_bucket_handlers.go +++ b/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"}, diff --git a/weed/s3api/s3api_server.go b/weed/s3api/s3api_server.go index 77c488f4d..e38aae23d 100644 --- a/weed/s3api/s3api_server.go +++ b/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{