2016-02-15 17:42:39 -08:00
/ *
2018-04-25 04:23:30 +05:30
* Minio Cloud Storage , ( C ) 2015 - 2018 Minio , Inc .
2016-02-15 17:42:39 -08: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 16:23:42 -07:00
package cmd
2016-02-15 17:42:39 -08:00
2016-02-21 17:57:05 -08:00
import (
2016-03-12 16:08:15 -08:00
"bytes"
2018-04-05 15:04:40 -07:00
"context"
2018-03-16 11:22:34 -07:00
"encoding/base64"
"encoding/hex"
2018-01-17 19:36:25 +01:00
"errors"
2018-05-04 20:16:14 +02:00
"io"
2016-03-12 16:08:15 -08:00
"io/ioutil"
2016-02-21 17:57:05 -08:00
"net/http"
"strings"
2018-03-02 15:23:04 -08:00
2018-04-05 15:04:40 -07:00
"github.com/minio/minio/cmd/logger"
2018-05-18 20:27:25 +02:00
"github.com/minio/minio/pkg/hash"
2018-04-25 04:23:30 +05:30
"github.com/minio/minio/pkg/policy"
2016-02-15 17:42:39 -08:00
)
2016-02-21 17:57:05 -08:00
// Verify if request has JWT.
func isRequestJWT ( r * http . Request ) bool {
2016-08-08 20:56:29 -07:00
return strings . HasPrefix ( r . Header . Get ( "Authorization" ) , jwtAlgorithm )
2016-02-21 17:57:05 -08:00
}
// Verify if request has AWS Signature Version '4'.
func isRequestSignatureV4 ( r * http . Request ) bool {
2016-08-08 20:56:29 -07:00
return strings . HasPrefix ( r . Header . Get ( "Authorization" ) , signV4Algorithm )
2016-02-21 17:57:05 -08:00
}
2016-09-30 14:32:13 -07: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 20:56:29 -07:00
// Verify if request has AWS PreSign Version '4'.
2016-02-21 17:57:05 -08:00
func isRequestPresignedSignatureV4 ( r * http . Request ) bool {
2016-08-08 20:56:29 -07:00
_ , ok := r . URL . Query ( ) [ "X-Amz-Credential" ]
return ok
2016-02-21 17:57:05 -08:00
}
2016-09-30 14:32:13 -07:00
// Verify request has AWS PreSign Version '2'.
func isRequestPresignedSignatureV2 ( r * http . Request ) bool {
_ , ok := r . URL . Query ( ) [ "AWSAccessKeyId" ]
return ok
}
2016-02-21 17:57:05 -08:00
// Verify if request has AWS Post policy Signature Version '4'.
func isRequestPostPolicySignatureV4 ( r * http . Request ) bool {
2018-01-07 20:47:48 -08:00
return strings . Contains ( r . Header . Get ( "Content-Type" ) , "multipart/form-data" ) &&
r . Method == http . MethodPost
2016-08-08 20:56:29 -07: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 12:07:03 -08:00
return r . Header . Get ( "x-amz-content-sha256" ) == streamingContentSHA256 &&
2018-01-07 20:47:48 -08:00
r . Method == http . MethodPut
2016-02-21 17:57:05 -08: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 16:46:56 -08:00
// Authorization type.
type authType int
// List of all supported auth types.
const (
authTypeUnknown authType = iota
authTypeAnonymous
authTypePresigned
2016-09-30 14:32:13 -07: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 16:46:56 -08:00
authTypePostPolicy
2016-08-08 20:56:29 -07: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 16:46:56 -08:00
authTypeSigned
2016-09-30 14:32:13 -07: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 16:46:56 -08:00
authTypeJWT
)
// Get request authentication type.
func getRequestAuthType ( r * http . Request ) authType {
2016-09-30 14:32:13 -07:00
if isRequestSignatureV2 ( r ) {
return authTypeSignedV2
} else if isRequestPresignedSignatureV2 ( r ) {
return authTypePresignedV2
} else if isRequestSignStreamingV4 ( r ) {
2016-08-08 20:56:29 -07: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 16:46:56 -08:00
return authTypeSigned
} else if isRequestPresignedSignatureV4 ( r ) {
return authTypePresigned
} else if isRequestJWT ( r ) {
return authTypeJWT
} else if isRequestPostPolicySignatureV4 ( r ) {
return authTypePostPolicy
2016-03-10 17:36:48 -08: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 16:46:56 -08:00
}
return authTypeUnknown
}
2018-01-17 19:36:25 +01: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 23:49:03 +02: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 19:36:25 +01:00
s3Err = isReqAuthenticated ( r , region )
}
if s3Err != ErrNone {
2018-04-05 15:04:40 -07: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 19:36:25 +01:00
}
return s3Err
}
2018-04-25 04:23:30 +05:30
func checkRequestAuthType ( ctx context . Context , r * http . Request , action policy . Action , bucketName , objectName string ) APIErrorCode {
isOwner := true
accountName := globalServerConfig . GetCredential ( ) . AccessKey
2016-11-21 13:51:05 -08:00
2018-04-25 04:23:30 +05:30
switch getRequestAuthType ( r ) {
case authTypeUnknown :
return ErrAccessDenied
2016-11-21 13:51:05 -08:00
case authTypePresignedV2 , authTypeSignedV2 :
2018-04-25 04:23:30 +05:30
if errorCode := isReqAuthenticatedV2 ( r ) ; errorCode != ErrNone {
return errorCode
}
2016-11-21 13:51:05 -08:00
case authTypeSigned , authTypePresigned :
2018-04-25 04:23:30 +05:30
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 13:51:05 -08:00
}
2018-04-25 04:23:30 +05:30
// 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 20:16:14 +02:00
payload , err := ioutil . ReadAll ( io . LimitReader ( r . Body , maxLocationConstraintSize ) )
2017-11-14 16:56:24 -08:00
if err != nil {
2018-04-25 04:23:30 +05:30
logger . LogIf ( ctx , err )
2018-05-04 20:16:14 +02:00
return ErrMalformedXML
2018-04-25 04:23:30 +05:30
}
// 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 20:16:14 +02:00
return s3Error
2017-11-14 16:56:24 -08:00
}
2018-04-25 04:23:30 +05:30
// 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 13:51:05 -08:00
return ErrAccessDenied
2016-03-12 16:08:15 -08:00
}
2016-09-30 14:32:13 -07: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 09:58:08 -07: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-03 04:21:49 +05:30
}
}
2016-02-21 17:57:05 -08:00
// Verify if request has valid AWS Signature Version '4'.
2016-09-29 15:51:00 -07:00
func isReqAuthenticated ( r * http . Request , region string ) ( s3Error APIErrorCode ) {
2017-04-10 09:58:08 -07:00
if errCode := reqSignatureV4Verify ( r , region ) ; errCode != ErrNone {
return errCode
}
2018-03-16 11:22:34 -07:00
2018-05-18 20:27:25 +02: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 11:22:34 -07:00
return ErrInvalidDigest
}
2016-03-12 16:08:15 -08:00
}
2017-04-10 09:58:08 -07:00
2018-05-18 20:27:25 +02: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 11:22:34 -07:00
}
2018-05-18 20:27:25 +02: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 11:22:34 -07:00
return ErrContentSHA256Mismatch
}
2017-04-10 09:58:08 -07:00
}
2018-05-18 20:27:25 +02:00
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
// The verification happens implicit during reading.
reader , err := hash . NewReader ( r . Body , - 1 , hex . EncodeToString ( contentMD5 ) , hex . EncodeToString ( contentSHA256 ) )
if err != nil {
return toAPIErrorCode ( err )
}
r . Body = ioutil . NopCloser ( reader )
2017-04-10 09:58:08 -07:00
return ErrNone
2016-02-21 17:57:05 -08:00
}
2016-07-17 13:23:15 -07:00
// authHandler - handles all the incoming authorization headers and validates them if possible.
2016-02-15 17:42:39 -08: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 20:56:29 -07:00
// List of all support S3 auth types.
2016-08-16 20:27:14 +05:30
var supportedS3AuthTypes = map [ authType ] struct { } {
authTypeAnonymous : { } ,
authTypePresigned : { } ,
2016-09-30 14:32:13 -07:00
authTypePresignedV2 : { } ,
2016-08-16 20:27:14 +05:30
authTypeSigned : { } ,
2016-09-30 14:32:13 -07:00
authTypeSignedV2 : { } ,
2016-08-16 20:27:14 +05:30
authTypePostPolicy : { } ,
authTypeStreamingSigned : { } ,
2016-08-08 20:56:29 -07:00
}
// Validate if the authType is valid and supported.
func isSupportedS3AuthType ( aType authType ) bool {
2016-08-16 20:27:14 +05:30
_ , ok := supportedS3AuthTypes [ aType ]
return ok
2016-08-08 20:56:29 -07:00
}
2016-02-16 18:50:36 -08:00
// handler for validating incoming authorization headers.
2016-02-15 17:42:39 -08:00
func ( a authHandler ) ServeHTTP ( w http . ResponseWriter , r * http . Request ) {
2016-08-08 20:56:29 -07:00
aType := getRequestAuthType ( r )
if isSupportedS3AuthType ( aType ) {
// Let top level caller validate for anonymous and known signed requests.
2016-02-16 18:50:36 -08:00
a . handler . ServeHTTP ( w , r )
return
2016-08-08 20:56:29 -07: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 16:46:56 -08:00
// Validate Authorization header if its valid for JWT request.
2016-12-27 21:58:10 +05:30
if ! isHTTPRequestValid ( r ) {
2016-02-15 17:42:39 -08: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 16:46:56 -08:00
a . handler . ServeHTTP ( w , r )
return
2016-02-15 17:42:39 -08:00
}
2017-01-06 00:37:00 -08:00
writeErrorResponse ( w , ErrSignatureVersionNotSupported , r . URL )
2016-02-15 17:42:39 -08:00
}