github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/auditlog/auditlog_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package auditlog_test
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/juju/clock/testclock"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/core/auditlog"
    17  	"github.com/juju/juju/core/paths"
    18  )
    19  
    20  type AuditLogSuite struct {
    21  	testing.IsolationSuite
    22  }
    23  
    24  var _ = gc.Suite(&AuditLogSuite{})
    25  
    26  func (s *AuditLogSuite) TestAuditLogFile(c *gc.C) {
    27  	dir := c.MkDir()
    28  	logFile := auditlog.NewLogFile(dir, 300, 10)
    29  	err := logFile.AddConversation(auditlog.Conversation{
    30  		Who:            "deerhoof",
    31  		What:           "gojira",
    32  		When:           "2017-11-27T13:21:24Z",
    33  		ModelName:      "admin/default",
    34  		ConversationID: "0123456789abcdef",
    35  		ConnectionID:   "AC1",
    36  	})
    37  	c.Assert(err, jc.ErrorIsNil)
    38  	err = logFile.AddRequest(auditlog.Request{
    39  		ConversationID: "0123456789abcdef",
    40  		ConnectionID:   "AC1",
    41  		RequestID:      25,
    42  		When:           "2017-12-12T11:34:56Z",
    43  		Facade:         "Application",
    44  		Method:         "Deploy",
    45  		Version:        4,
    46  		Args:           `{"applications": [{"application": "prometheus"}]}`,
    47  	})
    48  	c.Assert(err, jc.ErrorIsNil)
    49  	err = logFile.AddResponse(auditlog.ResponseErrors{
    50  		ConversationID: "0123456789abcdef",
    51  		ConnectionID:   "AC1",
    52  		RequestID:      25,
    53  		When:           "2017-12-12T11:35:11Z",
    54  		Errors: []*auditlog.Error{
    55  			{Message: "oops", Code: "unauthorized access"},
    56  		},
    57  	})
    58  	c.Assert(err, jc.ErrorIsNil)
    59  	err = logFile.Close()
    60  	c.Assert(err, jc.ErrorIsNil)
    61  
    62  	bytes, err := os.ReadFile(filepath.Join(dir, "audit.log"))
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	c.Assert(string(bytes), gc.Equals, expectedLogContents)
    65  }
    66  
    67  func (s *AuditLogSuite) TestAuditLogFilePriming(c *gc.C) {
    68  	dir := c.MkDir()
    69  	logFile := auditlog.NewLogFile(dir, 300, 10)
    70  	err := logFile.Close()
    71  	c.Assert(err, jc.ErrorIsNil)
    72  
    73  	info, err := os.Stat(filepath.Join(dir, "audit.log"))
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	c.Assert(info.Mode(), gc.Equals, paths.LogfilePermission)
    76  	// The chown will only work when run as root.
    77  }
    78  
    79  func (s *AuditLogSuite) TestRecorder(c *gc.C) {
    80  	var log fakeLog
    81  	logTime, err := time.Parse(time.RFC3339, "2017-11-27T15:45:23Z")
    82  	c.Assert(err, jc.ErrorIsNil)
    83  	clock := testclock.NewClock(logTime)
    84  	rec, err := auditlog.NewRecorder(&log, clock, auditlog.ConversationArgs{
    85  		Who:          "wildbirds and peacedrums",
    86  		What:         "Doubt/Hope",
    87  		ModelName:    "admin/default",
    88  		ConnectionID: 687,
    89  	})
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	clock.Advance(time.Second)
    92  	err = rec.AddRequest(auditlog.RequestArgs{
    93  		RequestID: 246,
    94  		Facade:    "Death Vessel",
    95  		Method:    "Horchata",
    96  		Version:   5,
    97  		Args:      `{"a": "something"}`,
    98  	})
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	clock.Advance(time.Second)
   101  	err = rec.AddResponse(auditlog.ResponseErrorsArgs{
   102  		RequestID: 246,
   103  		Errors: []*auditlog.Error{{
   104  			Message: "something bad",
   105  			Code:    "bad request",
   106  		}},
   107  	})
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	log.stub.CheckCallNames(c, "AddConversation", "AddRequest", "AddResponse")
   111  	calls := log.stub.Calls()
   112  	rec0 := calls[0].Args[0].(auditlog.Conversation)
   113  	callID := rec0.ConversationID
   114  	c.Assert(rec0, gc.DeepEquals, auditlog.Conversation{
   115  		Who:            "wildbirds and peacedrums",
   116  		What:           "Doubt/Hope",
   117  		When:           "2017-11-27T15:45:23Z",
   118  		ModelName:      "admin/default",
   119  		ConnectionID:   "2AF",
   120  		ConversationID: callID,
   121  	})
   122  	c.Assert(calls[1].Args[0], gc.DeepEquals, auditlog.Request{
   123  		ConversationID: callID,
   124  		ConnectionID:   "2AF",
   125  		RequestID:      246,
   126  		When:           "2017-11-27T15:45:24Z",
   127  		Facade:         "Death Vessel",
   128  		Method:         "Horchata",
   129  		Version:        5,
   130  		Args:           `{"a": "something"}`,
   131  	})
   132  	c.Assert(calls[2].Args[0], gc.DeepEquals, auditlog.ResponseErrors{
   133  		ConversationID: callID,
   134  		ConnectionID:   "2AF",
   135  		RequestID:      246,
   136  		When:           "2017-11-27T15:45:25Z",
   137  		Errors: []*auditlog.Error{{
   138  			Message: "something bad",
   139  			Code:    "bad request",
   140  		}},
   141  	})
   142  }
   143  
   144  type fakeLog struct {
   145  	stub testing.Stub
   146  }
   147  
   148  func (l *fakeLog) AddConversation(m auditlog.Conversation) error {
   149  	l.stub.AddCall("AddConversation", m)
   150  	return l.stub.NextErr()
   151  }
   152  
   153  func (l *fakeLog) AddRequest(m auditlog.Request) error {
   154  	l.stub.AddCall("AddRequest", m)
   155  	return l.stub.NextErr()
   156  }
   157  
   158  func (l *fakeLog) AddResponse(m auditlog.ResponseErrors) error {
   159  	l.stub.AddCall("AddResponse", m)
   160  	return l.stub.NextErr()
   161  }
   162  
   163  func (l *fakeLog) Close() error {
   164  	l.stub.AddCall("Close")
   165  	return l.stub.NextErr()
   166  }
   167  
   168  var (
   169  	expectedLogContents = `
   170  {"conversation":{"who":"deerhoof","what":"gojira","when":"2017-11-27T13:21:24Z","model-name":"admin/default","model-uuid":"","conversation-id":"0123456789abcdef","connection-id":"AC1"}}
   171  {"request":{"conversation-id":"0123456789abcdef","connection-id":"AC1","request-id":25,"when":"2017-12-12T11:34:56Z","facade":"Application","method":"Deploy","version":4,"args":"{\"applications\": [{\"application\": \"prometheus\"}]}"}}
   172  {"errors":{"conversation-id":"0123456789abcdef","connection-id":"AC1","request-id":25,"when":"2017-12-12T11:35:11Z","errors":[{"message":"oops","code":"unauthorized access"}]}}
   173  `[1:]
   174  )