http.go 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. package service
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "net/http"
  7. "strings"
  8. )
  9. // POTENTIAL BREAKING CHANGE notice. Context keys used will change to a name-spaced strings to avoid clashes.
  10. // If you are using the constants service.CTXKeyAuthenticated and service.CTXKeyCredentials
  11. // defined below when retrieving data from the request context your code will be unaffected.
  12. // However if, for example, you are retrieving context like this: r.Context().Value(1) then
  13. // you will need to update to replace the 1 with service.CTXKeyCredentials.
  14. type ctxKey int
  15. const (
  16. // spnegoNegTokenRespKRBAcceptCompleted - The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead.
  17. spnegoNegTokenRespKRBAcceptCompleted = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg=="
  18. // spnegoNegTokenRespReject - The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead.
  19. spnegoNegTokenRespReject = "Negotiate oQcwBaADCgEC"
  20. // CTXKeyAuthenticated is the request context key holding a boolean indicating if the request has been authenticated.
  21. CTXKeyAuthenticated ctxKey = 0
  22. // CTXKeyCredentials is the request context key holding the credentials gopkg.in/jcmturner/goidentity.v2/Identity object.
  23. CTXKeyCredentials ctxKey = 1
  24. // HTTPHeaderAuthRequest is the header that will hold authn/z information.
  25. HTTPHeaderAuthRequest = "Authorization"
  26. // HTTPHeaderAuthResponse is the header that will hold SPNEGO data from the server.
  27. HTTPHeaderAuthResponse = "WWW-Authenticate"
  28. // HTTPHeaderAuthResponseValueKey is the key in the auth header for SPNEGO.
  29. HTTPHeaderAuthResponseValueKey = "Negotiate"
  30. // UnauthorizedMsg is the message returned in the body when authentication fails.
  31. UnauthorizedMsg = "Unauthorised.\n"
  32. )
  33. // SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper.
  34. func SPNEGOKRB5Authenticate(f http.Handler, c *Config, l *log.Logger) http.Handler {
  35. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  36. s := strings.SplitN(r.Header.Get(HTTPHeaderAuthRequest), " ", 2)
  37. if len(s) != 2 || s[0] != HTTPHeaderAuthResponseValueKey {
  38. w.Header().Set(HTTPHeaderAuthResponse, HTTPHeaderAuthResponseValueKey)
  39. http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
  40. return
  41. }
  42. id, authned, err := c.Authenticate(s[1], r.RemoteAddr)
  43. if err != nil {
  44. rejectSPNEGO(w, l, fmt.Sprintf("%v - %v", r.RemoteAddr, err))
  45. return
  46. }
  47. if authned {
  48. ctx := r.Context()
  49. ctx = context.WithValue(ctx, CTXKeyCredentials, id)
  50. ctx = context.WithValue(ctx, CTXKeyAuthenticated, true)
  51. if l != nil {
  52. l.Printf("%v %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain())
  53. }
  54. spnegoResponseAcceptCompleted(w)
  55. f.ServeHTTP(w, r.WithContext(ctx))
  56. } else {
  57. rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO Kerberos authentication failed: %v", r.RemoteAddr, err))
  58. return
  59. }
  60. return
  61. })
  62. }
  63. // Set the headers for a rejected SPNEGO negotiation and return an unauthorized status code.
  64. func rejectSPNEGO(w http.ResponseWriter, l *log.Logger, logMsg string) {
  65. if l != nil {
  66. l.Println(logMsg)
  67. }
  68. spnegoResponseReject(w)
  69. }
  70. func spnegoResponseReject(w http.ResponseWriter) {
  71. w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespReject)
  72. http.Error(w, UnauthorizedMsg, http.StatusUnauthorized)
  73. }
  74. func spnegoResponseAcceptCompleted(w http.ResponseWriter) {
  75. w.Header().Set(HTTPHeaderAuthResponse, spnegoNegTokenRespKRBAcceptCompleted)
  76. }