mirror of https://github.com/minio/minio.git
fix: reject duplicate keys in PostPolicyJSON document (#11902)
fixes #11894
This commit is contained in:
parent
b383522743
commit
90d8ec6310
|
@ -17,6 +17,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
|
@ -926,10 +927,11 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||
|
||||
// Handle policy if it is set.
|
||||
if len(policyBytes) > 0 {
|
||||
|
||||
postPolicyForm, err := parsePostPolicyForm(string(policyBytes))
|
||||
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPostPolicyConditionInvalidFormat), r.URL, guessIsBrowserReq(r))
|
||||
errAPI := errorCodes.ToAPIErr(ErrPostPolicyConditionInvalidFormat)
|
||||
errAPI.Description = fmt.Sprintf("%s '(%s)'", errAPI.Description, err)
|
||||
writeErrorResponse(ctx, w, errAPI, r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -431,8 +431,10 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||
}
|
||||
}
|
||||
|
||||
// cleanup ".trash/" folder every 10 minutes with sufficient sleep cycles.
|
||||
deletedObjectsCleanupInterval, err := time.ParseDuration(env.Get(envMinioDeleteCleanupInterval, "10m"))
|
||||
// cleanup ".trash/" folder every 5m minutes with sufficient sleep cycles, between each
|
||||
// deletes a dynamic sleeper is used with a factor of 10 ratio with max delay between
|
||||
// deletes to be 2 seconds.
|
||||
deletedObjectsCleanupInterval, err := time.ParseDuration(env.Get(envMinioDeleteCleanupInterval, "5m"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bcicen/jstream"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
)
|
||||
|
||||
// startWithConds - map which indicates if a given condition supports starts-with policy operator
|
||||
|
@ -121,24 +122,37 @@ type PostPolicyForm struct {
|
|||
// Go stdlib doesn't support parsing JSON with duplicate
|
||||
// keys, so we need to use this technique to merge the
|
||||
// keys.
|
||||
func sanitizePolicy(policy string) (io.Reader, error) {
|
||||
func sanitizePolicy(r io.Reader) (io.Reader, error) {
|
||||
var buf bytes.Buffer
|
||||
e := json.NewEncoder(&buf)
|
||||
d := jstream.NewDecoder(strings.NewReader(policy), 0)
|
||||
d := jstream.NewDecoder(r, 0).ObjectAsKVS()
|
||||
sset := set.NewStringSet()
|
||||
for mv := range d.Stream() {
|
||||
e.Encode(mv.Value)
|
||||
var kvs jstream.KVS
|
||||
if mv.ValueType == jstream.Object {
|
||||
// This is a JSON object type (that preserves key order)
|
||||
kvs = mv.Value.(jstream.KVS)
|
||||
for _, kv := range kvs {
|
||||
if sset.Contains(kv.Key) {
|
||||
// Reject duplicate conditions or expiration.
|
||||
return nil, fmt.Errorf("input policy has multiple %s, please fix your client code", kv.Key)
|
||||
}
|
||||
sset.Add(kv.Key)
|
||||
}
|
||||
e.Encode(kvs)
|
||||
}
|
||||
}
|
||||
return &buf, d.Err()
|
||||
}
|
||||
|
||||
// parsePostPolicyForm - Parse JSON policy string into typed PostPolicyForm structure.
|
||||
func parsePostPolicyForm(policy string) (PostPolicyForm, error) {
|
||||
preader, err := sanitizePolicy(policy)
|
||||
func parsePostPolicyForm(r io.Reader) (PostPolicyForm, error) {
|
||||
reader, err := sanitizePolicy(r)
|
||||
if err != nil {
|
||||
return PostPolicyForm{}, err
|
||||
}
|
||||
|
||||
d := json.NewDecoder(preader)
|
||||
d := json.NewDecoder(reader)
|
||||
|
||||
// Convert po into interfaces and
|
||||
// perform strict type conversion using reflection.
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
|
@ -40,10 +42,19 @@ func TestParsePostPolicyForm(t *testing.T) {
|
|||
policy: `{"conditions":[["eq","$bucket","asdf"],["eq","$key","hello.txt"]],"conditions":[["eq","$success_action_status","201"],["eq","$Content-Type","plain/text"],["eq","$success_action_status","201"],["eq","$x-amz-algorithm","AWS4-HMAC-SHA256"],["eq","$x-amz-credential","Q3AM3UQ867SPQQA43P2F/20210315/us-east-1/s3/aws4_request"],["eq","$x-amz-date","20210315T091621Z"]]`,
|
||||
success: false,
|
||||
},
|
||||
// duplicate conditions, shall be parsed properly
|
||||
// duplicate 'expiration' reject
|
||||
{
|
||||
policy: `{"expiration":"2021-03-22T09:16:21.310Z","expiration":"2021-03-22T09:16:21.310Z","conditions":[["eq","$bucket","evil"],["eq","$key","hello.txt"],["eq","$success_action_status","201"],["eq","$Content-Type","plain/text"],["eq","$success_action_status","201"],["eq","$x-amz-algorithm","AWS4-HMAC-SHA256"],["eq","$x-amz-credential","Q3AM3UQ867SPQQA43P2F/20210315/us-east-1/s3/aws4_request"],["eq","$x-amz-date","20210315T091621Z"]]}`,
|
||||
},
|
||||
// duplicate '$bucket' reject
|
||||
{
|
||||
policy: `{"expiration":"2021-03-22T09:16:21.310Z","conditions":[["eq","$bucket","good"],["eq","$key","hello.txt"]],"conditions":[["eq","$bucket","evil"],["eq","$key","hello.txt"],["eq","$success_action_status","201"],["eq","$Content-Type","plain/text"],["eq","$success_action_status","201"],["eq","$x-amz-algorithm","AWS4-HMAC-SHA256"],["eq","$x-amz-credential","Q3AM3UQ867SPQQA43P2F/20210315/us-east-1/s3/aws4_request"],["eq","$x-amz-date","20210315T091621Z"]]}`,
|
||||
success: false,
|
||||
},
|
||||
// duplicate conditions, reject
|
||||
{
|
||||
policy: `{"expiration":"2021-03-22T09:16:21.310Z","conditions":[["eq","$bucket","asdf"],["eq","$key","hello.txt"]],"conditions":[["eq","$success_action_status","201"],["eq","$Content-Type","plain/text"],["eq","$success_action_status","201"],["eq","$x-amz-algorithm","AWS4-HMAC-SHA256"],["eq","$x-amz-credential","Q3AM3UQ867SPQQA43P2F/20210315/us-east-1/s3/aws4_request"],["eq","$x-amz-date","20210315T091621Z"]]}`,
|
||||
success: true,
|
||||
success: false,
|
||||
},
|
||||
// no duplicates, shall be parsed properly.
|
||||
{
|
||||
|
@ -55,8 +66,7 @@ func TestParsePostPolicyForm(t *testing.T) {
|
|||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run("", func(t *testing.T) {
|
||||
_, err := parsePostPolicyForm(testCase.policy)
|
||||
fmt.Println(err)
|
||||
_, err := parsePostPolicyForm(strings.NewReader(testCase.policy))
|
||||
if testCase.success && err != nil {
|
||||
t.Errorf("Expected success but failed with %s", err)
|
||||
}
|
||||
|
@ -136,7 +146,7 @@ func TestPostPolicyForm(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
postPolicyForm, err := parsePostPolicyForm(string(policyBytes))
|
||||
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue