2016-02-15 20:42:39 -05:00
/ *
2018-04-24 18:53:30 -04:00
* Minio Cloud Storage , ( C ) 2015 - 2018 Minio , Inc .
2016-02-15 20:42:39 -05:00
*
* 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 .
* /
2016-08-18 19:23:42 -04:00
package cmd
2016-02-15 20:42:39 -05:00
2016-02-21 20:57:05 -05:00
import (
2016-03-12 19:08:15 -05:00
"bytes"
2018-04-05 18:04:40 -04:00
"context"
2018-03-16 14:22:34 -04:00
"encoding/base64"
"encoding/hex"
2018-01-17 13:36:25 -05:00
"errors"
2018-05-04 14:16:14 -04:00
"io"
2016-03-12 19:08:15 -05:00
"io/ioutil"
2016-02-21 20:57:05 -05:00
"net/http"
"strings"
2018-03-02 18:23:04 -05:00
2018-04-05 18:04:40 -04:00
"github.com/minio/minio/cmd/logger"
2018-05-18 14:27:25 -04:00
"github.com/minio/minio/pkg/hash"
2018-04-24 18:53:30 -04:00
"github.com/minio/minio/pkg/policy"
2016-02-15 20:42:39 -05:00
)
2016-02-21 20:57:05 -05:00
// Verify if request has JWT.
func isRequestJWT ( r * http . Request ) bool {
2016-08-08 23:56:29 -04:00
return strings . HasPrefix ( r . Header . Get ( "Authorization" ) , jwtAlgorithm )
2016-02-21 20:57:05 -05:00
}
// Verify if request has AWS Signature Version '4'.
func isRequestSignatureV4 ( r * http . Request ) bool {
2016-08-08 23:56:29 -04:00
return strings . HasPrefix ( r . Header . Get ( "Authorization" ) , signV4Algorithm )
2016-02-21 20:57:05 -05:00
}
2016-09-30 17:32:13 -04:00
// Verify if request has AWS Signature Version '2'.
func isRequestSignatureV2 ( r * http . Request ) bool {
return ( ! strings . HasPrefix ( r . Header . Get ( "Authorization" ) , signV4Algorithm ) &&
strings . HasPrefix ( r . Header . Get ( "Authorization" ) , signV2Algorithm ) )
}
2016-08-08 23:56:29 -04:00
// Verify if request has AWS PreSign Version '4'.
2016-02-21 20:57:05 -05:00
func isRequestPresignedSignatureV4 ( r * http . Request ) bool {
2016-08-08 23:56:29 -04:00
_ , ok := r . URL . Query ( ) [ "X-Amz-Credential" ]
return ok
2016-02-21 20:57:05 -05:00
}
2016-09-30 17:32:13 -04:00
// Verify request has AWS PreSign Version '2'.
func isRequestPresignedSignatureV2 ( r * http . Request ) bool {
_ , ok := r . URL . Query ( ) [ "AWSAccessKeyId" ]
return ok
}
2016-02-21 20:57:05 -05:00
// Verify if request has AWS Post policy Signature Version '4'.
func isRequestPostPolicySignatureV4 ( r * http . Request ) bool {
2018-01-07 23:47:48 -05:00
return strings . Contains ( r . Header . Get ( "Content-Type" ) , "multipart/form-data" ) &&
r . Method == http . MethodPost
2016-08-08 23:56:29 -04:00
}
// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
func isRequestSignStreamingV4 ( r * http . Request ) bool {
2017-02-20 15:07:03 -05:00
return r . Header . Get ( "x-amz-content-sha256" ) == streamingContentSHA256 &&
2018-01-07 23:47:48 -05:00
r . Method == http . MethodPut
2016-02-21 20:57:05 -05:00
}
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
// Authorization type.
type authType int
// List of all supported auth types.
const (
authTypeUnknown authType = iota
authTypeAnonymous
authTypePresigned
2016-09-30 17:32:13 -04:00
authTypePresignedV2
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
authTypePostPolicy
2016-08-08 23:56:29 -04:00
authTypeStreamingSigned
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
authTypeSigned
2016-09-30 17:32:13 -04:00
authTypeSignedV2
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
authTypeJWT
)
// Get request authentication type.
func getRequestAuthType ( r * http . Request ) authType {
2016-09-30 17:32:13 -04:00
if isRequestSignatureV2 ( r ) {
return authTypeSignedV2
} else if isRequestPresignedSignatureV2 ( r ) {
return authTypePresignedV2
} else if isRequestSignStreamingV4 ( r ) {
2016-08-08 23:56:29 -04:00
return authTypeStreamingSigned
} else if isRequestSignatureV4 ( r ) {
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
return authTypeSigned
} else if isRequestPresignedSignatureV4 ( r ) {
return authTypePresigned
} else if isRequestJWT ( r ) {
return authTypeJWT
} else if isRequestPostPolicySignatureV4 ( r ) {
return authTypePostPolicy
2016-03-10 20:36:48 -05:00
} else if _ , ok := r . Header [ "Authorization" ] ; ! ok {
return authTypeAnonymous
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
}
return authTypeUnknown
}
2018-01-17 13:36:25 -05:00
// 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 ( r * http . Request , region string ) APIErrorCode {
s3Err := ErrAccessDenied
2018-05-30 17:49:03 -04:00
if _ , ok := r . Header [ "X-Amz-Content-Sha256" ] ; ok && getRequestAuthType ( r ) == authTypeSigned && ! skipContentSha256Cksum ( r ) { // we only support V4 (no presign) with auth. body
2018-01-17 13:36:25 -05:00
s3Err = isReqAuthenticated ( r , region )
}
if s3Err != ErrNone {
2018-04-05 18:04:40 -04:00
reqInfo := ( & logger . ReqInfo { } ) . AppendTags ( "requestHeaders" , dumpRequest ( r ) )
ctx := logger . SetReqInfo ( context . Background ( ) , reqInfo )
logger . LogIf ( ctx , errors . New ( getAPIError ( s3Err ) . Description ) )
2018-01-17 13:36:25 -05:00
}
return s3Err
}
2018-04-24 18:53:30 -04:00
func checkRequestAuthType ( ctx context . Context , r * http . Request , action policy . Action , bucketName , objectName string ) APIErrorCode {
isOwner := true
accountName := globalServerConfig . GetCredential ( ) . AccessKey
2016-11-21 16:51:05 -05:00
2018-04-24 18:53:30 -04:00
switch getRequestAuthType ( r ) {
case authTypeUnknown :
return ErrAccessDenied
2016-11-21 16:51:05 -05:00
case authTypePresignedV2 , authTypeSignedV2 :
2018-04-24 18:53:30 -04:00
if errorCode := isReqAuthenticatedV2 ( r ) ; errorCode != ErrNone {
return errorCode
}
2016-11-21 16:51:05 -05:00
case authTypeSigned , authTypePresigned :
2018-04-24 18:53:30 -04:00
region := globalServerConfig . GetRegion ( )
switch action {
case policy . GetBucketLocationAction , policy . ListAllMyBucketsAction :
region = ""
}
if errorCode := isReqAuthenticated ( r , region ) ; errorCode != ErrNone {
return errorCode
}
default :
isOwner = false
accountName = ""
2016-11-21 16:51:05 -05:00
}
2018-04-24 18:53:30 -04:00
// LocationConstraint is valid only for CreateBucketAction.
var locationConstraint string
if action == policy . CreateBucketAction {
// To extract region from XML in request body, get copy of request body.
2018-05-04 14:16:14 -04:00
payload , err := ioutil . ReadAll ( io . LimitReader ( r . Body , maxLocationConstraintSize ) )
2017-11-14 19:56:24 -05:00
if err != nil {
2018-04-24 18:53:30 -04:00
logger . LogIf ( ctx , err )
2018-05-04 14:16:14 -04:00
return ErrMalformedXML
2018-04-24 18:53:30 -04:00
}
// Populate payload to extract location constraint.
r . Body = ioutil . NopCloser ( bytes . NewReader ( payload ) )
var s3Error APIErrorCode
locationConstraint , s3Error = parseLocationConstraint ( r )
if s3Error != ErrNone {
2018-05-04 14:16:14 -04:00
return s3Error
2017-11-14 19:56:24 -05:00
}
2018-04-24 18:53:30 -04:00
// Populate payload again to handle it in HTTP handler.
r . Body = ioutil . NopCloser ( bytes . NewReader ( payload ) )
}
if globalPolicySys . IsAllowed ( policy . Args {
AccountName : accountName ,
Action : action ,
BucketName : bucketName ,
ConditionValues : getConditionValues ( r , locationConstraint ) ,
IsOwner : isOwner ,
ObjectName : objectName ,
} ) {
return ErrNone
}
2016-11-21 16:51:05 -05:00
return ErrAccessDenied
2016-03-12 19:08:15 -05:00
}
2016-09-30 17:32:13 -04:00
// Verify if request has valid AWS Signature Version '2'.
func isReqAuthenticatedV2 ( r * http . Request ) ( s3Error APIErrorCode ) {
if isRequestSignatureV2 ( r ) {
return doesSignV2Match ( r )
}
return doesPresignV2SignatureMatch ( r )
}
2017-04-10 12:58:08 -04:00
func reqSignatureV4Verify ( r * http . Request , region string ) ( s3Error APIErrorCode ) {
sha256sum := getContentSha256Cksum ( r )
switch {
case isRequestSignatureV4 ( r ) :
return doesSignatureMatch ( sha256sum , r , region )
case isRequestPresignedSignatureV4 ( r ) :
return doesPresignedSignatureMatch ( sha256sum , r , region )
default :
return ErrAccessDenied
2016-10-02 18:51:49 -04:00
}
}
2016-02-21 20:57:05 -05:00
// Verify if request has valid AWS Signature Version '4'.
2016-09-29 18:51:00 -04:00
func isReqAuthenticated ( r * http . Request , region string ) ( s3Error APIErrorCode ) {
2017-04-10 12:58:08 -04:00
if errCode := reqSignatureV4Verify ( r , region ) ; errCode != ErrNone {
return errCode
}
2018-03-16 14:22:34 -04:00
2018-05-18 14:27:25 -04:00
var (
err error
contentMD5 , contentSHA256 [ ] byte
)
// Extract 'Content-Md5' if present.
if _ , ok := r . Header [ "Content-Md5" ] ; ok {
contentMD5 , err = base64 . StdEncoding . Strict ( ) . DecodeString ( r . Header . Get ( "Content-Md5" ) )
if err != nil || len ( contentMD5 ) == 0 {
2018-03-16 14:22:34 -04:00
return ErrInvalidDigest
}
2016-03-12 19:08:15 -05:00
}
2017-04-10 12:58:08 -04:00
2018-05-18 14:27:25 -04:00
// Extract either 'X-Amz-Content-Sha256' header or 'X-Amz-Content-Sha256' query parameter (if V4 presigned)
// Do not verify 'X-Amz-Content-Sha256' if skipSHA256.
if skipSHA256 := skipContentSha256Cksum ( r ) ; ! skipSHA256 && isRequestPresignedSignatureV4 ( r ) {
if sha256Sum , ok := r . URL . Query ( ) [ "X-Amz-Content-Sha256" ] ; ok && len ( sha256Sum ) > 0 {
contentSHA256 , err = hex . DecodeString ( sha256Sum [ 0 ] )
if err != nil {
return ErrContentSHA256Mismatch
}
2018-03-16 14:22:34 -04:00
}
2018-05-18 14:27:25 -04:00
} else if _ , ok := r . Header [ "X-Amz-Content-Sha256" ] ; ! skipSHA256 && ok {
contentSHA256 , err = hex . DecodeString ( r . Header . Get ( "X-Amz-Content-Sha256" ) )
if err != nil || len ( contentSHA256 ) == 0 {
2018-03-16 14:22:34 -04:00
return ErrContentSHA256Mismatch
}
2017-04-10 12:58:08 -04:00
}
2018-05-18 14:27:25 -04:00
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
// The verification happens implicit during reading.
2018-09-27 23:36:17 -04:00
reader , err := hash . NewReader ( r . Body , - 1 , hex . EncodeToString ( contentMD5 ) , hex . EncodeToString ( contentSHA256 ) , - 1 )
2018-05-18 14:27:25 -04:00
if err != nil {
return toAPIErrorCode ( err )
}
r . Body = ioutil . NopCloser ( reader )
2017-04-10 12:58:08 -04:00
return ErrNone
2016-02-21 20:57:05 -05:00
}
2016-07-17 16:23:15 -04:00
// authHandler - handles all the incoming authorization headers and validates them if possible.
2016-02-15 20:42:39 -05:00
type authHandler struct {
handler http . Handler
}
// setAuthHandler to validate authorization header for the incoming request.
func setAuthHandler ( h http . Handler ) http . Handler {
return authHandler { h }
}
2016-08-08 23:56:29 -04:00
// List of all support S3 auth types.
2016-08-16 10:57:14 -04:00
var supportedS3AuthTypes = map [ authType ] struct { } {
authTypeAnonymous : { } ,
authTypePresigned : { } ,
2016-09-30 17:32:13 -04:00
authTypePresignedV2 : { } ,
2016-08-16 10:57:14 -04:00
authTypeSigned : { } ,
2016-09-30 17:32:13 -04:00
authTypeSignedV2 : { } ,
2016-08-16 10:57:14 -04:00
authTypePostPolicy : { } ,
authTypeStreamingSigned : { } ,
2016-08-08 23:56:29 -04:00
}
// Validate if the authType is valid and supported.
func isSupportedS3AuthType ( aType authType ) bool {
2016-08-16 10:57:14 -04:00
_ , ok := supportedS3AuthTypes [ aType ]
return ok
2016-08-08 23:56:29 -04:00
}
2016-02-16 21:50:36 -05:00
// handler for validating incoming authorization headers.
2016-02-15 20:42:39 -05:00
func ( a authHandler ) ServeHTTP ( w http . ResponseWriter , r * http . Request ) {
2016-08-08 23:56:29 -04:00
aType := getRequestAuthType ( r )
if isSupportedS3AuthType ( aType ) {
// Let top level caller validate for anonymous and known signed requests.
2016-02-16 21:50:36 -05:00
a . handler . ServeHTTP ( w , r )
return
2016-08-08 23:56:29 -04:00
} else if aType == authTypeJWT {
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
// Validate Authorization header if its valid for JWT request.
2016-12-27 11:28:10 -05:00
if ! isHTTPRequestValid ( r ) {
2016-02-15 20:42:39 -05:00
w . WriteHeader ( http . StatusUnauthorized )
return
}
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
a . handler . ServeHTTP ( w , r )
return
2016-02-15 20:42:39 -05:00
}
2017-01-06 03:37:00 -05:00
writeErrorResponse ( w , ErrSignatureVersionNotSupported , r . URL )
2016-02-15 20:42:39 -05:00
}