135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package client
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/jcmturner/gokrb5/v8/messages"
|
|
"github.com/jcmturner/gokrb5/v8/types"
|
|
)
|
|
|
|
// Cache for service tickets held by the client.
|
|
type Cache struct {
|
|
Entries map[string]CacheEntry
|
|
mux sync.RWMutex
|
|
}
|
|
|
|
// CacheEntry holds details for a cache entry.
|
|
type CacheEntry struct {
|
|
SPN string
|
|
Ticket messages.Ticket `json:"-"`
|
|
AuthTime time.Time
|
|
StartTime time.Time
|
|
EndTime time.Time
|
|
RenewTill time.Time
|
|
SessionKey types.EncryptionKey `json:"-"`
|
|
}
|
|
|
|
// NewCache creates a new client ticket cache instance.
|
|
func NewCache() *Cache {
|
|
return &Cache{
|
|
Entries: map[string]CacheEntry{},
|
|
}
|
|
}
|
|
|
|
// getEntry returns a cache entry that matches the SPN.
|
|
func (c *Cache) getEntry(spn string) (CacheEntry, bool) {
|
|
c.mux.RLock()
|
|
defer c.mux.RUnlock()
|
|
e, ok := (*c).Entries[spn]
|
|
return e, ok
|
|
}
|
|
|
|
// JSON returns information about the cached service tickets in a JSON format.
|
|
func (c *Cache) JSON() (string, error) {
|
|
c.mux.RLock()
|
|
defer c.mux.RUnlock()
|
|
var es []CacheEntry
|
|
keys := make([]string, 0, len(c.Entries))
|
|
for k := range c.Entries {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
es = append(es, c.Entries[k])
|
|
}
|
|
b, err := json.MarshalIndent(&es, "", " ")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(b), nil
|
|
}
|
|
|
|
// addEntry adds a ticket to the cache.
|
|
func (c *Cache) addEntry(tkt messages.Ticket, authTime, startTime, endTime, renewTill time.Time, sessionKey types.EncryptionKey) CacheEntry {
|
|
spn := tkt.SName.PrincipalNameString()
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
(*c).Entries[spn] = CacheEntry{
|
|
SPN: spn,
|
|
Ticket: tkt,
|
|
AuthTime: authTime,
|
|
StartTime: startTime,
|
|
EndTime: endTime,
|
|
RenewTill: renewTill,
|
|
SessionKey: sessionKey,
|
|
}
|
|
return c.Entries[spn]
|
|
}
|
|
|
|
// clear deletes all the cache entries
|
|
func (c *Cache) clear() {
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
for k := range c.Entries {
|
|
delete(c.Entries, k)
|
|
}
|
|
}
|
|
|
|
// RemoveEntry removes the cache entry for the defined SPN.
|
|
func (c *Cache) RemoveEntry(spn string) {
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
delete(c.Entries, spn)
|
|
}
|
|
|
|
// GetCachedTicket returns a ticket from the cache for the SPN.
|
|
// Only a ticket that is currently valid will be returned.
|
|
func (cl *Client) GetCachedTicket(spn string) (messages.Ticket, types.EncryptionKey, bool) {
|
|
if e, ok := cl.cache.getEntry(spn); ok {
|
|
//If within time window of ticket return it
|
|
if time.Now().UTC().After(e.StartTime) && time.Now().UTC().Before(e.EndTime) {
|
|
cl.Log("ticket received from cache for %s", spn)
|
|
return e.Ticket, e.SessionKey, true
|
|
} else if time.Now().UTC().Before(e.RenewTill) {
|
|
e, err := cl.renewTicket(e)
|
|
if err != nil {
|
|
return e.Ticket, e.SessionKey, false
|
|
}
|
|
return e.Ticket, e.SessionKey, true
|
|
}
|
|
}
|
|
var tkt messages.Ticket
|
|
var key types.EncryptionKey
|
|
return tkt, key, false
|
|
}
|
|
|
|
// renewTicket renews a cache entry ticket.
|
|
// To renew from outside the client package use GetCachedTicket
|
|
func (cl *Client) renewTicket(e CacheEntry) (CacheEntry, error) {
|
|
spn := e.Ticket.SName
|
|
_, _, err := cl.TGSREQGenerateAndExchange(spn, e.Ticket.Realm, e.Ticket, e.SessionKey, true)
|
|
if err != nil {
|
|
return e, err
|
|
}
|
|
e, ok := cl.cache.getEntry(e.Ticket.SName.PrincipalNameString())
|
|
if !ok {
|
|
return e, errors.New("ticket was not added to cache")
|
|
}
|
|
cl.Log("ticket renewed for %s (EndTime: %v)", spn.PrincipalNameString(), e.EndTime)
|
|
return e, nil
|
|
}
|