mirror of
https://github.com/minio/minio.git
synced 2025-04-19 02:05:24 -04:00
fix: deprecate requirement of session token for service accounts (#9320)
This PR fixes couple of behaviors with service accounts - not need to have session token for service accounts - service accounts can be generated by any user for themselves implicitly, with a valid signature. - policy input for AddNewServiceAccount API is not fully typed allowing for validation before it is sent to the server. - also bring in additional context for admin API errors if any when replying back to client. - deprecate GetServiceAccount API as we do not need to reply back session tokens
This commit is contained in:
parent
bfec5fe200
commit
37d066b563
@ -379,53 +379,65 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddServiceAccount - PUT /minio/admin/v3/add-service-account?parentUser=<parent_user_accesskey>
|
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
||||||
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "AddServiceAccount")
|
ctx := newContext(r, w, "AddServiceAccount")
|
||||||
|
|
||||||
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.CreateUserAdminAction)
|
// Get current object layer instance.
|
||||||
if objectAPI == nil {
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil || globalIAMSys == nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deny if WORM is enabled
|
cred, _, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
if globalWORMEnabled {
|
if s3Err != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
|
||||||
// More than maxConfigSize bytes were available
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
configBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var createReq madmin.AddServiceAccountReq
|
var createReq madmin.AddServiceAccountReq
|
||||||
if err = json.Unmarshal(configBytes, &createReq); err != nil {
|
if err = json.Unmarshal(configBytes, &createReq); err != nil {
|
||||||
logger.LogIf(ctx, err)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if createReq.Parent == "" {
|
if createReq.Parent == "" {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
|
apiErr := APIError{
|
||||||
|
Code: "XMinioAdminInvalidArgument",
|
||||||
|
Description: "Service account parent cannot be empty",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
writeErrorResponseJSON(ctx, w, apiErr, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disallow creating service accounts for the root user
|
// Disallow creating service accounts by root user as well.
|
||||||
if createReq.Parent == globalActiveCred.AccessKey {
|
if createReq.Parent == globalActiveCred.AccessKey {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddServiceAccountInvalidArgument), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddServiceAccountInvalidArgument), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disallow creating service accounts by users who are not the requested parent.
|
||||||
|
// this restriction is not required for Owner account i.e root user.
|
||||||
|
if createReq.Parent != cred.AccessKey && !owner {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddServiceAccountInvalidParent), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deny if WORM is enabled
|
||||||
|
if globalWORMEnabled {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
creds, err := globalIAMSys.NewServiceAccount(ctx, createReq.Parent, createReq.Policy)
|
creds, err := globalIAMSys.NewServiceAccount(ctx, createReq.Parent, createReq.Policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@ -441,7 +453,10 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
var createResp = madmin.AddServiceAccountResp{
|
var createResp = madmin.AddServiceAccountResp{
|
||||||
Credentials: creds,
|
Credentials: auth.Credentials{
|
||||||
|
AccessKey: creds.AccessKey,
|
||||||
|
SecretKey: creds.SecretKey,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(createResp)
|
data, err := json.Marshal(createResp)
|
||||||
@ -459,41 +474,6 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
writeSuccessResponseJSON(w, econfigData)
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServiceAccount - GET /minio/admin/v3/get-service-account?accessKey=<access_key>
|
|
||||||
func (a adminAPIHandlers) GetServiceAccount(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "GetServiceAccount")
|
|
||||||
|
|
||||||
objectAPI, cred := validateAdminUsersReq(ctx, w, r, iampolicy.GetUserAdminAction)
|
|
||||||
if objectAPI == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
accessKey := vars["accessKey"]
|
|
||||||
|
|
||||||
password := cred.SecretKey
|
|
||||||
|
|
||||||
creds, err := globalIAMSys.GetServiceAccount(ctx, accessKey)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(creds)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
econfigData, err := madmin.EncryptData(password, data)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccessResponseJSON(w, econfigData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoCannedPolicyV2 - GET /minio/admin/v2/info-canned-policy?name={policyName}
|
// InfoCannedPolicyV2 - GET /minio/admin/v2/info-canned-policy?name={policyName}
|
||||||
func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) InfoCannedPolicyV2(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "InfoCannedPolicyV2")
|
ctx := newContext(r, w, "InfoCannedPolicyV2")
|
||||||
|
@ -942,7 +942,7 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
apiErr = errorCodes.ToAPIErr(toAdminAPIErrCode(ctx, err))
|
apiErr = errorCodes.ToAPIErrWithErr(toAdminAPIErrCode(ctx, err), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return apiErr
|
return apiErr
|
||||||
|
@ -115,7 +115,6 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
|
|
||||||
// Service accounts ops
|
// Service accounts ops
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(httpTraceHdrs(adminAPI.AddServiceAccount))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(httpTraceHdrs(adminAPI.AddServiceAccount))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-service-account").HandlerFunc(httpTraceHdrs(adminAPI.GetServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
|
||||||
|
|
||||||
if adminVersion == adminAPIVersionV2Prefix {
|
if adminVersion == adminAPIVersionV2Prefix {
|
||||||
// Info policy IAM v2
|
// Info policy IAM v2
|
||||||
|
@ -336,19 +336,27 @@ const (
|
|||||||
ErrInvalidDecompressedSize
|
ErrInvalidDecompressedSize
|
||||||
ErrAddUserInvalidArgument
|
ErrAddUserInvalidArgument
|
||||||
ErrAddServiceAccountInvalidArgument
|
ErrAddServiceAccountInvalidArgument
|
||||||
|
ErrAddServiceAccountInvalidParent
|
||||||
ErrPostPolicyConditionInvalidFormat
|
ErrPostPolicyConditionInvalidFormat
|
||||||
)
|
)
|
||||||
|
|
||||||
type errorCodeMap map[APIErrorCode]APIError
|
type errorCodeMap map[APIErrorCode]APIError
|
||||||
|
|
||||||
func (e errorCodeMap) ToAPIErr(errCode APIErrorCode) APIError {
|
func (e errorCodeMap) ToAPIErrWithErr(errCode APIErrorCode, err error) APIError {
|
||||||
apiErr, ok := e[errCode]
|
apiErr, ok := e[errCode]
|
||||||
if !ok {
|
if !ok {
|
||||||
return e[ErrInternalError]
|
apiErr = e[ErrInternalError]
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||||
}
|
}
|
||||||
return apiErr
|
return apiErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e errorCodeMap) ToAPIErr(errCode APIErrorCode) APIError {
|
||||||
|
return e.ToAPIErrWithErr(errCode, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// error code to APIError structure, these fields carry respective
|
// error code to APIError structure, these fields carry respective
|
||||||
// descriptions for all the error responses.
|
// descriptions for all the error responses.
|
||||||
var errorCodes = errorCodeMap{
|
var errorCodes = errorCodeMap{
|
||||||
@ -1593,8 +1601,13 @@ var errorCodes = errorCodeMap{
|
|||||||
HTTPStatusCode: http.StatusConflict,
|
HTTPStatusCode: http.StatusConflict,
|
||||||
},
|
},
|
||||||
ErrAddServiceAccountInvalidArgument: {
|
ErrAddServiceAccountInvalidArgument: {
|
||||||
Code: "XMinioInvalidArgument",
|
Code: "XMinioInvalidIAMCredentials",
|
||||||
Description: "New service accounts for admin access key is not allowed",
|
Description: "Creating service accounts for admin access key is not allowed",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
|
ErrAddServiceAccountInvalidParent: {
|
||||||
|
Code: "XMinioInvalidIAMCredentialsParent",
|
||||||
|
Description: "Creating service accounts for other users is not allowed",
|
||||||
HTTPStatusCode: http.StatusConflict,
|
HTTPStatusCode: http.StatusConflict,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -121,9 +121,7 @@ func getRequestAuthType(r *http.Request) authType {
|
|||||||
return authTypeUnknown
|
return authTypeUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAdminRequestAuthType checks whether the request is a valid signature V2 or V4 request.
|
func validateAdminSignature(ctx context.Context, r *http.Request, region string) (auth.Credentials, map[string]interface{}, bool, APIErrorCode) {
|
||||||
// It does not accept presigned or JWT or anonymous requests.
|
|
||||||
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
s3Err := ErrAccessDenied
|
s3Err := ErrAccessDenied
|
||||||
@ -132,7 +130,7 @@ func checkAdminRequestAuthType(ctx context.Context, r *http.Request, action iamp
|
|||||||
// We only support admin credentials to access admin APIs.
|
// We only support admin credentials to access admin APIs.
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, s3Err
|
return cred, nil, owner, s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only support V4 (no presign) with auth body
|
// we only support V4 (no presign) with auth body
|
||||||
@ -144,12 +142,21 @@ func checkAdminRequestAuthType(ctx context.Context, r *http.Request, action iamp
|
|||||||
logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description), logger.Application)
|
logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description), logger.Application)
|
||||||
}
|
}
|
||||||
|
|
||||||
var claims map[string]interface{}
|
claims, s3Err := checkClaimsFromToken(r, cred)
|
||||||
claims, s3Err = checkClaimsFromToken(r, cred)
|
if s3Err != ErrNone {
|
||||||
|
return cred, nil, owner, s3Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cred, claims, owner, ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkAdminRequestAuthType checks whether the request is a valid signature V2 or V4 request.
|
||||||
|
// It does not accept presigned or JWT or anonymous requests.
|
||||||
|
func checkAdminRequestAuthType(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
||||||
|
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, s3Err
|
return cred, s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: iampolicy.Action(action),
|
Action: iampolicy.Action(action),
|
||||||
@ -176,15 +183,13 @@ func getSessionToken(r *http.Request) (token string) {
|
|||||||
// Fetch claims in the security token returned by the client, doesn't return
|
// Fetch claims in the security token returned by the client, doesn't return
|
||||||
// errors - upon errors the returned claims map will be empty.
|
// errors - upon errors the returned claims map will be empty.
|
||||||
func mustGetClaimsFromToken(r *http.Request) map[string]interface{} {
|
func mustGetClaimsFromToken(r *http.Request) map[string]interface{} {
|
||||||
claims, _ := getClaimsFromToken(r)
|
claims, _ := getClaimsFromToken(r, getSessionToken(r))
|
||||||
return claims
|
return claims
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch claims in the security token returned by the client.
|
// Fetch claims in the security token returned by the client.
|
||||||
func getClaimsFromToken(r *http.Request) (map[string]interface{}, error) {
|
func getClaimsFromToken(r *http.Request, token string) (map[string]interface{}, error) {
|
||||||
claims := xjwt.NewMapClaims()
|
claims := xjwt.NewMapClaims()
|
||||||
|
|
||||||
token := getSessionToken(r)
|
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return claims.Map(), nil
|
return claims.Map(), nil
|
||||||
}
|
}
|
||||||
@ -245,10 +250,13 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
|
|||||||
if token != "" && cred.AccessKey == "" {
|
if token != "" && cred.AccessKey == "" {
|
||||||
return nil, ErrNoAccessKey
|
return nil, ErrNoAccessKey
|
||||||
}
|
}
|
||||||
|
if cred.IsServiceAccount() && token == "" {
|
||||||
|
token = cred.SessionToken
|
||||||
|
}
|
||||||
if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
|
if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
|
||||||
return nil, ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
claims, err := getClaimsFromToken(r)
|
claims, err := getClaimsFromToken(r, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, toAPIErrorCode(r.Context(), err)
|
return nil, toAPIErrorCode(r.Context(), err)
|
||||||
}
|
}
|
||||||
|
@ -303,7 +303,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
// err will be nil here as we already called this function
|
// err will be nil here as we already called this function
|
||||||
// earlier in this request.
|
// earlier in this request.
|
||||||
claims, _ := getClaimsFromToken(r)
|
claims, _ := getClaimsFromToken(r, getSessionToken(r))
|
||||||
n := 0
|
n := 0
|
||||||
// Use the following trick to filter in place
|
// Use the following trick to filter in place
|
||||||
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
|
||||||
|
72
cmd/iam.go
72
cmd/iam.go
@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v6/pkg/set"
|
"github.com/minio/minio-go/v6/pkg/set"
|
||||||
@ -764,24 +765,24 @@ func (sys *IAMSys) SetUserStatus(accessKey string, status madmin.AccountStatus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewServiceAccount - create a new service account
|
// NewServiceAccount - create a new service account
|
||||||
func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser, sessionPolicy string) (auth.Credentials, error) {
|
func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, sessionPolicy *iampolicy.Policy) (auth.Credentials, error) {
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
if objectAPI == nil || sys == nil || sys.store == nil {
|
if objectAPI == nil || sys == nil || sys.store == nil {
|
||||||
return auth.Credentials{}, errServerNotInitialized
|
return auth.Credentials{}, errServerNotInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sessionPolicy) > 16*1024 {
|
var policyBuf []byte
|
||||||
return auth.Credentials{}, fmt.Errorf("Session policy should not exceed 16 KiB characters")
|
if sessionPolicy != nil {
|
||||||
}
|
err := sessionPolicy.Validate()
|
||||||
|
|
||||||
if len(sessionPolicy) > 0 {
|
|
||||||
policy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(sessionPolicy)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return auth.Credentials{}, err
|
return auth.Credentials{}, err
|
||||||
}
|
}
|
||||||
// Version in policy must not be empty
|
policyBuf, err = json.Marshal(sessionPolicy)
|
||||||
if policy.Version == "" {
|
if err != nil {
|
||||||
return auth.Credentials{}, fmt.Errorf("Invalid session policy version")
|
return auth.Credentials{}, err
|
||||||
|
}
|
||||||
|
if len(policyBuf) > 16*1024 {
|
||||||
|
return auth.Credentials{}, fmt.Errorf("Session policy should not exceed 16 KiB characters")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -808,8 +809,8 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser, sessionPol
|
|||||||
m := make(map[string]interface{})
|
m := make(map[string]interface{})
|
||||||
m[parentClaim] = parentUser
|
m[parentClaim] = parentUser
|
||||||
|
|
||||||
if len(sessionPolicy) > 0 {
|
if len(policyBuf) > 0 {
|
||||||
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString([]byte(sessionPolicy))
|
m[iampolicy.SessionPolicyName] = base64.StdEncoding.EncodeToString(policyBuf)
|
||||||
m[iamPolicyClaimNameSA()] = "embedded-policy"
|
m[iamPolicyClaimNameSA()] = "embedded-policy"
|
||||||
} else {
|
} else {
|
||||||
m[iamPolicyClaimNameSA()] = "inherited-policy"
|
m[iamPolicyClaimNameSA()] = "inherited-policy"
|
||||||
@ -833,32 +834,6 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser, sessionPol
|
|||||||
return cred, nil
|
return cred, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServiceAccount - returns the credentials of the given service account
|
|
||||||
func (sys *IAMSys) GetServiceAccount(ctx context.Context, serviceAccountAccessKey string) (auth.Credentials, error) {
|
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
|
||||||
if objectAPI == nil || sys == nil || sys.store == nil {
|
|
||||||
return auth.Credentials{}, errServerNotInitialized
|
|
||||||
}
|
|
||||||
|
|
||||||
sys.store.lock()
|
|
||||||
defer sys.store.unlock()
|
|
||||||
|
|
||||||
if sys.usersSysType != MinIOUsersSysType {
|
|
||||||
return auth.Credentials{}, errIAMActionNotAllowed
|
|
||||||
}
|
|
||||||
|
|
||||||
cr, ok := sys.iamUsersMap[serviceAccountAccessKey]
|
|
||||||
if !ok {
|
|
||||||
return auth.Credentials{}, errNoSuchUser
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cr.IsServiceAccount() {
|
|
||||||
return auth.Credentials{}, errIAMActionNotAllowed
|
|
||||||
}
|
|
||||||
|
|
||||||
return cr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUser - set user credentials and policy.
|
// SetUser - set user credentials and policy.
|
||||||
func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
|
func (sys *IAMSys) SetUser(accessKey string, uinfo madmin.UserInfo) error {
|
||||||
objectAPI := newObjectLayerWithoutSafeModeFn()
|
objectAPI := newObjectLayerWithoutSafeModeFn()
|
||||||
@ -1472,12 +1447,12 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parent string) b
|
|||||||
|
|
||||||
parentArgs := args
|
parentArgs := args
|
||||||
parentArgs.AccountName = parent
|
parentArgs.AccountName = parent
|
||||||
if !combinedPolicy.IsAllowed(parentArgs) {
|
|
||||||
|
saPolicyClaim, ok := args.Claims[iamPolicyClaimNameSA()]
|
||||||
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
saPolicyClaim, ok := args.Claims[iamPolicyClaimNameSA()]
|
|
||||||
if ok {
|
|
||||||
saPolicyClaimStr, ok := saPolicyClaim.(string)
|
saPolicyClaimStr, ok := saPolicyClaim.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Sub policy if set, should be a string reject
|
// Sub policy if set, should be a string reject
|
||||||
@ -1486,15 +1461,15 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parent string) b
|
|||||||
}
|
}
|
||||||
|
|
||||||
if saPolicyClaimStr == "inherited-policy" {
|
if saPolicyClaimStr == "inherited-policy" {
|
||||||
// Immediately returns true since at this stage, since
|
return combinedPolicy.IsAllowed(parentArgs)
|
||||||
// parent user is allowed to do this action.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check if we have a sessionPolicy.
|
// Now check if we have a sessionPolicy.
|
||||||
spolicy, ok := args.Claims[iampolicy.SessionPolicyName]
|
spolicy, ok := args.Claims[iampolicy.SessionPolicyName]
|
||||||
if ok {
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
spolicyStr, ok := spolicy.(string)
|
spolicyStr, ok := spolicy.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Sub policy if set, should be a string reject
|
// Sub policy if set, should be a string reject
|
||||||
@ -1515,10 +1490,7 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parent string) b
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return subPolicy.IsAllowed(args)
|
return combinedPolicy.IsAllowed(parentArgs) && subPolicy.IsAllowed(parentArgs)
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAllowedSTS is meant for STS based temporary credentials,
|
// IsAllowedSTS is meant for STS based temporary credentials,
|
||||||
|
@ -102,7 +102,7 @@ func (cred Credentials) String() string {
|
|||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
s.WriteString(cred.SessionToken)
|
s.WriteString(cred.SessionToken)
|
||||||
}
|
}
|
||||||
if !cred.Expiration.IsZero() && cred.Expiration != timeSentinel {
|
if !cred.Expiration.IsZero() && !cred.Expiration.Equal(timeSentinel) {
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
s.WriteString(cred.Expiration.String())
|
s.WriteString(cred.Expiration.String())
|
||||||
}
|
}
|
||||||
@ -111,7 +111,7 @@ func (cred Credentials) String() string {
|
|||||||
|
|
||||||
// IsExpired - returns whether Credential is expired or not.
|
// IsExpired - returns whether Credential is expired or not.
|
||||||
func (cred Credentials) IsExpired() bool {
|
func (cred Credentials) IsExpired() bool {
|
||||||
if cred.Expiration.IsZero() || cred.Expiration == timeSentinel {
|
if cred.Expiration.IsZero() || cred.Expiration.Equal(timeSentinel) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,12 +120,12 @@ func (cred Credentials) IsExpired() bool {
|
|||||||
|
|
||||||
// IsTemp - returns whether credential is temporary or not.
|
// IsTemp - returns whether credential is temporary or not.
|
||||||
func (cred Credentials) IsTemp() bool {
|
func (cred Credentials) IsTemp() bool {
|
||||||
return cred.SessionToken != "" && cred.ParentUser == ""
|
return cred.SessionToken != "" && cred.ParentUser == "" && !cred.Expiration.IsZero() && !cred.Expiration.Equal(timeSentinel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsServiceAccount - returns whether credential is a service account or not
|
// IsServiceAccount - returns whether credential is a service account or not
|
||||||
func (cred Credentials) IsServiceAccount() bool {
|
func (cred Credentials) IsServiceAccount() bool {
|
||||||
return cred.ParentUser != ""
|
return cred.ParentUser != "" && (cred.Expiration.IsZero() || cred.Expiration.Equal(timeSentinel))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid - returns whether credential is valid or not.
|
// IsValid - returns whether credential is valid or not.
|
||||||
|
@ -62,6 +62,7 @@ const (
|
|||||||
|
|
||||||
// CreateUserAdminAction - allow creating MinIO user
|
// CreateUserAdminAction - allow creating MinIO user
|
||||||
CreateUserAdminAction = "admin:CreateUser"
|
CreateUserAdminAction = "admin:CreateUser"
|
||||||
|
|
||||||
// DeleteUserAdminAction - allow deleting MinIO user
|
// DeleteUserAdminAction - allow deleting MinIO user
|
||||||
DeleteUserAdminAction = "admin:DeleteUser"
|
DeleteUserAdminAction = "admin:DeleteUser"
|
||||||
// ListUsersAdminAction - allow list users permission
|
// ListUsersAdminAction - allow list users permission
|
||||||
|
@ -24,6 +24,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
|
"github.com/minio/minio/pkg/bucket/policy/condition"
|
||||||
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,10 +44,18 @@ func main() {
|
|||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create policy
|
p := iampolicy.Policy{
|
||||||
policy := `{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject"],"Effect": "Allow","Resource": ["arn:aws:s3:::testbucket/*"],"Sid": ""}]}`
|
Version: iampolicy.DefaultVersion,
|
||||||
|
Statements: []Statement{
|
||||||
|
iampolicy.NewStatement(
|
||||||
|
policy.Allow,
|
||||||
|
iampolicy.NewActionSet(iampolicy.GetObjectAction),
|
||||||
|
iampolicy.NewResourceSet(iampolicy.NewResource("testbucket/*", "")),
|
||||||
|
condition.NewFunctions(),
|
||||||
|
)},
|
||||||
|
}
|
||||||
|
|
||||||
creds, err := madmClnt.AddServiceAccount(context.Background(), "parentuser", policy)
|
creds, err := madmClnt.AddServiceAccount(context.Background(), "parentuser", &p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/minio/minio/pkg/bucket/policy"
|
||||||
|
"github.com/minio/minio/pkg/bucket/policy/condition"
|
||||||
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
"github.com/minio/minio/pkg/madmin"
|
"github.com/minio/minio/pkg/madmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,9 +48,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create policy
|
// Create policy
|
||||||
policy := `{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject"],"Effect": "Allow","Resource": ["arn:aws:s3:::my-bucketname/*"],"Sid": ""}]}`
|
p := iampolicy.Policy{
|
||||||
|
Version: iampolicy.DefaultVersion,
|
||||||
|
Statements: []iampolicy.Statement{
|
||||||
|
iampolicy.NewStatement(
|
||||||
|
policy.Allow,
|
||||||
|
iampolicy.NewActionSet(iampolicy.GetObjectAction),
|
||||||
|
iampolicy.NewResourceSet(iampolicy.NewResource("testbucket/*", "")),
|
||||||
|
condition.NewFunctions(),
|
||||||
|
)},
|
||||||
|
}
|
||||||
|
|
||||||
if err = madmClnt.AddCannedPolicy(context.Background(), "get-only", policy); err != nil {
|
if err = madmClnt.AddCannedPolicy(context.Background(), "get-only", &p); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
// +build ignore
|
|
||||||
|
|
||||||
/*
|
|
||||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/madmin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
|
||||||
// dummy values, please replace them with original values.
|
|
||||||
|
|
||||||
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
|
||||||
// dummy values, please replace them with original values.
|
|
||||||
|
|
||||||
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
|
||||||
// New returns an MinIO Admin client object.
|
|
||||||
madmClnt, err := madmin.New("your-minio.example.com:9000", "YOUR-ACCESSKEYID", "YOUR-SECRETACCESSKEY", true)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
creds, err := madmClnt.GetServiceAccount(context.Background(), "service-account-access-key")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(creds)
|
|
||||||
}
|
|
@ -114,6 +114,10 @@ func (adm *AdminClient) AddCannedPolicy(ctx context.Context, policyName string,
|
|||||||
return ErrInvalidArgument("policy input cannot be empty")
|
return ErrInvalidArgument("policy input cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := policy.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
buf, err := json.Marshal(policy)
|
buf, err := json.Marshal(policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/auth"
|
"github.com/minio/minio/pkg/auth"
|
||||||
|
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccountStatus - account status.
|
// AccountStatus - account status.
|
||||||
@ -215,7 +216,7 @@ func (adm *AdminClient) SetUserStatus(ctx context.Context, accessKey string, sta
|
|||||||
// AddServiceAccountReq is the request body of the add service account admin call
|
// AddServiceAccountReq is the request body of the add service account admin call
|
||||||
type AddServiceAccountReq struct {
|
type AddServiceAccountReq struct {
|
||||||
Parent string `json:"parent"`
|
Parent string `json:"parent"`
|
||||||
Policy string `json:"policy"`
|
Policy *iampolicy.Policy `json:"policy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddServiceAccountResp is the response body of the add service account admin call
|
// AddServiceAccountResp is the response body of the add service account admin call
|
||||||
@ -225,12 +226,17 @@ type AddServiceAccountResp struct {
|
|||||||
|
|
||||||
// AddServiceAccount - creates a new service account belonging to the given parent user
|
// AddServiceAccount - creates a new service account belonging to the given parent user
|
||||||
// while restricting the service account permission by the given policy document.
|
// while restricting the service account permission by the given policy document.
|
||||||
func (adm *AdminClient) AddServiceAccount(ctx context.Context, parentUser string, policy string) (auth.Credentials, error) {
|
func (adm *AdminClient) AddServiceAccount(ctx context.Context, parentUser string, policy *iampolicy.Policy) (auth.Credentials, error) {
|
||||||
|
|
||||||
if !auth.IsAccessKeyValid(parentUser) {
|
if !auth.IsAccessKeyValid(parentUser) {
|
||||||
return auth.Credentials{}, auth.ErrInvalidAccessKeyLength
|
return auth.Credentials{}, auth.ErrInvalidAccessKeyLength
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if policy != nil {
|
||||||
|
if err := policy.Validate(); err != nil {
|
||||||
|
return auth.Credentials{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(AddServiceAccountReq{
|
data, err := json.Marshal(AddServiceAccountReq{
|
||||||
Parent: parentUser,
|
Parent: parentUser,
|
||||||
Policy: policy,
|
Policy: policy,
|
||||||
@ -271,41 +277,3 @@ func (adm *AdminClient) AddServiceAccount(ctx context.Context, parentUser string
|
|||||||
}
|
}
|
||||||
return serviceAccountResp.Credentials, nil
|
return serviceAccountResp.Credentials, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetServiceAccount returns the credential of the service account
|
|
||||||
func (adm *AdminClient) GetServiceAccount(ctx context.Context, serviceAccountAccessKey string) (auth.Credentials, error) {
|
|
||||||
|
|
||||||
if !auth.IsAccessKeyValid(serviceAccountAccessKey) {
|
|
||||||
return auth.Credentials{}, auth.ErrInvalidAccessKeyLength
|
|
||||||
}
|
|
||||||
|
|
||||||
queryValues := url.Values{}
|
|
||||||
queryValues.Set("accessKey", serviceAccountAccessKey)
|
|
||||||
|
|
||||||
reqData := requestData{
|
|
||||||
relPath: adminAPIPrefix + "/get-service-account",
|
|
||||||
queryValues: queryValues,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute GET on /minio/admin/v3/get-service-account to set a user.
|
|
||||||
resp, err := adm.executeMethod(ctx, http.MethodGet, reqData)
|
|
||||||
defer closeResponse(resp)
|
|
||||||
if err != nil {
|
|
||||||
return auth.Credentials{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return auth.Credentials{}, httpRespToErrorResponse(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := DecryptData(adm.getSecretKey(), resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return auth.Credentials{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var creds auth.Credentials
|
|
||||||
if err = json.Unmarshal(data, &creds); err != nil {
|
|
||||||
return auth.Credentials{}, err
|
|
||||||
}
|
|
||||||
return creds, nil
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user