mirror of
https://github.com/minio/minio.git
synced 2025-04-20 02:27:50 -04:00
Add aws:Referer condition key support. (#3641)
This change implements bucket policy enhancements required to restrict access based on HTTP referer. See https://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html#example-bucket-policies-use-case-4 for more information. Fixes #3540
This commit is contained in:
parent
69b81af93e
commit
b408d0e87d
@ -125,7 +125,8 @@ func checkRequestAuthType(r *http.Request, bucket, policyAction, region string)
|
|||||||
|
|
||||||
if reqAuthType == authTypeAnonymous && policyAction != "" {
|
if reqAuthType == authTypeAnonymous && policyAction != "" {
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
||||||
return enforceBucketPolicy(bucket, policyAction, r.URL)
|
return enforceBucketPolicy(bucket, policyAction, r.URL.Path,
|
||||||
|
r.Referer(), r.URL.Query())
|
||||||
}
|
}
|
||||||
|
|
||||||
// By default return ErrAccessDenied
|
// By default return ErrAccessDenied
|
||||||
|
@ -33,7 +33,7 @@ import (
|
|||||||
|
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
||||||
// Enforces bucket policies for a bucket for a given tatusaction.
|
// Enforces bucket policies for a bucket for a given tatusaction.
|
||||||
func enforceBucketPolicy(bucket string, action string, reqURL *url.URL) (s3Error APIErrorCode) {
|
func enforceBucketPolicy(bucket, action, resource, referer string, queryParams url.Values) (s3Error APIErrorCode) {
|
||||||
// Verify if bucket actually exists
|
// Verify if bucket actually exists
|
||||||
if err := checkBucketExist(bucket, newObjectLayerFn()); err != nil {
|
if err := checkBucketExist(bucket, newObjectLayerFn()); err != nil {
|
||||||
err = errorCause(err)
|
err = errorCause(err)
|
||||||
@ -57,16 +57,21 @@ func enforceBucketPolicy(bucket string, action string, reqURL *url.URL) (s3Error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct resource in 'arn:aws:s3:::examplebucket/object' format.
|
// Construct resource in 'arn:aws:s3:::examplebucket/object' format.
|
||||||
resource := bucketARNPrefix + strings.TrimSuffix(strings.TrimPrefix(reqURL.Path, "/"), "/")
|
arn := bucketARNPrefix + strings.TrimSuffix(strings.TrimPrefix(resource, "/"), "/")
|
||||||
|
|
||||||
// Get conditions for policy verification.
|
// Get conditions for policy verification.
|
||||||
conditionKeyMap := make(map[string]set.StringSet)
|
conditionKeyMap := make(map[string]set.StringSet)
|
||||||
for queryParam := range reqURL.Query() {
|
for queryParam := range queryParams {
|
||||||
conditionKeyMap[queryParam] = set.CreateStringSet(reqURL.Query().Get(queryParam))
|
conditionKeyMap[queryParam] = set.CreateStringSet(queryParams.Get(queryParam))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add request referer to conditionKeyMap if present.
|
||||||
|
if referer != "" {
|
||||||
|
conditionKeyMap["referer"] = set.CreateStringSet(referer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate action, resource and conditions with current policy statements.
|
// Validate action, resource and conditions with current policy statements.
|
||||||
if !bucketPolicyEvalStatements(action, resource, conditionKeyMap, policy.Statements) {
|
if !bucketPolicyEvalStatements(action, arn, conditionKeyMap, policy.Statements) {
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
return ErrNone
|
return ErrNone
|
||||||
|
@ -71,6 +71,10 @@ func actionMatch(pattern, action string) bool {
|
|||||||
return wildcard.MatchSimple(pattern, action)
|
return wildcard.MatchSimple(pattern, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refererMatch(pattern, referer string) bool {
|
||||||
|
return wildcard.MatchSimple(pattern, referer)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify if given resource matches with policy statement.
|
// Verify if given resource matches with policy statement.
|
||||||
func bucketPolicyResourceMatch(resource string, statement policyStatement) bool {
|
func bucketPolicyResourceMatch(resource string, statement policyStatement) bool {
|
||||||
// the resource rule for object could contain "*" wild card.
|
// the resource rule for object could contain "*" wild card.
|
||||||
@ -85,33 +89,55 @@ func bucketPolicyConditionMatch(conditions map[string]set.StringSet, statement p
|
|||||||
// Supports following conditions.
|
// Supports following conditions.
|
||||||
// - StringEquals
|
// - StringEquals
|
||||||
// - StringNotEquals
|
// - StringNotEquals
|
||||||
|
// - StringLike
|
||||||
|
// - StringNotLike
|
||||||
//
|
//
|
||||||
// Supported applicable condition keys for each conditions.
|
// Supported applicable condition keys for each conditions.
|
||||||
// - s3:prefix
|
// - s3:prefix
|
||||||
// - s3:max-keys
|
// - s3:max-keys
|
||||||
var conditionMatches = true
|
// - s3:aws-Referer
|
||||||
|
|
||||||
for condition, conditionKeyVal := range statement.Conditions {
|
for condition, conditionKeyVal := range statement.Conditions {
|
||||||
if condition == "StringEquals" {
|
if condition == "StringEquals" {
|
||||||
if !conditionKeyVal["s3:prefix"].Equals(conditions["prefix"]) {
|
if !conditionKeyVal["s3:prefix"].Equals(conditions["prefix"]) {
|
||||||
conditionMatches = false
|
return false
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if !conditionKeyVal["s3:max-keys"].Equals(conditions["max-keys"]) {
|
if !conditionKeyVal["s3:max-keys"].Equals(conditions["max-keys"]) {
|
||||||
conditionMatches = false
|
return false
|
||||||
break
|
|
||||||
}
|
}
|
||||||
} else if condition == "StringNotEquals" {
|
} else if condition == "StringNotEquals" {
|
||||||
if !conditionKeyVal["s3:prefix"].Equals(conditions["prefix"]) {
|
if !conditionKeyVal["s3:prefix"].Equals(conditions["prefix"]) {
|
||||||
conditionMatches = false
|
return false
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if !conditionKeyVal["s3:max-keys"].Equals(conditions["max-keys"]) {
|
if !conditionKeyVal["s3:max-keys"].Equals(conditions["max-keys"]) {
|
||||||
conditionMatches = false
|
return false
|
||||||
break
|
}
|
||||||
|
} else if condition == "StringLike" {
|
||||||
|
awsReferers := conditionKeyVal["aws:Referer"]
|
||||||
|
// wildcard match of referer in statement was not empty.
|
||||||
|
// StringLike has a match, i.e, condition evaluates to true.
|
||||||
|
for referer := range conditions["referer"] {
|
||||||
|
if !awsReferers.FuncMatch(refererMatch, referer).IsEmpty() {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// No matching referer found, so the condition is false.
|
||||||
|
return false
|
||||||
|
} else if condition == "StringNotLike" {
|
||||||
|
awsReferers := conditionKeyVal["aws:Referer"]
|
||||||
|
// wildcard match of referer in statement was not empty.
|
||||||
|
// StringNotLike has a match, i.e, condition evaluates to false.
|
||||||
|
for referer := range conditions["referer"] {
|
||||||
|
if !awsReferers.FuncMatch(refererMatch, referer).IsEmpty() {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return conditionMatches
|
}
|
||||||
|
// No matching referer found, so the condition is true.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No conditions were present in the statement, so trivially true (always).
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutBucketPolicyHandler - PUT Bucket policy
|
// PutBucketPolicyHandler - PUT Bucket policy
|
||||||
|
@ -942,6 +942,34 @@ func TestBucketPolicyConditionMatch(t *testing.T) {
|
|||||||
|
|
||||||
expectedMatch: false,
|
expectedMatch: false,
|
||||||
},
|
},
|
||||||
|
// Test case - 9.
|
||||||
|
// StringLike condition matches.
|
||||||
|
{
|
||||||
|
statementCondition: getStatementWithCondition("StringLike", "aws:Referer", "http://www.example.com/"),
|
||||||
|
condition: getInnerMap("referer", "http://www.example.com/"),
|
||||||
|
expectedMatch: true,
|
||||||
|
},
|
||||||
|
// Test case - 10.
|
||||||
|
// StringLike condition doesn't match.
|
||||||
|
{
|
||||||
|
statementCondition: getStatementWithCondition("StringLike", "aws:Referer", "http://www.example.com/"),
|
||||||
|
condition: getInnerMap("referer", "www.somethingelse.com"),
|
||||||
|
expectedMatch: false,
|
||||||
|
},
|
||||||
|
// Test case - 11.
|
||||||
|
// StringNotLike condition evaluates to false.
|
||||||
|
{
|
||||||
|
statementCondition: getStatementWithCondition("StringNotLike", "aws:Referer", "http://www.example.com/"),
|
||||||
|
condition: getInnerMap("referer", "http://www.example.com/"),
|
||||||
|
expectedMatch: false,
|
||||||
|
},
|
||||||
|
// Test case - 12.
|
||||||
|
// StringNotLike condition evaluates to true.
|
||||||
|
{
|
||||||
|
statementCondition: getStatementWithCondition("StringNotLike", "aws:Referer", "http://www.example.com/"),
|
||||||
|
condition: getInnerMap("referer", "http://somethingelse.com/"),
|
||||||
|
expectedMatch: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
|
@ -35,11 +35,11 @@ var supportedActionMap = set.CreateStringSet("*", "s3:*", "s3:GetObject",
|
|||||||
"s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts")
|
"s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts")
|
||||||
|
|
||||||
// supported Conditions type.
|
// supported Conditions type.
|
||||||
var supportedConditionsType = set.CreateStringSet("StringEquals", "StringNotEquals")
|
var supportedConditionsType = set.CreateStringSet("StringEquals", "StringNotEquals", "StringLike", "StringNotLike")
|
||||||
|
|
||||||
// Validate s3:prefix, s3:max-keys are present if not
|
// Validate s3:prefix, s3:max-keys are present if not
|
||||||
// supported keys for the conditions.
|
// supported keys for the conditions.
|
||||||
var supportedConditionsKey = set.CreateStringSet("s3:prefix", "s3:max-keys")
|
var supportedConditionsKey = set.CreateStringSet("s3:prefix", "s3:max-keys", "aws:Referer")
|
||||||
|
|
||||||
// supportedEffectMap - supported effects.
|
// supportedEffectMap - supported effects.
|
||||||
var supportedEffectMap = set.CreateStringSet("Allow", "Deny")
|
var supportedEffectMap = set.CreateStringSet("Allow", "Deny")
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/pkg/policy"
|
||||||
"github.com/minio/minio-go/pkg/set"
|
"github.com/minio/minio-go/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -342,6 +343,18 @@ func TestIsValidPrincipals(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getEmptyConditionKeyMap - returns a function that generates a
|
||||||
|
// condition key map for a given key.
|
||||||
|
func getEmptyConditionKeyMap(conditionKey string) func() map[string]map[string]set.StringSet {
|
||||||
|
emptyConditonGenerator := func() map[string]map[string]set.StringSet {
|
||||||
|
emptyMap := make(map[string]set.StringSet)
|
||||||
|
conditions := make(map[string]map[string]set.StringSet)
|
||||||
|
conditions[conditionKey] = emptyMap
|
||||||
|
return conditions
|
||||||
|
}
|
||||||
|
return emptyConditonGenerator
|
||||||
|
}
|
||||||
|
|
||||||
// Tests validate policyStatement condition validator.
|
// Tests validate policyStatement condition validator.
|
||||||
func TestIsValidConditions(t *testing.T) {
|
func TestIsValidConditions(t *testing.T) {
|
||||||
// returns empty conditions map.
|
// returns empty conditions map.
|
||||||
@ -350,22 +363,17 @@ func TestIsValidConditions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// returns map with the "StringEquals" set to empty map.
|
// returns map with the "StringEquals" set to empty map.
|
||||||
setEmptyStringEquals := func() map[string]map[string]set.StringSet {
|
setEmptyStringEquals := getEmptyConditionKeyMap("StringEquals")
|
||||||
emptyMap := make(map[string]set.StringSet)
|
|
||||||
conditions := make(map[string]map[string]set.StringSet)
|
|
||||||
conditions["StringEquals"] = emptyMap
|
|
||||||
return conditions
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns map with the "StringNotEquals" set to empty map.
|
// returns map with the "StringNotEquals" set to empty map.
|
||||||
setEmptyStringNotEquals := func() map[string]map[string]set.StringSet {
|
setEmptyStringNotEquals := getEmptyConditionKeyMap("StringNotEquals")
|
||||||
emptyMap := make(map[string]set.StringSet)
|
|
||||||
conditions := make(map[string]map[string]set.StringSet)
|
// returns map with the "StringLike" set to empty map.
|
||||||
conditions["StringNotEquals"] = emptyMap
|
setEmptyStringLike := getEmptyConditionKeyMap("StringLike")
|
||||||
return conditions
|
|
||||||
|
// returns map with the "StringNotLike" set to empty map.
|
||||||
|
setEmptyStringNotLike := getEmptyConditionKeyMap("StringNotLike")
|
||||||
|
|
||||||
}
|
|
||||||
// Generate conditions.
|
// Generate conditions.
|
||||||
generateConditions := func(key1, key2, value string) map[string]map[string]set.StringSet {
|
generateConditions := func(key1, key2, value string) map[string]map[string]set.StringSet {
|
||||||
innerMap := make(map[string]set.StringSet)
|
innerMap := make(map[string]set.StringSet)
|
||||||
@ -377,11 +385,11 @@ func TestIsValidConditions(t *testing.T) {
|
|||||||
|
|
||||||
// generate ambigious conditions.
|
// generate ambigious conditions.
|
||||||
generateAmbigiousConditions := func() map[string]map[string]set.StringSet {
|
generateAmbigiousConditions := func() map[string]map[string]set.StringSet {
|
||||||
innerMap := make(map[string]set.StringSet)
|
prefixMap := make(map[string]set.StringSet)
|
||||||
innerMap["s3:prefix"] = set.CreateStringSet("Asia/")
|
prefixMap["s3:prefix"] = set.CreateStringSet("Asia/")
|
||||||
conditions := make(map[string]map[string]set.StringSet)
|
conditions := make(map[string]map[string]set.StringSet)
|
||||||
conditions["StringEquals"] = innerMap
|
conditions["StringEquals"] = prefixMap
|
||||||
conditions["StringNotEquals"] = innerMap
|
conditions["StringNotEquals"] = prefixMap
|
||||||
return conditions
|
return conditions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,6 +425,8 @@ func TestIsValidConditions(t *testing.T) {
|
|||||||
setEmptyConditions(),
|
setEmptyConditions(),
|
||||||
setEmptyStringEquals(),
|
setEmptyStringEquals(),
|
||||||
setEmptyStringNotEquals(),
|
setEmptyStringNotEquals(),
|
||||||
|
setEmptyStringLike(),
|
||||||
|
setEmptyStringNotLike(),
|
||||||
generateConditions("StringEquals", "s3:prefix", "Asia/"),
|
generateConditions("StringEquals", "s3:prefix", "Asia/"),
|
||||||
generateConditions("StringEquals", "s3:max-keys", "100"),
|
generateConditions("StringEquals", "s3:max-keys", "100"),
|
||||||
generateConditions("StringNotEquals", "s3:prefix", "Asia/"),
|
generateConditions("StringNotEquals", "s3:prefix", "Asia/"),
|
||||||
@ -464,7 +474,11 @@ func TestIsValidConditions(t *testing.T) {
|
|||||||
{testConditions[9], nil, true},
|
{testConditions[9], nil, true},
|
||||||
// Test case - 11.
|
// Test case - 11.
|
||||||
{testConditions[10], nil, true},
|
{testConditions[10], nil, true},
|
||||||
// Test case 10.
|
// Test case - 12.
|
||||||
|
{testConditions[11], nil, true},
|
||||||
|
// Test case - 13.
|
||||||
|
{testConditions[11], nil, true},
|
||||||
|
// Test case - 14.
|
||||||
{testConditions[11], nil, true},
|
{testConditions[11], nil, true},
|
||||||
}
|
}
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
@ -709,3 +723,64 @@ func TestParseBucketPolicy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAWSRefererCondition(t *testing.T) {
|
||||||
|
resource := set.CreateStringSet([]string{
|
||||||
|
fmt.Sprintf("%s%s", bucketARNPrefix, "minio-bucket"+"/"+"Asia"+"*"),
|
||||||
|
}...)
|
||||||
|
|
||||||
|
conditionsKeyMap := make(policy.ConditionKeyMap)
|
||||||
|
conditionsKeyMap.Add("aws:Referer",
|
||||||
|
set.CreateStringSet("www.example.com",
|
||||||
|
"http://www.example.com"))
|
||||||
|
|
||||||
|
requestConditionKeyMap := make(map[string]set.StringSet)
|
||||||
|
requestConditionKeyMap["referer"] = set.CreateStringSet("www.example.com")
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
effect string
|
||||||
|
conditionKey string
|
||||||
|
match bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
effect: "Allow",
|
||||||
|
conditionKey: "StringLike",
|
||||||
|
match: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
effect: "Allow",
|
||||||
|
conditionKey: "StringNotLike",
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
effect: "Deny",
|
||||||
|
conditionKey: "StringLike",
|
||||||
|
match: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
effect: "Deny",
|
||||||
|
conditionKey: "StringNotLike",
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range testCases {
|
||||||
|
conditions := make(map[string]map[string]set.StringSet)
|
||||||
|
conditions[test.conditionKey] = conditionsKeyMap
|
||||||
|
|
||||||
|
allowStatement := policyStatement{
|
||||||
|
Sid: "Testing AWS referer condition",
|
||||||
|
Effect: test.effect,
|
||||||
|
Principal: map[string]interface{}{
|
||||||
|
"AWS": "*",
|
||||||
|
},
|
||||||
|
Resources: resource,
|
||||||
|
Conditions: conditions,
|
||||||
|
}
|
||||||
|
|
||||||
|
if result := bucketPolicyConditionMatch(requestConditionKeyMap, allowStatement); result != test.match {
|
||||||
|
t.Errorf("Test %d - Expected conditons to evaluate to %v but got %v",
|
||||||
|
i+1, test.match, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -56,10 +56,9 @@ func setGetRespHeaders(w http.ResponseWriter, reqParams url.Values) {
|
|||||||
func errAllowableObjectNotFound(bucket string, r *http.Request) APIErrorCode {
|
func errAllowableObjectNotFound(bucket string, r *http.Request) APIErrorCode {
|
||||||
if getRequestAuthType(r) == authTypeAnonymous {
|
if getRequestAuthType(r) == authTypeAnonymous {
|
||||||
//we care about the bucket as a whole, not a particular resource
|
//we care about the bucket as a whole, not a particular resource
|
||||||
url := *r.URL
|
resource := "/" + bucket
|
||||||
url.Path = "/" + bucket
|
if s3Error := enforceBucketPolicy(bucket, "s3:ListBucket", resource,
|
||||||
|
r.Referer(), r.URL.Query()); s3Error != ErrNone {
|
||||||
if s3Error := enforceBucketPolicy(bucket, "s3:ListBucket", &url); s3Error != ErrNone {
|
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -440,7 +439,8 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
|||||||
return
|
return
|
||||||
case authTypeAnonymous:
|
case authTypeAnonymous:
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
|
||||||
if s3Error := enforceBucketPolicy(bucket, "s3:PutObject", r.URL); s3Error != ErrNone {
|
if s3Error := enforceBucketPolicy(bucket, "s3:PutObject", r.URL.Path,
|
||||||
|
r.Referer(), r.URL.Query()); s3Error != ErrNone {
|
||||||
writeErrorResponse(w, s3Error, r.URL)
|
writeErrorResponse(w, s3Error, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -600,7 +600,8 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
|||||||
return
|
return
|
||||||
case authTypeAnonymous:
|
case authTypeAnonymous:
|
||||||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
|
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
|
||||||
if s3Error := enforceBucketPolicy(bucket, "s3:PutObject", r.URL); s3Error != ErrNone {
|
if s3Error := enforceBucketPolicy(bucket, "s3:PutObject", r.URL.Path,
|
||||||
|
r.Referer(), r.URL.Query()); s3Error != ErrNone {
|
||||||
writeErrorResponse(w, s3Error, r.URL)
|
writeErrorResponse(w, s3Error, r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,14 @@ This package implements parsing and validating bucket access policies based on A
|
|||||||
|
|
||||||
StringEquals
|
StringEquals
|
||||||
StringNotEquals
|
StringNotEquals
|
||||||
|
StringLike
|
||||||
|
StringNotLike
|
||||||
|
|
||||||
Supported applicable condition keys for each conditions.
|
Supported applicable condition keys for each conditions.
|
||||||
|
|
||||||
s3:prefix
|
s3:prefix
|
||||||
s3:max-keys
|
s3:max-keys
|
||||||
|
aws:Referer
|
||||||
|
|
||||||
### Nested policy support.
|
### Nested policy support.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user