mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2025-05-04 21:57:48 +08:00
commit
f5de42fae3
@ -26,9 +26,10 @@ services:
|
|||||||
filer:
|
filer:
|
||||||
image: chrislusf/seaweedfs:local
|
image: chrislusf/seaweedfs:local
|
||||||
ports:
|
ports:
|
||||||
|
- 8111:8111
|
||||||
- 8888:8888
|
- 8888:8888
|
||||||
- 18888:18888
|
- 18888:18888
|
||||||
command: '-v=1 filer -master="master:9333"'
|
command: '-v=1 filer -master="master:9333" -iam'
|
||||||
depends_on:
|
depends_on:
|
||||||
- master
|
- master
|
||||||
- volume
|
- volume
|
||||||
|
1
go.mod
1
go.mod
@ -42,6 +42,7 @@ require (
|
|||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.11.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway v1.11.0 // indirect
|
||||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/copier v0.2.8
|
||||||
github.com/json-iterator/go v1.1.10
|
github.com/json-iterator/go v1.1.10
|
||||||
github.com/karlseguin/ccache v2.0.3+incompatible // indirect
|
github.com/karlseguin/ccache v2.0.3+incompatible // indirect
|
||||||
github.com/karlseguin/ccache/v2 v2.0.7
|
github.com/karlseguin/ccache/v2 v2.0.7
|
||||||
|
2
go.sum
2
go.sum
@ -439,6 +439,8 @@ github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjL
|
|||||||
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
||||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||||
|
github.com/jinzhu/copier v0.2.8 h1:N8MbL5niMwE3P4dOwurJixz5rMkKfujmMRFmAanSzWE=
|
||||||
|
github.com/jinzhu/copier v0.2.8/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
@ -18,6 +18,7 @@ const (
|
|||||||
FilerConfName = "filer.conf"
|
FilerConfName = "filer.conf"
|
||||||
IamConfigDirecotry = "/etc/iam"
|
IamConfigDirecotry = "/etc/iam"
|
||||||
IamIdentityFile = "identity.json"
|
IamIdentityFile = "identity.json"
|
||||||
|
IamPoliciesFile = "policies.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilerConf struct {
|
type FilerConf struct {
|
||||||
|
@ -50,20 +50,25 @@ func writeErrorResponse(w http.ResponseWriter, errorCode s3err.ErrorCode, reqURL
|
|||||||
writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string) {
|
func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string, msg error) {
|
||||||
errCode := err.Error()
|
errCode := err.Error()
|
||||||
errorResp := ErrorResponse{}
|
errorResp := ErrorResponse{}
|
||||||
errorResp.Error.Type = "Sender"
|
errorResp.Error.Type = "Sender"
|
||||||
errorResp.Error.Code = &errCode
|
errorResp.Error.Code = &errCode
|
||||||
|
if msg != nil {
|
||||||
|
errMsg := msg.Error()
|
||||||
|
errorResp.Error.Message = &errMsg
|
||||||
|
}
|
||||||
glog.Errorf("Response %+v", err)
|
glog.Errorf("Response %+v", err)
|
||||||
switch errCode {
|
switch errCode {
|
||||||
case iam.ErrCodeNoSuchEntityException:
|
case iam.ErrCodeNoSuchEntityException:
|
||||||
msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value)
|
msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value)
|
||||||
errorResp.Error.Message = &msg
|
errorResp.Error.Message = &msg
|
||||||
writeResponse(w, http.StatusNotFound, encodeResponse(errorResp), mimeXML)
|
writeResponse(w, http.StatusNotFound, encodeResponse(errorResp), mimeXML)
|
||||||
|
case iam.ErrCodeServiceFailureException:
|
||||||
|
writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML)
|
||||||
default:
|
default:
|
||||||
writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML)
|
writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -19,27 +20,77 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/"
|
charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/"
|
||||||
|
policyDocumentVersion = "2012-10-17"
|
||||||
|
StatementActionAdmin = "*"
|
||||||
|
StatementActionWrite = "Put*"
|
||||||
|
StatementActionRead = "Get*"
|
||||||
|
StatementActionList = "List*"
|
||||||
|
StatementActionTagging = "Tagging*"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
seededRand *rand.Rand = rand.New(
|
seededRand *rand.Rand = rand.New(
|
||||||
rand.NewSource(time.Now().UnixNano()))
|
rand.NewSource(time.Now().UnixNano()))
|
||||||
policyDocuments = map[string]*PolicyDocument{}
|
policyDocuments = map[string]*PolicyDocument{}
|
||||||
|
policyLock = sync.RWMutex{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func MapToStatementAction(action string) string {
|
||||||
|
switch action {
|
||||||
|
case StatementActionAdmin:
|
||||||
|
return s3_constants.ACTION_ADMIN
|
||||||
|
case StatementActionWrite:
|
||||||
|
return s3_constants.ACTION_WRITE
|
||||||
|
case StatementActionRead:
|
||||||
|
return s3_constants.ACTION_READ
|
||||||
|
case StatementActionList:
|
||||||
|
return s3_constants.ACTION_LIST
|
||||||
|
case StatementActionTagging:
|
||||||
|
return s3_constants.ACTION_TAGGING
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapToIdentitiesAction(action string) string {
|
||||||
|
switch action {
|
||||||
|
case s3_constants.ACTION_ADMIN:
|
||||||
|
return StatementActionAdmin
|
||||||
|
case s3_constants.ACTION_WRITE:
|
||||||
|
return StatementActionWrite
|
||||||
|
case s3_constants.ACTION_READ:
|
||||||
|
return StatementActionRead
|
||||||
|
case s3_constants.ACTION_LIST:
|
||||||
|
return StatementActionList
|
||||||
|
case s3_constants.ACTION_TAGGING:
|
||||||
|
return StatementActionTagging
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Statement struct {
|
type Statement struct {
|
||||||
Effect string `json:"Effect"`
|
Effect string `json:"Effect"`
|
||||||
Action []string `json:"Action"`
|
Action []string `json:"Action"`
|
||||||
Resource []string `json:"Resource"`
|
Resource []string `json:"Resource"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Policies struct {
|
||||||
|
Policies map[string]PolicyDocument `json:"policies"`
|
||||||
|
}
|
||||||
|
|
||||||
type PolicyDocument struct {
|
type PolicyDocument struct {
|
||||||
Version string `json:"Version"`
|
Version string `json:"Version"`
|
||||||
Statement []*Statement `json:"Statement"`
|
Statement []*Statement `json:"Statement"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p PolicyDocument) String() string {
|
||||||
|
b, _ := json.Marshal(p)
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
func Hash(s *string) string {
|
func Hash(s *string) string {
|
||||||
h := sha1.New()
|
h := sha1.New()
|
||||||
h.Write([]byte(*s))
|
h.Write([]byte(*s))
|
||||||
@ -83,7 +134,7 @@ func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values ur
|
|||||||
func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) {
|
func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) {
|
||||||
for i, ident := range s3cfg.Identities {
|
for i, ident := range s3cfg.Identities {
|
||||||
if userName == ident.Name {
|
if userName == ident.Name {
|
||||||
ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
|
s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +170,16 @@ func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values
|
|||||||
resp.CreatePolicyResult.Policy.PolicyName = &policyName
|
resp.CreatePolicyResult.Policy.PolicyName = &policyName
|
||||||
resp.CreatePolicyResult.Policy.Arn = &arn
|
resp.CreatePolicyResult.Policy.Arn = &arn
|
||||||
resp.CreatePolicyResult.Policy.PolicyId = &policyId
|
resp.CreatePolicyResult.Policy.PolicyId = &policyId
|
||||||
policyDocuments[policyName] = &policyDocument
|
policies := Policies{}
|
||||||
|
policyLock.Lock()
|
||||||
|
defer policyLock.Unlock()
|
||||||
|
if err = iama.s3ApiConfig.GetPolicies(&policies); err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
policies.Policies[policyName] = policyDocument
|
||||||
|
if err = iama.s3ApiConfig.PutPolicies(&policies); err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,19 +204,69 @@ func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MapAction(action string) string {
|
func (iama *IamApiServer) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp GetUserPolicyResponse, err error) {
|
||||||
switch action {
|
userName := values.Get("UserName")
|
||||||
case "*":
|
policyName := values.Get("PolicyName")
|
||||||
return s3_constants.ACTION_ADMIN
|
for _, ident := range s3cfg.Identities {
|
||||||
case "Put*":
|
if userName != ident.Name {
|
||||||
return s3_constants.ACTION_WRITE
|
continue
|
||||||
case "Get*":
|
}
|
||||||
return s3_constants.ACTION_READ
|
|
||||||
case "List*":
|
resp.GetUserPolicyResult.UserName = userName
|
||||||
return s3_constants.ACTION_LIST
|
resp.GetUserPolicyResult.PolicyName = policyName
|
||||||
default:
|
if len(ident.Actions) == 0 {
|
||||||
return s3_constants.ACTION_TAGGING
|
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
|
||||||
|
}
|
||||||
|
|
||||||
|
policyDocument := PolicyDocument{Version: policyDocumentVersion}
|
||||||
|
statements := make(map[string][]string)
|
||||||
|
for _, action := range ident.Actions {
|
||||||
|
// parse "Read:EXAMPLE-BUCKET"
|
||||||
|
act := strings.Split(action, ":")
|
||||||
|
|
||||||
|
resource := "*"
|
||||||
|
if len(act) == 2 {
|
||||||
|
resource = fmt.Sprintf("arn:aws:s3:::%s/*", act[1])
|
||||||
|
}
|
||||||
|
statements[resource] = append(statements[resource],
|
||||||
|
fmt.Sprintf("s3:%s", MapToIdentitiesAction(act[0])),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for resource, actions := range statements {
|
||||||
|
isEqAction := false
|
||||||
|
for i, statement := range policyDocument.Statement {
|
||||||
|
if reflect.DeepEqual(statement.Action, actions) {
|
||||||
|
policyDocument.Statement[i].Resource = append(
|
||||||
|
policyDocument.Statement[i].Resource, resource)
|
||||||
|
isEqAction = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isEqAction {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
policyDocumentStatement := Statement{
|
||||||
|
Effect: "Allow",
|
||||||
|
Action: actions,
|
||||||
|
}
|
||||||
|
policyDocumentStatement.Resource = append(policyDocumentStatement.Resource, resource)
|
||||||
|
policyDocument.Statement = append(policyDocument.Statement, &policyDocumentStatement)
|
||||||
|
}
|
||||||
|
resp.GetUserPolicyResult.PolicyDocument = policyDocument.String()
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
|
||||||
|
userName := values.Get("UserName")
|
||||||
|
for i, ident := range s3cfg.Identities {
|
||||||
|
if ident.Name == userName {
|
||||||
|
s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetActions(policy *PolicyDocument) (actions []string) {
|
func GetActions(policy *PolicyDocument) (actions []string) {
|
||||||
@ -178,8 +288,9 @@ func GetActions(policy *PolicyDocument) (actions []string) {
|
|||||||
glog.Infof("not match action: %s", act)
|
glog.Infof("not match action: %s", act)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
statementAction := MapToStatementAction(act[1])
|
||||||
if res[5] == "*" {
|
if res[5] == "*" {
|
||||||
actions = append(actions, MapAction(act[1]))
|
actions = append(actions, statementAction)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Parse my-bucket/shared/*
|
// Parse my-bucket/shared/*
|
||||||
@ -188,7 +299,7 @@ func GetActions(policy *PolicyDocument) (actions []string) {
|
|||||||
glog.Infof("not match bucket: %s", path)
|
glog.Infof("not match bucket: %s", path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0]))
|
actions = append(actions, fmt.Sprintf("%s:%s", statementAction, path[0]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,14 +388,15 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
|
|||||||
userName := values.Get("UserName")
|
userName := values.Get("UserName")
|
||||||
response, err = iama.GetUser(s3cfg, userName)
|
response, err = iama.GetUser(s3cfg, userName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeIamErrorResponse(w, err, "user", userName)
|
writeIamErrorResponse(w, err, "user", userName, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
changed = false
|
||||||
case "DeleteUser":
|
case "DeleteUser":
|
||||||
userName := values.Get("UserName")
|
userName := values.Get("UserName")
|
||||||
response, err = iama.DeleteUser(s3cfg, userName)
|
response, err = iama.DeleteUser(s3cfg, userName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeIamErrorResponse(w, err, "user", userName)
|
writeIamErrorResponse(w, err, "user", userName, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "CreateAccessKey":
|
case "CreateAccessKey":
|
||||||
@ -305,8 +417,23 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
|
writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "GetUserPolicy":
|
||||||
|
response, err = iama.GetUserPolicy(s3cfg, values)
|
||||||
|
if err != nil {
|
||||||
|
writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
changed = false
|
||||||
|
case "DeleteUserPolicy":
|
||||||
|
if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil {
|
||||||
|
writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
writeErrorResponse(w, s3err.ErrNotImplemented, r.URL)
|
errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||||
|
errorResponse := ErrorResponse{}
|
||||||
|
errorResponse.Error.Code = &errNotImplemented.Code
|
||||||
|
errorResponse.Error.Message = &errNotImplemented.Description
|
||||||
|
writeResponse(w, errNotImplemented.HTTPStatusCode, encodeResponse(errorResponse), mimeXML)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if changed {
|
if changed {
|
||||||
@ -314,7 +441,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
|
|||||||
err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
|
err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
|
||||||
s3cfgLock.Unlock()
|
s3cfgLock.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
writeIamErrorResponse(w, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,16 @@ type PutUserPolicyResponse struct {
|
|||||||
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"`
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetUserPolicyResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetUserPolicyResponse"`
|
||||||
|
GetUserPolicyResult struct {
|
||||||
|
UserName string `xml:"UserName"`
|
||||||
|
PolicyName string `xml:"PolicyName"`
|
||||||
|
PolicyDocument string `xml:"PolicyDocument"`
|
||||||
|
} `xml:"GetUserPolicyResult"`
|
||||||
|
}
|
||||||
|
|
||||||
type ErrorResponse struct {
|
type ErrorResponse struct {
|
||||||
CommonResponse
|
CommonResponse
|
||||||
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ErrorResponse"`
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ErrorResponse"`
|
||||||
|
@ -4,11 +4,14 @@ package iamapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/chrislusf/seaweedfs/weed/filer"
|
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||||
"github.com/chrislusf/seaweedfs/weed/pb"
|
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||||
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/s3api"
|
||||||
|
. "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
|
||||||
"github.com/chrislusf/seaweedfs/weed/wdclient"
|
"github.com/chrislusf/seaweedfs/weed/wdclient"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@ -19,6 +22,8 @@ import (
|
|||||||
type IamS3ApiConfig interface {
|
type IamS3ApiConfig interface {
|
||||||
GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||||
PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||||
|
GetPolicies(policies *Policies) (err error)
|
||||||
|
PutPolicies(policies *Policies) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type IamS3ApiConfigure struct {
|
type IamS3ApiConfigure struct {
|
||||||
@ -36,7 +41,7 @@ type IamServerOption struct {
|
|||||||
|
|
||||||
type IamApiServer struct {
|
type IamApiServer struct {
|
||||||
s3ApiConfig IamS3ApiConfig
|
s3ApiConfig IamS3ApiConfig
|
||||||
filerclient *filer_pb.SeaweedFilerClient
|
iam *s3api.IdentityAccessManagement
|
||||||
}
|
}
|
||||||
|
|
||||||
var s3ApiConfigure IamS3ApiConfig
|
var s3ApiConfigure IamS3ApiConfig
|
||||||
@ -46,9 +51,10 @@ func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer
|
|||||||
option: option,
|
option: option,
|
||||||
masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")),
|
masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")),
|
||||||
}
|
}
|
||||||
|
s3Option := s3api.S3ApiServerOption{Filer: option.Filer}
|
||||||
iamApiServer = &IamApiServer{
|
iamApiServer = &IamApiServer{
|
||||||
s3ApiConfig: s3ApiConfigure,
|
s3ApiConfig: s3ApiConfigure,
|
||||||
|
iam: s3api.NewIdentityAccessManagement(&s3Option),
|
||||||
}
|
}
|
||||||
|
|
||||||
iamApiServer.registerRouter(router)
|
iamApiServer.registerRouter(router)
|
||||||
@ -62,7 +68,8 @@ func (iama *IamApiServer) registerRouter(router *mux.Router) {
|
|||||||
// ListBuckets
|
// ListBuckets
|
||||||
|
|
||||||
// apiRouter.Methods("GET").Path("/").HandlerFunc(track(s3a.iam.Auth(s3a.ListBucketsHandler, ACTION_ADMIN), "LIST"))
|
// apiRouter.Methods("GET").Path("/").HandlerFunc(track(s3a.iam.Auth(s3a.ListBucketsHandler, ACTION_ADMIN), "LIST"))
|
||||||
apiRouter.Path("/").Methods("POST").HandlerFunc(iama.DoActions)
|
apiRouter.Methods("POST").Path("/").HandlerFunc(iama.iam.Auth(iama.DoActions, ACTION_ADMIN))
|
||||||
|
//
|
||||||
// NotFound
|
// NotFound
|
||||||
apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||||
}
|
}
|
||||||
@ -102,3 +109,41 @@ func (iam IamS3ApiConfigure) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfigurat
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (iam IamS3ApiConfigure) GetPolicies(policies *Policies) (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = pb.WithGrpcFilerClient(iam.option.FilerGrpcAddress, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
|
if err = filer.ReadEntry(iam.masterClient, client, filer.IamConfigDirecotry, filer.IamPoliciesFile, &buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if buf.Len() == 0 {
|
||||||
|
policies.Policies = make(map[string]PolicyDocument)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), policies); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iam IamS3ApiConfigure) PutPolicies(policies *Policies) (err error) {
|
||||||
|
var b []byte
|
||||||
|
if b, err = json.Marshal(policies); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pb.WithGrpcFilerClient(
|
||||||
|
iam.option.FilerGrpcAddress,
|
||||||
|
iam.option.GrpcDialOption,
|
||||||
|
func(client filer_pb.SeaweedFilerClient) error {
|
||||||
|
if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamPoliciesFile, b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -7,29 +7,43 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/service/iam"
|
"github.com/aws/aws-sdk-go/service/iam"
|
||||||
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var S3config iam_pb.S3ApiConfiguration
|
|
||||||
var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||||
var PutS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
var PutS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||||
|
var GetPolicies func(policies *Policies) (err error)
|
||||||
|
var PutPolicies func(policies *Policies) (err error)
|
||||||
|
|
||||||
|
var s3config = iam_pb.S3ApiConfiguration{}
|
||||||
|
var policiesFile = Policies{Policies: make(map[string]PolicyDocument)}
|
||||||
|
var ias = IamApiServer{s3ApiConfig: iamS3ApiConfigureMock{}}
|
||||||
|
|
||||||
type iamS3ApiConfigureMock struct{}
|
type iamS3ApiConfigureMock struct{}
|
||||||
|
|
||||||
func (iam iamS3ApiConfigureMock) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
func (iam iamS3ApiConfigureMock) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
||||||
s3cfg = &S3config
|
_ = copier.Copy(&s3cfg.Identities, &s3config.Identities)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iam iamS3ApiConfigureMock) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
func (iam iamS3ApiConfigureMock) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
||||||
S3config = *s3cfg
|
_ = copier.Copy(&s3config.Identities, &s3cfg.Identities)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var a = IamApiServer{}
|
func (iam iamS3ApiConfigureMock) GetPolicies(policies *Policies) (err error) {
|
||||||
|
_ = copier.Copy(&policies, &policiesFile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iam iamS3ApiConfigureMock) PutPolicies(policies *Policies) (err error) {
|
||||||
|
_ = copier.Copy(&policiesFile, &policies)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestCreateUser(t *testing.T) {
|
func TestCreateUser(t *testing.T) {
|
||||||
userName := aws.String("Test")
|
userName := aws.String("Test")
|
||||||
@ -64,17 +78,6 @@ func TestListAccessKeys(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusOK, response.Code)
|
assert.Equal(t, http.StatusOK, response.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteUser(t *testing.T) {
|
|
||||||
userName := aws.String("Test")
|
|
||||||
params := &iam.DeleteUserInput{UserName: userName}
|
|
||||||
req, _ := iam.New(session.New()).DeleteUserRequest(params)
|
|
||||||
_ = req.Build()
|
|
||||||
out := DeleteUserResponse{}
|
|
||||||
response, err := executeRequest(req.HTTPRequest, out)
|
|
||||||
assert.Equal(t, nil, err)
|
|
||||||
assert.Equal(t, http.StatusNotFound, response.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetUser(t *testing.T) {
|
func TestGetUser(t *testing.T) {
|
||||||
userName := aws.String("Test")
|
userName := aws.String("Test")
|
||||||
params := &iam.GetUserInput{UserName: userName}
|
params := &iam.GetUserInput{UserName: userName}
|
||||||
@ -83,7 +86,7 @@ func TestGetUser(t *testing.T) {
|
|||||||
out := GetUserResponse{}
|
out := GetUserResponse{}
|
||||||
response, err := executeRequest(req.HTTPRequest, out)
|
response, err := executeRequest(req.HTTPRequest, out)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
assert.Equal(t, http.StatusNotFound, response.Code)
|
assert.Equal(t, http.StatusOK, response.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo flat statement
|
// Todo flat statement
|
||||||
@ -147,11 +150,32 @@ func TestPutUserPolicy(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusOK, response.Code)
|
assert.Equal(t, http.StatusOK, response.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUserPolicy(t *testing.T) {
|
||||||
|
userName := aws.String("Test")
|
||||||
|
params := &iam.GetUserPolicyInput{UserName: userName, PolicyName: aws.String("S3-read-only-example-bucket")}
|
||||||
|
req, _ := iam.New(session.New()).GetUserPolicyRequest(params)
|
||||||
|
_ = req.Build()
|
||||||
|
out := GetUserPolicyResponse{}
|
||||||
|
response, err := executeRequest(req.HTTPRequest, out)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, http.StatusOK, response.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteUser(t *testing.T) {
|
||||||
|
userName := aws.String("Test")
|
||||||
|
params := &iam.DeleteUserInput{UserName: userName}
|
||||||
|
req, _ := iam.New(session.New()).DeleteUserRequest(params)
|
||||||
|
_ = req.Build()
|
||||||
|
out := DeleteUserResponse{}
|
||||||
|
response, err := executeRequest(req.HTTPRequest, out)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, http.StatusOK, response.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func executeRequest(req *http.Request, v interface{}) (*httptest.ResponseRecorder, error) {
|
func executeRequest(req *http.Request, v interface{}) (*httptest.ResponseRecorder, error) {
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
apiRouter := mux.NewRouter().SkipClean(true)
|
apiRouter := mux.NewRouter().SkipClean(true)
|
||||||
a.s3ApiConfig = iamS3ApiConfigureMock{}
|
apiRouter.Path("/").Methods("POST").HandlerFunc(ias.DoActions)
|
||||||
apiRouter.Path("/").Methods("POST").HandlerFunc(a.DoActions)
|
|
||||||
apiRouter.ServeHTTP(rr, req)
|
apiRouter.ServeHTTP(rr, req)
|
||||||
return rr, xml.Unmarshal(rr.Body.Bytes(), &v)
|
return rr, xml.Unmarshal(rr.Body.Bytes(), &v)
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -132,6 +133,17 @@ func (iam *IdentityAccessManagement) doesSignatureMatch(hashedPayload string, r
|
|||||||
// Query string.
|
// Query string.
|
||||||
queryStr := req.URL.Query().Encode()
|
queryStr := req.URL.Query().Encode()
|
||||||
|
|
||||||
|
// Get hashed Payload
|
||||||
|
if signV4Values.Credential.scope.service != "s3" && hashedPayload == emptySHA256 && r.Body != nil {
|
||||||
|
buf, _ := ioutil.ReadAll(r.Body)
|
||||||
|
r.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
|
||||||
|
b, _ := ioutil.ReadAll(bytes.NewBuffer(buf))
|
||||||
|
if len(b) != 0 {
|
||||||
|
bodyHash := sha256.Sum256(b)
|
||||||
|
hashedPayload = hex.EncodeToString(bodyHash[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get canonical request.
|
// Get canonical request.
|
||||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, hashedPayload, queryStr, req.URL.Path, req.Method)
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, hashedPayload, queryStr, req.URL.Path, req.Method)
|
||||||
|
|
||||||
@ -139,7 +151,10 @@ func (iam *IdentityAccessManagement) doesSignatureMatch(hashedPayload string, r
|
|||||||
stringToSign := getStringToSign(canonicalRequest, t, signV4Values.Credential.getScope())
|
stringToSign := getStringToSign(canonicalRequest, t, signV4Values.Credential.getScope())
|
||||||
|
|
||||||
// Get hmac signing key.
|
// Get hmac signing key.
|
||||||
signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, signV4Values.Credential.scope.region)
|
signingKey := getSigningKey(cred.SecretKey,
|
||||||
|
signV4Values.Credential.scope.date,
|
||||||
|
signV4Values.Credential.scope.region,
|
||||||
|
signV4Values.Credential.scope.service)
|
||||||
|
|
||||||
// Calculate signature.
|
// Calculate signature.
|
||||||
newSignature := getSignature(signingKey, stringToSign)
|
newSignature := getSignature(signingKey, stringToSign)
|
||||||
@ -310,7 +325,7 @@ func (iam *IdentityAccessManagement) doesPolicySignatureV4Match(formValues http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get signing key.
|
// Get signing key.
|
||||||
signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, credHeader.scope.region)
|
signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, credHeader.scope.region, credHeader.scope.service)
|
||||||
|
|
||||||
// Get signature.
|
// Get signature.
|
||||||
newSignature := getSignature(signingKey, formValues.Get("Policy"))
|
newSignature := getSignature(signingKey, formValues.Get("Policy"))
|
||||||
@ -427,7 +442,10 @@ func (iam *IdentityAccessManagement) doesPresignedSignatureMatch(hashedPayload s
|
|||||||
presignedStringToSign := getStringToSign(presignedCanonicalReq, t, pSignValues.Credential.getScope())
|
presignedStringToSign := getStringToSign(presignedCanonicalReq, t, pSignValues.Credential.getScope())
|
||||||
|
|
||||||
// Get hmac presigned signing key.
|
// Get hmac presigned signing key.
|
||||||
presignedSigningKey := getSigningKey(cred.SecretKey, pSignValues.Credential.scope.date, pSignValues.Credential.scope.region)
|
presignedSigningKey := getSigningKey(cred.SecretKey,
|
||||||
|
pSignValues.Credential.scope.date,
|
||||||
|
pSignValues.Credential.scope.region,
|
||||||
|
pSignValues.Credential.scope.service)
|
||||||
|
|
||||||
// Get new signature.
|
// Get new signature.
|
||||||
newSignature := getSignature(presignedSigningKey, presignedStringToSign)
|
newSignature := getSignature(presignedSigningKey, presignedStringToSign)
|
||||||
@ -655,11 +673,11 @@ func sumHMAC(key []byte, data []byte) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getSigningKey hmac seed to calculate final signature.
|
// getSigningKey hmac seed to calculate final signature.
|
||||||
func getSigningKey(secretKey string, t time.Time, region string) []byte {
|
func getSigningKey(secretKey string, t time.Time, region string, service string) []byte {
|
||||||
date := sumHMAC([]byte("AWS4"+secretKey), []byte(t.Format(yyyymmdd)))
|
date := sumHMAC([]byte("AWS4"+secretKey), []byte(t.Format(yyyymmdd)))
|
||||||
regionBytes := sumHMAC(date, []byte(region))
|
regionBytes := sumHMAC(date, []byte(region))
|
||||||
service := sumHMAC(regionBytes, []byte("s3"))
|
serviceBytes := sumHMAC(regionBytes, []byte(service))
|
||||||
signingKey := sumHMAC(service, []byte("aws4_request"))
|
signingKey := sumHMAC(serviceBytes, []byte("aws4_request"))
|
||||||
return signingKey
|
return signingKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,7 +370,7 @@ func preSignV4(req *http.Request, accessKeyID, secretAccessKey string, expires i
|
|||||||
queryStr := strings.Replace(query.Encode(), "+", "%20", -1)
|
queryStr := strings.Replace(query.Encode(), "+", "%20", -1)
|
||||||
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, req.URL.Path, req.Method)
|
canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, req.URL.Path, req.Method)
|
||||||
stringToSign := getStringToSign(canonicalRequest, date, scope)
|
stringToSign := getStringToSign(canonicalRequest, date, scope)
|
||||||
signingKey := getSigningKey(secretAccessKey, date, region)
|
signingKey := getSigningKey(secretAccessKey, date, region, "s3")
|
||||||
signature := getSignature(signingKey, stringToSign)
|
signature := getSignature(signingKey, stringToSign)
|
||||||
|
|
||||||
req.URL.RawQuery = query.Encode()
|
req.URL.RawQuery = query.Encode()
|
||||||
|
@ -45,7 +45,7 @@ func getChunkSignature(secretKey string, seedSignature string, region string, da
|
|||||||
hashedChunk
|
hashedChunk
|
||||||
|
|
||||||
// Get hmac signing key.
|
// Get hmac signing key.
|
||||||
signingKey := getSigningKey(secretKey, date, region)
|
signingKey := getSigningKey(secretKey, date, region, "s3")
|
||||||
|
|
||||||
// Calculate signature.
|
// Calculate signature.
|
||||||
newSignature := getSignature(signingKey, stringToSign)
|
newSignature := getSignature(signingKey, stringToSign)
|
||||||
@ -117,7 +117,7 @@ func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cr
|
|||||||
stringToSign := getStringToSign(canonicalRequest, date, signV4Values.Credential.getScope())
|
stringToSign := getStringToSign(canonicalRequest, date, signV4Values.Credential.getScope())
|
||||||
|
|
||||||
// Get hmac signing key.
|
// Get hmac signing key.
|
||||||
signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, region)
|
signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, region, "s3")
|
||||||
|
|
||||||
// Calculate signature.
|
// Calculate signature.
|
||||||
newSignature := getSignature(signingKey, stringToSign)
|
newSignature := getSignature(signingKey, stringToSign)
|
||||||
|
Loading…
Reference in New Issue
Block a user