s3api_server.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. package s3api
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "time"
  11. "github.com/seaweedfs/seaweedfs/weed/credential"
  12. "github.com/seaweedfs/seaweedfs/weed/filer"
  13. "github.com/seaweedfs/seaweedfs/weed/glog"
  14. "github.com/seaweedfs/seaweedfs/weed/iam/integration"
  15. "github.com/seaweedfs/seaweedfs/weed/iam/policy"
  16. "github.com/seaweedfs/seaweedfs/weed/iam/sts"
  17. "github.com/seaweedfs/seaweedfs/weed/pb/s3_pb"
  18. "github.com/seaweedfs/seaweedfs/weed/util/grace"
  19. "github.com/gorilla/mux"
  20. "github.com/seaweedfs/seaweedfs/weed/pb"
  21. . "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  22. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  23. "github.com/seaweedfs/seaweedfs/weed/security"
  24. "github.com/seaweedfs/seaweedfs/weed/util"
  25. util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
  26. util_http_client "github.com/seaweedfs/seaweedfs/weed/util/http/client"
  27. "google.golang.org/grpc"
  28. )
  29. type S3ApiServerOption struct {
  30. Filer pb.ServerAddress
  31. Port int
  32. Config string
  33. DomainName string
  34. AllowedOrigins []string
  35. BucketsPath string
  36. GrpcDialOption grpc.DialOption
  37. AllowEmptyFolder bool
  38. AllowDeleteBucketNotEmpty bool
  39. LocalFilerSocket string
  40. DataCenter string
  41. FilerGroup string
  42. IamConfig string // Advanced IAM configuration file path
  43. }
  44. type S3ApiServer struct {
  45. s3_pb.UnimplementedSeaweedS3Server
  46. option *S3ApiServerOption
  47. iam *IdentityAccessManagement
  48. iamIntegration *S3IAMIntegration // Advanced IAM integration for JWT authentication
  49. cb *CircuitBreaker
  50. randomClientId int32
  51. filerGuard *security.Guard
  52. client util_http_client.HTTPClientInterface
  53. bucketRegistry *BucketRegistry
  54. credentialManager *credential.CredentialManager
  55. bucketConfigCache *BucketConfigCache
  56. }
  57. func NewS3ApiServer(router *mux.Router, option *S3ApiServerOption) (s3ApiServer *S3ApiServer, err error) {
  58. return NewS3ApiServerWithStore(router, option, "")
  59. }
  60. func NewS3ApiServerWithStore(router *mux.Router, option *S3ApiServerOption, explicitStore string) (s3ApiServer *S3ApiServer, err error) {
  61. startTsNs := time.Now().UnixNano()
  62. v := util.GetViper()
  63. signingKey := v.GetString("jwt.filer_signing.key")
  64. v.SetDefault("jwt.filer_signing.expires_after_seconds", 10)
  65. expiresAfterSec := v.GetInt("jwt.filer_signing.expires_after_seconds")
  66. readSigningKey := v.GetString("jwt.filer_signing.read.key")
  67. v.SetDefault("jwt.filer_signing.read.expires_after_seconds", 60)
  68. readExpiresAfterSec := v.GetInt("jwt.filer_signing.read.expires_after_seconds")
  69. v.SetDefault("cors.allowed_origins.values", "*")
  70. if len(option.AllowedOrigins) == 0 {
  71. allowedOrigins := v.GetString("cors.allowed_origins.values")
  72. domains := strings.Split(allowedOrigins, ",")
  73. option.AllowedOrigins = domains
  74. }
  75. var iam *IdentityAccessManagement
  76. iam = NewIdentityAccessManagementWithStore(option, explicitStore)
  77. s3ApiServer = &S3ApiServer{
  78. option: option,
  79. iam: iam,
  80. randomClientId: util.RandomInt32(),
  81. filerGuard: security.NewGuard([]string{}, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec),
  82. cb: NewCircuitBreaker(option),
  83. credentialManager: iam.credentialManager,
  84. bucketConfigCache: NewBucketConfigCache(60 * time.Minute), // Increased TTL since cache is now event-driven
  85. }
  86. // Initialize advanced IAM system if config is provided
  87. if option.IamConfig != "" {
  88. glog.V(0).Infof("Loading advanced IAM configuration from: %s", option.IamConfig)
  89. iamManager, err := loadIAMManagerFromConfig(option.IamConfig, func() string {
  90. return string(option.Filer)
  91. })
  92. if err != nil {
  93. glog.Errorf("Failed to load IAM configuration: %v", err)
  94. } else {
  95. // Create S3 IAM integration with the loaded IAM manager
  96. s3iam := NewS3IAMIntegration(iamManager, string(option.Filer))
  97. // Set IAM integration in server
  98. s3ApiServer.iamIntegration = s3iam
  99. // Set the integration in the traditional IAM for compatibility
  100. iam.SetIAMIntegration(s3iam)
  101. glog.V(0).Infof("Advanced IAM system initialized successfully")
  102. }
  103. }
  104. if option.Config != "" {
  105. grace.OnReload(func() {
  106. if err := s3ApiServer.iam.loadS3ApiConfigurationFromFile(option.Config); err != nil {
  107. glog.Errorf("fail to load config file %s: %v", option.Config, err)
  108. } else {
  109. glog.V(0).Infof("Loaded %d identities from config file %s", len(s3ApiServer.iam.identities), option.Config)
  110. }
  111. })
  112. }
  113. s3ApiServer.bucketRegistry = NewBucketRegistry(s3ApiServer)
  114. if option.LocalFilerSocket == "" {
  115. if s3ApiServer.client, err = util_http.NewGlobalHttpClient(); err != nil {
  116. return nil, err
  117. }
  118. } else {
  119. s3ApiServer.client = &http.Client{
  120. Transport: &http.Transport{
  121. DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
  122. return net.Dial("unix", option.LocalFilerSocket)
  123. },
  124. },
  125. }
  126. }
  127. s3ApiServer.registerRouter(router)
  128. go s3ApiServer.subscribeMetaEvents("s3", startTsNs, filer.DirectoryEtcRoot, []string{option.BucketsPath})
  129. return s3ApiServer, nil
  130. }
  131. // handleCORSOriginValidation handles the common CORS origin validation logic
  132. func (s3a *S3ApiServer) handleCORSOriginValidation(w http.ResponseWriter, r *http.Request) bool {
  133. origin := r.Header.Get("Origin")
  134. if origin != "" {
  135. if len(s3a.option.AllowedOrigins) == 0 || s3a.option.AllowedOrigins[0] == "*" {
  136. origin = "*"
  137. } else {
  138. originFound := false
  139. for _, allowedOrigin := range s3a.option.AllowedOrigins {
  140. if origin == allowedOrigin {
  141. originFound = true
  142. break
  143. }
  144. }
  145. if !originFound {
  146. writeFailureResponse(w, r, http.StatusForbidden)
  147. return false
  148. }
  149. }
  150. }
  151. w.Header().Set("Access-Control-Allow-Origin", origin)
  152. w.Header().Set("Access-Control-Expose-Headers", "*")
  153. w.Header().Set("Access-Control-Allow-Methods", "*")
  154. w.Header().Set("Access-Control-Allow-Headers", "*")
  155. w.Header().Set("Access-Control-Allow-Credentials", "true")
  156. return true
  157. }
  158. func (s3a *S3ApiServer) registerRouter(router *mux.Router) {
  159. // API Router
  160. apiRouter := router.PathPrefix("/").Subrouter()
  161. // Readiness Probe
  162. apiRouter.Methods(http.MethodGet).Path("/status").HandlerFunc(s3a.StatusHandler)
  163. apiRouter.Methods(http.MethodGet).Path("/healthz").HandlerFunc(s3a.StatusHandler)
  164. var routers []*mux.Router
  165. if s3a.option.DomainName != "" {
  166. domainNames := strings.Split(s3a.option.DomainName, ",")
  167. for _, domainName := range domainNames {
  168. routers = append(routers, apiRouter.Host(
  169. fmt.Sprintf("%s.%s:%d", "{bucket:.+}", domainName, s3a.option.Port)).Subrouter())
  170. routers = append(routers, apiRouter.Host(
  171. fmt.Sprintf("%s.%s", "{bucket:.+}", domainName)).Subrouter())
  172. }
  173. }
  174. routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
  175. // Get CORS middleware instance with caching
  176. corsMiddleware := s3a.getCORSMiddleware()
  177. for _, bucket := range routers {
  178. // Apply CORS middleware to bucket routers for automatic CORS header handling
  179. bucket.Use(corsMiddleware.Handler)
  180. // Bucket-specific OPTIONS handler for CORS preflight requests
  181. // Use PathPrefix to catch all bucket-level preflight routes including /bucket/object
  182. bucket.PathPrefix("/").Methods(http.MethodOptions).HandlerFunc(corsMiddleware.HandleOptionsRequest)
  183. // each case should follow the next rule:
  184. // - requesting object with query must precede any other methods
  185. // - requesting object must precede any methods with buckets
  186. // - requesting bucket with query must precede raw methods with buckets
  187. // - requesting bucket must be processed in the end
  188. // objects with query
  189. // CopyObjectPart
  190. bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", `.*?(\/|%2F).*?`).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.CopyObjectPartHandler, ACTION_WRITE)), "PUT")).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
  191. // PutObjectPart
  192. bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectPartHandler, ACTION_WRITE)), "PUT")).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
  193. // CompleteMultipartUpload
  194. bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.CompleteMultipartUploadHandler, ACTION_WRITE)), "POST")).Queries("uploadId", "{uploadId:.*}")
  195. // NewMultipartUpload
  196. bucket.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.NewMultipartUploadHandler, ACTION_WRITE)), "POST")).Queries("uploads", "")
  197. // AbortMultipartUpload
  198. bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.AbortMultipartUploadHandler, ACTION_WRITE)), "DELETE")).Queries("uploadId", "{uploadId:.*}")
  199. // ListObjectParts
  200. bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.ListObjectPartsHandler, ACTION_READ)), "GET")).Queries("uploadId", "{uploadId:.*}")
  201. // ListMultipartUploads
  202. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.ListMultipartUploadsHandler, ACTION_READ)), "GET")).Queries("uploads", "")
  203. // GetObjectTagging
  204. bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetObjectTaggingHandler, ACTION_READ)), "GET")).Queries("tagging", "")
  205. // PutObjectTagging
  206. bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectTaggingHandler, ACTION_TAGGING)), "PUT")).Queries("tagging", "")
  207. // DeleteObjectTagging
  208. bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteObjectTaggingHandler, ACTION_TAGGING)), "DELETE")).Queries("tagging", "")
  209. // PutObjectACL
  210. bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectAclHandler, ACTION_WRITE_ACP)), "PUT")).Queries("acl", "")
  211. // PutObjectRetention
  212. bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectRetentionHandler, ACTION_WRITE)), "PUT")).Queries("retention", "")
  213. // PutObjectLegalHold
  214. bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectLegalHoldHandler, ACTION_WRITE)), "PUT")).Queries("legal-hold", "")
  215. // GetObjectACL
  216. bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetObjectAclHandler, ACTION_READ_ACP)), "GET")).Queries("acl", "")
  217. // GetObjectRetention
  218. bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetObjectRetentionHandler, ACTION_READ)), "GET")).Queries("retention", "")
  219. // GetObjectLegalHold
  220. bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetObjectLegalHoldHandler, ACTION_READ)), "GET")).Queries("legal-hold", "")
  221. // objects with query
  222. // raw objects
  223. // HeadObject
  224. bucket.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(track(s3a.AuthWithPublicRead(func(w http.ResponseWriter, r *http.Request) {
  225. limitedHandler, _ := s3a.cb.Limit(s3a.HeadObjectHandler, ACTION_READ)
  226. limitedHandler(w, r)
  227. }, ACTION_READ), "GET"))
  228. // GetObject, but directory listing is not supported
  229. bucket.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(track(s3a.AuthWithPublicRead(func(w http.ResponseWriter, r *http.Request) {
  230. limitedHandler, _ := s3a.cb.Limit(s3a.GetObjectHandler, ACTION_READ)
  231. limitedHandler(w, r)
  232. }, ACTION_READ), "GET"))
  233. // CopyObject
  234. bucket.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.CopyObjectHandler, ACTION_WRITE)), "COPY"))
  235. // PutObject
  236. bucket.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectHandler, ACTION_WRITE)), "PUT"))
  237. // DeleteObject
  238. bucket.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteObjectHandler, ACTION_WRITE)), "DELETE"))
  239. // raw objects
  240. // buckets with query
  241. // DeleteMultipleObjects
  242. bucket.Methods(http.MethodPost).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteMultipleObjectsHandler, ACTION_WRITE)), "DELETE")).Queries("delete", "")
  243. // GetBucketACL
  244. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketAclHandler, ACTION_READ_ACP)), "GET")).Queries("acl", "")
  245. // PutBucketACL
  246. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketAclHandler, ACTION_WRITE_ACP)), "PUT")).Queries("acl", "")
  247. // GetBucketPolicy
  248. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketPolicyHandler, ACTION_READ)), "GET")).Queries("policy", "")
  249. // PutBucketPolicy
  250. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketPolicyHandler, ACTION_WRITE)), "PUT")).Queries("policy", "")
  251. // DeleteBucketPolicy
  252. bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteBucketPolicyHandler, ACTION_WRITE)), "DELETE")).Queries("policy", "")
  253. // GetBucketCors
  254. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketCorsHandler, ACTION_READ)), "GET")).Queries("cors", "")
  255. // PutBucketCors
  256. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketCorsHandler, ACTION_WRITE)), "PUT")).Queries("cors", "")
  257. // DeleteBucketCors
  258. bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteBucketCorsHandler, ACTION_WRITE)), "DELETE")).Queries("cors", "")
  259. // GetBucketLifecycleConfiguration
  260. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketLifecycleConfigurationHandler, ACTION_READ)), "GET")).Queries("lifecycle", "")
  261. // PutBucketLifecycleConfiguration
  262. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketLifecycleConfigurationHandler, ACTION_WRITE)), "PUT")).Queries("lifecycle", "")
  263. // DeleteBucketLifecycleConfiguration
  264. bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteBucketLifecycleHandler, ACTION_WRITE)), "DELETE")).Queries("lifecycle", "")
  265. // GetBucketLocation
  266. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketLocationHandler, ACTION_READ)), "GET")).Queries("location", "")
  267. // GetBucketRequestPayment
  268. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketRequestPaymentHandler, ACTION_READ)), "GET")).Queries("requestPayment", "")
  269. // GetBucketVersioning
  270. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketVersioningHandler, ACTION_READ)), "GET")).Queries("versioning", "")
  271. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketVersioningHandler, ACTION_WRITE)), "PUT")).Queries("versioning", "")
  272. // GetObjectLockConfiguration / PutObjectLockConfiguration (bucket-level operations)
  273. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetObjectLockConfigurationHandler, ACTION_READ)), "GET")).Queries("object-lock", "")
  274. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutObjectLockConfigurationHandler, ACTION_WRITE)), "PUT")).Queries("object-lock", "")
  275. // GetBucketTagging
  276. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketTaggingHandler, ACTION_TAGGING)), "GET")).Queries("tagging", "")
  277. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketTaggingHandler, ACTION_TAGGING)), "PUT")).Queries("tagging", "")
  278. bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteBucketTaggingHandler, ACTION_TAGGING)), "DELETE")).Queries("tagging", "")
  279. // GetBucketEncryption
  280. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetBucketEncryptionHandler, ACTION_ADMIN)), "GET")).Queries("encryption", "")
  281. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketEncryptionHandler, ACTION_ADMIN)), "PUT")).Queries("encryption", "")
  282. bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteBucketEncryptionHandler, ACTION_ADMIN)), "DELETE")).Queries("encryption", "")
  283. // GetPublicAccessBlockHandler
  284. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetPublicAccessBlockHandler, ACTION_ADMIN)), "GET")).Queries("publicAccessBlock", "")
  285. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutPublicAccessBlockHandler, ACTION_ADMIN)), "PUT")).Queries("publicAccessBlock", "")
  286. bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeletePublicAccessBlockHandler, ACTION_ADMIN)), "DELETE")).Queries("publicAccessBlock", "")
  287. // ListObjectsV2
  288. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.AuthWithPublicRead(func(w http.ResponseWriter, r *http.Request) {
  289. limitedHandler, _ := s3a.cb.Limit(s3a.ListObjectsV2Handler, ACTION_LIST)
  290. limitedHandler(w, r)
  291. }, ACTION_LIST), "LIST")).Queries("list-type", "2")
  292. // ListObjectVersions
  293. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.ListObjectVersionsHandler, ACTION_LIST)), "LIST")).Queries("versions", "")
  294. // buckets with query
  295. // PutBucketOwnershipControls
  296. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.PutBucketOwnershipControls, ACTION_ADMIN), "PUT")).Queries("ownershipControls", "")
  297. //GetBucketOwnershipControls
  298. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.iam.Auth(s3a.GetBucketOwnershipControls, ACTION_READ), "GET")).Queries("ownershipControls", "")
  299. //DeleteBucketOwnershipControls
  300. bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.DeleteBucketOwnershipControls, ACTION_ADMIN), "DELETE")).Queries("ownershipControls", "")
  301. // raw buckets
  302. // PostPolicy
  303. bucket.Methods(http.MethodPost).HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PostPolicyBucketHandler, ACTION_WRITE)), "POST"))
  304. // HeadBucket
  305. bucket.Methods(http.MethodHead).HandlerFunc(track(s3a.AuthWithPublicRead(func(w http.ResponseWriter, r *http.Request) {
  306. limitedHandler, _ := s3a.cb.Limit(s3a.HeadBucketHandler, ACTION_READ)
  307. limitedHandler(w, r)
  308. }, ACTION_READ), "GET"))
  309. // PutBucket
  310. bucket.Methods(http.MethodPut).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.PutBucketHandler, ACTION_ADMIN)), "PUT"))
  311. // DeleteBucket
  312. bucket.Methods(http.MethodDelete).HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.DeleteBucketHandler, ACTION_DELETE_BUCKET)), "DELETE"))
  313. // ListObjectsV1 (Legacy)
  314. bucket.Methods(http.MethodGet).HandlerFunc(track(s3a.AuthWithPublicRead(func(w http.ResponseWriter, r *http.Request) {
  315. limitedHandler, _ := s3a.cb.Limit(s3a.ListObjectsV1Handler, ACTION_LIST)
  316. limitedHandler(w, r)
  317. }, ACTION_LIST), "LIST"))
  318. // raw buckets
  319. }
  320. // Global OPTIONS handler for service-level requests (non-bucket requests)
  321. // This handles requests like OPTIONS /, OPTIONS /status, OPTIONS /healthz
  322. // Place this after bucket handlers to avoid interfering with bucket CORS middleware
  323. apiRouter.Methods(http.MethodOptions).PathPrefix("/").HandlerFunc(
  324. func(w http.ResponseWriter, r *http.Request) {
  325. // Only handle if this is not a bucket-specific request
  326. vars := mux.Vars(r)
  327. bucket := vars["bucket"]
  328. if bucket != "" {
  329. // This is a bucket-specific request, let bucket CORS middleware handle it
  330. http.NotFound(w, r)
  331. return
  332. }
  333. if s3a.handleCORSOriginValidation(w, r) {
  334. writeSuccessResponseEmpty(w, r)
  335. }
  336. })
  337. // ListBuckets
  338. apiRouter.Methods(http.MethodGet).Path("/").HandlerFunc(track(s3a.ListBucketsHandler, "LIST"))
  339. // NotFound
  340. apiRouter.NotFoundHandler = http.HandlerFunc(s3err.NotFoundHandler)
  341. }
  342. // loadIAMManagerFromConfig loads the advanced IAM manager from configuration file
  343. func loadIAMManagerFromConfig(configPath string, filerAddressProvider func() string) (*integration.IAMManager, error) {
  344. // Read configuration file
  345. configData, err := os.ReadFile(configPath)
  346. if err != nil {
  347. return nil, fmt.Errorf("failed to read config file: %w", err)
  348. }
  349. // Parse configuration structure
  350. var configRoot struct {
  351. STS *sts.STSConfig `json:"sts"`
  352. Policy *policy.PolicyEngineConfig `json:"policy"`
  353. Providers []map[string]interface{} `json:"providers"`
  354. Roles []*integration.RoleDefinition `json:"roles"`
  355. Policies []struct {
  356. Name string `json:"name"`
  357. Document *policy.PolicyDocument `json:"document"`
  358. } `json:"policies"`
  359. }
  360. if err := json.Unmarshal(configData, &configRoot); err != nil {
  361. return nil, fmt.Errorf("failed to parse config: %w", err)
  362. }
  363. // Create IAM configuration
  364. iamConfig := &integration.IAMConfig{
  365. STS: configRoot.STS,
  366. Policy: configRoot.Policy,
  367. Roles: &integration.RoleStoreConfig{
  368. StoreType: "memory", // Use memory store for JSON config-based setup
  369. },
  370. }
  371. // Initialize IAM manager
  372. iamManager := integration.NewIAMManager()
  373. if err := iamManager.Initialize(iamConfig, filerAddressProvider); err != nil {
  374. return nil, fmt.Errorf("failed to initialize IAM manager: %w", err)
  375. }
  376. // Load identity providers
  377. providerFactory := sts.NewProviderFactory()
  378. for _, providerConfig := range configRoot.Providers {
  379. provider, err := providerFactory.CreateProvider(&sts.ProviderConfig{
  380. Name: providerConfig["name"].(string),
  381. Type: providerConfig["type"].(string),
  382. Enabled: true,
  383. Config: providerConfig["config"].(map[string]interface{}),
  384. })
  385. if err != nil {
  386. glog.Warningf("Failed to create provider %s: %v", providerConfig["name"], err)
  387. continue
  388. }
  389. if provider != nil {
  390. if err := iamManager.RegisterIdentityProvider(provider); err != nil {
  391. glog.Warningf("Failed to register provider %s: %v", providerConfig["name"], err)
  392. } else {
  393. glog.V(1).Infof("Registered identity provider: %s", providerConfig["name"])
  394. }
  395. }
  396. }
  397. // Load policies
  398. for _, policyDef := range configRoot.Policies {
  399. if err := iamManager.CreatePolicy(context.Background(), "", policyDef.Name, policyDef.Document); err != nil {
  400. glog.Warningf("Failed to create policy %s: %v", policyDef.Name, err)
  401. }
  402. }
  403. // Load roles
  404. for _, roleDef := range configRoot.Roles {
  405. if err := iamManager.CreateRole(context.Background(), "", roleDef.RoleName, roleDef); err != nil {
  406. glog.Warningf("Failed to create role %s: %v", roleDef.RoleName, err)
  407. }
  408. }
  409. glog.V(0).Infof("Loaded %d providers, %d policies and %d roles from config", len(configRoot.Providers), len(configRoot.Policies), len(configRoot.Roles))
  410. return iamManager, nil
  411. }