auth_credentials.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. package s3api
  2. import (
  3. "fmt"
  4. "github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
  5. "net/http"
  6. "os"
  7. "strings"
  8. "sync"
  9. "github.com/seaweedfs/seaweedfs/weed/filer"
  10. "github.com/seaweedfs/seaweedfs/weed/glog"
  11. "github.com/seaweedfs/seaweedfs/weed/pb"
  12. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  13. "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
  14. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  15. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  16. )
  17. var IdentityAnonymous *Identity
  18. type Action string
  19. type Iam interface {
  20. Check(f http.HandlerFunc, actions ...Action) http.HandlerFunc
  21. }
  22. type IdentityAccessManagement struct {
  23. m sync.RWMutex
  24. identities []*Identity
  25. isAuthEnabled bool
  26. domain string
  27. }
  28. type Identity struct {
  29. Name string
  30. AccountId string
  31. Credentials []*Credential
  32. Actions []Action
  33. }
  34. func (i *Identity) isAnonymous() bool {
  35. return i.Name == s3account.AccountAnonymous.Name
  36. }
  37. type Credential struct {
  38. AccessKey string
  39. SecretKey string
  40. }
  41. func (action Action) isAdmin() bool {
  42. return strings.HasPrefix(string(action), s3_constants.ACTION_ADMIN)
  43. }
  44. func (action Action) isOwner(bucket string) bool {
  45. return string(action) == s3_constants.ACTION_ADMIN+":"+bucket
  46. }
  47. func (action Action) overBucket(bucket string) bool {
  48. return strings.HasSuffix(string(action), ":"+bucket) || strings.HasSuffix(string(action), ":*")
  49. }
  50. func (action Action) getPermission() Permission {
  51. switch act := strings.Split(string(action), ":")[0]; act {
  52. case s3_constants.ACTION_ADMIN:
  53. return Permission("FULL_CONTROL")
  54. case s3_constants.ACTION_WRITE:
  55. return Permission("WRITE")
  56. case s3_constants.ACTION_READ:
  57. return Permission("READ")
  58. default:
  59. return Permission("")
  60. }
  61. }
  62. func NewIdentityAccessManagement(option *S3ApiServerOption) *IdentityAccessManagement {
  63. iam := &IdentityAccessManagement{
  64. domain: option.DomainName,
  65. }
  66. if option.Config != "" {
  67. if err := iam.loadS3ApiConfigurationFromFile(option.Config); err != nil {
  68. glog.Fatalf("fail to load config file %s: %v", option.Config, err)
  69. }
  70. } else {
  71. if err := iam.loadS3ApiConfigurationFromFiler(option); err != nil {
  72. glog.Warningf("fail to load config: %v", err)
  73. }
  74. }
  75. return iam
  76. }
  77. func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFiler(option *S3ApiServerOption) (err error) {
  78. var content []byte
  79. err = pb.WithFilerClient(false, 0, option.Filer, option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  80. content, err = filer.ReadInsideFiler(client, filer.IamConfigDirectory, filer.IamIdentityFile)
  81. return err
  82. })
  83. if err != nil {
  84. return fmt.Errorf("read S3 config: %v", err)
  85. }
  86. return iam.LoadS3ApiConfigurationFromBytes(content)
  87. }
  88. func (iam *IdentityAccessManagement) loadS3ApiConfigurationFromFile(fileName string) error {
  89. content, readErr := os.ReadFile(fileName)
  90. if readErr != nil {
  91. glog.Warningf("fail to read %s : %v", fileName, readErr)
  92. return fmt.Errorf("fail to read %s : %v", fileName, readErr)
  93. }
  94. return iam.LoadS3ApiConfigurationFromBytes(content)
  95. }
  96. func (iam *IdentityAccessManagement) LoadS3ApiConfigurationFromBytes(content []byte) error {
  97. s3ApiConfiguration := &iam_pb.S3ApiConfiguration{}
  98. if err := filer.ParseS3ConfigurationFromBytes(content, s3ApiConfiguration); err != nil {
  99. glog.Warningf("unmarshal error: %v", err)
  100. return fmt.Errorf("unmarshal error: %v", err)
  101. }
  102. if err := filer.CheckDuplicateAccessKey(s3ApiConfiguration); err != nil {
  103. return err
  104. }
  105. if err := iam.loadS3ApiConfiguration(s3ApiConfiguration); err != nil {
  106. return err
  107. }
  108. return nil
  109. }
  110. func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3ApiConfiguration) error {
  111. var identities []*Identity
  112. for _, ident := range config.Identities {
  113. t := &Identity{
  114. Name: ident.Name,
  115. AccountId: s3account.AccountAdmin.Id,
  116. Credentials: nil,
  117. Actions: nil,
  118. }
  119. if ident.Name == s3account.AccountAnonymous.Name {
  120. if ident.AccountId != "" && ident.AccountId != s3account.AccountAnonymous.Id {
  121. glog.Warningf("anonymous identity is associated with a non-anonymous account ID, the association is invalid")
  122. }
  123. t.AccountId = s3account.AccountAnonymous.Id
  124. IdentityAnonymous = t
  125. } else {
  126. if len(ident.AccountId) > 0 {
  127. t.AccountId = ident.AccountId
  128. }
  129. }
  130. for _, action := range ident.Actions {
  131. t.Actions = append(t.Actions, Action(action))
  132. }
  133. for _, cred := range ident.Credentials {
  134. t.Credentials = append(t.Credentials, &Credential{
  135. AccessKey: cred.AccessKey,
  136. SecretKey: cred.SecretKey,
  137. })
  138. }
  139. identities = append(identities, t)
  140. }
  141. if IdentityAnonymous == nil {
  142. IdentityAnonymous = &Identity{
  143. Name: s3account.AccountAnonymous.Name,
  144. AccountId: s3account.AccountAnonymous.Id,
  145. }
  146. }
  147. iam.m.Lock()
  148. // atomically switch
  149. iam.identities = identities
  150. if !iam.isAuthEnabled { // one-directional, no toggling
  151. iam.isAuthEnabled = len(identities) > 0
  152. }
  153. iam.m.Unlock()
  154. return nil
  155. }
  156. func (iam *IdentityAccessManagement) isEnabled() bool {
  157. return iam.isAuthEnabled
  158. }
  159. func (iam *IdentityAccessManagement) lookupByAccessKey(accessKey string) (identity *Identity, cred *Credential, found bool) {
  160. iam.m.RLock()
  161. defer iam.m.RUnlock()
  162. for _, ident := range iam.identities {
  163. for _, cred := range ident.Credentials {
  164. // println("checking", ident.Name, cred.AccessKey)
  165. if cred.AccessKey == accessKey {
  166. return ident, cred, true
  167. }
  168. }
  169. }
  170. glog.V(1).Infof("could not find accessKey %s", accessKey)
  171. return nil, nil, false
  172. }
  173. func (iam *IdentityAccessManagement) lookupAnonymous() (identity *Identity, found bool) {
  174. iam.m.RLock()
  175. defer iam.m.RUnlock()
  176. for _, ident := range iam.identities {
  177. if ident.isAnonymous() {
  178. return ident, true
  179. }
  180. }
  181. return nil, false
  182. }
  183. func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) http.HandlerFunc {
  184. return func(w http.ResponseWriter, r *http.Request) {
  185. if !iam.isEnabled() {
  186. f(w, r)
  187. return
  188. }
  189. identity, errCode := iam.authRequest(r, action)
  190. if errCode == s3err.ErrNone {
  191. if identity != nil && identity.Name != "" {
  192. r.Header.Set(s3_constants.AmzIdentityId, identity.Name)
  193. if identity.isAdmin() {
  194. r.Header.Set(s3_constants.AmzIsAdmin, "true")
  195. } else if _, ok := r.Header[s3_constants.AmzIsAdmin]; ok {
  196. r.Header.Del(s3_constants.AmzIsAdmin)
  197. }
  198. }
  199. f(w, r)
  200. return
  201. }
  202. s3err.WriteErrorResponse(w, r, errCode)
  203. }
  204. }
  205. // check whether the request has valid access keys
  206. func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) (*Identity, s3err.ErrorCode) {
  207. var identity *Identity
  208. var s3Err s3err.ErrorCode
  209. var found bool
  210. var authType string
  211. switch getRequestAuthType(r) {
  212. case authTypeStreamingSigned:
  213. return identity, s3err.ErrNone
  214. case authTypeUnknown:
  215. glog.V(3).Infof("unknown auth type")
  216. r.Header.Set(s3_constants.AmzAuthType, "Unknown")
  217. return identity, s3err.ErrAccessDenied
  218. case authTypePresignedV2, authTypeSignedV2:
  219. glog.V(3).Infof("v2 auth type")
  220. identity, s3Err = iam.isReqAuthenticatedV2(r)
  221. authType = "SigV2"
  222. case authTypeSigned, authTypePresigned:
  223. glog.V(3).Infof("v4 auth type")
  224. identity, s3Err = iam.reqSignatureV4Verify(r)
  225. authType = "SigV4"
  226. case authTypePostPolicy:
  227. glog.V(3).Infof("post policy auth type")
  228. r.Header.Set(s3_constants.AmzAuthType, "PostPolicy")
  229. return identity, s3err.ErrNone
  230. case authTypeJWT:
  231. glog.V(3).Infof("jwt auth type")
  232. r.Header.Set(s3_constants.AmzAuthType, "Jwt")
  233. return identity, s3err.ErrNotImplemented
  234. case authTypeAnonymous:
  235. authType = "Anonymous"
  236. identity, found = iam.lookupAnonymous()
  237. if !found {
  238. r.Header.Set(s3_constants.AmzAuthType, authType)
  239. return identity, s3err.ErrAccessDenied
  240. }
  241. default:
  242. return identity, s3err.ErrNotImplemented
  243. }
  244. if len(authType) > 0 {
  245. r.Header.Set(s3_constants.AmzAuthType, authType)
  246. }
  247. if s3Err != s3err.ErrNone {
  248. return identity, s3Err
  249. }
  250. glog.V(3).Infof("user name: %v actions: %v, action: %v", identity.Name, identity.Actions, action)
  251. bucket, object := s3_constants.GetBucketAndObject(r)
  252. if !identity.canDo(action, bucket, object) {
  253. return identity, s3err.ErrAccessDenied
  254. }
  255. if !identity.isAnonymous() {
  256. r.Header.Set(s3_constants.AmzAccountId, identity.AccountId)
  257. }
  258. return identity, s3err.ErrNone
  259. }
  260. func (iam *IdentityAccessManagement) authUser(r *http.Request) (*Identity, s3err.ErrorCode) {
  261. var identity *Identity
  262. var s3Err s3err.ErrorCode
  263. var found bool
  264. var authType string
  265. switch getRequestAuthType(r) {
  266. case authTypeStreamingSigned:
  267. return identity, s3err.ErrNone
  268. case authTypeUnknown:
  269. glog.V(3).Infof("unknown auth type")
  270. r.Header.Set(s3_constants.AmzAuthType, "Unknown")
  271. return identity, s3err.ErrAccessDenied
  272. case authTypePresignedV2, authTypeSignedV2:
  273. glog.V(3).Infof("v2 auth type")
  274. identity, s3Err = iam.isReqAuthenticatedV2(r)
  275. authType = "SigV2"
  276. case authTypeSigned, authTypePresigned:
  277. glog.V(3).Infof("v4 auth type")
  278. identity, s3Err = iam.reqSignatureV4Verify(r)
  279. authType = "SigV4"
  280. case authTypePostPolicy:
  281. glog.V(3).Infof("post policy auth type")
  282. r.Header.Set(s3_constants.AmzAuthType, "PostPolicy")
  283. return identity, s3err.ErrNone
  284. case authTypeJWT:
  285. glog.V(3).Infof("jwt auth type")
  286. r.Header.Set(s3_constants.AmzAuthType, "Jwt")
  287. return identity, s3err.ErrNotImplemented
  288. case authTypeAnonymous:
  289. authType = "Anonymous"
  290. identity, found = iam.lookupAnonymous()
  291. if !found {
  292. r.Header.Set(s3_constants.AmzAuthType, authType)
  293. return identity, s3err.ErrAccessDenied
  294. }
  295. default:
  296. return identity, s3err.ErrNotImplemented
  297. }
  298. if len(authType) > 0 {
  299. r.Header.Set(s3_constants.AmzAuthType, authType)
  300. }
  301. glog.V(3).Infof("auth error: %v", s3Err)
  302. if s3Err != s3err.ErrNone {
  303. return identity, s3Err
  304. }
  305. return identity, s3err.ErrNone
  306. }
  307. func (identity *Identity) canDo(action Action, bucket string, objectKey string) bool {
  308. if identity.isAdmin() {
  309. return true
  310. }
  311. for _, a := range identity.Actions {
  312. if a == action {
  313. return true
  314. }
  315. }
  316. if bucket == "" {
  317. return false
  318. }
  319. target := string(action) + ":" + bucket + objectKey
  320. adminTarget := s3_constants.ACTION_ADMIN + ":" + bucket + objectKey
  321. limitedByBucket := string(action) + ":" + bucket
  322. adminLimitedByBucket := s3_constants.ACTION_ADMIN + ":" + bucket
  323. for _, a := range identity.Actions {
  324. act := string(a)
  325. if strings.HasSuffix(act, "*") {
  326. if strings.HasPrefix(target, act[:len(act)-1]) {
  327. return true
  328. }
  329. if strings.HasPrefix(adminTarget, act[:len(act)-1]) {
  330. return true
  331. }
  332. } else {
  333. if act == limitedByBucket {
  334. return true
  335. }
  336. if act == adminLimitedByBucket {
  337. return true
  338. }
  339. }
  340. }
  341. return false
  342. }
  343. func (identity *Identity) isAdmin() bool {
  344. for _, a := range identity.Actions {
  345. if a == "Admin" {
  346. return true
  347. }
  348. }
  349. return false
  350. }