aboutsummaryrefslogtreecommitdiff
path: root/weed/iamapi
diff options
context:
space:
mode:
Diffstat (limited to 'weed/iamapi')
-rw-r--r--weed/iamapi/iamapi_handlers.go8
-rw-r--r--weed/iamapi/iamapi_management_handlers.go87
-rw-r--r--weed/iamapi/iamapi_response.go5
-rw-r--r--weed/iamapi/iamapi_server.go54
-rw-r--r--weed/iamapi/iamapi_test.go43
5 files changed, 142 insertions, 55 deletions
diff --git a/weed/iamapi/iamapi_handlers.go b/weed/iamapi/iamapi_handlers.go
index 7765d9e95..a59834e88 100644
--- a/weed/iamapi/iamapi_handlers.go
+++ b/weed/iamapi/iamapi_handlers.go
@@ -8,7 +8,7 @@ import (
"net/http"
)
-func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string, msg error) {
+func writeIamErrorResponse(w http.ResponseWriter, r *http.Request, err error, object string, value string, msg error) {
errCode := err.Error()
errorResp := ErrorResponse{}
errorResp.Error.Type = "Sender"
@@ -22,10 +22,10 @@ func writeIamErrorResponse(w http.ResponseWriter, err error, object string, valu
case iam.ErrCodeNoSuchEntityException:
msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value)
errorResp.Error.Message = &msg
- s3err.WriteXMLResponse(w, http.StatusNotFound, errorResp)
+ s3err.WriteXMLResponse(w, r, http.StatusNotFound, errorResp)
case iam.ErrCodeServiceFailureException:
- s3err.WriteXMLResponse(w, http.StatusInternalServerError, errorResp)
+ s3err.WriteXMLResponse(w, r, http.StatusInternalServerError, errorResp)
default:
- s3err.WriteXMLResponse(w, http.StatusInternalServerError, errorResp)
+ s3err.WriteXMLResponse(w, r, http.StatusInternalServerError, errorResp)
}
}
diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go
index 0826ce336..488e92aa5 100644
--- a/weed/iamapi/iamapi_management_handlers.go
+++ b/weed/iamapi/iamapi_management_handlers.go
@@ -4,10 +4,6 @@ import (
"crypto/sha1"
"encoding/json"
"fmt"
- "github.com/chrislusf/seaweedfs/weed/glog"
- "github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
- "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
- "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
"math/rand"
"net/http"
"net/url"
@@ -16,6 +12,11 @@ import (
"sync"
"time"
+ "github.com/chrislusf/seaweedfs/weed/glog"
+ "github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
+ "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants"
+ "github.com/chrislusf/seaweedfs/weed/s3api/s3err"
+
"github.com/aws/aws-sdk-go/service/iam"
)
@@ -155,6 +156,22 @@ func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName str
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
}
+func (iama *IamApiServer) UpdateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp UpdateUserResponse, err error) {
+ userName := values.Get("UserName")
+ newUserName := values.Get("NewUserName")
+ if newUserName != "" {
+ for _, ident := range s3cfg.Identities {
+ if userName == ident.Name {
+ ident.Name = newUserName
+ return resp, nil
+ }
+ }
+ } else {
+ return resp, nil
+ }
+ return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
+}
+
func GetPolicyDocument(policy *string) (policyDocument PolicyDocument, err error) {
if err = json.Unmarshal([]byte(*policy), &policyDocument); err != nil {
return PolicyDocument{}, err
@@ -360,9 +377,38 @@ func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, valu
return resp
}
+// handleImplicitUsername adds username who signs the request to values if 'username' is not specified
+// According to https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/create-access-key.html/
+// "If you do not specify a user name, IAM determines the user name implicitly based on the Amazon Web
+// Services access key ID signing the request."
+func handleImplicitUsername(r *http.Request, values url.Values) {
+ if len(r.Header["Authorization"]) == 0 || values.Get("UserName") != "" {
+ return
+ }
+ // get username who signs the request. For a typical Authorization:
+ // "AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request, SignedHeaders=content-type;
+ // host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8",
+ // the "test1" will be extracted as the username
+ glog.V(4).Infof("Authorization field: %v", r.Header["Authorization"][0])
+ s := strings.Split(r.Header["Authorization"][0], "Credential=")
+ if len(s) < 2 {
+ return
+ }
+ s = strings.Split(s[1], ",")
+ if len(s) < 2 {
+ return
+ }
+ s = strings.Split(s[0], "/")
+ if len(s) < 5 {
+ return
+ }
+ userName := s[2]
+ values.Set("UserName", userName)
+}
+
func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
- s3err.WriteErrorResponse(w, s3err.ErrInvalidRequest, r)
+ s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
return
}
values := r.PostForm
@@ -370,7 +416,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
s3cfgLock.RLock()
s3cfg := &iam_pb.S3ApiConfiguration{}
if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil {
- s3err.WriteErrorResponse(w, s3err.ErrInternalError, r)
+ s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
}
s3cfgLock.RUnlock()
@@ -384,6 +430,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
response = iama.ListUsers(s3cfg, values)
changed = false
case "ListAccessKeys":
+ handleImplicitUsername(r, values)
response = iama.ListAccessKeys(s3cfg, values)
changed = false
case "CreateUser":
@@ -392,52 +439,62 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
userName := values.Get("UserName")
response, err = iama.GetUser(s3cfg, userName)
if err != nil {
- writeIamErrorResponse(w, err, "user", userName, nil)
+ writeIamErrorResponse(w, r, err, "user", userName, nil)
return
}
changed = false
+ case "UpdateUser":
+ response, err = iama.UpdateUser(s3cfg, values)
+ if err != nil {
+ glog.Errorf("UpdateUser: %+v", err)
+ s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
+ return
+ }
case "DeleteUser":
userName := values.Get("UserName")
response, err = iama.DeleteUser(s3cfg, userName)
if err != nil {
- writeIamErrorResponse(w, err, "user", userName, nil)
+ writeIamErrorResponse(w, r, err, "user", userName, nil)
return
}
case "CreateAccessKey":
+ handleImplicitUsername(r, values)
response = iama.CreateAccessKey(s3cfg, values)
case "DeleteAccessKey":
+ handleImplicitUsername(r, values)
response = iama.DeleteAccessKey(s3cfg, values)
case "CreatePolicy":
response, err = iama.CreatePolicy(s3cfg, values)
if err != nil {
glog.Errorf("CreatePolicy: %+v", err)
- s3err.WriteErrorResponse(w, s3err.ErrInvalidRequest, r)
+ s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
return
}
case "PutUserPolicy":
response, err = iama.PutUserPolicy(s3cfg, values)
if err != nil {
glog.Errorf("PutUserPolicy: %+v", err)
- s3err.WriteErrorResponse(w, s3err.ErrInvalidRequest, r)
+ s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
return
}
case "GetUserPolicy":
response, err = iama.GetUserPolicy(s3cfg, values)
if err != nil {
- writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil)
+ writeIamErrorResponse(w, r, 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)
+ writeIamErrorResponse(w, r, err, "user", values.Get("UserName"), nil)
+ return
}
default:
errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented)
errorResponse := ErrorResponse{}
errorResponse.Error.Code = &errNotImplemented.Code
errorResponse.Error.Message = &errNotImplemented.Description
- s3err.WriteXMLResponse(w, errNotImplemented.HTTPStatusCode, errorResponse)
+ s3err.WriteXMLResponse(w, r, errNotImplemented.HTTPStatusCode, errorResponse)
return
}
if changed {
@@ -445,9 +502,9 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
s3cfgLock.Unlock()
if err != nil {
- writeIamErrorResponse(w, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err)
+ writeIamErrorResponse(w, r, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err)
return
}
}
- s3err.WriteXMLResponse(w, http.StatusOK, response)
+ s3err.WriteXMLResponse(w, r, http.StatusOK, response)
}
diff --git a/weed/iamapi/iamapi_response.go b/weed/iamapi/iamapi_response.go
index 77328b608..df9443f0d 100644
--- a/weed/iamapi/iamapi_response.go
+++ b/weed/iamapi/iamapi_response.go
@@ -66,6 +66,11 @@ type GetUserResponse struct {
} `xml:"GetUserResult"`
}
+type UpdateUserResponse struct {
+ CommonResponse
+ XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ UpdateUserResponse"`
+}
+
type CreateAccessKeyResponse struct {
CommonResponse
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateAccessKeyResponse"`
diff --git a/weed/iamapi/iamapi_server.go b/weed/iamapi/iamapi_server.go
index ec718bd41..62c7f867c 100644
--- a/weed/iamapi/iamapi_server.go
+++ b/weed/iamapi/iamapi_server.go
@@ -18,7 +18,6 @@ import (
"github.com/gorilla/mux"
"google.golang.org/grpc"
"net/http"
- "strings"
)
type IamS3ApiConfig interface {
@@ -34,11 +33,10 @@ type IamS3ApiConfigure struct {
}
type IamServerOption struct {
- Masters string
- Filer string
- Port int
- FilerGrpcAddress string
- GrpcDialOption grpc.DialOption
+ Masters map[string]pb.ServerAddress
+ Filer pb.ServerAddress
+ Port int
+ GrpcDialOption grpc.DialOption
}
type IamApiServer struct {
@@ -51,7 +49,7 @@ var s3ApiConfigure IamS3ApiConfig
func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer *IamApiServer, err error) {
s3ApiConfigure = IamS3ApiConfigure{
option: option,
- masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")),
+ masterClient: wdclient.NewMasterClient(option.GrpcDialOption, "", "iam", "", "", option.Masters),
}
s3Option := s3api.S3ApiServerOption{Filer: option.Filer}
iamApiServer = &IamApiServer{
@@ -78,7 +76,7 @@ func (iama *IamApiServer) registerRouter(router *mux.Router) {
func (iam IamS3ApiConfigure) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
var buf bytes.Buffer
- err = pb.WithGrpcFilerClient(iam.option.FilerGrpcAddress, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
+ err = pb.WithGrpcFilerClient(false, iam.option.Filer, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
if err = filer.ReadEntry(iam.masterClient, client, filer.IamConfigDirecotry, filer.IamIdentityFile, &buf); err != nil {
return err
}
@@ -100,24 +98,20 @@ func (iam IamS3ApiConfigure) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfigurat
if err := filer.ProtoToText(&buf, s3cfg); err != nil {
return fmt.Errorf("ProtoToText: %s", err)
}
- return pb.WithGrpcFilerClient(
- iam.option.FilerGrpcAddress,
- iam.option.GrpcDialOption,
- func(client filer_pb.SeaweedFilerClient) error {
- err = util.Retry("saveIamIdentity", func() error {
- return filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamIdentityFile, buf.Bytes())
- })
- if err != nil {
- return err
- }
- return nil
- },
- )
+ return pb.WithGrpcFilerClient(false, iam.option.Filer, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
+ err = util.Retry("saveIamIdentity", func() error {
+ return filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamIdentityFile, buf.Bytes())
+ })
+ if err != nil {
+ return err
+ }
+ return nil
+ })
}
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 {
+ err = pb.WithGrpcFilerClient(false, iam.option.Filer, 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
}
@@ -141,14 +135,10 @@ func (iam IamS3ApiConfigure) PutPolicies(policies *Policies) (err error) {
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
- },
- )
+ return pb.WithGrpcFilerClient(false, iam.option.Filer, 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
+ })
}
diff --git a/weed/iamapi/iamapi_test.go b/weed/iamapi/iamapi_test.go
index 09aaf0ac8..375e9a2f3 100644
--- a/weed/iamapi/iamapi_test.go
+++ b/weed/iamapi/iamapi_test.go
@@ -2,6 +2,11 @@ package iamapi
import (
"encoding/xml"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "testing"
+
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/iam"
@@ -9,9 +14,6 @@ import (
"github.com/gorilla/mux"
"github.com/jinzhu/copier"
"github.com/stretchr/testify/assert"
- "net/http"
- "net/http/httptest"
- "testing"
)
var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
@@ -161,8 +163,20 @@ func TestGetUserPolicy(t *testing.T) {
assert.Equal(t, http.StatusOK, response.Code)
}
-func TestDeleteUser(t *testing.T) {
+func TestUpdateUser(t *testing.T) {
userName := aws.String("Test")
+ newUserName := aws.String("Test-New")
+ params := &iam.UpdateUserInput{NewUserName: newUserName, UserName: userName}
+ req, _ := iam.New(session.New()).UpdateUserRequest(params)
+ _ = req.Build()
+ out := UpdateUserResponse{}
+ 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-New")
params := &iam.DeleteUserInput{UserName: userName}
req, _ := iam.New(session.New()).DeleteUserRequest(params)
_ = req.Build()
@@ -179,3 +193,24 @@ func executeRequest(req *http.Request, v interface{}) (*httptest.ResponseRecorde
apiRouter.ServeHTTP(rr, req)
return rr, xml.Unmarshal(rr.Body.Bytes(), &v)
}
+
+func TestHandleImplicitUsername(t *testing.T) {
+ var tests = []struct {
+ r *http.Request
+ values url.Values
+ userName string
+ }{
+ {&http.Request{}, url.Values{}, ""},
+ {&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8"}}}, url.Values{}, "test1"},
+ {&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 =197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8"}}}, url.Values{}, ""},
+ {&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam/aws4_request SignedHeaders=content-type;host;x-amz-date Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8"}}}, url.Values{}, ""},
+ {&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=197FSAQ7HHTA48X64O3A/20220420/test1/iam, SignedHeaders=content-type;host;x-amz-date, Signature=6757dc6b3d7534d67e17842760310e99ee695408497f6edc4fdb84770c252dc8"}}}, url.Values{}, ""},
+ }
+
+ for i, test := range tests {
+ handleImplicitUsername(test.r, test.values)
+ if un := test.values.Get("UserName"); un != test.userName {
+ t.Errorf("No.%d: Got: %v, Expected: %v", i, un, test.userName)
+ }
+ }
+}