Quellcode durchsuchen

configuration of service improvement

Jonathan Turner vor 7 Jahren
Ursprung
Commit
764ba85b75
7 geänderte Dateien mit 64 neuen und 63 gelöschten Zeilen
  1. 3 1
      examples/example-AD.go
  2. 2 1
      examples/example.go
  3. 2 1
      examples/httpServer.go
  4. 5 6
      service/APExchange.go
  5. 13 8
      service/authenticator.go
  6. 31 0
      service/config.go
  7. 8 46
      service/http.go

+ 3 - 1
examples/example-AD.go

@@ -65,7 +65,9 @@ func httpServer() *httptest.Server {
 	b, _ := hex.DecodeString(testdata.SYSHTTP_KEYTAB)
 	kt, _ := keytab.Parse(b)
 	th := http.HandlerFunc(testAppHandler)
-	s := httptest.NewServer(service.SPNEGOKRB5Authenticate(th, kt, "sysHTTP", false, l))
+	c := service.NewSPNEGOAuthenticator(kt)
+	c.ServicePrincipal = "sysHTTP"
+	s := httptest.NewServer(service.SPNEGOKRB5Authenticate(th, c, l))
 	return s
 }
 

+ 2 - 1
examples/example.go

@@ -65,7 +65,8 @@ func httpServer() *httptest.Server {
 	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
 	kt, _ := keytab.Parse(b)
 	th := http.HandlerFunc(testAppHandler)
-	s := httptest.NewServer(service.SPNEGOKRB5Authenticate(th, kt, "", false, l))
+	c := service.NewSPNEGOAuthenticator(kt)
+	s := httptest.NewServer(service.SPNEGOKRB5Authenticate(th, c, l))
 	return s
 }
 

+ 2 - 1
examples/httpServer.go

