зеркало из https://github.com/mozilla/mig.git
668 строки
17 KiB
Go
668 строки
17 KiB
Go
// 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: Julien Vehent jvehent@mozilla.com [:ulfr]
|
|
|
|
/* This package implements interfaces to write modules for MIG.
|
|
For a guide on how to write modules, head over to
|
|
http://mig.mozilla.org/doc/modules.rst.html
|
|
*/
|
|
package modules /* import "github.com/mozilla/mig/modules" */
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var ModuleRunDir string
|
|
|
|
// Message defines the input messages received by modules.
|
|
//
|
|
// All messages will have Class and Parameters set. PersistSock is used in a case
|
|
// where a parameters message is being sent for a persistent module. In this case,
|
|
// PersistSock will contain the socket specification the module has registered as
|
|
// listening on.
|
|
type Message struct {
|
|
Class MessageClass `json:"class"` // represent the type of message being passed to the module
|
|
Parameters interface{} `json:"parameters,omitempty"` // for `parameters` class, this interface contains the module parameters
|
|
PersistSock string `json:"persistsock,omitempty"` // Persistent module socket path if required
|
|
}
|
|
|
|
type MessageClass string
|
|
|
|
const (
|
|
MsgClassParameters MessageClass = "parameters"
|
|
MsgClassStop MessageClass = "stop"
|
|
MsgClassPing MessageClass = "ping"
|
|
MsgClassLog MessageClass = "log"
|
|
MsgClassRegister MessageClass = "register"
|
|
MsgClassConfig MessageClass = "config"
|
|
MsgClassAlert MessageClass = "alert"
|
|
)
|
|
|
|
// Parameter format expected for a log message
|
|
type LogParams struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// Parameter format expected for a register message
|
|
type RegParams struct {
|
|
SockPath string `json:"sockpath"`
|
|
}
|
|
|
|
type ConfigParams struct {
|
|
Config interface{} `json:"config"`
|
|
Hostname string `json:"hostname"`
|
|
Env interface{} `json:"environment"`
|
|
Tags map[string]string `json:"tags"`
|
|
}
|
|
|
|
// AlertParams describes the parameters used in an alert message
|
|
type AlertParams struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// Result implement the base type for results returned by modules.
|
|
// All modules must return this type of result. The fields are:
|
|
//
|
|
// - FoundAnything: a boolean that must be set to true if the module ran
|
|
// a search that returned at least one positive result
|
|
//
|
|
// - Success: a boolean that must be set to true if the module ran without
|
|
// fatal errors. soft errors are reported in Errors
|
|
//
|
|
// - Elements: an undefined type that can be customized by the module to
|
|
// contain the detailled results
|
|
//
|
|
// - Statistics: an undefined type that can be customized by the module to
|
|
// contain some information about how it ran
|
|
//
|
|
// - Errors: an array of strings that contain non-fatal errors encountered
|
|
// by the module
|
|
type Result struct {
|
|
FoundAnything bool `json:"foundanything"`
|
|
Success bool `json:"success"`
|
|
Elements interface{} `json:"elements"`
|
|
Statistics interface{} `json:"statistics"`
|
|
Errors []string `json:"errors"`
|
|
}
|
|
|
|
// Runner provides the interface to an execution of a module
|
|
type Runner interface {
|
|
Run(ModuleReader) string
|
|
ValidateParameters() error
|
|
}
|
|
|
|
// PersistRunner provides the interface to execution of a persistent module. All
|
|
// modules will satisfy Runner. Persistent modules will satisfy both Runner and
|
|
// PersistRunner.
|
|
type PersistRunner interface {
|
|
RunPersist(ModuleReader, ModuleWriter)
|
|
PersistModConfig() interface{}
|
|
}
|
|
|
|
// ModuleReader is used to read module communications. It's intent is to
|
|
// wrap the initial reader (e.g., stdin) with a buffered reader that will exist for
|
|
// the lifetime of execution of the module. When the module reads input, it will
|
|
// read from BufferReader inside ModuleReader since our communication is line delimited,
|
|
// so we want to make sure we allocate this buffer only once.
|
|
type ModuleReader struct {
|
|
Reader io.Reader
|
|
BufferReader *bufio.Reader
|
|
}
|
|
|
|
// Create a new ModuleReader wrapping reader r.
|
|
func NewModuleReader(r io.Reader) (ret ModuleReader) {
|
|
ret.Reader = r
|
|
ret.BufferReader = bufio.NewReader(ret.Reader)
|
|
return
|
|
}
|
|
|
|
// ModuleWriter is used to write module communications. We don't require bufio on
|
|
// writes, but this type exists just to provide consistency with ModuleReader.
|
|
type ModuleWriter struct {
|
|
Writer io.Writer
|
|
}
|
|
|
|
// Create a new ModuleWriter wrapping writer w.
|
|
func NewModuleWriter(w io.Writer) (ret ModuleWriter) {
|
|
ret.Writer = w
|
|
return
|
|
}
|
|
|
|
// MakeMessage creates a new modules.Message with a given class and parameters and
|
|
// return the byte slice of the json marshalled message
|
|
func MakeMessage(class MessageClass, params interface{}, comp bool) (rawMsg []byte, err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("Failed to make modules.Message: %v", e)
|
|
}
|
|
}()
|
|
|
|
var msg Message
|
|
msg.Class = class
|
|
msg.Parameters = params
|
|
// If the compression flag is set, treat Parameters as a compressed
|
|
// byte string.
|
|
if comp {
|
|
pstr, ok := msg.Parameters.(string)
|
|
if !ok {
|
|
panic("Compressed parameter was not a string")
|
|
}
|
|
b := bytes.NewBuffer([]byte(pstr))
|
|
rb64 := base64.NewDecoder(base64.StdEncoding, b)
|
|
r, err := gzip.NewReader(rb64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
rb, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = json.Unmarshal(rb, &msg.Parameters)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
rawMsg, err = json.Marshal(msg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// MakeMessageLog creates a new message of class log.
|
|
func MakeMessageLog(f string, args ...interface{}) (rawMsg []byte, err error) {
|
|
param := LogParams{Message: fmt.Sprintf(f, args...)}
|
|
msg := Message{Class: MsgClassLog, Parameters: param}
|
|
rawMsg, err = json.Marshal(&msg)
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to make module log message: %v", err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Creates a new message of class register.
|
|
func MakeMessageRegister(spec string) (rawMsg []byte, err error) {
|
|
param := RegParams{SockPath: spec}
|
|
msg := Message{Class: MsgClassRegister, Parameters: param}
|
|
rawMsg, err = json.Marshal(&msg)
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to make module register message: %v", err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Creates a new message of class config.
|
|
func MakeMessageConfig(cfgdata interface{}, hostname string, env interface{},
|
|
tags map[string]string) (rawMsg []byte, err error) {
|
|
param := ConfigParams{
|
|
Config: cfgdata,
|
|
Hostname: hostname,
|
|
Env: env,
|
|
Tags: tags,
|
|
}
|
|
msg := Message{Class: MsgClassConfig, Parameters: param}
|
|
rawMsg, err = json.Marshal(&msg)
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to make module config message: %v", err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// MakeMessageAlert creates a new message of class alert
|
|
func MakeMessageAlert(f string, args ...interface{}) (rawMsg []byte, err error) {
|
|
param := AlertParams{Message: fmt.Sprintf(f, args...)}
|
|
msg := Message{Class: MsgClassAlert, Parameters: param}
|
|
rawMsg, err = json.Marshal(&msg)
|
|
if err != nil {
|
|
err = fmt.Errorf("Failed to make module alert message: %v", err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Keep reading until we get a full line or an error, and return
|
|
func readInputLine(rdr *bufio.Reader) ([]byte, error) {
|
|
var ret []byte
|
|
for {
|
|
lb, isPrefix, err := rdr.ReadLine()
|
|
if err != nil {
|
|
return ret, err
|
|
}
|
|
ret = append(ret, lb...)
|
|
if !isPrefix {
|
|
break
|
|
}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// ReadInput reads one line of input from ModuleReader r, unmarshal it into a modules.Message
|
|
// and returns the message to the caller
|
|
func ReadInput(r ModuleReader) (msg Message, err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("ReadInput() -> %v", e)
|
|
}
|
|
}()
|
|
linebuffer, err := readInputLine(r.BufferReader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = json.Unmarshal(linebuffer, &msg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// ReadInputParameters reads the first line from ModuleReader r and expects to find a
|
|
// modules.Message of class `parameters`. This function uses ReadInput and will
|
|
// block waiting for data on stdin
|
|
func ReadInputParameters(r ModuleReader, p interface{}) (err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("ReadInputParameters() -> %v", e)
|
|
}
|
|
}()
|
|
msg, err := ReadInput(r)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if msg.Class != MsgClassParameters {
|
|
panic("unexpected input is not module parameters")
|
|
}
|
|
rawParams, err := json.Marshal(msg.Parameters)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = json.Unmarshal(rawParams, p)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// ReadPersistInputParameters performs the same function as ReadInputParameters
|
|
// however it also validates a socket path has been specified to query the
|
|
// persistent module, returning an error if this is not present. Populates
|
|
// p and also returns the socket path.
|
|
func ReadPersistInputParameters(r ModuleReader, p interface{}) (spath string, err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("ReadPersistInputParameters() -> %v", e)
|
|
}
|
|
}()
|
|
msg, err := ReadInput(r)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if msg.Class != MsgClassParameters {
|
|
panic("unexpected input is not module parameters")
|
|
}
|
|
if msg.PersistSock == "" {
|
|
panic("no persistsock set in message")
|
|
}
|
|
spath = msg.PersistSock
|
|
rawParams, err := json.Marshal(msg.Parameters)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = json.Unmarshal(rawParams, p)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Write output in buf to writer w. buf is expected to contain a single line
|
|
// of data, and a line feed is appended to terminate the line as module IO is
|
|
// line delimited.
|
|
func WriteOutput(buf []byte, w ModuleWriter) (err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("WriteOutput() -> %v", e)
|
|
}
|
|
}()
|
|
// Since our output is line delimited, add a newline
|
|
buf = append(buf, '\n')
|
|
left := len(buf)
|
|
for left > 0 {
|
|
nb, err := w.Writer.Write(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
left -= nb
|
|
buf = buf[nb:]
|
|
}
|
|
return
|
|
}
|
|
|
|
// WatchForStop continuously reads stdin for a stop message. When one is received,
|
|
// `true` is sent into the stop channel.
|
|
func WatchForStop(r ModuleReader, stopChan *chan bool) error {
|
|
for {
|
|
msg, err := ReadInput(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if msg.Class == MsgClassStop {
|
|
*stopChan <- true
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// dispatchFunc can be set by a module using RegisterDispatchFunction, and is called
|
|
// inside DefaultPersistHandlers if the module recieves an alert message from the master
|
|
// agent process.
|
|
var dispatchFunc func(string)
|
|
|
|
// RegisterDispatchFunction can be called by a module to register a function that will be
|
|
// used to process incoming alert messages from the master agent process. It is primarily
|
|
// used by the dispatch module.
|
|
func RegisterDispatchFunction(f func(string)) {
|
|
dispatchFunc = f
|
|
}
|
|
|
|
// A general management function that can be called by persistent modules from the
|
|
// RunPersist function. Looks after replying to ping messages, writing logs, and other
|
|
// communication between the agent and the running persistent module.
|
|
func DefaultPersistHandlers(in ModuleReader, out ModuleWriter, logch chan string,
|
|
errch chan error, regch chan string, alertch chan string, confch chan ConfigParams) {
|
|
inChan := make(chan Message, 0)
|
|
go func() {
|
|
for {
|
|
msg, err := ReadInput(in)
|
|
if err != nil {
|
|
close(inChan)
|
|
break
|
|
}
|
|
inChan <- msg
|
|
}
|
|
}()
|
|
for {
|
|
failed := false
|
|
|
|
select {
|
|
case em := <-errch:
|
|
failed = true
|
|
// An error occurred somewhere in the persistent module and
|
|
// we want to exit. Try to write the log message, and also
|
|
// schedule a hard exit to ensure we do not run into a blocking
|
|
// scenario during the write.
|
|
go func() {
|
|
time.Sleep(time.Second * 5)
|
|
os.Exit(1)
|
|
}()
|
|
logmsg, err := MakeMessageLog("%v", em)
|
|
if err == nil {
|
|
WriteOutput(logmsg, out)
|
|
}
|
|
os.Exit(1)
|
|
case s := <-alertch:
|
|
almsg, err := MakeMessageAlert("%v", s)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
err = WriteOutput(almsg, out)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
case s := <-logch:
|
|
logmsg, err := MakeMessageLog("%v", s)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
err = WriteOutput(logmsg, out)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
case r := <-regch:
|
|
regmsg, err := MakeMessageRegister(r)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
err = WriteOutput(regmsg, out)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
case msg, ok := <-inChan:
|
|
if !ok {
|
|
failed = true
|
|
break
|
|
}
|
|
switch msg.Class {
|
|
case "ping":
|
|
buf, err := json.Marshal(&msg)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
err = WriteOutput(buf, out)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
case "config":
|
|
var confparam ConfigParams
|
|
buf, err := json.Marshal(msg.Parameters)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
err = json.Unmarshal(buf, &confparam)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
confch <- confparam
|
|
case "alert":
|
|
if dispatchFunc != nil {
|
|
var alparam AlertParams
|
|
buf, err := json.Marshal(msg.Parameters)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
err = json.Unmarshal(buf, &alparam)
|
|
if err != nil {
|
|
failed = true
|
|
break
|
|
}
|
|
dispatchFunc(alparam.Message)
|
|
}
|
|
}
|
|
}
|
|
|
|
if failed {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Request handler that can be called from RunPersist in a persistent module. Looks
|
|
// after accepting incoming requests to listener l, and routing the requests to the
|
|
// registered incoming request handler f.
|
|
func HandlePersistRequest(l net.Listener, f func(interface{}) string, errch chan error) {
|
|
for {
|
|
conn, err := l.Accept()
|
|
if err != nil {
|
|
errch <- err
|
|
return
|
|
}
|
|
go func() {
|
|
var p interface{}
|
|
mw := NewModuleWriter(conn)
|
|
mr := NewModuleReader(conn)
|
|
err = ReadInputParameters(mr, &p)
|
|
if err != nil {
|
|
errch <- err
|
|
return
|
|
}
|
|
resp := f(p)
|
|
WriteOutput([]byte(resp), mw)
|
|
err = conn.Close()
|
|
if err != nil {
|
|
errch <- err
|
|
return
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// Sends the parameters in p as a request to a persistent module listening at socket
|
|
// specification sockspec; would typically be used in the Run() function of a
|
|
// persistent module.
|
|
func SendPersistRequest(p interface{}, sockspec string) (res string) {
|
|
defer func() {
|
|
// If something goes wrong here we will want to format and
|
|
// return the result an a Result type, with the error
|
|
// message set.
|
|
if e := recover(); e != nil {
|
|
var r Result
|
|
r.Errors = append(r.Errors, fmt.Sprintf("%v", e))
|
|
r.Success = false
|
|
resbuf, _ := json.Marshal(&r)
|
|
res = string(resbuf)
|
|
}
|
|
}()
|
|
var (
|
|
fam string
|
|
address string
|
|
)
|
|
args := strings.Split(sockspec, ":")
|
|
if len(args) < 1 || len(args) > 3 {
|
|
panic("invalid socket specification for request")
|
|
}
|
|
switch args[0] {
|
|
case "unix":
|
|
if len(args) != 2 {
|
|
panic("invalid socket specification for unix connection")
|
|
}
|
|
fam = "unix"
|
|
address = args[1]
|
|
case "tcp":
|
|
if len(args) != 3 {
|
|
panic("invalid socket specification for tcp connection")
|
|
}
|
|
fam = "tcp"
|
|
address = strings.Join(args[1:], ":")
|
|
default:
|
|
panic("socket specification had invalid address family")
|
|
}
|
|
conn, err := net.Dial(fam, address)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
nw := NewModuleWriter(conn)
|
|
buf, err := MakeMessage(MsgClassParameters, p, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = WriteOutput(buf, nw)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
rb, err := ioutil.ReadAll(conn)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
res = string(rb)
|
|
return
|
|
}
|
|
|
|
// Get a listener for a persistent module that is appropriate for the platform type, returns
|
|
// the listener itself in addition to the socket specification that should be registered.
|
|
func GetPersistListener(modname string) (l net.Listener, specname string, err error) {
|
|
switch runtime.GOOS {
|
|
case "darwin", "linux":
|
|
sname := fmt.Sprintf("persist-%v.sock", modname)
|
|
spath := path.Join(ModuleRunDir, sname)
|
|
specname = "unix:" + spath
|
|
_ = os.Remove(spath)
|
|
l, err = net.Listen("unix", spath)
|
|
return
|
|
}
|
|
err = fmt.Errorf("persistent listener not available for this platform")
|
|
return
|
|
}
|
|
|
|
// HasResultsPrinter implements functions used by module to print information
|
|
type HasResultsPrinter interface {
|
|
PrintResults(Result, bool) ([]string, error)
|
|
}
|
|
|
|
// GetElements reads the elements from a struct of results into the el interface
|
|
func (r Result) GetElements(el interface{}) (err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("GetElements() -> %v", e)
|
|
}
|
|
}()
|
|
buf, err := json.Marshal(r.Elements)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = json.Unmarshal(buf, el)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetStatistics reads the statistics from a struct of results into the stats interface
|
|
func (r Result) GetStatistics(stats interface{}) (err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("GetStatistics() -> %v", e)
|
|
}
|
|
}()
|
|
buf, err := json.Marshal(r.Statistics)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = json.Unmarshal(buf, stats)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
type HasEnhancedPrivacy interface {
|
|
EnhancePrivacy(Result) (Result, error)
|
|
}
|
|
|
|
// HasParamsCreator implements a function that creates module parameters
|
|
type HasParamsCreator interface {
|
|
ParamsCreator() (interface{}, error)
|
|
}
|
|
|
|
// HasParamsParser implements a function that parses command line parameters
|
|
type HasParamsParser interface {
|
|
ParamsParser([]string) (interface{}, error)
|
|
}
|