audit: support a configurable prefix string to write before each message (#2359)

A static token at the beginning of a log line can help systems parse
logs better. For example, rsyslog and syslog-ng will recognize the
'@cee: ' prefix and will parse the rest of the line as a valid json message.
This is useful in environments where there is a mix of structured and
unstructured logs.
This commit is contained in:
Tommy Murphy 2017-02-10 19:56:28 -05:00 committed by Jeff Mitchell
parent 864156773a
commit ca06bc0b53
10 changed files with 130 additions and 13 deletions

View File

@ -8,13 +8,22 @@ import (
// JSONFormatWriter is an AuditFormatWriter implementation that structures data into
// a JSON format.
type JSONFormatWriter struct{}
type JSONFormatWriter struct {
Prefix string
}
func (f *JSONFormatWriter) WriteRequest(w io.Writer, req *AuditRequestEntry) error {
if req == nil {
return fmt.Errorf("request entry was nil, cannot encode")
}
if len(f.Prefix) > 0 {
_, err := w.Write([]byte(f.Prefix))
if err != nil {
return err
}
}
enc := json.NewEncoder(w)
return enc.Encode(req)
}
@ -24,6 +33,13 @@ func (f *JSONFormatWriter) WriteResponse(w io.Writer, resp *AuditResponseEntry)
return fmt.Errorf("response entry was nil, cannot encode")
}
if len(f.Prefix) > 0 {
_, err := w.Write([]byte(f.Prefix))
if err != nil {
return err
}
}
enc := json.NewEncoder(w)
return enc.Encode(resp)
}

View File

