http_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package spnego
  2. import (
  3. "bytes"
  4. "crypto/rand"
  5. "encoding/hex"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "log"
  11. "mime/multipart"
  12. "net/http"
  13. "net/http/cookiejar"
  14. "net/http/httptest"
  15. "os"
  16. "sync"
  17. "testing"
  18. "github.com/gorilla/sessions"
  19. "github.com/jcmturner/goidentity/v6"
  20. "github.com/jcmturner/gokrb5/v8/client"
  21. "github.com/jcmturner/gokrb5/v8/config"
  22. "github.com/jcmturner/gokrb5/v8/keytab"
  23. "github.com/jcmturner/gokrb5/v8/service"
  24. "github.com/jcmturner/gokrb5/v8/test"
  25. "github.com/jcmturner/gokrb5/v8/test/testdata"
  26. "github.com/stretchr/testify/assert"
  27. )
  28. func TestClient_SetSPNEGOHeader(t *testing.T) {
  29. test.Integration(t)
  30. b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
  31. kt := keytab.New()
  32. kt.Unmarshal(b)
  33. c, _ := config.NewFromString(testdata.TEST_KRB5CONF)
  34. addr := os.Getenv("TEST_KDC_ADDR")
  35. if addr == "" {
  36. addr = testdata.TEST_KDC_ADDR
  37. }
  38. c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
  39. l := log.New(os.Stderr, "SPNEGO Client:", log.LstdFlags)
  40. cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c, client.Logger(l))
  41. err := cl.Login()
  42. if err != nil {
  43. t.Fatalf("error on AS_REQ: %v\n", err)
  44. }
  45. urls := []string{
  46. "http://cname.test.gokrb5",
  47. "http://host.test.gokrb5",
  48. }
  49. paths := []string{
  50. "/modkerb/index.html",
  51. //"/modgssapi/index.html",
  52. }
  53. for _, url := range urls {
  54. for _, p := range paths {
  55. r, _ := http.NewRequest("GET", url+p, nil)
  56. httpResp, err := http.DefaultClient.Do(r)
  57. if err != nil {
  58. t.Fatalf("%s request error: %v", url+p, err)
  59. }
  60. assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected")
  61. err = SetSPNEGOHeader(cl, r, "")
  62. if err != nil {
  63. t.Fatalf("error setting client SPNEGO header: %v", err)
  64. }
  65. httpResp, err = http.DefaultClient.Do(r)
  66. if err != nil {
  67. t.Fatalf("%s request error: %v\n", url+p, err)
  68. }
  69. assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
  70. }
  71. }
  72. }
  73. func TestSPNEGOHTTPClient(t *testing.T) {
  74. test.Integration(t)
  75. b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
  76. kt := keytab.New()
  77. kt.Unmarshal(b)
  78. c, _ := config.NewFromString(testdata.TEST_KRB5CONF)
  79. addr := os.Getenv("TEST_KDC_ADDR")
  80. if addr == "" {
  81. addr = testdata.TEST_KDC_ADDR
  82. }
  83. c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
  84. l := log.New(os.Stderr, "SPNEGO Client:", log.LstdFlags)
  85. cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c, client.Logger(l))
  86. err := cl.Login()
  87. if err != nil {
  88. t.Fatalf("error on AS_REQ: %v\n", err)
  89. }
  90. urls := []string{
  91. "http://cname.test.gokrb5",
  92. "http://host.test.gokrb5",
  93. }
  94. // This path issues a redirect which the http client will automatically follow.
  95. // It should cause a replay issue if the negInit token is sent in the first instance.
  96. paths := []string{
  97. "/modgssapi", // This issues a redirect which the http client will automatically follow. Could cause a replay issue
  98. "/redirect",
  99. }
  100. for _, url := range urls {
  101. for _, p := range paths {
  102. r, _ := http.NewRequest("GET", url+p, nil)
  103. httpCl := http.DefaultClient
  104. httpCl.CheckRedirect = func(req *http.Request, via []*http.Request) error {
  105. t.Logf("http client redirect: %+v", *req)
  106. return nil
  107. }
  108. spnegoCl := NewClient(cl, httpCl, "")
  109. httpResp, err := spnegoCl.Do(r)
  110. if err != nil {
  111. t.Fatalf("%s request error: %v", url+p, err)
  112. }
  113. assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
  114. }
  115. }
  116. }
  117. func TestService_SPNEGOKRB_NoAuthHeader(t *testing.T) {
  118. s := httpServer()
  119. defer s.Close()
  120. r, _ := http.NewRequest("GET", s.URL, nil)
  121. httpResp, err := http.DefaultClient.Do(r)
  122. if err != nil {
  123. t.Fatalf("Request error: %v\n", err)
  124. }
  125. assert.Equal(t, http.StatusUnauthorized, httpResp.StatusCode, "Status code in response to client with no SPNEGO not as expected")
  126. assert.Equal(t, "Negotiate", httpResp.Header.Get("WWW-Authenticate"), "Negotiation header not set by server.")
  127. }
  128. func TestService_SPNEGOKRB_ValidUser(t *testing.T) {
  129. test.Integration(t)
  130. s := httpServer()
  131. defer s.Close()
  132. r, _ := http.NewRequest("GET", s.URL, nil)
  133. cl := getClient()
  134. err := SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
  135. if err != nil {
  136. t.Fatalf("error setting client's SPNEGO header: %v", err)
  137. }
  138. httpResp, err := http.DefaultClient.Do(r)
  139. if err != nil {
  140. t.Fatalf("Request error: %v\n", err)
  141. }
  142. assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
  143. }
  144. func TestService_SPNEGOKRB_Replay(t *testing.T) {
  145. test.Integration(t)
  146. s := httpServerWithoutSessionManager()
  147. defer s.Close()
  148. r1, _ := http.NewRequest("GET", s.URL, nil)
  149. cl := getClient()
  150. err := SetSPNEGOHeader(cl, r1, "HTTP/host.test.gokrb5")
  151. if err != nil {
  152. t.Fatalf("error setting client's SPNEGO header: %v", err)
  153. }
  154. // First request with this ticket should be accepted
  155. httpResp, err := http.DefaultClient.Do(r1)
  156. if err != nil {
  157. t.Fatalf("Request error: %v\n", err)
  158. }
  159. assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
  160. // Use ticket again should be rejected
  161. httpResp, err = http.DefaultClient.Do(r1)
  162. if err != nil {
  163. t.Fatalf("Request error: %v\n", err)
  164. }
  165. 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.")
  166. // Form a 2nd ticket
  167. r2, _ := http.NewRequest("GET", s.URL, nil)
  168. err = SetSPNEGOHeader(cl, r2, "HTTP/host.test.gokrb5")
  169. if err != nil {
  170. t.Fatalf("error setting client's SPNEGO header: %v", err)
  171. }
  172. // First use of 2nd ticket should be accepted
  173. httpResp, err = http.DefaultClient.Do(r2)
  174. if err != nil {
  175. t.Fatalf("Request error: %v\n", err)
  176. }
  177. assert.Equal(t, http.StatusOK, httpResp.StatusCode, "Status code in response to client SPNEGO request not as expected")
  178. // Using the 1st ticket again should still be rejected
  179. httpResp, err = http.DefaultClient.Do(r1)
  180. if err != nil {
  181. t.Fatalf("Request error: %v\n", err)
  182. }
  183. 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.")
  184. // Using the 2nd again should be rejected as replay
  185. httpResp, err = http.DefaultClient.Do(r2)
  186. if err != nil {
  187. t.Fatalf("Request error: %v\n", err)
  188. }
  189. 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.")
  190. }
  191. func TestService_SPNEGOKRB_ReplayCache_Concurrency(t *testing.T) {
  192. test.Integration(t)
  193. s := httpServerWithoutSessionManager()
  194. defer s.Close()
  195. r1, _ := http.NewRequest("GET", s.URL, nil)
  196. cl := getClient()
  197. err := SetSPNEGOHeader(cl, r1, "HTTP/host.test.gokrb5")
  198. if err != nil {
  199. t.Fatalf("error setting client's SPNEGO header: %v", err)
  200. }
  201. r1h := r1.Header.Get(HTTPHeaderAuthRequest)
  202. r2, _ := http.NewRequest("GET", s.URL, nil)
  203. err = SetSPNEGOHeader(cl, r2, "HTTP/host.test.gokrb5")
  204. if err != nil {
  205. t.Fatalf("error setting client's SPNEGO header: %v", err)
  206. }
  207. r2h := r2.Header.Get(HTTPHeaderAuthRequest)
  208. // Concurrent 1st requests should be OK
  209. var wg sync.WaitGroup
  210. wg.Add(2)
  211. go httpGet(r1, &wg)
  212. go httpGet(r2, &wg)
  213. wg.Wait()
  214. // A number of concurrent requests with the same ticket should be rejected due to replay
  215. var wg2 sync.WaitGroup
  216. noReq := 10
  217. wg2.Add(noReq * 2)
  218. for i := 0; i < noReq; i++ {
  219. rr1, _ := http.NewRequest("GET", s.URL, nil)
  220. rr1.Header.Set(HTTPHeaderAuthRequest, r1h)
  221. rr2, _ := http.NewRequest("GET", s.URL, nil)
  222. rr2.Header.Set(HTTPHeaderAuthRequest, r2h)
  223. go httpGet(rr1, &wg2)
  224. go httpGet(rr2, &wg2)
  225. }
  226. wg2.Wait()
  227. }
  228. func TestService_SPNEGOKRB_Upload(t *testing.T) {
  229. test.Integration(t)
  230. s := httpServer()
  231. defer s.Close()
  232. bodyBuf := &bytes.Buffer{}
  233. bodyWriter := multipart.NewWriter(bodyBuf)
  234. fileWriter, err := bodyWriter.CreateFormFile("uploadfile", "testfile.bin")
  235. if err != nil {
  236. t.Fatalf("error writing to buffer: %v", err)
  237. }
  238. data := make([]byte, 10240)
  239. rand.Read(data)
  240. br := bytes.NewReader(data)
  241. _, err = io.Copy(fileWriter, br)
  242. if err != nil {
  243. t.Fatalf("error copying bytes: %v", err)
  244. }
  245. bodyWriter.Close()
  246. r, _ := http.NewRequest("POST", s.URL, bodyBuf)
  247. r.Header.Set("Content-Type", bodyWriter.FormDataContentType())
  248. cl := getClient()
  249. cookieJar, _ := cookiejar.New(nil)
  250. httpCl := http.DefaultClient
  251. httpCl.Jar = cookieJar
  252. spnegoCl := NewClient(cl, httpCl, "HTTP/host.test.gokrb5")
  253. httpResp, err := spnegoCl.Do(r)
  254. if err != nil {
  255. t.Fatalf("Request error: %v\n", err)
  256. }
  257. if httpResp.StatusCode != http.StatusOK {
  258. bodyBytes, _ := ioutil.ReadAll(httpResp.Body)
  259. bodyString := string(bodyBytes)
  260. httpResp.Body.Close()
  261. t.Errorf("unexpected code from http server (%d): %s", httpResp.StatusCode, bodyString)
  262. }
  263. }
  264. func httpGet(r *http.Request, wg *sync.WaitGroup) {
  265. defer wg.Done()
  266. http.DefaultClient.Do(r)
  267. }
  268. func httpServerWithoutSessionManager() *httptest.Server {
  269. l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.LstdFlags)
  270. b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
  271. kt := keytab.New()
  272. kt.Unmarshal(b)
  273. th := http.HandlerFunc(testAppHandler)
  274. s := httptest.NewServer(SPNEGOKRB5Authenticate(th, kt, service.Logger(l)))
  275. return s
  276. }
  277. func httpServer() *httptest.Server {
  278. l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.LstdFlags)
  279. b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
  280. kt := keytab.New()
  281. kt.Unmarshal(b)
  282. th := http.HandlerFunc(testAppHandler)
  283. s := httptest.NewServer(SPNEGOKRB5Authenticate(th, kt, service.Logger(l), service.SessionManager(NewSessionMgr("gokrb5"))))
  284. return s
  285. }
  286. func testAppHandler(w http.ResponseWriter, r *http.Request) {
  287. if r.Method == http.MethodPost {
  288. maxUploadSize := int64(11240)
  289. if err := r.ParseMultipartForm(maxUploadSize); err != nil {
  290. http.Error(w, fmt.Sprintf("cannot parse multipart form: %v", err), http.StatusBadRequest)
  291. return
  292. }
  293. r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
  294. file, _, err := r.FormFile("uploadfile")
  295. if err != nil {
  296. http.Error(w, "INVALID_FILE", http.StatusBadRequest)
  297. return
  298. }
  299. defer file.Close()
  300. // write out to /dev/null
  301. _, err = io.Copy(ioutil.Discard, file)
  302. if err != nil {
  303. http.Error(w, "WRITE_ERR", http.StatusInternalServerError)
  304. return
  305. }
  306. }
  307. w.WriteHeader(http.StatusOK)
  308. id := goidentity.FromHTTPRequestContext(r)
  309. fmt.Fprintf(w, "<html>\nTEST.GOKRB5 Handler\nAuthenticed user: %s\nUser's realm: %s\n</html>",
  310. id.UserName(),
  311. id.Domain())
  312. return
  313. }
  314. func getClient() *client.Client {
  315. b, _ := hex.DecodeString(testdata.TESTUSER1_KEYTAB)
  316. kt := keytab.New()
  317. kt.Unmarshal(b)
  318. c, _ := config.NewFromString(testdata.TEST_KRB5CONF)
  319. c.LibDefaults.NoAddresses = true
  320. addr := os.Getenv("TEST_KDC_ADDR")
  321. if addr == "" {
  322. addr = testdata.TEST_KDC_ADDR
  323. }
  324. c.Realms[0].KDC = []string{addr + ":" + testdata.TEST_KDC}
  325. c.Realms[0].KPasswdServer = []string{addr + ":464"}
  326. cl := client.NewWithKeytab("testuser1", "TEST.GOKRB5", kt, c)
  327. return cl
  328. }
  329. type SessionMgr struct {
  330. skey []byte
  331. store sessions.Store
  332. cookieName string
  333. }
  334. func NewSessionMgr(cookieName string) SessionMgr {
  335. skey := []byte("thisistestsecret") // Best practice is to load this key from a secure location.
  336. return SessionMgr{
  337. skey: skey,
  338. store: sessions.NewCookieStore(skey),
  339. cookieName: cookieName,
  340. }
  341. }
  342. func (smgr SessionMgr) Get(r *http.Request, k string) ([]byte, error) {
  343. s, err := smgr.store.Get(r, smgr.cookieName)
  344. if err != nil {
  345. return nil, err
  346. }
  347. if s == nil {
  348. return nil, errors.New("nil session")
  349. }
  350. b, ok := s.Values[k].([]byte)
  351. if !ok {
  352. return nil, fmt.Errorf("could not get bytes held in session at %s", k)
  353. }
  354. return b, nil
  355. }
  356. func (smgr SessionMgr) New(w http.ResponseWriter, r *http.Request, k string, v []byte) error {
  357. s, err := smgr.store.New(r, smgr.cookieName)
  358. if err != nil {
  359. return fmt.Errorf("could not get new session from session manager: %v", err)
  360. }
  361. s.Values[k] = v
  362. return s.Save(r, w)
  363. }