This repository has been archived by the owner on Sep 14, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 237
/
examplepersist.go
285 lines (251 loc) · 9.21 KB
/
examplepersist.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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Aaron Meihm ameihm@mozilla.com [:alm]
package examplepersist /* import "github.com/mozilla/mig/modules/examplepersist" */
// This is an example persistent module that provides a basic template that
// can be used to create one.
//
// A persistent module will run continuously and be supervised by the main
// agent process. When an investigator queries the persistent module, the
// query is sent to the persistent module over the socket it has registered
// as listening on.
//
// A lot of the concepts found in standard modules apply here, the primary
// differences between a standard module and a persistent module are documented
// in this code.
import (
"encoding/json"
"fmt"
"runtime"
"time"
"github.com/mozilla/mig/modules"
)
type module struct {
}
func (m *module) NewRun() modules.Runner {
return new(run)
}
func init() {
modules.Register("examplepersist", new(module))
}
type run struct {
Parameters Parameters
Results modules.Result
}
// A persistent module is still queryable by an investigator and can return results,
// we have similar results creation functions here.
func buildResults(e elements, r *modules.Result) (buf []byte, err error) {
r.Success = true
r.Elements = e
r.FoundAnything = true
buf, err = json.Marshal(r)
return
}
// The log channel can be used by functions in the persistent module to send a log
// message up to the agent, where it is logged in the agent's log.
var logChan chan string
// The alert channel can be used by functions in the persistent module to send alerts
// to the agent.
var alertChan chan string
// The error channel can be used to indicate something went wrong in the module by
// writing an error to it. This will result in the modules default handler function
// returning and the module exiting.
var handlerErrChan chan error
// When the agent sends the persistent module it's configuration, it will come in via
// the config channel as a ConfigParams type so we can load it into our configuration
var configChan chan modules.ConfigParams
// An example background task the module will execute while it is being supervised by
// the agent. This example just logs the current time up to the agent every 30
// seconds by default, or if a configuration file existed for the module it will use
// the interval value set there.
func runSomeTasks() {
var cfg config
// After the agent starts this module, it will send any module configuration
// which we can read immediately here. The configuration will come in via
// configChan as a JSON document, which we unmarshal into our config struct.
incfg := <-configChan
buf, err := json.Marshal(incfg.Config)
if err != nil {
handlerErrChan <- err
return
}
err = json.Unmarshal(buf, &cfg)
if err != nil {
handlerErrChan <- err
return
}
logChan <- "module received configuration"
if cfg.ExamplePersist.Interval <= 0 {
logChan <- "config interval was <= 0, defaulting to 30 seconds"
cfg.ExamplePersist.Interval = 30
}
for {
time.Sleep(time.Duration(cfg.ExamplePersist.Interval) * time.Second)
// Send a log message up to the agent
logChan <- fmt.Sprintf("running, current time is %v", time.Now())
}
}
// The request handler here would essentially be what you would find in the Run
// function of a standard module. Parameters enter this routine, where they are processed
// by the module and a result string is returned.
//
// The request handler is set as part of module initialization in the RunPersist function.
func requestHandler(p interface{}) (ret string) {
var results modules.Result
defer func() {
if e := recover(); e != nil {
results.Errors = append(results.Errors, fmt.Sprintf("%v", e))
results.Success = false
err, _ := json.Marshal(results)
ret = string(err)
return
}
}()
// Marshal and unmarshal the parameters into the type we want; p is our
// incoming request parameters.
param := Parameters{}
buf, err := json.Marshal(p)
if err != nil {
panic(err)
}
err = json.Unmarshal(buf, ¶m)
if err != nil {
panic(err)
}
// Create the response
e := elements{String: param.String}
resp, err := buildResults(e, &results)
if err != nil {
panic(err)
}
return string(resp)
}
// The configuration for the persistent module; in the case of a persistent module
// that does not require configuration, our config struct would just be empty, but
// we need to define something to return so we can satisfy the PersistRunner
// interface.
//
// We need to make sure we have JSON tags here as this structure will be marshalled
// and sent to the running module by the agent.
type config struct {
ExamplePersist struct {
Interval int `json:"interval"`
}
}
// PersistModConfig must be implemented in persistent modules so we can satisfy
// the PersistRunner interface. Typically here we will just return a new config
// structure that will get used to load our configuration.
func (r *run) PersistModConfig() interface{} {
return &config{}
}
// RunPersist is the function used to initialize the persistent component
// of the module. It should not return. In this example, we do our initialization
// and call modules.DefaultPersistHandlers, which looks after handling all
// persistent module management processes on the module side.
func (r *run) RunPersist(in modules.ModuleReader, out modules.ModuleWriter) {
// Create a string channel, used to send log messages up to the agent
// from the module tasks. Functions in the persistent module can
// log messages through the agent by writing to this channel.
logChan = make(chan string, 64)
// Create a string channel used to send registration messages up to the
// agent. We will pass our persistent module query socket location
// through this channel after we have initialized it, so the agent knows
// where we are listening.
//
// This string will be "protocol:address", so for example it could be
// "unix:/var/lib/mig/mysock.sock", or "tcp:127.0.0.1:55000" (as examples)
regChan := make(chan string, 64)
// Create an error channel we will pass to the handlers. Writing an
// error to this channel will cause DefaultPersistHandlers() to return
// and the module to exit.
handlerErrChan = make(chan error, 64)
// Create a config channel we will read our configuration from.
configChan = make(chan modules.ConfigParams, 1)
// Initialize the alert channel
alertChan = make(chan string, 64)
// Start up an example background task we want our module to run
// continuously.
go runSomeTasks()
// Get our listener we will listen for queries from the agent on.
l, spec, err := modules.GetPersistListener("examplepersist")
if err != nil {
handlerErrChan <- err
} else {
// We know our listener location, send it to the agent, this registers
// us and allows queries from an investigator to make it to the module.
regChan <- spec
}
// Spawn the request handler; this will route new requests to requstHandler.
go modules.HandlePersistRequest(l, requestHandler, handlerErrChan)
// Finally, enter the standard module management function. This will not return
// unless an error occurs.
modules.DefaultPersistHandlers(in, out, logChan, handlerErrChan, regChan,
alertChan, configChan)
}
// Module Run function, used to make queries using the module.
func (r *run) Run(in modules.ModuleReader) (resStr string) {
defer func() {
if e := recover(); e != nil {
// return error in json
r.Results.Errors = append(r.Results.Errors, fmt.Sprintf("%v", e))
r.Results.Success = false
err, _ := json.Marshal(r.Results)
resStr = string(err)
return
}
}()
// Restrict go runtime processor utilization here, this might be moved
// into a more generic agent module function at some point.
runtime.GOMAXPROCS(1)
// Read module parameters from stdin. Note we use ReadPersistInputParameters here
// as the socket path is being sent as well, and the function needs this to know
// where to query the persistent module.
sockspec, err := modules.ReadPersistInputParameters(in, &r.Parameters)
if err != nil {
panic(err)
}
err = r.ValidateParameters()
if err != nil {
panic(err)
}
// With a standard module, we'd process the request and return the results here.
// Since this is a persistent module, we want to forward the parameters the
// investigator provided to the module which has been running persistently. We
// forward the request on to the listening socket and return the results.
resStr = modules.SendPersistRequest(r.Parameters, sockspec)
return
}
func (r *run) ValidateParameters() (err error) {
if r.Parameters.String == "" {
return fmt.Errorf("must set a string to echo")
}
return
}
func (r *run) PrintResults(result modules.Result, foundOnly bool) (prints []string, err error) {
var (
elem elements
)
err = result.GetElements(&elem)
if err != nil {
panic(err)
}
resStr := fmt.Sprintf("echo string was %q", elem.String)
prints = append(prints, resStr)
if !foundOnly {
for _, we := range result.Errors {
prints = append(prints, we)
}
}
return
}
type elements struct {
String string `json:"string"`
}
type Parameters struct {
String string `json:"string"` // String to echo back
}
func newParameters() *Parameters {
return &Parameters{}
}