From d8af244708e7fce342baebe87ff636fb69839e6c Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 1 Apr 2020 00:04:25 -0700 Subject: [PATCH] Add numeric/date policy conditions (#9233) add new policy conditions - NumericEquals - NumericNotEquals - NumericLessThan - NumericLessThanEquals - NumericGreaterThan - NumericGreaterThanEquals - DateEquals - DateNotEquals - DateLessThan - DateLessThanEquals - DateGreaterThan - DateGreaterThanEquals --- Makefile | 1 + buildscripts/verify-healing.sh | 73 ++++---- cmd/auth-handler.go | 2 +- cmd/iam.go | 1 - cmd/object-lock.go | 5 +- cmd/policy.go | 62 +++++-- pkg/bucket/policy/action.go | 34 ++-- .../policy/condition/binaryequalsfunc.go | 4 + pkg/bucket/policy/condition/boolfunc.go | 4 + pkg/bucket/policy/condition/dateequalsfunc.go | 164 +++++++++++++++++ .../policy/condition/dategreaterthanfunc.go | 153 ++++++++++++++++ .../policy/condition/datelessthanfunc.go | 153 ++++++++++++++++ pkg/bucket/policy/condition/func.go | 12 ++ pkg/bucket/policy/condition/func_test.go | 9 +- pkg/bucket/policy/condition/key.go | 27 +++ pkg/bucket/policy/condition/name.go | 24 +++ .../policy/condition/numericequalsfunc.go | 168 ++++++++++++++++++ .../policy/condition/numericgreaterfunc.go | 153 ++++++++++++++++ .../policy/condition/numericlessfunc.go | 153 ++++++++++++++++ .../policy/condition/stringequalsfunc.go | 4 + pkg/iam/policy/action.go | 36 ++-- 21 files changed, 1152 insertions(+), 90 deletions(-) create mode 100644 pkg/bucket/policy/condition/dateequalsfunc.go create mode 100644 pkg/bucket/policy/condition/dategreaterthanfunc.go create mode 100644 pkg/bucket/policy/condition/datelessthanfunc.go create mode 100644 pkg/bucket/policy/condition/numericequalsfunc.go create mode 100644 pkg/bucket/policy/condition/numericgreaterfunc.go create mode 100644 pkg/bucket/policy/condition/numericlessfunc.go diff --git a/Makefile b/Makefile index 7f95d336a..d03435899 100644 --- a/Makefile +++ b/Makefile @@ -101,3 +101,4 @@ clean: @rm -rvf minio @rm -rvf build @rm -rvf release + @rm -rvf .verify* diff --git a/buildscripts/verify-healing.sh b/buildscripts/verify-healing.sh index 4bd477a04..5fce83635 100755 --- a/buildscripts/verify-healing.sh +++ b/buildscripts/verify-healing.sh @@ -30,33 +30,49 @@ MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server ) function start_minio_3_node() { declare -a minio_pids + declare -a ARGS export MINIO_ACCESS_KEY=minio export MINIO_SECRET_KEY=minio123 + start_port=$(shuf -i 10000-65000 -n 1) for i in $(seq 1 3); do - ARGS+=("http://127.0.0.1:$[8000+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[8000+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[8000+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[8000+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[8000+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[8000+$i]${WORK_DIR}/$i/6/") + ARGS+=("http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/") done - "${MINIO[@]}" --address ":8001" ${ARGS[@]} > "${WORK_DIR}/dist-minio-8001.log" 2>&1 & + "${MINIO[@]}" --address ":$[$start_port+1]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server1.log" 2>&1 & minio_pids[0]=$! + disown "${minio_pids[0]}" - "${MINIO[@]}" --address ":8002" ${ARGS[@]} > "${WORK_DIR}/dist-minio-8002.log" 2>&1 & + "${MINIO[@]}" --address ":$[$start_port+2]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server2.log" 2>&1 & minio_pids[1]=$! + disown "${minio_pids[1]}" - "${MINIO[@]}" --address ":8003" ${ARGS[@]} > "${WORK_DIR}/dist-minio-8003.log" 2>&1 & + "${MINIO[@]}" --address ":$[$start_port+3]" ${ARGS[@]} > "${WORK_DIR}/dist-minio-server3.log" 2>&1 & minio_pids[2]=$! + disown "${minio_pids[2]}" sleep "$1" - echo "${minio_pids[@]}" + for pid in "${minio_pids[@]}"; do + if ! kill "$pid"; then + for i in $(seq 1 3); do + echo "server$i log:" + cat "${WORK_DIR}/dist-minio-server$i.log" + done + echo "FAILED" + purge "$WORK_DIR" + exit 1 + fi + # forcibly killing, to proceed further properly. + kill -9 "$pid" + sleep 1 # wait 1sec per pid + done } function check_online() { - for i in $(seq 1 3); do - if grep -q 'Server switching to safe mode' ${WORK_DIR}/dist-minio-$[8000+$i].log; then - echo "1" - fi - done + if grep -q 'Server switching to safe mode' ${WORK_DIR}/dist-minio-*.log; then + echo "1" + fi } function purge() @@ -75,50 +91,21 @@ function __init__() } function perform_test() { - minio_pids=( $(start_minio_3_node 60) ) - for pid in "${minio_pids[@]}"; do - if ! kill "$pid"; then - for i in $(seq 1 3); do - echo "server$i log:" - cat "${WORK_DIR}/dist-minio-$[8000+$i].log" - done - echo "FAILED" - purge "$WORK_DIR" - exit 1 - fi - # forcibly killing, to proceed further properly. - kill -9 "$pid" - done + start_minio_3_node 60 echo "Testing Distributed Erasure setup healing of drives" echo "Remove the contents of the disks belonging to '${1}' erasure set" rm -rf ${WORK_DIR}/${1}/*/ - minio_pids=( $(start_minio_3_node 60) ) - for pid in "${minio_pids[@]}"; do - if ! kill "$pid"; then - for i in $(seq 1 3); do - echo "server$i log:" - cat "${WORK_DIR}/dist-minio-$[8000+$i].log" - done - echo "FAILED" - purge "$WORK_DIR" - exit 1 - fi - # forcibly killing, to proceed further properly. - # if the previous kill is taking time. - kill -9 "$pid" - done + start_minio_3_node 60 rv=$(check_online) if [ "$rv" == "1" ]; then - for pid in "${minio_pids[@]}"; do - kill -9 "$pid" - done + pkill -9 minio for i in $(seq 1 3); do echo "server$i log:" - cat "${WORK_DIR}/dist-minio-$[8000+$i].log" + cat "${WORK_DIR}/dist-minio-server$i.log" done echo "FAILED" purge "$WORK_DIR" diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index 3efaa545d..a4578af5b 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -491,7 +491,7 @@ func isPutActionAllowed(atype authType, bucketName, objectName string, r *http.R if cred.AccessKey == "" { if globalPolicySys.IsAllowed(policy.Args{ AccountName: cred.AccessKey, - Action: policy.PutObjectAction, + Action: policy.Action(action), BucketName: bucketName, ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, diff --git a/cmd/iam.go b/cmd/iam.go index 00a3a6f11..2a2a35cf7 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1312,7 +1312,6 @@ var iamAccountWriteAccessActions = iampolicy.NewActionSet( ) var iamAccountOtherAccessActions = iampolicy.NewActionSet( - iampolicy.BypassGovernanceModeAction, iampolicy.BypassGovernanceRetentionAction, iampolicy.PutObjectRetentionAction, iampolicy.GetObjectRetentionAction, diff --git a/cmd/object-lock.go b/cmd/object-lock.go index e2af9da29..f24324b3a 100644 --- a/cmd/object-lock.go +++ b/cmd/object-lock.go @@ -19,6 +19,7 @@ package cmd import ( "bytes" "context" + "errors" "net/http" "path" @@ -251,7 +252,7 @@ func initBucketObjectLockConfig(buckets []BucketInfo, objAPI ObjectLayer) error configFile := path.Join(bucketConfigPrefix, bucket.Name, bucketObjectLockEnabledConfigFile) bucketObjLockData, err := readConfig(ctx, objAPI, configFile) if err != nil { - if err == errConfigNotFound { + if errors.Is(err, errConfigNotFound) { continue } return err @@ -266,7 +267,7 @@ func initBucketObjectLockConfig(buckets []BucketInfo, objAPI ObjectLayer) error configFile = path.Join(bucketConfigPrefix, bucket.Name, objectLockConfig) configData, err := readConfig(ctx, objAPI, configFile) if err != nil { - if err == errConfigNotFound { + if errors.Is(err, errConfigNotFound) { globalBucketObjectLockConfig.Set(bucket.Name, objectlock.Retention{}) continue } diff --git a/cmd/policy.go b/cmd/policy.go index 7a3f447a4..bba4f99c4 100644 --- a/cmd/policy.go +++ b/cmd/policy.go @@ -22,14 +22,17 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "path" + "strings" "sync" + "time" jsoniter "github.com/json-iterator/go" miniogopolicy "github.com/minio/minio-go/v6/pkg/policy" + xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/bucket/policy" - "github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/handlers" ) @@ -145,14 +148,14 @@ func NewPolicySys() *PolicySys { func getConditionValues(request *http.Request, locationConstraint string, username string, claims map[string]interface{}) map[string][]string { currTime := UTCNow() - principalType := func() string { - if username != "" { - return "User" - } - return "Anonymous" - }() + + principalType := "Anonymous" + if username != "" { + principalType = "User" + } + args := map[string][]string{ - "CurrenTime": {currTime.Format(event.AMZTimeFormat)}, + "CurrentTime": {currTime.Format(time.RFC3339)}, "EpochTime": {fmt.Sprintf("%d", currTime.Unix())}, "principaltype": {principalType}, "SecureTransport": {fmt.Sprintf("%t", request.TLS != nil)}, @@ -163,7 +166,25 @@ func getConditionValues(request *http.Request, locationConstraint string, userna "username": {username}, } - for key, values := range request.Header { + if locationConstraint != "" { + args["LocationConstraint"] = []string{locationConstraint} + } + + // TODO: support object-lock-remaining-retention-days + cloneHeader := request.Header.Clone() + + for _, objLock := range []string{ + xhttp.AmzObjectLockMode, + xhttp.AmzObjectLockLegalHold, + xhttp.AmzObjectLockRetainUntilDate, + } { + if values, ok := cloneHeader[objLock]; ok { + args[strings.TrimPrefix(objLock, "X-Amz-")] = values + } + cloneHeader.Del(objLock) + } + + for key, values := range cloneHeader { if existingValues, found := args[key]; found { args[key] = append(existingValues, values...) } else { @@ -171,7 +192,23 @@ func getConditionValues(request *http.Request, locationConstraint string, userna } } - for key, values := range request.URL.Query() { + var cloneURLValues = url.Values{} + for k, v := range request.URL.Query() { + cloneURLValues[k] = v + } + + for _, objLock := range []string{ + xhttp.AmzObjectLockMode, + xhttp.AmzObjectLockLegalHold, + xhttp.AmzObjectLockRetainUntilDate, + } { + if values, ok := cloneURLValues[objLock]; ok { + args[strings.TrimPrefix(objLock, "X-Amz-")] = values + } + cloneURLValues.Del(objLock) + } + + for key, values := range cloneURLValues { if existingValues, found := args[key]; found { args[key] = append(existingValues, values...) } else { @@ -179,10 +216,6 @@ func getConditionValues(request *http.Request, locationConstraint string, userna } } - if locationConstraint != "" { - args["LocationConstraint"] = []string{locationConstraint} - } - // JWT specific values for k, v := range claims { vStr, ok := v.(string) @@ -190,6 +223,7 @@ func getConditionValues(request *http.Request, locationConstraint string, userna args[k] = []string{vStr} } } + return args } diff --git a/pkg/bucket/policy/action.go b/pkg/bucket/policy/action.go index e6d843f80..6182decda 100644 --- a/pkg/bucket/policy/action.go +++ b/pkg/bucket/policy/action.go @@ -89,8 +89,6 @@ const ( // GetBucketLifecycleAction - GetBucketLifecycle Rest API action. GetBucketLifecycleAction = "s3:GetLifecycleConfiguration" - // BypassGovernanceModeAction - bypass governance mode for DeleteObject Rest API action. - BypassGovernanceModeAction = "s3:BypassGovernanceMode" // BypassGovernanceRetentionAction - bypass governance retention for PutObjectRetention, PutObject and DeleteObject Rest API action. BypassGovernanceRetentionAction = "s3:BypassGovernanceRetention" // PutObjectRetentionAction - PutObjectRetention Rest API action. @@ -127,7 +125,6 @@ var supportedObjectActions = map[Action]struct{}{ GetObjectAction: {}, ListMultipartUploadPartsAction: {}, PutObjectAction: {}, - BypassGovernanceModeAction: {}, BypassGovernanceRetentionAction: {}, PutObjectRetentionAction: {}, GetObjectRetentionAction: {}, @@ -172,7 +169,6 @@ var supportedActions = map[Action]struct{}{ PutObjectLegalHoldAction: {}, PutBucketObjectLockConfigurationAction: {}, GetBucketObjectLockConfigurationAction: {}, - BypassGovernanceModeAction: {}, BypassGovernanceRetentionAction: {}, GetObjectTaggingAction: {}, PutObjectTaggingAction: {}, @@ -263,13 +259,31 @@ var actionConditionKeyMap = map[Action]condition.KeySet{ condition.S3XAmzServerSideEncryptionCustomerAlgorithm, condition.S3XAmzMetadataDirective, condition.S3XAmzStorageClass, + condition.S3ObjectLockRetainUntilDate, + condition.S3ObjectLockMode, + condition.S3ObjectLockLegalHold, + }, condition.CommonKeys...)...), + PutObjectRetentionAction: condition.NewKeySet( + append([]condition.Key{ + condition.S3ObjectLockRemainingRetentionDays, + condition.S3ObjectLockRetainUntilDate, + condition.S3ObjectLockMode, + condition.S3ObjectLockLegalHold, + }, condition.CommonKeys...)...), + + GetObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...), + PutObjectLegalHoldAction: condition.NewKeySet( + append([]condition.Key{ + condition.S3ObjectLockLegalHold, + }, condition.CommonKeys...)...), + GetObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...), + BypassGovernanceRetentionAction: condition.NewKeySet( + append([]condition.Key{ + condition.S3ObjectLockRemainingRetentionDays, + condition.S3ObjectLockRetainUntilDate, + condition.S3ObjectLockMode, + condition.S3ObjectLockLegalHold, }, condition.CommonKeys...)...), - PutObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...), - GetObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...), - BypassGovernanceModeAction: condition.NewKeySet(condition.CommonKeys...), - BypassGovernanceRetentionAction: condition.NewKeySet(condition.CommonKeys...), - PutObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...), - GetObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...), GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...), PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...), PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...), diff --git a/pkg/bucket/policy/condition/binaryequalsfunc.go b/pkg/bucket/policy/condition/binaryequalsfunc.go index cb55bfaaf..25799ee9f 100644 --- a/pkg/bucket/policy/condition/binaryequalsfunc.go +++ b/pkg/bucket/policy/condition/binaryequalsfunc.go @@ -111,6 +111,10 @@ func validateBinaryEqualsValues(n name, key Key, values set.StringSet) error { if s != "COPY" && s != "REPLACE" { return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzMetadataDirective, n) } + case S3XAmzContentSha256: + if s == "" { + return fmt.Errorf("invalid empty value for '%v' for %v condition", S3XAmzContentSha256, n) + } } values.Add(s) } diff --git a/pkg/bucket/policy/condition/boolfunc.go b/pkg/bucket/policy/condition/boolfunc.go index 402b4c858..b8ecd3e6e 100644 --- a/pkg/bucket/policy/condition/boolfunc.go +++ b/pkg/bucket/policy/condition/boolfunc.go @@ -38,6 +38,10 @@ func (f booleanFunc) evaluate(values map[string][]string) bool { requestValue = values[f.k.Name()] } + if len(requestValue) == 0 { + return false + } + return f.value == requestValue[0] } diff --git a/pkg/bucket/policy/condition/dateequalsfunc.go b/pkg/bucket/policy/condition/dateequalsfunc.go new file mode 100644 index 000000000..64871f9b0 --- /dev/null +++ b/pkg/bucket/policy/condition/dateequalsfunc.go @@ -0,0 +1,164 @@ +/* + * MinIO Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "fmt" + "net/http" + "reflect" + "time" +) + +func toDateEqualsFuncString(n name, key Key, value time.Time) string { + return fmt.Sprintf("%v:%v:%v", n, key, value.Format(time.RFC3339)) +} + +// dateEqualsFunc - String equals function. It checks whether value by Key in given +// values map is in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is in values. +type dateEqualsFunc struct { + k Key + value time.Time +} + +// evaluate() - evaluates to check whether value by Key in given values is in +// condition values. +func (f dateEqualsFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + t, err := time.Parse(time.RFC3339, requestValue[0]) + if err != nil { + return false + } + + return f.value.Equal(t) +} + +// key() - returns condition key which is used by this condition function. +func (f dateEqualsFunc) key() Key { + return f.k +} + +// name() - returns "DateEquals" condition name. +func (f dateEqualsFunc) name() name { + return dateEquals +} + +func (f dateEqualsFunc) String() string { + return toDateEqualsFuncString(dateEquals, f.k, f.value) +} + +// toMap - returns map representation of this function. +func (f dateEqualsFunc) toMap() map[Key]ValueSet { + if !f.k.IsValid() { + return nil + } + + values := NewValueSet() + values.Add(NewStringValue(f.value.Format(time.RFC3339))) + + return map[Key]ValueSet{ + f.k: values, + } +} + +// dateNotEqualsFunc - String not equals function. It checks whether value by Key in +// given values is NOT in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is NOT in values. +type dateNotEqualsFunc struct { + dateEqualsFunc +} + +// evaluate() - evaluates to check whether value by Key in given values is NOT in +// condition values. +func (f dateNotEqualsFunc) evaluate(values map[string][]string) bool { + return !f.dateEqualsFunc.evaluate(values) +} + +// name() - returns "DateNotEquals" condition name. +func (f dateNotEqualsFunc) name() name { + return dateNotEquals +} + +func (f dateNotEqualsFunc) String() string { + return toDateEqualsFuncString(dateNotEquals, f.dateEqualsFunc.k, f.dateEqualsFunc.value) +} + +func valueToTime(n name, values ValueSet) (v time.Time, err error) { + if len(values) != 1 { + return v, fmt.Errorf("only one value is allowed for %s condition", n) + } + + for vs := range values { + switch vs.GetType() { + case reflect.String: + s, err := vs.GetString() + if err != nil { + return v, err + } + if v, err = time.Parse(time.RFC3339, s); err != nil { + return v, fmt.Errorf("value %s must be a time.Time string for %s condition: %w", vs, n, err) + } + default: + return v, fmt.Errorf("value %s must be a time.Time for %s condition", vs, n) + } + } + + return v, nil + +} + +// newDateEqualsFunc - returns new DateEquals function. +func newDateEqualsFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToTime(dateEquals, values) + if err != nil { + return nil, err + } + + return NewDateEqualsFunc(key, v) +} + +// NewDateEqualsFunc - returns new DateEquals function. +func NewDateEqualsFunc(key Key, value time.Time) (Function, error) { + return &dateEqualsFunc{key, value}, nil +} + +// newDateNotEqualsFunc - returns new DateNotEquals function. +func newDateNotEqualsFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToTime(dateNotEquals, values) + if err != nil { + return nil, err + } + + return NewDateNotEqualsFunc(key, v) +} + +// NewDateNotEqualsFunc - returns new DateNotEquals function. +func NewDateNotEqualsFunc(key Key, value time.Time) (Function, error) { + return &dateNotEqualsFunc{dateEqualsFunc{key, value}}, nil +} diff --git a/pkg/bucket/policy/condition/dategreaterthanfunc.go b/pkg/bucket/policy/condition/dategreaterthanfunc.go new file mode 100644 index 000000000..25c375d53 --- /dev/null +++ b/pkg/bucket/policy/condition/dategreaterthanfunc.go @@ -0,0 +1,153 @@ +/* + * MinIO Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "fmt" + "net/http" + "time" +) + +func toDateGreaterThanFuncString(n name, key Key, value time.Time) string { + return fmt.Sprintf("%v:%v:%v", n, key, value.Format(time.RFC3339)) +} + +// dateGreaterThanFunc - String equals function. It checks whether value by Key in given +// values map is in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is in values. +type dateGreaterThanFunc struct { + k Key + value time.Time +} + +// evaluate() - evaluates to check whether value by Key in given values is in +// condition values. +func (f dateGreaterThanFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + t, err := time.Parse(time.RFC3339, requestValue[0]) + if err != nil { + return false + } + + return t.After(f.value) +} + +// key() - returns condition key which is used by this condition function. +func (f dateGreaterThanFunc) key() Key { + return f.k +} + +// name() - returns "DateGreaterThan" condition name. +func (f dateGreaterThanFunc) name() name { + return dateGreaterThan +} + +func (f dateGreaterThanFunc) String() string { + return toDateGreaterThanFuncString(dateGreaterThan, f.k, f.value) +} + +// toMap - returns map representation of this function. +func (f dateGreaterThanFunc) toMap() map[Key]ValueSet { + if !f.k.IsValid() { + return nil + } + + values := NewValueSet() + values.Add(NewStringValue(f.value.Format(time.RFC3339))) + + return map[Key]ValueSet{ + f.k: values, + } +} + +// dateNotEqualsFunc - String not equals function. It checks whether value by Key in +// given values is NOT in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is NOT in values. +type dateGreaterThanEqualsFunc struct { + dateGreaterThanFunc +} + +// evaluate() - evaluates to check whether value by Key in given values is NOT in +// condition values. +func (f dateGreaterThanEqualsFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + t, err := time.Parse(time.RFC3339, requestValue[0]) + if err != nil { + return false + } + + return t.After(f.value) || t.Equal(f.value) +} + +// name() - returns "DateNotEquals" condition name. +func (f dateGreaterThanEqualsFunc) name() name { + return dateGreaterThanEquals +} + +func (f dateGreaterThanEqualsFunc) String() string { + return toDateGreaterThanFuncString(dateNotEquals, f.dateGreaterThanFunc.k, f.dateGreaterThanFunc.value) +} + +// newDateGreaterThanFunc - returns new DateGreaterThan function. +func newDateGreaterThanFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToTime(dateGreaterThan, values) + if err != nil { + return nil, err + } + + return NewDateGreaterThanFunc(key, v) +} + +// NewDateGreaterThanFunc - returns new DateGreaterThan function. +func NewDateGreaterThanFunc(key Key, value time.Time) (Function, error) { + return &dateGreaterThanFunc{key, value}, nil +} + +// newDateNotEqualsFunc - returns new DateNotEquals function. +func newDateGreaterThanEqualsFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToTime(dateNotEquals, values) + if err != nil { + return nil, err + } + + return NewDateGreaterThanEqualsFunc(key, v) +} + +// NewDateGreaterThanEqualsFunc - returns new DateNotEquals function. +func NewDateGreaterThanEqualsFunc(key Key, value time.Time) (Function, error) { + return &dateGreaterThanEqualsFunc{dateGreaterThanFunc{key, value}}, nil +} diff --git a/pkg/bucket/policy/condition/datelessthanfunc.go b/pkg/bucket/policy/condition/datelessthanfunc.go new file mode 100644 index 000000000..20b16d28f --- /dev/null +++ b/pkg/bucket/policy/condition/datelessthanfunc.go @@ -0,0 +1,153 @@ +/* + * MinIO Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "fmt" + "net/http" + "time" +) + +func toDateLessThanFuncString(n name, key Key, value time.Time) string { + return fmt.Sprintf("%v:%v:%v", n, key, value.Format(time.RFC3339)) +} + +// dateLessThanFunc - String equals function. It checks whether value by Key in given +// values map is in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is in values. +type dateLessThanFunc struct { + k Key + value time.Time +} + +// evaluate() - evaluates to check whether value by Key in given values is in +// condition values. +func (f dateLessThanFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + t, err := time.Parse(time.RFC3339, requestValue[0]) + if err != nil { + return false + } + + return t.Before(f.value) +} + +// key() - returns condition key which is used by this condition function. +func (f dateLessThanFunc) key() Key { + return f.k +} + +// name() - returns "DateLessThan" condition name. +func (f dateLessThanFunc) name() name { + return dateLessThan +} + +func (f dateLessThanFunc) String() string { + return toDateLessThanFuncString(dateLessThan, f.k, f.value) +} + +// toMap - returns map representation of this function. +func (f dateLessThanFunc) toMap() map[Key]ValueSet { + if !f.k.IsValid() { + return nil + } + + values := NewValueSet() + values.Add(NewStringValue(f.value.Format(time.RFC3339))) + + return map[Key]ValueSet{ + f.k: values, + } +} + +// dateNotEqualsFunc - String not equals function. It checks whether value by Key in +// given values is NOT in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is NOT in values. +type dateLessThanEqualsFunc struct { + dateLessThanFunc +} + +// evaluate() - evaluates to check whether value by Key in given values is NOT in +// condition values. +func (f dateLessThanEqualsFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + t, err := time.Parse(time.RFC3339, requestValue[0]) + if err != nil { + return false + } + + return t.Before(f.value) || t.Equal(f.value) +} + +// name() - returns "DateNotEquals" condition name. +func (f dateLessThanEqualsFunc) name() name { + return dateLessThanEquals +} + +func (f dateLessThanEqualsFunc) String() string { + return toDateLessThanFuncString(dateNotEquals, f.dateLessThanFunc.k, f.dateLessThanFunc.value) +} + +// newDateLessThanFunc - returns new DateLessThan function. +func newDateLessThanFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToTime(dateLessThan, values) + if err != nil { + return nil, err + } + + return NewDateLessThanFunc(key, v) +} + +// NewDateLessThanFunc - returns new DateLessThan function. +func NewDateLessThanFunc(key Key, value time.Time) (Function, error) { + return &dateLessThanFunc{key, value}, nil +} + +// newDateNotEqualsFunc - returns new DateNotEquals function. +func newDateLessThanEqualsFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToTime(dateNotEquals, values) + if err != nil { + return nil, err + } + + return NewDateLessThanEqualsFunc(key, v) +} + +// NewDateLessThanEqualsFunc - returns new DateNotEquals function. +func NewDateLessThanEqualsFunc(key Key, value time.Time) (Function, error) { + return &dateLessThanEqualsFunc{dateLessThanFunc{key, value}}, nil +} diff --git a/pkg/bucket/policy/condition/func.go b/pkg/bucket/policy/condition/func.go index fb6f686b4..08a368b37 100644 --- a/pkg/bucket/policy/condition/func.go +++ b/pkg/bucket/policy/condition/func.go @@ -106,6 +106,18 @@ var conditionFuncMap = map[name]func(Key, ValueSet) (Function, error){ notIPAddress: newNotIPAddressFunc, null: newNullFunc, boolean: newBooleanFunc, + numericEquals: newNumericEqualsFunc, + numericNotEquals: newNumericNotEqualsFunc, + numericLessThan: newNumericLessThanFunc, + numericLessThanEquals: newNumericLessThanEqualsFunc, + numericGreaterThan: newNumericGreaterThanFunc, + numericGreaterThanEquals: newNumericGreaterThanEqualsFunc, + dateEquals: newDateEqualsFunc, + dateNotEquals: newDateNotEqualsFunc, + dateLessThan: newDateLessThanFunc, + dateLessThanEquals: newDateLessThanEqualsFunc, + dateGreaterThan: newDateGreaterThanFunc, + dateGreaterThanEquals: newDateGreaterThanEqualsFunc, // Add new conditions here. } diff --git a/pkg/bucket/policy/condition/func_test.go b/pkg/bucket/policy/condition/func_test.go index 2aa15acc7..77f47be6f 100644 --- a/pkg/bucket/policy/condition/func_test.go +++ b/pkg/bucket/policy/condition/func_test.go @@ -268,12 +268,7 @@ func TestFunctionsUnmarshalJSON(t *testing.T) { case3Data := []byte(`{}`) - // Remove this test after supporting date conditions. case4Data := []byte(`{ -"DateEquals": { "aws:CurrentTime": "2013-06-30T00:00:00Z" } -}`) - - case5Data := []byte(`{ "StringLike": { "s3:x-amz-metadata-directive": "REPL*" }, @@ -336,10 +331,8 @@ func TestFunctionsUnmarshalJSON(t *testing.T) { {case2Data, NewFunctions(func6), false}, // empty condition error. {case3Data, nil, true}, - // unsupported condition error. - {case4Data, nil, true}, // Success case multiple keys, same condition. - {case5Data, NewFunctions(func1, func2_1, func2_2, func2_3, func3, func4, func5, func6, func7), false}, + {case4Data, NewFunctions(func1, func2_1, func2_2, func2_3, func3, func4, func5, func6, func7), false}, } for i, testCase := range testCases { diff --git a/pkg/bucket/policy/condition/key.go b/pkg/bucket/policy/condition/key.go index 4046ff031..08f490a9e 100644 --- a/pkg/bucket/policy/condition/key.go +++ b/pkg/bucket/policy/condition/key.go @@ -43,6 +43,9 @@ const ( // PutObject API only. S3XAmzMetadataDirective Key = "s3:x-amz-metadata-directive" + // S3XAmzContentSha256 - set a static content-sha256 for all calls for a given action. + S3XAmzContentSha256 = "s3:x-amz-content-sha256" + // S3XAmzStorageClass - key representing x-amz-storage-class HTTP header applicable to PutObject API // only. S3XAmzStorageClass Key = "s3:x-amz-storage-class" @@ -59,6 +62,24 @@ const ( // S3MaxKeys - key representing max-keys query parameter of ListBucket API only. S3MaxKeys Key = "s3:max-keys" + // S3ObjectLockRemainingRetentionDays - key representing object-lock-remaining-retention-days + // Enables enforcement of an object relative to the remaining retention days, you can set + // minimum and maximum allowable retention periods for a bucket using a bucket policy. + // This key are specific for s3:PutObjectRetention API. + S3ObjectLockRemainingRetentionDays Key = "s3:object-lock-remaining-retention-days" + + // S3ObjectLockMode - key representing object-lock-mode + // Enables enforcement of the specified object retention mode + S3ObjectLockMode Key = "s3:object-lock-mode" + + // S3ObjectLockRetainUntilDate - key representing object-lock-retain-util-date + // Enables enforcement of a specific retain-until-date + S3ObjectLockRetainUntilDate Key = "s3:object-lock-retain-until-date" + + // S3ObjectLockLegalHold - key representing object-local-legal-hold + // Enables enforcement of the specified object legal hold status + S3ObjectLockLegalHold Key = "s3:object-lock-legal-hold" + // AWSReferer - key representing Referer header of any API. AWSReferer Key = "aws:Referer" @@ -94,10 +115,15 @@ var AllSupportedKeys = append([]Key{ S3XAmzServerSideEncryptionCustomerAlgorithm, S3XAmzMetadataDirective, S3XAmzStorageClass, + S3XAmzContentSha256, S3LocationConstraint, S3Prefix, S3Delimiter, S3MaxKeys, + S3ObjectLockRemainingRetentionDays, + S3ObjectLockMode, + S3ObjectLockLegalHold, + S3ObjectLockRetainUntilDate, AWSReferer, AWSSourceIP, AWSUserAgent, @@ -121,6 +147,7 @@ var CommonKeys = append([]Key{ AWSPrincipalType, AWSUserID, AWSUsername, + S3XAmzContentSha256, }, JWTKeys...) func substFuncFromValues(values map[string][]string) func(string) string { diff --git a/pkg/bucket/policy/condition/name.go b/pkg/bucket/policy/condition/name.go index e03d2521c..270cd1a4d 100644 --- a/pkg/bucket/policy/condition/name.go +++ b/pkg/bucket/policy/condition/name.go @@ -35,6 +35,18 @@ const ( notIPAddress = "NotIpAddress" null = "Null" boolean = "Bool" + numericEquals = "NumericEquals" + numericNotEquals = "NumericNotEquals" + numericLessThan = "NumericLessThan" + numericLessThanEquals = "NumericLessThanEquals" + numericGreaterThan = "NumericGreaterThan" + numericGreaterThanEquals = "NumericGreaterThanEquals" + dateEquals = "DateEquals" + dateNotEquals = "DateNotEquals" + dateLessThan = "DateLessThan" + dateLessThanEquals = "DateLessThanEquals" + dateGreaterThan = "DateGreaterThan" + dateGreaterThanEquals = "DateGreaterThanEquals" ) var supportedConditions = []name{ @@ -49,6 +61,18 @@ var supportedConditions = []name{ notIPAddress, null, boolean, + numericEquals, + numericNotEquals, + numericLessThan, + numericLessThanEquals, + numericGreaterThan, + numericGreaterThanEquals, + dateEquals, + dateNotEquals, + dateLessThan, + dateLessThanEquals, + dateGreaterThan, + dateGreaterThanEquals, // Add new conditions here. } diff --git a/pkg/bucket/policy/condition/numericequalsfunc.go b/pkg/bucket/policy/condition/numericequalsfunc.go new file mode 100644 index 000000000..42134a0be --- /dev/null +++ b/pkg/bucket/policy/condition/numericequalsfunc.go @@ -0,0 +1,168 @@ +/* + * MinIO Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "fmt" + "net/http" + "reflect" + "strconv" +) + +func toNumericEqualsFuncString(n name, key Key, value int) string { + return fmt.Sprintf("%v:%v:%v", n, key, value) +} + +// numericEqualsFunc - String equals function. It checks whether value by Key in given +// values map is in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is in values. +type numericEqualsFunc struct { + k Key + value int +} + +// evaluate() - evaluates to check whether value by Key in given values is in +// condition values. +func (f numericEqualsFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + rvInt, err := strconv.Atoi(requestValue[0]) + if err != nil { + return false + } + + return f.value == rvInt +} + +// key() - returns condition key which is used by this condition function. +func (f numericEqualsFunc) key() Key { + return f.k +} + +// name() - returns "NumericEquals" condition name. +func (f numericEqualsFunc) name() name { + return numericEquals +} + +func (f numericEqualsFunc) String() string { + return toNumericEqualsFuncString(numericEquals, f.k, f.value) +} + +// toMap - returns map representation of this function. +func (f numericEqualsFunc) toMap() map[Key]ValueSet { + if !f.k.IsValid() { + return nil + } + + values := NewValueSet() + values.Add(NewIntValue(f.value)) + + return map[Key]ValueSet{ + f.k: values, + } +} + +// numericNotEqualsFunc - String not equals function. It checks whether value by Key in +// given values is NOT in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is NOT in values. +type numericNotEqualsFunc struct { + numericEqualsFunc +} + +// evaluate() - evaluates to check whether value by Key in given values is NOT in +// condition values. +func (f numericNotEqualsFunc) evaluate(values map[string][]string) bool { + return !f.numericEqualsFunc.evaluate(values) +} + +// name() - returns "NumericNotEquals" condition name. +func (f numericNotEqualsFunc) name() name { + return numericNotEquals +} + +func (f numericNotEqualsFunc) String() string { + return toNumericEqualsFuncString(numericNotEquals, f.numericEqualsFunc.k, f.numericEqualsFunc.value) +} + +func valueToInt(n name, values ValueSet) (v int, err error) { + if len(values) != 1 { + return -1, fmt.Errorf("only one value is allowed for %s condition", n) + } + + for vs := range values { + switch vs.GetType() { + case reflect.Int: + if v, err = vs.GetInt(); err != nil { + return -1, err + } + case reflect.String: + s, err := vs.GetString() + if err != nil { + return -1, err + } + if v, err = strconv.Atoi(s); err != nil { + return -1, fmt.Errorf("value %s must be a int for %s condition: %w", vs, n, err) + } + default: + return -1, fmt.Errorf("value %s must be a int for %s condition", vs, n) + } + } + + return v, nil + +} + +// newNumericEqualsFunc - returns new NumericEquals function. +func newNumericEqualsFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToInt(numericEquals, values) + if err != nil { + return nil, err + } + + return NewNumericEqualsFunc(key, v) +} + +// NewNumericEqualsFunc - returns new NumericEquals function. +func NewNumericEqualsFunc(key Key, value int) (Function, error) { + return &numericEqualsFunc{key, value}, nil +} + +// newNumericNotEqualsFunc - returns new NumericNotEquals function. +func newNumericNotEqualsFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToInt(numericNotEquals, values) + if err != nil { + return nil, err + } + + return NewNumericNotEqualsFunc(key, v) +} + +// NewNumericNotEqualsFunc - returns new NumericNotEquals function. +func NewNumericNotEqualsFunc(key Key, value int) (Function, error) { + return &numericNotEqualsFunc{numericEqualsFunc{key, value}}, nil +} diff --git a/pkg/bucket/policy/condition/numericgreaterfunc.go b/pkg/bucket/policy/condition/numericgreaterfunc.go new file mode 100644 index 000000000..e4d07a4e1 --- /dev/null +++ b/pkg/bucket/policy/condition/numericgreaterfunc.go @@ -0,0 +1,153 @@ +/* + * MinIO Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "fmt" + "net/http" + "strconv" +) + +func toNumericGreaterThanFuncString(n name, key Key, value int) string { + return fmt.Sprintf("%v:%v:%v", n, key, value) +} + +// numericGreaterThanFunc - String equals function. It checks whether value by Key in given +// values map is in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is in values. +type numericGreaterThanFunc struct { + k Key + value int +} + +// evaluate() - evaluates to check whether value by Key in given values is in +// condition values. +func (f numericGreaterThanFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + rvInt, err := strconv.Atoi(requestValue[0]) + if err != nil { + return false + } + + return rvInt > f.value +} + +// key() - returns condition key which is used by this condition function. +func (f numericGreaterThanFunc) key() Key { + return f.k +} + +// name() - returns "NumericGreaterThan" condition name. +func (f numericGreaterThanFunc) name() name { + return numericGreaterThan +} + +func (f numericGreaterThanFunc) String() string { + return toNumericGreaterThanFuncString(numericGreaterThan, f.k, f.value) +} + +// toMap - returns map representation of this function. +func (f numericGreaterThanFunc) toMap() map[Key]ValueSet { + if !f.k.IsValid() { + return nil + } + + values := NewValueSet() + values.Add(NewIntValue(f.value)) + + return map[Key]ValueSet{ + f.k: values, + } +} + +// numericGreaterThanEqualsFunc - String not equals function. It checks whether value by Key in +// given values is NOT in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is NOT in values. +type numericGreaterThanEqualsFunc struct { + numericGreaterThanFunc +} + +// evaluate() - evaluates to check whether value by Key in given values is NOT in +// condition values. +func (f numericGreaterThanEqualsFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + rvInt, err := strconv.Atoi(requestValue[0]) + if err != nil { + return false + } + + return rvInt >= f.value +} + +// name() - returns "NumericGreaterThanEquals" condition name. +func (f numericGreaterThanEqualsFunc) name() name { + return numericGreaterThanEquals +} + +func (f numericGreaterThanEqualsFunc) String() string { + return toNumericGreaterThanFuncString(numericGreaterThanEquals, f.numericGreaterThanFunc.k, f.numericGreaterThanFunc.value) +} + +// newNumericGreaterThanFunc - returns new NumericGreaterThan function. +func newNumericGreaterThanFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToInt(numericGreaterThan, values) + if err != nil { + return nil, err + } + + return NewNumericGreaterThanFunc(key, v) +} + +// NewNumericGreaterThanFunc - returns new NumericGreaterThan function. +func NewNumericGreaterThanFunc(key Key, value int) (Function, error) { + return &numericGreaterThanFunc{key, value}, nil +} + +// newNumericGreaterThanEqualsFunc - returns new NumericGreaterThanEquals function. +func newNumericGreaterThanEqualsFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToInt(numericGreaterThanEquals, values) + if err != nil { + return nil, err + } + + return NewNumericGreaterThanEqualsFunc(key, v) +} + +// NewNumericGreaterThanEqualsFunc - returns new NumericGreaterThanEquals function. +func NewNumericGreaterThanEqualsFunc(key Key, value int) (Function, error) { + return &numericGreaterThanEqualsFunc{numericGreaterThanFunc{key, value}}, nil +} diff --git a/pkg/bucket/policy/condition/numericlessfunc.go b/pkg/bucket/policy/condition/numericlessfunc.go new file mode 100644 index 000000000..7890b655d --- /dev/null +++ b/pkg/bucket/policy/condition/numericlessfunc.go @@ -0,0 +1,153 @@ +/* + * MinIO Cloud Storage, (C) 2020 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "fmt" + "net/http" + "strconv" +) + +func toNumericLessThanFuncString(n name, key Key, value int) string { + return fmt.Sprintf("%v:%v:%v", n, key, value) +} + +// numericLessThanFunc - String equals function. It checks whether value by Key in given +// values map is in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is in values. +type numericLessThanFunc struct { + k Key + value int +} + +// evaluate() - evaluates to check whether value by Key in given values is in +// condition values. +func (f numericLessThanFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + rvInt, err := strconv.Atoi(requestValue[0]) + if err != nil { + return false + } + + return rvInt < f.value +} + +// key() - returns condition key which is used by this condition function. +func (f numericLessThanFunc) key() Key { + return f.k +} + +// name() - returns "NumericLessThan" condition name. +func (f numericLessThanFunc) name() name { + return numericLessThan +} + +func (f numericLessThanFunc) String() string { + return toNumericLessThanFuncString(numericLessThan, f.k, f.value) +} + +// toMap - returns map representation of this function. +func (f numericLessThanFunc) toMap() map[Key]ValueSet { + if !f.k.IsValid() { + return nil + } + + values := NewValueSet() + values.Add(NewIntValue(f.value)) + + return map[Key]ValueSet{ + f.k: values, + } +} + +// numericLessThanEqualsFunc - String not equals function. It checks whether value by Key in +// given values is NOT in condition values. +// For example, +// - if values = ["mybucket/foo"], at evaluate() it returns whether string +// in value map for Key is NOT in values. +type numericLessThanEqualsFunc struct { + numericLessThanFunc +} + +// evaluate() - evaluates to check whether value by Key in given values is NOT in +// condition values. +func (f numericLessThanEqualsFunc) evaluate(values map[string][]string) bool { + requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())] + if !ok { + requestValue = values[f.k.Name()] + } + + if len(requestValue) == 0 { + return false + } + + rvInt, err := strconv.Atoi(requestValue[0]) + if err != nil { + return false + } + + return rvInt <= f.value +} + +// name() - returns "NumericLessThanEquals" condition name. +func (f numericLessThanEqualsFunc) name() name { + return numericLessThanEquals +} + +func (f numericLessThanEqualsFunc) String() string { + return toNumericLessThanFuncString(numericLessThanEquals, f.numericLessThanFunc.k, f.numericLessThanFunc.value) +} + +// newNumericLessThanFunc - returns new NumericLessThan function. +func newNumericLessThanFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToInt(numericLessThan, values) + if err != nil { + return nil, err + } + + return NewNumericLessThanFunc(key, v) +} + +// NewNumericLessThanFunc - returns new NumericLessThan function. +func NewNumericLessThanFunc(key Key, value int) (Function, error) { + return &numericLessThanFunc{key, value}, nil +} + +// newNumericLessThanEqualsFunc - returns new NumericLessThanEquals function. +func newNumericLessThanEqualsFunc(key Key, values ValueSet) (Function, error) { + v, err := valueToInt(numericLessThanEquals, values) + if err != nil { + return nil, err + } + + return NewNumericLessThanEqualsFunc(key, v) +} + +// NewNumericLessThanEqualsFunc - returns new NumericLessThanEquals function. +func NewNumericLessThanEqualsFunc(key Key, value int) (Function, error) { + return &numericLessThanEqualsFunc{numericLessThanFunc{key, value}}, nil +} diff --git a/pkg/bucket/policy/condition/stringequalsfunc.go b/pkg/bucket/policy/condition/stringequalsfunc.go index 27a09c37b..0c8f60547 100644 --- a/pkg/bucket/policy/condition/stringequalsfunc.go +++ b/pkg/bucket/policy/condition/stringequalsfunc.go @@ -143,6 +143,10 @@ func validateStringEqualsValues(n name, key Key, values set.StringSet) error { if s != "COPY" && s != "REPLACE" { return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzMetadataDirective, n) } + case S3XAmzContentSha256: + if s == "" { + return fmt.Errorf("invalid empty value for '%v' for %v condition", S3XAmzContentSha256, n) + } } } diff --git a/pkg/iam/policy/action.go b/pkg/iam/policy/action.go index cbf03ee7d..8ec80a79a 100644 --- a/pkg/iam/policy/action.go +++ b/pkg/iam/policy/action.go @@ -90,9 +90,6 @@ const ( // PutObjectAction - PutObject Rest API action. PutObjectAction = "s3:PutObject" - // BypassGovernanceModeAction - bypass governance mode for DeleteObject Rest API action. - BypassGovernanceModeAction = "s3:BypassGovernanceMode" - // BypassGovernanceRetentionAction - bypass governance retention for PutObjectRetention, PutObject and DeleteObject Rest API action. BypassGovernanceRetentionAction = "s3:BypassGovernanceRetention" @@ -162,7 +159,6 @@ var supportedActions = map[Action]struct{}{ PutObjectLegalHoldAction: {}, PutBucketObjectLockConfigurationAction: {}, GetBucketObjectLockConfigurationAction: {}, - BypassGovernanceModeAction: {}, BypassGovernanceRetentionAction: {}, GetObjectTaggingAction: {}, PutObjectTaggingAction: {}, @@ -179,7 +175,6 @@ var supportedObjectActions = map[Action]struct{}{ GetObjectAction: {}, ListMultipartUploadPartsAction: {}, PutObjectAction: {}, - BypassGovernanceModeAction: {}, BypassGovernanceRetentionAction: {}, PutObjectRetentionAction: {}, GetObjectRetentionAction: {}, @@ -301,13 +296,32 @@ var actionConditionKeyMap = map[Action]condition.KeySet{ condition.S3XAmzServerSideEncryptionCustomerAlgorithm, condition.S3XAmzMetadataDirective, condition.S3XAmzStorageClass, + condition.S3ObjectLockRetainUntilDate, + condition.S3ObjectLockMode, + condition.S3ObjectLockLegalHold, + }, condition.CommonKeys...)...), + + PutObjectRetentionAction: condition.NewKeySet( + append([]condition.Key{ + condition.S3ObjectLockRemainingRetentionDays, + condition.S3ObjectLockRetainUntilDate, + condition.S3ObjectLockMode, + condition.S3ObjectLockLegalHold, + }, condition.CommonKeys...)...), + + GetObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...), + PutObjectLegalHoldAction: condition.NewKeySet( + append([]condition.Key{ + condition.S3ObjectLockLegalHold, + }, condition.CommonKeys...)...), + GetObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...), + BypassGovernanceRetentionAction: condition.NewKeySet( + append([]condition.Key{ + condition.S3ObjectLockRemainingRetentionDays, + condition.S3ObjectLockRetainUntilDate, + condition.S3ObjectLockMode, + condition.S3ObjectLockLegalHold, }, condition.CommonKeys...)...), - PutObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...), - GetObjectRetentionAction: condition.NewKeySet(condition.CommonKeys...), - PutObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...), - GetObjectLegalHoldAction: condition.NewKeySet(condition.CommonKeys...), - BypassGovernanceModeAction: condition.NewKeySet(condition.CommonKeys...), - BypassGovernanceRetentionAction: condition.NewKeySet(condition.CommonKeys...), GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...), PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...), PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),