You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

711 lines
28 KiB

  1. /*
  2. * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cmd
  17. import (
  18. "bytes"
  19. "encoding/json"
  20. "errors"
  21. "fmt"
  22. "testing"
  23. "github.com/minio/minio-go/pkg/set"
  24. )
  25. // Common bucket actions for both read and write policies.
  26. var (
  27. readWriteBucketActions = []string{
  28. "s3:GetBucketLocation",
  29. "s3:ListBucket",
  30. "s3:ListBucketMultipartUploads",
  31. // Add more bucket level read-write actions here.
  32. }
  33. readWriteObjectActions = []string{
  34. "s3:AbortMultipartUpload",
  35. "s3:DeleteObject",
  36. "s3:GetObject",
  37. "s3:ListMultipartUploadParts",
  38. "s3:PutObject",
  39. // Add more object level read-write actions here.
  40. }
  41. )
  42. // Write only actions.
  43. var (
  44. writeOnlyBucketActions = []string{
  45. "s3:GetBucketLocation",
  46. "s3:ListBucketMultipartUploads",
  47. // Add more bucket level write actions here.
  48. }
  49. writeOnlyObjectActions = []string{
  50. "s3:AbortMultipartUpload",
  51. "s3:DeleteObject",
  52. "s3:ListMultipartUploadParts",
  53. "s3:PutObject",
  54. // Add more object level write actions here.
  55. }
  56. )
  57. // Read only actions.
  58. var (
  59. readOnlyBucketActions = []string{
  60. "s3:GetBucketLocation",
  61. "s3:ListBucket",
  62. // Add more bucket level read actions here.
  63. }
  64. readOnlyObjectActions = []string{
  65. "s3:GetObject",
  66. // Add more object level read actions here.
  67. }
  68. )
  69. // Obtain bucket statement for read-write bucketPolicy.
  70. func getReadWriteObjectStatement(bucketName, objectPrefix string) policyStatement {
  71. objectResourceStatement := policyStatement{}
  72. objectResourceStatement.Effect = "Allow"
  73. objectResourceStatement.Principal = map[string]interface{}{
  74. "AWS": "*",
  75. }
  76. objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
  77. objectResourceStatement.Actions = set.CreateStringSet(readWriteObjectActions...)
  78. return objectResourceStatement
  79. }
  80. // Obtain object statement for read-write bucketPolicy.
  81. func getReadWriteBucketStatement(bucketName, objectPrefix string) policyStatement {
  82. bucketResourceStatement := policyStatement{}
  83. bucketResourceStatement.Effect = "Allow"
  84. bucketResourceStatement.Principal = map[string]interface{}{
  85. "AWS": "*",
  86. }
  87. bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
  88. bucketResourceStatement.Actions = set.CreateStringSet(readWriteBucketActions...)
  89. return bucketResourceStatement
  90. }
  91. // Obtain statements for read-write bucketPolicy.
  92. func getReadWriteStatement(bucketName, objectPrefix string) []policyStatement {
  93. statements := []policyStatement{}
  94. // Save the read write policy.
  95. statements = append(statements, getReadWriteBucketStatement(bucketName, objectPrefix), getReadWriteObjectStatement(bucketName, objectPrefix))
  96. return statements
  97. }
  98. // Obtain bucket statement for read only bucketPolicy.
  99. func getReadOnlyBucketStatement(bucketName, objectPrefix string) policyStatement {
  100. bucketResourceStatement := policyStatement{}
  101. bucketResourceStatement.Effect = "Allow"
  102. bucketResourceStatement.Principal = map[string]interface{}{
  103. "AWS": "*",
  104. }
  105. bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
  106. bucketResourceStatement.Actions = set.CreateStringSet(readOnlyBucketActions...)
  107. return bucketResourceStatement
  108. }
  109. // Obtain object statement for read only bucketPolicy.
  110. func getReadOnlyObjectStatement(bucketName, objectPrefix string) policyStatement {
  111. objectResourceStatement := policyStatement{}
  112. objectResourceStatement.Effect = "Allow"
  113. objectResourceStatement.Principal = map[string]interface{}{
  114. "AWS": "*",
  115. }
  116. objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
  117. objectResourceStatement.Actions = set.CreateStringSet(readOnlyObjectActions...)
  118. return objectResourceStatement
  119. }
  120. // Obtain statements for read only bucketPolicy.
  121. func getReadOnlyStatement(bucketName, objectPrefix string) []policyStatement {
  122. statements := []policyStatement{}
  123. // Save the read only policy.
  124. statements = append(statements, getReadOnlyBucketStatement(bucketName, objectPrefix), getReadOnlyObjectStatement(bucketName, objectPrefix))
  125. return statements
  126. }
  127. // Obtain bucket statements for write only bucketPolicy.
  128. func getWriteOnlyBucketStatement(bucketName, objectPrefix string) policyStatement {
  129. bucketResourceStatement := policyStatement{}
  130. bucketResourceStatement.Effect = "Allow"
  131. bucketResourceStatement.Principal = map[string]interface{}{
  132. "AWS": "*",
  133. }
  134. bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...)
  135. bucketResourceStatement.Actions = set.CreateStringSet(writeOnlyBucketActions...)
  136. return bucketResourceStatement
  137. }
  138. // Obtain object statements for write only bucketPolicy.
  139. func getWriteOnlyObjectStatement(bucketName, objectPrefix string) policyStatement {
  140. objectResourceStatement := policyStatement{}
  141. objectResourceStatement.Effect = "Allow"
  142. objectResourceStatement.Principal = map[string]interface{}{
  143. "AWS": "*",
  144. }
  145. objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...)
  146. objectResourceStatement.Actions = set.CreateStringSet(writeOnlyObjectActions...)
  147. return objectResourceStatement
  148. }
  149. // Obtain statements for write only bucketPolicy.
  150. func getWriteOnlyStatement(bucketName, objectPrefix string) []policyStatement {
  151. statements := []policyStatement{}
  152. // Write only policy.
  153. // Save the write only policy.
  154. statements = append(statements, getWriteOnlyBucketStatement(bucketName, objectPrefix), getWriteOnlyBucketStatement(bucketName, objectPrefix))
  155. return statements
  156. }
  157. // Tests validate Action validator.
  158. func TestIsValidActions(t *testing.T) {
  159. testCases := []struct {
  160. // input.
  161. actions set.StringSet
  162. // expected output.
  163. err error
  164. // flag indicating whether the test should pass.
  165. shouldPass bool
  166. }{
  167. // Inputs with unsupported Action.
  168. // Test case - 1.
  169. // "s3:ListObject" is an invalid Action.
  170. {set.CreateStringSet([]string{"s3:GetObject", "s3:ListObject", "s3:RemoveObject"}...),
  171. errors.New("Unsupported actions found: ‘set.StringSet{\"s3:RemoveObject\":struct {}{}, \"s3:ListObject\":struct {}{}}’, please validate your policy document."), false},
  172. // Test case - 2.
  173. // Empty Actions.
  174. {set.CreateStringSet([]string{}...), errors.New("Action list cannot be empty."), false},
  175. // Test case - 3.
  176. // "s3:DeleteEverything"" is an invalid Action.
  177. {set.CreateStringSet([]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}...),
  178. errors.New("Unsupported actions found: ‘set.StringSet{\"s3:DeleteEverything\":struct {}{}}’, please validate your policy document."), false},
  179. // Inputs with valid Action.
  180. // Test Case - 4.
  181. {set.CreateStringSet([]string{
  182. "s3:*", "*", "s3:GetObject", "s3:ListBucket",
  183. "s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject",
  184. "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads",
  185. "s3:ListMultipartUploadParts"}...), nil, true},
  186. }
  187. for i, testCase := range testCases {
  188. err := isValidActions(testCase.actions)
  189. if err != nil && testCase.shouldPass {
  190. t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
  191. }
  192. if err == nil && !testCase.shouldPass {
  193. t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
  194. }
  195. }
  196. }
  197. // Tests validate Effect validator.
  198. func TestIsValidEffect(t *testing.T) {
  199. testCases := []struct {
  200. // input.
  201. effect string
  202. // expected output.
  203. err error
  204. // flag indicating whether the test should pass.
  205. shouldPass bool
  206. }{
  207. // Inputs with unsupported Effect.
  208. // Test case - 1.
  209. {"", errors.New("Policy effect cannot be empty."), false},
  210. // Test case - 2.
  211. {"DontAllow", errors.New("Unsupported Effect found: ‘DontAllow’, please validate your policy document."), false},
  212. // Test case - 3.
  213. {"NeverAllow", errors.New("Unsupported Effect found: ‘NeverAllow’, please validate your policy document."), false},
  214. // Test case - 4.
  215. {"AllowAlways", errors.New("Unsupported Effect found: ‘AllowAlways’, please validate your policy document."), false},
  216. // Inputs with valid Effect.
  217. // Test Case - 5.
  218. {"Allow", nil, true},
  219. // Test Case - 6.
  220. {"Deny", nil, true},
  221. }
  222. for i, testCase := range testCases {
  223. err := isValidEffect(testCase.effect)
  224. if err != nil && testCase.shouldPass {
  225. t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
  226. }
  227. if err == nil && !testCase.shouldPass {
  228. t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
  229. }
  230. // Failed as expected, but does it fail for the expected reason.
  231. if err != nil && !testCase.shouldPass {
  232. if err.Error() != testCase.err.Error() {
  233. t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
  234. }
  235. }
  236. }
  237. }
  238. // Tests validate Resources validator.
  239. func TestIsValidResources(t *testing.T) {
  240. testCases := []struct {
  241. // input.
  242. resources []string
  243. // expected output.
  244. err error
  245. // flag indicating whether the test should pass.
  246. shouldPass bool
  247. }{
  248. // Inputs with unsupported Action.
  249. // Test case - 1.
  250. // Empty Resources.
  251. {[]string{}, errors.New("Resource list cannot be empty."), false},
  252. // Test case - 2.
  253. // A valid resource should have prefix "arn:aws:s3:::".
  254. {[]string{"my-resource"}, errors.New("Unsupported resource style found: ‘my-resource’, please validate your policy document."), false},
  255. // Test case - 3.
  256. // A Valid resource should have bucket name followed by "arn:aws:s3:::".
  257. {[]string{"arn:aws:s3:::"}, errors.New("Invalid resource style found: ‘arn:aws:s3:::’, please validate your policy document."), false},
  258. // Test Case - 4.
  259. // Valid resource shouldn't have slash('/') followed by "arn:aws:s3:::".
  260. {[]string{"arn:aws:s3:::/"}, errors.New("Invalid resource style found: ‘arn:aws:s3:::/’, please validate your policy document."), false},
  261. // Test cases with valid Resources.
  262. {[]string{"arn:aws:s3:::my-bucket"}, nil, true},
  263. {[]string{"arn:aws:s3:::my-bucket/Asia/*"}, nil, true},
  264. {[]string{"arn:aws:s3:::my-bucket/Asia/India/*"}, nil, true},
  265. }
  266. for i, testCase := range testCases {
  267. err := isValidResources(set.CreateStringSet(testCase.resources...))
  268. if err != nil && testCase.shouldPass {
  269. t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
  270. }
  271. if err == nil && !testCase.shouldPass {
  272. t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
  273. }
  274. // Failed as expected, but does it fail for the expected reason.
  275. if err != nil && !testCase.shouldPass {
  276. if err.Error() != testCase.err.Error() {
  277. t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
  278. }
  279. }
  280. }
  281. }
  282. // Tests validate principals validator.
  283. func TestIsValidPrincipals(t *testing.T) {
  284. testCases := []struct {
  285. // input.
  286. principals []string
  287. // expected output.
  288. err error
  289. // flag indicating whether the test should pass.
  290. shouldPass bool
  291. }{
  292. // Inputs with unsupported Principals.
  293. // Test case - 1.
  294. // Empty Principals list.
  295. {[]string{}, errors.New("Principal cannot be empty."), false},
  296. // Test case - 2.
  297. // "*" is the only valid principal.
  298. {[]string{"my-principal"}, errors.New("Unsupported principals found: ‘set.StringSet{\"my-principal\":struct {}{}}’, please validate your policy document."), false},
  299. // Test case - 3.
  300. {[]string{"*", "111122233"}, errors.New("Unsupported principals found: ‘set.StringSet{\"111122233\":struct {}{}}’, please validate your policy document."), false},
  301. // Test case - 4.
  302. // Test case with valid principal value.
  303. {[]string{"*"}, nil, true},
  304. }
  305. for i, testCase := range testCases {
  306. err := isValidPrincipals(map[string]interface{}{
  307. "AWS": testCase.principals,
  308. })
  309. if err != nil && testCase.shouldPass {
  310. t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
  311. }
  312. if err == nil && !testCase.shouldPass {
  313. t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
  314. }
  315. // Failed as expected, but does it fail for the expected reason.
  316. if err != nil && !testCase.shouldPass {
  317. if err.Error() != testCase.err.Error() {
  318. t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
  319. }
  320. }
  321. }
  322. }
  323. // Tests validate policyStatement condition validator.
  324. func TestIsValidConditions(t *testing.T) {
  325. // returns empty conditions map.
  326. setEmptyConditions := func() map[string]map[string]set.StringSet {
  327. return make(map[string]map[string]set.StringSet)
  328. }
  329. // returns map with the "StringEquals" set to empty map.
  330. setEmptyStringEquals := func() map[string]map[string]set.StringSet {
  331. emptyMap := make(map[string]set.StringSet)
  332. conditions := make(map[string]map[string]set.StringSet)
  333. conditions["StringEquals"] = emptyMap
  334. return conditions
  335. }
  336. // returns map with the "StringNotEquals" set to empty map.
  337. setEmptyStringNotEquals := func() map[string]map[string]set.StringSet {
  338. emptyMap := make(map[string]set.StringSet)
  339. conditions := make(map[string]map[string]set.StringSet)
  340. conditions["StringNotEquals"] = emptyMap
  341. return conditions
  342. }
  343. // Generate conditions.
  344. generateConditions := func(key1, key2, value string) map[string]map[string]set.StringSet {
  345. innerMap := make(map[string]set.StringSet)
  346. innerMap[key2] = set.CreateStringSet(value)
  347. conditions := make(map[string]map[string]set.StringSet)
  348. conditions[key1] = innerMap
  349. return conditions
  350. }
  351. // generate ambigious conditions.
  352. generateAmbigiousConditions := func() map[string]map[string]set.StringSet {
  353. innerMap := make(map[string]set.StringSet)
  354. innerMap["s3:prefix"] = set.CreateStringSet("Asia/")
  355. conditions := make(map[string]map[string]set.StringSet)
  356. conditions["StringEquals"] = innerMap
  357. conditions["StringNotEquals"] = innerMap
  358. return conditions
  359. }
  360. // generate valid and non valid type in the condition map.
  361. generateValidInvalidConditions := func() map[string]map[string]set.StringSet {
  362. innerMap := make(map[string]set.StringSet)
  363. innerMap["s3:prefix"] = set.CreateStringSet("Asia/")
  364. conditions := make(map[string]map[string]set.StringSet)
  365. conditions["StringEquals"] = innerMap
  366. conditions["InvalidType"] = innerMap
  367. return conditions
  368. }
  369. // generate valid and invalid keys for valid types in the same condition map.
  370. generateValidInvalidConditionKeys := func() map[string]map[string]set.StringSet {
  371. innerMapValid := make(map[string]set.StringSet)
  372. innerMapValid["s3:prefix"] = set.CreateStringSet("Asia/")
  373. innerMapInValid := make(map[string]set.StringSet)
  374. innerMapInValid["s3:invalid"] = set.CreateStringSet("Asia/")
  375. conditions := make(map[string]map[string]set.StringSet)
  376. conditions["StringEquals"] = innerMapValid
  377. conditions["StringEquals"] = innerMapInValid
  378. return conditions
  379. }
  380. // List of Conditions used for test cases.
  381. testConditions := []map[string]map[string]set.StringSet{
  382. generateConditions("StringValues", "s3:max-keys", "100"),
  383. generateConditions("StringEquals", "s3:Object", "100"),
  384. generateAmbigiousConditions(),
  385. generateValidInvalidConditions(),
  386. generateValidInvalidConditionKeys(),
  387. setEmptyConditions(),
  388. setEmptyStringEquals(),
  389. setEmptyStringNotEquals(),
  390. generateConditions("StringEquals", "s3:prefix", "Asia/"),
  391. generateConditions("StringEquals", "s3:max-keys", "100"),
  392. generateConditions("StringNotEquals", "s3:prefix", "Asia/"),
  393. generateConditions("StringNotEquals", "s3:max-keys", "100"),
  394. }
  395. testCases := []struct {
  396. inputCondition map[string]map[string]set.StringSet
  397. // expected result.
  398. expectedErr error
  399. // flag indicating whether test should pass.
  400. shouldPass bool
  401. }{
  402. // Malformed conditions.
  403. // Test case - 1.
  404. // "StringValues" is an invalid type.
  405. {testConditions[0], fmt.Errorf("Unsupported condition type 'StringValues', " +
  406. "please validate your policy document."), false},
  407. // Test case - 2.
  408. // "s3:Object" is an invalid key.
  409. {testConditions[1], fmt.Errorf("Unsupported condition key " +
  410. "'StringEquals', please validate your policy document."), false},
  411. // Test case - 3.
  412. // Test case with Ambigious conditions set.
  413. {testConditions[2], fmt.Errorf("Ambigious condition values for key 's3:prefix', " +
  414. "please validate your policy document."), false},
  415. // Test case - 4.
  416. // Test case with valid and invalid condition types.
  417. {testConditions[3], fmt.Errorf("Unsupported condition type 'InvalidType', " +
  418. "please validate your policy document."), false},
  419. // Test case - 5.
  420. // Test case with valid and invalid condition keys.
  421. {testConditions[4], fmt.Errorf("Unsupported condition key 'StringEquals', " +
  422. "please validate your policy document."), false},
  423. // Test cases with valid conditions.
  424. // Test case - 6.
  425. {testConditions[5], nil, true},
  426. // Test case - 7.
  427. {testConditions[6], nil, true},
  428. // Test case - 8.
  429. {testConditions[7], nil, true},
  430. // Test case - 9.
  431. {testConditions[8], nil, true},
  432. // Test case - 10.
  433. {testConditions[9], nil, true},
  434. // Test case - 11.
  435. {testConditions[10], nil, true},
  436. // Test case 10.
  437. {testConditions[11], nil, true},
  438. }
  439. for i, testCase := range testCases {
  440. actualErr := isValidConditions(testCase.inputCondition)
  441. if actualErr != nil && testCase.shouldPass {
  442. t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, actualErr.Error())
  443. }
  444. if actualErr == nil && !testCase.shouldPass {
  445. t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.expectedErr.Error())
  446. }
  447. // Failed as expected, but does it fail for the expected reason.
  448. if actualErr != nil && !testCase.shouldPass {
  449. if actualErr.Error() != testCase.expectedErr.Error() {
  450. t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.expectedErr.Error(), actualErr.Error())
  451. }
  452. }
  453. }
  454. }
  455. // Tests validate Policy Action and Resource fields.
  456. func TestCheckbucketPolicyResources(t *testing.T) {
  457. // constructing policy statement without invalidPrefixActions (check bucket-policy-parser.go).
  458. setValidPrefixActions := func(statements []policyStatement) []policyStatement {
  459. statements[0].Actions = set.CreateStringSet([]string{"s3:DeleteObject", "s3:PutObject"}...)
  460. return statements
  461. }
  462. // contracting policy statement with recursive resources.
  463. // should result in ErrMalformedPolicy
  464. setRecurseResource := func(statements []policyStatement) []policyStatement {
  465. statements[0].Resources = set.CreateStringSet([]string{"arn:aws:s3:::minio-bucket/Asia/*", "arn:aws:s3:::minio-bucket/Asia/India/*"}...)
  466. return statements
  467. }
  468. // constructing policy statement with lexically close characters.
  469. // should not result in ErrMalformedPolicy
  470. setResourceLexical := func(statements []policyStatement) []policyStatement {
  471. statements[0].Resources = set.CreateStringSet([]string{"arn:aws:s3:::minio-bucket/op*", "arn:aws:s3:::minio-bucket/oo*"}...)
  472. return statements
  473. }
  474. // List of bucketPolicy used for tests.
  475. bucketAccessPolicies := []bucketPolicy{
  476. // bucketPolicy - 1.
  477. // Contains valid read only policy statement.
  478. {Version: "1.0", Statements: getReadOnlyStatement("minio-bucket", "")},
  479. // bucketPolicy - 2.
  480. // Contains valid read-write only policy statement.
  481. {Version: "1.0", Statements: getReadWriteStatement("minio-bucket", "Asia/")},
  482. // bucketPolicy - 3.
  483. // Contains valid write only policy statement.
  484. {Version: "1.0", Statements: getWriteOnlyStatement("minio-bucket", "Asia/India/")},
  485. // bucketPolicy - 4.
  486. // Contains invalidPrefixActions.
  487. // Since resourcePrefix is not to the bucket-name, it return ErrMalformedPolicy.
  488. {Version: "1.0", Statements: getReadOnlyStatement("minio-bucket-fail", "Asia/India/")},
  489. // bucketPolicy - 5.
  490. // constructing policy statement without invalidPrefixActions (check bucket-policy-parser.go).
  491. // but bucket part of the resource is not equal to the bucket name.
  492. // this results in return of ErrMalformedPolicy.
  493. {Version: "1.0", Statements: setValidPrefixActions(getWriteOnlyStatement("minio-bucket-fail", "Asia/India/"))},
  494. // bucketPolicy - 6.
  495. // contracting policy statement with recursive resources.
  496. // should result in ErrMalformedPolicy
  497. {Version: "1.0", Statements: setRecurseResource(setValidPrefixActions(getWriteOnlyStatement("minio-bucket", "")))},
  498. // BucketPolciy - 7.
  499. // constructing policy statement with non recursive but
  500. // lexically close resources.
  501. // should result in ErrNone.
  502. {Version: "1.0", Statements: setResourceLexical(setValidPrefixActions(getWriteOnlyStatement("minio-bucket", "oo")))},
  503. }
  504. testCases := []struct {
  505. inputPolicy bucketPolicy
  506. // expected results.
  507. apiErrCode APIErrorCode
  508. // Flag indicating whether the test should pass.
  509. shouldPass bool
  510. }{
  511. // Test case - 1.
  512. {bucketAccessPolicies[0], ErrNone, true},
  513. // Test case - 2.
  514. {bucketAccessPolicies[1], ErrNone, true},
  515. // Test case - 3.
  516. {bucketAccessPolicies[2], ErrNone, true},
  517. // Test case - 4.
  518. // contains invalidPrefixActions (check bucket-policy-parser.go).
  519. // Resource prefix will not be equal to the bucket name in this case.
  520. {bucketAccessPolicies[3], ErrMalformedPolicy, false},
  521. // Test case - 5.
  522. // actions contain invalidPrefixActions (check bucket-policy-parser.go).
  523. // Resource prefix bucket part is not equal to the bucket name in this case.
  524. {bucketAccessPolicies[4], ErrMalformedPolicy, false},
  525. // Test case - 6.
  526. // contracting policy statement with recursive resources.
  527. // should result in ErrPolicyNesting.
  528. {bucketAccessPolicies[5], ErrPolicyNesting, false},
  529. // Test case - 7.
  530. // constructing policy statement with lexically close
  531. // characters.
  532. // should result in ErrNone.
  533. {bucketAccessPolicies[6], ErrNone, true},
  534. }
  535. for i, testCase := range testCases {
  536. apiErrCode := checkBucketPolicyResources("minio-bucket", &testCase.inputPolicy)
  537. if apiErrCode != ErrNone && testCase.shouldPass {
  538. t.Errorf("Test %d: Expected to pass, but failed with Errocode %v", i+1, apiErrCode)
  539. }
  540. if apiErrCode == ErrNone && !testCase.shouldPass {
  541. t.Errorf("Test %d: Expected to fail with ErrCode %v, but passed instead", i+1, testCase.apiErrCode)
  542. }
  543. // Failed as expected, but does it fail for the expected reason.
  544. if apiErrCode != ErrNone && !testCase.shouldPass {
  545. if testCase.apiErrCode != apiErrCode {
  546. t.Errorf("Test %d: Expected to fail with error code %v, but instead failed with error code %v", i+1, testCase.apiErrCode, apiErrCode)
  547. }
  548. }
  549. }
  550. }
  551. // Tests validate parsing of BucketAccessPolicy.
  552. func TestParseBucketPolicy(t *testing.T) {
  553. // set Unsupported Actions.
  554. setUnsupportedActions := func(statements []policyStatement) []policyStatement {
  555. // "s3:DeleteEverything"" is an Unsupported Action.
  556. statements[0].Actions = set.CreateStringSet([]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}...)
  557. return statements
  558. }
  559. // set unsupported Effect.
  560. setUnsupportedEffect := func(statements []policyStatement) []policyStatement {
  561. // Effect "Don't allow" is Unsupported.
  562. statements[0].Effect = "DontAllow"
  563. return statements
  564. }
  565. // set unsupported principals.
  566. setUnsupportedPrincipals := func(statements []policyStatement) []policyStatement {
  567. // "User1111"" is an Unsupported Principal.
  568. statements[0].Principal = map[string]interface{}{
  569. "AWS": []string{"*", "User1111"},
  570. }
  571. return statements
  572. }
  573. // set unsupported Resources.
  574. setUnsupportedResources := func(statements []policyStatement) []policyStatement {
  575. // "s3:DeleteEverything"" is an Unsupported Action.
  576. statements[0].Resources = set.CreateStringSet([]string{"my-resource"}...)
  577. return statements
  578. }
  579. // List of bucketPolicy used for test cases.
  580. bucketAccesPolicies := []bucketPolicy{
  581. // bucketPolicy - 0.
  582. // bucketPolicy statement empty.
  583. {Version: "1.0"},
  584. // bucketPolicy - 1.
  585. // bucketPolicy version empty.
  586. {Version: "", Statements: []policyStatement{}},
  587. // bucketPolicy - 2.
  588. // Readonly bucketPolicy.
  589. {Version: "1.0", Statements: getReadOnlyStatement("minio-bucket", "")},
  590. // bucketPolicy - 3.
  591. // Read-Write bucket policy.
  592. {Version: "1.0", Statements: getReadWriteStatement("minio-bucket", "Asia/")},
  593. // bucketPolicy - 4.
  594. // Write only bucket policy.
  595. {Version: "1.0", Statements: getWriteOnlyStatement("minio-bucket", "Asia/India/")},
  596. // bucketPolicy - 5.
  597. // bucketPolicy statement contains unsupported action.
  598. {Version: "1.0", Statements: setUnsupportedActions(getReadOnlyStatement("minio-bucket", ""))},
  599. // bucketPolicy - 6.
  600. // bucketPolicy statement contains unsupported Effect.
  601. {Version: "1.0", Statements: setUnsupportedEffect(getReadWriteStatement("minio-bucket", "Asia/"))},
  602. // bucketPolicy - 7.
  603. // bucketPolicy statement contains unsupported Principal.
  604. {Version: "1.0", Statements: setUnsupportedPrincipals(getWriteOnlyStatement("minio-bucket", "Asia/India/"))},
  605. // bucketPolicy - 8.
  606. // bucketPolicy statement contains unsupported Resource.
  607. {Version: "1.0", Statements: setUnsupportedResources(getWriteOnlyStatement("minio-bucket", "Asia/India/"))},
  608. }
  609. testCases := []struct {
  610. inputPolicy bucketPolicy
  611. // expected results.
  612. expectedPolicy bucketPolicy
  613. err error
  614. // Flag indicating whether the test should pass.
  615. shouldPass bool
  616. }{
  617. // Test case - 1.
  618. // bucketPolicy statement empty.
  619. {bucketAccesPolicies[0], bucketPolicy{}, errors.New("Policy statement cannot be empty."), false},
  620. // Test case - 2.
  621. // bucketPolicy version empty.
  622. {bucketAccesPolicies[1], bucketPolicy{}, errors.New("Policy version cannot be empty."), false},
  623. // Test case - 3.
  624. // Readonly bucketPolicy.
  625. {bucketAccesPolicies[2], bucketAccesPolicies[2], nil, true},
  626. // Test case - 4.
  627. // Read-Write bucket policy.
  628. {bucketAccesPolicies[3], bucketAccesPolicies[3], nil, true},
  629. // Test case - 5.
  630. // Write only bucket policy.
  631. {bucketAccesPolicies[4], bucketAccesPolicies[4], nil, true},
  632. // Test case - 6.
  633. // bucketPolicy statement contains unsupported action.
  634. {bucketAccesPolicies[5], bucketAccesPolicies[5], fmt.Errorf("Unsupported actions found: ‘set.StringSet{\"s3:DeleteEverything\":struct {}{}}’, please validate your policy document."), false},
  635. // Test case - 7.
  636. // bucketPolicy statement contains unsupported Effect.
  637. {bucketAccesPolicies[6], bucketAccesPolicies[6], fmt.Errorf("Unsupported Effect found: ‘DontAllow’, please validate your policy document."), false},
  638. // Test case - 8.
  639. // bucketPolicy statement contains unsupported Principal.
  640. {bucketAccesPolicies[7], bucketAccesPolicies[7], fmt.Errorf("Unsupported principals found: ‘set.StringSet{\"User1111\":struct {}{}}’, please validate your policy document."), false},
  641. // Test case - 9.
  642. // bucketPolicy statement contains unsupported Resource.
  643. {bucketAccesPolicies[8], bucketAccesPolicies[8], fmt.Errorf("Unsupported resource style found: ‘my-resource’, please validate your policy document."), false},
  644. }
  645. for i, testCase := range testCases {
  646. var buffer bytes.Buffer
  647. encoder := json.NewEncoder(&buffer)
  648. err := encoder.Encode(testCase.inputPolicy)
  649. if err != nil {
  650. t.Fatalf("Test %d: Couldn't Marshal bucket policy %s", i+1, err)
  651. }
  652. var actualAccessPolicy = &bucketPolicy{}
  653. err = parseBucketPolicy(&buffer, actualAccessPolicy)
  654. if err != nil && testCase.shouldPass {
  655. t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
  656. }
  657. if err == nil && !testCase.shouldPass {
  658. t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
  659. }
  660. // Failed as expected, but does it fail for the expected reason.
  661. if err != nil && !testCase.shouldPass {
  662. if err.Error() != testCase.err.Error() {
  663. t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
  664. }
  665. }
  666. // Test passes as expected, but the output values are verified for correctness here.
  667. if err == nil && testCase.shouldPass {
  668. if testCase.expectedPolicy.String() != actualAccessPolicy.String() {
  669. t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1)
  670. }
  671. }
  672. }
  673. }