mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2024-01-19 02:48:24 +00:00
[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>
This commit is contained in:
parent
c9177c92e5
commit
f8b94cac0e
5
Makefile
5
Makefile
|
@ -10,5 +10,8 @@ install:
|
||||||
full_install:
|
full_install:
|
||||||
cd weed; go install -tags "elastic gocdk sqlite ydb tikv"
|
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 ./...
|
cd weed; go test -tags "elastic gocdk sqlite ydb tikv" -v ./...
|
||||||
|
|
|
@ -40,7 +40,10 @@
|
||||||
"List",
|
"List",
|
||||||
"Tagging",
|
"Tagging",
|
||||||
"Write"
|
"Write"
|
||||||
]
|
],
|
||||||
|
"account": {
|
||||||
|
"id": "testid"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "s3_tests_alt",
|
"name": "s3_tests_alt",
|
||||||
|
@ -101,5 +104,12 @@
|
||||||
"Write"
|
"Write"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"id" : "testid",
|
||||||
|
"displayName": "M. Tester",
|
||||||
|
"emailAddress": "tester@ceph.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
|
@ -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 = s3_tests
|
display_name = M. Tester
|
||||||
|
|
||||||
# main user_idname set in vstart.sh
|
# main user_idname set in vstart.sh
|
||||||
user_id = s3_tests
|
user_id = testid
|
||||||
|
|
||||||
# main email set in vstart.sh
|
# main email set in vstart.sh
|
||||||
email = tester@ceph.com
|
email = tester@ceph.com
|
||||||
|
|
|
@ -16,13 +16,14 @@ service SeaweedIdentityAccessManagement {
|
||||||
|
|
||||||
message S3ApiConfiguration {
|
message S3ApiConfiguration {
|
||||||
repeated Identity identities = 1;
|
repeated Identity identities = 1;
|
||||||
|
repeated Account accounts = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Identity {
|
message Identity {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
repeated Credential credentials = 2;
|
repeated Credential credentials = 2;
|
||||||
repeated string actions = 3;
|
repeated string actions = 3;
|
||||||
string accountId = 4;
|
Account account = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Credential {
|
message Credential {
|
||||||
|
@ -32,6 +33,12 @@ message Credential {
|
||||||
// bool is_disabled = 4;
|
// bool is_disabled = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Account {
|
||||||
|
string id = 1;
|
||||||
|
string display_name = 2;
|
||||||
|
string email_address = 3;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
message Policy {
|
message Policy {
|
||||||
repeated Statement statements = 1;
|
repeated Statement statements = 1;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.30.0
|
// protoc-gen-go v1.30.0
|
||||||
// protoc v4.23.3
|
// protoc v4.23.2
|
||||||
// source: iam.proto
|
// source: iam.proto
|
||||||
|
|
||||||
package iam_pb
|
package iam_pb
|
||||||
|
@ -26,6 +26,7 @@ type S3ApiConfiguration struct {
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Identities []*Identity `protobuf:"bytes,1,rep,name=identities,proto3" json:"identities,omitempty"`
|
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() {
|
func (x *S3ApiConfiguration) Reset() {
|
||||||
|
@ -67,6 +68,13 @@ func (x *S3ApiConfiguration) GetIdentities() []*Identity {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *S3ApiConfiguration) GetAccounts() []*Account {
|
||||||
|
if x != nil {
|
||||||
|
return x.Accounts
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@ -75,7 +83,7 @@ type Identity struct {
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
Credentials []*Credential `protobuf:"bytes,2,rep,name=credentials,proto3" json:"credentials,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"`
|
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() {
|
func (x *Identity) Reset() {
|
||||||
|
@ -131,11 +139,11 @@ func (x *Identity) GetActions() []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Identity) GetAccountId() string {
|
func (x *Identity) GetAccount() *Account {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.AccountId
|
return x.Account
|
||||||
}
|
}
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Credential struct {
|
type Credential struct {
|
||||||
|
@ -193,36 +201,109 @@ func (x *Credential) GetSecretKey() string {
|
||||||
return ""
|
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 protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_iam_proto_rawDesc = []byte{
|
var file_iam_proto_rawDesc = []byte{
|
||||||
0x0a, 0x09, 0x69, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x69, 0x61, 0x6d,
|
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,
|
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,
|
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,
|
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,
|
0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x61,
|
||||||
0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x0b,
|
0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08,
|
||||||
0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x08, 0x49, 0x64, 0x65,
|
||||||
0x0b, 0x32, 0x12, 0x2e, 0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65,
|
0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
|
||||||
0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,
|
0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x0b, 0x63, 0x72, 0x65,
|
||||||
0x6c, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
|
0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12,
|
||||||
0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09,
|
0x2e, 0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
|
||||||
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x61, 0x6c, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12,
|
||||||
0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x4a, 0x0a, 0x0a, 0x43, 0x72,
|
0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
|
||||||
0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x65,
|
0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x61, 0x63, 0x63,
|
||||||
0x73, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63,
|
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x69, 0x61, 0x6d,
|
||||||
0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65,
|
0x5f, 0x70, 0x62, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x07, 0x61, 0x63, 0x63,
|
||||||
0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x63,
|
0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4a, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,
|
||||||
0x72, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x32, 0x21, 0x0a, 0x1f, 0x53, 0x65, 0x61, 0x77, 0x65, 0x65,
|
0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6b, 0x65, 0x79,
|
||||||
0x64, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4d,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x4b, 0x0a, 0x10, 0x73, 0x65, 0x61,
|
0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18,
|
||||||
0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x08, 0x49,
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b, 0x65, 0x79,
|
||||||
0x61, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
0x22, 0x61, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||||
0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x61, 0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x73, 0x65,
|
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64,
|
||||||
0x61, 0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70, 0x62, 0x2f,
|
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x69, 0x61, 0x6d, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
var (
|
||||||
|
@ -237,20 +318,23 @@ func file_iam_proto_rawDescGZIP() []byte {
|
||||||
return file_iam_proto_rawDescData
|
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{}{
|
var file_iam_proto_goTypes = []interface{}{
|
||||||
(*S3ApiConfiguration)(nil), // 0: iam_pb.S3ApiConfiguration
|
(*S3ApiConfiguration)(nil), // 0: iam_pb.S3ApiConfiguration
|
||||||
(*Identity)(nil), // 1: iam_pb.Identity
|
(*Identity)(nil), // 1: iam_pb.Identity
|
||||||
(*Credential)(nil), // 2: iam_pb.Credential
|
(*Credential)(nil), // 2: iam_pb.Credential
|
||||||
|
(*Account)(nil), // 3: iam_pb.Account
|
||||||
}
|
}
|
||||||
var file_iam_proto_depIdxs = []int32{
|
var file_iam_proto_depIdxs = []int32{
|
||||||
1, // 0: iam_pb.S3ApiConfiguration.identities:type_name -> iam_pb.Identity
|
1, // 0: iam_pb.S3ApiConfiguration.identities:type_name -> iam_pb.Identity
|
||||||
2, // 1: iam_pb.Identity.credentials:type_name -> iam_pb.Credential
|
3, // 1: iam_pb.S3ApiConfiguration.accounts:type_name -> iam_pb.Account
|
||||||
2, // [2:2] is the sub-list for method output_type
|
2, // 2: iam_pb.Identity.credentials:type_name -> iam_pb.Credential
|
||||||
2, // [2:2] is the sub-list for method input_type
|
3, // 3: iam_pb.Identity.account:type_name -> iam_pb.Account
|
||||||
2, // [2:2] is the sub-list for extension type_name
|
4, // [4:4] is the sub-list for method output_type
|
||||||
2, // [2:2] is the sub-list for extension extendee
|
4, // [4:4] is the sub-list for method input_type
|
||||||
0, // [0:2] is the sub-list for field type_name
|
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() }
|
func init() { file_iam_proto_init() }
|
||||||
|
@ -295,6 +379,18 @@ func file_iam_proto_init() {
|
||||||
return nil
|
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{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
|
@ -302,7 +398,7 @@ func file_iam_proto_init() {
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_iam_proto_rawDesc,
|
RawDescriptor: file_iam_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 3,
|
NumMessages: 4,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.2.0
|
// - protoc-gen-go-grpc v1.3.0
|
||||||
// - protoc v4.23.3
|
// - protoc v4.23.2
|
||||||
// source: iam.proto
|
// source: iam.proto
|
||||||
|
|
||||||
package iam_pb
|
package iam_pb
|
||||||
|
@ -15,6 +15,8 @@ import (
|
||||||
// Requires gRPC-Go v1.32.0 or later.
|
// Requires gRPC-Go v1.32.0 or later.
|
||||||
const _ = grpc.SupportPackageIsVersion7
|
const _ = grpc.SupportPackageIsVersion7
|
||||||
|
|
||||||
|
const ()
|
||||||
|
|
||||||
// SeaweedIdentityAccessManagementClient is the client API for SeaweedIdentityAccessManagement service.
|
// 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.
|
// 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.
|
||||||
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
|
|
||||||
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||||
|
@ -29,6 +27,8 @@ type IdentityAccessManagement struct {
|
||||||
|
|
||||||
identities []*Identity
|
identities []*Identity
|
||||||
accessKeyIdent map[string]*Identity
|
accessKeyIdent map[string]*Identity
|
||||||
|
accounts map[string]*Account
|
||||||
|
emailAccount map[string]*Account
|
||||||
hashes map[string]*sync.Pool
|
hashes map[string]*sync.Pool
|
||||||
hashCounters map[string]*int32
|
hashCounters map[string]*int32
|
||||||
identityAnonymous *Identity
|
identityAnonymous *Identity
|
||||||
|
@ -39,20 +39,50 @@ type IdentityAccessManagement struct {
|
||||||
|
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
Name string
|
Name string
|
||||||
AccountId string
|
Account *Account
|
||||||
Credentials []*Credential
|
Credentials []*Credential
|
||||||
Actions []Action
|
Actions []Action
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Identity) isAnonymous() bool {
|
// Account represents a system user, a system user can
|
||||||
return i.Name == s3account.AccountAnonymous.Name
|
// 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 {
|
type Credential struct {
|
||||||
AccessKey string
|
AccessKey string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Identity) isAnonymous() bool {
|
||||||
|
return i.Account.Id == s3_constants.AccountAnonymousId
|
||||||
|
}
|
||||||
|
|
||||||
func (action Action) isAdmin() bool {
|
func (action Action) isAdmin() bool {
|
||||||
return strings.HasPrefix(string(action), s3_constants.ACTION_ADMIN)
|
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), ":*")
|
return strings.HasSuffix(string(action), ":"+bucket) || strings.HasSuffix(string(action), ":*")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "Permission": "FULL_CONTROL"|"WRITE"|"WRITE_ACP"|"READ"|"READ_ACP"
|
||||||
func (action Action) getPermission() Permission {
|
func (action Action) getPermission() Permission {
|
||||||
switch act := strings.Split(string(action), ":")[0]; act {
|
switch act := strings.Split(string(action), ":")[0]; act {
|
||||||
case s3_constants.ACTION_ADMIN:
|
case s3_constants.ACTION_ADMIN:
|
||||||
return Permission("FULL_CONTROL")
|
return Permission("FULL_CONTROL")
|
||||||
case s3_constants.ACTION_WRITE:
|
case s3_constants.ACTION_WRITE:
|
||||||
return Permission("WRITE")
|
return Permission("WRITE")
|
||||||
|
case s3_constants.ACTION_WRITE_ACP:
|
||||||
|
return Permission("WRITE_ACP")
|
||||||
case s3_constants.ACTION_READ:
|
case s3_constants.ACTION_READ:
|
||||||
return Permission("READ")
|
return Permission("READ")
|
||||||
|
case s3_constants.ACTION_READ_ACP:
|
||||||
|
return Permission("READ_ACP")
|
||||||
default:
|
default:
|
||||||
return Permission("")
|
return Permission("")
|
||||||
}
|
}
|
||||||
|
@ -138,26 +173,69 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
|
||||||
var identities []*Identity
|
var identities []*Identity
|
||||||
var identityAnonymous *Identity
|
var identityAnonymous *Identity
|
||||||
accessKeyIdent := make(map[string]*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 {
|
for _, ident := range config.Identities {
|
||||||
t := &Identity{
|
t := &Identity{
|
||||||
Name: ident.Name,
|
Name: ident.Name,
|
||||||
AccountId: s3account.AccountAdmin.Id,
|
|
||||||
Credentials: nil,
|
Credentials: nil,
|
||||||
Actions: nil,
|
Actions: nil,
|
||||||
}
|
}
|
||||||
|
switch {
|
||||||
if ident.Name == s3account.AccountAnonymous.Name {
|
case ident.Name == AccountAnonymous.Id:
|
||||||
if ident.AccountId != "" && ident.AccountId != s3account.AccountAnonymous.Id {
|
t.Account = &AccountAnonymous
|
||||||
glog.Warningf("anonymous identity is associated with a non-anonymous account ID, the association is invalid")
|
|
||||||
}
|
|
||||||
t.AccountId = s3account.AccountAnonymous.Id
|
|
||||||
identityAnonymous = t
|
identityAnonymous = t
|
||||||
} else {
|
case ident.Account == nil:
|
||||||
if len(ident.AccountId) > 0 {
|
t.Account = &AccountAdmin
|
||||||
t.AccountId = ident.AccountId
|
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 {
|
for _, action := range ident.Actions {
|
||||||
t.Actions = append(t.Actions, Action(action))
|
t.Actions = append(t.Actions, Action(action))
|
||||||
}
|
}
|
||||||
|
@ -170,15 +248,19 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
|
||||||
}
|
}
|
||||||
identities = append(identities, t)
|
identities = append(identities, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
iam.m.Lock()
|
iam.m.Lock()
|
||||||
// atomically switch
|
// atomically switch
|
||||||
iam.identities = identities
|
iam.identities = identities
|
||||||
iam.identityAnonymous = identityAnonymous
|
iam.identityAnonymous = identityAnonymous
|
||||||
|
iam.accounts = accounts
|
||||||
|
iam.emailAccount = emailAccount
|
||||||
iam.accessKeyIdent = accessKeyIdent
|
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
|
||||||
}
|
}
|
||||||
iam.m.Unlock()
|
iam.m.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +291,24 @@ func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, foun
|
||||||
return nil, false
|
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 {
|
func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !iam.isEnabled() {
|
if !iam.isEnabled() {
|
||||||
|
@ -287,9 +387,8 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action)
|
||||||
return identity, s3err.ErrAccessDenied
|
return identity, s3err.ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if !identity.isAnonymous() {
|
r.Header.Set(s3_constants.AmzAccountId, identity.Account.Id)
|
||||||
r.Header.Set(s3_constants.AmzAccountId, identity.AccountId)
|
|
||||||
}
|
|
||||||
return identity, s3err.ErrNone
|
return identity, s3err.ErrNone
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,12 @@ package s3api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
jsonpb "google.golang.org/protobuf/encoding/protojson"
|
|
||||||
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||||
|
jsonpb "google.golang.org/protobuf/encoding/protojson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIdentityListFileFormat(t *testing.T) {
|
func TestIdentityListFileFormat(t *testing.T) {
|
||||||
|
@ -146,11 +144,22 @@ func TestCanDo(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoadS3ApiConfigurationTestCase struct {
|
type LoadS3ApiConfigurationTestCase struct {
|
||||||
|
pbAccount *iam_pb.Account
|
||||||
pbIdent *iam_pb.Identity
|
pbIdent *iam_pb.Identity
|
||||||
expectIdent *Identity
|
expectIdent *Identity
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadS3ApiConfiguration(t *testing.T) {
|
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{
|
testCases := map[string]*LoadS3ApiConfigurationTestCase{
|
||||||
"notSpecifyAccountId": {
|
"notSpecifyAccountId": {
|
||||||
pbIdent: &iam_pb.Identity{
|
pbIdent: &iam_pb.Identity{
|
||||||
|
@ -167,8 +176,8 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectIdent: &Identity{
|
expectIdent: &Identity{
|
||||||
Name: "notSpecifyAccountId",
|
Name: "notSpecifyAccountId",
|
||||||
AccountId: s3account.AccountAdmin.Id,
|
Account: &AccountAdmin,
|
||||||
Actions: []Action{
|
Actions: []Action{
|
||||||
"Read",
|
"Read",
|
||||||
"Write",
|
"Write",
|
||||||
|
@ -182,17 +191,18 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"specifiedAccountID": {
|
"specifiedAccountID": {
|
||||||
|
pbAccount: &pbSpecifiedAccount,
|
||||||
pbIdent: &iam_pb.Identity{
|
pbIdent: &iam_pb.Identity{
|
||||||
Name: "specifiedAccountID",
|
Name: "specifiedAccountID",
|
||||||
AccountId: "specifiedAccountID",
|
Account: &pbSpecifiedAccount,
|
||||||
Actions: []string{
|
Actions: []string{
|
||||||
"Read",
|
"Read",
|
||||||
"Write",
|
"Write",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectIdent: &Identity{
|
expectIdent: &Identity{
|
||||||
Name: "specifiedAccountID",
|
Name: "specifiedAccountID",
|
||||||
AccountId: "specifiedAccountID",
|
Account: &specifiedAccount,
|
||||||
Actions: []Action{
|
Actions: []Action{
|
||||||
"Read",
|
"Read",
|
||||||
"Write",
|
"Write",
|
||||||
|
@ -208,8 +218,8 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectIdent: &Identity{
|
expectIdent: &Identity{
|
||||||
Name: "anonymous",
|
Name: "anonymous",
|
||||||
AccountId: "anonymous",
|
Account: &AccountAnonymous,
|
||||||
Actions: []Action{
|
Actions: []Action{
|
||||||
"Read",
|
"Read",
|
||||||
"Write",
|
"Write",
|
||||||
|
@ -223,6 +233,9 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, v := range testCases {
|
for _, v := range testCases {
|
||||||
config.Identities = append(config.Identities, v.pbIdent)
|
config.Identities = append(config.Identities, v.pbIdent)
|
||||||
|
if v.pbAccount != nil {
|
||||||
|
config.Accounts = append(config.Accounts, v.pbAccount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iam := IdentityAccessManagement{}
|
iam := IdentityAccessManagement{}
|
||||||
|
@ -234,7 +247,7 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
|
||||||
for _, ident := range iam.identities {
|
for _, ident := range iam.identities {
|
||||||
tc := testCases[ident.Name]
|
tc := testCases[ident.Name]
|
||||||
if !reflect.DeepEqual(ident, tc.expectIdent) {
|
if !reflect.DeepEqual(ident, tc.expectIdent) {
|
||||||
t.Error("not expect")
|
t.Errorf("not expect for ident name %s", ident.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
"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/s3api/s3err"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||||
"math"
|
"math"
|
||||||
|
@ -19,7 +18,7 @@ var loadBucketMetadataFromFiler = func(r *BucketRegistry, bucketName string) (*B
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildBucketMetadata(r.s3a.accountManager, entry), nil
|
return buildBucketMetadata(r.s3a.iam, entry), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type BucketMetaData struct {
|
type BucketMetaData struct {
|
||||||
|
@ -73,13 +72,13 @@ func (r *BucketRegistry) init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BucketRegistry) LoadBucketMetadata(entry *filer_pb.Entry) {
|
func (r *BucketRegistry) LoadBucketMetadata(entry *filer_pb.Entry) {
|
||||||
bucketMetadata := buildBucketMetadata(r.s3a.accountManager, entry)
|
bucketMetadata := buildBucketMetadata(r.s3a.iam, entry)
|
||||||
r.metadataCacheLock.Lock()
|
r.metadataCacheLock.Lock()
|
||||||
defer r.metadataCacheLock.Unlock()
|
defer r.metadataCacheLock.Unlock()
|
||||||
r.metadataCache[entry.Name] = bucketMetadata
|
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)
|
entryJson, _ := json.Marshal(entry)
|
||||||
glog.V(3).Infof("build bucket metadata,entry=%s", entryJson)
|
glog.V(3).Infof("build bucket metadata,entry=%s", entryJson)
|
||||||
bucketMetadata := &BucketMetaData{
|
bucketMetadata := &BucketMetaData{
|
||||||
|
@ -90,8 +89,8 @@ func buildBucketMetadata(accountManager *s3account.AccountManager, entry *filer_
|
||||||
|
|
||||||
// Default owner: `AccountAdmin`
|
// Default owner: `AccountAdmin`
|
||||||
Owner: &s3.Owner{
|
Owner: &s3.Owner{
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: &AccountAdmin.Id,
|
||||||
DisplayName: &s3account.AccountAdmin.Name,
|
DisplayName: &AccountAdmin.DisplayName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if entry.Extended != nil {
|
if entry.Extended != nil {
|
||||||
|
@ -112,8 +111,8 @@ func buildBucketMetadata(accountManager *s3account.AccountManager, entry *filer_
|
||||||
acpOwnerBytes, ok := entry.Extended[s3_constants.ExtAmzOwnerKey]
|
acpOwnerBytes, ok := entry.Extended[s3_constants.ExtAmzOwnerKey]
|
||||||
if ok && len(acpOwnerBytes) > 0 {
|
if ok && len(acpOwnerBytes) > 0 {
|
||||||
ownerAccountId := string(acpOwnerBytes)
|
ownerAccountId := string(acpOwnerBytes)
|
||||||
ownerAccountName, exists := accountManager.IdNameMapping[ownerAccountId]
|
ownerAccountName := accountManager.GetAccountNameById(ownerAccountId)
|
||||||
if !exists {
|
if ownerAccountName == "" {
|
||||||
glog.Warningf("owner[id=%s] is invalid, bucket: %s", ownerAccountId, bucketMetadata.Name)
|
glog.Warningf("owner[id=%s] is invalid, bucket: %s", ownerAccountId, bucketMetadata.Name)
|
||||||
} else {
|
} else {
|
||||||
bucketMetadata.Owner = &s3.Owner{
|
bucketMetadata.Owner = &s3.Owner{
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/aws/aws-sdk-go/service/s3"
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
"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/s3_constants"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -31,7 +31,7 @@ var (
|
||||||
Name: "entryWithValidAcp",
|
Name: "entryWithValidAcp",
|
||||||
Extended: map[string][]byte{
|
Extended: map[string][]byte{
|
||||||
s3_constants.ExtOwnershipKey: []byte(s3_constants.OwnershipBucketOwnerEnforced),
|
s3_constants.ExtOwnershipKey: []byte(s3_constants.OwnershipBucketOwnerEnforced),
|
||||||
s3_constants.ExtAmzOwnerKey: []byte(s3account.AccountAdmin.Name),
|
s3_constants.ExtAmzOwnerKey: []byte(AccountAdmin.DisplayName),
|
||||||
s3_constants.ExtAmzAclKey: goodEntryAcl,
|
s3_constants.ExtAmzAclKey: goodEntryAcl,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -88,8 +88,8 @@ var tcs = []*BucketMetadataTestCase{
|
||||||
Name: badEntry.Name,
|
Name: badEntry.Name,
|
||||||
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
||||||
Owner: &s3.Owner{
|
Owner: &s3.Owner{
|
||||||
DisplayName: &s3account.AccountAdmin.Name,
|
DisplayName: &AccountAdmin.DisplayName,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: &AccountAdmin.Id,
|
||||||
},
|
},
|
||||||
Acl: nil,
|
Acl: nil,
|
||||||
},
|
},
|
||||||
|
@ -99,8 +99,8 @@ var tcs = []*BucketMetadataTestCase{
|
||||||
Name: goodEntry.Name,
|
Name: goodEntry.Name,
|
||||||
ObjectOwnership: s3_constants.OwnershipBucketOwnerEnforced,
|
ObjectOwnership: s3_constants.OwnershipBucketOwnerEnforced,
|
||||||
Owner: &s3.Owner{
|
Owner: &s3.Owner{
|
||||||
DisplayName: &s3account.AccountAdmin.Name,
|
DisplayName: &AccountAdmin.DisplayName,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: &AccountAdmin.Id,
|
||||||
},
|
},
|
||||||
Acl: s3_constants.PublicRead,
|
Acl: s3_constants.PublicRead,
|
||||||
},
|
},
|
||||||
|
@ -110,8 +110,8 @@ var tcs = []*BucketMetadataTestCase{
|
||||||
Name: ownershipEmptyStr.Name,
|
Name: ownershipEmptyStr.Name,
|
||||||
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
||||||
Owner: &s3.Owner{
|
Owner: &s3.Owner{
|
||||||
DisplayName: &s3account.AccountAdmin.Name,
|
DisplayName: &AccountAdmin.DisplayName,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: &AccountAdmin.Id,
|
||||||
},
|
},
|
||||||
Acl: nil,
|
Acl: nil,
|
||||||
},
|
},
|
||||||
|
@ -121,8 +121,8 @@ var tcs = []*BucketMetadataTestCase{
|
||||||
Name: ownershipValid.Name,
|
Name: ownershipValid.Name,
|
||||||
ObjectOwnership: s3_constants.OwnershipBucketOwnerEnforced,
|
ObjectOwnership: s3_constants.OwnershipBucketOwnerEnforced,
|
||||||
Owner: &s3.Owner{
|
Owner: &s3.Owner{
|
||||||
DisplayName: &s3account.AccountAdmin.Name,
|
DisplayName: &AccountAdmin.DisplayName,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: &AccountAdmin.Id,
|
||||||
},
|
},
|
||||||
Acl: nil,
|
Acl: nil,
|
||||||
},
|
},
|
||||||
|
@ -132,8 +132,8 @@ var tcs = []*BucketMetadataTestCase{
|
||||||
Name: acpEmptyStr.Name,
|
Name: acpEmptyStr.Name,
|
||||||
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
||||||
Owner: &s3.Owner{
|
Owner: &s3.Owner{
|
||||||
DisplayName: &s3account.AccountAdmin.Name,
|
DisplayName: &AccountAdmin.DisplayName,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: &AccountAdmin.Id,
|
||||||
},
|
},
|
||||||
Acl: nil,
|
Acl: nil,
|
||||||
},
|
},
|
||||||
|
@ -143,8 +143,8 @@ var tcs = []*BucketMetadataTestCase{
|
||||||
Name: acpEmptyObject.Name,
|
Name: acpEmptyObject.Name,
|
||||||
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
||||||
Owner: &s3.Owner{
|
Owner: &s3.Owner{
|
||||||
DisplayName: &s3account.AccountAdmin.Name,
|
DisplayName: &AccountAdmin.DisplayName,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: &AccountAdmin.Id,
|
||||||
},
|
},
|
||||||
Acl: nil,
|
Acl: nil,
|
||||||
},
|
},
|
||||||
|
@ -154,8 +154,8 @@ var tcs = []*BucketMetadataTestCase{
|
||||||
Name: acpOwnerNil.Name,
|
Name: acpOwnerNil.Name,
|
||||||
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
ObjectOwnership: s3_constants.DefaultOwnershipForExists,
|
||||||
Owner: &s3.Owner{
|
Owner: &s3.Owner{
|
||||||
DisplayName: &s3account.AccountAdmin.Name,
|
DisplayName: &AccountAdmin.DisplayName,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: &AccountAdmin.Id,
|
||||||
},
|
},
|
||||||
Acl: make([]*s3.Grant, 0),
|
Acl: make([]*s3.Grant, 0),
|
||||||
},
|
},
|
||||||
|
@ -163,14 +163,10 @@ var tcs = []*BucketMetadataTestCase{
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildBucketMetadata(t *testing.T) {
|
func TestBuildBucketMetadata(t *testing.T) {
|
||||||
accountManager := &s3account.AccountManager{
|
iam := &IdentityAccessManagement{}
|
||||||
IdNameMapping: map[string]string{
|
_ = iam.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{})
|
||||||
s3account.AccountAdmin.Id: s3account.AccountAdmin.Name,
|
|
||||||
s3account.AccountAnonymous.Id: s3account.AccountAnonymous.Name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
resultBucketMetadata := buildBucketMetadata(accountManager, tc.filerEntry)
|
resultBucketMetadata := buildBucketMetadata(iam, tc.filerEntry)
|
||||||
if !reflect.DeepEqual(resultBucketMetadata, tc.expectBucketMetadata) {
|
if !reflect.DeepEqual(resultBucketMetadata, tc.expectBucketMetadata) {
|
||||||
t.Fatalf("result is unexpect: \nresult: %v, \nexpect: %v", resultBucketMetadata, tc.expectBucketMetadata)
|
t.Fatalf("result is unexpect: \nresult: %v, \nexpect: %v", resultBucketMetadata, tc.expectBucketMetadata)
|
||||||
}
|
}
|
||||||
|
|
6
weed/s3api/s3_constants/s3_acp.go
Normal file
6
weed/s3api/s3_constants/s3_acp.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package s3_constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccountAnonymousId = "anonymous"
|
||||||
|
AccountAdminId = "admin"
|
||||||
|
)
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package s3acl
|
package s3api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -8,25 +8,29 @@ import (
|
||||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
"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/s3api/s3err"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"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
|
// GetAccountId get AccountId from request headers, AccountAnonymousId will be return if not presen
|
||||||
func GetAccountId(r *http.Request) string {
|
func GetAccountId(r *http.Request) string {
|
||||||
id := r.Header.Get(s3_constants.AmzAccountId)
|
id := r.Header.Get(s3_constants.AmzAccountId)
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
return s3account.AccountAnonymous.Id
|
return s3_constants.AccountAnonymousId
|
||||||
} else {
|
} else {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractAcl extracts the acl from the request body, or from the header if request body is empty
|
// 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 {
|
if r.Body != nil && r.Body != http.NoBody {
|
||||||
defer util.CloseRequest(r)
|
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
|
// 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)
|
ownerId, grants, errCode = ParseAndValidateAclHeaders(r, accountManager, ownership, bucketOwnerId, accountId, putAcl)
|
||||||
if errCode != s3err.ErrNone {
|
if errCode != s3err.ErrNone {
|
||||||
return
|
return
|
||||||
|
@ -69,7 +73,7 @@ func ParseAndValidateAclHeadersOrElseDefault(r *http.Request, accountManager *s3
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAndValidateAclHeaders parse and validate acl from header
|
// 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)
|
ownerId, grants, errCode = ParseAclHeaders(r, ownership, bucketOwnerId, accountId, putAcl)
|
||||||
if errCode != s3err.ErrNone {
|
if errCode != s3err.ErrNone {
|
||||||
return
|
return
|
||||||
|
@ -257,7 +261,7 @@ func ParseCannedAclHeader(bucketOwnership, bucketOwnerId, accountId, cannedAcl s
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAndTransferGrants validate grant & transfer Email-Grant to Id-Grant
|
// 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
|
var result []*s3.Grant
|
||||||
for _, grant := range grants {
|
for _, grant := range grants {
|
||||||
grantee := grant.Grantee
|
grantee := grant.Grantee
|
||||||
|
@ -283,8 +287,8 @@ func ValidateAndTransferGrants(accountManager *s3account.AccountManager, grants
|
||||||
glog.Warning("invalid canonical grantee! account id is nil")
|
glog.Warning("invalid canonical grantee! account id is nil")
|
||||||
return nil, s3err.ErrInvalidRequest
|
return nil, s3err.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
_, ok := accountManager.IdNameMapping[*grantee.ID]
|
name := accountManager.GetAccountNameById(*grantee.ID)
|
||||||
if !ok {
|
if len(name) == 0 {
|
||||||
glog.Warningf("invalid canonical grantee! account id[%s] is not exists", *grantee.ID)
|
glog.Warningf("invalid canonical grantee! account id[%s] is not exists", *grantee.ID)
|
||||||
return nil, s3err.ErrInvalidRequest
|
return nil, s3err.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
@ -294,8 +298,8 @@ func ValidateAndTransferGrants(accountManager *s3account.AccountManager, grants
|
||||||
glog.Warning("invalid email grantee! email address is nil")
|
glog.Warning("invalid email grantee! email address is nil")
|
||||||
return nil, s3err.ErrInvalidRequest
|
return nil, s3err.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
accountId, ok := accountManager.EmailIdMapping[*grantee.EmailAddress]
|
accountId := accountManager.GetAccountIdByEmail(*grantee.EmailAddress)
|
||||||
if !ok {
|
if len(accountId) == 0 {
|
||||||
glog.Warningf("invalid email grantee! email address[%s] is not exists", *grantee.EmailAddress)
|
glog.Warningf("invalid email grantee! email address[%s] is not exists", *grantee.EmailAddress)
|
||||||
return nil, s3err.ErrInvalidRequest
|
return nil, s3err.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
|
@ -348,7 +352,7 @@ func DetermineReqGrants(accountId, aclAction string) (grants []*s3.Grant) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// group grantee (AuthenticateUsers)
|
// group grantee (AuthenticateUsers)
|
||||||
if accountId != s3account.AccountAnonymous.Id {
|
if accountId != s3_constants.AccountAnonymousId {
|
||||||
grants = append(grants, &s3.Grant{
|
grants = append(grants, &s3.Grant{
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeGroup,
|
Type: &s3_constants.GrantTypeGroup,
|
|
@ -1,34 +1,38 @@
|
||||||
package s3acl
|
package s3api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/s3"
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
"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/s3_constants"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var accountManager *IdentityAccessManagement
|
||||||
accountManager = &s3account.AccountManager{
|
|
||||||
IdNameMapping: map[string]string{
|
func init() {
|
||||||
s3account.AccountAdmin.Id: s3account.AccountAdmin.Name,
|
accountManager = &IdentityAccessManagement{}
|
||||||
s3account.AccountAnonymous.Id: s3account.AccountAnonymous.Name,
|
_ = accountManager.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{
|
||||||
"accountA": "accountA",
|
Accounts: []*iam_pb.Account{
|
||||||
"accountB": "accountB",
|
{
|
||||||
|
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) {
|
func TestGetAccountId(t *testing.T) {
|
||||||
req := &http.Request{
|
req := &http.Request{
|
||||||
|
@ -36,22 +40,22 @@ func TestGetAccountId(t *testing.T) {
|
||||||
}
|
}
|
||||||
//case1
|
//case1
|
||||||
//accountId: "admin"
|
//accountId: "admin"
|
||||||
req.Header.Set(s3_constants.AmzAccountId, s3account.AccountAdmin.Id)
|
req.Header.Set(s3_constants.AmzAccountId, s3_constants.AccountAdminId)
|
||||||
if GetAccountId(req) != s3account.AccountAdmin.Id {
|
if GetAccountId(req) != s3_constants.AccountAdminId {
|
||||||
t.Fatal("expect accountId: admin")
|
t.Fatal("expect accountId: admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
//case2
|
//case2
|
||||||
//accountId: "anoymous"
|
//accountId: "anoymous"
|
||||||
req.Header.Set(s3_constants.AmzAccountId, s3account.AccountAnonymous.Id)
|
req.Header.Set(s3_constants.AmzAccountId, s3_constants.AccountAnonymousId)
|
||||||
if GetAccountId(req) != s3account.AccountAnonymous.Id {
|
if GetAccountId(req) != s3_constants.AccountAnonymousId {
|
||||||
t.Fatal("expect accountId: anonymous")
|
t.Fatal("expect accountId: anonymous")
|
||||||
}
|
}
|
||||||
|
|
||||||
//case3
|
//case3
|
||||||
//accountId is nil => "anonymous"
|
//accountId is nil => "anonymous"
|
||||||
req.Header.Del(s3_constants.AmzAccountId)
|
req.Header.Del(s3_constants.AmzAccountId)
|
||||||
if GetAccountId(req) != s3account.AccountAnonymous.Id {
|
if GetAccountId(req) != s3_constants.AccountAnonymousId {
|
||||||
t.Fatal("expect accountId: anonymous")
|
t.Fatal("expect accountId: anonymous")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +68,6 @@ func TestExtractAcl(t *testing.T) {
|
||||||
}
|
}
|
||||||
testCases := make([]*Case, 0)
|
testCases := make([]*Case, 0)
|
||||||
accountAdminId := "admin"
|
accountAdminId := "admin"
|
||||||
|
|
||||||
{
|
{
|
||||||
//case1 (good case)
|
//case1 (good case)
|
||||||
//parse acp from request body
|
//parse acp from request body
|
||||||
|
@ -249,14 +252,14 @@ func TestParseAndValidateAclHeaders(t *testing.T) {
|
||||||
{
|
{
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeCanonicalUser,
|
Type: &s3_constants.GrantTypeCanonicalUser,
|
||||||
ID: &s3account.AccountAnonymous.Id,
|
ID: aws.String(s3_constants.AccountAnonymousId),
|
||||||
},
|
},
|
||||||
Permission: &s3_constants.PermissionFullControl,
|
Permission: &s3_constants.PermissionFullControl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeCanonicalUser,
|
Type: &s3_constants.GrantTypeCanonicalUser,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
},
|
},
|
||||||
Permission: &s3_constants.PermissionFullControl,
|
Permission: &s3_constants.PermissionFullControl,
|
||||||
},
|
},
|
||||||
|
@ -391,7 +394,7 @@ func grantsEquals(a, b []*s3.Grant) bool {
|
||||||
func TestDetermineReqGrants(t *testing.T) {
|
func TestDetermineReqGrants(t *testing.T) {
|
||||||
{
|
{
|
||||||
//case1: request account is anonymous
|
//case1: request account is anonymous
|
||||||
accountId := s3account.AccountAnonymous.Id
|
accountId := s3_constants.AccountAnonymousId
|
||||||
reqPermission := s3_constants.PermissionRead
|
reqPermission := s3_constants.PermissionRead
|
||||||
|
|
||||||
resultGrants := DetermineReqGrants(accountId, reqPermission)
|
resultGrants := DetermineReqGrants(accountId, reqPermission)
|
||||||
|
@ -496,7 +499,7 @@ func TestAssembleEntryWithAcp(t *testing.T) {
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeGroup,
|
Type: &s3_constants.GrantTypeGroup,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
URI: &s3_constants.GranteeGroupAllUsers,
|
URI: &s3_constants.GranteeGroupAllUsers,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -569,13 +572,13 @@ func TestGrantEquals(t *testing.T) {
|
||||||
GrantEquals(&s3.Grant{
|
GrantEquals(&s3.Grant{
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
EmailAddress: &s3account.AccountAdmin.EmailAddress,
|
//EmailAddress: &s3account.AccountAdmin.EmailAddress,
|
||||||
},
|
},
|
||||||
}, &s3.Grant{
|
}, &s3.Grant{
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
},
|
},
|
||||||
}): true,
|
}): true,
|
||||||
|
|
||||||
|
@ -623,13 +626,13 @@ func TestGrantEquals(t *testing.T) {
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeGroup,
|
Type: &s3_constants.GrantTypeGroup,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
},
|
},
|
||||||
}, &s3.Grant{
|
}, &s3.Grant{
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeGroup,
|
Type: &s3_constants.GrantTypeGroup,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
},
|
},
|
||||||
}): true,
|
}): true,
|
||||||
|
|
||||||
|
@ -637,14 +640,14 @@ func TestGrantEquals(t *testing.T) {
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeGroup,
|
Type: &s3_constants.GrantTypeGroup,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
URI: &s3_constants.GranteeGroupAllUsers,
|
URI: &s3_constants.GranteeGroupAllUsers,
|
||||||
},
|
},
|
||||||
}, &s3.Grant{
|
}, &s3.Grant{
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeGroup,
|
Type: &s3_constants.GrantTypeGroup,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
},
|
},
|
||||||
}): false,
|
}): false,
|
||||||
|
|
||||||
|
@ -652,7 +655,7 @@ func TestGrantEquals(t *testing.T) {
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeGroup,
|
Type: &s3_constants.GrantTypeGroup,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
URI: &s3_constants.GranteeGroupAllUsers,
|
URI: &s3_constants.GranteeGroupAllUsers,
|
||||||
},
|
},
|
||||||
}, &s3.Grant{
|
}, &s3.Grant{
|
||||||
|
@ -692,7 +695,7 @@ func TestSetAcpGrantsHeader(t *testing.T) {
|
||||||
Permission: &s3_constants.PermissionRead,
|
Permission: &s3_constants.PermissionRead,
|
||||||
Grantee: &s3.Grantee{
|
Grantee: &s3.Grantee{
|
||||||
Type: &s3_constants.GrantTypeGroup,
|
Type: &s3_constants.GrantTypeGroup,
|
||||||
ID: &s3account.AccountAdmin.Id,
|
ID: aws.String(s3_constants.AccountAdminId),
|
||||||
URI: &s3_constants.GranteeGroupAllUsers,
|
URI: &s3_constants.GranteeGroupAllUsers,
|
||||||
},
|
},
|
||||||
},
|
},
|
|
@ -2,7 +2,6 @@ package s3api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
"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/s3api/s3err"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
@ -10,7 +9,7 @@ import (
|
||||||
func getAccountId(r *http.Request) string {
|
func getAccountId(r *http.Request) string {
|
||||||
id := r.Header.Get(s3_constants.AmzAccountId)
|
id := r.Header.Get(s3_constants.AmzAccountId)
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
return s3account.AccountAnonymous.Id
|
return AccountAnonymous.Id
|
||||||
} else {
|
} else {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
@ -22,7 +21,7 @@ func (s3a *S3ApiServer) checkAccessByOwnership(r *http.Request, bucket string) s
|
||||||
return errCode
|
return errCode
|
||||||
}
|
}
|
||||||
accountId := getAccountId(r)
|
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.ErrNone
|
||||||
}
|
}
|
||||||
return s3err.ErrAccessDenied
|
return s3err.ErrAccessDenied
|
||||||
|
|
|
@ -259,17 +259,18 @@ func (s3a *S3ApiServer) GetBucketAclHandler(w http.ResponseWriter, r *http.Reque
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
identityId := r.Header.Get(s3_constants.AmzIdentityId)
|
amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
|
||||||
|
amzDisplayName := s3a.iam.GetAccountNameById(amzAccountId)
|
||||||
response := AccessControlPolicy{
|
response := AccessControlPolicy{
|
||||||
Owner: CanonicalUser{
|
Owner: CanonicalUser{
|
||||||
ID: identityId,
|
ID: amzAccountId,
|
||||||
DisplayName: identityId,
|
DisplayName: amzDisplayName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
|
response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
|
||||||
Grantee: Grantee{
|
Grantee: Grantee{
|
||||||
ID: identityId,
|
ID: amzAccountId,
|
||||||
DisplayName: identityId,
|
DisplayName: amzDisplayName,
|
||||||
Type: "CanonicalUser",
|
Type: "CanonicalUser",
|
||||||
XMLXSI: "CanonicalUser",
|
XMLXSI: "CanonicalUser",
|
||||||
XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},
|
XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/filer"
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb/s3_pb"
|
"github.com/seaweedfs/seaweedfs/weed/pb/s3_pb"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -42,7 +41,6 @@ type S3ApiServer struct {
|
||||||
randomClientId int32
|
randomClientId int32
|
||||||
filerGuard *security.Guard
|
filerGuard *security.Guard
|
||||||
client *http.Client
|
client *http.Client
|
||||||
accountManager *s3account.AccountManager
|
|
||||||
bucketRegistry *BucketRegistry
|
bucketRegistry *BucketRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +61,6 @@ func NewS3ApiServer(router *mux.Router, option *S3ApiServerOption) (s3ApiServer
|
||||||
filerGuard: security.NewGuard([]string{}, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec),
|
filerGuard: security.NewGuard([]string{}, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec),
|
||||||
cb: NewCircuitBreaker(option),
|
cb: NewCircuitBreaker(option),
|
||||||
}
|
}
|
||||||
s3ApiServer.accountManager = s3account.NewAccountManager(s3ApiServer)
|
|
||||||
s3ApiServer.bucketRegistry = NewBucketRegistry(s3ApiServer)
|
s3ApiServer.bucketRegistry = NewBucketRegistry(s3ApiServer)
|
||||||
if option.LocalFilerSocket == "" {
|
if option.LocalFilerSocket == "" {
|
||||||
s3ApiServer.client = &http.Client{Transport: &http.Transport{
|
s3ApiServer.client = &http.Client{Transport: &http.Transport{
|
||||||
|
|
Loading…
Reference in a new issue