STS integration, JWT auth and Stateless MCS (#70)

This commit changes the authentication mechanism between mcs and minio to an sts
(security token service) schema using the user provided credentials, previously
mcs was using master credentials. With that said in order for you to
login to MCS as an admin your user must exists first on minio and have enough
privileges to do administrative operations.

```
./mc admin user add myminio alevsk alevsk12345
```

```
cat admin.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "admin:*",
        "s3:*"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}

./mc admin policy add myminio admin admin.json
```

```
./mc admin policy set myminio admin user=alevsk
```
This commit is contained in:
Lenin Alevski
2020-04-22 23:43:17 -07:00
committed by GitHub
parent 605b80037a
commit 0f52136fd2
29 changed files with 916 additions and 303 deletions

View File

@@ -31,7 +31,8 @@ import (
func registerAdminArnsHandlers(api *operations.McsAPI) {
// return a list of arns
api.AdminAPIArnListHandler = admin_api.ArnListHandlerFunc(func(params admin_api.ArnListParams, principal *models.Principal) middleware.Responder {
arnsResp, err := getArnsResponse()
sessionID := string(*principal)
arnsResp, err := getArnsResponse(sessionID)
if err != nil {
return admin_api.NewArnListDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -53,8 +54,8 @@ func getArns(ctx context.Context, client MinioAdmin) (*models.ArnsResponse, erro
}
// getArnsResponse returns a list of active arns in the instance
func getArnsResponse() (*models.ArnsResponse, error) {
mAdmin, err := newMAdminClient()
func getArnsResponse(sessionID string) (*models.ArnsResponse, error) {
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err

View File

@@ -33,7 +33,8 @@ import (
func registerConfigHandlers(api *operations.McsAPI) {
// List Configurations
api.AdminAPIListConfigHandler = admin_api.ListConfigHandlerFunc(func(params admin_api.ListConfigParams, principal *models.Principal) middleware.Responder {
configListResp, err := getListConfigResponse()
sessionID := string(*principal)
configListResp, err := getListConfigResponse(sessionID)
if err != nil {
return admin_api.NewListConfigDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -41,7 +42,8 @@ func registerConfigHandlers(api *operations.McsAPI) {
})
// Configuration Info
api.AdminAPIConfigInfoHandler = admin_api.ConfigInfoHandlerFunc(func(params admin_api.ConfigInfoParams, principal *models.Principal) middleware.Responder {
config, err := getConfigResponse(params)
sessionID := string(*principal)
config, err := getConfigResponse(sessionID, params)
if err != nil {
return admin_api.NewConfigInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -49,7 +51,8 @@ func registerConfigHandlers(api *operations.McsAPI) {
})
// Set Configuration
api.AdminAPISetConfigHandler = admin_api.SetConfigHandlerFunc(func(params admin_api.SetConfigParams, principal *models.Principal) middleware.Responder {
if err := setConfigResponse(params.Name, params.Body); err != nil {
sessionID := string(*principal)
if err := setConfigResponse(sessionID, params.Name, params.Body); err != nil {
return admin_api.NewSetConfigDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return admin_api.NewSetConfigNoContent()
@@ -75,8 +78,8 @@ func listConfig(client MinioAdmin) ([]*models.ConfigDescription, error) {
}
// getListConfigResponse performs listConfig() and serializes it to the handler's output
func getListConfigResponse() (*models.ListConfigResponse, error) {
mAdmin, err := newMAdminClient()
func getListConfigResponse(sessionID string) (*models.ListConfigResponse, error) {
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -122,8 +125,8 @@ func getConfig(client MinioAdmin, name string) ([]*models.ConfigurationKV, error
}
// getConfigResponse performs getConfig() and serializes it to the handler's output
func getConfigResponse(params admin_api.ConfigInfoParams) (*models.Configuration, error) {
mAdmin, err := newMAdminClient()
func getConfigResponse(sessionID string, params admin_api.ConfigInfoParams) (*models.Configuration, error) {
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -175,8 +178,8 @@ func buildConfig(configName *string, kvs []*models.ConfigurationKV) *string {
}
// setConfigResponse implements setConfig() to be used by handler
func setConfigResponse(name string, configRequest *models.SetConfigRequest) error {
mAdmin, err := newMAdminClient()
func setConfigResponse(sessionID string, name string, configRequest *models.SetConfigRequest) error {
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err

View File

@@ -34,7 +34,8 @@ import (
func registerGroupsHandlers(api *operations.McsAPI) {
// List Groups
api.AdminAPIListGroupsHandler = admin_api.ListGroupsHandlerFunc(func(params admin_api.ListGroupsParams, principal *models.Principal) middleware.Responder {
listGroupsResponse, err := getListGroupsResponse()
sessionID := string(*principal)
listGroupsResponse, err := getListGroupsResponse(sessionID)
if err != nil {
return admin_api.NewListGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -42,7 +43,8 @@ func registerGroupsHandlers(api *operations.McsAPI) {
})
// Group Info
api.AdminAPIGroupInfoHandler = admin_api.GroupInfoHandlerFunc(func(params admin_api.GroupInfoParams, principal *models.Principal) middleware.Responder {
groupInfo, err := getGroupInfoResponse(params)
sessionID := string(*principal)
groupInfo, err := getGroupInfoResponse(sessionID, params)
if err != nil {
return admin_api.NewGroupInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -50,21 +52,24 @@ func registerGroupsHandlers(api *operations.McsAPI) {
})
// Add Group
api.AdminAPIAddGroupHandler = admin_api.AddGroupHandlerFunc(func(params admin_api.AddGroupParams, principal *models.Principal) middleware.Responder {
if err := getAddGroupResponse(params.Body); err != nil {
sessionID := string(*principal)
if err := getAddGroupResponse(sessionID, params.Body); err != nil {
return admin_api.NewAddGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return admin_api.NewAddGroupCreated()
})
// Remove Group
api.AdminAPIRemoveGroupHandler = admin_api.RemoveGroupHandlerFunc(func(params admin_api.RemoveGroupParams, principal *models.Principal) middleware.Responder {
if err := getRemoveGroupResponse(params); err != nil {
sessionID := string(*principal)
if err := getRemoveGroupResponse(sessionID, params); err != nil {
return admin_api.NewRemoveGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return admin_api.NewRemoveGroupNoContent()
})
// Update Group
api.AdminAPIUpdateGroupHandler = admin_api.UpdateGroupHandlerFunc(func(params admin_api.UpdateGroupParams, principal *models.Principal) middleware.Responder {
groupUpdateResp, err := getUpdateGroupResponse(params)
sessionID := string(*principal)
groupUpdateResp, err := getUpdateGroupResponse(sessionID, params)
if err != nil {
return admin_api.NewUpdateGroupDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -82,9 +87,9 @@ func listGroups(ctx context.Context, client MinioAdmin) (*[]string, error) {
}
// getListGroupsResponse performs listGroups() and serializes it to the handler's output
func getListGroupsResponse() (*models.ListGroupsResponse, error) {
func getListGroupsResponse(sessionID string) (*models.ListGroupsResponse, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -116,9 +121,9 @@ func groupInfo(ctx context.Context, client MinioAdmin, group string) (*madmin.Gr
}
// getGroupInfoResponse performs groupInfo() and serializes it to the handler's output
func getGroupInfoResponse(params admin_api.GroupInfoParams) (*models.Group, error) {
func getGroupInfoResponse(sessionID string, params admin_api.GroupInfoParams) (*models.Group, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -157,7 +162,7 @@ func addGroup(ctx context.Context, client MinioAdmin, group string, members []st
}
// getAddGroupResponse performs addGroup() and serializes it to the handler's output
func getAddGroupResponse(params *models.AddGroupRequest) error {
func getAddGroupResponse(sessionID string, params *models.AddGroupRequest) error {
ctx := context.Background()
// AddGroup request needed to proceed
if params == nil {
@@ -165,7 +170,7 @@ func getAddGroupResponse(params *models.AddGroupRequest) error {
return errors.New(500, "error AddGroup body not in request")
}
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
@@ -196,14 +201,14 @@ func removeGroup(ctx context.Context, client MinioAdmin, group string) error {
}
// getRemoveGroupResponse performs removeGroup() and serializes it to the handler's output
func getRemoveGroupResponse(params admin_api.RemoveGroupParams) error {
func getRemoveGroupResponse(sessionID string, params admin_api.RemoveGroupParams) error {
ctx := context.Background()
if params.Name == "" {
log.Println("error group name not in request")
return errors.New(500, "error group name not in request")
}
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
@@ -276,7 +281,7 @@ func setGroupStatus(ctx context.Context, client MinioAdmin, group, status string
// getUpdateGroupResponse updates a group by adding or removing it's members depending on the request,
// also sets the group's status if status in the request is different than the current one.
// Then serializes the output to be used by the handler.
func getUpdateGroupResponse(params admin_api.UpdateGroupParams) (*models.Group, error) {
func getUpdateGroupResponse(sessionID string, params admin_api.UpdateGroupParams) (*models.Group, error) {
ctx := context.Background()
if params.Name == "" {
log.Println("error group name not in request")
@@ -289,7 +294,7 @@ func getUpdateGroupResponse(params admin_api.UpdateGroupParams) (*models.Group,
expectedGroupUpdate := params.Body
groupName := params.Name
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err

View File

@@ -31,7 +31,8 @@ import (
func registerAdminInfoHandlers(api *operations.McsAPI) {
// return usage stats
api.AdminAPIAdminInfoHandler = admin_api.AdminInfoHandlerFunc(func(params admin_api.AdminInfoParams, principal *models.Principal) middleware.Responder {
infoResp, err := getAdminInfoResponse()
sessionID := string(*principal)
infoResp, err := getAdminInfoResponse(sessionID)
if err != nil {
return admin_api.NewAdminInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -62,8 +63,8 @@ func getAdminInfo(ctx context.Context, client MinioAdmin) (*UsageInfo, error) {
}
// getAdminInfoResponse returns the response containing total buckets, objects and usage.
func getAdminInfoResponse() (*models.AdminInfoResponse, error) {
mAdmin, err := newMAdminClient()
func getAdminInfoResponse(sessionID string) (*models.AdminInfoResponse, error) {
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err

View File

@@ -32,7 +32,8 @@ import (
func registerAdminNotificationEndpointsHandlers(api *operations.McsAPI) {
// return a list of notification endpoints
api.AdminAPINotificationEndpointListHandler = admin_api.NotificationEndpointListHandlerFunc(func(params admin_api.NotificationEndpointListParams, principal *models.Principal) middleware.Responder {
notifEndpoints, err := getNotificationEndpointsResponse()
sessionID := string(*principal)
notifEndpoints, err := getNotificationEndpointsResponse(sessionID)
if err != nil {
return admin_api.NewNotificationEndpointListDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -40,7 +41,8 @@ func registerAdminNotificationEndpointsHandlers(api *operations.McsAPI) {
})
// add a new notification endpoints
api.AdminAPIAddNotificationEndpointHandler = admin_api.AddNotificationEndpointHandlerFunc(func(params admin_api.AddNotificationEndpointParams, principal *models.Principal) middleware.Responder {
notifEndpoints, err := getAddNotificationEndpointResponse(&params)
sessionID := string(*principal)
notifEndpoints, err := getAddNotificationEndpointResponse(sessionID, &params)
if err != nil {
return admin_api.NewAddNotificationEndpointDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -78,8 +80,8 @@ func getNotificationEndpoints(ctx context.Context, client MinioAdmin) (*models.N
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getNotificationEndpointsResponse() (*models.NotifEndpointResponse, error) {
mAdmin, err := newMAdminClient()
func getNotificationEndpointsResponse(sessionID string) (*models.NotifEndpointResponse, error) {
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -151,8 +153,8 @@ func addNotificationEndpoint(ctx context.Context, client MinioAdmin, params *adm
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getAddNotificationEndpointResponse(params *admin_api.AddNotificationEndpointParams) (*models.NotificationEndpoint, error) {
mAdmin, err := newMAdminClient()
func getAddNotificationEndpointResponse(sessionID string, params *admin_api.AddNotificationEndpointParams) (*models.NotificationEndpoint, error) {
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err

View File

@@ -35,7 +35,8 @@ import (
func registersPoliciesHandler(api *operations.McsAPI) {
// List Policies
api.AdminAPIListPoliciesHandler = admin_api.ListPoliciesHandlerFunc(func(params admin_api.ListPoliciesParams, principal *models.Principal) middleware.Responder {
listPoliciesResponse, err := getListPoliciesResponse()
sessionID := string(*principal)
listPoliciesResponse, err := getListPoliciesResponse(sessionID)
if err != nil {
return admin_api.NewListPoliciesDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -43,7 +44,8 @@ func registersPoliciesHandler(api *operations.McsAPI) {
})
// Policy Info
api.AdminAPIPolicyInfoHandler = admin_api.PolicyInfoHandlerFunc(func(params admin_api.PolicyInfoParams, principal *models.Principal) middleware.Responder {
policyInfo, err := getPolicyInfoResponse(params)
sessionID := string(*principal)
policyInfo, err := getPolicyInfoResponse(sessionID, params)
if err != nil {
return admin_api.NewPolicyInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -51,7 +53,8 @@ func registersPoliciesHandler(api *operations.McsAPI) {
})
// Add Policy
api.AdminAPIAddPolicyHandler = admin_api.AddPolicyHandlerFunc(func(params admin_api.AddPolicyParams, principal *models.Principal) middleware.Responder {
policyResponse, err := getAddPolicyResponse(params.Body)
sessionID := string(*principal)
policyResponse, err := getAddPolicyResponse(sessionID, params.Body)
if err != nil {
return admin_api.NewAddPolicyDefault(500).WithPayload(&models.Error{
Code: 500,
@@ -62,14 +65,16 @@ func registersPoliciesHandler(api *operations.McsAPI) {
})
// Remove Policy
api.AdminAPIRemovePolicyHandler = admin_api.RemovePolicyHandlerFunc(func(params admin_api.RemovePolicyParams, principal *models.Principal) middleware.Responder {
if err := getRemovePolicyResponse(params); err != nil {
sessionID := string(*principal)
if err := getRemovePolicyResponse(sessionID, params); err != nil {
return admin_api.NewRemovePolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return admin_api.NewRemovePolicyNoContent()
})
// Set Policy
api.AdminAPISetPolicyHandler = admin_api.SetPolicyHandlerFunc(func(params admin_api.SetPolicyParams, principal *models.Principal) middleware.Responder {
if err := getSetPolicyResponse(params.Name, params.Body); err != nil {
sessionID := string(*principal)
if err := getSetPolicyResponse(sessionID, params.Name, params.Body); err != nil {
return admin_api.NewSetPolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return admin_api.NewSetPolicyNoContent()
@@ -97,9 +102,9 @@ func listPolicies(ctx context.Context, client MinioAdmin) ([]*models.Policy, err
}
// getListPoliciesResponse performs listPolicies() and serializes it to the handler's output
func getListPoliciesResponse() (*models.ListPoliciesResponse, error) {
func getListPoliciesResponse(sessionID string) (*models.ListPoliciesResponse, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -131,13 +136,13 @@ func removePolicy(ctx context.Context, client MinioAdmin, name string) error {
}
// getRemovePolicyResponse() performs removePolicy() and serializes it to the handler's output
func getRemovePolicyResponse(params admin_api.RemovePolicyParams) error {
func getRemovePolicyResponse(sessionID string, params admin_api.RemovePolicyParams) error {
ctx := context.Background()
if params.Name == "" {
log.Println("error policy name not in request")
return errors.New(500, "error policy name not in request")
}
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
@@ -173,14 +178,14 @@ func addPolicy(ctx context.Context, client MinioAdmin, name, policy string) (*mo
}
// getAddPolicyResponse performs addPolicy() and serializes it to the handler's output
func getAddPolicyResponse(params *models.AddPolicyRequest) (*models.Policy, error) {
func getAddPolicyResponse(sessionID string, params *models.AddPolicyRequest) (*models.Policy, error) {
ctx := context.Background()
if params == nil {
log.Println("error AddPolicy body not in request")
return nil, errors.New(500, "error AddPolicy body not in request")
}
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -213,9 +218,9 @@ func policyInfo(ctx context.Context, client MinioAdmin, name string) (*models.Po
}
// getPolicyInfoResponse performs policyInfo() and serializes it to the handler's output
func getPolicyInfoResponse(params admin_api.PolicyInfoParams) (*models.Policy, error) {
func getPolicyInfoResponse(sessionID string, params admin_api.PolicyInfoParams) (*models.Policy, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -244,13 +249,13 @@ func setPolicy(ctx context.Context, client MinioAdmin, name, entityName string,
}
// getSetPolicyResponse() performs setPolicy() and serializes it to the handler's output
func getSetPolicyResponse(name string, params *models.SetPolicyRequest) error {
func getSetPolicyResponse(sessionID string, name string, params *models.SetPolicyRequest) error {
ctx := context.Background()
if name == "" {
log.Println("error policy name not in request")
return errors.New(500, "error policy name not in request")
}
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err

View File

@@ -35,7 +35,8 @@ import (
func registerProfilingHandler(api *operations.McsAPI) {
// Start Profiling
api.AdminAPIProfilingStartHandler = admin_api.ProfilingStartHandlerFunc(func(params admin_api.ProfilingStartParams, principal *models.Principal) middleware.Responder {
profilingStartResponse, err := getProfilingStartResponse(params.Body)
sessionID := string(*principal)
profilingStartResponse, err := getProfilingStartResponse(sessionID, params.Body)
if err != nil {
return admin_api.NewProfilingStartDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -43,7 +44,8 @@ func registerProfilingHandler(api *operations.McsAPI) {
})
// Stop and download profiling data
api.AdminAPIProfilingStopHandler = admin_api.ProfilingStopHandlerFunc(func(params admin_api.ProfilingStopParams, principal *models.Principal) middleware.Responder {
profilingStopResponse, err := getProfilingStopResponse()
sessionID := string(*principal)
profilingStopResponse, err := getProfilingStopResponse(sessionID)
if err != nil {
return admin_api.NewProfilingStopDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -90,13 +92,13 @@ func startProfiling(ctx context.Context, client MinioAdmin, profilerType models.
}
// getProfilingStartResponse performs startProfiling() and serializes it to the handler's output
func getProfilingStartResponse(params *models.ProfilingStartRequest) (*models.StartProfilingList, error) {
func getProfilingStartResponse(sessionID string, params *models.ProfilingStartRequest) (*models.StartProfilingList, error) {
ctx := context.Background()
if params == nil {
log.Println("error profiling type not in body request")
return nil, errors.New(500, "error AddPolicy body not in request")
}
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -127,9 +129,9 @@ func stopProfiling(ctx context.Context, client MinioAdmin) (io.ReadCloser, error
}
// getProfilingStopResponse() performs setPolicy() and serializes it to the handler's output
func getProfilingStopResponse() (io.ReadCloser, error) {
func getProfilingStopResponse(sessionID string) (io.ReadCloser, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err

View File

@@ -32,7 +32,8 @@ import (
func registerServiceHandlers(api *operations.McsAPI) {
// Restart Service
api.AdminAPIRestartServiceHandler = admin_api.RestartServiceHandlerFunc(func(params admin_api.RestartServiceParams, principal *models.Principal) middleware.Responder {
if err := getRestartServiceResponse(); err != nil {
sessionID := string(*principal)
if err := getRestartServiceResponse(sessionID); err != nil {
return admin_api.NewRestartServiceDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return admin_api.NewRestartServiceNoContent()
@@ -61,9 +62,9 @@ func serviceRestart(ctx context.Context, client MinioAdmin) error {
}
// getRestartServiceResponse performs serviceRestart()
func getRestartServiceResponse() error {
func getRestartServiceResponse(sessionID string) error {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err

View File

@@ -34,7 +34,8 @@ import (
func registerUsersHandlers(api *operations.McsAPI) {
// List Users
api.AdminAPIListUsersHandler = admin_api.ListUsersHandlerFunc(func(params admin_api.ListUsersParams, principal *models.Principal) middleware.Responder {
listUsersResponse, err := getListUsersResponse()
sessionID := string(*principal)
listUsersResponse, err := getListUsersResponse(sessionID)
if err != nil {
return admin_api.NewListUsersDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -42,7 +43,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
})
// Add User
api.AdminAPIAddUserHandler = admin_api.AddUserHandlerFunc(func(params admin_api.AddUserParams, principal *models.Principal) middleware.Responder {
userResponse, err := getUserAddResponse(params)
sessionID := string(*principal)
userResponse, err := getUserAddResponse(sessionID, params)
if err != nil {
return admin_api.NewAddUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -50,7 +52,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
})
// Remove User
api.AdminAPIRemoveUserHandler = admin_api.RemoveUserHandlerFunc(func(params admin_api.RemoveUserParams, principal *models.Principal) middleware.Responder {
err := getRemoveUserResponse(params)
sessionID := string(*principal)
err := getRemoveUserResponse(sessionID, params)
if err != nil {
return admin_api.NewRemoveUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -58,7 +61,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
})
// Update User-Groups
api.AdminAPIUpdateUserGroupsHandler = admin_api.UpdateUserGroupsHandlerFunc(func(params admin_api.UpdateUserGroupsParams, principal *models.Principal) middleware.Responder {
userUpdateResponse, err := getUpdateUserGroupsResponse(params)
sessionID := string(*principal)
userUpdateResponse, err := getUpdateUserGroupsResponse(sessionID, params)
if err != nil {
return admin_api.NewUpdateUserGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -67,7 +71,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
})
// Get User
api.AdminAPIGetUserInfoHandler = admin_api.GetUserInfoHandlerFunc(func(params admin_api.GetUserInfoParams, principal *models.Principal) middleware.Responder {
userInfoResponse, err := getUserInfoResponse(params)
sessionID := string(*principal)
userInfoResponse, err := getUserInfoResponse(sessionID, params)
if err != nil {
return admin_api.NewGetUserDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -76,7 +81,8 @@ func registerUsersHandlers(api *operations.McsAPI) {
})
// Update User
api.AdminAPIUpdateUserInfoHandler = admin_api.UpdateUserInfoHandlerFunc(func(params admin_api.UpdateUserInfoParams, principal *models.Principal) middleware.Responder {
userUpdateResponse, err := getUpdateUserResponse(params)
sessionID := string(*principal)
userUpdateResponse, err := getUpdateUserResponse(sessionID, params)
if err != nil {
return admin_api.NewUpdateUserInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -85,9 +91,10 @@ func registerUsersHandlers(api *operations.McsAPI) {
})
// Update User-Groups Bulk
api.AdminAPIBulkUpdateUsersGroupsHandler = admin_api.BulkUpdateUsersGroupsHandlerFunc(func(params admin_api.BulkUpdateUsersGroupsParams, principal *models.Principal) middleware.Responder {
error := getAddUsersListToGroupsResponse(params)
if error != nil {
return admin_api.NewBulkUpdateUsersGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(error.Error())})
sessionID := string(*principal)
err := getAddUsersListToGroupsResponse(sessionID, params)
if err != nil {
return admin_api.NewBulkUpdateUsersGroupsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return admin_api.NewBulkUpdateUsersGroupsOK()
@@ -119,9 +126,9 @@ func listUsers(ctx context.Context, client MinioAdmin) ([]*models.User, error) {
}
// getListUsersResponse performs listUsers() and serializes it to the handler's output
func getListUsersResponse() (*models.ListUsersResponse, error) {
func getListUsersResponse(sessionID string) (*models.ListUsersResponse, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -167,9 +174,9 @@ func addUser(ctx context.Context, client MinioAdmin, accessKey, secretKey *strin
return userRet, nil
}
func getUserAddResponse(params admin_api.AddUserParams) (*models.User, error) {
func getUserAddResponse(sessionID string, params admin_api.AddUserParams) (*models.User, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -194,10 +201,10 @@ func removeUser(ctx context.Context, client MinioAdmin, accessKey string) error
return nil
}
func getRemoveUserResponse(params admin_api.RemoveUserParams) error {
func getRemoveUserResponse(sessionID string, params admin_api.RemoveUserParams) error {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err
@@ -226,10 +233,10 @@ func getUserInfo(ctx context.Context, client MinioAdmin, accessKey string) (*mad
return &userInfo, nil
}
func getUserInfoResponse(params admin_api.GetUserInfoParams) (*models.User, error) {
func getUserInfoResponse(sessionID string, params admin_api.GetUserInfoParams) (*models.User, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -341,10 +348,10 @@ func updateUserGroups(ctx context.Context, client MinioAdmin, user string, group
return userReturn, nil
}
func getUpdateUserGroupsResponse(params admin_api.UpdateUserGroupsParams) (*models.User, error) {
func getUpdateUserGroupsResponse(sessionID string, params admin_api.UpdateUserGroupsParams) (*models.User, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -382,10 +389,10 @@ func setUserStatus(ctx context.Context, client MinioAdmin, user string, status s
return nil
}
func getUpdateUserResponse(params admin_api.UpdateUserInfoParams) (*models.User, error) {
func getUpdateUserResponse(sessionID string, params admin_api.UpdateUserInfoParams) (*models.User, error) {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return nil, err
@@ -455,10 +462,10 @@ func addUsersListToGroups(ctx context.Context, client MinioAdmin, usersToUpdate
return nil
}
func getAddUsersListToGroupsResponse(params admin_api.BulkUpdateUsersGroupsParams) error {
func getAddUsersListToGroupsResponse(sessionID string, params admin_api.BulkUpdateUsersGroupsParams) error {
ctx := context.Background()
mAdmin, err := newMAdminClient()
mAdmin, err := newMAdminClient(sessionID)
if err != nil {
log.Println("error creating Madmin Client:", err)
return err

View File

@@ -24,6 +24,8 @@ import (
mcCmd "github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/minio-go/v6/pkg/credentials"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin"
)
@@ -192,14 +194,17 @@ func (ac adminClient) stopProfiling(ctx context.Context) (io.ReadCloser, error)
return ac.client.DownloadProfilingData(ctx)
}
func newMAdminClient() (*madmin.AdminClient, error) {
endpoint := getMinIOServer()
accessKeyID := getAccessKey()
secretAccessKey := getSecretKey()
adminClient, pErr := NewAdminClient(endpoint, accessKeyID, secretAccessKey)
if pErr != nil {
return nil, pErr.Cause
func newMAdminClient(jwt string) (*madmin.AdminClient, error) {
claims, err := auth.JWTAuthenticate(jwt)
if err != nil {
return nil, err
}
adminClient, err := madmin.NewWithOptions(getMinIOEndpoint(), &madmin.Options{
Creds: credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken),
Secure: getMinIOEndpointIsSecure(),
})
if err != nil {
return nil, err
}
return adminClient, nil
}

View File

@@ -22,7 +22,10 @@ import (
mc "github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/mcs/pkg/auth"
xjwt "github.com/minio/mcs/pkg/auth/jwt"
"github.com/minio/minio-go/v6"
"github.com/minio/minio-go/v6/pkg/credentials"
)
func init() {
@@ -107,20 +110,67 @@ func (c mcS3Client) removeNotificationConfig(arn string, event string, prefix st
return c.client.RemoveNotificationConfig(arn, event, prefix, suffix)
}
// newMinioClient creates a new MinIO client to talk to the server
func newMinioClient() (*minio.Client, error) {
endpoint := getMinIOEndpoint()
accessKeyID := getAccessKey()
secretAccessKey := getSecretKey()
useSSL := getMinIOEndpointIsSecure()
// Define MCSCredentials interface with all functions to be implemented
// by mock when testing, it should include all needed minioCredentials.Credentials api calls
// that are used within this project.
type MCSCredentials interface {
Get() (credentials.Value, error)
Expire()
}
// Initialize minio client object.
minioClient, err := minio.NewV4(endpoint, accessKeyID, secretAccessKey, useSSL)
// Interface implementation
//
// Define the structure of a mc S3Client and define the functions that are actually used
// from mcsCredentials api.
type mcsCredentials struct {
minioCredentials *credentials.Credentials
}
// implements *Credentials.Get()
func (c mcsCredentials) Get() (credentials.Value, error) {
return c.minioCredentials.Get()
}
// implements *Credentials.Expire()
func (c mcsCredentials) Expire() {
c.minioCredentials.Expire()
}
func newMcsCredentials(accessKey, secretKey, location string) (*credentials.Credentials, error) {
return credentials.NewSTSAssumeRole(getMinIOServer(), credentials.STSAssumeRoleOptions{
AccessKey: accessKey,
SecretKey: secretKey,
Location: location,
DurationSeconds: xjwt.GetMcsSTSAndJWTDurationInSeconds(),
})
}
// getMcsCredentialsFromJWT returns the *minioCredentials.Credentials associated to the
// provided jwt, this is useful for running the Expire() or IsExpired() operations
func getMcsCredentialsFromJWT(jwt string) (*credentials.Credentials, error) {
claims, err := auth.JWTAuthenticate(jwt)
if err != nil {
return nil, err
}
creds := credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken)
return creds, nil
}
return minioClient, nil
// newMinioClient creates a new MinIO client based on the minioCredentials extracted
// from the provided jwt
func newMinioClient(jwt string) (*minio.Client, error) {
creds, err := getMcsCredentialsFromJWT(jwt)
if err != nil {
return nil, err
}
adminClient, err := minio.NewWithOptions(getMinIOEndpoint(), &minio.Options{
Creds: creds,
Secure: getMinIOEndpointIsSecure(),
})
if err != nil {
return nil, err
}
return adminClient, nil
}
// newS3BucketClient creates a new mc S3Client to talk to the server based on a bucket
@@ -150,7 +200,7 @@ func newS3BucketClient(bucketName *string) (*mc.S3Client, error) {
// parameters.
func newS3Config(endpoint, accessKey, secretKey string, isSecure bool) *mc.Config {
// We have a valid alias and hostConfig. We populate the
// credentials from the match found in the config file.
// minioCredentials from the match found in the config file.
s3Config := new(mc.Config)
s3Config.AppName = "mcs" // TODO: make this a constant

View File

@@ -198,7 +198,6 @@ func getSecureFeaturePolicy() string {
return env.Get(McsSecureFeaturePolicy, "")
}
// FeaturePolicy allows the Feature-Policy header with the value to be set with a custom value. Default is "".
func getSecureExpectCTHeader() string {
return env.Get(McsSecureExpectCTHeader, "")
}

View File

@@ -24,9 +24,8 @@ import (
"net/http"
"strings"
"github.com/minio/mcs/restapi/sessions"
"github.com/minio/mcs/models"
"github.com/minio/mcs/pkg/auth"
assetfs "github.com/elazarl/go-bindata-assetfs"
@@ -60,7 +59,7 @@ func configureAPI(api *operations.McsAPI) http.Handler {
// Applies when the "x-token" header is set
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
if sessions.GetInstance().ValidSession(token) {
if auth.IsJWTValid(token) {
prin := models.Principal(token)
return &prin, nil
}

View File

@@ -1,95 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package sessions
import (
"crypto/rand"
"io"
"strings"
"sync"
mcCmd "github.com/minio/mc/cmd"
)
type Singleton struct {
sessions map[string]*mcCmd.Config
}
var instance *Singleton
var once sync.Once
// Returns a Singleton instance that keeps the sessions
func GetInstance() *Singleton {
once.Do(func() {
//build sessions hash
sessions := make(map[string]*mcCmd.Config)
instance = &Singleton{
sessions: sessions,
}
})
return instance
}
// The delete built-in function deletes the element with the specified key (m[key]) from the map.
// If m is nil or there is no such element, delete is a no-op. https://golang.org/pkg/builtin/#delete
func (s *Singleton) DeleteSession(sessionID string) {
delete(s.sessions, sessionID)
}
func (s *Singleton) NewSession(cfg *mcCmd.Config) string {
// genereate random session id
sessionID := RandomCharString(64)
// store the cfg under that session id
s.sessions[sessionID] = cfg
return sessionID
}
func (s *Singleton) ValidSession(sessionID string) bool {
if _, ok := s.sessions[sessionID]; ok {
return true
}
return false
}
// Do not use:
// https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
// It relies on math/rand and therefore not on a cryptographically secure RNG => It must not be used
// for access/secret keys.
// The alphabet of random character string. Each character must be unique.
//
// The RandomCharString implementation requires that: 256 / len(letters) is a natural numbers.
// For example: 256 / 64 = 4. However, 5 > 256/62 > 4 and therefore we must not use a alphabet
// of 62 characters.
// The reason is that if 256 / len(letters) is not a natural number then certain characters become
// more likely then others.
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
func RandomCharString(n int) string {
random := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, random); err != nil {
panic(err) // Can only happen if we would run out of entropy.
}
var s strings.Builder
for _, v := range random {
j := v % byte(len(letters))
s.WriteByte(letters[j])
}
return s.String()
}

View File

@@ -1,47 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package sessions
import (
"testing"
mcCmd "github.com/minio/mc/cmd"
"github.com/stretchr/testify/assert"
)
// TestNewSession tests the creation of a new sesison for a valid cfg object
func TestNewSession(t *testing.T) {
assert := assert.New(t)
cfg := mcCmd.Config{}
// Test Case 1: No collision
sessionID := GetInstance().NewSession(&cfg)
assert.NotEmpty(sessionID, "Session ID was returned empty")
}
// TestValidateSession tests a valid sessionId on the sessions object
func TestValidateSession(t *testing.T) {
assert := assert.New(t)
cfg := mcCmd.Config{}
// Test Case 1: Valid session
sessionID := GetInstance().NewSession(&cfg)
isValid := GetInstance().ValidSession(sessionID)
assert.Equal(isValid, true, "Session was not found valid")
// Test Case 2: Invalid session
isInvalid := GetInstance().ValidSession("random")
assert.Equal(isInvalid, false, "Session was found valid")
}

View File

@@ -37,7 +37,8 @@ import (
func registerBucketsHandlers(api *operations.McsAPI) {
// list buckets
api.UserAPIListBucketsHandler = user_api.ListBucketsHandlerFunc(func(params user_api.ListBucketsParams, principal *models.Principal) middleware.Responder {
listBucketsResponse, err := getListBucketsResponse()
sessionID := string(*principal)
listBucketsResponse, err := getListBucketsResponse(sessionID)
if err != nil {
return user_api.NewListBucketsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -45,14 +46,16 @@ func registerBucketsHandlers(api *operations.McsAPI) {
})
// make bucket
api.UserAPIMakeBucketHandler = user_api.MakeBucketHandlerFunc(func(params user_api.MakeBucketParams, principal *models.Principal) middleware.Responder {
if err := getMakeBucketResponse(params.Body); err != nil {
sessionID := string(*principal)
if err := getMakeBucketResponse(sessionID, params.Body); err != nil {
return user_api.NewMakeBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
return user_api.NewMakeBucketCreated()
})
// delete bucket
api.UserAPIDeleteBucketHandler = user_api.DeleteBucketHandlerFunc(func(params user_api.DeleteBucketParams, principal *models.Principal) middleware.Responder {
if err := getDeleteBucketResponse(params); err != nil {
sessionID := string(*principal)
if err := getDeleteBucketResponse(sessionID, params); err != nil {
return user_api.NewMakeBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -60,7 +63,8 @@ func registerBucketsHandlers(api *operations.McsAPI) {
})
// get bucket info
api.UserAPIBucketInfoHandler = user_api.BucketInfoHandlerFunc(func(params user_api.BucketInfoParams, principal *models.Principal) middleware.Responder {
bucketInfoResp, err := getBucketInfoResponse(params)
sessionID := string(*principal)
bucketInfoResp, err := getBucketInfoResponse(sessionID, params)
if err != nil {
return user_api.NewBucketInfoDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -69,7 +73,8 @@ func registerBucketsHandlers(api *operations.McsAPI) {
})
// set bucket policy
api.UserAPIBucketSetPolicyHandler = user_api.BucketSetPolicyHandlerFunc(func(params user_api.BucketSetPolicyParams, principal *models.Principal) middleware.Responder {
bucketSetPolicyResp, err := getBucketSetPolicyResponse(params.Name, params.Body)
sessionID := string(*principal)
bucketSetPolicyResp, err := getBucketSetPolicyResponse(sessionID, params.Name, params.Body)
if err != nil {
return user_api.NewBucketSetPolicyDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -97,11 +102,10 @@ func listBuckets(ctx context.Context, client MinioClient) ([]*models.Bucket, err
}
// getListBucketsResponse performs listBuckets() and serializes it to the handler's output
func getListBucketsResponse() (*models.ListBucketsResponse, error) {
func getListBucketsResponse(sessionID string) (*models.ListBucketsResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
mClient, err := newMinioClient()
mClient, err := newMinioClient(sessionID)
if err != nil {
log.Println("error creating MinIO Client:", err)
return nil, err
@@ -133,7 +137,7 @@ func makeBucket(ctx context.Context, client MinioClient, bucketName string) erro
}
// getMakeBucketResponse performs makeBucket() to create a bucket with its access policy
func getMakeBucketResponse(br *models.MakeBucketRequest) error {
func getMakeBucketResponse(sessionID string, br *models.MakeBucketRequest) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
// bucket request needed to proceed
@@ -141,7 +145,7 @@ func getMakeBucketResponse(br *models.MakeBucketRequest) error {
log.Println("error bucket body not in request")
return errors.New(500, "error bucket body not in request")
}
mClient, err := newMinioClient()
mClient, err := newMinioClient(sessionID)
if err != nil {
log.Println("error creating MinIO Client:", err)
return err
@@ -187,11 +191,11 @@ func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName s
// getBucketSetPolicyResponse calls setBucketAccessPolicy() to set a access policy to a bucket
// and returns the serialized output.
func getBucketSetPolicyResponse(bucketName string, req *models.SetBucketPolicyRequest) (*models.Bucket, error) {
func getBucketSetPolicyResponse(sessionID string, bucketName string, req *models.SetBucketPolicyRequest) (*models.Bucket, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
mClient, err := newMinioClient()
mClient, err := newMinioClient(sessionID)
if err != nil {
log.Println("error creating MinIO Client:", err)
return nil, err
@@ -223,14 +227,14 @@ func removeBucket(client MinioClient, bucketName string) error {
}
// getDeleteBucketResponse performs removeBucket() to delete a bucket
func getDeleteBucketResponse(params user_api.DeleteBucketParams) error {
func getDeleteBucketResponse(sessionID string, params user_api.DeleteBucketParams) error {
if params.Name == "" {
log.Println("error bucket name not in request")
return errors.New(500, "error bucket name not in request")
}
bucketName := params.Name
mClient, err := newMinioClient()
mClient, err := newMinioClient(sessionID)
if err != nil {
log.Println("error creating MinIO Client:", err)
return err
@@ -272,8 +276,8 @@ func getBucketInfo(client MinioClient, bucketName string) (*models.Bucket, error
}
// getBucketInfoResponse calls getBucketInfo() to get the bucket's info
func getBucketInfoResponse(params user_api.BucketInfoParams) (*models.Bucket, error) {
mClient, err := newMinioClient()
func getBucketInfoResponse(sessionID string, params user_api.BucketInfoParams) (*models.Bucket, error) {
mClient, err := newMinioClient(sessionID)
if err != nil {
log.Println("error creating MinIO Client:", err)
return nil, err

View File

@@ -31,7 +31,8 @@ import (
func registerBucketEventsHandlers(api *operations.McsAPI) {
// list bucket events
api.UserAPIListBucketEventsHandler = user_api.ListBucketEventsHandlerFunc(func(params user_api.ListBucketEventsParams, principal *models.Principal) middleware.Responder {
listBucketEventsResponse, err := getListBucketEventsResponse(params)
sessionID := string(*principal)
listBucketEventsResponse, err := getListBucketEventsResponse(sessionID, params)
if err != nil {
return user_api.NewListBucketEventsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
}
@@ -124,8 +125,8 @@ func listBucketEvents(client MinioClient, bucketName string) ([]*models.Notifica
}
// getListBucketsResponse performs listBucketEvents() and serializes it to the handler's output
func getListBucketEventsResponse(params user_api.ListBucketEventsParams) (*models.ListBucketEventsResponse, error) {
mClient, err := newMinioClient()
func getListBucketEventsResponse(sessionID string, params user_api.ListBucketEventsParams) (*models.ListBucketEventsResponse, error) {
mClient, err := newMinioClient(sessionID)
if err != nil {
log.Println("error creating MinIO Client:", err)
return nil, err

View File

@@ -20,31 +20,14 @@ import (
"errors"
"log"
"github.com/minio/mc/pkg/probe"
"github.com/minio/mcs/restapi/sessions"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
mcCmd "github.com/minio/mc/cmd"
"github.com/minio/mcs/models"
"github.com/minio/mcs/pkg/auth"
"github.com/minio/mcs/restapi/operations"
"github.com/minio/mcs/restapi/operations/user_api"
)
// Wraps the code at mc/cmd
type McCmd interface {
BuildS3Config(url, accessKey, secretKey, api, lookup string) (*mcCmd.Config, *probe.Error)
}
// Implementation of McCmd
type mcCmdWrapper struct {
}
func (mc mcCmdWrapper) BuildS3Config(url, accessKey, secretKey, api, lookup string) (*mcCmd.Config, *probe.Error) {
return mcCmd.BuildS3Config(url, accessKey, secretKey, api, lookup)
}
func registerLoginHandlers(api *operations.McsAPI) {
// get login strategy
api.UserAPILoginDetailHandler = user_api.LoginDetailHandlerFunc(func(params user_api.LoginDetailParams) middleware.Responder {
@@ -59,28 +42,34 @@ func registerLoginHandlers(api *operations.McsAPI) {
}
return user_api.NewLoginCreated().WithPayload(loginResponse)
})
}
var ErrInvalidCredentials = errors.New("invalid credentials")
var ErrInvalidCredentials = errors.New("invalid minioCredentials")
// login performs a check of credentials against MinIO
func login(mc McCmd, accessKey, secretKey *string) (*string, error) {
// Probe the credentials
cfg, pErr := mc.BuildS3Config(getMinIOServer(), *accessKey, *secretKey, "", "auto")
if pErr != nil {
// login performs a check of minioCredentials against MinIO
func login(credentials MCSCredentials) (*string, error) {
// try to obtain minioCredentials,
tokens, err := credentials.Get()
if err != nil {
return nil, ErrInvalidCredentials
}
// if we made it here, the credentials work, generate a session
sessionID := sessions.GetInstance().NewSession(cfg)
return &sessionID, nil
// if we made it here, the minioCredentials work, generate a jwt with claims
jwt, err := auth.NewJWTWithClaimsForClient(&tokens, getMinIOServer())
if err != nil {
return nil, ErrInvalidCredentials
}
return &jwt, nil
}
// getLoginResponse performs login() and serializes it to the handler's output
func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, error) {
mc := mcCmdWrapper{}
sessionID, err := login(&mc, lr.AccessKey, lr.SecretKey)
creds, err := newMcsCredentials(*lr.AccessKey, *lr.SecretKey, "")
if err != nil {
log.Println("error login:", err)
return nil, err
}
credentials := mcsCredentials{minioCredentials: creds}
sessionID, err := login(credentials)
if err != nil {
log.Println("error login:", err)
return nil, err

View File

@@ -20,43 +20,41 @@ import (
"errors"
"testing"
mcCmd "github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v6/pkg/credentials"
"github.com/stretchr/testify/assert"
)
var mcBuildS3ConfigMock func(url, accessKey, secretKey, api, lookup string) (*mcCmd.Config, *probe.Error)
// Define a mock struct of MCSCredentials interface implementation
type mcsCredentialsMock struct{}
type mcCmdMock struct{}
// Common mocks
var mcsCredentialsGetMock func() (credentials.Value, error)
func (mc mcCmdMock) BuildS3Config(url, accessKey, secretKey, api, lookup string) (*mcCmd.Config, *probe.Error) {
return mcBuildS3ConfigMock(url, accessKey, secretKey, api, lookup)
// mock function of Get()
func (ac mcsCredentialsMock) Get() (credentials.Value, error) {
return mcsCredentialsGetMock()
}
// TestLogin tests the case of passing a valid and an invalid access/secret pair
func TestLogin(t *testing.T) {
assert := assert.New(t)
// We will write a test against play
// Probe the credentials
mcx := mcCmdMock{}
access := "ABCDEFHIJK"
secret := "ABCDEFHIJKABCDEFHIJK"
// Test Case 1: Valid credentials
mcBuildS3ConfigMock = func(url, accessKey, secretKey, api, lookup string) (config *mcCmd.Config, p *probe.Error) {
return &mcCmd.Config{}, nil
funcAssert := assert.New(t)
mcsCredentials := mcsCredentialsMock{}
// Test Case 1: Valid mcsCredentials
mcsCredentialsGetMock = func() (credentials.Value, error) {
return credentials.Value{
AccessKeyID: "fakeAccessKeyID",
SecretAccessKey: "fakeSecretAccessKey",
SessionToken: "fakeSessionToken",
SignerType: 0,
}, nil
}
sessionID, err := login(mcx, &access, &secret)
assert.NotEmpty(sessionID, "Session ID was returned empty")
assert.Nil(err, "error creating a session")
jwt, err := login(mcsCredentials)
funcAssert.NotEmpty(jwt, "JWT was returned empty")
funcAssert.Nil(err, "error creating a session")
// Test Case 2: Invalid credentials
mcBuildS3ConfigMock = func(url, accessKey, secretKey, api, lookup string) (config *mcCmd.Config, p *probe.Error) {
return nil, probe.NewError(errors.New("Bad credentials"))
mcsCredentialsGetMock = func() (credentials.Value, error) {
return credentials.Value{}, errors.New("")
}
sessionID, err = login(mcx, &access, &secret)
assert.Empty(sessionID, "Session ID was not returned empty")
assert.NotNil(err, "not error returned creating a session")
_, err = login(mcsCredentials)
funcAssert.NotNil(err, "not error returned creating a session")
}

View File

@@ -17,14 +17,13 @@
package restapi
import (
"errors"
"log"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/mcs/models"
"github.com/minio/mcs/restapi/operations"
"github.com/minio/mcs/restapi/operations/user_api"
"github.com/minio/mcs/restapi/sessions"
)
func registerLogoutHandlers(api *operations.McsAPI) {
@@ -38,21 +37,23 @@ func registerLogoutHandlers(api *operations.McsAPI) {
})
}
// logout() deletes provided bearer token from in memory sessions map
// then checks that the session actually got removed
func logout(sessionID string) error {
sessionsMap := sessions.GetInstance()
sessionsMap.DeleteSession(sessionID)
if sessionsMap.ValidSession(sessionID) {
return errors.New("something went wrong deleting your session, please try again")
}
return nil
// logout() call Expire() on the provided minioCredentials
func logout(credentials MCSCredentials) {
credentials.Expire()
}
// getLogoutResponse performs logout() and returns nil or error
func getLogoutResponse(sessionID string) error {
if err := logout(sessionID); err != nil {
func getLogoutResponse(jwt string) error {
creds, err := getMcsCredentialsFromJWT(jwt)
if err != nil {
log.Println(err)
return err
}
credentials := mcsCredentials{minioCredentials: creds}
if err != nil {
log.Println("error creating MinIO Client:", err)
return err
}
logout(credentials)
return nil
}

View File

@@ -1,21 +1,29 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package restapi
import (
"testing"
import "testing"
mcCmd "github.com/minio/mc/cmd"
"github.com/minio/mcs/restapi/sessions"
)
// TestLogout tests the case of deleting a valid session id
func TestLogout(t *testing.T) {
cfg := mcCmd.Config{}
// Creating a new session
sessionID := sessions.GetInstance().NewSession(&cfg)
// Test Case 1: Delete a session Valid sessionID
function := "logout()"
err := logout(sessionID)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
// mock function of Get()
func (ac mcsCredentialsMock) Expire() {
// Do nothing
// Implementing this method for the mcsCredentials interface
}
func TestLogout(t *testing.T) {
// There's nothing to test right now
}