auth.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. // mgo - MongoDB driver for Go
  2. //
  3. // Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
  4. //
  5. // All rights reserved.
  6. //
  7. // Redistribution and use in source and binary forms, with or without
  8. // modification, are permitted provided that the following conditions are met:
  9. //
  10. // 1. Redistributions of source code must retain the above copyright notice, this
  11. // list of conditions and the following disclaimer.
  12. // 2. Redistributions in binary form must reproduce the above copyright notice,
  13. // this list of conditions and the following disclaimer in the documentation
  14. // and/or other materials provided with the distribution.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  20. // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. package mgo
  27. import (
  28. "crypto/md5"
  29. "crypto/sha1"
  30. "encoding/hex"
  31. "errors"
  32. "fmt"
  33. "sync"
  34. "gopkg.in/mgo.v2/bson"
  35. "gopkg.in/mgo.v2/internal/scram"
  36. )
  37. type authCmd struct {
  38. Authenticate int
  39. Nonce string
  40. User string
  41. Key string
  42. }
  43. type startSaslCmd struct {
  44. StartSASL int `bson:"startSasl"`
  45. }
  46. type authResult struct {
  47. ErrMsg string
  48. Ok bool
  49. }
  50. type getNonceCmd struct {
  51. GetNonce int
  52. }
  53. type getNonceResult struct {
  54. Nonce string
  55. Err string "$err"
  56. Code int
  57. }
  58. type logoutCmd struct {
  59. Logout int
  60. }
  61. type saslCmd struct {
  62. Start int `bson:"saslStart,omitempty"`
  63. Continue int `bson:"saslContinue,omitempty"`
  64. ConversationId int `bson:"conversationId,omitempty"`
  65. Mechanism string `bson:"mechanism,omitempty"`
  66. Payload []byte
  67. }
  68. type saslResult struct {
  69. Ok bool `bson:"ok"`
  70. NotOk bool `bson:"code"` // Server <= 2.3.2 returns ok=1 & code>0 on errors (WTF?)
  71. Done bool
  72. ConversationId int `bson:"conversationId"`
  73. Payload []byte
  74. ErrMsg string
  75. }
  76. type saslStepper interface {
  77. Step(serverData []byte) (clientData []byte, done bool, err error)
  78. Close()
  79. }
  80. func (socket *mongoSocket) getNonce() (nonce string, err error) {
  81. socket.Lock()
  82. for socket.cachedNonce == "" && socket.dead == nil {
  83. debugf("Socket %p to %s: waiting for nonce", socket, socket.addr)
  84. socket.gotNonce.Wait()
  85. }
  86. if socket.cachedNonce == "mongos" {
  87. socket.Unlock()
  88. return "", errors.New("Can't authenticate with mongos; see http://j.mp/mongos-auth")
  89. }
  90. debugf("Socket %p to %s: got nonce", socket, socket.addr)
  91. nonce, err = socket.cachedNonce, socket.dead
  92. socket.cachedNonce = ""
  93. socket.Unlock()
  94. if err != nil {
  95. nonce = ""
  96. }
  97. return
  98. }
  99. func (socket *mongoSocket) resetNonce() {
  100. debugf("Socket %p to %s: requesting a new nonce", socket, socket.addr)
  101. op := &queryOp{}
  102. op.query = &getNonceCmd{GetNonce: 1}
  103. op.collection = "admin.$cmd"
  104. op.limit = -1
  105. op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
  106. if err != nil {
  107. socket.kill(errors.New("getNonce: "+err.Error()), true)
  108. return
  109. }
  110. result := &getNonceResult{}
  111. err = bson.Unmarshal(docData, &result)
  112. if err != nil {
  113. socket.kill(errors.New("Failed to unmarshal nonce: "+err.Error()), true)
  114. return
  115. }
  116. debugf("Socket %p to %s: nonce unmarshalled: %#v", socket, socket.addr, result)
  117. if result.Code == 13390 {
  118. // mongos doesn't yet support auth (see http://j.mp/mongos-auth)
  119. result.Nonce = "mongos"
  120. } else if result.Nonce == "" {
  121. var msg string
  122. if result.Err != "" {
  123. msg = fmt.Sprintf("Got an empty nonce: %s (%d)", result.Err, result.Code)
  124. } else {
  125. msg = "Got an empty nonce"
  126. }
  127. socket.kill(errors.New(msg), true)
  128. return
  129. }
  130. socket.Lock()
  131. if socket.cachedNonce != "" {
  132. socket.Unlock()
  133. panic("resetNonce: nonce already cached")
  134. }
  135. socket.cachedNonce = result.Nonce
  136. socket.gotNonce.Signal()
  137. socket.Unlock()
  138. }
  139. err := socket.Query(op)
  140. if err != nil {
  141. socket.kill(errors.New("resetNonce: "+err.Error()), true)
  142. }
  143. }
  144. func (socket *mongoSocket) Login(cred Credential) error {
  145. socket.Lock()
  146. if cred.Mechanism == "" && socket.serverInfo.MaxWireVersion >= 3 {
  147. cred.Mechanism = "SCRAM-SHA-1"
  148. }
  149. for _, sockCred := range socket.creds {
  150. if sockCred == cred {
  151. debugf("Socket %p to %s: login: db=%q user=%q (already logged in)", socket, socket.addr, cred.Source, cred.Username)
  152. socket.Unlock()
  153. return nil
  154. }
  155. }
  156. if socket.dropLogout(cred) {
  157. debugf("Socket %p to %s: login: db=%q user=%q (cached)", socket, socket.addr, cred.Source, cred.Username)
  158. socket.creds = append(socket.creds, cred)
  159. socket.Unlock()
  160. return nil
  161. }
  162. socket.Unlock()
  163. debugf("Socket %p to %s: login: db=%q user=%q", socket, socket.addr, cred.Source, cred.Username)
  164. var err error
  165. switch cred.Mechanism {
  166. case "", "MONGODB-CR", "MONGO-CR": // Name changed to MONGODB-CR in SERVER-8501.
  167. err = socket.loginClassic(cred)
  168. case "PLAIN":
  169. err = socket.loginPlain(cred)
  170. case "MONGODB-X509":
  171. err = socket.loginX509(cred)
  172. default:
  173. // Try SASL for everything else, if it is available.
  174. err = socket.loginSASL(cred)
  175. }
  176. if err != nil {
  177. debugf("Socket %p to %s: login error: %s", socket, socket.addr, err)
  178. } else {
  179. debugf("Socket %p to %s: login successful", socket, socket.addr)
  180. }
  181. return err
  182. }
  183. func (socket *mongoSocket) loginClassic(cred Credential) error {
  184. // Note that this only works properly because this function is
  185. // synchronous, which means the nonce won't get reset while we're
  186. // using it and any other login requests will block waiting for a
  187. // new nonce provided in the defer call below.
  188. nonce, err := socket.getNonce()
  189. if err != nil {
  190. return err
  191. }
  192. defer socket.resetNonce()
  193. psum := md5.New()
  194. psum.Write([]byte(cred.Username + ":mongo:" + cred.Password))
  195. ksum := md5.New()
  196. ksum.Write([]byte(nonce + cred.Username))
  197. ksum.Write([]byte(hex.EncodeToString(psum.Sum(nil))))
  198. key := hex.EncodeToString(ksum.Sum(nil))
  199. cmd := authCmd{Authenticate: 1, User: cred.Username, Nonce: nonce, Key: key}
  200. res := authResult{}
  201. return socket.loginRun(cred.Source, &cmd, &res, func() error {
  202. if !res.Ok {
  203. return errors.New(res.ErrMsg)
  204. }
  205. socket.Lock()
  206. socket.dropAuth(cred.Source)
  207. socket.creds = append(socket.creds, cred)
  208. socket.Unlock()
  209. return nil
  210. })
  211. }
  212. type authX509Cmd struct {
  213. Authenticate int
  214. User string
  215. Mechanism string
  216. }
  217. func (socket *mongoSocket) loginX509(cred Credential) error {
  218. cmd := authX509Cmd{Authenticate: 1, User: cred.Username, Mechanism: "MONGODB-X509"}
  219. res := authResult{}
  220. return socket.loginRun(cred.Source, &cmd, &res, func() error {
  221. if !res.Ok {
  222. return errors.New(res.ErrMsg)
  223. }
  224. socket.Lock()
  225. socket.dropAuth(cred.Source)
  226. socket.creds = append(socket.creds, cred)
  227. socket.Unlock()
  228. return nil
  229. })
  230. }
  231. func (socket *mongoSocket) loginPlain(cred Credential) error {
  232. cmd := saslCmd{Start: 1, Mechanism: "PLAIN", Payload: []byte("\x00" + cred.Username + "\x00" + cred.Password)}
  233. res := authResult{}
  234. return socket.loginRun(cred.Source, &cmd, &res, func() error {
  235. if !res.Ok {
  236. return errors.New(res.ErrMsg)
  237. }
  238. socket.Lock()
  239. socket.dropAuth(cred.Source)
  240. socket.creds = append(socket.creds, cred)
  241. socket.Unlock()
  242. return nil
  243. })
  244. }
  245. func (socket *mongoSocket) loginSASL(cred Credential) error {
  246. var sasl saslStepper
  247. var err error
  248. if cred.Mechanism == "SCRAM-SHA-1" {
  249. // SCRAM is handled without external libraries.
  250. sasl = saslNewScram(cred)
  251. } else if len(cred.ServiceHost) > 0 {
  252. sasl, err = saslNew(cred, cred.ServiceHost)
  253. } else {
  254. sasl, err = saslNew(cred, socket.Server().Addr)
  255. }
  256. if err != nil {
  257. return err
  258. }
  259. defer sasl.Close()
  260. // The goal of this logic is to carry a locked socket until the
  261. // local SASL step confirms the auth is valid; the socket needs to be
  262. // locked so that concurrent action doesn't leave the socket in an
  263. // auth state that doesn't reflect the operations that took place.
  264. // As a simple case, imagine inverting login=>logout to logout=>login.
  265. //
  266. // The logic below works because the lock func isn't called concurrently.
  267. locked := false
  268. lock := func(b bool) {
  269. if locked != b {
  270. locked = b
  271. if b {
  272. socket.Lock()
  273. } else {
  274. socket.Unlock()
  275. }
  276. }
  277. }
  278. lock(true)
  279. defer lock(false)
  280. start := 1
  281. cmd := saslCmd{}
  282. res := saslResult{}
  283. for {
  284. payload, done, err := sasl.Step(res.Payload)
  285. if err != nil {
  286. return err
  287. }
  288. if done && res.Done {
  289. socket.dropAuth(cred.Source)
  290. socket.creds = append(socket.creds, cred)
  291. break
  292. }
  293. lock(false)
  294. cmd = saslCmd{
  295. Start: start,
  296. Continue: 1 - start,
  297. ConversationId: res.ConversationId,
  298. Mechanism: cred.Mechanism,
  299. Payload: payload,
  300. }
  301. start = 0
  302. err = socket.loginRun(cred.Source, &cmd, &res, func() error {
  303. // See the comment on lock for why this is necessary.
  304. lock(true)
  305. if !res.Ok || res.NotOk {
  306. return fmt.Errorf("server returned error on SASL authentication step: %s", res.ErrMsg)
  307. }
  308. return nil
  309. })
  310. if err != nil {
  311. return err
  312. }
  313. if done && res.Done {
  314. socket.dropAuth(cred.Source)
  315. socket.creds = append(socket.creds, cred)
  316. break
  317. }
  318. }
  319. return nil
  320. }
  321. func saslNewScram(cred Credential) *saslScram {
  322. credsum := md5.New()
  323. credsum.Write([]byte(cred.Username + ":mongo:" + cred.Password))
  324. client := scram.NewClient(sha1.New, cred.Username, hex.EncodeToString(credsum.Sum(nil)))
  325. return &saslScram{cred: cred, client: client}
  326. }
  327. type saslScram struct {
  328. cred Credential
  329. client *scram.Client
  330. }
  331. func (s *saslScram) Close() {}
  332. func (s *saslScram) Step(serverData []byte) (clientData []byte, done bool, err error) {
  333. more := s.client.Step(serverData)
  334. return s.client.Out(), !more, s.client.Err()
  335. }
  336. func (socket *mongoSocket) loginRun(db string, query, result interface{}, f func() error) error {
  337. var mutex sync.Mutex
  338. var replyErr error
  339. mutex.Lock()
  340. op := queryOp{}
  341. op.query = query
  342. op.collection = db + ".$cmd"
  343. op.limit = -1
  344. op.replyFunc = func(err error, reply *replyOp, docNum int, docData []byte) {
  345. defer mutex.Unlock()
  346. if err != nil {
  347. replyErr = err
  348. return
  349. }
  350. err = bson.Unmarshal(docData, result)
  351. if err != nil {
  352. replyErr = err
  353. } else {
  354. // Must handle this within the read loop for the socket, so
  355. // that concurrent login requests are properly ordered.
  356. replyErr = f()
  357. }
  358. }
  359. err := socket.Query(&op)
  360. if err != nil {
  361. return err
  362. }
  363. mutex.Lock() // Wait.
  364. return replyErr
  365. }
  366. func (socket *mongoSocket) Logout(db string) {
  367. socket.Lock()
  368. cred, found := socket.dropAuth(db)
  369. if found {
  370. debugf("Socket %p to %s: logout: db=%q (flagged)", socket, socket.addr, db)
  371. socket.logout = append(socket.logout, cred)
  372. }
  373. socket.Unlock()
  374. }
  375. func (socket *mongoSocket) LogoutAll() {
  376. socket.Lock()
  377. if l := len(socket.creds); l > 0 {
  378. debugf("Socket %p to %s: logout all (flagged %d)", socket, socket.addr, l)
  379. socket.logout = append(socket.logout, socket.creds...)
  380. socket.creds = socket.creds[0:0]
  381. }
  382. socket.Unlock()
  383. }
  384. func (socket *mongoSocket) flushLogout() (ops []interface{}) {
  385. socket.Lock()
  386. if l := len(socket.logout); l > 0 {
  387. debugf("Socket %p to %s: logout all (flushing %d)", socket, socket.addr, l)
  388. for i := 0; i != l; i++ {
  389. op := queryOp{}
  390. op.query = &logoutCmd{1}
  391. op.collection = socket.logout[i].Source + ".$cmd"
  392. op.limit = -1
  393. ops = append(ops, &op)
  394. }
  395. socket.logout = socket.logout[0:0]
  396. }
  397. socket.Unlock()
  398. return
  399. }
  400. func (socket *mongoSocket) dropAuth(db string) (cred Credential, found bool) {
  401. for i, sockCred := range socket.creds {
  402. if sockCred.Source == db {
  403. copy(socket.creds[i:], socket.creds[i+1:])
  404. socket.creds = socket.creds[:len(socket.creds)-1]
  405. return sockCred, true
  406. }
  407. }
  408. return cred, false
  409. }
  410. func (socket *mongoSocket) dropLogout(cred Credential) (found bool) {
  411. for i, sockCred := range socket.logout {
  412. if sockCred == cred {
  413. copy(socket.logout[i:], socket.logout[i+1:])
  414. socket.logout = socket.logout[:len(socket.logout)-1]
  415. return true
  416. }
  417. }
  418. return false
  419. }