Add NETCONF notification for ONU activation and Kafka client to receive events, update dependencies
Change-Id: I5f768fa8077ef7c64e00a534744ca47492344935
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/ASExchange.go b/vendor/github.com/jcmturner/gokrb5/v8/client/ASExchange.go
new file mode 100644
index 0000000..5becccc
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/ASExchange.go
@@ -0,0 +1,182 @@
+package client
+
+import (
+ "github.com/jcmturner/gokrb5/v8/crypto"
+ "github.com/jcmturner/gokrb5/v8/crypto/etype"
+ "github.com/jcmturner/gokrb5/v8/iana/errorcode"
+ "github.com/jcmturner/gokrb5/v8/iana/keyusage"
+ "github.com/jcmturner/gokrb5/v8/iana/patype"
+ "github.com/jcmturner/gokrb5/v8/krberror"
+ "github.com/jcmturner/gokrb5/v8/messages"
+ "github.com/jcmturner/gokrb5/v8/types"
+)
+
+// ASExchange performs an AS exchange for the client to retrieve a TGT.
+func (cl *Client) ASExchange(realm string, ASReq messages.ASReq, referral int) (messages.ASRep, error) {
+ if ok, err := cl.IsConfigured(); !ok {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.ConfigError, "AS Exchange cannot be performed")
+ }
+
+ // Set PAData if required
+ err := setPAData(cl, nil, &ASReq)
+ if err != nil {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: issue with setting PAData on AS_REQ")
+ }
+
+ b, err := ASReq.Marshal()
+ if err != nil {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ")
+ }
+ var ASRep messages.ASRep
+
+ rb, err := cl.sendToKDC(b, realm)
+ if err != nil {
+ if e, ok := err.(messages.KRBError); ok {
+ switch e.ErrorCode {
+ case errorcode.KDC_ERR_PREAUTH_REQUIRED, errorcode.KDC_ERR_PREAUTH_FAILED:
+ // From now on assume this client will need to do this pre-auth and set the PAData
+ cl.settings.assumePreAuthentication = true
+ err = setPAData(cl, &e, &ASReq)
+ if err != nil {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: failed setting AS_REQ PAData for pre-authentication required")
+ }
+ b, err := ASReq.Marshal()
+ if err != nil {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed marshaling AS_REQ with PAData")
+ }
+ rb, err = cl.sendToKDC(b, realm)
+ if err != nil {
+ if _, ok := err.(messages.KRBError); ok {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
+ }
+ return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
+ }
+ case errorcode.KDC_ERR_WRONG_REALM:
+ // Client referral https://tools.ietf.org/html/rfc6806.html#section-7
+ if referral > 5 {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "maximum number of client referrals exceeded")
+ }
+ referral++
+ return cl.ASExchange(e.CRealm, ASReq, referral)
+ default:
+ return messages.ASRep{}, krberror.Errorf(err, krberror.KDCError, "AS Exchange Error: kerberos error response from KDC")
+ }
+ } else {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.NetworkingError, "AS Exchange Error: failed sending AS_REQ to KDC")
+ }
+ }
+ err = ASRep.Unmarshal(rb)
+ if err != nil {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.EncodingError, "AS Exchange Error: failed to process the AS_REP")
+ }
+ if ok, err := ASRep.Verify(cl.Config, cl.Credentials, ASReq); !ok {
+ return messages.ASRep{}, krberror.Errorf(err, krberror.KRBMsgError, "AS Exchange Error: AS_REP is not valid or client password/keytab incorrect")
+ }
+ return ASRep, nil
+}
+
+// setPAData adds pre-authentication data to the AS_REQ.
+func setPAData(cl *Client, krberr *messages.KRBError, ASReq *messages.ASReq) error {
+ if !cl.settings.DisablePAFXFAST() {
+ pa := types.PAData{PADataType: patype.PA_REQ_ENC_PA_REP}
+ ASReq.PAData = append(ASReq.PAData, pa)
+ }
+ if cl.settings.AssumePreAuthentication() {
+ // Identify the etype to use to encrypt the PA Data
+ var et etype.EType
+ var err error
+ var key types.EncryptionKey
+ var kvno int
+ if krberr == nil {
+ // This is not in response to an error from the KDC. It is preemptive or renewal
+ // There is no KRB Error that tells us the etype to use
+ etn := cl.settings.preAuthEType // Use the etype that may have previously been negotiated
+ if etn == 0 {
+ etn = int32(cl.Config.LibDefaults.PreferredPreauthTypes[0]) // Resort to config
+ }
+ et, err = crypto.GetEtype(etn)
+ if err != nil {
+ return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
+ }
+ key, kvno, err = cl.Key(et, 0, nil)
+ if err != nil {
+ return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
+ }
+ } else {
+ // Get the etype to use from the PA data in the KRBError e-data
+ et, err = preAuthEType(krberr)
+ if err != nil {
+ return krberror.Errorf(err, krberror.EncryptingError, "error getting etype for pre-auth encryption")
+ }
+ cl.settings.preAuthEType = et.GetETypeID() // Set the etype that has been defined for potential future use
+ key, kvno, err = cl.Key(et, 0, krberr)
+ if err != nil {
+ return krberror.Errorf(err, krberror.EncryptingError, "error getting key from credentials")
+ }
+ }
+ // Generate the PA data
+ paTSb, err := types.GetPAEncTSEncAsnMarshalled()
+ if err != nil {
+ return krberror.Errorf(err, krberror.KRBMsgError, "error creating PAEncTSEnc for Pre-Authentication")
+ }
+ paEncTS, err := crypto.GetEncryptedData(paTSb, key, keyusage.AS_REQ_PA_ENC_TIMESTAMP, kvno)
+ if err != nil {
+ return krberror.Errorf(err, krberror.EncryptingError, "error encrypting pre-authentication timestamp")
+ }
+ pb, err := paEncTS.Marshal()
+ if err != nil {
+ return krberror.Errorf(err, krberror.EncodingError, "error marshaling the PAEncTSEnc encrypted data")
+ }
+ pa := types.PAData{
+ PADataType: patype.PA_ENC_TIMESTAMP,
+ PADataValue: pb,
+ }
+ // Look for and delete any exiting patype.PA_ENC_TIMESTAMP
+ for i, pa := range ASReq.PAData {
+ if pa.PADataType == patype.PA_ENC_TIMESTAMP {
+ ASReq.PAData[i] = ASReq.PAData[len(ASReq.PAData)-1]
+ ASReq.PAData = ASReq.PAData[:len(ASReq.PAData)-1]
+ }
+ }
+ ASReq.PAData = append(ASReq.PAData, pa)
+ }
+ return nil
+}
+
+// preAuthEType establishes what encryption type to use for pre-authentication from the KRBError returned from the KDC.
+func preAuthEType(krberr *messages.KRBError) (etype etype.EType, err error) {
+ //RFC 4120 5.2.7.5 covers the preference order of ETYPE-INFO2 and ETYPE-INFO.
+ var etypeID int32
+ var pas types.PADataSequence
+ e := pas.Unmarshal(krberr.EData)
+ if e != nil {
+ err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling KRBError data")
+ return
+ }
+Loop:
+ for _, pa := range pas {
+ switch pa.PADataType {
+ case patype.PA_ETYPE_INFO2:
+ info, e := pa.GetETypeInfo2()
+ if e != nil {
+ err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO2 data")
+ return
+ }
+ etypeID = info[0].EType
+ break Loop
+ case patype.PA_ETYPE_INFO:
+ info, e := pa.GetETypeInfo()
+ if e != nil {
+ err = krberror.Errorf(e, krberror.EncodingError, "error unmashalling ETYPE-INFO data")
+ return
+ }
+ etypeID = info[0].EType
+ }
+ }
+ etype, e = crypto.GetEtype(etypeID)
+ if e != nil {
+ err = krberror.Errorf(e, krberror.EncryptingError, "error creating etype")
+ return
+ }
+ return etype, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/TGSExchange.go b/vendor/github.com/jcmturner/gokrb5/v8/client/TGSExchange.go
new file mode 100644
index 0000000..e4571ce
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/TGSExchange.go
@@ -0,0 +1,103 @@
+package client
+
+import (
+ "github.com/jcmturner/gokrb5/v8/iana/flags"
+ "github.com/jcmturner/gokrb5/v8/iana/nametype"
+ "github.com/jcmturner/gokrb5/v8/krberror"
+ "github.com/jcmturner/gokrb5/v8/messages"
+ "github.com/jcmturner/gokrb5/v8/types"
+)
+
+// TGSREQGenerateAndExchange generates the TGS_REQ and performs a TGS exchange to retrieve a ticket to the specified SPN.
+func (cl *Client) TGSREQGenerateAndExchange(spn types.PrincipalName, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, renewal bool) (tgsReq messages.TGSReq, tgsRep messages.TGSRep, err error) {
+ tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, spn, renewal)
+ if err != nil {
+ return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: failed to generate a new TGS_REQ")
+ }
+ return cl.TGSExchange(tgsReq, kdcRealm, tgsRep.Ticket, sessionKey, 0)
+}
+
+// TGSExchange exchanges the provided TGS_REQ with the KDC to retrieve a TGS_REP.
+// Referrals are automatically handled.
+// The client's cache is updated with the ticket received.
+func (cl *Client) TGSExchange(tgsReq messages.TGSReq, kdcRealm string, tgt messages.Ticket, sessionKey types.EncryptionKey, referral int) (messages.TGSReq, messages.TGSRep, error) {
+ var tgsRep messages.TGSRep
+ b, err := tgsReq.Marshal()
+ if err != nil {
+ return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to marshal TGS_REQ")
+ }
+ r, err := cl.sendToKDC(b, kdcRealm)
+ if err != nil {
+ if _, ok := err.(messages.KRBError); ok {
+ return tgsReq, tgsRep, krberror.Errorf(err, krberror.KDCError, "TGS Exchange Error: kerberos error response from KDC when requesting for %s", tgsReq.ReqBody.SName.PrincipalNameString())
+ }
+ return tgsReq, tgsRep, krberror.Errorf(err, krberror.NetworkingError, "TGS Exchange Error: issue sending TGS_REQ to KDC")
+ }
+ err = tgsRep.Unmarshal(r)
+ if err != nil {
+ return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
+ }
+ err = tgsRep.DecryptEncPart(sessionKey)
+ if err != nil {
+ return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: failed to process the TGS_REP")
+ }
+ if ok, err := tgsRep.Verify(cl.Config, tgsReq); !ok {
+ return tgsReq, tgsRep, krberror.Errorf(err, krberror.EncodingError, "TGS Exchange Error: TGS_REP is not valid")
+ }
+
+ if tgsRep.Ticket.SName.NameString[0] == "krbtgt" && !tgsRep.Ticket.SName.Equal(tgsReq.ReqBody.SName) {
+ if referral > 5 {
+ return tgsReq, tgsRep, krberror.Errorf(err, krberror.KRBMsgError, "TGS Exchange Error: maximum number of referrals exceeded")
+ }
+ // Server referral https://tools.ietf.org/html/rfc6806.html#section-8
+ // The TGS Rep contains a TGT for another domain as the service resides in that domain.
+ cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
+ realm := tgsRep.Ticket.SName.NameString[len(tgsRep.Ticket.SName.NameString)-1]
+ referral++
+ if types.IsFlagSet(&tgsReq.ReqBody.KDCOptions, flags.EncTktInSkey) && len(tgsReq.ReqBody.AdditionalTickets) > 0 {
+ tgsReq, err = messages.NewUser2UserTGSReq(cl.Credentials.CName(), kdcRealm, cl.Config, tgt, sessionKey, tgsReq.ReqBody.SName, tgsReq.Renewal, tgsReq.ReqBody.AdditionalTickets[0])
+ if err != nil {
+ return tgsReq, tgsRep, err
+ }
+ }
+ tgsReq, err = messages.NewTGSReq(cl.Credentials.CName(), realm, cl.Config, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, tgsReq.ReqBody.SName, tgsReq.Renewal)
+ if err != nil {
+ return tgsReq, tgsRep, err
+ }
+ return cl.TGSExchange(tgsReq, realm, tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, referral)
+ }
+ cl.cache.addEntry(
+ tgsRep.Ticket,
+ tgsRep.DecryptedEncPart.AuthTime,
+ tgsRep.DecryptedEncPart.StartTime,
+ tgsRep.DecryptedEncPart.EndTime,
+ tgsRep.DecryptedEncPart.RenewTill,
+ tgsRep.DecryptedEncPart.Key,
+ )
+ cl.Log("ticket added to cache for %s (EndTime: %v)", tgsRep.Ticket.SName.PrincipalNameString(), tgsRep.DecryptedEncPart.EndTime)
+ return tgsReq, tgsRep, err
+}
+
+// GetServiceTicket makes a request to get a service ticket for the SPN specified
+// SPN format: <SERVICE>/<FQDN> Eg. HTTP/www.example.com
+// The ticket will be added to the client's ticket cache
+func (cl *Client) GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) {
+ var tkt messages.Ticket
+ var skey types.EncryptionKey
+ if tkt, skey, ok := cl.GetCachedTicket(spn); ok {
+ // Already a valid ticket in the cache
+ return tkt, skey, nil
+ }
+ princ := types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, spn)
+ realm := cl.Config.ResolveRealm(princ.NameString[len(princ.NameString)-1])
+
+ tgt, skey, err := cl.sessionTGT(realm)
+ if err != nil {
+ return tkt, skey, err
+ }
+ _, tgsRep, err := cl.TGSREQGenerateAndExchange(princ, realm, tgt, skey, false)
+ if err != nil {
+ return tkt, skey, err
+ }
+ return tgsRep.Ticket, tgsRep.DecryptedEncPart.Key, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/cache.go b/vendor/github.com/jcmturner/gokrb5/v8/client/cache.go
new file mode 100644
index 0000000..552e73e
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/cache.go
@@ -0,0 +1,134 @@
+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
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/client.go b/vendor/github.com/jcmturner/gokrb5/v8/client/client.go
new file mode 100644
index 0000000..074e3f1
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/client.go
@@ -0,0 +1,329 @@
+// Package client provides a client library and methods for Kerberos 5 authentication.
+package client
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+ "time"
+
+ "github.com/jcmturner/gokrb5/v8/config"
+ "github.com/jcmturner/gokrb5/v8/credentials"
+ "github.com/jcmturner/gokrb5/v8/crypto"
+ "github.com/jcmturner/gokrb5/v8/crypto/etype"
+ "github.com/jcmturner/gokrb5/v8/iana/errorcode"
+ "github.com/jcmturner/gokrb5/v8/iana/nametype"
+ "github.com/jcmturner/gokrb5/v8/keytab"
+ "github.com/jcmturner/gokrb5/v8/krberror"
+ "github.com/jcmturner/gokrb5/v8/messages"
+ "github.com/jcmturner/gokrb5/v8/types"
+)
+
+// Client side configuration and state.
+type Client struct {
+ Credentials *credentials.Credentials
+ Config *config.Config
+ settings *Settings
+ sessions *sessions
+ cache *Cache
+}
+
+// NewWithPassword creates a new client from a password credential.
+// Set the realm to empty string to use the default realm from config.
+func NewWithPassword(username, realm, password string, krb5conf *config.Config, settings ...func(*Settings)) *Client {
+ creds := credentials.New(username, realm)
+ return &Client{
+ Credentials: creds.WithPassword(password),
+ Config: krb5conf,
+ settings: NewSettings(settings...),
+ sessions: &sessions{
+ Entries: make(map[string]*session),
+ },
+ cache: NewCache(),
+ }
+}
+
+// NewWithKeytab creates a new client from a keytab credential.
+func NewWithKeytab(username, realm string, kt *keytab.Keytab, krb5conf *config.Config, settings ...func(*Settings)) *Client {
+ creds := credentials.New(username, realm)
+ return &Client{
+ Credentials: creds.WithKeytab(kt),
+ Config: krb5conf,
+ settings: NewSettings(settings...),
+ sessions: &sessions{
+ Entries: make(map[string]*session),
+ },
+ cache: NewCache(),
+ }
+}
+
+// NewFromCCache create a client from a populated client cache.
+//
+// WARNING: A client created from CCache does not automatically renew TGTs and a failure will occur after the TGT expires.
+func NewFromCCache(c *credentials.CCache, krb5conf *config.Config, settings ...func(*Settings)) (*Client, error) {
+ cl := &Client{
+ Credentials: c.GetClientCredentials(),
+ Config: krb5conf,
+ settings: NewSettings(settings...),
+ sessions: &sessions{
+ Entries: make(map[string]*session),
+ },
+ cache: NewCache(),
+ }
+ spn := types.PrincipalName{
+ NameType: nametype.KRB_NT_SRV_INST,
+ NameString: []string{"krbtgt", c.DefaultPrincipal.Realm},
+ }
+ cred, ok := c.GetEntry(spn)
+ if !ok {
+ return cl, errors.New("TGT not found in CCache")
+ }
+ var tgt messages.Ticket
+ err := tgt.Unmarshal(cred.Ticket)
+ if err != nil {
+ return cl, fmt.Errorf("TGT bytes in cache are not valid: %v", err)
+ }
+ cl.sessions.Entries[c.DefaultPrincipal.Realm] = &session{
+ realm: c.DefaultPrincipal.Realm,
+ authTime: cred.AuthTime,
+ endTime: cred.EndTime,
+ renewTill: cred.RenewTill,
+ tgt: tgt,
+ sessionKey: cred.Key,
+ }
+ for _, cred := range c.GetEntries() {
+ var tkt messages.Ticket
+ err = tkt.Unmarshal(cred.Ticket)
+ if err != nil {
+ return cl, fmt.Errorf("cache entry ticket bytes are not valid: %v", err)
+ }
+ cl.cache.addEntry(
+ tkt,
+ cred.AuthTime,
+ cred.StartTime,
+ cred.EndTime,
+ cred.RenewTill,
+ cred.Key,
+ )
+ }
+ return cl, nil
+}
+
+// Key returns the client's encryption key for the specified encryption type and its kvno (kvno of zero will find latest).
+// The key can be retrieved either from the keytab or generated from the client's password.
+// If the client has both a keytab and a password defined the keytab is favoured as the source for the key
+// A KRBError can be passed in the event the KDC returns one of type KDC_ERR_PREAUTH_REQUIRED and is required to derive
+// the key for pre-authentication from the client's password. If a KRBError is not available, pass nil to this argument.
+func (cl *Client) Key(etype etype.EType, kvno int, krberr *messages.KRBError) (types.EncryptionKey, int, error) {
+ if cl.Credentials.HasKeytab() && etype != nil {
+ return cl.Credentials.Keytab().GetEncryptionKey(cl.Credentials.CName(), cl.Credentials.Domain(), kvno, etype.GetETypeID())
+ } else if cl.Credentials.HasPassword() {
+ if krberr != nil && krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
+ var pas types.PADataSequence
+ err := pas.Unmarshal(krberr.EData)
+ if err != nil {
+ return types.EncryptionKey{}, 0, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err)
+ }
+ key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)
+ return key, 0, err
+ }
+ key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID(), types.PADataSequence{})
+ return key, 0, err
+ }
+ return types.EncryptionKey{}, 0, errors.New("credential has neither keytab or password to generate key")
+}
+
+// IsConfigured indicates if the client has the values required set.
+func (cl *Client) IsConfigured() (bool, error) {
+ if cl.Credentials.UserName() == "" {
+ return false, errors.New("client does not have a username")
+ }
+ if cl.Credentials.Domain() == "" {
+ return false, errors.New("client does not have a define realm")
+ }
+ // Client needs to have either a password, keytab or a session already (later when loading from CCache)
+ if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
+ authTime, _, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+ if err != nil || authTime.IsZero() {
+ return false, errors.New("client has neither a keytab nor a password set and no session")
+ }
+ }
+ if !cl.Config.LibDefaults.DNSLookupKDC {
+ for _, r := range cl.Config.Realms {
+ if r.Realm == cl.Credentials.Domain() {
+ if len(r.KDC) > 0 {
+ return true, nil
+ }
+ return false, errors.New("client krb5 config does not have any defined KDCs for the default realm")
+ }
+ }
+ }
+ return true, nil
+}
+
+// Login the client with the KDC via an AS exchange.
+func (cl *Client) Login() error {
+ if ok, err := cl.IsConfigured(); !ok {
+ return err
+ }
+ if !cl.Credentials.HasPassword() && !cl.Credentials.HasKeytab() {
+ _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+ if err != nil {
+ return krberror.Errorf(err, krberror.KRBMsgError, "no user credentials available and error getting any existing session")
+ }
+ if time.Now().UTC().After(endTime) {
+ return krberror.New(krberror.KRBMsgError, "cannot login, no user credentials available and no valid existing session")
+ }
+ // no credentials but there is a session with tgt already
+ return nil
+ }
+ ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
+ if err != nil {
+ return krberror.Errorf(err, krberror.KRBMsgError, "error generating new AS_REQ")
+ }
+ ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
+ if err != nil {
+ return err
+ }
+ cl.addSession(ASRep.Ticket, ASRep.DecryptedEncPart)
+ return nil
+}
+
+// AffirmLogin will only perform an AS exchange with the KDC if the client does not already have a TGT.
+func (cl *Client) AffirmLogin() error {
+ _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+ if err != nil || time.Now().UTC().After(endTime) {
+ err := cl.Login()
+ if err != nil {
+ return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
+ }
+ }
+ return nil
+}
+
+// realmLogin obtains or renews a TGT and establishes a session for the realm specified.
+func (cl *Client) realmLogin(realm string) error {
+ if realm == cl.Credentials.Domain() {
+ return cl.Login()
+ }
+ _, endTime, _, _, err := cl.sessionTimes(cl.Credentials.Domain())
+ if err != nil || time.Now().UTC().After(endTime) {
+ err := cl.Login()
+ if err != nil {
+ return fmt.Errorf("could not get valid TGT for client's realm: %v", err)
+ }
+ }
+ tgt, skey, err := cl.sessionTGT(cl.Credentials.Domain())
+ if err != nil {
+ return err
+ }
+
+ spn := types.PrincipalName{
+ NameType: nametype.KRB_NT_SRV_INST,
+ NameString: []string{"krbtgt", realm},
+ }
+
+ _, tgsRep, err := cl.TGSREQGenerateAndExchange(spn, cl.Credentials.Domain(), tgt, skey, false)
+ if err != nil {
+ return err
+ }
+ cl.addSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)
+
+ return nil
+}
+
+// Destroy stops the auto-renewal of all sessions and removes the sessions and cache entries from the client.
+func (cl *Client) Destroy() {
+ creds := credentials.New("", "")
+ cl.sessions.destroy()
+ cl.cache.clear()
+ cl.Credentials = creds
+ cl.Log("client destroyed")
+}
+
+// Diagnostics runs a set of checks that the client is properly configured and writes details to the io.Writer provided.
+func (cl *Client) Diagnostics(w io.Writer) error {
+ cl.Print(w)
+ var errs []string
+ if cl.Credentials.HasKeytab() {
+ var loginRealmEncTypes []int32
+ for _, e := range cl.Credentials.Keytab().Entries {
+ if e.Principal.Realm == cl.Credentials.Realm() {
+ loginRealmEncTypes = append(loginRealmEncTypes, e.Key.KeyType)
+ }
+ }
+ for _, et := range cl.Config.LibDefaults.DefaultTktEnctypeIDs {
+ var etInKt bool
+ for _, val := range loginRealmEncTypes {
+ if val == et {
+ etInKt = true
+ break
+ }
+ }
+ if !etInKt {
+ errs = append(errs, fmt.Sprintf("default_tkt_enctypes specifies %d but this enctype is not available in the client's keytab", et))
+ }
+ }
+ for _, et := range cl.Config.LibDefaults.PreferredPreauthTypes {
+ var etInKt bool
+ for _, val := range loginRealmEncTypes {
+ if int(val) == et {
+ etInKt = true
+ break
+ }
+ }
+ if !etInKt {
+ errs = append(errs, fmt.Sprintf("preferred_preauth_types specifies %d but this enctype is not available in the client's keytab", et))
+ }
+ }
+ }
+ udpCnt, udpKDC, err := cl.Config.GetKDCs(cl.Credentials.Realm(), false)
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error when resolving KDCs for UDP communication: %v", err))
+ }
+ if udpCnt < 1 {
+ errs = append(errs, "no KDCs resolved for communication via UDP.")
+ } else {
+ b, _ := json.MarshalIndent(&udpKDC, "", " ")
+ fmt.Fprintf(w, "UDP KDCs: %s\n", string(b))
+ }
+ tcpCnt, tcpKDC, err := cl.Config.GetKDCs(cl.Credentials.Realm(), false)
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error when resolving KDCs for TCP communication: %v", err))
+ }
+ if tcpCnt < 1 {
+ errs = append(errs, "no KDCs resolved for communication via TCP.")
+ } else {
+ b, _ := json.MarshalIndent(&tcpKDC, "", " ")
+ fmt.Fprintf(w, "TCP KDCs: %s\n", string(b))
+ }
+
+ if errs == nil || len(errs) < 1 {
+ return nil
+ }
+ err = fmt.Errorf(strings.Join(errs, "\n"))
+ return err
+}
+
+// Print writes the details of the client to the io.Writer provided.
+func (cl *Client) Print(w io.Writer) {
+ c, _ := cl.Credentials.JSON()
+ fmt.Fprintf(w, "Credentials:\n%s\n", c)
+
+ s, _ := cl.sessions.JSON()
+ fmt.Fprintf(w, "TGT Sessions:\n%s\n", s)
+
+ c, _ = cl.cache.JSON()
+ fmt.Fprintf(w, "Service ticket cache:\n%s\n", c)
+
+ s, _ = cl.settings.JSON()
+ fmt.Fprintf(w, "Settings:\n%s\n", s)
+
+ j, _ := cl.Config.JSON()
+ fmt.Fprintf(w, "Krb5 config:\n%s\n", j)
+
+ k, _ := cl.Credentials.Keytab().JSON()
+ fmt.Fprintf(w, "Keytab:\n%s\n", k)
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/network.go b/vendor/github.com/jcmturner/gokrb5/v8/client/network.go
new file mode 100644
index 0000000..634f015
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/network.go
@@ -0,0 +1,218 @@
+package client
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "strings"
+ "time"
+
+ "github.com/jcmturner/gokrb5/v8/iana/errorcode"
+ "github.com/jcmturner/gokrb5/v8/messages"
+)
+
+// SendToKDC performs network actions to send data to the KDC.
+func (cl *Client) sendToKDC(b []byte, realm string) ([]byte, error) {
+ var rb []byte
+ if cl.Config.LibDefaults.UDPPreferenceLimit == 1 {
+ //1 means we should always use TCP
+ rb, errtcp := cl.sendKDCTCP(realm, b)
+ if errtcp != nil {
+ if e, ok := errtcp.(messages.KRBError); ok {
+ return rb, e
+ }
+ return rb, fmt.Errorf("communication error with KDC via TCP: %v", errtcp)
+ }
+ return rb, nil
+ }
+ if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
+ //Try UDP first, TCP second
+ rb, errudp := cl.sendKDCUDP(realm, b)
+ if errudp != nil {
+ if e, ok := errudp.(messages.KRBError); ok && e.ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG {
+ // Got a KRBError from KDC
+ // If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP.
+ return rb, e
+ }
+ // Try TCP
+ r, errtcp := cl.sendKDCTCP(realm, b)
+ if errtcp != nil {
+ if e, ok := errtcp.(messages.KRBError); ok {
+ // Got a KRBError
+ return r, e
+ }
+ return r, fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", errudp, errtcp)
+ }
+ rb = r
+ }
+ return rb, nil
+ }
+ //Try TCP first, UDP second
+ rb, errtcp := cl.sendKDCTCP(realm, b)
+ if errtcp != nil {
+ if e, ok := errtcp.(messages.KRBError); ok {
+ // Got a KRBError from KDC so returning and not trying UDP.
+ return rb, e
+ }
+ rb, errudp := cl.sendKDCUDP(realm, b)
+ if errudp != nil {
+ if e, ok := errudp.(messages.KRBError); ok {
+ // Got a KRBError
+ return rb, e
+ }
+ return rb, fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", errtcp, errudp)
+ }
+ }
+ return rb, nil
+}
+
+// sendKDCUDP sends bytes to the KDC via UDP.
+func (cl *Client) sendKDCUDP(realm string, b []byte) ([]byte, error) {
+ var r []byte
+ _, kdcs, err := cl.Config.GetKDCs(realm, false)
+ if err != nil {
+ return r, err
+ }
+ r, err = dialSendUDP(kdcs, b)
+ if err != nil {
+ return r, err
+ }
+ return checkForKRBError(r)
+}
+
+// dialSendUDP establishes a UDP connection to a KDC.
+func dialSendUDP(kdcs map[int]string, b []byte) ([]byte, error) {
+ var errs []string
+ for i := 1; i <= len(kdcs); i++ {
+ udpAddr, err := net.ResolveUDPAddr("udp", kdcs[i])
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err))
+ continue
+ }
+
+ conn, err := net.DialTimeout("udp", udpAddr.String(), 5*time.Second)
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err))
+ continue
+ }
+ if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
+ errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err))
+ continue
+ }
+ // conn is guaranteed to be a UDPConn
+ rb, err := sendUDP(conn.(*net.UDPConn), b)
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err))
+ continue
+ }
+ return rb, nil
+ }
+ return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
+}
+
+// sendUDP sends bytes to connection over UDP.
+func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
+ var r []byte
+ defer conn.Close()
+ _, err := conn.Write(b)
+ if err != nil {
+ return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
+ }
+ udpbuf := make([]byte, 4096)
+ n, _, err := conn.ReadFrom(udpbuf)
+ r = udpbuf[:n]
+ if err != nil {
+ return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
+ }
+ if len(r) < 1 {
+ return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String())
+ }
+ return r, nil
+}
+
+// sendKDCTCP sends bytes to the KDC via TCP.
+func (cl *Client) sendKDCTCP(realm string, b []byte) ([]byte, error) {
+ var r []byte
+ _, kdcs, err := cl.Config.GetKDCs(realm, true)
+ if err != nil {
+ return r, err
+ }
+ r, err = dialSendTCP(kdcs, b)
+ if err != nil {
+ return r, err
+ }
+ return checkForKRBError(r)
+}
+
+// dialKDCTCP establishes a TCP connection to a KDC.
+func dialSendTCP(kdcs map[int]string, b []byte) ([]byte, error) {
+ var errs []string
+ for i := 1; i <= len(kdcs); i++ {
+ tcpAddr, err := net.ResolveTCPAddr("tcp", kdcs[i])
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err))
+ continue
+ }
+
+ conn, err := net.DialTimeout("tcp", tcpAddr.String(), 5*time.Second)
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err))
+ continue
+ }
+ if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
+ errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err))
+ continue
+ }
+ // conn is guaranteed to be a TCPConn
+ rb, err := sendTCP(conn.(*net.TCPConn), b)
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err))
+ continue
+ }
+ return rb, nil
+ }
+ return nil, errors.New("error in getting a TCP connection to any of the KDCs")
+}
+
+// sendTCP sends bytes to connection over TCP.
+func sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
+ defer conn.Close()
+ var r []byte
+ // RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.
+ hb := make([]byte, 4, 4)
+ binary.BigEndian.PutUint32(hb, uint32(len(b)))
+ b = append(hb, b...)
+
+ _, err := conn.Write(b)
+ if err != nil {
+ return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
+ }
+
+ sh := make([]byte, 4, 4)
+ _, err = conn.Read(sh)
+ if err != nil {
+ return r, fmt.Errorf("error reading response size header: %v", err)
+ }
+ s := binary.BigEndian.Uint32(sh)
+
+ rb := make([]byte, s, s)
+ _, err = io.ReadFull(conn, rb)
+ if err != nil {
+ return r, fmt.Errorf("error reading response: %v", err)
+ }
+ if len(rb) < 1 {
+ return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
+ }
+ return rb, nil
+}
+
+// checkForKRBError checks if the response bytes from the KDC are a KRBError.
+func checkForKRBError(b []byte) ([]byte, error) {
+ var KRBErr messages.KRBError
+ if err := KRBErr.Unmarshal(b); err == nil {
+ return b, KRBErr
+ }
+ return b, nil
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/passwd.go b/vendor/github.com/jcmturner/gokrb5/v8/client/passwd.go
new file mode 100644
index 0000000..fe20559
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/passwd.go
@@ -0,0 +1,75 @@
+package client
+
+import (
+ "fmt"
+
+ "github.com/jcmturner/gokrb5/v8/kadmin"
+ "github.com/jcmturner/gokrb5/v8/messages"
+)
+
+// Kpasswd server response codes.
+const (
+ KRB5_KPASSWD_SUCCESS = 0
+ KRB5_KPASSWD_MALFORMED = 1
+ KRB5_KPASSWD_HARDERROR = 2
+ KRB5_KPASSWD_AUTHERROR = 3
+ KRB5_KPASSWD_SOFTERROR = 4
+ KRB5_KPASSWD_ACCESSDENIED = 5
+ KRB5_KPASSWD_BAD_VERSION = 6
+ KRB5_KPASSWD_INITIAL_FLAG_NEEDED = 7
+)
+
+// ChangePasswd changes the password of the client to the value provided.
+func (cl *Client) ChangePasswd(newPasswd string) (bool, error) {
+ ASReq, err := messages.NewASReqForChgPasswd(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
+ if err != nil {
+ return false, err
+ }
+ ASRep, err := cl.ASExchange(cl.Credentials.Domain(), ASReq, 0)
+ if err != nil {
+ return false, err
+ }
+
+ msg, key, err := kadmin.ChangePasswdMsg(cl.Credentials.CName(), cl.Credentials.Domain(), newPasswd, ASRep.Ticket, ASRep.DecryptedEncPart.Key)
+ if err != nil {
+ return false, err
+ }
+ r, err := cl.sendToKPasswd(msg)
+ if err != nil {
+ return false, err
+ }
+ err = r.Decrypt(key)
+ if err != nil {
+ return false, err
+ }
+ if r.ResultCode != KRB5_KPASSWD_SUCCESS {
+ return false, fmt.Errorf("error response from kadmin: code: %d; result: %s; krberror: %v", r.ResultCode, r.Result, r.KRBError)
+ }
+ cl.Credentials.WithPassword(newPasswd)
+ return true, nil
+}
+
+func (cl *Client) sendToKPasswd(msg kadmin.Request) (r kadmin.Reply, err error) {
+ _, kps, err := cl.Config.GetKpasswdServers(cl.Credentials.Domain(), true)
+ if err != nil {
+ return
+ }
+ b, err := msg.Marshal()
+ if err != nil {
+ return
+ }
+ var rb []byte
+ if len(b) <= cl.Config.LibDefaults.UDPPreferenceLimit {
+ rb, err = dialSendUDP(kps, b)
+ if err != nil {
+ return
+ }
+ } else {
+ rb, err = dialSendTCP(kps, b)
+ if err != nil {
+ return
+ }
+ }
+ err = r.Unmarshal(rb)
+ return
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/session.go b/vendor/github.com/jcmturner/gokrb5/v8/client/session.go
new file mode 100644
index 0000000..f7654d0
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/session.go
@@ -0,0 +1,295 @@
+package client
+
+import (
+ "encoding/json"
+ "fmt"
+ "sort"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/jcmturner/gokrb5/v8/iana/nametype"
+ "github.com/jcmturner/gokrb5/v8/krberror"
+ "github.com/jcmturner/gokrb5/v8/messages"
+ "github.com/jcmturner/gokrb5/v8/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
+}
+
+// jsonSession is used to enable marshaling some information of a session in a JSON format
+type jsonSession struct {
+ Realm string
+ AuthTime time.Time
+ EndTime time.Time
+ RenewTill time.Time
+ SessionKeyExpiration time.Time
+}
+
+// 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
+}
+
+// JSON return information about the held sessions in a JSON format.
+func (s *sessions) JSON() (string, error) {
+ s.mux.RLock()
+ defer s.mux.RUnlock()
+ var js []jsonSession
+ keys := make([]string, 0, len(s.Entries))
+ for k := range s.Entries {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ r, at, et, rt, kt := s.Entries[k].timeDetails()
+ j := jsonSession{
+ Realm: r,
+ AuthTime: at,
+ EndTime: et,
+ RenewTill: rt,
+ SessionKeyExpiration: kt,
+ }
+ js = append(js, j)
+ }
+ b, err := json.MarshalIndent(js, "", " ")
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+}
+
+// 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
+}
+
+// sessionTimes provides the timing information with regards to a session for the realm specified.
+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])
+}
diff --git a/vendor/github.com/jcmturner/gokrb5/v8/client/settings.go b/vendor/github.com/jcmturner/gokrb5/v8/client/settings.go
new file mode 100644
index 0000000..bcd3945
--- /dev/null
+++ b/vendor/github.com/jcmturner/gokrb5/v8/client/settings.go
@@ -0,0 +1,93 @@
+package client
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+)
+
+// Settings holds optional client settings.
+type Settings struct {
+ disablePAFXFast bool
+ assumePreAuthentication bool
+ preAuthEType int32
+ logger *log.Logger
+}
+
+// jsonSettings is used when marshaling the Settings details to JSON format.
+type jsonSettings struct {
+ DisablePAFXFast bool
+ AssumePreAuthentication bool
+}
+
+// NewSettings creates a new client settings struct.
+func NewSettings(settings ...func(*Settings)) *Settings {
+ s := new(Settings)
+ for _, set := range settings {
+ set(s)
+ }
+ return s
+}
+
+// DisablePAFXFAST used to configure the client to not use PA_FX_FAST.
+//
+// s := NewSettings(DisablePAFXFAST(true))
+func DisablePAFXFAST(b bool) func(*Settings) {
+ return func(s *Settings) {
+ s.disablePAFXFast = b
+ }
+}
+
+// DisablePAFXFAST indicates is the client should disable the use of PA_FX_FAST.
+func (s *Settings) DisablePAFXFAST() bool {
+ return s.disablePAFXFast
+}
+
+// AssumePreAuthentication used to configure the client to assume pre-authentication is required.
+//
+// s := NewSettings(AssumePreAuthentication(true))
+func AssumePreAuthentication(b bool) func(*Settings) {
+ return func(s *Settings) {
+ s.assumePreAuthentication = b
+ }
+}
+
+// AssumePreAuthentication indicates if the client should proactively assume using pre-authentication.
+func (s *Settings) AssumePreAuthentication() bool {
+ return s.assumePreAuthentication
+}
+
+// Logger used to configure client with a logger.
+//
+// s := NewSettings(kt, Logger(l))
+func Logger(l *log.Logger) func(*Settings) {
+ return func(s *Settings) {
+ s.logger = l
+ }
+}
+
+// Logger returns the client logger instance.
+func (s *Settings) Logger() *log.Logger {
+ return s.logger
+}
+
+// Log will write to the service's logger if it is configured.
+func (cl *Client) Log(format string, v ...interface{}) {
+ if cl.settings.Logger() != nil {
+ cl.settings.Logger().Output(2, fmt.Sprintf(format, v...))
+ }
+}
+
+// JSON returns a JSON representation of the settings.
+func (s *Settings) JSON() (string, error) {
+ js := jsonSettings{
+ DisablePAFXFast: s.disablePAFXFast,
+ AssumePreAuthentication: s.assumePreAuthentication,
+ }
+ b, err := json.MarshalIndent(js, "", " ")
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+
+}