@@ -34,7 +34,8 @@ func main() {
 
 	// Set up handler mappings wrapping in the SPNEGOKRB5Authenticate handler wrapper
 	mux := http.NewServeMux()
-	mux.Handle("/", service.SPNEGOKRB5Authenticate(th, kt, "", false, l))
+	c := service.NewSPNEGOAuthenticator(kt)
+	mux.Handle("/", service.SPNEGOKRB5Authenticate(th, c, l))
 
 	// Start up the web server
 	log.Fatal(http.ListenAndServe(port, mux))

+ 5 - 6
service/APExchange.go

@@ -7,16 +7,15 @@ import (
 	"gopkg.in/jcmturner/gokrb5.v5/credentials"
 	"gopkg.in/jcmturner/gokrb5.v5/iana/errorcode"
 	"gopkg.in/jcmturner/gokrb5.v5/iana/flags"
-	"gopkg.in/jcmturner/gokrb5.v5/keytab"
 	"gopkg.in/jcmturner/gokrb5.v5/krberror"
 	"gopkg.in/jcmturner/gokrb5.v5/messages"
 	"gopkg.in/jcmturner/gokrb5.v5/types"
 )
 
 // ValidateAPREQ validates an AP_REQ sent to the service. Returns a boolean for if the AP_REQ is valid and the client's principal name and realm.
-func ValidateAPREQ(APReq messages.APReq, kt keytab.Keytab, sa string, cAddr string, requireHostAddr bool) (bool, credentials.Credentials, error) {
+func ValidateAPREQ(APReq messages.APReq, c *SPNEGOAuthenticator) (bool, credentials.Credentials, error) {
 	var creds credentials.Credentials
-	err := APReq.Ticket.DecryptEncPart(kt, sa)
+	err := APReq.Ticket.DecryptEncPart(*c.Keytab, c.ServicePrincipal)
 	if err != nil {
 		return false, creds, krberror.Errorf(err, krberror.DecryptingError, "error decrypting encpart of service ticket provided")
 	}
@@ -35,7 +34,7 @@ func ValidateAPREQ(APReq messages.APReq, kt keytab.Keytab, sa string, cAddr stri
 		//address of the client.  If no match is found or the server insists on
 		//ticket addresses but none are present in the ticket, the
 		//KRB_AP_ERR_BADADDR error is returned.
-		h, err := types.GetHostAddress(cAddr)
+		h, err := types.GetHostAddress(c.ClientAddr)
 		if err != nil {
 			err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, err.Error())
 			return false, creds, err
@@ -44,7 +43,7 @@ func ValidateAPREQ(APReq messages.APReq, kt keytab.Keytab, sa string, cAddr stri
 			err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "Client address not within the list contained in the service ticket")
 			return false, creds, err
 		}
-	} else if requireHostAddr {
+	} else if c.RequireHostAddr {
 		err := messages.NewKRBError(APReq.Ticket.SName, APReq.Ticket.Realm, errorcode.KRB_AP_ERR_BADADDR, "ticket does not contain HostAddress values required")
 		return false, creds, err
 	}
@@ -81,7 +80,7 @@ func ValidateAPREQ(APReq messages.APReq, kt keytab.Keytab, sa string, cAddr stri
 	creds.SetAuthTime(t)
 	creds.SetAuthenticated(true)
 	creds.SetValidUntil(APReq.Ticket.DecryptedEncPart.EndTime)
-	isPAC, pac, err := APReq.Ticket.GetPACType(kt, sa)
+	isPAC, pac, err := APReq.Ticket.GetPACType(*c.Keytab, c.ServicePrincipal)
 	if isPAC && err != nil {
 		return false, creds, err
 	}

+ 13 - 8
service/authenticator.go

@@ -17,15 +17,20 @@ import (
 
 // SPNEGOAuthenticator implements gopkg.in/jcmturner/goidentity.v2.Authenticator interface
 type SPNEGOAuthenticator struct {
-	SPNEGOHeaderValue string
-	Keytab            *keytab.Keytab
-	ServiceAccount    string
-	ClientAddr        string
-	RequireHostAddr   bool
+	SPNEGOHeaderValue  string
+	Keytab             *keytab.Keytab
+	ServicePrincipal   string
+	ClientAddr         string
+	RequireHostAddr    bool
+	DisablePACDecoding bool
+}
+
+func NewSPNEGOAuthenticator(kt keytab.Keytab) *SPNEGOAuthenticator {
+	return &SPNEGOAuthenticator{Keytab: &kt}
 }
 
 // Authenticate and retrieve a goidentity.Identity. In this case it is a pointer to a credentials.Credentials
-func (a SPNEGOAuthenticator) Authenticate() (i goidentity.Identity, ok bool, err error) {
+func (a *SPNEGOAuthenticator) Authenticate() (i goidentity.Identity, ok bool, err error) {
 	b, err := base64.StdEncoding.DecodeString(a.SPNEGOHeaderValue)
 	if err != nil {
 		err = fmt.Errorf("SPNEGO error in base64 decoding negotiation header: %v", err)
@@ -52,12 +57,12 @@ func (a SPNEGOAuthenticator) Authenticate() (i goidentity.Identity, ok bool, err
 		return
 	}
 
-	ok, c, err := ValidateAPREQ(mt.APReq, *a.Keytab, a.ServiceAccount, a.ClientAddr, a.RequireHostAddr)
+	ok, creds, err := ValidateAPREQ(mt.APReq, a)
 	if err != nil {
 		err = fmt.Errorf("SPNEGO validation error: %v", err)
 		return
 	}
-	i = &c
+	i = &creds
 	return
 }
 

+ 31 - 0
service/config.go

@@ -0,0 +1,31 @@
+package service
+
+import "gopkg.in/jcmturner/gokrb5.v5/keytab"
+
+// Config for service side implementation
+//
+// Keytab (mandatory) - keytab for the service user
+//
+// KeytabPrincipal (optional) - keytab principal override for the service.
+// The service looks for this principal in the keytab to use to decrypt tickets.
+// If "" is passed as KeytabPrincipal then the principal will be automatically derived
+// from the service name (SName) and realm in the ticket the service is trying to decrypt.
+// This is often sufficient if you create the SPN in MIT KDC with: /usr/sbin/kadmin.local -q "add_principal HTTP/<fqdn>"
+// When Active Directory is used for the KDC this may need to be the account name you have set the SPN against
+// (setspn.exe -a "HTTP/<fqdn>" <account name>)
+// If you are unsure run:
+//
+// klist -k <service's keytab file>
+//
+// and use the value from the Principal column for the keytab entry the service should use.
+//
+// RequireHostAddr - require that the kerberos ticket must include client host IP addresses and one must match the client making the request.
+// This is controled in the client config with the noaddresses option (http://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html).
+//
+// DisablePACDecoding - if set to true decoding of the Microsoft PAC will be disabled.
+type Config struct {
+	Keytab             keytab.Keytab
+	KeytabPrincipal    string
+	RequireHostAddr    bool
+	DisablePACDecoding bool
+}

+ 8 - 46
service/http.go

@@ -2,14 +2,10 @@ package service
 
 import (
 	"context"
-	"encoding/base64"
 	"fmt"
 	"log"
 	"net/http"
 	"strings"
-
-	"gopkg.in/jcmturner/gokrb5.v5/gssapi"
-	"gopkg.in/jcmturner/gokrb5.v5/keytab"
 )
 
 // POTENTIAL BREAKING CHANGE notice. Context keys used will change to a name-spaced strings to avoid clashes.
@@ -39,22 +35,7 @@ const (
 )
 
 // SPNEGOKRB5Authenticate is a Kerberos SPNEGO authentication HTTP handler wrapper.
-//
-// kt - keytab for the service user
-//
-// ktprinc - keytab principal override for the service.
-// The service looks for this principal in the keytab to use to decrypt tickets.
-// If "" is passed as ktprinc then the principal will be automatically derived
-// from the service name (SName) and realm in the ticket the service is trying to decrypt.
-// This is often sufficient if you create the SPN in MIT KDC with: /usr/sbin/kadmin.local -q "add_principal HTTP/<fqdn>"
-// When Active Directory is used for the KDC this may need to be the account name you have set the SPN against
-// (setspn.exe -a "HTTP/<fqdn>" <account name>)
-// If you are unsure run:
-//
-// klist -k <service's keytab file>
-//
-// and use the value from the Principal column for the keytab entry the service should use.
-func SPNEGOKRB5Authenticate(f http.Handler, kt keytab.Keytab, ktprinc string, requireHostAddr bool, l *log.Logger) http.Handler {
+func SPNEGOKRB5Authenticate(f http.Handler, c *SPNEGOAuthenticator, l *log.Logger) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		s := strings.SplitN(r.Header.Get(HTTPHeaderAuthRequest), " ", 2)
 		if len(s) != 2 || s[0] != HTTPHeaderAuthResponseValueKey {
@@ -63,38 +44,18 @@ func SPNEGOKRB5Authenticate(f http.Handler, kt keytab.Keytab, ktprinc string, re
 			w.Write([]byte(UnauthorizedMsg))
 			return
 		}
-		b, err := base64.StdEncoding.DecodeString(s[1])
+		c.SPNEGOHeaderValue = s[1]
+		id, authned, err := c.Authenticate()
 		if err != nil {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO error in base64 decoding negotiation header: %v", r.RemoteAddr, err))
-			return
-		}
-		var spnego gssapi.SPNEGO
-		err = spnego.Unmarshal(b)
-		if !spnego.Init {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO negotiation token is not a NegTokenInit: %v", r.RemoteAddr, err))
+			rejectSPNEGO(w, l, fmt.Sprintf("%v - %v", r.RemoteAddr, err))
 			return
 		}
-		if !spnego.NegTokenInit.MechTypes[0].Equal(gssapi.MechTypeOIDKRB5) && !spnego.NegTokenInit.MechTypes[0].Equal(gssapi.MechTypeOIDMSLegacyKRB5) {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO OID of MechToken is not of type KRB5", r.RemoteAddr))
-			return
-		}
-		var mt gssapi.MechToken
-		err = mt.Unmarshal(spnego.NegTokenInit.MechToken)
-		if err != nil {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO error unmarshaling MechToken: %v", r.RemoteAddr, err))
-			return
-		}
-		if !mt.IsAPReq() {
-			rejectSPNEGO(w, l, fmt.Sprintf("%v - MechToken does not contain an AP_REQ - KRB_AP_ERR_MSG_TYPE", r.RemoteAddr))
-			return
-		}
-
-		if ok, creds, err := ValidateAPREQ(mt.APReq, kt, ktprinc, r.RemoteAddr, requireHostAddr); ok {
+		if authned {
 			ctx := r.Context()
-			ctx = context.WithValue(ctx, CTXKeyCredentials, creds)
+			ctx = context.WithValue(ctx, CTXKeyCredentials, id)
 			ctx = context.WithValue(ctx, CTXKeyAuthenticated, true)
 			if l != nil {
-				l.Printf("%v %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, creds.Username, creds.Realm)
+				l.Printf("%v %s@%s - SPNEGO authentication succeeded", r.RemoteAddr, id.UserName(), id.Domain())
 			}
 			spnegoResponseAcceptCompleted(w)
 			f.ServeHTTP(w, r.WithContext(ctx))
@@ -102,6 +63,7 @@ func SPNEGOKRB5Authenticate(f http.Handler, kt keytab.Keytab, ktprinc string, re
 			rejectSPNEGO(w, l, fmt.Sprintf("%v - SPNEGO Kerberos authentication failed: %v", r.RemoteAddr, err))
 			return
 		}
+		return
 	})
 }