1## Version 8 Usage
2
3### Configuration
4The gokrb5 libraries use the same krb5.conf configuration file format as MIT Kerberos,
5described [here](https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html).
6Config instances can be created by loading from a file path or by passing a string, io.Reader or bufio.Scanner to the
7relevant method:
8```go
9import "github.com/jcmturner/gokrb5/v8/config"
10cfg, err := config.Load("/path/to/config/file")
11cfg, err := config.NewConfigFromString(krb5Str) //String must have appropriate newline separations
12cfg, err := config.NewConfigFromReader(reader)
13cfg, err := config.NewConfigFromScanner(scanner)
14```
15### Keytab files
16Standard keytab files can be read from a file or from a slice of bytes:
17```go
18import 	"github.com/jcmturner/gokrb5/v8/keytab"
19ktFromFile, err := keytab.Load("/path/to/file.keytab")
20ktFromBytes, err := keytab.Parse(b)
21
22```
23
24---
25
26### Kerberos Client
27**Create** a client instance with either a password or a keytab.
28A configuration must also be passed. Additionally optional additional settings can be provided.
29```go
30import 	"github.com/jcmturner/gokrb5/v8/client"
31cl := client.NewWithPassword("username", "REALM.COM", "password", cfg)
32cl := client.NewWithKeytab("username", "REALM.COM", kt, cfg)
33```
34Optional settings are provided using the functions defined in the ``client/settings.go`` source file.
35
36**Login**:
37```go
38err := cl.Login()
39```
40Kerberos Ticket Granting Tickets (TGT) will be automatically renewed unless the client was created from a CCache.
41
42A client can be **destroyed** with the following method:
43```go
44cl.Destroy()
45```
46
47#### Active Directory KDC and FAST negotiation
48Active Directory does not commonly support FAST negotiation so you will need to disable this on the client.
49If this is the case you will see this error:
50```KDC did not respond appropriately to FAST negotiation```
51To resolve this disable PA-FX-Fast on the client before performing Login().
52This is done with one of the optional client settings as shown below:
53```go
54cl := client.NewWithPassword("username", "REALM.COM", "password", cfg, client.DisablePAFXFAST(true))
55```
56
57#### Authenticate to a Service
58
59##### HTTP SPNEGO
60Create the HTTP request object and then create an SPNEGO client and use this to process the request with methods that
61are the same as on a HTTP client.
62If nil is passed as the HTTP client when creating the SPNEGO client the http.DefaultClient is used.
63When creating the SPNEGO client pass the Service Principal Name (SPN) or auto generate the SPN from the request
64object by passing a null string "".
65```go
66r, _ := http.NewRequest("GET", "http://host.test.gokrb5/index.html", nil)
67spnegoCl := spnego.NewClient(cl, nil, "")
68resp, err := spnegoCl.Do(r)
69```
70
71##### Generic Kerberos Client
72To authenticate to a service a client will need to request a service ticket for a Service Principal Name (SPN) and form
73into an AP_REQ message along with an authenticator encrypted with the session key that was delivered from the KDC along
74with the service ticket.
75
76The steps below outline how to do this.
77* Get the service ticket and session key for the service the client is authenticating to.
78The following method will use the client's cache either returning a valid cached ticket, renewing a cached ticket with
79the KDC or requesting a new ticket from the KDC.
80Therefore the GetServiceTicket method can be continually used for the most efficient interaction with the KDC.
81```go
82tkt, key, err := cl.GetServiceTicket("HTTP/host.test.gokrb5")
83```
84
85The steps after this will be specific to the application protocol but it will likely involve a client/server
86Authentication Protocol exchange (AP exchange).
87This will involve these steps:
88
89* Generate a new Authenticator and generate a sequence number and subkey:
90```go
91auth, _ := types.NewAuthenticator(cl.Credentials.Realm, cl.Credentials.CName)
92etype, _ := crypto.GetEtype(key.KeyType)
93auth.GenerateSeqNumberAndSubKey(key.KeyType, etype.GetKeyByteSize())
94```
95* Set the checksum on the authenticator
96The checksum is an application specific value. Set as follows:
97```go
98auth.Cksum = types.Checksum{
99		CksumType: checksumIDint,
100		Checksum:  checksumBytesSlice,
101	}
102```
103* Create the AP_REQ:
104```go
105APReq, err := messages.NewAPReq(tkt, key, auth)
106```
107
108Now send the AP_REQ to the service. How this is done will be specific to the application use case.
109
110#### Changing a Client Password
111This feature uses the Microsoft Kerberos Password Change protocol (RFC 3244).
112This is implemented in Microsoft Active Directory and in MIT krb5kdc as of version 1.7.
113Typically the kpasswd server listens on port 464.
114
115Below is example code for how to use this feature:
116```go
117cfg, err := config.Load("/path/to/config/file")
118if err != nil {
119	panic(err.Error())
120}
121kt, err := keytab.Load("/path/to/file.keytab")
122if err != nil {
123	panic(err.Error())
124}
125cl := client.NewWithKeytab("username", "REALM.COM", kt)
126cl.WithConfig(cfg)
127
128ok, err := cl.ChangePasswd("newpassword")
129if err != nil {
130	panic(err.Error())
131}
132if !ok {
133	panic("failed to change password")
134}
135```
136
137The client kerberos config (krb5.conf) will need to have either the kpassd_server or admin_server defined in the
138relevant [realms] section. For example:
139```
140REALM.COM = {
141  kdc = 127.0.0.1:88
142  kpasswd_server = 127.0.0.1:464
143  default_domain = realm.com
144 }
145```
146See https://web.mit.edu/kerberos/krb5-latest/doc/admin/conf_files/krb5_conf.html#realms for more information.
147
148#### Client Diagnostics
149In the event of issues the configuration of a client can be investigated with its ``Diagnostics`` method.
150This will check that the required enctypes defined in the client's krb5 config are available in its keytab.
151It will also check that KDCs can be resolved for the client's REALM.
152The error returned will contain details of any failed checks.
153The configuration details of the client will be written to the ``io.Writer`` provided.
154
155---
156
157### Kerberised Service
158
159#### SPNEGO/Kerberos HTTP Service
160A HTTP handler wrapper can be used to implement Kerberos SPNEGO authentication for web services.
161To configure the wrapper the keytab for the SPN and a Logger are required:
162```go
163kt, err := keytab.Load("/path/to/file.keytab")
164l := log.New(os.Stderr, "GOKRB5 Service: ", log.Ldate|log.Ltime|log.Lshortfile)
165```
166Create a handler function of the application's handling method (apphandler in the example below):
167```go
168h := http.HandlerFunc(apphandler)
169```
170Configure the HTTP handler:
171```go
172http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l)))
173```
174The handler to be wrapped and the keytab are required arguments.
175Additional optional settings can be provided, such as the logger shown above.
176
177Another example of optional settings may be that when using Active Directory where the SPN is mapped to a user account
178the keytab may contain an entry for this user account. In this case this should be specified as below with the
179``KeytabPrincipal``:
180```go
181http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l), service.KeytabPrincipal(pn)))
182```
183
184##### Session Management
185For efficiency reasons it is not desirable to authenticate on every call to a web service.
186Therefore most authenticated web applications implement some form of session with the user.
187Such sessions can be supported by passing a "session manager" into the ``SPNEGOKRB5Authenticate`` wrapper handler.
188In order to not demand a specific session manager solution, the session manager must implement a simple interface:
189```go
190type SessionMgr interface {
191	New(w http.ResponseWriter, r *http.Request, k string, v []byte) error
192	Get(r *http.Request, k string) ([]byte, error)
193}
194```
195- New - creates a new session for the request and adds a piece of data (key/value pair) to the session
196- Get - extract from an existing session the value held within it under the key provided.
197This should return nil bytes or an error if there is no existing session.
198
199The session manager (sm) that implements this interface should then be passed to the ``SPNEGOKRB5Authenticate`` wrapper
200handler as below:
201```go
202http.Handler("/", spnego.SPNEGOKRB5Authenticate(h, &kt, service.Logger(l), service.SessionManager(sm)))
203```
204
205The ``httpServer.go`` source file in the examples directory shows how this can be used with the popular gorilla web toolkit.
206
207##### Validating Users and Accessing Users' Details
208If authentication succeeds then the request's context will have a credentials objected added to it.
209This object implements the ``github.com/jcmturner/goidentity/identity`` interface.
210If Microsoft Active Directory is used as the KDC then additional ADCredentials are available in the
211``credentials.Attributes`` map under the key ``credentials.AttributeKeyADCredentials``.
212For example the SIDs of the users group membership are available and can be used by your application for authorization.
213
214Checking and access the credentials within your application:
215```go
216// Get a goidentity credentials object from the request's context
217creds := goidentity.FromHTTPRequestContext(r)
218// Check if it indicates it is authenticated
219if creds != nil && creds.Authenticated() {
220    // Check for Active Directory attributes
221	if ADCredsJSON, ok := creds.Attributes()[credentials.AttributeKeyADCredentials]; ok {
222		ADCreds := new(credentials.ADCredentials)
223        // Unmarshal the AD attributes
224		err := json.Unmarshal([]byte(ADCredsJSON), ADCreds)
225		if err == nil {
226			// Now access the fields of the ADCredentials struct. For example: ADCreds.GroupMembershipSIDs
227		}
228	}
229} else {
230    // Not authenticated user
231	w.WriteHeader(http.StatusUnauthorized)
232	fmt.Fprint(w, "Authentication failed")
233}
234```
235
236#### Generic Kerberised Service - Validating Client Details
237To validate the AP_REQ sent by the client on the service side call this method:
238```go
239import 	"github.com/jcmturner/gokrb5/v8/service"
240s := service.NewSettings(&kt) // kt is a keytab and optional settings can also be provided.
241if ok, creds, err := service.VerifyAPREQ(&APReq, s); ok {
242        // Perform application specific actions
243        // creds object has details about the client identity
244}
245```
246