mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2024-01-19 02:48:24 +00:00
init Iam Api Server
This commit is contained in:
parent
c276117fef
commit
03c7953254
|
@ -23,6 +23,7 @@ var Commands = []*Command{
|
||||||
cmdMaster,
|
cmdMaster,
|
||||||
cmdMount,
|
cmdMount,
|
||||||
cmdS3,
|
cmdS3,
|
||||||
|
cmdIam,
|
||||||
cmdMsgBroker,
|
cmdMsgBroker,
|
||||||
cmdScaffold,
|
cmdScaffold,
|
||||||
cmdServer,
|
cmdServer,
|
||||||
|
|
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.ParseFilerGrpcAddress(*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
|
||||||
|
}
|
81
weed/iamapi/iamapi_handlers.go
Normal file
81
weed/iamapi/iamapi_handlers.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
69
weed/iamapi/iamapi_management_handlers.go
Normal file
69
weed/iamapi/iamapi_management_handlers.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package iamapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/pb/iam_pb"
|
||||||
|
"github.com/chrislusf/seaweedfs/weed/s3api/s3err"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
// "github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/iam"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = "2010-05-08"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListUsersResponse struct {
|
||||||
|
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"`
|
||||||
|
ResponseMetadata struct {
|
||||||
|
RequestId string `xml:"RequestId"`
|
||||||
|
} `xml:"ResponseMetadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// {'Action': 'CreateUser', 'Version': '2010-05-08', 'UserName': 'Bob'}
|
||||||
|
// {'Action': 'ListUsers', 'Version': '2010-05-08'}
|
||||||
|
func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) ListUsersResponse {
|
||||||
|
glog.Info("Do ListUsers")
|
||||||
|
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(values url.Values) ListUsersResponse {
|
||||||
|
return ListUsersResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
s3cfg := &iam_pb.S3ApiConfiguration{}
|
||||||
|
if err := iama.GetS3ApiConfiguration(s3cfg); err != nil {
|
||||||
|
writeErrorResponse(w, s3err.ErrInternalError, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Info("values ", values)
|
||||||
|
var response interface{}
|
||||||
|
switch r.Form.Get("Action") {
|
||||||
|
case "ListUsers":
|
||||||
|
response = iama.ListUsers(s3cfg, values)
|
||||||
|
case "ListAccessKeys":
|
||||||
|
response = iama.ListAccessKeys(values)
|
||||||
|
default:
|
||||||
|
writeErrorResponse(w, s3err.ErrNotImplemented, r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseXML(w, encodeResponse(response))
|
||||||
|
}
|
72
weed/iamapi/iamapi_server.go
Normal file
72
weed/iamapi/iamapi_server.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package iamapi
|
||||||
|
|
||||||
|
// https://docs.aws.amazon.com/cli/latest/reference/iam/list-roles.html
|
||||||
|
// https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateRole.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"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 IamServerOption struct {
|
||||||
|
Masters string
|
||||||
|
Filer string
|
||||||
|
Port int
|
||||||
|
FilerGrpcAddress string
|
||||||
|
GrpcDialOption grpc.DialOption
|
||||||
|
}
|
||||||
|
|
||||||
|
type IamApiServer struct {
|
||||||
|
option *IamServerOption
|
||||||
|
masterClient *wdclient.MasterClient
|
||||||
|
filerclient *filer_pb.SeaweedFilerClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer *IamApiServer, err error) {
|
||||||
|
iamApiServer = &IamApiServer{
|
||||||
|
option: option,
|
||||||
|
masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")),
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (iama *IamApiServer) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = pb.WithGrpcFilerClient(iama.option.FilerGrpcAddress, iama.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
|
if err = filer.ReadEntry(iama.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
|
||||||
|
}
|
Loading…
Reference in a new issue