// Package credentials provides credentials management for Kerberos 5 authentication. package credentials import ( "bytes" "encoding/gob" "encoding/json" "time" "github.com/hashicorp/go-uuid" "github.com/jcmturner/gokrb5/v8/iana/nametype" "github.com/jcmturner/gokrb5/v8/keytab" "github.com/jcmturner/gokrb5/v8/types" ) const ( // AttributeKeyADCredentials assigned number for AD credentials. AttributeKeyADCredentials = "gokrb5AttributeKeyADCredentials" ) // Credentials struct for a user. // Contains either a keytab, password or both. // Keytabs are used over passwords if both are defined. type Credentials struct { username string displayName string realm string cname types.PrincipalName keytab *keytab.Keytab password string attributes map[string]interface{} validUntil time.Time authenticated bool human bool authTime time.Time groupMembership map[string]bool sessionID string } // marshalCredentials is used to enable marshaling and unmarshaling of credentials // without having exported fields on the Credentials struct type marshalCredentials struct { Username string DisplayName string Realm string CName types.PrincipalName `json:"-"` Keytab bool Password bool Attributes map[string]interface{} `json:"-"` ValidUntil time.Time Authenticated bool Human bool AuthTime time.Time GroupMembership map[string]bool `json:"-"` SessionID string } // ADCredentials contains information obtained from the PAC. type ADCredentials struct { EffectiveName string FullName string UserID int PrimaryGroupID int LogOnTime time.Time LogOffTime time.Time PasswordLastSet time.Time GroupMembershipSIDs []string LogonDomainName string LogonDomainID string LogonServer string } // New creates a new Credentials instance. func New(username string, realm string) *Credentials { uid, err := uuid.GenerateUUID() if err != nil { uid = "00unique-sess-ions-uuid-unavailable0" } return &Credentials{ username: username, displayName: username, realm: realm, cname: types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, username), keytab: keytab.New(), attributes: make(map[string]interface{}), groupMembership: make(map[string]bool), sessionID: uid, human: true, } } // NewFromPrincipalName creates a new Credentials instance with the user details provides as a PrincipalName type. func NewFromPrincipalName(cname types.PrincipalName, realm string) *Credentials { c := New(cname.PrincipalNameString(), realm) c.cname = cname return c } // WithKeytab sets the Keytab in the Credentials struct. func (c *Credentials) WithKeytab(kt *keytab.Keytab) *Credentials { c.keytab = kt c.password = "" return c } // Keytab returns the credential's Keytab. func (c *Credentials) Keytab() *keytab.Keytab { return c.keytab } // HasKeytab queries if the Credentials has a keytab defined. func (c *Credentials) HasKeytab() bool { if c.keytab != nil && len(c.keytab.Entries) > 0 { return true } return false } // WithPassword sets the password in the Credentials struct. func (c *Credentials) WithPassword(password string) *Credentials { c.password = password c.keytab = keytab.New() // clear any keytab return c } // Password returns the credential's password. func (c *Credentials) Password() string { return c.password } // HasPassword queries if the Credentials has a password defined. func (c *Credentials) HasPassword() bool { if c.password != "" { return true } return false } // SetValidUntil sets the expiry time of the credentials func (c *Credentials) SetValidUntil(t time.Time) { c.validUntil = t } // SetADCredentials adds ADCredentials attributes to the credentials func (c *Credentials) SetADCredentials(a ADCredentials) { c.SetAttribute(AttributeKeyADCredentials, a) if a.FullName != "" { c.SetDisplayName(a.FullName) } if a.EffectiveName != "" { c.SetUserName(a.EffectiveName) } for i := range a.GroupMembershipSIDs { c.AddAuthzAttribute(a.GroupMembershipSIDs[i]) } } // GetADCredentials returns ADCredentials attributes sorted in the credential func (c *Credentials) GetADCredentials() ADCredentials { if a, ok := c.attributes[AttributeKeyADCredentials].(ADCredentials); ok { return a } return ADCredentials{} } // Methods to implement goidentity.Identity interface // UserName returns the credential's username. func (c *Credentials) UserName() string { return c.username } // SetUserName sets the username value on the credential. func (c *Credentials) SetUserName(s string) { c.username = s } // CName returns the credential's client principal name. func (c *Credentials) CName() types.PrincipalName { return c.cname } // SetCName sets the client principal name on the credential. func (c *Credentials) SetCName(pn types.PrincipalName) { c.cname = pn } // Domain returns the credential's domain. func (c *Credentials) Domain() string { return c.realm } // SetDomain sets the domain value on the credential. func (c *Credentials) SetDomain(s string) { c.realm = s } // Realm returns the credential's realm. Same as the domain. func (c *Credentials) Realm() string { return c.Domain() } // SetRealm sets the realm value on the credential. Same as the domain func (c *Credentials) SetRealm(s string) { c.SetDomain(s) } // DisplayName returns the credential's display name. func (c *Credentials) DisplayName() string { return c.displayName } // SetDisplayName sets the display name value on the credential. func (c *Credentials) SetDisplayName(s string) { c.displayName = s } // Human returns if the credential represents a human or not. func (c *Credentials) Human() bool { return c.human } // SetHuman sets the credential as human. func (c *Credentials) SetHuman(b bool) { c.human = b } // AuthTime returns the time the credential was authenticated. func (c *Credentials) AuthTime() time.Time { return c.authTime } // SetAuthTime sets the time the credential was authenticated. func (c *Credentials) SetAuthTime(t time.Time) { c.authTime = t } // AuthzAttributes returns the credentials authorizing attributes. func (c *Credentials) AuthzAttributes() []string { s := make([]string, len(c.groupMembership)) i := 0 for a := range c.groupMembership { s[i] = a i++ } return s } // Authenticated indicates if the credential has been successfully authenticated or not. func (c *Credentials) Authenticated() bool { return c.authenticated } // SetAuthenticated sets the credential as having been successfully authenticated. func (c *Credentials) SetAuthenticated(b bool) { c.authenticated = b } // AddAuthzAttribute adds an authorization attribute to the credential. func (c *Credentials) AddAuthzAttribute(a string) { c.groupMembership[a] = true } // RemoveAuthzAttribute removes an authorization attribute from the credential. func (c *Credentials) RemoveAuthzAttribute(a string) { if _, ok := c.groupMembership[a]; !ok { return } delete(c.groupMembership, a) } // EnableAuthzAttribute toggles an authorization attribute to an enabled state on the credential. func (c *Credentials) EnableAuthzAttribute(a string) { if enabled, ok := c.groupMembership[a]; ok && !enabled { c.groupMembership[a] = true } } // DisableAuthzAttribute toggles an authorization attribute to a disabled state on the credential. func (c *Credentials) DisableAuthzAttribute(a string) { if enabled, ok := c.groupMembership[a]; ok && enabled { c.groupMembership[a] = false } } // Authorized indicates if the credential has the specified authorizing attribute. func (c *Credentials) Authorized(a string) bool { if enabled, ok := c.groupMembership[a]; ok && enabled { return true } return false } // SessionID returns the credential's session ID. func (c *Credentials) SessionID() string { return c.sessionID } // Expired indicates if the credential has expired. func (c *Credentials) Expired() bool { if !c.validUntil.IsZero() && time.Now().UTC().After(c.validUntil) { return true } return false } // ValidUntil returns the credential's valid until date func (c *Credentials) ValidUntil() time.Time { return c.validUntil } // Attributes returns the Credentials' attributes map. func (c *Credentials) Attributes() map[string]interface{} { return c.attributes } // SetAttribute sets the value of an attribute. func (c *Credentials) SetAttribute(k string, v interface{}) { c.attributes[k] = v } // SetAttributes replaces the attributes map with the one provided. func (c *Credentials) SetAttributes(a map[string]interface{}) { c.attributes = a } // RemoveAttribute deletes an attribute from the attribute map that has the key provided. func (c *Credentials) RemoveAttribute(k string) { delete(c.attributes, k) } // Marshal the Credentials into a byte slice func (c *Credentials) Marshal() ([]byte, error) { gob.Register(map[string]interface{}{}) gob.Register(ADCredentials{}) buf := new(bytes.Buffer) enc := gob.NewEncoder(buf) mc := marshalCredentials{ Username: c.username, DisplayName: c.displayName, Realm: c.realm, CName: c.cname, Keytab: c.HasKeytab(), Password: c.HasPassword(), Attributes: c.attributes, ValidUntil: c.validUntil, Authenticated: c.authenticated, Human: c.human, AuthTime: c.authTime, GroupMembership: c.groupMembership, SessionID: c.sessionID, } err := enc.Encode(&mc) if err != nil { return []byte{}, err } return buf.Bytes(), nil } // Unmarshal a byte slice into Credentials func (c *Credentials) Unmarshal(b []byte) error { gob.Register(map[string]interface{}{}) gob.Register(ADCredentials{}) mc := new(marshalCredentials) buf := bytes.NewBuffer(b) dec := gob.NewDecoder(buf) err := dec.Decode(mc) if err != nil { return err } c.username = mc.Username c.displayName = mc.DisplayName c.realm = mc.Realm c.cname = mc.CName c.attributes = mc.Attributes c.validUntil = mc.ValidUntil c.authenticated = mc.Authenticated c.human = mc.Human c.authTime = mc.AuthTime c.groupMembership = mc.GroupMembership c.sessionID = mc.SessionID return nil } // JSON return details of the Credentials in a JSON format. func (c *Credentials) JSON() (string, error) { mc := marshalCredentials{ Username: c.username, DisplayName: c.displayName, Realm: c.realm, CName: c.cname, Keytab: c.HasKeytab(), Password: c.HasPassword(), ValidUntil: c.validUntil, Authenticated: c.authenticated, Human: c.human, AuthTime: c.authTime, SessionID: c.sessionID, } b, err := json.MarshalIndent(mc, "", " ") if err != nil { return "", err } return string(b), nil }