mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
fix: missing user policy enforcement in PostPolicyHandler (#11682)
This commit is contained in:
parent
c6a120df0e
commit
039f59b552
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user