-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathfingerprint_manager.go
203 lines (167 loc) · 6.1 KB
/
fingerprint_manager.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
package client
import (
"sync"
"time"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/fingerprint"
"github.com/hashicorp/nomad/helper/pluginutils/loader"
"github.com/hashicorp/nomad/nomad/structs"
)
// FingerprintManager runs a client fingerprinters on a continuous basis, and
// updates the client when the node has changed
type FingerprintManager struct {
singletonLoader loader.PluginCatalog
getConfig func() *config.Config
node *structs.Node
nodeLock sync.Mutex
shutdownCh chan struct{}
// updateNodeAttributes is a callback to the client to update the state of its
// associated node
updateNodeAttributes func(*fingerprint.FingerprintResponse) *structs.Node
reloadableFps map[string]fingerprint.ReloadableFingerprint
logger log.Logger
}
// NewFingerprintManager is a constructor that creates and returns an instance
// of FingerprintManager
func NewFingerprintManager(
singletonLoader loader.PluginCatalog,
getConfig func() *config.Config,
node *structs.Node,
shutdownCh chan struct{},
updateNodeAttributes func(*fingerprint.FingerprintResponse) *structs.Node,
logger log.Logger) *FingerprintManager {
return &FingerprintManager{
singletonLoader: singletonLoader,
getConfig: getConfig,
updateNodeAttributes: updateNodeAttributes,
node: node,
shutdownCh: shutdownCh,
logger: logger.Named("fingerprint_mgr"),
reloadableFps: make(map[string]fingerprint.ReloadableFingerprint),
}
}
// setNode updates the current client node
func (fm *FingerprintManager) setNode(node *structs.Node) {
fm.nodeLock.Lock()
defer fm.nodeLock.Unlock()
fm.node = node
}
// getNode returns the current client node
func (fm *FingerprintManager) getNode() *structs.Node {
fm.nodeLock.Lock()
defer fm.nodeLock.Unlock()
return fm.node
}
// Run starts the process of fingerprinting the node. It does an initial pass,
// identifying allowlisted and denylisted fingerprints/drivers. Then, for
// those which require periotic checking, it starts a periodic process for
// each.
func (fp *FingerprintManager) Run() error {
// First, set up all fingerprints
cfg := fp.getConfig()
// COMPAT(1.0) using inclusive language, whitelist is kept for backward compatibility.
allowlistFingerprints := cfg.ReadStringListToMap("fingerprint.allowlist", "fingerprint.whitelist")
allowlistFingerprintsEnabled := len(allowlistFingerprints) > 0
// COMPAT(1.0) using inclusive language, blacklist is kept for backward compatibility.
denylistFingerprints := cfg.ReadStringListToMap("fingerprint.denylist", "fingerprint.blacklist")
fp.logger.Debug("built-in fingerprints", "fingerprinters", fingerprint.BuiltinFingerprints())
var availableFingerprints []string
var skippedFingerprints []string
for _, name := range fingerprint.BuiltinFingerprints() {
// Skip modules that are not in the allowlist if it is enabled.
if _, ok := allowlistFingerprints[name]; allowlistFingerprintsEnabled && !ok {
skippedFingerprints = append(skippedFingerprints, name)
continue
}
// Skip modules that are in the denylist
if _, ok := denylistFingerprints[name]; ok {
skippedFingerprints = append(skippedFingerprints, name)
continue
}
availableFingerprints = append(availableFingerprints, name)
}
if err := fp.setupFingerprinters(availableFingerprints); err != nil {
return err
}
if len(skippedFingerprints) != 0 {
fp.logger.Debug("fingerprint modules skipped due to allow/denylist",
"skipped_fingerprinters", skippedFingerprints)
}
return nil
}
// Reload will reload any registered ReloadableFingerprinters and immediately call Fingerprint
func (fm *FingerprintManager) Reload() {
for name, fp := range fm.reloadableFps {
fm.logger.Info("reloading fingerprinter", "fingerprinter", name)
fp.Reload()
if _, err := fm.fingerprint(name, fp); err != nil {
fm.logger.Warn("error fingerprinting after reload", "fingerprinter", name, "error", err)
}
}
}
// setupFingerprints is used to fingerprint the node to see if these attributes are
// supported
func (fm *FingerprintManager) setupFingerprinters(fingerprints []string) error {
var appliedFingerprints []string
for _, name := range fingerprints {
f, err := fingerprint.NewFingerprint(name, fm.logger)
if err != nil {
fm.logger.Error("error fingerprinting", "error", err, "fingerprinter", name)
return err
}
detected, err := fm.fingerprint(name, f)
if err != nil {
return err
}
// log the fingerprinters which have been applied
if detected {
appliedFingerprints = append(appliedFingerprints, name)
}
p, period := f.Periodic()
if p {
go fm.runFingerprint(f, period, name)
}
if rfp, ok := f.(fingerprint.ReloadableFingerprint); ok {
fm.reloadableFps[name] = rfp
}
}
fm.logger.Debug("detected fingerprints", "node_attrs", appliedFingerprints)
return nil
}
// runFingerprint runs each fingerprinter individually on an ongoing basis
func (fm *FingerprintManager) runFingerprint(f fingerprint.Fingerprint, period time.Duration, name string) {
fm.logger.Debug("fingerprinting periodically", "fingerprinter", name, "period", period)
timer := time.NewTimer(period)
defer timer.Stop()
for {
select {
case <-timer.C:
timer.Reset(period)
_, err := fm.fingerprint(name, f)
if err != nil {
fm.logger.Debug("error periodic fingerprinting", "error", err, "fingerprinter", name)
continue
}
case <-fm.shutdownCh:
return
}
}
}
// fingerprint does an initial fingerprint of the client. If the fingerprinter
// is meant to be run continuously, a process is launched to perform this
// fingerprint on an ongoing basis in the background.
func (fm *FingerprintManager) fingerprint(name string, f fingerprint.Fingerprint) (bool, error) {
var response fingerprint.FingerprintResponse
fm.nodeLock.Lock()
request := &fingerprint.FingerprintRequest{Config: fm.getConfig(), Node: fm.node}
err := f.Fingerprint(request, &response)
fm.nodeLock.Unlock()
if err != nil {
return false, err
}
if node := fm.updateNodeAttributes(&response); node != nil {
fm.setNode(node)
}
return response.Detected, nil
}