fix: missing user policy enforcement in PostPolicyHandler (#11682)

This commit is contained in:
Harshavardhana 2021-03-03 08:47:08 -08:00 committed by GitHub
parent c6a120df0e
commit 039f59b552
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 63 additions and 24 deletions

View File

@ -185,12 +185,12 @@ 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, getSessionToken(r)) claims, _ := getClaimsFromToken(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, token string) (map[string]interface{}, error) { func getClaimsFromToken(token string) (map[string]interface{}, error) {
claims := xjwt.NewMapClaims() claims := xjwt.NewMapClaims()
if token == "" { if token == "" {
return claims.Map(), nil return claims.Map(), nil
@ -237,7 +237,7 @@ func getClaimsFromToken(r *http.Request, token string) (map[string]interface{},
if err != nil { if err != nil {
// Base64 decoding fails, we should log to indicate // Base64 decoding fails, we should log to indicate
// something is malforming the request sent by client. // something is malforming the request sent by client.
logger.LogIf(r.Context(), err, logger.Application) logger.LogIf(GlobalContext, err, logger.Application)
return nil, errAuthentication return nil, errAuthentication
} }
claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes) claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes)
@ -258,7 +258,7 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
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, token) claims, err := getClaimsFromToken(token)
if err != nil { if err != nil {
return nil, toAPIErrorCode(r.Context(), err) return nil, toAPIErrorCode(r.Context(), err)
} }

View File

@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"crypto/subtle"
"encoding/base64" "encoding/base64"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
@ -25,7 +26,6 @@ import (
"net/textproto" "net/textproto"
"net/url" "net/url"
"path" "path"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -337,7 +337,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, getSessionToken(r)) claims, _ := getClaimsFromToken(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
@ -797,13 +797,15 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
return return
} }
resource, err := getResource(r.URL.Path, r.Host, globalDomainNames) resource, err := getResource(r.URL.Path, r.Host, globalDomainNames)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r))
return return
} }
// Make sure that the URL does not contain object name. // Make sure that the URL does not contain object name.
if bucket != filepath.Clean(resource[1:]) { if bucket != path.Clean(resource[1:]) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
return return
} }
@ -846,7 +848,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
defer fileBody.Close() defer fileBody.Close()
formValues.Set("Bucket", bucket) formValues.Set("Bucket", bucket)
if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") { if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") {
// S3 feature to replace ${filename} found in Key form field // S3 feature to replace ${filename} found in Key form field
// by the filename attribute passed in multipart // by the filename attribute passed in multipart
@ -866,12 +867,51 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
} }
// Verify policy signature. // Verify policy signature.
errCode := doesPolicySignatureMatch(formValues) cred, errCode := doesPolicySignatureMatch(formValues)
if errCode != ErrNone { if errCode != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
return return
} }
// Once signature is validated, check if the user has
// explicit permissions for the user.
{
token := formValues.Get(xhttp.AmzSecurityToken)
if token != "" && cred.AccessKey == "" {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoAccessKey), r.URL, guessIsBrowserReq(r))
return
}
if cred.IsServiceAccount() && token == "" {
token = cred.SessionToken
}
if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidToken), r.URL, guessIsBrowserReq(r))
return
}
// Extract claims if any.
claims, err := getClaimsFromToken(token)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey,
Action: iampolicy.PutObjectAction,
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
BucketName: bucket,
ObjectName: object,
IsOwner: globalActiveCred.AccessKey == cred.AccessKey,
Claims: claims,
}) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL, guessIsBrowserReq(r))
return
}
}
policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy")) policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy"))
if err != nil { if err != nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))

View File

