auth_credentials.go 11 KB

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