http_test.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. package service
  2. import (
  3. "encoding/hex"
  4. "fmt"
  5. "github.com/stretchr/testify/assert"
  6. "gopkg.in/jcmturner/gokrb5.v2/client"
  7. "gopkg.in/jcmturner/gokrb5.v2/credentials"
  8. "gopkg.in/jcmturner/gokrb5.v2/iana/nametype"
  9. "gopkg.in/jcmturner/gokrb5.v2/keytab"
  10. "gopkg.in/jcmturner/gokrb5.v2/messages"
  11. "gopkg.in/jcmturner/gokrb5.v2/testdata"
  12. "gopkg.in/jcmturner/gokrb5.v2/types"
  13. "io/ioutil"
  14. "log"
  15. "net/http"
  16. "net/http/httptest"
  17. "sync"
  18. "testing"
  19. "time"
  20. )
  21. func TestService_SPNEGOKRB_NoAuthHeader(t *testing.T) {
  22. s := httpServer()
  23. defer s.Close()
  24. r, _ := http.NewRequest("GET", s.URL, nil)
  25. httpResp, err := http.DefaultClient.Do(r)
  26. if err != nil {
  27. t.Fatalf("Request error: %v\n", err)
  28. }
  29. assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected")
  30. assert.Equal(t, "Negotiate", httpResp.Header.Get("WWW-Authenticate"), "Negitation header not set by server.")
  31. }
  32. func TestService_SPNEGOKRB_ValidUser(t *testing.T) {
  33. s := httpServer()
  34. defer s.Close()
  35. cl := getClient()
  36. sname := types.PrincipalName{
  37. NameType: nametype.KRB_NT_PRINCIPAL,
  38. NameString: []string{"HTTP", "host.test.gokrb5"},
  39. }
  40. b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
  41. kt, _ := keytab.Parse(b)
  42. st := time.Now().UTC()
  43. tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
  44. sname, "TEST.GOKRB5",
  45. types.NewKrbFlags(),
  46. kt,
  47. 18,
  48. 1,
  49. st,
  50. st,
  51. st.Add(time.Duration(24)*time.Hour),
  52. st.Add(time.Duration(48)*time.Hour),
  53. )
  54. if err != nil {
  55. t.Fatalf("Error getting test ticket: %v", err)
  56. }
  57. r, _ := http.NewRequest("GET", s.URL, nil)
  58. err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r)
  59. if err != nil {
  60. t.Fatalf("Error setting client SPNEGO header: %v", err)
  61. }
  62. httpResp, err := http.DefaultClient.Do(r)
  63. if err != nil {
  64. t.Fatalf("Request error: %v\n", err)
  65. }
  66. assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
  67. }
  68. func TestService_SPNEGOKRB_Replay(t *testing.T) {
  69. s := httpServer()
  70. defer s.Close()
  71. cl := getClient()
  72. sname := types.PrincipalName{
  73. NameType: nametype.KRB_NT_PRINCIPAL,
  74. NameString: []string{"HTTP", "host.test.gokrb5"},
  75. }
  76. b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
  77. kt, _ := keytab.Parse(b)
  78. st := time.Now().UTC()
  79. tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
  80. sname, "TEST.GOKRB5",
  81. types.NewKrbFlags(),
  82. kt,
  83. 18,
  84. 1,
  85. st,
  86. st,
  87. st.Add(time.Duration(24)*time.Hour),
  88. st.Add(time.Duration(48)*time.Hour),
  89. )
  90. if err != nil {
  91. t.Fatalf("Error getting test ticket: %v", err)
  92. }
  93. r1, _ := http.NewRequest("GET", s.URL, nil)
  94. err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r1)
  95. if err != nil {
  96. t.Fatalf("Error setting client SPNEGO header: %v", err)
  97. }
  98. // First request with this ticket should be accepted
  99. httpResp, err := http.DefaultClient.Do(r1)
  100. if err != nil {
  101. t.Fatalf("Request error: %v\n", err)
  102. }
  103. assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
  104. // Use ticket again should be rejected
  105. httpResp, err = http.DefaultClient.Do(r1)
  106. if err != nil {
  107. t.Fatalf("Request error: %v\n", err)
  108. }
  109. assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.")
  110. // Form a 2nd ticket
  111. st = time.Now().UTC()
  112. tkt2, sessionKey2, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
  113. sname, "TEST.GOKRB5",
  114. types.NewKrbFlags(),
  115. kt,
  116. 18,
  117. 1,
  118. st,
  119. st,
  120. st.Add(time.Duration(24)*time.Hour),
  121. st.Add(time.Duration(48)*time.Hour),
  122. )
  123. if err != nil {
  124. t.Fatalf("Error getting test ticket: %v", err)
  125. }
  126. r2, _ := http.NewRequest("GET", s.URL, nil)
  127. err = client.SetSPNEGOHeader(*cl.Credentials, tkt2, sessionKey2, r2)
  128. if err != nil {
  129. t.Fatalf("Error setting client SPNEGO header: %v", err)
  130. }
  131. // First use of 2nd ticket should be accepted
  132. httpResp, err = http.DefaultClient.Do(r2)
  133. if err != nil {
  134. t.Fatalf("Request error: %v\n", err)
  135. }
  136. assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
  137. // Using the 1st ticket again should still be rejected
  138. httpResp, err = http.DefaultClient.Do(r1)
  139. if err != nil {
  140. t.Fatalf("Request error: %v\n", err)
  141. }
  142. assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.")
  143. // Using the 2nd again should be rejected as replay
  144. httpResp, err = http.DefaultClient.Do(r2)
  145. if err != nil {
  146. t.Fatalf("Request error: %v\n", err)
  147. }
  148. assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected. Expected a replay to be detected.")
  149. }
  150. func TestService_SPNEGOKRB_ReplayCache_Concurrency(t *testing.T) {
  151. s := httpServer()
  152. defer s.Close()
  153. cl := getClient()
  154. sname := types.PrincipalName{
  155. NameType: nametype.KRB_NT_PRINCIPAL,
  156. NameString: []string{"HTTP", "host.test.gokrb5"},
  157. }
  158. b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
  159. kt, _ := keytab.Parse(b)
  160. st := time.Now().UTC()
  161. tkt, sessionKey, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
  162. sname, "TEST.GOKRB5",
  163. types.NewKrbFlags(),
  164. kt,
  165. 18,
  166. 1,
  167. st,
  168. st,
  169. st.Add(time.Duration(24)*time.Hour),
  170. st.Add(time.Duration(48)*time.Hour),
  171. )
  172. if err != nil {
  173. t.Fatalf("Error getting test ticket: %v", err)
  174. }
  175. r1, _ := http.NewRequest("GET", s.URL, nil)
  176. err = client.SetSPNEGOHeader(*cl.Credentials, tkt, sessionKey, r1)
  177. if err != nil {
  178. t.Fatalf("Error setting client SPNEGO header: %v", err)
  179. }
  180. // Form a 2nd ticket
  181. st = time.Now().UTC()
  182. tkt2, sessionKey2, err := messages.NewTicket(cl.Credentials.CName, cl.Credentials.Realm,
  183. sname, "TEST.GOKRB5",
  184. types.NewKrbFlags(),
  185. kt,
  186. 18,
  187. 1,
  188. st,
  189. st,
  190. st.Add(time.Duration(24)*time.Hour),
  191. st.Add(time.Duration(48)*time.Hour),
  192. )
  193. if err != nil {
  194. t.Fatalf("Error getting test ticket: %v", err)
  195. }
  196. r2, _ := http.NewRequest("GET", s.URL, nil)
  197. err = client.SetSPNEGOHeader(*cl.Credentials, tkt2, sessionKey2, r2)
  198. if err != nil {
  199. t.Fatalf("Error setting client SPNEGO header: %v", err)
  200. }
  201. // Concurrent 1st requests should be OK
  202. var wg sync.WaitGroup
  203. wg.Add(2)
  204. go httpGet(r1, &wg)
  205. go httpGet(r2, &wg)
  206. wg.Wait()
  207. // A number of concurrent requests with the same ticket should be rejected due to replay
  208. var wg2 sync.WaitGroup
  209. noReq := 10
  210. wg2.Add(noReq * 2)
  211. for i := 0; i < noReq; i++ {
  212. go httpGet(r1, &wg2)
  213. go httpGet(r2, &wg2)
  214. }
  215. wg2.Wait()
  216. }
  217. func httpGet(r *http.Request, wg *sync.WaitGroup) {
  218. defer wg.Done()
  219. http.DefaultClient.Do(r)
  220. }
  221. func httpServer() *httptest.Server {
  222. l := log.New(ioutil.Discard, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile)
  223. b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
  224. kt, _ := keytab.Parse(b)
  225. th := http.HandlerFunc(testAppHandler)
  226. s := httptest.NewServer(SPNEGOKRB5Authenticate(th, kt, "", l))
  227. return s
  228. }
  229. func testAppHandler(w http.ResponseWriter, r *http.Request) {
  230. w.WriteHeader(http.StatusOK)
  231. ctx := r.Context()
  232. fmt.Fprintf(w, "<html>\nTEST.GOKRB5 Handler\nAuthenticed user: %s\nUser's realm: %s\n</html>", ctx.Value(CTXKeyCredentials).(credentials.Credentials).Username, ctx.Value(CTXKeyCredentials).(credentials.Credentials).Realm)
  233. return
  234. }