mirror of
https://github.com/minio/minio.git
synced 2025-04-29 06:08:00 -04:00
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
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
@ -926,10 +927,11 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
|||||||
|
|
||||||
// Handle policy if it is set.
|
// Handle policy if it is set.
|
||||||
if len(policyBytes) > 0 {
|
if len(policyBytes) > 0 {
|
||||||
|
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
||||||
postPolicyForm, err := parsePostPolicyForm(string(policyBytes))
|
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,8 +431,10 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup ".trash/" folder every 10 minutes with sufficient sleep cycles.
|
// cleanup ".trash/" folder every 5m minutes with sufficient sleep cycles, between each
|
||||||
deletedObjectsCleanupInterval, err := time.ParseDuration(env.Get(envMinioDeleteCleanupInterval, "10m"))
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bcicen/jstream"
|
"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
|
// 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
|
// Go stdlib doesn't support parsing JSON with duplicate
|
||||||
// keys, so we need to use this technique to merge the
|
// keys, so we need to use this technique to merge the
|
||||||
// keys.
|
// keys.
|
||||||
func sanitizePolicy(policy string) (io.Reader, error) {
|
func sanitizePolicy(r io.Reader) (io.Reader, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
e := json.NewEncoder(&buf)
|
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() {
|
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()
|
return &buf, d.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePostPolicyForm - Parse JSON policy string into typed PostPolicyForm structure.
|
// parsePostPolicyForm - Parse JSON policy string into typed PostPolicyForm structure.
|
||||||
func parsePostPolicyForm(policy string) (PostPolicyForm, error) {
|
func parsePostPolicyForm(r io.Reader) (PostPolicyForm, error) {
|
||||||
preader, err := sanitizePolicy(policy)
|
reader, err := sanitizePolicy(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PostPolicyForm{}, err
|
return PostPolicyForm{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d := json.NewDecoder(preader)
|
d := json.NewDecoder(reader)
|
||||||
|
|
||||||
// Convert po into interfaces and
|
// Convert po into interfaces and
|
||||||
// perform strict type conversion using reflection.
|
// perform strict type conversion using reflection.
|
||||||
|
@ -17,9 +17,11 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
minio "github.com/minio/minio-go/v7"
|
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"]]`,
|
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,
|
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"]]}`,
|
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.
|
// no duplicates, shall be parsed properly.
|
||||||
{
|
{
|
||||||
@ -55,8 +66,7 @@ func TestParsePostPolicyForm(t *testing.T) {
|
|||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
testCase := testCase
|
testCase := testCase
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
_, err := parsePostPolicyForm(testCase.policy)
|
_, err := parsePostPolicyForm(strings.NewReader(testCase.policy))
|
||||||
fmt.Println(err)
|
|
||||||
if testCase.success && err != nil {
|
if testCase.success && err != nil {
|
||||||
t.Errorf("Expected success but failed with %s", err)
|
t.Errorf("Expected success but failed with %s", err)
|
||||||
}
|
}
|
||||||
@ -136,7 +146,7 @@ func TestPostPolicyForm(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
postPolicyForm, err := parsePostPolicyForm(string(policyBytes))
|
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user