s3_policy_templates_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. package s3api
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/require"
  7. )
  8. func TestS3PolicyTemplates(t *testing.T) {
  9. templates := NewS3PolicyTemplates()
  10. t.Run("S3ReadOnlyPolicy", func(t *testing.T) {
  11. policy := templates.GetS3ReadOnlyPolicy()
  12. require.NotNil(t, policy)
  13. assert.Equal(t, "2012-10-17", policy.Version)
  14. assert.Len(t, policy.Statement, 1)
  15. stmt := policy.Statement[0]
  16. assert.Equal(t, "Allow", stmt.Effect)
  17. assert.Equal(t, "S3ReadOnlyAccess", stmt.Sid)
  18. assert.Contains(t, stmt.Action, "s3:GetObject")
  19. assert.Contains(t, stmt.Action, "s3:ListBucket")
  20. assert.NotContains(t, stmt.Action, "s3:PutObject")
  21. assert.NotContains(t, stmt.Action, "s3:DeleteObject")
  22. assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
  23. assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
  24. })
  25. t.Run("S3WriteOnlyPolicy", func(t *testing.T) {
  26. policy := templates.GetS3WriteOnlyPolicy()
  27. require.NotNil(t, policy)
  28. assert.Equal(t, "2012-10-17", policy.Version)
  29. assert.Len(t, policy.Statement, 1)
  30. stmt := policy.Statement[0]
  31. assert.Equal(t, "Allow", stmt.Effect)
  32. assert.Equal(t, "S3WriteOnlyAccess", stmt.Sid)
  33. assert.Contains(t, stmt.Action, "s3:PutObject")
  34. assert.Contains(t, stmt.Action, "s3:CreateMultipartUpload")
  35. assert.NotContains(t, stmt.Action, "s3:GetObject")
  36. assert.NotContains(t, stmt.Action, "s3:DeleteObject")
  37. assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
  38. assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
  39. })
  40. t.Run("S3AdminPolicy", func(t *testing.T) {
  41. policy := templates.GetS3AdminPolicy()
  42. require.NotNil(t, policy)
  43. assert.Equal(t, "2012-10-17", policy.Version)
  44. assert.Len(t, policy.Statement, 1)
  45. stmt := policy.Statement[0]
  46. assert.Equal(t, "Allow", stmt.Effect)
  47. assert.Equal(t, "S3FullAccess", stmt.Sid)
  48. assert.Contains(t, stmt.Action, "s3:*")
  49. assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
  50. assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
  51. })
  52. }
  53. func TestBucketSpecificPolicies(t *testing.T) {
  54. templates := NewS3PolicyTemplates()
  55. bucketName := "test-bucket"
  56. t.Run("BucketSpecificReadPolicy", func(t *testing.T) {
  57. policy := templates.GetBucketSpecificReadPolicy(bucketName)
  58. require.NotNil(t, policy)
  59. assert.Equal(t, "2012-10-17", policy.Version)
  60. assert.Len(t, policy.Statement, 1)
  61. stmt := policy.Statement[0]
  62. assert.Equal(t, "Allow", stmt.Effect)
  63. assert.Equal(t, "BucketSpecificReadAccess", stmt.Sid)
  64. assert.Contains(t, stmt.Action, "s3:GetObject")
  65. assert.Contains(t, stmt.Action, "s3:ListBucket")
  66. assert.NotContains(t, stmt.Action, "s3:PutObject")
  67. expectedBucketArn := "arn:seaweed:s3:::" + bucketName
  68. expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
  69. assert.Contains(t, stmt.Resource, expectedBucketArn)
  70. assert.Contains(t, stmt.Resource, expectedObjectArn)
  71. })
  72. t.Run("BucketSpecificWritePolicy", func(t *testing.T) {
  73. policy := templates.GetBucketSpecificWritePolicy(bucketName)
  74. require.NotNil(t, policy)
  75. assert.Equal(t, "2012-10-17", policy.Version)
  76. assert.Len(t, policy.Statement, 1)
  77. stmt := policy.Statement[0]
  78. assert.Equal(t, "Allow", stmt.Effect)
  79. assert.Equal(t, "BucketSpecificWriteAccess", stmt.Sid)
  80. assert.Contains(t, stmt.Action, "s3:PutObject")
  81. assert.Contains(t, stmt.Action, "s3:CreateMultipartUpload")
  82. assert.NotContains(t, stmt.Action, "s3:GetObject")
  83. expectedBucketArn := "arn:seaweed:s3:::" + bucketName
  84. expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
  85. assert.Contains(t, stmt.Resource, expectedBucketArn)
  86. assert.Contains(t, stmt.Resource, expectedObjectArn)
  87. })
  88. }
  89. func TestPathBasedAccessPolicy(t *testing.T) {
  90. templates := NewS3PolicyTemplates()
  91. bucketName := "shared-bucket"
  92. pathPrefix := "user123/documents"
  93. policy := templates.GetPathBasedAccessPolicy(bucketName, pathPrefix)
  94. require.NotNil(t, policy)
  95. assert.Equal(t, "2012-10-17", policy.Version)
  96. assert.Len(t, policy.Statement, 2)
  97. // First statement: List bucket with prefix condition
  98. listStmt := policy.Statement[0]
  99. assert.Equal(t, "Allow", listStmt.Effect)
  100. assert.Equal(t, "ListBucketPermission", listStmt.Sid)
  101. assert.Contains(t, listStmt.Action, "s3:ListBucket")
  102. assert.Contains(t, listStmt.Resource, "arn:seaweed:s3:::"+bucketName)
  103. assert.NotNil(t, listStmt.Condition)
  104. // Second statement: Object operations on path
  105. objectStmt := policy.Statement[1]
  106. assert.Equal(t, "Allow", objectStmt.Effect)
  107. assert.Equal(t, "PathBasedObjectAccess", objectStmt.Sid)
  108. assert.Contains(t, objectStmt.Action, "s3:GetObject")
  109. assert.Contains(t, objectStmt.Action, "s3:PutObject")
  110. assert.Contains(t, objectStmt.Action, "s3:DeleteObject")
  111. expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/" + pathPrefix + "/*"
  112. assert.Contains(t, objectStmt.Resource, expectedObjectArn)
  113. }
  114. func TestIPRestrictedPolicy(t *testing.T) {
  115. templates := NewS3PolicyTemplates()
  116. allowedCIDRs := []string{"192.168.1.0/24", "10.0.0.0/8"}
  117. policy := templates.GetIPRestrictedPolicy(allowedCIDRs)
  118. require.NotNil(t, policy)
  119. assert.Equal(t, "2012-10-17", policy.Version)
  120. assert.Len(t, policy.Statement, 1)
  121. stmt := policy.Statement[0]
  122. assert.Equal(t, "Allow", stmt.Effect)
  123. assert.Equal(t, "IPRestrictedS3Access", stmt.Sid)
  124. assert.Contains(t, stmt.Action, "s3:*")
  125. assert.NotNil(t, stmt.Condition)
  126. // Check IP condition structure
  127. condition := stmt.Condition
  128. ipAddress, exists := condition["IpAddress"]
  129. assert.True(t, exists)
  130. sourceIp, exists := ipAddress["aws:SourceIp"]
  131. assert.True(t, exists)
  132. assert.Equal(t, allowedCIDRs, sourceIp)
  133. }
  134. func TestTimeBasedAccessPolicy(t *testing.T) {
  135. templates := NewS3PolicyTemplates()
  136. startHour := 9 // 9 AM
  137. endHour := 17 // 5 PM
  138. policy := templates.GetTimeBasedAccessPolicy(startHour, endHour)
  139. require.NotNil(t, policy)
  140. assert.Equal(t, "2012-10-17", policy.Version)
  141. assert.Len(t, policy.Statement, 1)
  142. stmt := policy.Statement[0]
  143. assert.Equal(t, "Allow", stmt.Effect)
  144. assert.Equal(t, "TimeBasedS3Access", stmt.Sid)
  145. assert.Contains(t, stmt.Action, "s3:GetObject")
  146. assert.Contains(t, stmt.Action, "s3:PutObject")
  147. assert.Contains(t, stmt.Action, "s3:ListBucket")
  148. assert.NotNil(t, stmt.Condition)
  149. // Check time condition structure
  150. condition := stmt.Condition
  151. _, hasGreater := condition["DateGreaterThan"]
  152. _, hasLess := condition["DateLessThan"]
  153. assert.True(t, hasGreater)
  154. assert.True(t, hasLess)
  155. }
  156. func TestMultipartUploadPolicyTemplate(t *testing.T) {
  157. templates := NewS3PolicyTemplates()
  158. bucketName := "large-files"
  159. policy := templates.GetMultipartUploadPolicy(bucketName)
  160. require.NotNil(t, policy)
  161. assert.Equal(t, "2012-10-17", policy.Version)
  162. assert.Len(t, policy.Statement, 2)
  163. // First statement: Multipart operations
  164. multipartStmt := policy.Statement[0]
  165. assert.Equal(t, "Allow", multipartStmt.Effect)
  166. assert.Equal(t, "MultipartUploadOperations", multipartStmt.Sid)
  167. assert.Contains(t, multipartStmt.Action, "s3:CreateMultipartUpload")
  168. assert.Contains(t, multipartStmt.Action, "s3:UploadPart")
  169. assert.Contains(t, multipartStmt.Action, "s3:CompleteMultipartUpload")
  170. assert.Contains(t, multipartStmt.Action, "s3:AbortMultipartUpload")
  171. assert.Contains(t, multipartStmt.Action, "s3:ListMultipartUploads")
  172. assert.Contains(t, multipartStmt.Action, "s3:ListParts")
  173. expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
  174. assert.Contains(t, multipartStmt.Resource, expectedObjectArn)
  175. // Second statement: List bucket
  176. listStmt := policy.Statement[1]
  177. assert.Equal(t, "Allow", listStmt.Effect)
  178. assert.Equal(t, "ListBucketForMultipart", listStmt.Sid)
  179. assert.Contains(t, listStmt.Action, "s3:ListBucket")
  180. expectedBucketArn := "arn:seaweed:s3:::" + bucketName
  181. assert.Contains(t, listStmt.Resource, expectedBucketArn)
  182. }
  183. func TestPresignedURLPolicy(t *testing.T) {
  184. templates := NewS3PolicyTemplates()
  185. bucketName := "shared-files"
  186. policy := templates.GetPresignedURLPolicy(bucketName)
  187. require.NotNil(t, policy)
  188. assert.Equal(t, "2012-10-17", policy.Version)
  189. assert.Len(t, policy.Statement, 1)
  190. stmt := policy.Statement[0]
  191. assert.Equal(t, "Allow", stmt.Effect)
  192. assert.Equal(t, "PresignedURLAccess", stmt.Sid)
  193. assert.Contains(t, stmt.Action, "s3:GetObject")
  194. assert.Contains(t, stmt.Action, "s3:PutObject")
  195. assert.NotNil(t, stmt.Condition)
  196. expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
  197. assert.Contains(t, stmt.Resource, expectedObjectArn)
  198. // Check signature version condition
  199. condition := stmt.Condition
  200. stringEquals, exists := condition["StringEquals"]
  201. assert.True(t, exists)
  202. signatureVersion, exists := stringEquals["s3:x-amz-signature-version"]
  203. assert.True(t, exists)
  204. assert.Equal(t, "AWS4-HMAC-SHA256", signatureVersion)
  205. }
  206. func TestTemporaryAccessPolicy(t *testing.T) {
  207. templates := NewS3PolicyTemplates()
  208. bucketName := "temp-bucket"
  209. expirationHours := 24
  210. policy := templates.GetTemporaryAccessPolicy(bucketName, expirationHours)
  211. require.NotNil(t, policy)
  212. assert.Equal(t, "2012-10-17", policy.Version)
  213. assert.Len(t, policy.Statement, 1)
  214. stmt := policy.Statement[0]
  215. assert.Equal(t, "Allow", stmt.Effect)
  216. assert.Equal(t, "TemporaryS3Access", stmt.Sid)
  217. assert.Contains(t, stmt.Action, "s3:GetObject")
  218. assert.Contains(t, stmt.Action, "s3:PutObject")
  219. assert.Contains(t, stmt.Action, "s3:ListBucket")
  220. assert.NotNil(t, stmt.Condition)
  221. // Check expiration condition
  222. condition := stmt.Condition
  223. dateLessThan, exists := condition["DateLessThan"]
  224. assert.True(t, exists)
  225. currentTime, exists := dateLessThan["aws:CurrentTime"]
  226. assert.True(t, exists)
  227. assert.IsType(t, "", currentTime) // Should be a string timestamp
  228. }
  229. func TestContentTypeRestrictedPolicy(t *testing.T) {
  230. templates := NewS3PolicyTemplates()
  231. bucketName := "media-bucket"
  232. allowedTypes := []string{"image/jpeg", "image/png", "video/mp4"}
  233. policy := templates.GetContentTypeRestrictedPolicy(bucketName, allowedTypes)
  234. require.NotNil(t, policy)
  235. assert.Equal(t, "2012-10-17", policy.Version)
  236. assert.Len(t, policy.Statement, 2)
  237. // First statement: Upload with content type restriction
  238. uploadStmt := policy.Statement[0]
  239. assert.Equal(t, "Allow", uploadStmt.Effect)
  240. assert.Equal(t, "ContentTypeRestrictedUpload", uploadStmt.Sid)
  241. assert.Contains(t, uploadStmt.Action, "s3:PutObject")
  242. assert.Contains(t, uploadStmt.Action, "s3:CreateMultipartUpload")
  243. assert.NotNil(t, uploadStmt.Condition)
  244. // Check content type condition
  245. condition := uploadStmt.Condition
  246. stringEquals, exists := condition["StringEquals"]
  247. assert.True(t, exists)
  248. contentType, exists := stringEquals["s3:content-type"]
  249. assert.True(t, exists)
  250. assert.Equal(t, allowedTypes, contentType)
  251. // Second statement: Read access without restrictions
  252. readStmt := policy.Statement[1]
  253. assert.Equal(t, "Allow", readStmt.Effect)
  254. assert.Equal(t, "ReadAccess", readStmt.Sid)
  255. assert.Contains(t, readStmt.Action, "s3:GetObject")
  256. assert.Contains(t, readStmt.Action, "s3:ListBucket")
  257. assert.Nil(t, readStmt.Condition) // No conditions for read access
  258. }
  259. func TestDenyDeletePolicy(t *testing.T) {
  260. templates := NewS3PolicyTemplates()
  261. policy := templates.GetDenyDeletePolicy()
  262. require.NotNil(t, policy)
  263. assert.Equal(t, "2012-10-17", policy.Version)
  264. assert.Len(t, policy.Statement, 2)
  265. // First statement: Allow everything except delete
  266. allowStmt := policy.Statement[0]
  267. assert.Equal(t, "Allow", allowStmt.Effect)
  268. assert.Equal(t, "AllowAllExceptDelete", allowStmt.Sid)
  269. assert.Contains(t, allowStmt.Action, "s3:GetObject")
  270. assert.Contains(t, allowStmt.Action, "s3:PutObject")
  271. assert.Contains(t, allowStmt.Action, "s3:ListBucket")
  272. assert.NotContains(t, allowStmt.Action, "s3:DeleteObject")
  273. assert.NotContains(t, allowStmt.Action, "s3:DeleteBucket")
  274. // Second statement: Explicitly deny delete operations
  275. denyStmt := policy.Statement[1]
  276. assert.Equal(t, "Deny", denyStmt.Effect)
  277. assert.Equal(t, "DenyDeleteOperations", denyStmt.Sid)
  278. assert.Contains(t, denyStmt.Action, "s3:DeleteObject")
  279. assert.Contains(t, denyStmt.Action, "s3:DeleteObjectVersion")
  280. assert.Contains(t, denyStmt.Action, "s3:DeleteBucket")
  281. }
  282. func TestPolicyTemplateMetadata(t *testing.T) {
  283. templates := NewS3PolicyTemplates()
  284. t.Run("GetAllPolicyTemplates", func(t *testing.T) {
  285. allTemplates := templates.GetAllPolicyTemplates()
  286. assert.Greater(t, len(allTemplates), 10) // Should have many templates
  287. // Check that each template has required fields
  288. for _, template := range allTemplates {
  289. assert.NotEmpty(t, template.Name)
  290. assert.NotEmpty(t, template.Description)
  291. assert.NotEmpty(t, template.Category)
  292. assert.NotEmpty(t, template.UseCase)
  293. assert.NotNil(t, template.Policy)
  294. assert.Equal(t, "2012-10-17", template.Policy.Version)
  295. }
  296. })
  297. t.Run("GetPolicyTemplateByName", func(t *testing.T) {
  298. // Test existing template
  299. template := templates.GetPolicyTemplateByName("S3ReadOnlyAccess")
  300. require.NotNil(t, template)
  301. assert.Equal(t, "S3ReadOnlyAccess", template.Name)
  302. assert.Equal(t, "Basic Access", template.Category)
  303. // Test non-existing template
  304. nonExistent := templates.GetPolicyTemplateByName("NonExistentTemplate")
  305. assert.Nil(t, nonExistent)
  306. })
  307. t.Run("GetPolicyTemplatesByCategory", func(t *testing.T) {
  308. basicAccessTemplates := templates.GetPolicyTemplatesByCategory("Basic Access")
  309. assert.GreaterOrEqual(t, len(basicAccessTemplates), 2)
  310. for _, template := range basicAccessTemplates {
  311. assert.Equal(t, "Basic Access", template.Category)
  312. }
  313. // Test non-existing category
  314. emptyCategory := templates.GetPolicyTemplatesByCategory("NonExistentCategory")
  315. assert.Empty(t, emptyCategory)
  316. })
  317. t.Run("PolicyTemplateParameters", func(t *testing.T) {
  318. allTemplates := templates.GetAllPolicyTemplates()
  319. // Find a template with parameters (like BucketSpecificRead)
  320. var templateWithParams *PolicyTemplateDefinition
  321. for _, template := range allTemplates {
  322. if template.Name == "BucketSpecificRead" {
  323. templateWithParams = &template
  324. break
  325. }
  326. }
  327. require.NotNil(t, templateWithParams)
  328. assert.Greater(t, len(templateWithParams.Parameters), 0)
  329. param := templateWithParams.Parameters[0]
  330. assert.Equal(t, "bucketName", param.Name)
  331. assert.Equal(t, "string", param.Type)
  332. assert.True(t, param.Required)
  333. assert.NotEmpty(t, param.Description)
  334. assert.NotEmpty(t, param.Example)
  335. })
  336. }
  337. func TestFormatHourHelper(t *testing.T) {
  338. tests := []struct {
  339. hour int
  340. expected string
  341. }{
  342. {0, "00"},
  343. {5, "05"},
  344. {9, "09"},
  345. {10, "10"},
  346. {15, "15"},
  347. {23, "23"},
  348. }
  349. for _, tt := range tests {
  350. t.Run(fmt.Sprintf("Hour_%d", tt.hour), func(t *testing.T) {
  351. result := formatHour(tt.hour)
  352. assert.Equal(t, tt.expected, result)
  353. })
  354. }
  355. }
  356. func TestPolicyTemplateCategories(t *testing.T) {
  357. templates := NewS3PolicyTemplates()
  358. allTemplates := templates.GetAllPolicyTemplates()
  359. // Extract all categories
  360. categoryMap := make(map[string]int)
  361. for _, template := range allTemplates {
  362. categoryMap[template.Category]++
  363. }
  364. // Expected categories
  365. expectedCategories := []string{
  366. "Basic Access",
  367. "Administrative",
  368. "Bucket-Specific",
  369. "Path-Restricted",
  370. "Security",
  371. "Upload-Specific",
  372. "Presigned URLs",
  373. "Content Control",
  374. "Data Protection",
  375. }
  376. for _, expectedCategory := range expectedCategories {
  377. count, exists := categoryMap[expectedCategory]
  378. assert.True(t, exists, "Category %s should exist", expectedCategory)
  379. assert.Greater(t, count, 0, "Category %s should have at least one template", expectedCategory)
  380. }
  381. }
  382. func TestPolicyValidation(t *testing.T) {
  383. templates := NewS3PolicyTemplates()
  384. allTemplates := templates.GetAllPolicyTemplates()
  385. // Test that all policies have valid structure
  386. for _, template := range allTemplates {
  387. t.Run("Policy_"+template.Name, func(t *testing.T) {
  388. policy := template.Policy
  389. // Basic validation
  390. assert.Equal(t, "2012-10-17", policy.Version)
  391. assert.Greater(t, len(policy.Statement), 0)
  392. // Validate each statement
  393. for i, stmt := range policy.Statement {
  394. assert.NotEmpty(t, stmt.Effect, "Statement %d should have effect", i)
  395. assert.Contains(t, []string{"Allow", "Deny"}, stmt.Effect, "Statement %d effect should be Allow or Deny", i)
  396. assert.Greater(t, len(stmt.Action), 0, "Statement %d should have actions", i)
  397. assert.Greater(t, len(stmt.Resource), 0, "Statement %d should have resources", i)
  398. // Check resource format
  399. for _, resource := range stmt.Resource {
  400. if resource != "*" {
  401. assert.Contains(t, resource, "arn:seaweed:s3:::", "Resource should be valid SeaweedFS S3 ARN: %s", resource)
  402. }
  403. }
  404. }
  405. })
  406. }
  407. }