[VOL-3255] enhancement to support device metrics
Change-Id: Ic26c2db30a58943a4eef6276ae1cf40350d51702
diff --git a/vendor/gopkg.in/jcmturner/gokrb5.v7/client/session.go b/vendor/gopkg.in/jcmturner/gokrb5.v7/client/session.go
new file mode 100644
index 0000000..ec6c513
--- /dev/null
+++ b/vendor/gopkg.in/jcmturner/gokrb5.v7/client/session.go
@@ -0,0 +1,255 @@
+package client
+
+import (
+ "fmt"
+ "strings"
+ "sync"
+ "time"
+
+ "gopkg.in/jcmturner/gokrb5.v7/iana/nametype"
+ "gopkg.in/jcmturner/gokrb5.v7/krberror"
+ "gopkg.in/jcmturner/gokrb5.v7/messages"
+ "gopkg.in/jcmturner/gokrb5.v7/types"
+)
+
+// sessions hold TGTs and are keyed on the realm name
+type sessions struct {
+ Entries map[string]*session
+ mux sync.RWMutex
+}
+
+// destroy erases all sessions
+func (s *sessions) destroy() {
+ s.mux.Lock()
+ defer s.mux.Unlock()
+ for k, e := range s.Entries {
+ e.destroy()
+ delete(s.Entries, k)
+ }
+}
+
+// update replaces a session with the one provided or adds it as a new one
+func (s *sessions) update(sess *session) {
+ s.mux.Lock()
+ defer s.mux.Unlock()
+ // if a session already exists for this, cancel its auto renew.
+ if i, ok := s.Entries[sess.realm]; ok {
+ if i != sess {
+ // Session in the sessions cache is not the same as one provided.
+ // Cancel the one in the cache and add this one.
+ i.mux.Lock()
+ defer i.mux.Unlock()
+ i.cancel <- true
+ s.Entries[sess.realm] = sess
+ return
+ }
+ }
+ // No session for this realm was found so just add it
+ s.Entries[sess.realm] = sess
+}
+
+// get returns the session for the realm specified
+func (s *sessions) get(realm string) (*session, bool) {
+ s.mux.RLock()
+ defer s.mux.RUnlock()
+ sess, ok := s.Entries[realm]
+ return sess, ok
+}
+
+// session holds the TGT details for a realm
+type session struct {
+ realm string
+ authTime time.Time
+ endTime time.Time
+ renewTill time.Time
+ tgt messages.Ticket
+ sessionKey types.EncryptionKey
+ sessionKeyExpiration time.Time
+ cancel chan bool
+ mux sync.RWMutex
+}
+
+// AddSession adds a session for a realm with a TGT to the client's session cache.
+// A goroutine is started to automatically renew the TGT before expiry.
+func (cl *Client) addSession(tgt messages.Ticket, dep messages.EncKDCRepPart) {
+ if strings.ToLower(tgt.SName.NameString[0]) != "krbtgt" {
+ // Not a TGT
+ return
+ }
+ realm := tgt.SName.NameString[len(tgt.SName.NameString)-1]
+ s := &session{
+ realm: realm,
+ authTime: dep.AuthTime,
+ endTime: dep.EndTime,
+ renewTill: dep.RenewTill,
+ tgt: tgt,
+ sessionKey: dep.Key,
+ sessionKeyExpiration: dep.KeyExpiration,
+ }
+ cl.sessions.update(s)
+ cl.enableAutoSessionRenewal(s)
+ cl.Log("TGT session added for %s (EndTime: %v)", realm, dep.EndTime)
+}
+
+// update overwrites the session details with those from the TGT and decrypted encPart
+func (s *session) update(tgt messages.Ticket, dep messages.EncKDCRepPart) {
+ s.mux.Lock()
+ defer s.mux.Unlock()
+ s.authTime = dep.AuthTime
+ s.endTime = dep.EndTime
+ s.renewTill = dep.RenewTill
+ s.tgt = tgt
+ s.sessionKey = dep.Key
+ s.sessionKeyExpiration = dep.KeyExpiration
+}
+
+// destroy will cancel any auto renewal of the session and set the expiration times to the current time
+func (s *session) destroy() {
+ s.mux.Lock()
+ defer s.mux.Unlock()
+ if s.cancel != nil {
+ s.cancel <- true
+ }
+ s.endTime = time.Now().UTC()
+ s.renewTill = s.endTime
+ s.sessionKeyExpiration = s.endTime
+}
+
+// valid informs if the TGT is still within the valid time window
+func (s *session) valid() bool {
+ s.mux.RLock()
+ defer s.mux.RUnlock()
+ t := time.Now().UTC()
+ if t.Before(s.endTime) && s.authTime.Before(t) {
+ return true
+ }
+ return false
+}
+
+// tgtDetails is a thread safe way to get the session's realm, TGT and session key values
+func (s *session) tgtDetails() (string, messages.Ticket, types.EncryptionKey) {
+ s.mux.RLock()
+ defer s.mux.RUnlock()
+ return s.realm, s.tgt, s.sessionKey
+}
+
+// timeDetails is a thread safe way to get the session's validity time values
+func (s *session) timeDetails() (string, time.Time, time.Time, time.Time, time.Time) {
+ s.mux.RLock()
+ defer s.mux.RUnlock()
+ return s.realm, s.authTime, s.endTime, s.renewTill, s.sessionKeyExpiration
+}
+
+// enableAutoSessionRenewal turns on the automatic renewal for the client's TGT session.
+func (cl *Client) enableAutoSessionRenewal(s *session) {
+ var timer *time.Timer
+ s.mux.Lock()
+ s.cancel = make(chan bool, 1)
+ s.mux.Unlock()
+ go func(s *session) {
+ for {
+ s.mux.RLock()
+ w := (s.endTime.Sub(time.Now().UTC()) * 5) / 6
+ s.mux.RUnlock()
+ if w < 0 {
+ return
+ }
+ timer = time.NewTimer(w)
+ select {
+ case <-timer.C:
+ renewal, err := cl.refreshSession(s)
+ if err != nil {
+ cl.Log("error refreshing session: %v", err)
+ }
+ if !renewal && err == nil {
+ // end this goroutine as there will have been a new login and new auto renewal goroutine created.
+ return
+ }
+ case <-s.cancel:
+ // cancel has been called. Stop the timer and exit.
+ timer.Stop()
+ return
+ }
+ }
+ }(s)
+}
+
+// renewTGT renews the client's TGT session.
+func (cl *Client) renewTGT(s *session) error {
+ realm, tgt, skey := s.tgtDetails()
+ spn := types.PrincipalName{
+ NameType: nametype.KRB_NT_SRV_INST,
+ NameString: []string{"krbtgt", realm},
+ }
+ _, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, true)
+ if err != nil {
+ return krberror.Errorf(err, krberror.KRBMsgError, "error renewing TGT for %s", realm)
+ }
+ s.update(tgsRep.Ticket, tgsRep.DecryptedEncPart)
+ cl.sessions.update(s)
+ cl.Log("TGT session renewed for %s (EndTime: %v)", realm, tgsRep.DecryptedEncPart.EndTime)
+ return nil
+}
+
+// refreshSession updates either through renewal or creating a new login.
+// The boolean indicates if the update was a renewal.
+func (cl *Client) refreshSession(s *session) (bool, error) {
+ s.mux.RLock()
+ realm := s.realm
+ renewTill := s.renewTill
+ s.mux.RUnlock()
+ cl.Log("refreshing TGT session for %s", realm)
+ if time.Now().UTC().Before(renewTill) {
+ err := cl.renewTGT(s)
+ return true, err
+ }
+ err := cl.realmLogin(realm)
+ return false, err
+}
+
+// ensureValidSession makes sure there is a valid session for the realm
+func (cl *Client) ensureValidSession(realm string) error {
+ s, ok := cl.sessions.get(realm)
+ if ok {
+ s.mux.RLock()
+ d := s.endTime.Sub(s.authTime) / 6
+ if s.endTime.Sub(time.Now().UTC()) > d {
+ s.mux.RUnlock()
+ return nil
+ }
+ s.mux.RUnlock()
+ _, err := cl.refreshSession(s)
+ return err
+ }
+ return cl.realmLogin(realm)
+}
+
+// sessionTGTDetails is a thread safe way to get the TGT and session key values for a realm
+func (cl *Client) sessionTGT(realm string) (tgt messages.Ticket, sessionKey types.EncryptionKey, err error) {
+ err = cl.ensureValidSession(realm)
+ if err != nil {
+ return
+ }
+ s, ok := cl.sessions.get(realm)
+ if !ok {
+ err = fmt.Errorf("could not find TGT session for %s", realm)
+ return
+ }
+ _, tgt, sessionKey = s.tgtDetails()
+ return
+}
+
+func (cl *Client) sessionTimes(realm string) (authTime, endTime, renewTime, sessionExp time.Time, err error) {
+ s, ok := cl.sessions.get(realm)
+ if !ok {
+ err = fmt.Errorf("could not find TGT session for %s", realm)
+ return
+ }
+ _, authTime, endTime, renewTime, sessionExp = s.timeDetails()
+ return
+}
+
+// spnRealm resolves the realm name of a service principal name
+func (cl *Client) spnRealm(spn types.PrincipalName) string {
+ return cl.Config.ResolveRealm(spn.NameString[len(spn.NameString)-1])
+}