GetUserPolicy

This commit is contained in:
Konstantin Lebedev 2021-04-10 23:57:45 +05:00
parent ba175f81b5
commit 5021bea698
7 changed files with 226 additions and 39 deletions

1
go.mod
View file

@ -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
View file

@ -437,6 +437,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=

View file

@ -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 {

View file

@ -11,6 +11,7 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"reflect"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -21,25 +22,75 @@ 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,6 +204,60 @@ func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values
return resp, nil return resp, nil
} }
func (iama *IamApiServer) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp GetUserPolicyResponse, err error) {
userName := values.Get("UserName")
policyName := values.Get("PolicyName")
for _, ident := range s3cfg.Identities {
if userName != ident.Name {
continue
}
resp.GetUserPolicyResult.UserName = userName
resp.GetUserPolicyResult.PolicyName = policyName
if len(ident.Actions) == 0 {
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) { func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
userName := values.Get("UserName") userName := values.Get("UserName")
for i, ident := range s3cfg.Identities { for i, ident := range s3cfg.Identities {
@ -155,21 +269,6 @@ func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, val
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
} }
func MapAction(action string) string {
switch action {
case "*":
return s3_constants.ACTION_ADMIN
case "Put*":
return s3_constants.ACTION_WRITE
case "Get*":
return s3_constants.ACTION_READ
case "List*":
return s3_constants.ACTION_LIST
default:
return s3_constants.ACTION_TAGGING
}
}
func GetActions(policy *PolicyDocument) (actions []string) { func GetActions(policy *PolicyDocument) (actions []string) {
for _, statement := range policy.Statement { for _, statement := range policy.Statement {
if statement.Effect != "Allow" { if statement.Effect != "Allow" {
@ -189,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/*
@ -199,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]))
} }
} }
} }
@ -291,6 +391,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
writeIamErrorResponse(w, err, "user", userName, nil) 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)
@ -316,6 +417,13 @@ 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": case "DeleteUserPolicy":
if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil { if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil {
writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil) writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil)

View file

@ -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"`

View file

@ -4,6 +4,7 @@ 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"
@ -21,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 {
@ -106,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
},
)
}

View file

@ -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)
} }