| 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 |
| } |