s3: require their own actions to change a bucket's CORS or versioning (#12433)

* s3: require their own actions to change a bucket's CORS or versioning

PUT/DELETE ?cors and PUT ?versioning were authorized as Write, so an
identity granted only object writes on a bucket could replace its CORS
rules or suspend versioning. They now need PutBucketCors,
DeleteBucketCors and PutBucketVersioning (or Admin), and inline policies
granting s3:PutBucketCors, s3:DeleteBucketCors or s3:PutBucketVersioning
map to those actions instead of Write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* s3: test that object Write cannot change bucket CORS or versioning

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* admin: offer the bucket CORS and versioning permissions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* s3: test each bucket config action with its own identity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chris LuandClaude Opus 5.5 authored and GitHub committed 2026-11-06 09:12:00 -08:00
1 parent 3a579ece3b
commit 3e64326f2b
7 files changed
+109 -7

No files matched your search

@@ -245,6 +245,11 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
<option value="PutBucketPolicy">Put Bucket Policy</option>
<option value="DeleteBucketPolicy">Delete Bucket Policy</option>
</optgroup>
<optgroup label="Bucket Configuration Permissions">
<option value="PutBucketCors">Put Bucket CORS</option>
<option value="DeleteBucketCors">Delete Bucket CORS</option>
<option value="PutBucketVersioning">Put Bucket Versioning</option>
</optgroup>
<optgroup label="S3 Tables Permissions">
<option value="S3TablesAdmin">S3 Tables Admin (Full Access)</option>
<option value="CreateTableBucket">Create Table Bucket</option>
@@ -359,6 +364,11 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
<option value="PutBucketPolicy">Put Bucket Policy</option>
<option value="DeleteBucketPolicy">Delete Bucket Policy</option>
</optgroup>
<optgroup label="Bucket Configuration Permissions">
<option value="PutBucketCors">Put Bucket CORS</option>
<option value="DeleteBucketCors">Delete Bucket CORS</option>
<option value="PutBucketVersioning">Put Bucket Versioning</option>
</optgroup>
<optgroup label="S3 Tables Permissions">
<option value="S3TablesAdmin">S3 Tables Admin (Full Access)</option>
<option value="CreateTableBucket">Create Table Bucket</option>
@@ -237,7 +237,7 @@ func ObjectStoreUsers(data dash.ObjectStoreUsersData) templ.Component {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</div><div class=\"mb-3\"><label for=\"email\" class=\"form-label\">Email</label> <input type=\"email\" class=\"form-control\" id=\"email\" name=\"email\"></div><div class=\"mb-3\"><label for=\"actions\" class=\"form-label\">Permissions</label> <select multiple class=\"form-control\" id=\"actions\" name=\"actions\" size=\"10\"><option value=\"Admin\">Admin (Full Access)</option> <option value=\"Read\">Read</option> <option value=\"Write\">Write</option> <option value=\"List\">List</option> <option value=\"Tagging\">Tagging</option> <option value=\"ReadAcp\" title=\"Needed for GET ?acl on buckets and objects\">Read ACL (ReadAcp)</option> <option value=\"WriteAcp\" title=\"Needed for PUT ?acl and for uploads that send x-amz-acl or x-amz-grant-* headers\">Write ACL (WriteAcp)</option> <optgroup label=\"Object Lock Permissions\"><option value=\"BypassGovernanceRetention\">Bypass Governance Retention</option> <option value=\"GetObjectRetention\">Get Object Retention</option> <option value=\"PutObjectRetention\">Put Object Retention</option> <option value=\"GetObjectLegalHold\">Get Object Legal Hold</option> <option value=\"PutObjectLegalHold\">Put Object Legal Hold</option> <option value=\"GetBucketObjectLockConfiguration\">Get Bucket Object Lock Configuration</option> <option value=\"PutBucketObjectLockConfiguration\">Put Bucket Object Lock Configuration</option></optgroup> <optgroup label=\"Bucket Policy Permissions\"><option value=\"PutBucketPolicy\">Put Bucket Policy</option> <option value=\"DeleteBucketPolicy\">Delete Bucket Policy</option></optgroup> <optgroup label=\"S3 Tables Permissions\"><option value=\"S3TablesAdmin\">S3 Tables Admin (Full Access)</option> <option value=\"CreateTableBucket\">Create Table Bucket</option> <option value=\"GetTableBucket\">Get Table Bucket</option> <option value=\"ListTableBuckets\">List Table Buckets</option> <option value=\"DeleteTableBucket\">Delete Table Bucket</option> <option value=\"PutTableBucketPolicy\">Put Table Bucket Policy</option> <option value=\"GetTableBucketPolicy\">Get Table Bucket Policy</option> <option value=\"DeleteTableBucketPolicy\">Delete Table Bucket Policy</option> <option value=\"CreateNamespace\">Create Namespace</option> <option value=\"GetNamespace\">Get Namespace</option> <option value=\"ListNamespaces\">List Namespaces</option> <option value=\"DeleteNamespace\">Delete Namespace</option> <option value=\"CreateTable\">Create Table</option> <option value=\"GetTable\">Get Table</option> <option value=\"ListTables\">List Tables</option> <option value=\"DeleteTable\">Delete Table</option> <option value=\"PutTablePolicy\">Put Table Policy</option> <option value=\"GetTablePolicy\">Get Table Policy</option> <option value=\"DeleteTablePolicy\">Delete Table Policy</option> <option value=\"TagResource\">Tag Resource</option> <option value=\"ListTagsForResource\">List Tags</option> <option value=\"UntagResource\">Untag Resource</option></optgroup></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple permissions</small></div><div class=\"mb-3\"><label class=\"form-label\">Bucket Scope</label> <small class=\"form-text text-muted d-block mb-2\">Apply selected permissions to specific buckets or all buckets</small><div class=\"form-check mb-2\"><input class=\"form-check-input\" type=\"radio\" name=\"bucketScope\" id=\"allBuckets\" value=\"all\" checked onchange=\"toggleBucketList()\"> <label class=\"form-check-label\" for=\"allBuckets\">All Buckets</label></div><div class=\"form-check mb-2\"><input class=\"form-check-input\" type=\"radio\" name=\"bucketScope\" id=\"specificBuckets\" value=\"specific\" onchange=\"toggleBucketList()\"> <label class=\"form-check-label\" for=\"specificBuckets\">Specific Buckets</label></div><div id=\"bucketSelectionList\" class=\"mt-2\" style=\"display: none;\"><select multiple class=\"form-select\" id=\"selectedBuckets\" size=\"5\"><!-- Options loaded dynamically --></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple buckets</small></div></div><div class=\"mb-3\"><label for=\"policies\" class=\"form-label\">Attached Policies</label> <select multiple class=\"form-control\" id=\"policies\" name=\"policies\" size=\"5\"><!-- Options loaded dynamically --></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple policies</small></div><div class=\"mb-3 form-check\"><input type=\"checkbox\" class=\"form-check-input\" id=\"generateKey\" name=\"generateKey\" checked> <label class=\"form-check-label\" for=\"generateKey\">Generate access key automatically</label></div></form></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-primary\" onclick=\"handleCreateUser()\">Create User</button></div></div></div></div><!-- Edit User Modal --><div class=\"modal fade\" id=\"editUseLine truncated
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</div><div class=\"mb-3\"><label for=\"email\" class=\"form-label\">Email</label> <input type=\"email\" class=\"form-control\" id=\"email\" name=\"email\"></div><div class=\"mb-3\"><label for=\"actions\" class=\"form-label\">Permissions</label> <select multiple class=\"form-control\" id=\"actions\" name=\"actions\" size=\"10\"><option value=\"Admin\">Admin (Full Access)</option> <option value=\"Read\">Read</option> <option value=\"Write\">Write</option> <option value=\"List\">List</option> <option value=\"Tagging\">Tagging</option> <option value=\"ReadAcp\" title=\"Needed for GET ?acl on buckets and objects\">Read ACL (ReadAcp)</option> <option value=\"WriteAcp\" title=\"Needed for PUT ?acl and for uploads that send x-amz-acl or x-amz-grant-* headers\">Write ACL (WriteAcp)</option> <optgroup label=\"Object Lock Permissions\"><option value=\"BypassGovernanceRetention\">Bypass Governance Retention</option> <option value=\"GetObjectRetention\">Get Object Retention</option> <option value=\"PutObjectRetention\">Put Object Retention</option> <option value=\"GetObjectLegalHold\">Get Object Legal Hold</option> <option value=\"PutObjectLegalHold\">Put Object Legal Hold</option> <option value=\"GetBucketObjectLockConfiguration\">Get Bucket Object Lock Configuration</option> <option value=\"PutBucketObjectLockConfiguration\">Put Bucket Object Lock Configuration</option></optgroup> <optgroup label=\"Bucket Policy Permissions\"><option value=\"PutBucketPolicy\">Put Bucket Policy</option> <option value=\"DeleteBucketPolicy\">Delete Bucket Policy</option></optgroup> <optgroup label=\"Bucket Configuration Permissions\"><option value=\"PutBucketCors\">Put Bucket CORS</option> <option value=\"DeleteBucketCors\">Delete Bucket CORS</option> <option value=\"PutBucketVersioning\">Put Bucket Versioning</option></optgroup> <optgroup label=\"S3 Tables Permissions\"><option value=\"S3TablesAdmin\">S3 Tables Admin (Full Access)</option> <option value=\"CreateTableBucket\">Create Table Bucket</option> <option value=\"GetTableBucket\">Get Table Bucket</option> <option value=\"ListTableBuckets\">List Table Buckets</option> <option value=\"DeleteTableBucket\">Delete Table Bucket</option> <option value=\"PutTableBucketPolicy\">Put Table Bucket Policy</option> <option value=\"GetTableBucketPolicy\">Get Table Bucket Policy</option> <option value=\"DeleteTableBucketPolicy\">Delete Table Bucket Policy</option> <option value=\"CreateNamespace\">Create Namespace</option> <option value=\"GetNamespace\">Get Namespace</option> <option value=\"ListNamespaces\">List Namespaces</option> <option value=\"DeleteNamespace\">Delete Namespace</option> <option value=\"CreateTable\">Create Table</option> <option value=\"GetTable\">Get Table</option> <option value=\"ListTables\">List Tables</option> <option value=\"DeleteTable\">Delete Table</option> <option value=\"PutTablePolicy\">Put Table Policy</option> <option value=\"GetTablePolicy\">Get Table Policy</option> <option value=\"DeleteTablePolicy\">Delete Table Policy</option> <option value=\"TagResource\">Tag Resource</option> <option value=\"ListTagsForResource\">List Tags</option> <option value=\"UntagResource\">Untag Resource</option></optgroup></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple permissions</small></div><div class=\"mb-3\"><label class=\"form-label\">Bucket Scope</label> <small class=\"form-text text-muted d-block mb-2\">Apply selected permissions to specific buckets or all buckets</small><div class=\"form-check mb-2\"><input class=\"form-check-input\" type=\"radio\" name=\"bucketScope\" id=\"allBuckets\" value=\"all\" checked onchange=\"toggleBucketList()\"> <label class=\"form-check-label\" for=\"allBuckets\">All Buckets</label></div><div class=\"form-check mb-2\"><input class=\"form-check-input\" type=\"radio\" name=\"bucketScope\" id=\"specificBuckets\" value=\"specific\" onchange=\"toggleBucketList()\"> <label class=\"form-check-label\" for=\"specificBuckets\">Specific Buckets</label></div><div id=\"bucketSelectionList\" class=\"mt-2\" style=\"display: none;\"><select multiple class=\"form-select\" id=\"selectedBuckets\" size=\"5\"><!-- Options loaded dynamically --></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple buckets</small></div></div><div class=\"mb-3\"><label for=\"policies\" class=\"form-label\">Attached Policies</label> <select multiple class=\"form-control\" id=\"policies\" name=\"policies\" size=\"5\"><!-- Options loaded dynamically --></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple policies</small></div><div class=\"mb-3 form-check\"><input type=\"checkbox\" class=\"form-check-input\" id=\"generateKey\" name=\"generateKey\" checked> <label class=\"form-check-label\" for=\"generateKey\">Generate access key automatically</label></div></form></div><div class=\"modal-footer\"><button type=\"button\" clasLine truncated
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+9 -3
View File
@@ -135,10 +135,10 @@ var baseS3ActionMap = map[string]string{
"ListAllMyBuckets": s3_constants.ACTION_LIST,
"GetBucketLocation": s3_constants.ACTION_READ,
"GetBucketVersioning": s3_constants.ACTION_READ,
"PutBucketVersioning": s3_constants.ACTION_WRITE,
"PutBucketVersioning": s3_constants.ACTION_PUT_BUCKET_VERSIONING,
"GetBucketCors": s3_constants.ACTION_READ,
"PutBucketCors": s3_constants.ACTION_WRITE,
"DeleteBucketCors": s3_constants.ACTION_WRITE,
"PutBucketCors": s3_constants.ACTION_PUT_BUCKET_CORS,
"DeleteBucketCors": s3_constants.ACTION_DELETE_BUCKET_CORS,
"GetBucketNotification": s3_constants.ACTION_READ,
"PutBucketNotification": s3_constants.ACTION_WRITE,
"GetBucketObjectLockConfiguration": s3_constants.ACTION_GET_BUCKET_OBJECT_LOCK_CONFIG,
@@ -220,6 +220,12 @@ func MapToIdentitiesAction(action string) string {
return "PutBucketPolicy"
case s3_constants.ACTION_DELETE_BUCKET_POLICY:
return "DeleteBucketPolicy"
case s3_constants.ACTION_PUT_BUCKET_CORS:
return "PutBucketCors"
case s3_constants.ACTION_DELETE_BUCKET_CORS:
return "DeleteBucketCors"
case s3_constants.ACTION_PUT_BUCKET_VERSIONING:
return "PutBucketVersioning"
case s3_constants.ACTION_LIST_MULTIPART_UPLOADS:
return "ListBucketMultipartUploads"
case s3_constants.ACTION_LIST_PARTS:
+6
View File
@@ -427,6 +427,12 @@ func mapBaseActionToS3Format(baseAction string) string {
return s3_constants.S3_ACTION_PUT_BUCKET_POLICY
case s3_constants.ACTION_DELETE_BUCKET_POLICY:
return s3_constants.S3_ACTION_DELETE_BUCKET_POLICY
case s3_constants.ACTION_PUT_BUCKET_CORS:
return s3_constants.S3_ACTION_PUT_BUCKET_CORS
case s3_constants.ACTION_DELETE_BUCKET_CORS:
return s3_constants.S3_ACTION_DELETE_BUCKET_CORS
case s3_constants.ACTION_PUT_BUCKET_VERSIONING:
return s3_constants.S3_ACTION_PUT_BUCKET_VERSIONING
case s3_constants.ACTION_PUT_BUCKET_QUOTA:
return s3_constants.S3_ACTION_PUT_BUCKET_QUOTA
case s3_constants.ACTION_GET_BUCKET_QUOTA:
+3
View File
@@ -18,6 +18,9 @@ const (
ACTION_PUT_BUCKET_OBJECT_LOCK_CONFIG = "PutBucketObjectLockConfiguration"
ACTION_PUT_BUCKET_POLICY = "PutBucketPolicy"
ACTION_DELETE_BUCKET_POLICY = "DeleteBucketPolicy"
ACTION_PUT_BUCKET_CORS = "PutBucketCors"
ACTION_DELETE_BUCKET_CORS = "DeleteBucketCors"
ACTION_PUT_BUCKET_VERSIONING = "PutBucketVersioning"
ACTION_PUT_BUCKET_QUOTA = "PutBucketQuota"
ACTION_GET_BUCKET_QUOTA = "GetBucketQuota"
// Scoped to their single operation: the multipart listing routes are
+3 -3
View File
@@ -1099,9 +1099,9 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
// GetBucketCors
bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketCorsHandler, ACTION_READ)), "GET")).Queries("cors", "")
// PutBucketCors
bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketCorsHandler, ACTION_WRITE)), "PUT")).Queries("cors", "")
bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketCorsHandler, ACTION_PUT_BUCKET_CORS)), "PUT")).Queries("cors", "")
// DeleteBucketCors
bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteBucketCorsHandler, ACTION_WRITE)), "DELETE")).Queries("cors", "")
bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteBucketCorsHandler, ACTION_DELETE_BUCKET_CORS)), "DELETE")).Queries("cors", "")
// GetBucketLifecycleConfiguration
bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketLifecycleConfigurationHandler, ACTION_READ)), "GET")).Queries("lifecycle", "")
@@ -1134,7 +1134,7 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
// GetBucketVersioning
bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketVersioningHandler, ACTION_READ)), "GET")).Queries("versioning", "")
bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketVersioningHandler, ACTION_WRITE)), "PUT")).Queries("versioning", "")
bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketVersioningHandler, ACTION_PUT_BUCKET_VERSIONING)), "PUT")).Queries("versioning", "")
// GetObjectLockConfiguration / PutObjectLockConfiguration (bucket-level operations)
bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetObjectLockConfigurationHandler, ACTION_GET_BUCKET_OBJECT_LOCK_CONFIG)), "GET")).Queries("object-lock", "")
+77
View File
@@ -588,3 +588,80 @@ func TestRouting_BucketLifecycleWritesNeedAdmin(t *testing.T) {
}
}
}
func TestRouting_BucketCorsAndVersioningWritesNeedTheirActions(t *testing.T) {
const bucket = "config-bucket"
configFile := filepath.Join(t.TempDir(), "s3.json")
require.NoError(t, os.WriteFile(configFile, []byte(`{
"identities": [
{"name":"writer","credentials":[{"accessKey":"WRITERKEY","secretKey":"writersecret"}],"actions":["Read:`+bucket+`","Write:`+bucket+`","List:`+bucket+`","Tagging:`+bucket+`"]},
{"name":"bucketadmin","credentials":[{"accessKey":"BUCKETADMINKEY","secretKey":"bucketadminsecret"}],"actions":["Admin:`+bucket+`"]},
{"name":"corsputter","credentials":[{"accessKey":"CORSPUTKEY","secretKey":"corsputsecret"}],"actions":["PutBucketCors:`+bucket+`"]},
{"name":"corsdeleter","credentials":[{"accessKey":"CORSDELKEY","secretKey":"corsdelsecret"}],"actions":["DeleteBucketCors:`+bucket+`"]},
{"name":"versioner","credentials":[{"accessKey":"VERSIONERKEY","secretKey":"versionersecret"}],"actions":["PutBucketVersioning:`+bucket+`"]},
{"name":"policyuser","credentials":[{"accessKey":"POLICYUSERKEY","secretKey":"policyusersecret"}],"policyNames":["ConfigPolicy"]}
],
"policies": [
{"name":"ConfigPolicy","content":"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:PutBucketCors\",\"s3:DeleteBucketCors\",\"s3:PutBucketVersioning\"],\"Resource\":[\"arn:aws:s3:::`+bucket+`\"]}]}"}
]
}`), 0600))
s3a := setupRoutingTestServer(t)
s3a.iam = NewIdentityAccessManagementWithStore(&S3ApiServerOption{Config: configFile}, nil, "memory")
require.True(t, s3a.iam.isEnabled())
s3a.cb = &CircuitBreaker{s3a: s3a}
s3a.bucketConfigCache = NewBucketConfigCache(time.Minute)
s3a.bucketConfigCache.SetNegativeCache(bucket)
router := mux.NewRouter()
s3a.registerRouter(router)
send := func(accessKey, secretKey, method, query, body string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, "http://localhost:8333/"+bucket+"?"+query, strings.NewReader(body))
creds := credentials.NewStaticCredentials(accessKey, secretKey, "")
_, err := v4.NewSigner(creds).Sign(req, strings.NewReader(body), "s3", "us-east-1", time.Now())
require.NoError(t, err)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
return rr
}
const corsXML = `<CORSConfiguration><CORSRule><AllowedOrigin>*</AllowedOrigin><AllowedMethod>GET</AllowedMethod></CORSRule></CORSConfiguration>`
const versioningXML = `<VersioningConfiguration><Status>Suspended</Status></VersioningConfiguration>`
singleActionKeys := map[string]string{"CORSPUTKEY": "corsputsecret", "CORSDELKEY": "corsdelsecret", "VERSIONERKEY": "versionersecret"}
for _, tc := range []struct {
method, query, body, allowedKey string
}{
{http.MethodPut, "cors", corsXML, "CORSPUTKEY"},
{http.MethodDelete, "cors", "", "CORSDELKEY"},
{http.MethodPut, "versioning", versioningXML, "VERSIONERKEY"},
} {
denied := [][2]string{{"WRITERKEY", "writersecret"}}
allowed := [][2]string{{"BUCKETADMINKEY", "bucketadminsecret"}, {"POLICYUSERKEY", "policyusersecret"}}
for key, secret := range singleActionKeys {
if key == tc.allowedKey {
allowed = append(allowed, [2]string{key, secret})
} else {
denied = append(denied, [2]string{key, secret})
}
}
for _, key := range denied {
rr := send(key[0], key[1], tc.method, tc.query, tc.body)
assert.Equal(t, http.StatusForbidden, rr.Code, "%s %s ?%s", key[0], tc.method, tc.query)
assert.Contains(t, rr.Body.String(), "AccessDenied", "%s %s ?%s", key[0], tc.method, tc.query)
}
for _, key := range allowed {
rr := send(key[0], key[1], tc.method, tc.query, tc.body)
assert.Equal(t, http.StatusNotFound, rr.Code, "%s %s ?%s", key[0], tc.method, tc.query)
assert.Contains(t, rr.Body.String(), "NoSuchBucket", "%s %s ?%s", key[0], tc.method, tc.query)
}
}
for policyAction, identityAction := range map[string]string{
"s3:PutBucketCors": s3_constants.ACTION_PUT_BUCKET_CORS,
"s3:DeleteBucketCors": s3_constants.ACTION_DELETE_BUCKET_CORS,
"s3:PutBucketVersioning": s3_constants.ACTION_PUT_BUCKET_VERSIONING,
} {
assert.Equal(t, identityAction, iamMapToStatementAction(policyAction), policyAction)
assert.Equal(t, policyAction, "s3:"+iamMapToIdentitiesAction(identityAction), identityAction)
}
}