@ -19,6 +19,7 @@ func TestFormatJSON_formatRequest(t *testing.T) {
Auth *logical.Auth
Req *logical.Request
Err error
Prefix string
Result string
}{
"auth, request": {
@ -37,6 +38,26 @@ func TestFormatJSON_formatRequest(t *testing.T) {
},
},
errors.New("this is an error"),
"",
testFormatJSONReqBasicStr,
},
"auth, request with prefix": {
&logical.Auth{ClientToken: "foo", Policies: []string{"root"}},
&logical.Request{
Operation: logical.UpdateOperation,
Path: "/foo",
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
Headers: map[string][]string{
"foo": []string{"bar"},
},
},
errors.New("this is an error"),
"@cee: ",
testFormatJSONReqBasicStr,
},
}
@ -44,7 +65,9 @@ func TestFormatJSON_formatRequest(t *testing.T) {
for name, tc := range cases {
var buf bytes.Buffer
formatter := AuditFormatter{
AuditFormatWriter: &JSONFormatWriter{},
AuditFormatWriter: &JSONFormatWriter{
Prefix: tc.Prefix,
},
}
salter, _ := salt.NewSalt(nil, nil)
config := FormatterConfig{
@ -54,13 +77,17 @@ func TestFormatJSON_formatRequest(t *testing.T) {
t.Fatalf("bad: %s\nerr: %s", name, err)
}
if !strings.HasPrefix(buf.String(), tc.Prefix) {
t.Fatalf("no prefix: %s \n log: %s\nprefix: %s", name, tc.Result, tc.Prefix)
}
var expectedjson = new(AuditRequestEntry)
if err := jsonutil.DecodeJSON([]byte(tc.Result), &expectedjson); err != nil {
t.Fatalf("bad json: %s", err)
}
var actualjson = new(AuditRequestEntry)
if err := jsonutil.DecodeJSON([]byte(buf.String()), &actualjson); err != nil {
if err := jsonutil.DecodeJSON([]byte(buf.String())[len(tc.Prefix):], &actualjson); err != nil {
t.Fatalf("bad json: %s", err)
}
@ -71,7 +98,7 @@ func TestFormatJSON_formatRequest(t *testing.T) {
t.Fatalf("unable to marshal json: %s", err)
}
if strings.TrimSpace(buf.String()) != string(expectedBytes) {
if !strings.HasSuffix(strings.TrimSpace(buf.String()), string(expectedBytes)) {
t.Fatalf(
"bad: %s\nResult:\n\n'%s'\n\nExpected:\n\n'%s'",
name, buf.String(), string(expectedBytes))

View File

@ -10,13 +10,22 @@ import (
// JSONxFormatWriter is an AuditFormatWriter implementation that structures data into
// a XML format.
type JSONxFormatWriter struct{}
type JSONxFormatWriter struct {
Prefix string
}
func (f *JSONxFormatWriter) WriteRequest(w io.Writer, req *AuditRequestEntry) error {
if req == nil {
return fmt.Errorf("request entry was nil, cannot encode")
}
if len(f.Prefix) > 0 {
_, err := w.Write([]byte(f.Prefix))
if err != nil {
return err
}
}
jsonBytes, err := json.Marshal(req)
if err != nil {
return err
@ -36,6 +45,13 @@ func (f *JSONxFormatWriter) WriteResponse(w io.Writer, resp *AuditResponseEntry)
return fmt.Errorf("response entry was nil, cannot encode")
}
if len(f.Prefix) > 0 {
_, err := w.Write([]byte(f.Prefix))
if err != nil {
return err
}
}
jsonBytes, err := json.Marshal(resp)
if err != nil {
return err

View File

@ -17,6 +17,7 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
Auth *logical.Auth
Req *logical.Request
Err error
Prefix string
Result string
Expected string
}{
@ -37,6 +38,27 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
},
errors.New("this is an error"),
"",
"",
`<json:object name="auth"><json:string name="accessor"></json:string><json:string name="client_token"></json:string><json:string name="display_name"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:string name="client_token_accessor"></json:string><json:null name="data" /><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id"></json:string><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`,
},
"auth, request with prefix": {
&logical.Auth{ClientToken: "foo", Policies: []string{"root"}},
&logical.Request{
Operation: logical.UpdateOperation,
Path: "/foo",
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: 60 * time.Second,
},
Headers: map[string][]string{
"foo": []string{"bar"},
},
},
errors.New("this is an error"),
"",
"@cee: ",
`<json:object name="auth"><json:string name="accessor"></json:string><json:string name="client_token"></json:string><json:string name="display_name"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:string name="client_token_accessor"></json:string><json:null name="data" /><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id"></json:string><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`,
},
}
@ -44,7 +66,9 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
for name, tc := range cases {
var buf bytes.Buffer
formatter := AuditFormatter{
AuditFormatWriter: &JSONxFormatWriter{},
AuditFormatWriter: &JSONxFormatWriter{
Prefix: tc.Prefix,
},
}
salter, _ := salt.NewSalt(nil, nil)
config := FormatterConfig{
@ -55,7 +79,11 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
t.Fatalf("bad: %s\nerr: %s", name, err)
}
if strings.TrimSpace(buf.String()) != string(tc.Expected) {
if !strings.HasPrefix(buf.String(), tc.Prefix) {
t.Fatalf("no prefix: %s \n log: %s\nprefix: %s", name, tc.Result, tc.Prefix)
}
if !strings.HasSuffix(strings.TrimSpace(buf.String()), string(tc.Expected)) {
t.Fatalf(
"bad: %s\nResult:\n\n'%s'\n\nExpected:\n\n'%s'",
name, strings.TrimSpace(buf.String()), string(tc.Expected))

View File

@ -76,9 +76,13 @@ func Factory(conf *audit.BackendConfig) (audit.Backend, error) {
switch format {
case "json":
b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{}
b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
Prefix: conf.Config["prefix"],
}
case "jsonx":
b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{}
b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{
Prefix: conf.Config["prefix"],
}
}
// Ensure that the file can be successfully opened for writing;

View File

@ -87,9 +87,13 @@ func Factory(conf *audit.BackendConfig) (audit.Backend, error) {
switch format {
case "json":
b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{}
b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
Prefix: conf.Config["prefix"],
}
case "jsonx":
b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{}
b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{
Prefix: conf.Config["prefix"],
}
}
return b, nil

View File

@ -74,9 +74,13 @@ func Factory(conf *audit.BackendConfig) (audit.Backend, error) {
switch format {
case "json":
b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{}
b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
Prefix: conf.Config["prefix"],
}
case "jsonx":
b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{}
b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{
Prefix: conf.Config["prefix"],
}
}
return b, nil

View File

@ -85,6 +85,12 @@ Following are the configuration options available for the backend.
Allows selecting the output format. Valid values are `json` (the
default) and `jsonx`, which formats the normal log entries as XML.
</li>
<li>
<span class="param">prefix</span>
<span class="param-flags">optional</span>
Allows a customizable string prefix to write before the actual log
line. Defaults to an empty string.
</li>
</ul>
</dd>
</dl>

View File

@ -74,6 +74,12 @@ Following are the configuration options available for the backend.
<span class="param-flags">optional</span>
Sets the timeout for writes to the socket. Defaults to "2s" (2 seconds).
</li>
<li>
<span class="param">prefix</span>
<span class="param-flags">optional</span>
Allows a customizable string prefix to write before the actual log
line. Defaults to an empty string.
</li>
</ul>
</dd>
</dl>

View File

@ -71,6 +71,12 @@ Following are the configuration options available for the backend.
Allows selecting the output format. Valid values are `json` (the
default) and `jsonx`, which formats the normal log entries as XML.
</li>
<li>
<span class="param">prefix</span>
<span class="param-flags">optional</span>
Allows a customizable string prefix to write before the actual log
line. Defaults to an empty string.
</li>
</ul>
</dd>
</dl>