-
Notifications
You must be signed in to change notification settings - Fork 4
/
ccachemon.go
232 lines (202 loc) · 6.02 KB
/
ccachemon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Package krbmon contains kerberos monitoring components.
package krbmon
import (
"fmt"
"os"
"os/user"
"path/filepath"
"reflect"
"strings"
"github.com/fsnotify/fsnotify"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/iana/nametype"
"github.com/jcmturner/gokrb5/v8/types"
log "github.com/sirupsen/logrus"
)
// CCacheUpdate is a ccache monitor update.
type CCacheUpdate struct {
CCache *credentials.CCache
}
// GetTGT returns the TGT for realm in the ccache.
func (u *CCacheUpdate) GetTGT(realm string) *credentials.Credential {
name := types.NewPrincipalName(nametype.KRB_NT_SRV_INST, "krbtgt/"+realm)
if tgt, ok := u.CCache.GetEntry(name); ok {
return tgt
}
return nil
}
// CCacheMon is a ccache monitor.
type CCacheMon struct {
cCacheDir string
cCacheFile string
watcher *fsnotify.Watcher
cCache *credentials.CCache
updates chan *CCacheUpdate
done chan struct{}
closed chan struct{}
}
// sendUpdate sends an update over the updates channel.
func (c *CCacheMon) sendUpdate(update *CCacheUpdate) {
// send an update or abort if we are shutting down
select {
case c.updates <- update:
case <-c.done:
}
}
// userCurrent is user.Current for testing.
var userCurrent = user.Current
// createCredentialCacheEnvVar creates an expected environment variable value
// for the credential cache based on the current user ID.
func createCredentialCacheEnvVar() string {
osUser, err := userCurrent()
if err != nil {
log.WithError(err).
Error("Kerberos CCache Monitor could not create credential cache environment variable value")
return ""
}
return fmt.Sprintf("FILE:/tmp/krb5cc_%s", osUser.Uid)
}
// getCredentialCacheFilename returns the ccache file name.
var getCredentialCacheFilename = func() (string, error) {
envVar := os.Getenv("KRB5CCNAME")
if envVar == "" {
newEnv := createCredentialCacheEnvVar()
log.WithField("new", newEnv).
Debug("Kerberos CCache Monitor could not get environment variable KRB5CCNAME, setting it")
envVar = newEnv
}
if !strings.HasPrefix(envVar, "FILE:") {
newEnv := createCredentialCacheEnvVar()
log.WithFields(log.Fields{
"old": envVar,
"new": newEnv,
}).Error("Kerberos CCache Monitor got invalid environment variable KRB5CCNAME, resetting it")
envVar = newEnv
}
if envVar == "" {
// environment variable still invalid
return "", fmt.Errorf("environment variable KRB5CCNAME is not set")
}
return strings.TrimPrefix(envVar, "FILE:"), nil
}
// isCCacheFileEvent checks if event is a ccache file event.
func (c *CCacheMon) isCCacheFileEvent(event fsnotify.Event) bool {
return event.Name == c.cCacheFile
}
// handleCCacheFileEvent handles a ccache file event.
func (c *CCacheMon) handleCCacheFileEvent(event fsnotify.Event) {
// check event
if !c.isCCacheFileEvent(event) {
return
}
log.WithFields(log.Fields{
"name": event.Name,
"op": event.Op,
}).Debug("Kerberos CCache Monitor handling file event")
// read ccache file
b, err := os.ReadFile(c.cCacheFile)
if err != nil {
log.WithError(err).Error("Kerberos CCache Monitor could not read credential cache file")
return
}
// check file length to make sure loading the credential cache below
// does not fail. this is a rough estimate of a minimum ccache file
// that contains: the version indicator (2 bytes), no header, minimum
// default principal (8 bytes), one minimum credential (59 bytes). See
// https://web.mit.edu/kerberos/krb5-devel/doc/formats/ccache_file_format.html
if len(b) < 69 {
log.Error("Kerberos CCache Monitor read invalid credential cache file")
return
}
// load ccache
cCache := new(credentials.CCache)
err = cCache.Unmarshal(b)
if err != nil {
log.WithError(err).Error("Kerberos CCache Monitor could not load credential cache")
return
}
// check if ccache changed
if reflect.DeepEqual(cCache, c.cCache) {
return
}
// ccache changed, send update
c.cCache = cCache
c.sendUpdate(&CCacheUpdate{CCache: c.cCache})
}
// handleCCacheFileError handles a ccache file error.
func (c *CCacheMon) handleCCacheFileError(err error) {
log.WithError(err).Error("Kerberos CCache Monitor watcher error event")
}
// start starts the ccache monitor.
func (c *CCacheMon) start() {
defer close(c.closed)
defer close(c.updates)
defer func() {
if err := watcherClose(c.watcher); err != nil {
log.WithError(err).Error("Kerberos CCache Monitor file watcher close error")
}
}()
// handle initial ccache file
c.handleCCacheFileEvent(fsnotify.Event{Name: c.cCacheFile})
// watch ccache file
for {
select {
case event, ok := <-c.watcher.Events:
if !ok {
log.Error("Kerberos CCache Monitor got unexpected close of events channel")
return
}
c.handleCCacheFileEvent(event)
case err, ok := <-c.watcher.Errors:
if !ok {
log.Error("Kerberos CCache Monitor got unexpected close of errors channel")
return
}
c.handleCCacheFileError(err)
case <-c.done:
return
}
}
}
// Start starts the ccache monitor.
func (c *CCacheMon) Start() error {
// get ccache file
cCacheFile, err := getCredentialCacheFilename()
if err != nil {
log.WithError(err).Error("Kerberos CCache Monitor could not get CCache file")
return err
}
c.cCacheFile = cCacheFile
// create watcher
watcher, err := fsnotifyNewWatcher()
if err != nil {
log.WithError(err).Error("Kerberos CCache Monitor file watcher error")
return err
}
// get ccache folder and add it to watcher
c.cCacheDir = filepath.Dir(cCacheFile)
if err := watcherAdd(watcher, c.cCacheDir); err != nil {
log.WithField("dir", c.cCacheDir).WithError(err).Error("Kerberos CCache Monitor add CCache error")
return err
}
c.watcher = watcher
go c.start()
return nil
}
// Stop stops the ccache monitor.
func (c *CCacheMon) Stop() {
close(c.done)
<-c.closed
}
// Updates returns the channel for ccache updates.
func (c *CCacheMon) Updates() chan *CCacheUpdate {
return c.updates
}
// NewCCacheMon returns a new ccache monitor.
func NewCCacheMon() *CCacheMon {
return &CCacheMon{
updates: make(chan *CCacheUpdate),
done: make(chan struct{}),
closed: make(chan struct{}),
}
}