http.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. package service
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "gopkg.in/jcmturner/gokrb5.v2/gssapi"
  7. "gopkg.in/jcmturner/gokrb5.v2/keytab"
  8. "log"
  9. "net/http"
  10. "strings"
  11. )
  12. type ctxKey int
  13. const (
  14. // spnegoNegTokenRespKRBAcceptCompleted - The response on successful authentication always has this header. Capturing as const so we don't have marshaling and encoding overhead.
  15. spnegoNegTokenRespKRBAcceptCompleted = "Negotiate oRQwEqADCgEAoQsGCSqGSIb3EgECAg=="
  16. // spnegoNegTokenRespReject - The response on a failed authentication always has this rejection header. Capturing as const so we don't have marshaling and encoding overhead.
  17. spnegoNegTokenRespReject = "Negotiate oQcwBaADCgEC"
  18. // CTXKeyAuthenticated is the request context key holding a boolean indicating if the request has been authenticated.
  19. CTXKeyAuthenticated ctxKey = 0
  20. // CTXKeyCredentials is the request context key holding the credentials gopkg.in/jcmturner/goidentity.v1/Identity object.
  21. CTXKeyCredentials ctxKey = 1
  22. )
  23. // SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper.
  24. //
  25. // kt - keytab for the service user
  26. //
  27. // sa - service account name.
  28. // If Active Directory is used for the KDC this is the account name you have set the SPN against (setspn.exe -a "HTTP/<fqdn>" <account name>)
  29. // If the SPN was added to the KDC without associating it with an account pass and empty string "". This is the case if you create the SPN in MIT KDC with: /usr/sbin/kadmin.local -q "add_principal HTTP/<fqdn>"
  30. func SPNEGOKRB5Authenticate(f http.Handler, kt keytab.Keytab, sa string, l *log.Logger) http.Handler {
  31. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  32. s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
  33. if len(s) != 2 || s[0] != "Negotiate" {
  34. w.Header().Set("WWW-Authenticate", "Negotiate")
  35. w.WriteHeader(401)
  36. w.Write([]byte("Unauthorised.\n"))
  37. return
  38. }
  39. b, err := base64.StdEncoding.DecodeString(s[1])
  40. if err != nil {
  41. rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO error in base64 decoding negotiation header: %v", r.RemoteAddr, err))
  42. return
  43. }
  44. var spnego gssapi.SPNEGO
  45. err = spnego.Unmarshal(b)
  46. if !spnego.Init {
  47. rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO negotiation token is not a NegTokenInit: %v", r.RemoteAddr, err))
  48. return
  49. }
  50. if !spnego.NegTokenInit.MechTypes[0].Equal(gssapi.MechTypeOIDKRB5) && !spnego.NegTokenInit.MechTypes[0].Equal(gssapi.MechTypeOIDMSLegacyKRB5) {
  51. rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO OID of MechToken is not of type KRB5", r.RemoteAddr))
  52. return
  53. }
  54. var mt gssapi.MechToken
  55. err = mt.Unmarshal(spnego.NegTokenInit.MechToken)
  56. if err != nil {
  57. rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO error unmarshaling MechToken: %v", r.RemoteAddr, err))
  58. return
  59. }
  60. if !mt.IsAPReq() {
  61. rejectSPNEGO(w, l, fmt.Sprintf("%v - MechToken does not contain an AP_REQ - KRB_AP_ERR_MSG_TYPE", r.RemoteAddr))
  62. return
  63. }
  64. if ok, creds, err := ValidateAPREQ(mt.APReq, kt, sa, r.RemoteAddr); ok {
  65. ctx := r.Context()
  66. ctx = context.WithValue(ctx, CTXKeyCredentials, creds)
  67. ctx = context.WithValue(ctx, CTXKeyAuthenticated, true)
  68. if l != nil {
  69. l.Printf("%v %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, creds.Username, creds.Realm)
  70. }
  71. spnegoResponseAcceptCompleted(w)
  72. f.ServeHTTP(w, r.WithContext(ctx))
  73. } else {
  74. rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO Kerberos authentication failed: %v", r.RemoteAddr, err))
  75. return
  76. }
  77. })
  78. }
  79. // Set the headers for a rejected SPNEGO negotiation and return an unauthorized status code.
  80. func rejectSPNEGO(w http.ResponseWriter, l *log.Logger, logMsg string) {
  81. if l != nil {
  82. l.Println(logMsg)
  83. }
  84. spnegoResponseReject(w)
  85. }
  86. func spnegoResponseReject(w http.ResponseWriter) {
  87. w.Header().Set("WWW-Authenticate", spnegoNegTokenRespReject)
  88. w.WriteHeader(http.StatusUnauthorized)
  89. w.Write([]byte("Unauthorised.\n"))
  90. }
  91. func spnegoResponseAcceptCompleted(w http.ResponseWriter) {
  92. w.Header().Set("WWW-Authenticate", spnegoNegTokenRespKRBAcceptCompleted)
  93. }