mirror of
https://github.com/minio/minio.git
synced 2025-07-29 02:00:59 -04:00
allow tagging policy condition for GetObject (#15777)
This commit is contained in:
parent
ed5b67720c
commit
f696a221af
@ -1203,6 +1203,34 @@ func (c *check) mustNotListObjects(ctx context.Context, client *minio.Client, bu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *check) mustPutObjectWithTags(ctx context.Context, client *minio.Client, bucket, object string) {
|
||||||
|
c.Helper()
|
||||||
|
_, err := client.PutObject(ctx, bucket, object, bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{
|
||||||
|
UserTags: map[string]string{
|
||||||
|
"security": "public",
|
||||||
|
"virus": "true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to upload the object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustGetObject(ctx context.Context, client *minio.Client, bucket, object string) {
|
||||||
|
c.Helper()
|
||||||
|
|
||||||
|
r, err := client.GetObject(ctx, bucket, object, minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to download the object: %v", err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(io.Discard, r)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to download the object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
c.Helper()
|
c.Helper()
|
||||||
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
||||||
|
@ -84,6 +84,8 @@ func isRequestSignStreamingV4(r *http.Request) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authorization type.
|
// Authorization type.
|
||||||
|
//
|
||||||
|
//go:generate stringer -type=authType -trimprefix=authType $GOFILE
|
||||||
type authType int
|
type authType int
|
||||||
|
|
||||||
// List of all supported auth types.
|
// List of all supported auth types.
|
||||||
@ -293,24 +295,27 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
|
|||||||
//
|
//
|
||||||
// returns APIErrorCode if any to be replied to the client.
|
// returns APIErrorCode if any to be replied to the client.
|
||||||
func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (s3Err APIErrorCode) {
|
func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (s3Err APIErrorCode) {
|
||||||
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action, bucketName, objectName)
|
logger.GetReqInfo(ctx).BucketName = bucketName
|
||||||
|
logger.GetReqInfo(ctx).ObjectName = objectName
|
||||||
|
|
||||||
|
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action)
|
||||||
return s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check request auth type verifies the incoming http request
|
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
// - validates the request signature
|
if logger.GetReqInfo(ctx) == nil {
|
||||||
// - validates the policy action if anonymous tests bucket policies if any,
|
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
|
||||||
// for authenticated requests validates IAM policies.
|
return ErrAccessDenied
|
||||||
//
|
}
|
||||||
// returns APIErrorCode if any to be replied to the client.
|
|
||||||
// Additionally returns the accessKey used in the request, and if this request is by an admin.
|
var cred auth.Credentials
|
||||||
func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (cred auth.Credentials, owner bool, s3Err APIErrorCode) {
|
var owner bool
|
||||||
switch getRequestAuthType(r) {
|
switch getRequestAuthType(r) {
|
||||||
case authTypeUnknown, authTypeStreamingSigned:
|
case authTypeUnknown, authTypeStreamingSigned:
|
||||||
return cred, owner, ErrSignatureVersionNotSupported
|
return ErrSignatureVersionNotSupported
|
||||||
case authTypePresignedV2, authTypeSignedV2:
|
case authTypePresignedV2, authTypeSignedV2:
|
||||||
if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
|
if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypeSigned, authTypePresigned:
|
case authTypeSigned, authTypePresigned:
|
||||||
@ -320,52 +325,67 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
|||||||
region = ""
|
region = ""
|
||||||
}
|
}
|
||||||
if s3Err = isReqAuthenticated(ctx, r, region, serviceS3); s3Err != ErrNone {
|
if s3Err = isReqAuthenticated(ctx, r, region, serviceS3); s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, owner, s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocationConstraint is valid only for CreateBucketAction.
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
var locationConstraint string
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
|
|
||||||
|
// region is valid only for CreateBucketAction.
|
||||||
|
var region string
|
||||||
if action == policy.CreateBucketAction {
|
if action == policy.CreateBucketAction {
|
||||||
// To extract region from XML in request body, get copy of request body.
|
// To extract region from XML in request body, get copy of request body.
|
||||||
payload, err := io.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
payload, err := io.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, err, logger.Application)
|
logger.LogIf(ctx, err, logger.Application)
|
||||||
return cred, owner, ErrMalformedXML
|
return ErrMalformedXML
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate payload to extract location constraint.
|
// Populate payload to extract location constraint.
|
||||||
r.Body = io.NopCloser(bytes.NewReader(payload))
|
r.Body = io.NopCloser(bytes.NewReader(payload))
|
||||||
|
region, s3Err = parseLocationConstraint(r)
|
||||||
var s3Error APIErrorCode
|
if s3Err != ErrNone {
|
||||||
locationConstraint, s3Error = parseLocationConstraint(r)
|
return s3Err
|
||||||
if s3Error != ErrNone {
|
|
||||||
return cred, owner, s3Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate payload again to handle it in HTTP handler.
|
// Populate payload again to handle it in HTTP handler.
|
||||||
r.Body = io.NopCloser(bytes.NewReader(payload))
|
r.Body = io.NopCloser(bytes.NewReader(payload))
|
||||||
}
|
}
|
||||||
if cred.AccessKey != "" {
|
|
||||||
logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
|
logger.GetReqInfo(ctx).Region = region
|
||||||
|
|
||||||
|
return s3Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
|
if reqInfo == nil {
|
||||||
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cred := reqInfo.Cred
|
||||||
|
owner := reqInfo.Owner
|
||||||
|
region := reqInfo.Region
|
||||||
|
bucket := reqInfo.BucketName
|
||||||
|
object := reqInfo.ObjectName
|
||||||
|
|
||||||
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||||
// Anonymous checks are not meant for ListBuckets action
|
// Anonymous checks are not meant for ListAllBuckets action
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: action,
|
Action: action,
|
||||||
BucketName: bucketName,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, locationConstraint, "", nil),
|
ConditionValues: getConditionValues(r, region, "", nil),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: objectName,
|
ObjectName: object,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, owner, ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
@ -374,31 +394,31 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
|||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, locationConstraint, "", nil),
|
ConditionValues: getConditionValues(r, region, "", nil),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: objectName,
|
ObjectName: object,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, owner, ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cred, owner, ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: iampolicy.Action(action),
|
||||||
BucketName: bucketName,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||||
ObjectName: objectName,
|
ObjectName: object,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, owner, ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
@ -408,18 +428,41 @@ func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action
|
|||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: iampolicy.ListBucketAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||||
ObjectName: objectName,
|
ObjectName: object,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, owner, ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cred, owner, ErrAccessDenied
|
return ErrAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check request auth type verifies the incoming http request
|
||||||
|
// - validates the request signature
|
||||||
|
// - validates the policy action if anonymous tests bucket policies if any,
|
||||||
|
// for authenticated requests validates IAM policies.
|
||||||
|
//
|
||||||
|
// returns APIErrorCode if any to be replied to the client.
|
||||||
|
// Additionally returns the accessKey used in the request, and if this request is by an admin.
|
||||||
|
func checkRequestAuthTypeCredential(ctx context.Context, r *http.Request, action policy.Action) (cred auth.Credentials, owner bool, s3Err APIErrorCode) {
|
||||||
|
s3Err = authenticateRequest(ctx, r, action)
|
||||||
|
reqInfo := logger.GetReqInfo(ctx)
|
||||||
|
if reqInfo == nil {
|
||||||
|
return cred, owner, ErrAccessDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
cred = reqInfo.Cred
|
||||||
|
owner = reqInfo.Owner
|
||||||
|
if s3Err != ErrNone {
|
||||||
|
return cred, owner, s3Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cred, owner, authorizeRequest(ctx, r, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if request has valid AWS Signature Version '2'.
|
// Verify if request has valid AWS Signature Version '2'.
|
||||||
@ -623,22 +666,22 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) {
|
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) {
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
|
region := globalSite.Region
|
||||||
switch atype {
|
switch atype {
|
||||||
case authTypeUnknown:
|
case authTypeUnknown:
|
||||||
return ErrSignatureVersionNotSupported
|
return ErrSignatureVersionNotSupported
|
||||||
case authTypeSignedV2, authTypePresignedV2:
|
case authTypeSignedV2, authTypePresignedV2:
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
|
case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
|
||||||
region := globalSite.Region
|
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.AccessKey != "" {
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
logger.GetReqInfo(ctx).AccessKey = cred.AccessKey
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
}
|
logger.GetReqInfo(ctx).Region = region
|
||||||
|
|
||||||
// Do not check for PutObjectRetentionAction permission,
|
// Do not check for PutObjectRetentionAction permission,
|
||||||
// if mode and retain until date are not set.
|
// if mode and retain until date are not set.
|
||||||
|
32
cmd/authtype_string.go
Normal file
32
cmd/authtype_string.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Code generated by "stringer -type=authType -trimprefix=authType auth-handler.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[authTypeUnknown-0]
|
||||||
|
_ = x[authTypeAnonymous-1]
|
||||||
|
_ = x[authTypePresigned-2]
|
||||||
|
_ = x[authTypePresignedV2-3]
|
||||||
|
_ = x[authTypePostPolicy-4]
|
||||||
|
_ = x[authTypeStreamingSigned-5]
|
||||||
|
_ = x[authTypeSigned-6]
|
||||||
|
_ = x[authTypeSignedV2-7]
|
||||||
|
_ = x[authTypeJWT-8]
|
||||||
|
_ = x[authTypeSTS-9]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _authType_name = "UnknownAnonymousPresignedPresignedV2PostPolicyStreamingSignedSignedSignedV2JWTSTS"
|
||||||
|
|
||||||
|
var _authType_index = [...]uint8{0, 7, 16, 25, 36, 46, 61, 67, 75, 78, 81}
|
||||||
|
|
||||||
|
func (i authType) String() string {
|
||||||
|
if i < 0 || i >= authType(len(_authType_index)-1) {
|
||||||
|
return "authType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _authType_name[_authType_index[i]:_authType_index[i+1]]
|
||||||
|
}
|
@ -300,7 +300,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
listBuckets := objectAPI.ListBuckets
|
listBuckets := objectAPI.ListBuckets
|
||||||
|
|
||||||
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.ListAllMyBucketsAction, "", "")
|
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.ListAllMyBucketsAction)
|
||||||
if s3Error != ErrNone && s3Error != ErrAccessDenied {
|
if s3Error != ErrNone && s3Error != ErrAccessDenied {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
@ -731,7 +731,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.CreateBucketAction, bucket, "")
|
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.CreateBucketAction)
|
||||||
if s3Error != ErrNone {
|
if s3Error != ErrNone {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
return
|
return
|
||||||
|
@ -187,23 +187,25 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Take read lock on object, here so subsequent lower-level
|
||||||
|
// calls do not need to.
|
||||||
|
lock := objectAPI.NewNSLock(bucket, object)
|
||||||
|
lkctx, err := lock.GetRLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
|
defer lock.RUnlock(lkctx.Cancel)
|
||||||
|
|
||||||
getObjectNInfo := objectAPI.GetObjectNInfo
|
getObjectNInfo := objectAPI.GetObjectNInfo
|
||||||
if api.CacheAPI() != nil {
|
if api.CacheAPI() != nil {
|
||||||
getObjectNInfo = api.CacheAPI().GetObjectNInfo
|
getObjectNInfo = api.CacheAPI().GetObjectNInfo
|
||||||
} else {
|
|
||||||
// Take read lock on object, here so subsequent lower-level
|
|
||||||
// calls do not need to.
|
|
||||||
lock := objectAPI.NewNSLock(bucket, object)
|
|
||||||
lkctx, err := lock.GetRLock(ctx, globalOperationTimeout)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx = lkctx.Context()
|
|
||||||
defer lock.RUnlock(lkctx.Cancel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
objInfo, err := getObjectInfo(ctx, bucket, object, opts)
|
gopts := opts
|
||||||
|
gopts.NoLock = true // We already have a lock, we can live with it.
|
||||||
|
objInfo, err := getObjectInfo(ctx, bucket, object, gopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if globalBucketVersioningSys.PrefixEnabled(bucket, object) {
|
if globalBucketVersioningSys.PrefixEnabled(bucket, object) {
|
||||||
// Versioning enabled quite possibly object is deleted might be delete-marker
|
// Versioning enabled quite possibly object is deleted might be delete-marker
|
||||||
@ -342,7 +344,7 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
|||||||
|
|
||||||
// Check for auth type to return S3 compatible error.
|
// Check for auth type to return S3 compatible error.
|
||||||
// type to return the correct error (NoSuchKey vs AccessDenied)
|
// type to return the correct error (NoSuchKey vs AccessDenied)
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectAction, bucket, object); s3Error != ErrNone {
|
if s3Error := authenticateRequest(ctx, r, policy.GetObjectAction); s3Error != ErrNone {
|
||||||
if getRequestAuthType(r) == authTypeAnonymous {
|
if getRequestAuthType(r) == authTypeAnonymous {
|
||||||
// As per "Permission" section in
|
// As per "Permission" section in
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html
|
||||||
@ -442,6 +444,12 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if reader == nil || !proxy.Proxy {
|
if reader == nil || !proxy.Proxy {
|
||||||
|
// validate if the request indeed was authorized, if it wasn't we need to return "ErrAccessDenied"
|
||||||
|
// instead of any namespace related error.
|
||||||
|
if s3Error := authorizeRequest(ctx, r, policy.GetObjectAction); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
if isErrPreconditionFailed(err) {
|
if isErrPreconditionFailed(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -474,6 +482,15 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
|||||||
|
|
||||||
objInfo := gr.ObjInfo
|
objInfo := gr.ObjInfo
|
||||||
|
|
||||||
|
if objInfo.UserTags != "" {
|
||||||
|
r.Header.Set(xhttp.AmzObjectTagging, objInfo.UserTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s3Error := authorizeRequest(ctx, r, policy.GetObjectAction); s3Error != ErrNone {
|
||||||
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !proxy.Proxy { // apply lifecycle rules only for local requests
|
if !proxy.Proxy { // apply lifecycle rules only for local requests
|
||||||
// Automatically remove the object/version is an expiry lifecycle rule can be applied
|
// Automatically remove the object/version is an expiry lifecycle rule can be applied
|
||||||
if lc, err := globalLifecycleSys.Get(bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(bucket); err == nil {
|
||||||
|
@ -36,6 +36,7 @@ func runAllIAMSTSTests(suite *TestSuiteIAM, c *check) {
|
|||||||
// The STS for root test needs to be the first one after setup.
|
// The STS for root test needs to be the first one after setup.
|
||||||
suite.TestSTSForRoot(c)
|
suite.TestSTSForRoot(c)
|
||||||
suite.TestSTS(c)
|
suite.TestSTS(c)
|
||||||
|
suite.TestSTSWithTags(c)
|
||||||
suite.TestSTSWithGroupPolicy(c)
|
suite.TestSTSWithGroupPolicy(c)
|
||||||
suite.TearDownSuite(c)
|
suite.TearDownSuite(c)
|
||||||
}
|
}
|
||||||
@ -72,6 +73,117 @@ func TestIAMInternalIDPSTSServerSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TestSuiteIAM) TestSTSWithTags(c *check) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
bucket := getRandomBucketName()
|
||||||
|
object := getRandomObjectName()
|
||||||
|
err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("bucket creat error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create policy, user and associate policy
|
||||||
|
policy := "mypolicy"
|
||||||
|
policyBytes := []byte(fmt.Sprintf(`{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "s3:GetObject",
|
||||||
|
"Resource": "arn:aws:s3:::%s/*",
|
||||||
|
"Condition": { "StringEquals": {"s3:ExistingObjectTag/security": "public" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "s3:DeleteObjectTagging",
|
||||||
|
"Resource": "arn:aws:s3:::%s/*",
|
||||||
|
"Condition": { "StringEquals": {"s3:ExistingObjectTag/security": "public" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": "s3:DeleteObject",
|
||||||
|
"Resource": "arn:aws:s3:::%s/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:PutObject"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::%s/*"
|
||||||
|
],
|
||||||
|
"Condition": {
|
||||||
|
"ForAllValues:StringLike": {
|
||||||
|
"s3:RequestObjectTagKeys": [
|
||||||
|
"security",
|
||||||
|
"virus"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, bucket, bucket, bucket, bucket))
|
||||||
|
err = s.adm.AddCannedPolicy(ctx, policy, policyBytes)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("policy add error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessKey, secretKey := mustGenerateCredentials(c)
|
||||||
|
err = s.adm.SetUser(ctx, accessKey, secretKey, madmin.AccountEnabled)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Unable to set user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.adm.SetPolicy(ctx, policy, accessKey, false)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Unable to set policy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm that the user is able to access the bucket
|
||||||
|
uClient := s.getUserClient(c, accessKey, secretKey, "")
|
||||||
|
c.mustPutObjectWithTags(ctx, uClient, bucket, object)
|
||||||
|
c.mustGetObject(ctx, uClient, bucket, object)
|
||||||
|
|
||||||
|
assumeRole := cr.STSAssumeRole{
|
||||||
|
Client: s.TestSuiteCommon.client,
|
||||||
|
STSEndpoint: s.endPoint,
|
||||||
|
Options: cr.STSAssumeRoleOptions{
|
||||||
|
AccessKey: accessKey,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
Location: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := assumeRole.Retrieve()
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("err calling assumeRole: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
minioClient, err := minio.New(s.endpoint, &minio.Options{
|
||||||
|
Creds: cr.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken),
|
||||||
|
Secure: s.secure,
|
||||||
|
Transport: s.TestSuiteCommon.client.Transport,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Error initializing client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate sts creds can access the object
|
||||||
|
c.mustPutObjectWithTags(ctx, uClient, bucket, object)
|
||||||
|
c.mustGetObject(ctx, uClient, bucket, object)
|
||||||
|
|
||||||
|
// Validate that the client can remove objects
|
||||||
|
if err = minioClient.RemoveObjectTagging(ctx, bucket, object, minio.RemoveObjectTaggingOptions{}); err != nil {
|
||||||
|
c.Fatalf("user is unable to delete the object tags: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = minioClient.RemoveObject(ctx, bucket, object, minio.RemoveObjectOptions{}); err != nil {
|
||||||
|
c.Fatalf("user is unable to delete the object: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TestSuiteIAM) TestSTS(c *check) {
|
func (s *TestSuiteIAM) TestSTS(c *check) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -810,10 +810,6 @@ func newContext(r *http.Request, w http.ResponseWriter, api string) context.Cont
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
object := likelyUnescapeGeneric(vars["object"], url.PathUnescape)
|
object := likelyUnescapeGeneric(vars["object"], url.PathUnescape)
|
||||||
prefix := likelyUnescapeGeneric(vars["prefix"], url.QueryUnescape)
|
|
||||||
if prefix != "" {
|
|
||||||
object = prefix
|
|
||||||
}
|
|
||||||
reqInfo := &logger.ReqInfo{
|
reqInfo := &logger.ReqInfo{
|
||||||
DeploymentID: globalDeploymentID,
|
DeploymentID: globalDeploymentID,
|
||||||
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
||||||
|
2
go.mod
2
go.mod
@ -49,7 +49,7 @@ require (
|
|||||||
github.com/minio/kes v0.21.0
|
github.com/minio/kes v0.21.0
|
||||||
github.com/minio/madmin-go v1.4.29
|
github.com/minio/madmin-go v1.4.29
|
||||||
github.com/minio/minio-go/v7 v7.0.40-0.20220928095841-8848d8affe8a
|
github.com/minio/minio-go/v7 v7.0.40-0.20220928095841-8848d8affe8a
|
||||||
github.com/minio/pkg v1.4.2
|
github.com/minio/pkg v1.4.3
|
||||||
github.com/minio/selfupdate v0.5.0
|
github.com/minio/selfupdate v0.5.0
|
||||||
github.com/minio/sha256-simd v1.0.0
|
github.com/minio/sha256-simd v1.0.0
|
||||||
github.com/minio/simdjson-go v0.4.2
|
github.com/minio/simdjson-go v0.4.2
|
||||||
|
4
go.sum
4
go.sum
@ -660,8 +660,8 @@ github.com/minio/minio-go/v7 v7.0.23/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2
|
|||||||
github.com/minio/minio-go/v7 v7.0.40-0.20220928095841-8848d8affe8a h1:COFh7S3tOKmJNYtKKFAuHQFH7MAaXxg4aAluXC9KQgc=
|
github.com/minio/minio-go/v7 v7.0.40-0.20220928095841-8848d8affe8a h1:COFh7S3tOKmJNYtKKFAuHQFH7MAaXxg4aAluXC9KQgc=
|
||||||
github.com/minio/minio-go/v7 v7.0.40-0.20220928095841-8848d8affe8a/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
|
github.com/minio/minio-go/v7 v7.0.40-0.20220928095841-8848d8affe8a/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
|
||||||
github.com/minio/pkg v1.1.20/go.mod h1:Xo7LQshlxGa9shKwJ7NzQbgW4s8T/Wc1cOStR/eUiMY=
|
github.com/minio/pkg v1.1.20/go.mod h1:Xo7LQshlxGa9shKwJ7NzQbgW4s8T/Wc1cOStR/eUiMY=
|
||||||
github.com/minio/pkg v1.4.2 h1:QEToJld+cy+mMLDv084kIOgzjJQMbM+ioI/WotHeYQY=
|
github.com/minio/pkg v1.4.3 h1:RJdhFj+5qK/oYuTuLGtTzn6GKniwI57eJ0LxrZ7xpw4=
|
||||||
github.com/minio/pkg v1.4.2/go.mod h1:mxCLAG+fOGIQr6odQ5Ukqc6qv9Zj6v1d6TD3NP82B7Y=
|
github.com/minio/pkg v1.4.3/go.mod h1:mxCLAG+fOGIQr6odQ5Ukqc6qv9Zj6v1d6TD3NP82B7Y=
|
||||||
github.com/minio/selfupdate v0.5.0 h1:0UH1HlL49+2XByhovKl5FpYTjKfvrQ2sgL1zEXK6mfI=
|
github.com/minio/selfupdate v0.5.0 h1:0UH1HlL49+2XByhovKl5FpYTjKfvrQ2sgL1zEXK6mfI=
|
||||||
github.com/minio/selfupdate v0.5.0/go.mod h1:mcDkzMgq8PRcpCRJo/NlPY7U45O5dfYl2Y0Rg7IustY=
|
github.com/minio/selfupdate v0.5.0/go.mod h1:mcDkzMgq8PRcpCRJo/NlPY7U45O5dfYl2Y0Rg7IustY=
|
||||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Key used for Get/SetReqInfo
|
// Key used for Get/SetReqInfo
|
||||||
@ -43,18 +45,21 @@ type ObjectVersion struct {
|
|||||||
// ReqInfo stores the request info.
|
// ReqInfo stores the request info.
|
||||||
// Reading/writing directly to struct requires appropriate R/W lock.
|
// Reading/writing directly to struct requires appropriate R/W lock.
|
||||||
type ReqInfo struct {
|
type ReqInfo struct {
|
||||||
RemoteHost string // Client Host/IP
|
RemoteHost string // Client Host/IP
|
||||||
Host string // Node Host/IP
|
Host string // Node Host/IP
|
||||||
UserAgent string // User Agent
|
UserAgent string // User Agent
|
||||||
DeploymentID string // x-minio-deployment-id
|
DeploymentID string // x-minio-deployment-id
|
||||||
RequestID string // x-amz-request-id
|
RequestID string // x-amz-request-id
|
||||||
API string // API name - GetObject PutObject NewMultipartUpload etc.
|
API string // API name - GetObject PutObject NewMultipartUpload etc.
|
||||||
BucketName string `json:",omitempty"` // Bucket name
|
BucketName string `json:",omitempty"` // Bucket name
|
||||||
ObjectName string `json:",omitempty"` // Object name
|
ObjectName string `json:",omitempty"` // Object name
|
||||||
VersionID string `json:",omitempty"` // corresponding versionID for the object
|
VersionID string `json:",omitempty"` // corresponding versionID for the object
|
||||||
Objects []ObjectVersion `json:",omitempty"` // Only set during MultiObject delete handler.
|
Objects []ObjectVersion `json:",omitempty"` // Only set during MultiObject delete handler.
|
||||||
AccessKey string // Access Key
|
Cred auth.Credentials `json:"-"`
|
||||||
tags []KeyVal // Any additional info not accommodated by above fields
|
Region string `json:"-"`
|
||||||
|
Owner bool `json:"-"`
|
||||||
|
AuthType string `json:"-"`
|
||||||
|
tags []KeyVal // Any additional info not accommodated by above fields
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user