@ -75,20 +75,18 @@ const (
// AWS S3 Signature V2 calculation rule is give here: // AWS S3 Signature V2 calculation rule is give here:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationStringToSign // http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationStringToSign
func doesPolicySignatureV2Match(formValues http.Header) (auth.Credentials, APIErrorCode) {
func doesPolicySignatureV2Match(formValues http.Header) APIErrorCode {
cred := globalActiveCred
accessKey := formValues.Get(xhttp.AmzAccessKeyID) accessKey := formValues.Get(xhttp.AmzAccessKeyID)
cred, _, s3Err := checkKeyValid(accessKey) cred, _, s3Err := checkKeyValid(accessKey)
if s3Err != ErrNone { if s3Err != ErrNone {
return s3Err return cred, s3Err
} }
policy := formValues.Get("Policy") policy := formValues.Get("Policy")
signature := formValues.Get(xhttp.AmzSignatureV2) signature := formValues.Get(xhttp.AmzSignatureV2)
if !compareSignatureV2(signature, calculateSignatureV2(policy, cred.SecretKey)) { if !compareSignatureV2(signature, calculateSignatureV2(policy, cred.SecretKey)) {
return ErrSignatureDoesNotMatch return cred, ErrSignatureDoesNotMatch
} }
return ErrNone return cred, ErrNone
} }
// Escape encodedQuery string into unescaped list of query params, returns error // Escape encodedQuery string into unescaped list of query params, returns error

View File

@ -265,7 +265,7 @@ func TestDoesPolicySignatureV2Match(t *testing.T) {
formValues.Set("Awsaccesskeyid", test.accessKey) formValues.Set("Awsaccesskeyid", test.accessKey)
formValues.Set("Signature", test.signature) formValues.Set("Signature", test.signature)
formValues.Set("Policy", test.policy) formValues.Set("Policy", test.policy)
errCode := doesPolicySignatureV2Match(formValues) _, errCode := doesPolicySignatureV2Match(formValues)
if errCode != test.errCode { if errCode != test.errCode {
t.Fatalf("(%d) expected to get %s, instead got %s", i+1, niceError(test.errCode), niceError(errCode)) t.Fatalf("(%d) expected to get %s, instead got %s", i+1, niceError(test.errCode), niceError(errCode))
} }

View File

@ -39,6 +39,7 @@ import (
"github.com/minio/minio-go/v7/pkg/s3utils" "github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/pkg/auth"
) )
// AWS Signature Version '4' constants. // AWS Signature Version '4' constants.
@ -149,7 +150,7 @@ func getSignature(signingKey []byte, stringToSign string) string {
} }
// Check to see if Policy is signed correctly. // Check to see if Policy is signed correctly.
func doesPolicySignatureMatch(formValues http.Header) APIErrorCode { func doesPolicySignatureMatch(formValues http.Header) (auth.Credentials, APIErrorCode) {
// For SignV2 - Signature field will be valid // For SignV2 - Signature field will be valid
if _, ok := formValues["Signature"]; ok { if _, ok := formValues["Signature"]; ok {
return doesPolicySignatureV2Match(formValues) return doesPolicySignatureV2Match(formValues)
@ -169,19 +170,19 @@ func compareSignatureV4(sig1, sig2 string) bool {
// doesPolicySignatureMatch - Verify query headers with post policy // doesPolicySignatureMatch - Verify query headers with post policy
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html // - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
// returns ErrNone if the signature matches. // returns ErrNone if the signature matches.
func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode { func doesPolicySignatureV4Match(formValues http.Header) (auth.Credentials, APIErrorCode) {
// Server region. // Server region.
region := globalServerRegion region := globalServerRegion
// Parse credential tag. // Parse credential tag.
credHeader, s3Err := parseCredentialHeader("Credential="+formValues.Get(xhttp.AmzCredential), region, serviceS3) credHeader, s3Err := parseCredentialHeader("Credential="+formValues.Get(xhttp.AmzCredential), region, serviceS3)
if s3Err != ErrNone { if s3Err != ErrNone {
return s3Err return auth.Credentials{}, s3Err
} }
cred, _, s3Err := checkKeyValid(credHeader.accessKey) cred, _, s3Err := checkKeyValid(credHeader.accessKey)
if s3Err != ErrNone { if s3Err != ErrNone {
return s3Err return cred, s3Err
} }
// Get signing key. // Get signing key.
@ -192,11 +193,11 @@ func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode {
// Verify signature. // Verify signature.
if !compareSignatureV4(newSignature, formValues.Get(xhttp.AmzSignature)) { if !compareSignatureV4(newSignature, formValues.Get(xhttp.AmzSignature)) {
return ErrSignatureDoesNotMatch return cred, ErrSignatureDoesNotMatch
} }
// Success. // Success.
return ErrNone return cred, ErrNone
} }
// doesPresignedSignatureMatch - Verify query headers with presigned signature // doesPresignedSignatureMatch - Verify query headers with presigned signature

View File

@ -84,7 +84,7 @@ func TestDoesPolicySignatureMatch(t *testing.T) {
// Run each test case individually. // Run each test case individually.
for i, testCase := range testCases { for i, testCase := range testCases {
code := doesPolicySignatureMatch(testCase.form) _, code := doesPolicySignatureMatch(testCase.form)
if code != testCase.expected { if code != testCase.expected {
t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(code)) t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(code))
} }