2018-08-10 01:22:19 +03:00
package main
import (
"flag"
"fmt"
"os"
"strings"
"text/template"
)
2018-08-10 17:49:06 +03:00
const configTemplate string = ` [ agent ]
; connection string to the MIG relay . must contain credentials .
relay = "{{.RelayAddress}}"
; location of the mig api
api = "{{.APIURL}}"
proxies = "{{.Proxies}}"
; location of the local stat socket
socket = "127.0.0.1:{{.StatSocketPort}}"
; frequency at which heartbeat messages are sent to the MIG relay
heartbeatfreq = "120s"
; timeout after which a module that has not finished is killed by the agent
moduletimeout = "300s"
; in immortal mode , the agent that encounter a fatal error
; will attempt to restart itself instead of just shutting down
isimmortal = { { . Immortal } }
; installservice orders the agent to deploy a service init configuration
; and start itself during the endpoint ' s boot process
installservice = { { . InstallAsService } }
; attempt to retrieve the public IP behind which the agent is running
discoverpublicip = on
; in check - in mode , the agent connects to the relay , runs all pending commands
; and exits . this mode is used to run the agent as a cron job , not a daemon .
checkin = { { . CronMode } }
refreshenv = "5m"
; enable privacy mode
extraprivacymode = { { . ExtraPrivacy } }
2018-08-15 23:57:11 +03:00
; Tags help investigators to target specific agents for queries .
2018-08-16 00:00:52 +03:00
; Multiple tags can be supplied as demonstrated below and the tag name and value are separated by a colon .
2018-08-15 23:57:11 +03:00
; tags = "operator:example"
; tags = "exampleTag:other" { { range $ tag , $ value := . Tags } }
tags = "{{ $tag }}:{{ $value }}" { { end } }
2018-08-10 17:49:06 +03:00
[ stats ]
maxactions = 15
[ certs ]
ca = "/etc/mig/ca.crt"
cert = "/etc/mig/agent.crt"
key = "/etc/mig/agent.key"
[ logging ]
mode = "file" ; stdout | file | syslog
level = "debug"
file = "/var/log/mig-agent.log"
maxfilesize = 10485760
`
2018-08-10 01:22:19 +03:00
// Defaults
const (
defaultAPIURL = "https://api.mig.mozilla.org/api/v1/"
defaultStatSocketPort = 51664
defaultImmortal = true
defaultInstallAsService = true
defaultCronMode = false
defaultExtraPrivacy = true
)
type proxyList [ ] string
2018-08-15 23:57:11 +03:00
type tagList [ ] string
2018-08-10 01:22:19 +03:00
// Config contains user-supplied configurable values that will be translated
// and injected into `release/mig-agent.cfg.template` to produce a valid
// `mig-agent.cfg`.
type Config struct {
RelayAddress string
APIURL string
Proxies proxyList
2018-08-22 23:04:03 +03:00
Tags tagList
2018-08-10 01:22:19 +03:00
StatSocketPort uint
Immortal bool
InstallAsService bool
CronMode bool
ExtraPrivacy bool
}
// translatedConfig contains the same data as `Config` with some
// transformations to make the output of this program consistent
// with what mig agents expect.
type translatedConfig struct {
RelayAddress string
APIURL string
Proxies string
2018-08-22 23:04:03 +03:00
Tags map [ string ] string
2018-08-10 01:22:19 +03:00
StatSocketPort uint16
Immortal string
InstallAsService string
CronMode string
ExtraPrivacy string
}
2018-08-16 18:22:42 +03:00
// onOrOff converts a bool from `true` to `"on"` and `false` to `"off"`.
2018-08-10 01:22:19 +03:00
func onOrOff ( on bool ) string {
if on {
return "on"
} else {
return "off"
}
}
2018-08-15 23:57:11 +03:00
// kvPairsToMap converts strings formatted like `key=value` to a map.
func kvPairsToMap ( kvs [ ] string ) ( map [ string ] string , error ) {
2018-08-22 23:04:03 +03:00
mapping := map [ string ] string { }
for _ , pair := range kvs {
parts := strings . Split ( pair , "=" )
if len ( parts ) != 2 {
return map [ string ] string { } , fmt . Errorf ( "encountered invalidly formatted tag \"%s\". Expected format key=value" )
}
mapping [ parts [ 0 ] ] = parts [ 1 ]
}
return mapping , nil
2018-08-15 23:57:11 +03:00
}
2018-08-16 18:22:42 +03:00
// translate converts a `Config` into a `translatedConfig`.
2018-08-15 23:57:11 +03:00
func translate ( config Config ) ( translatedConfig , error ) {
2018-08-22 23:04:03 +03:00
tags , err := kvPairsToMap ( config . Tags )
if err != nil {
return translatedConfig { } , err
}
2018-08-15 23:57:11 +03:00
2018-08-22 23:04:03 +03:00
tx := translatedConfig {
2018-08-10 01:22:19 +03:00
RelayAddress : config . RelayAddress ,
APIURL : config . APIURL ,
Proxies : strings . Join ( [ ] string ( config . Proxies ) , "," ) ,
2018-08-22 23:04:03 +03:00
Tags : tags ,
2018-08-10 01:22:19 +03:00
StatSocketPort : uint16 ( config . StatSocketPort ) ,
Immortal : onOrOff ( config . Immortal ) ,
InstallAsService : onOrOff ( config . InstallAsService ) ,
CronMode : onOrOff ( config . CronMode ) ,
ExtraPrivacy : onOrOff ( config . ExtraPrivacy ) ,
}
2018-08-15 23:57:11 +03:00
2018-08-22 23:04:03 +03:00
return tx , nil
2018-08-10 01:22:19 +03:00
}
2018-08-16 18:22:42 +03:00
// usage prints a usage string giving a more detailed explanation about how
// to use this tool.
func usage ( ) {
2018-08-22 23:04:03 +03:00
fmt . Fprintf ( os . Stderr , "This program is used to generate a mig-agent.cfg file, which it prints to stdout.\n\n" )
fmt . Fprintf ( os . Stderr , "The generated configuration should be written to:\n" )
fmt . Fprintf ( os . Stderr , "\t/etc/mig/mig-agent.cfg on Linux and macOS.\n" )
fmt . Fprintf ( os . Stderr , "\tC:\\mig\\mig-agent.cfg on Windows.\n\n" )
fmt . Fprintf ( os . Stderr , "The -relay argument is required.\n\n" )
flag . PrintDefaults ( )
2018-08-16 18:22:42 +03:00
}
2018-08-10 01:22:19 +03:00
// IsValid verifies that values that must be supplied for a configuration have
// been supplied and that those whose values cannot be guaranteed to be
// correct by the type system and flag API are valid.
func ( config Config ) IsValid ( ) bool {
return config . RelayAddress != "" && config . StatSocketPort <= 65535
}
// String is required by the `flag.Value` interface.
func ( proxies * proxyList ) String ( ) string {
return strings . Join ( [ ] string ( * proxies ) , "," )
}
// Set is required by the `flag.Value` interface. We use it to enable users to
// supply multiple proxy addresses, which we collect into an array.
func ( proxies * proxyList ) Set ( value string ) error {
* proxies = append ( * proxies , value )
return nil
}
2018-08-15 23:57:11 +03:00
// String is required by the `flag.Value` interface.
func ( tags * tagList ) String ( ) string {
2018-08-22 23:04:03 +03:00
return strings . Join ( [ ] string ( * tags ) , "," )
2018-08-15 23:57:11 +03:00
}
// Set is required by the `flag.Value` interface. We use it to enable users to
// supply multiple tag key=value pairs, which we collect into an array.
func ( tags * tagList ) Set ( kvPair string ) error {
2018-08-22 23:04:03 +03:00
if ! strings . Contains ( kvPair , "=" ) {
return fmt . Errorf ( "expected tag argument to be formatted key=value" )
}
* tags = append ( * tags , kvPair )
return nil
2018-08-15 23:57:11 +03:00
}
2018-08-10 01:22:19 +03:00
func main ( ) {
2018-08-22 23:04:03 +03:00
flag . Usage = usage
2018-08-16 18:22:42 +03:00
2018-08-10 01:22:19 +03:00
relayAddr := flag . String (
"relay" ,
"" ,
"Connection string to the MIG relay. Must contain credentials" )
apiUrl := flag . String (
"api" ,
defaultAPIURL ,
"URL of the MIG API" )
var proxies proxyList
flag . Var (
& proxies ,
"proxy" ,
"Address of a proxy to use. Multiple -proxy arguments are accepted" )
2018-08-22 23:04:03 +03:00
var tags tagList
flag . Var (
& tags ,
"tag" ,
"A tagName=tagValue pair to tag the agent with. Multuple -tag arguments are accepted" )
statPort := flag . Uint (
2018-08-10 01:22:19 +03:00
"statport" ,
defaultStatSocketPort ,
"Port to bind the agent's stat socket to on localhost" )
immortal := flag . Bool (
"immortal" ,
defaultImmortal ,
"Instruct the agent to automatically recover from fatal errors" )
service := flag . Bool (
"service" ,
defaultInstallAsService ,
"Instruct the agent to install itself as a service" )
cronMode := flag . Bool (
"cron" ,
defaultCronMode ,
"Instruct the agent to run all queued actions before terminating instead of running as a daemon" )
extraPrivacy := flag . Bool (
"extraprivate" ,
defaultExtraPrivacy ,
"Instruct the agent to run with extra privacy controls" )
flag . Parse ( )
2018-08-10 17:49:06 +03:00
template := template . Must ( template . New ( "mig-agent.cfg" ) . Parse ( configTemplate ) )
2018-08-10 02:10:26 +03:00
2018-08-10 01:22:19 +03:00
config := Config {
RelayAddress : * relayAddr ,
APIURL : * apiUrl ,
Proxies : proxies ,
2018-08-22 23:04:03 +03:00
Tags : tags ,
2018-08-10 01:22:19 +03:00
StatSocketPort : * statPort ,
Immortal : * immortal ,
InstallAsService : * service ,
CronMode : * cronMode ,
ExtraPrivacy : * extraPrivacy ,
}
if ! config . IsValid ( ) {
fmt . Fprintf ( os . Stderr , "Invalid configuration data supplied. Check that the -relay argument is present and that the -statport argument is a valid port number.\n" )
os . Exit ( 1 )
}
2018-08-15 23:57:11 +03:00
translated , err := translate ( config )
2018-08-22 23:04:03 +03:00
if err != nil {
fmt . Fprintf ( os . Stderr , "Error creating configuration: %s\n" , err . Error ( ) )
os . Exit ( 1 )
}
2018-08-15 23:57:11 +03:00
err = template . Execute ( os . Stdout , translated )
2018-08-10 01:22:19 +03:00
if err != nil {
fmt . Fprintf ( os . Stderr , "Error filling configuration template: %s\n" , err . Error ( ) )
os . Exit ( 1 )
}
}