зеркало из https://github.com/mozilla/mig.git
Vendor gozdef
This commit is contained in:
Родитель
0d161acb07
Коммит
685018106b
|
@ -0,0 +1,65 @@
|
|||
gozdef - Go client to send events to MozDef
|
||||
===========================================
|
||||
|
||||
http://godoc.org/github.com/jvehent/gozdef
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/jvehent/gozdef"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// initialize a publisher to mozdef's rabbitmq relay
|
||||
conf := gozdef.MqConf{
|
||||
Host: "mozdef.rabbitmq.example.net",
|
||||
Port: 5671,
|
||||
User: "gozdefclient",
|
||||
Pass: "s3cr3tpassw0rd",
|
||||
Vhost: "mozdef",
|
||||
Exchange: "eventtask",
|
||||
RoutingKey: "eventtask",
|
||||
UseTLS: true,
|
||||
CACertPath: "/etc/pki/CA/certs/ca.crt",
|
||||
Timeout: "10s",
|
||||
}
|
||||
publisher, err := gozdef.InitAmqp(conf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// create a new event and set values in the fields
|
||||
ev, err := gozdef.NewEvent()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ev.Category = "demo"
|
||||
ev.Source = "test client"
|
||||
ev.Summary = "tl;dr: everything's fine!"
|
||||
ev.Tags = append(ev.Tags, "gozdef")
|
||||
|
||||
// add details to the event, these fields are completely customizable
|
||||
ev.Details = struct {
|
||||
SrcIP string `json:"sourceipaddress"`
|
||||
DestIP string `json:"destinationipaddress"`
|
||||
Offense string `json:"offense"`
|
||||
Blocked bool
|
||||
}{
|
||||
SrcIP: "10.0.1.2",
|
||||
DestIP: "192.168.1.5",
|
||||
Offense: "brute force",
|
||||
Blocked: true,
|
||||
}
|
||||
|
||||
// set the event severity to INFO
|
||||
ev.Info()
|
||||
|
||||
// publish to mozdef
|
||||
err = publisher.Send(ev)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,106 @@
|
|||
// 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]
|
||||
|
||||
package gozdef
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Generic MozDef event handling
|
||||
|
||||
// Event represent a piece of information being passed to MozDef
|
||||
type Event struct {
|
||||
Timestamp time.Time `json:"timestamp"` // Full date plus time timestamp of the event in ISO format including the timezone offset
|
||||
Category string `json:"category"` // General category/type of event
|
||||
Hostname string `json:"hostname"` // The fully qualified domain name of the host sending the message
|
||||
ProcessID float64 `json:"processid"` // The PID of the process sending the log
|
||||
ProcessName string `json:"processname"` // The name of the process sending the log
|
||||
Severity string `json:"severity"` // RFC5424 severity level of the event in all caps: DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY
|
||||
Source string `json:"source"` // Source of the event (file name, system name, component name)
|
||||
Summary string `json:"summary"` // Short human-readable version of the event suitable for IRC, SMS, etc.
|
||||
Tags []string `json:"tags"` // An array or list of any tags you would like applied to the event
|
||||
Details interface{} `json:"details"` // Additional, event-specific fields included with the event
|
||||
}
|
||||
|
||||
// NewEvent returns a new generic event that can be populated and submitted to MozDef
|
||||
func NewEvent() (e Event, err error) {
|
||||
e.Timestamp = time.Now().UTC()
|
||||
e.Hostname, err = os.Hostname()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.ProcessID = float64(os.Getpid())
|
||||
e.ProcessName = os.Args[0]
|
||||
return
|
||||
}
|
||||
|
||||
const severityRegex string = "^(DEBUG|INFO|NOTICE|WARNING|ERROR|CRITICAL|ALERT|EMERGENCY)$"
|
||||
|
||||
// Debug sets the severity level of the event to DEBUG
|
||||
func (e *Event) Debug() {
|
||||
e.Severity = "DEBUG"
|
||||
}
|
||||
|
||||
// Info sets the severity level of the event to INFO
|
||||
func (e *Event) Info() {
|
||||
e.Severity = "INFO"
|
||||
}
|
||||
|
||||
// Notice sets the severity level of the event to NOTICE
|
||||
func (e *Event) Notice() {
|
||||
e.Severity = "NOTICE"
|
||||
}
|
||||
|
||||
// Warning sets the severity level of the event to WARNING
|
||||
func (e *Event) Warning() {
|
||||
e.Severity = "WARNING"
|
||||
}
|
||||
|
||||
// Error sets the severity level of the event to ERROR
|
||||
func (e *Event) Error() {
|
||||
e.Severity = "ERROR"
|
||||
}
|
||||
|
||||
// Critical sets the severity level of the event to CRITICAL
|
||||
func (e *Event) Critical() {
|
||||
e.Severity = "CRITICAL"
|
||||
}
|
||||
|
||||
// Alert sets the severity level of the event to ALERT
|
||||
func (e *Event) Alert() {
|
||||
e.Severity = "ALERT"
|
||||
}
|
||||
|
||||
// Emergency sets the severity level of the event to EMERGENCY
|
||||
func (e *Event) Emergency() {
|
||||
e.Severity = "EMERGENCY"
|
||||
}
|
||||
|
||||
// Validate verifies that an event is formatted correctly
|
||||
func (e Event) Validate() error {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hostname != e.Hostname {
|
||||
return fmt.Errorf("event hostname does not match the host's fqdn")
|
||||
}
|
||||
if float64(os.Getpid()) != e.ProcessID {
|
||||
return fmt.Errorf("event processid does not match the id of the current process")
|
||||
}
|
||||
if os.Args[0] != e.ProcessName {
|
||||
return fmt.Errorf("event processname does not match the name of the current process")
|
||||
}
|
||||
resev := regexp.MustCompile(severityRegex)
|
||||
if !resev.MatchString(e.Severity) {
|
||||
return fmt.Errorf("invalid severity '%s', must be one of %s", e.Severity, severityRegex)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
// gozdef is a client for MozDef's AMQP and Rest endpoints. It formats
|
||||
// messages into MozDef's standard event format and publishes them.
|
||||
//
|
||||
// Reference: http://mozdef.readthedocs.org/en/latest/usage.html#json-format
|
||||
//
|
||||
// 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]
|
||||
|
||||
// Package gozdef provides an interface for submitting events to MozDef in a
|
||||
// standardized format.
|
||||
package gozdef
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/streadway/amqp"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Publisher sends events to MozDef, either via AMQP (if initialized
|
||||
// with InitAmqp()) or via rest API (if initialized via InitAPI())
|
||||
type Publisher struct {
|
||||
useAmqp bool // selects the sending mode, if set to false use rest api instead of amqp
|
||||
amqpChan *amqp.Channel // channel handler
|
||||
mqconf MqConf // rabbitmq configuration the publisher was initialized with
|
||||
apiClient *http.Client // http client handler
|
||||
apiconf APIConf // api configuration the publisher was initialized with
|
||||
}
|
||||
|
||||
// Send submits an event indicated by ExternalEvent e to the initialized publisher p
|
||||
func (p Publisher) Send(e ExternalEvent) error {
|
||||
err := e.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If using AMQP, publish the event on the configured queue
|
||||
if p.useAmqp {
|
||||
msg := amqp.Publishing{
|
||||
DeliveryMode: amqp.Persistent,
|
||||
Timestamp: time.Now(),
|
||||
ContentType: "text/plain",
|
||||
Body: data,
|
||||
}
|
||||
return p.amqpChan.Publish(p.mqconf.Exchange, p.mqconf.RoutingKey, false, false, msg)
|
||||
}
|
||||
// Otherwise, we will be sending the event to the REST API
|
||||
b := bytes.NewBufferString(string(data))
|
||||
resp, err := p.apiClient.Post(p.apiconf.URL, "application/json", b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// MqConf holds the configuration parameters to connect to a rabbitmq instance
|
||||
type MqConf struct {
|
||||
Host string // hostname of the rabbitmq host
|
||||
Port int // port of the rabbitmq host
|
||||
User string // username to authenticate on rabbitmq
|
||||
Pass string // password to authenticate on rabbitmq
|
||||
Vhost string // the virtual host to connect to
|
||||
Exchange string // the amqp exchange to publish to
|
||||
RoutingKey string // the amqp routing key events should be published with
|
||||
UseTLS bool // if set, establish the connection using AMQPS
|
||||
ClientCertPath string // (optional) file system path to a client certificate
|
||||
ClientKeyPath string // (optional) file system path to a client private key
|
||||
CACertPath string // file system path to the Root CA cert
|
||||
Timeout string // connection timeout
|
||||
}
|
||||
|
||||
// InitAmqp establishes a connection to the rabbitmq endpoint defined in the configuration
|
||||
func InitAmqp(conf MqConf) (p Publisher, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("InitAmqp failed with error: %v", e)
|
||||
}
|
||||
}()
|
||||
var scheme, user, pass, host, port, vhost string
|
||||
if conf.UseTLS {
|
||||
scheme = "amqps"
|
||||
} else {
|
||||
scheme = "amqp"
|
||||
}
|
||||
if conf.User == "" {
|
||||
panic("MQ User is missing")
|
||||
}
|
||||
user = conf.User
|
||||
if conf.Pass == "" {
|
||||
panic("MQ Pass is missing")
|
||||
}
|
||||
pass = conf.Pass
|
||||
if conf.Host == "" {
|
||||
panic("MQ Host is missing")
|
||||
}
|
||||
host = conf.Host
|
||||
if conf.Port < 1 {
|
||||
panic("MQ Port is missing")
|
||||
}
|
||||
port = fmt.Sprintf("%d", conf.Port)
|
||||
vhost = conf.Vhost
|
||||
dialaddr := scheme + "://" + user + ":" + pass + "@" + host + ":" + port + "/" + vhost
|
||||
|
||||
timeout, _ := time.ParseDuration(conf.Timeout)
|
||||
var dialConfig amqp.Config
|
||||
dialConfig.Heartbeat = timeout
|
||||
dialConfig.Dial = func(network, addr string) (net.Conn, error) {
|
||||
return net.DialTimeout(network, addr, timeout)
|
||||
}
|
||||
if conf.UseTLS {
|
||||
// import the ca cert
|
||||
data, err := ioutil.ReadFile(conf.CACertPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ca := x509.NewCertPool()
|
||||
if ok := ca.AppendCertsFromPEM(data); !ok {
|
||||
panic("failed to import CA Certificate")
|
||||
}
|
||||
TLSconfig := tls.Config{
|
||||
RootCAs: ca,
|
||||
InsecureSkipVerify: false,
|
||||
Rand: rand.Reader,
|
||||
}
|
||||
dialConfig.TLSClientConfig = &TLSconfig
|
||||
if conf.ClientCertPath != "" && conf.ClientKeyPath != "" {
|
||||
// import the client certificates
|
||||
cert, err := tls.LoadX509KeyPair(conf.ClientCertPath, conf.ClientKeyPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
TLSconfig.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
}
|
||||
// Setup the AMQP broker connection
|
||||
amqpConn, err := amqp.DialConfig(dialaddr, dialConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.amqpChan, err = amqpConn.Channel()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.useAmqp = true
|
||||
p.mqconf = conf
|
||||
return
|
||||
}
|
||||
|
||||
// APIConf holds the configuration parameters to publish events to the REST API
|
||||
type APIConf struct {
|
||||
URL string // a fully qualified URL where events are posted
|
||||
UseProxy bool
|
||||
}
|
||||
|
||||
// InitAPI initializes a new Publisher that can be used to submit events to the
|
||||
// REST API
|
||||
func InitAPI(conf APIConf) (p Publisher, err error) {
|
||||
if conf.URL == "" {
|
||||
return p, fmt.Errorf("must set URL value in APIConf")
|
||||
}
|
||||
if conf.UseProxy {
|
||||
p.apiClient = &http.Client{}
|
||||
} else {
|
||||
p.apiClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
p.useAmqp = false
|
||||
p.apiconf = conf
|
||||
return p, nil
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// 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]
|
||||
|
||||
package gozdef
|
||||
|
||||
// ExternalEvent provides a generalized interface that all event types
|
||||
// must provide
|
||||
type ExternalEvent interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// ComplianceItem measures the compliance of a target
|
||||
// with particular requirement. The item must be send to mozdef
|
||||
// in the details of a regular Event.
|
||||
type ComplianceItem struct {
|
||||
Utctimestamp string `json:"utctimestamp"`
|
||||
Target string `json:"target"`
|
||||
Compliance bool `json:"compliance"`
|
||||
Link string `json:"link"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
|
||||
Policy struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Level string `json:"level"`
|
||||
} `json:"policy"`
|
||||
|
||||
Check struct {
|
||||
Ref string `json:"ref"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
Location string `json:"location"`
|
||||
|
||||
Test struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
} `json:"test"`
|
||||
} `json:"check"`
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// 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 gozdef
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MozDef vulnerability event handling
|
||||
|
||||
// VulnEvent describes a vulnerability event
|
||||
type VulnEvent struct {
|
||||
Description string `json:"description"`
|
||||
UTCTimestamp time.Time `json:"utctimestamp"`
|
||||
SourceName string `json:"sourcename"`
|
||||
CredentialedChecks bool `json:"credentialed_checks"`
|
||||
Vuln []VulnVuln `json:"vulnerabilities"`
|
||||
ExemptVuln []VulnVuln `json:"exempt_vulnerabilities"`
|
||||
Version int `json:"version"`
|
||||
Zone string `json:"zone"`
|
||||
|
||||
Asset struct {
|
||||
IPAddress string `json:"ipaddress"`
|
||||
Hostname string `json:"hostname"`
|
||||
OS string `json:"os"`
|
||||
Owner struct {
|
||||
Operator string `json:"operator"`
|
||||
Team string `json:"team"`
|
||||
V2Bkey string `json:"v2bkey"`
|
||||
} `json:"owner"`
|
||||
} `json:"asset"`
|
||||
}
|
||||
|
||||
// VulnVuln describes individual vulnerabilities for inclusion in a vulnerability
|
||||
// event
|
||||
type VulnVuln struct {
|
||||
Risk string `json:"risk"`
|
||||
Link string `json:"link"`
|
||||
CVE string `json:"cve"`
|
||||
CVSS string `json:"cvss"`
|
||||
Name string `json:"name"`
|
||||
Packages []string `json:"vulnerable_packages"`
|
||||
LikelihoodIndicator string `json:"likelihood_indicator"`
|
||||
}
|
||||
|
||||
// NewVulnEvent initializes a new VulnEvent that can be populated and submitted
|
||||
// to MozDef
|
||||
func NewVulnEvent() (e VulnEvent, err error) {
|
||||
e.UTCTimestamp = time.Now().UTC()
|
||||
e.Version = 2
|
||||
return
|
||||
}
|
||||
|
||||
// Validate verifies that an event is formatted correctly
|
||||
func (e VulnEvent) Validate() error {
|
||||
if e.SourceName == "" {
|
||||
return fmt.Errorf("must set SourceName in event")
|
||||
}
|
||||
if e.Asset.IPAddress == "" && e.Asset.Hostname == "" {
|
||||
return fmt.Errorf("must set IPAddress or Hostname in event")
|
||||
}
|
||||
return nil
|
||||
}
|
Загрузка…
Ссылка в новой задаче