mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2024-01-19 02:48:24 +00:00
commit
e1fc78ed46
|
@ -25,6 +25,7 @@ var Commands = []*Command{
|
||||||
cmdMaster,
|
cmdMaster,
|
||||||
cmdMount,
|
cmdMount,
|
||||||
cmdS3,
|
cmdS3,
|
||||||
|
cmdIam,
|
||||||
cmdMsgBroker,
|
cmdMsgBroker,
|
||||||
cmdScaffold,
|
cmdScaffold,
|
||||||
cmdServer,
|
cmdServer,
|
||||||
|
|
|
@ -25,6 +25,8 @@ var (
|
||||||
filerS3Options S3Options
|
filerS3Options S3Options
|
||||||
filerStartWebDav *bool
|
filerStartWebDav *bool
|
||||||
filerWebDavOptions WebDavOption
|
filerWebDavOptions WebDavOption
|
||||||
|
filerStartIam *bool
|
||||||
|
filerIamOptions IamOptions
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilerOptions struct {
|
type FilerOptions struct {
|
||||||
|
@ -91,6 +93,10 @@ func init() {
|
||||||
filerWebDavOptions.tlsCertificate = cmdFiler.Flag.String("webdav.cert.file", "", "path to the TLS certificate file")
|
filerWebDavOptions.tlsCertificate = cmdFiler.Flag.String("webdav.cert.file", "", "path to the TLS certificate file")
|
||||||
filerWebDavOptions.cacheDir = cmdFiler.Flag.String("webdav.cacheDir", os.TempDir(), "local cache directory for file chunks")
|
filerWebDavOptions.cacheDir = cmdFiler.Flag.String("webdav.cacheDir", os.TempDir(), "local cache directory for file chunks")
|
||||||
filerWebDavOptions.cacheSizeMB = cmdFiler.Flag.Int64("webdav.cacheCapacityMB", 1000, "local cache capacity in MB")
|
filerWebDavOptions.cacheSizeMB = cmdFiler.Flag.Int64("webdav.cacheCapacityMB", 1000, "local cache capacity in MB")
|
||||||
|
|
||||||
|
// start iam on filer
|
||||||
|
filerStartIam = cmdFiler.Flag.Bool("iam", false, "whether to start IAM service")
|
||||||
|
filerIamOptions.port = cmdFiler.Flag.Int("iam.port", 8111, "iam server http listen port")
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdFiler = &Command{
|
var cmdFiler = &Command{
|
||||||
|
@ -121,22 +127,33 @@ func runFiler(cmd *Command, args []string) bool {
|
||||||
|
|
||||||
go stats_collect.StartMetricsServer(*f.metricsHttpPort)
|
go stats_collect.StartMetricsServer(*f.metricsHttpPort)
|
||||||
|
|
||||||
|
filerAddress := fmt.Sprintf("%s:%d", *f.ip, *f.port)
|
||||||
|
startDelay := time.Duration(2)
|
||||||
if *filerStartS3 {
|
if *filerStartS3 {
|
||||||
filerAddress := fmt.Sprintf("%s:%d", *f.ip, *f.port)
|
|
||||||
filerS3Options.filer = &filerAddress
|
filerS3Options.filer = &filerAddress
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(startDelay * time.Second)
|
||||||
filerS3Options.startS3Server()
|
filerS3Options.startS3Server()
|
||||||
}()
|
}()
|
||||||
|
startDelay++
|
||||||
}
|
}
|
||||||
|
|
||||||
if *filerStartWebDav {
|
if *filerStartWebDav {
|
||||||
filerAddress := fmt.Sprintf("%s:%d", *f.ip, *f.port)
|
|
||||||
filerWebDavOptions.filer = &filerAddress
|
filerWebDavOptions.filer = &filerAddress
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(startDelay * time.Second)
|
||||||
filerWebDavOptions.startWebDav()
|
filerWebDavOptions.startWebDav()
|
||||||
}()
|
}()
|
||||||
|
startDelay++
|
||||||
|
}
|
||||||
|
|
||||||
|
if *filerStartIam {
|
||||||
|
filerIamOptions.filer = &filerAddress
|
||||||
|
filerIamOptions.masters = f.masters
|
||||||
|
go func() {
|
||||||
|
time.Sleep(startDelay * time.Second)
|
||||||
|
filerIamOptions.startIamServer()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
f.startFiler()
|
f.startFiler()
|
||||||
|
|
97
weed/command/iam.go
Normal file
97
weed/command/iam.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/iamapi"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/security"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/util"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
iamStandaloneOptions IamOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
type IamOptions struct {
|
||||||
|
filer *string
|
||||||
|
masters *string
|
||||||
|
port *int
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cmdIam.Run = runIam // break init cycle
|
||||||
|
iamStandaloneOptions.filer = cmdIam.Flag.String("filer", "localhost:8888", "filer server address")
|
||||||
|
iamStandaloneOptions.masters = cmdIam.Flag.String("master", "localhost:9333", "comma-separated master servers")
|
||||||
|
iamStandaloneOptions.port = cmdIam.Flag.Int("port", 8111, "iam server http listen port")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdIam = &Command{
|
||||||
|
UsageLine: "iam [-port=8111] [-filer=<ip:port>] [-masters=<ip:port>,<ip:port>]",
|
||||||
|
Short: "start a iam API compatible server",
|
||||||
|
Long: "start a iam API compatible server.",
|
||||||
|
}
|
||||||
|
|
||||||
|
func runIam(cmd *Command, args []string) bool {
|
||||||
|
return iamStandaloneOptions.startIamServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iamopt *IamOptions) startIamServer() bool {
|
||||||
|
filerGrpcAddress, err := pb.ParseServerToGrpcAddress(*iamopt.filer)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatal(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
|
||||||
|
for {
|
||||||
|
err = pb.WithGrpcFilerClient(filerGrpcAddress, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
|
resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get filer %s configuration: %v", filerGrpcAddress, err)
|
||||||
|
}
|
||||||
|
glog.V(0).Infof("IAM read filer configuration: %s", resp)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
glog.V(0).Infof("wait to connect to filer %s grpc address %s", *iamopt.filer, filerGrpcAddress)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
} else {
|
||||||
|
glog.V(0).Infof("connected to filer %s grpc address %s", *iamopt.filer, filerGrpcAddress)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router := mux.NewRouter().SkipClean(true)
|
||||||
|
_, iamApiServer_err := iamapi.NewIamApiServer(router, &iamapi.IamServerOption{
|
||||||
|
Filer: *iamopt.filer,
|
||||||
|
Port: *iamopt.port,
|
||||||
|
FilerGrpcAddress: filerGrpcAddress,
|
||||||
|
GrpcDialOption: grpcDialOption,
|
||||||
|
})
|
||||||
|
glog.V(0).Info("NewIamApiServer created")
|
||||||
|
if iamApiServer_err != nil {
|
||||||
|
glog.Fatalf("IAM API Server startup error: %v", iamApiServer_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpS := &http.Server{Handler: router}
|
||||||
|
|
||||||
|
listenAddress := fmt.Sprintf(":%d", *iamopt.port)
|
||||||
|
iamApiListener, err := util.NewListener(listenAddress, time.Duration(10)*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("IAM API Server listener on %s error: %v", listenAddress, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(0).Infof("Start Seaweed IAM API Server %s at http port %d", util.Version(), *iamopt.port)
|
||||||
|
if err = httpS.Serve(iamApiListener); err != nil {
|
||||||
|
glog.Fatalf("IAM API Server Fail to serve: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -41,8 +41,12 @@ func ReadContent(filerAddress string, dir, name string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveAs(host string, port int, dir, name string, contentType string, byteBuffer *bytes.Buffer) error {
|
func SaveAs(host string, port int, dir, name string, contentType string, byteBuffer *bytes.Buffer) error {
|
||||||
|
var target string
|
||||||
target := fmt.Sprintf("http://%s:%d%s/%s", host, port, dir, name)
|
if port == 0 {
|
||||||
|
target = fmt.Sprintf("http://%s%s/%s", host, dir, name)
|
||||||
|
} else {
|
||||||
|
target = fmt.Sprintf("http://%s:%d%s/%s", host, port, dir, name)
|
||||||
|
}
|
||||||
|
|
||||||
// set the HTTP method, url, and request body
|
// set the HTTP method, url, and request body
|
||||||
req, err := http.NewRequest(http.MethodPut, target, byteBuffer)
|
req, err := http.NewRequest(http.MethodPut, target, byteBuffer)
|
||||||
|
|
100
weed/iamapi/iamapi_handlers.go
Normal file
100
weed/iamapi/iamapi_handlers.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package iamapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/service/iam"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mimeType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
mimeNone mimeType = ""
|
||||||
|
mimeXML mimeType = "application/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setCommonHeaders(w http.ResponseWriter) {
|
||||||
|
w.Header().Set("x-amz-request-id", fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||||
|
w.Header().Set("Accept-Ranges", "bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encodes the response headers into XML format.
|
||||||
|
func encodeResponse(response interface{}) []byte {
|
||||||
|
var bytesBuffer bytes.Buffer
|
||||||
|
bytesBuffer.WriteString(xml.Header)
|
||||||
|
e := xml.NewEncoder(&bytesBuffer)
|
||||||
|
e.Encode(response)
|
||||||
|
return bytesBuffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the http routes match respond with MethodNotAllowed
|
||||||
|
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
glog.V(0).Infof("unsupported %s %s", r.Method, r.RequestURI)
|
||||||
|
writeErrorResponse(w, s3err.ErrMethodNotAllowed, r.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeErrorResponse(w http.ResponseWriter, errorCode s3err.ErrorCode, reqURL *url.URL) {
|
||||||
|
apiError := s3err.GetAPIError(errorCode)
|
||||||
|
errorResponse := getRESTErrorResponse(apiError, reqURL.Path)
|
||||||
|
encodedErrorResponse := encodeResponse(errorResponse)
|
||||||
|
writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string) {
|
||||||
|
errCode := err.Error()
|
||||||
|
errorResp := ErrorResponse{}
|
||||||
|
errorResp.Error.Type = "Sender"
|
||||||
|
errorResp.Error.Code = &errCode
|
||||||
|
glog.Errorf("Response %+v", err)
|
||||||
|
switch errCode {
|
||||||
|
case iam.ErrCodeNoSuchEntityException:
|
||||||
|
msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value)
|
||||||
|
errorResp.Error.Message = &msg
|
||||||
|
writeResponse(w, http.StatusNotFound, encodeResponse(errorResp), mimeXML)
|
||||||
|
default:
|
||||||
|
writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRESTErrorResponse(err s3err.APIError, resource string) s3err.RESTErrorResponse {
|
||||||
|
return s3err.RESTErrorResponse{
|
||||||
|
Code: err.Code,
|
||||||
|
Message: err.Description,
|
||||||
|
Resource: resource,
|
||||||
|
RequestID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) {
|
||||||
|
setCommonHeaders(w)
|
||||||
|
if response != nil {
|
||||||
|
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
|
||||||
|
}
|
||||||
|
if mType != mimeNone {
|
||||||
|
w.Header().Set("Content-Type", string(mType))
|
||||||
|
}
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
if response != nil {
|
||||||
|
glog.V(4).Infof("status %d %s: %s", statusCode, mType, string(response))
|
||||||
|
_, err := w.Write(response)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(0).Infof("write err: %v", err)
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSuccessResponseXML(w http.ResponseWriter, response []byte) {
|
||||||
|
writeResponse(w, http.StatusOK, response, mimeXML)
|
||||||
|
}
|
322
weed/iamapi/iamapi_management_handlers.go
Normal file
322
weed/iamapi/iamapi_management_handlers.go
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
package iamapi
|
||||||
|
|
||||||
|
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"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/service/iam"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
seededRand *rand.Rand = rand.New(
|
||||||
|
rand.NewSource(time.Now().UnixNano()))
|
||||||
|
policyDocuments = map[string]*PolicyDocument{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Statement struct {
|
||||||
|
Effect string `json:"Effect"`
|
||||||
|
Action []string `json:"Action"`
|
||||||
|
Resource []string `json:"Resource"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PolicyDocument struct {
|
||||||
|
Version string `json:"Version"`
|
||||||
|
Statement []*Statement `json:"Statement"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Hash(s *string) string {
|
||||||
|
h := sha1.New()
|
||||||
|
h.Write([]byte(*s))
|
||||||
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringWithCharset(length int, charset string) string {
|
||||||
|
b := make([]byte, length)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = charset[seededRand.Intn(len(charset))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListUsersResponse) {
|
||||||
|
for _, ident := range s3cfg.Identities {
|
||||||
|
resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name})
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListAccessKeysResponse) {
|
||||||
|
status := iam.StatusTypeActive
|
||||||
|
for _, ident := range s3cfg.Identities {
|
||||||
|
for _, cred := range ident.Credentials {
|
||||||
|
resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata,
|
||||||
|
&iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateUserResponse) {
|
||||||
|
userName := values.Get("UserName")
|
||||||
|
resp.CreateUserResult.User.UserName = &userName
|
||||||
|
s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName})
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) {
|
||||||
|
for i, ident := range s3cfg.Identities {
|
||||||
|
if userName == ident.Name {
|
||||||
|
ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp GetUserResponse, err error) {
|
||||||
|
for _, ident := range s3cfg.Identities {
|
||||||
|
if userName == ident.Name {
|
||||||
|
resp.GetUserResult.User = iam.User{UserName: &ident.Name}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return policyDocument, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreatePolicyResponse, err error) {
|
||||||
|
policyName := values.Get("PolicyName")
|
||||||
|
policyDocumentString := values.Get("PolicyDocument")
|
||||||
|
policyDocument, err := GetPolicyDocument(&policyDocumentString)
|
||||||
|
if err != nil {
|
||||||
|
return CreatePolicyResponse{}, err
|
||||||
|
}
|
||||||
|
policyId := Hash(&policyDocumentString)
|
||||||
|
arn := fmt.Sprintf("arn:aws:iam:::policy/%s", policyName)
|
||||||
|
resp.CreatePolicyResult.Policy.PolicyName = &policyName
|
||||||
|
resp.CreatePolicyResult.Policy.Arn = &arn
|
||||||
|
resp.CreatePolicyResult.Policy.PolicyId = &policyId
|
||||||
|
policyDocuments[policyName] = &policyDocument
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) {
|
||||||
|
userName := values.Get("UserName")
|
||||||
|
policyName := values.Get("PolicyName")
|
||||||
|
policyDocumentString := values.Get("PolicyDocument")
|
||||||
|
policyDocument, err := GetPolicyDocument(&policyDocumentString)
|
||||||
|
if err != nil {
|
||||||
|
return PutUserPolicyResponse{}, err
|
||||||
|
}
|
||||||
|
policyDocuments[policyName] = &policyDocument
|
||||||
|
actions := GetActions(&policyDocument)
|
||||||
|
for _, ident := range s3cfg.Identities {
|
||||||
|
if userName == ident.Name {
|
||||||
|
for _, action := range actions {
|
||||||
|
ident.Actions = append(ident.Actions, action)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
for _, statement := range policy.Statement {
|
||||||
|
if statement.Effect != "Allow" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, resource := range statement.Resource {
|
||||||
|
// Parse "arn:aws:s3:::my-bucket/shared/*"
|
||||||
|
res := strings.Split(resource, ":")
|
||||||
|
if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" {
|
||||||
|
glog.Infof("not match resource: %s", res)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, action := range statement.Action {
|
||||||
|
// Parse "s3:Get*"
|
||||||
|
act := strings.Split(action, ":")
|
||||||
|
if len(act) != 2 || act[0] != "s3" {
|
||||||
|
glog.Infof("not match action: %s", act)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if res[5] == "*" {
|
||||||
|
actions = append(actions, MapAction(act[1]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Parse my-bucket/shared/*
|
||||||
|
path := strings.Split(res[5], "/")
|
||||||
|
if len(path) != 2 || path[1] != "*" {
|
||||||
|
glog.Infof("not match bucket: %s", path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateAccessKeyResponse) {
|
||||||
|
userName := values.Get("UserName")
|
||||||
|
status := iam.StatusTypeActive
|
||||||
|
accessKeyId := StringWithCharset(21, charsetUpper)
|
||||||
|
secretAccessKey := StringWithCharset(42, charset)
|
||||||
|
resp.CreateAccessKeyResult.AccessKey.AccessKeyId = &accessKeyId
|
||||||
|
resp.CreateAccessKeyResult.AccessKey.SecretAccessKey = &secretAccessKey
|
||||||
|
resp.CreateAccessKeyResult.AccessKey.UserName = &userName
|
||||||
|
resp.CreateAccessKeyResult.AccessKey.Status = &status
|
||||||
|
changed := false
|
||||||
|
for _, ident := range s3cfg.Identities {
|
||||||
|
if userName == ident.Name {
|
||||||
|
ident.Credentials = append(ident.Credentials,
|
||||||
|
&iam_pb.Credential{AccessKey: accessKeyId, SecretKey: secretAccessKey})
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
s3cfg.Identities = append(s3cfg.Identities,
|
||||||
|
&iam_pb.Identity{Name: userName,
|
||||||
|
Credentials: []*iam_pb.Credential{
|
||||||
|
{
|
||||||
|
AccessKey: accessKeyId,
|
||||||
|
SecretKey: secretAccessKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteAccessKeyResponse) {
|
||||||
|
userName := values.Get("UserName")
|
||||||
|
accessKeyId := values.Get("AccessKeyId")
|
||||||
|
for _, ident := range s3cfg.Identities {
|
||||||
|
if userName == ident.Name {
|
||||||
|
for i, cred := range ident.Credentials {
|
||||||
|
if cred.AccessKey == accessKeyId {
|
||||||
|
ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values := r.PostForm
|
||||||
|
var s3cfgLock sync.RWMutex
|
||||||
|
s3cfgLock.RLock()
|
||||||
|
s3cfg := &iam_pb.S3ApiConfiguration{}
|
||||||
|
if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil {
|
||||||
|
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s3cfgLock.RUnlock()
|
||||||
|
|
||||||
|
glog.V(4).Infof("DoActions: %+v", values)
|
||||||
|
var response interface{}
|
||||||
|
var err error
|
||||||
|
changed := true
|
||||||
|
switch r.Form.Get("Action") {
|
||||||
|
case "ListUsers":
|
||||||
|
response = iama.ListUsers(s3cfg, values)
|
||||||
|
changed = false
|
||||||
|
case "ListAccessKeys":
|
||||||
|
response = iama.ListAccessKeys(s3cfg, values)
|
||||||
|
changed = false
|
||||||
|
case "CreateUser":
|
||||||
|
response = iama.CreateUser(s3cfg, values)
|
||||||
|
case "GetUser":
|
||||||
|
userName := values.Get("UserName")
|
||||||
|
response, err = iama.GetUser(s3cfg, userName)
|
||||||
|
if err != nil {
|
||||||
|
writeIamErrorResponse(w, err, "user", userName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "DeleteUser":
|
||||||
|
userName := values.Get("UserName")
|
||||||
|
response, err = iama.DeleteUser(s3cfg, userName)
|
||||||
|
if err != nil {
|
||||||
|
writeIamErrorResponse(w, err, "user", userName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "CreateAccessKey":
|
||||||
|
response = iama.CreateAccessKey(s3cfg, values)
|
||||||
|
case "DeleteAccessKey":
|
||||||
|
response = iama.DeleteAccessKey(s3cfg, values)
|
||||||
|
case "CreatePolicy":
|
||||||
|
response, err = iama.CreatePolicy(s3cfg, values)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("CreatePolicy: %+v", err)
|
||||||
|
writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "PutUserPolicy":
|
||||||
|
response, err = iama.PutUserPolicy(s3cfg, values)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("PutUserPolicy: %+v", err)
|
||||||
|
writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
writeErrorResponse(w, s3err.ErrNotImplemented, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
s3cfgLock.Lock()
|
||||||
|
err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg)
|
||||||
|
s3cfgLock.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeSuccessResponseXML(w, encodeResponse(response))
|
||||||
|
}
|
93
weed/iamapi/iamapi_response.go
Normal file
93
weed/iamapi/iamapi_response.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package iamapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/service/iam"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommonResponse struct {
|
||||||
|
ResponseMetadata struct {
|
||||||
|
RequestId string `xml:"RequestId"`
|
||||||
|
} `xml:"ResponseMetadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListUsersResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListUsersResponse"`
|
||||||
|
ListUsersResult struct {
|
||||||
|
Users []*iam.User `xml:"Users>member"`
|
||||||
|
IsTruncated bool `xml:"IsTruncated"`
|
||||||
|
} `xml:"ListUsersResult"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListAccessKeysResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListAccessKeysResponse"`
|
||||||
|
ListAccessKeysResult struct {
|
||||||
|
AccessKeyMetadata []*iam.AccessKeyMetadata `xml:"AccessKeyMetadata>member"`
|
||||||
|
IsTruncated bool `xml:"IsTruncated"`
|
||||||
|
} `xml:"ListAccessKeysResult"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteAccessKeyResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteAccessKeyResponse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePolicyResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreatePolicyResponse"`
|
||||||
|
CreatePolicyResult struct {
|
||||||
|
Policy iam.Policy `xml:"Policy"`
|
||||||
|
} `xml:"CreatePolicyResult"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateUserResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateUserResponse"`
|
||||||
|
CreateUserResult struct {
|
||||||
|
User iam.User `xml:"User"`
|
||||||
|
} `xml:"CreateUserResult"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteUserResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteUserResponse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetUserResponse"`
|
||||||
|
GetUserResult struct {
|
||||||
|
User iam.User `xml:"User"`
|
||||||
|
} `xml:"GetUserResult"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateAccessKeyResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateAccessKeyResponse"`
|
||||||
|
CreateAccessKeyResult struct {
|
||||||
|
AccessKey iam.AccessKey `xml:"AccessKey"`
|
||||||
|
} `xml:"CreateAccessKeyResult"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PutUserPolicyResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
CommonResponse
|
||||||
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ErrorResponse"`
|
||||||
|
Error struct {
|
||||||
|
iam.ErrorDetails
|
||||||
|
Type string `xml:"Type"`
|
||||||
|
} `xml:"Error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CommonResponse) SetRequestId() {
|
||||||
|
r.ResponseMetadata.RequestId = fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
|
}
|
104
weed/iamapi/iamapi_server.go
Normal file
104
weed/iamapi/iamapi_server.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package iamapi
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/cli/latest/reference/iam/list-roles.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/filer"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/wdclient"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IamS3ApiConfig interface {
|
||||||
|
GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||||
|
PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IamS3ApiConfigure struct {
|
||||||
|
option *IamServerOption
|
||||||
|
masterClient *wdclient.MasterClient
|
||||||
|
}
|
||||||
|
|
||||||
|
type IamServerOption struct {
|
||||||
|
Masters string
|
||||||
|
Filer string
|
||||||
|
Port int
|
||||||
|
FilerGrpcAddress string
|
||||||
|
GrpcDialOption grpc.DialOption
|
||||||
|
}
|
||||||
|
|
||||||
|
type IamApiServer struct {
|
||||||
|
s3ApiConfig IamS3ApiConfig
|
||||||
|
filerclient *filer_pb.SeaweedFilerClient
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ",")),
|
||||||
|
}
|
||||||
|
|
||||||
|
iamApiServer = &IamApiServer{
|
||||||
|
s3ApiConfig: s3ApiConfigure,
|
||||||
|
}
|
||||||
|
|
||||||
|
iamApiServer.registerRouter(router)
|
||||||
|
|
||||||
|
return iamApiServer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iama *IamApiServer) registerRouter(router *mux.Router) {
|
||||||
|
// API Router
|
||||||
|
apiRouter := router.PathPrefix("/").Subrouter()
|
||||||
|
// ListBuckets
|
||||||
|
|
||||||
|
// apiRouter.Methods("GET").Path("/").HandlerFunc(track(s3a.iam.Auth(s3a.ListBucketsHandler, ACTION_ADMIN), "LIST"))
|
||||||
|
apiRouter.Path("/").Methods("POST").HandlerFunc(iama.DoActions)
|
||||||
|
// NotFound
|
||||||
|
apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if err = filer.ReadEntry(iam.masterClient, client, filer.IamConfigDirecotry, filer.IamIdentityFile, &buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
if err = filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iam IamS3ApiConfigure) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
if err := filer.S3ConfigurationToText(&buf, s3cfg); err != nil {
|
||||||
|
return fmt.Errorf("S3ConfigurationToText: %s", err)
|
||||||
|
}
|
||||||
|
return pb.WithGrpcFilerClient(
|
||||||
|
iam.option.FilerGrpcAddress,
|
||||||
|
iam.option.GrpcDialOption,
|
||||||
|
func(client filer_pb.SeaweedFilerClient) error {
|
||||||
|
if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamIdentityFile, buf.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
157
weed/iamapi/iamapi_test.go
Normal file
157
weed/iamapi/iamapi_test.go
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
package iamapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/iam"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var S3config iam_pb.S3ApiConfiguration
|
||||||
|
var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||||
|
var PutS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error)
|
||||||
|
|
||||||
|
type iamS3ApiConfigureMock struct{}
|
||||||
|
|
||||||
|
func (iam iamS3ApiConfigureMock) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
||||||
|
s3cfg = &S3config
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iam iamS3ApiConfigureMock) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
||||||
|
S3config = *s3cfg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = IamApiServer{}
|
||||||
|
|
||||||
|
func TestCreateUser(t *testing.T) {
|
||||||
|
userName := aws.String("Test")
|
||||||
|
params := &iam.CreateUserInput{UserName: userName}
|
||||||
|
req, _ := iam.New(session.New()).CreateUserRequest(params)
|
||||||
|
_ = req.Build()
|
||||||
|
out := CreateUserResponse{}
|
||||||
|
response, err := executeRequest(req.HTTPRequest, out)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, http.StatusOK, response.Code)
|
||||||
|
//assert.Equal(t, out.XMLName, "lol")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListUsers(t *testing.T) {
|
||||||
|
params := &iam.ListUsersInput{}
|
||||||
|
req, _ := iam.New(session.New()).ListUsersRequest(params)
|
||||||
|
_ = req.Build()
|
||||||
|
out := ListUsersResponse{}
|
||||||
|
response, err := executeRequest(req.HTTPRequest, out)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, http.StatusOK, response.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListAccessKeys(t *testing.T) {
|
||||||
|
svc := iam.New(session.New())
|
||||||
|
params := &iam.ListAccessKeysInput{}
|
||||||
|
req, _ := svc.ListAccessKeysRequest(params)
|
||||||
|
_ = req.Build()
|
||||||
|
out := ListAccessKeysResponse{}
|
||||||
|
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.StatusNotFound, response.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetUser(t *testing.T) {
|
||||||
|
userName := aws.String("Test")
|
||||||
|
params := &iam.GetUserInput{UserName: userName}
|
||||||
|
req, _ := iam.New(session.New()).GetUserRequest(params)
|
||||||
|
_ = req.Build()
|
||||||
|
out := GetUserResponse{}
|
||||||
|
response, err := executeRequest(req.HTTPRequest, out)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, http.StatusNotFound, response.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo flat statement
|
||||||
|
func TestCreatePolicy(t *testing.T) {
|
||||||
|
params := &iam.CreatePolicyInput{
|
||||||
|
PolicyName: aws.String("S3-read-only-example-bucket"),
|
||||||
|
PolicyDocument: aws.String(`
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:Get*",
|
||||||
|
"s3:List*"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::EXAMPLE-BUCKET",
|
||||||
|
"arn:aws:s3:::EXAMPLE-BUCKET/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`),
|
||||||
|
}
|
||||||
|
req, _ := iam.New(session.New()).CreatePolicyRequest(params)
|
||||||
|
_ = req.Build()
|
||||||
|
out := CreatePolicyResponse{}
|
||||||
|
response, err := executeRequest(req.HTTPRequest, out)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, http.StatusOK, response.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPutUserPolicy(t *testing.T) {
|
||||||
|
userName := aws.String("Test")
|
||||||
|
params := &iam.PutUserPolicyInput{
|
||||||
|
UserName: userName,
|
||||||
|
PolicyName: aws.String("S3-read-only-example-bucket"),
|
||||||
|
PolicyDocument: aws.String(
|
||||||
|
`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:Get*",
|
||||||
|
"s3:List*"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::EXAMPLE-BUCKET",
|
||||||
|
"arn:aws:s3:::EXAMPLE-BUCKET/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`),
|
||||||
|
}
|
||||||
|
req, _ := iam.New(session.New()).PutUserPolicyRequest(params)
|
||||||
|
_ = req.Build()
|
||||||
|
out := PutUserPolicyResponse{}
|
||||||
|
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) {
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
apiRouter := mux.NewRouter().SkipClean(true)
|
||||||
|
a.s3ApiConfig = iamS3ApiConfigureMock{}
|
||||||
|
apiRouter.Path("/").Methods("POST").HandlerFunc(a.DoActions)
|
||||||
|
apiRouter.ServeHTTP(rr, req)
|
||||||
|
return rr, xml.Unmarshal(rr.Body.Bytes(), &v)
|
||||||
|
}
|
Loading…
Reference in a new issue