2021-04-18 15:41:13 -04:00
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2015-10-04 19:31:07 -04:00
2016-08-18 19:23:42 -04:00
package cmd
2015-10-02 02:51:17 -04:00
import (
2021-03-21 01:16:30 -04:00
"bytes"
2015-10-02 02:51:17 -04:00
"encoding/json"
2016-11-30 21:30:59 -05:00
"errors"
2015-10-02 02:51:17 -04:00
"fmt"
2021-03-21 01:16:30 -04:00
"io"
2016-12-02 20:00:33 -05:00
"net/http"
2015-10-02 02:51:17 -04:00
"reflect"
2016-11-30 21:30:59 -05:00
"strconv"
2016-02-15 20:42:39 -05:00
"strings"
2015-10-02 02:51:17 -04:00
"time"
2021-03-21 01:16:30 -04:00
"github.com/bcicen/jstream"
2023-09-25 15:35:25 -04:00
"github.com/minio/minio-go/v7/pkg/encrypt"
2021-03-25 16:57:57 -04:00
"github.com/minio/minio-go/v7/pkg/set"
2023-09-25 15:35:25 -04:00
xhttp "github.com/minio/minio/internal/http"
2015-10-02 02:51:17 -04:00
)
2017-01-18 15:24:34 -05:00
// startWithConds - map which indicates if a given condition supports starts-with policy operator
var startsWithConds = map [ string ] bool {
2018-12-28 17:04:39 -05:00
"$acl" : true ,
"$bucket" : false ,
"$cache-control" : true ,
"$content-type" : true ,
"$content-disposition" : true ,
"$content-encoding" : true ,
"$expires" : true ,
"$key" : true ,
2017-01-18 15:24:34 -05:00
"$success_action_redirect" : true ,
"$redirect" : true ,
2021-07-13 16:33:03 -04:00
"$success_action_status" : true ,
2017-01-18 15:24:34 -05:00
"$x-amz-algorithm" : false ,
"$x-amz-credential" : false ,
"$x-amz-date" : false ,
}
2023-09-25 15:35:25 -04:00
var postPolicyIgnoreKeys = map [ string ] bool {
"Policy" : true ,
xhttp . AmzSignature : true ,
xhttp . ContentEncoding : true ,
http . CanonicalHeaderKey ( xhttp . AmzChecksumAlgo ) : true ,
http . CanonicalHeaderKey ( xhttp . AmzChecksumCRC32 ) : true ,
http . CanonicalHeaderKey ( xhttp . AmzChecksumCRC32C ) : true ,
http . CanonicalHeaderKey ( xhttp . AmzChecksumSHA1 ) : true ,
http . CanonicalHeaderKey ( xhttp . AmzChecksumSHA256 ) : true ,
http . CanonicalHeaderKey ( xhttp . AmzChecksumMode ) : true ,
}
2017-01-18 15:24:34 -05:00
// Add policy conditionals.
const (
policyCondEqual = "eq"
policyCondStartsWith = "starts-with"
policyCondContentLength = "content-length-range"
)
2015-10-02 02:51:17 -04:00
// toString - Safely convert interface to string without causing panic.
func toString ( val interface { } ) string {
switch v := val . ( type ) {
case string :
return v
2018-08-06 13:26:40 -04:00
default :
return ""
2015-10-02 02:51:17 -04:00
}
}
2016-12-02 20:00:33 -05:00
// toLowerString - safely convert interface to lower string
func toLowerString ( val interface { } ) string {
return strings . ToLower ( toString ( val ) )
}
2015-10-02 02:51:17 -04:00
// toInteger _ Safely convert interface to integer without causing panic.
2016-11-30 21:30:59 -05:00
func toInteger ( val interface { } ) ( int64 , error ) {
2015-10-02 02:51:17 -04:00
switch v := val . ( type ) {
2016-11-21 07:15:26 -05:00
case float64 :
2016-11-30 21:30:59 -05:00
return int64 ( v ) , nil
2016-11-21 07:15:26 -05:00
case int64 :
2016-11-30 21:30:59 -05:00
return v , nil
2016-11-21 07:15:26 -05:00
case int :
2016-11-30 21:30:59 -05:00
return int64 ( v ) , nil
case string :
i , err := strconv . Atoi ( v )
return int64 ( i ) , err
2018-08-06 13:26:40 -04:00
default :
return 0 , errors . New ( "Invalid number format" )
2015-10-02 02:51:17 -04:00
}
}
// isString - Safely check if val is of type string without causing panic.
func isString ( val interface { } ) bool {
2018-08-06 13:26:40 -04:00
_ , ok := val . ( string )
return ok
2015-10-02 02:51:17 -04:00
}
2016-10-25 02:47:03 -04:00
// ContentLengthRange - policy content-length-range field.
type contentLengthRange struct {
2016-11-21 07:15:26 -05:00
Min int64
Max int64
2016-10-25 02:47:03 -04:00
Valid bool // If content-length-range was part of policy
}
2015-10-02 02:51:17 -04:00
// PostPolicyForm provides strict static type conversion and validation for Amazon S3's POST policy JSON string.
type PostPolicyForm struct {
Expiration time . Time // Expiration date and time of the POST policy.
Conditions struct { // Conditional policy structure.
2019-09-22 17:20:49 -04:00
Policies [ ] struct {
2015-10-02 02:51:17 -04:00
Operator string
2019-09-22 17:20:49 -04:00
Key string
2015-10-02 02:51:17 -04:00
Value string
}
2016-10-25 02:47:03 -04:00
ContentLengthRange contentLengthRange
2015-10-02 02:51:17 -04:00
}
}
2021-03-21 01:16:30 -04:00
// implemented to ensure that duplicate keys in JSON
// are merged together into a single JSON key, also
// to remove any extraneous JSON bodies.
//
// Go stdlib doesn't support parsing JSON with duplicate
// keys, so we need to use this technique to merge the
// keys.
2021-03-25 16:57:57 -04:00
func sanitizePolicy ( r io . Reader ) ( io . Reader , error ) {
2021-03-21 01:16:30 -04:00
var buf bytes . Buffer
e := json . NewEncoder ( & buf )
2021-03-25 16:57:57 -04:00
d := jstream . NewDecoder ( r , 0 ) . ObjectAsKVS ( )
sset := set . NewStringSet ( )
2021-03-21 01:16:30 -04:00
for mv := range d . Stream ( ) {
2021-03-25 16:57:57 -04:00
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 )
}
2021-03-21 01:16:30 -04:00
}
return & buf , d . Err ( )
}
2019-07-10 01:18:43 -04:00
// parsePostPolicyForm - Parse JSON policy string into typed PostPolicyForm structure.
2021-03-25 16:57:57 -04:00
func parsePostPolicyForm ( r io . Reader ) ( PostPolicyForm , error ) {
reader , err := sanitizePolicy ( r )
2021-03-21 01:16:30 -04:00
if err != nil {
return PostPolicyForm { } , err
}
2021-03-25 16:57:57 -04:00
d := json . NewDecoder ( reader )
2021-03-21 01:16:30 -04:00
2015-10-02 02:51:17 -04:00
// Convert po into interfaces and
// perform strict type conversion using reflection.
var rawPolicy struct {
Expiration string ` json:"expiration" `
Conditions [ ] interface { } ` json:"conditions" `
}
2021-03-21 01:16:30 -04:00
d . DisallowUnknownFields ( )
if err := d . Decode ( & rawPolicy ) ; err != nil {
return PostPolicyForm { } , err
2015-10-02 02:51:17 -04:00
}
parsedPolicy := PostPolicyForm { }
// Parse expiry time.
2016-04-29 17:24:10 -04:00
parsedPolicy . Expiration , err = time . Parse ( time . RFC3339Nano , rawPolicy . Expiration )
if err != nil {
2021-03-21 01:16:30 -04:00
return PostPolicyForm { } , err
2015-10-02 02:51:17 -04:00
}
// Parse conditions.
for _ , val := range rawPolicy . Conditions {
switch condt := val . ( type ) {
case map [ string ] interface { } : // Handle key:value map types.
for k , v := range condt {
if ! isString ( v ) { // Pre-check value type.
// All values must be of type string.
2016-11-15 21:14:23 -05:00
return parsedPolicy , fmt . Errorf ( "Unknown type %s of conditional field value %s found in POST policy form" , reflect . TypeOf ( condt ) . String ( ) , condt )
2015-10-02 02:51:17 -04:00
}
// {"acl": "public-read" } is an alternate way to indicate - [ "eq", "$acl", "public-read" ]
// In this case we will just collapse this into "eq" for all use cases.
2019-09-22 17:20:49 -04:00
parsedPolicy . Conditions . Policies = append ( parsedPolicy . Conditions . Policies , struct {
2015-10-02 02:51:17 -04:00
Operator string
2019-09-22 17:20:49 -04:00
Key string
2015-10-02 02:51:17 -04:00
Value string
} {
2019-09-22 17:20:49 -04:00
policyCondEqual , "$" + strings . ToLower ( k ) , toString ( v ) ,
} )
2015-10-02 02:51:17 -04:00
}
case [ ] interface { } : // Handle array types.
if len ( condt ) != 3 { // Return error if we have insufficient elements.
2016-11-15 21:14:23 -05:00
return parsedPolicy , fmt . Errorf ( "Malformed conditional fields %s of type %s found in POST policy form" , condt , reflect . TypeOf ( condt ) . String ( ) )
2015-10-02 02:51:17 -04:00
}
2016-12-02 20:00:33 -05:00
switch toLowerString ( condt [ 0 ] ) {
2017-01-18 15:24:34 -05:00
case policyCondEqual , policyCondStartsWith :
2015-10-02 02:51:17 -04:00
for _ , v := range condt { // Pre-check all values for type.
if ! isString ( v ) {
// All values must be of type string.
2016-11-15 21:14:23 -05:00
return parsedPolicy , fmt . Errorf ( "Unknown type %s of conditional field value %s found in POST policy form" , reflect . TypeOf ( condt ) . String ( ) , condt )
2015-10-02 02:51:17 -04:00
}
}
2016-12-02 20:00:33 -05:00
operator , matchType , value := toLowerString ( condt [ 0 ] ) , toLowerString ( condt [ 1 ] ) , toString ( condt [ 2 ] )
2019-09-22 17:20:49 -04:00
if ! strings . HasPrefix ( matchType , "$" ) {
return parsedPolicy , fmt . Errorf ( "Invalid according to Policy: Policy Condition failed: [%s, %s, %s]" , operator , matchType , value )
}
parsedPolicy . Conditions . Policies = append ( parsedPolicy . Conditions . Policies , struct {
2015-10-02 02:51:17 -04:00
Operator string
2019-09-22 17:20:49 -04:00
Key string
2015-10-02 02:51:17 -04:00
Value string
} {
2019-09-22 17:20:49 -04:00
operator , matchType , value ,
} )
2017-01-18 15:24:34 -05:00
case policyCondContentLength :
2016-11-30 21:30:59 -05:00
min , err := toInteger ( condt [ 1 ] )
if err != nil {
return parsedPolicy , err
}
max , err := toInteger ( condt [ 2 ] )
if err != nil {
return parsedPolicy , err
}
2016-11-21 07:15:26 -05:00
parsedPolicy . Conditions . ContentLengthRange = contentLengthRange {
2016-11-30 21:30:59 -05:00
Min : min ,
Max : max ,
2016-11-21 07:15:26 -05:00
Valid : true ,
}
2015-10-02 02:51:17 -04:00
default :
// Condition should be valid.
2016-11-15 21:14:23 -05:00
return parsedPolicy , fmt . Errorf ( "Unknown type %s of conditional field value %s found in POST policy form" ,
reflect . TypeOf ( condt ) . String ( ) , condt )
2015-10-02 02:51:17 -04:00
}
default :
2016-11-15 21:14:23 -05:00
return parsedPolicy , fmt . Errorf ( "Unknown field %s of type %s found in POST policy form" ,
condt , reflect . TypeOf ( condt ) . String ( ) )
2015-10-02 02:51:17 -04:00
}
}
return parsedPolicy , nil
}
2016-02-15 20:42:39 -05:00
2024-01-18 02:03:17 -05:00
// checkPolicyCond returns a boolean to indicate if a condition is satisfied according
2016-12-02 20:00:33 -05:00
// to the passed operator
func checkPolicyCond ( op string , input1 , input2 string ) bool {
switch op {
2017-01-18 15:24:34 -05:00
case policyCondEqual :
2016-12-02 20:00:33 -05:00
return input1 == input2
2017-01-18 15:24:34 -05:00
case policyCondStartsWith :
2016-12-02 20:00:33 -05:00
return strings . HasPrefix ( input1 , input2 )
}
return false
}
2016-03-12 19:08:15 -05:00
// checkPostPolicy - apply policy conditions and validate input values.
2016-12-02 20:00:33 -05:00
// (http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html)
2019-03-05 15:10:47 -05:00
func checkPostPolicy ( formValues http . Header , postPolicyForm PostPolicyForm ) error {
2016-12-02 20:00:33 -05:00
// Check if policy document expiry date is still not reached
2017-03-18 14:28:41 -04:00
if ! postPolicyForm . Expiration . After ( UTCNow ( ) ) {
2019-03-05 15:10:47 -05:00
return fmt . Errorf ( "Invalid according to Policy: Policy expired" )
2016-02-15 20:42:39 -05:00
}
2023-09-25 15:35:25 -04:00
// check all formValues appear in postPolicyForm or return error. #https://github.com/minio/minio/issues/17391
checkHeader := map [ string ] [ ] string { }
ignoreKeys := map [ string ] bool { }
for key , value := range formValues {
switch {
case ignoreKeys [ key ] , postPolicyIgnoreKeys [ key ] , strings . HasPrefix ( key , encrypt . SseGenericHeader ) :
continue
case strings . HasPrefix ( key , "X-Amz-Ignore-" ) :
ignoreKey := strings . Replace ( key , "X-Amz-Ignore-" , "" , 1 )
ignoreKeys [ ignoreKey ] = true
// if it have already
delete ( checkHeader , ignoreKey )
default :
checkHeader [ key ] = value
}
}
2019-04-02 02:45:32 -04:00
// map to store the metadata
metaMap := make ( map [ string ] string )
2019-09-22 17:20:49 -04:00
for _ , policy := range postPolicyForm . Conditions . Policies {
if strings . HasPrefix ( policy . Key , "$x-amz-meta-" ) {
formCanonicalName := http . CanonicalHeaderKey ( strings . TrimPrefix ( policy . Key , "$" ) )
metaMap [ formCanonicalName ] = policy . Value
2019-04-02 02:45:32 -04:00
}
}
// Check if any extra metadata field is passed as input
for key := range formValues {
if strings . HasPrefix ( key , "X-Amz-Meta-" ) {
if _ , ok := metaMap [ key ] ; ! ok {
return fmt . Errorf ( "Invalid according to Policy: Extra input fields: %s" , key )
}
}
}
2016-12-02 20:00:33 -05:00
// Flag to indicate if all policies conditions are satisfied
2019-10-15 21:35:41 -04:00
var condPassed bool
2016-12-02 20:00:33 -05:00
// Iterate over policy conditions and check them against received form fields
2019-09-22 17:20:49 -04:00
for _ , policy := range postPolicyForm . Conditions . Policies {
2016-12-02 20:00:33 -05:00
// Form fields names are in canonical format, convert conditions names
// to canonical for simplification purpose, so `$key` will become `Key`
2019-09-22 17:20:49 -04:00
formCanonicalName := http . CanonicalHeaderKey ( strings . TrimPrefix ( policy . Key , "$" ) )
2016-12-02 20:00:33 -05:00
// Operator for the current policy condition
2019-09-22 17:20:49 -04:00
op := policy . Operator
2023-09-25 15:35:25 -04:00
// Multiple values should not occur
if len ( checkHeader [ formCanonicalName ] ) >= 2 {
return fmt . Errorf ( "Invalid according to Policy: Policy Condition failed: [%s, %s, %s]. FormValues have multiple values: [%s]" , op , policy . Key , policy . Value , strings . Join ( checkHeader [ formCanonicalName ] , ", " ) )
}
2016-12-02 20:00:33 -05:00
// If the current policy condition is known
2019-09-22 17:20:49 -04:00
if startsWithSupported , condFound := startsWithConds [ policy . Key ] ; condFound {
2016-12-02 20:00:33 -05:00
// Check if the current condition supports starts-with operator
2017-01-18 15:24:34 -05:00
if op == policyCondStartsWith && ! startsWithSupported {
2019-03-05 15:10:47 -05:00
return fmt . Errorf ( "Invalid according to Policy: Policy Condition failed" )
2016-12-02 20:00:33 -05:00
}
// Check if current policy condition is satisfied
2019-09-22 17:20:49 -04:00
condPassed = checkPolicyCond ( op , formValues . Get ( formCanonicalName ) , policy . Value )
2019-03-05 15:10:47 -05:00
if ! condPassed {
return fmt . Errorf ( "Invalid according to Policy: Policy Condition failed" )
}
2021-11-16 12:28:29 -05:00
} else if strings . HasPrefix ( policy . Key , "$x-amz-meta-" ) || strings . HasPrefix ( policy . Key , "$x-amz-" ) {
2016-12-02 20:00:33 -05:00
// This covers all conditions X-Amz-Meta-* and X-Amz-*
2021-11-16 12:28:29 -05:00
// Check if policy condition is satisfied
condPassed = checkPolicyCond ( op , formValues . Get ( formCanonicalName ) , policy . Value )
if ! condPassed {
return fmt . Errorf ( "Invalid according to Policy: Policy Condition failed: [%s, %s, %s]" , op , policy . Key , policy . Value )
2016-12-02 20:00:33 -05:00
}
2016-02-15 20:42:39 -05:00
}
2023-09-25 15:35:25 -04:00
delete ( checkHeader , formCanonicalName )
}
2024-05-06 06:52:41 -04:00
// For SignV2 - Signature/AWSAccessKeyId field will be ignored.
2024-04-19 12:45:54 -04:00
if _ , ok := formValues [ xhttp . AmzSignatureV2 ] ; ok {
delete ( checkHeader , xhttp . AmzSignatureV2 )
2024-05-06 06:52:41 -04:00
for k := range checkHeader {
// case-insensitivity for AWSAccessKeyId
if strings . EqualFold ( k , xhttp . AmzAccessKeyID ) {
delete ( checkHeader , k )
break
}
}
2024-04-19 12:45:54 -04:00
}
2023-09-25 15:35:25 -04:00
if len ( checkHeader ) != 0 {
logKeys := make ( [ ] string , 0 , len ( checkHeader ) )
for key := range checkHeader {
logKeys = append ( logKeys , key )
}
2024-07-10 10:18:44 -04:00
return fmt . Errorf ( "Each form field that you specify in a form must appear in the list of policy conditions. %q not specified in the policy." , strings . Join ( logKeys , ", " ) )
2016-02-15 20:42:39 -05:00
}
2016-12-02 20:00:33 -05:00
2019-03-05 15:10:47 -05:00
return nil
2016-02-15 20:42:39 -05:00
}