Skip to content

Commit

Permalink
Add file output support
Browse files Browse the repository at this point in the history
  • Loading branch information
nbrownus committed Nov 30, 2016
1 parent db01785 commit 7628c4d
Show file tree
Hide file tree
Showing 3 changed files with 317 additions and 19 deletions.
109 changes: 99 additions & 10 deletions audit.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
//There is one rule here. "thou shall not block"
//Slack Technologies, Inc 2015
//Ryan Huber
package main

import (
"errors"
"flag"
"fmt"
"github.com/pkg/profile"
"github.com/spf13/viper"
"log"
"log/syslog"
"os"
"os/exec"
"os/user"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -65,14 +65,44 @@ func setRules(config *viper.Viper) {
}
}

func createOutput(config *viper.Viper) *AuditWriter {
if config.GetBool("output.syslog.enabled") == false {
el.Fatalln("No outputs have been enabled")
func createOutput(config *viper.Viper) (*AuditWriter, error) {
var writer *AuditWriter
var err error
i := 0

if config.GetBool("output.syslog.enabled") == true {
i++
writer, err = createSyslogOutput(config)
if err != nil {
return nil, err
}
}

if config.GetBool("output.file.enabled") == true {
i++
writer, err = createFileOutput(config)
if err != nil {
return nil, err
}
}

if i > 1 {
return nil, errors.New("Only one output can be enabled at a time")
}

if writer == nil {
return nil, errors.New("No outputs were configured")
}

return writer, nil
}

func createSyslogOutput(config *viper.Viper) (*AuditWriter, error) {
attempts := config.GetInt("output.syslog.attempts")
if attempts < 1 {
el.Fatalln("output attempts for syslog must be at least 1", attempts, "provided")
return nil, errors.New(
fmt.Sprintf("Output attempts for syslog must be at least 1, %v provided", attempts),
)
}

syslogWriter, err := syslog.Dial(
Expand All @@ -83,10 +113,65 @@ func createOutput(config *viper.Viper) *AuditWriter {
)

if err != nil {
el.Fatalln("Failed to open syslog writer. Error:", err)
return nil, errors.New(fmt.Sprintf("Failed to open syslog writer. Error: %v", err))
}

return NewAuditWriter(syslogWriter, attempts)
return NewAuditWriter(syslogWriter, attempts), nil
}

func createFileOutput(config *viper.Viper) (*AuditWriter, error) {
attempts := config.GetInt("output.file.attempts")
if attempts < 1 {
return nil, errors.New(
fmt.Sprintf("Output attempts for file must be at least 1, %v provided", attempts),
)
}

mode := os.FileMode(config.GetInt("output.file.mode"))
if mode < 1 {
return nil, errors.New("Output file mode should be greater than 0000")
}

f, err := os.OpenFile(
config.GetString("output.file.path"),
os.O_APPEND|os.O_CREATE|os.O_WRONLY, mode,
)

if err != nil {
return nil, errors.New(fmt.Sprintf("Failed to open output file. Error: %s", err))
}

if err := f.Chmod(mode); err != nil {
return nil, errors.New(fmt.Sprintf("Failed to set file permissions. Error: %s", err))
}

uname := config.GetString("output.file.user")
u, err := user.Lookup(uname)
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not find uid for user %s. Error: %s", uname, err))
}

gname := config.GetString("output.file.group")
g, err := user.LookupGroup(gname)
if err != nil {
return nil, errors.New(fmt.Sprintf("Could not find gid for group %s. Error: %s", gname, err))
}

uid, err := strconv.ParseInt(u.Uid, 10, 32)
if err != nil {
return nil, errors.New(fmt.Sprintf("Found uid could not be parsed. Error: %s", err))
}

gid, err := strconv.ParseInt(g.Gid, 10, 32)
if err != nil {
return nil, errors.New(fmt.Sprintf("Found gid could not be parsed. Error: %s", err))
}

if err = f.Chown(int(uid), int(gid)); err != nil {
return nil, errors.New(fmt.Sprintf("Could not chown output file. Error: %s", err))
}

return NewAuditWriter(f, attempts), nil
}

func createFilters(config *viper.Viper) []AuditFilter {
Expand Down Expand Up @@ -186,7 +271,11 @@ func main() {
defer profile.Start(profile.Quiet, profile.ProfilePath(".")).Stop()
}

writer := createOutput(config)
writer, err := createOutput(config)
if err != nil {
el.Fatal(err)
}

nlClient := NewNetlinkClient(config.GetInt("socket_buffer.receive"))
marshaller := NewAuditMarshaller(
writer,
Expand Down
204 changes: 196 additions & 8 deletions audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import (
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"io/ioutil"
"log/syslog"
"net"
"os"
"os/user"
"path"
"strconv"
"syscall"
"testing"
)
Expand Down Expand Up @@ -49,16 +54,199 @@ func Test_setRules(t *testing.T) {
t.Skip("Not implemented")
}

func Test_createOutput(t *testing.T) {
//TODO: Test all config settings are used
//TODO: Test failure to connect to syslog
//TODO: Test fatal if syslog is not enabled
t.Skip("Not implemented")
func Test_createFileOutput(t *testing.T) {
// attempts error
c := viper.New()
c.Set("output.file.attempts", 0)
w, err := createFileOutput(c)
assert.EqualError(t, err, "Output attempts for file must be at least 1, 0 provided")
assert.Nil(t, w)

// failure to create/open file
c = viper.New()
c.Set("output.file.attempts", 1)
c.Set("output.file.path", "/do/not/exist/please")
c.Set("output.file.mode", 0644)
w, err = createFileOutput(c)
assert.EqualError(t, err, "Failed to open output file. Error: open /do/not/exist/please: no such file or directory")
assert.Nil(t, w)

// chmod error
c = viper.New()
c.Set("output.file.attempts", 1)
c.Set("output.file.path", path.Join(os.TempDir(), "go-audit.test.log"))
w, err = createFileOutput(c)
assert.EqualError(t, err, "Output file mode should be greater than 0000")
assert.Nil(t, w)

// uid error
c = viper.New()
c.Set("output.file.attempts", 1)
c.Set("output.file.path", path.Join(os.TempDir(), "go-audit.test.log"))
c.Set("output.file.mode", 0644)
w, err = createFileOutput(c)
assert.EqualError(t, err, "Could not find uid for user . Error: user: unknown user ")
assert.Nil(t, w)

uid := os.Getuid()
gid := os.Getgid()
u, _ := user.LookupId(strconv.Itoa(uid))
g, _ := user.LookupGroupId(strconv.Itoa(gid))

// travis-ci is silly
if u.Name == "" {
u.Name = g.Name
}

// gid error
c = viper.New()
c.Set("output.file.attempts", 1)
c.Set("output.file.path", path.Join(os.TempDir(), "go-audit.test.log"))
c.Set("output.file.mode", 0644)
c.Set("output.file.user", u.Name)
w, err = createFileOutput(c)
assert.EqualError(t, err, "Could not find gid for group . Error: group: unknown group ")
assert.Nil(t, w)

// chown error
c = viper.New()
c.Set("output.file.attempts", 1)
c.Set("output.file.path", path.Join(os.TempDir(), "go-audit.test.log"))
c.Set("output.file.mode", 0644)
c.Set("output.file.user", "root")
c.Set("output.file.group", "root")
w, err = createFileOutput(c)
assert.EqualError(t, err, "Could not chown output file. Error: chown /tmp/go-audit.test.log: operation not permitted")
assert.Nil(t, w)

// All good
c = viper.New()
c.Set("output.file.attempts", 1)
c.Set("output.file.path", path.Join(os.TempDir(), "go-audit.test.log"))
c.Set("output.file.mode", 0644)
c.Set("output.file.user", u.Name)
c.Set("output.file.group", g.Name)
w, err = createFileOutput(c)
assert.Nil(t, err)
assert.NotNil(t, w)
assert.IsType(t, &os.File{}, w.w)
}

func Test_main(t *testing.T) {
//TODO: This one will be tricky in its current format
t.Skip("Not implemented")
func Test_createSyslogOutput(t *testing.T) {
// attempts error
c := viper.New()
c.Set("output.syslog.attempts", 0)
w, err := createSyslogOutput(c)
assert.EqualError(t, err, "Output attempts for syslog must be at least 1, 0 provided")
assert.Nil(t, w)

// dial error
c = viper.New()
c.Set("output.syslog.attempts", 1)
c.Set("output.syslog.priority", -1)
w, err = createSyslogOutput(c)
assert.EqualError(t, err, "Failed to open syslog writer. Error: log/syslog: invalid priority")
assert.Nil(t, w)

// All good
l, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatal(err)
}

defer l.Close()

c = viper.New()
c.Set("output.syslog.attempts", 1)
c.Set("output.syslog.network", "tcp")
c.Set("output.syslog.address", l.Addr().String())
w, err = createSyslogOutput(c)
assert.Nil(t, err)
assert.NotNil(t, w)
assert.IsType(t, &syslog.Writer{}, w.w)
}

func Test_createOutput(t *testing.T) {
// no outputs
c := viper.New()
w, err := createOutput(c)
assert.EqualError(t, err, "No outputs were configured")
assert.Nil(t, w)

// multiple outputs
uid := os.Getuid()
gid := os.Getgid()
u, _ := user.LookupId(strconv.Itoa(uid))
g, _ := user.LookupGroupId(strconv.Itoa(gid))

// travis-ci is silly
if u.Name == "" {
u.Name = g.Name
}

l, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatal(err)
}

defer l.Close()

c = viper.New()
c.Set("output.syslog.enabled", true)
c.Set("output.syslog.attempts", 1)
c.Set("output.syslog.network", "tcp")
c.Set("output.syslog.address", l.Addr().String())

c.Set("output.file.enabled", true)
c.Set("output.file.attempts", 1)
c.Set("output.file.path", path.Join(os.TempDir(), "go-audit.test.log"))
c.Set("output.file.mode", 0644)
c.Set("output.file.user", u.Name)
c.Set("output.file.group", g.Name)

w, err = createOutput(c)
assert.EqualError(t, err, "Only one output can be enabled at a time")
assert.Nil(t, w)

// syslog error
c = viper.New()
c.Set("output.syslog.enabled", true)
c.Set("output.syslog.attempts", 0)
w, err = createOutput(c)
assert.EqualError(t, err, "Output attempts for syslog must be at least 1, 0 provided")
assert.Nil(t, w)

// file error
c = viper.New()
c.Set("output.file.enabled", true)
c.Set("output.file.attempts", 0)
w, err = createOutput(c)
assert.EqualError(t, err, "Output attempts for file must be at least 1, 0 provided")
assert.Nil(t, w)

// All good syslog
c = viper.New()
c.Set("output.syslog.attempts", 1)
c.Set("output.syslog.network", "tcp")
c.Set("output.syslog.address", l.Addr().String())
w, err = createSyslogOutput(c)
assert.Nil(t, err)
assert.NotNil(t, w)
assert.IsType(t, &syslog.Writer{}, w.w)

// All good file
c = viper.New()
c.Set("output.file.enabled", true)
c.Set("output.file.attempts", 1)
c.Set("output.file.path", path.Join(os.TempDir(), "go-audit.test.log"))
c.Set("output.file.mode", 0644)
c.Set("output.file.user", u.Name)
c.Set("output.file.group", g.Name)
w, err = createOutput(c)
assert.Nil(t, err)
assert.NotNil(t, w)
assert.IsType(t, &AuditWriter{}, w)
assert.IsType(t, &os.File{}, w.w)
}

func Benchmark_MultiPacketMessage(b *testing.B) {
Expand Down
Loading

0 comments on commit 7628c4d

Please sign in to comment.