Skip to content

Commit 69411d7

Browse files
authored
VAULT-30108: Include User-Agent header in audit requests by default (#28596)
* include user-agent header in audit by default * add user-agent audit tests * update audit default headers docs * add changelog entry * remove temp changes from TestAuditedHeadersConfig_ApplyConfig * more TestAuditedHeadersConfig_ApplyConfig fixes * add some test comments * verify type assertions in TestAudit_Headers * more type assertion checks
1 parent 05f32b6 commit 69411d7

File tree

5 files changed

+123
-6
lines changed

5 files changed

+123
-6
lines changed

audit/headers.go

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ func (a *HeadersConfig) DefaultHeaders() map[string]*headerSettings {
175175
return map[string]*headerSettings{
176176
correlationID: {},
177177
xCorrelationID: {},
178+
"user-agent": {},
178179
}
179180
}
180181

audit/headers_test.go

+26-4
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,11 @@ func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
254254
t.Fatal(err)
255255
}
256256

257+
const hmacPrefix = "hmac-sha256:"
258+
257259
expected := map[string][]string{
258260
"x-test-header": {"foo"},
259-
"x-vault-header": {"hmac-sha256:", "hmac-sha256:"},
261+
"x-vault-header": {hmacPrefix, hmacPrefix},
260262
}
261263

262264
if len(expected) != len(result) {
@@ -271,7 +273,7 @@ func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
271273
}
272274

273275
for i, e := range expectedValues {
274-
if e == "hmac-sha256:" {
276+
if e == hmacPrefix {
275277
if !strings.HasPrefix(resultValues[i], e) {
276278
t.Fatalf("Expected headers did not match actual: Expected %#v...\n Got %#v\n", e, resultValues[i])
277279
}
@@ -609,13 +611,28 @@ func TestAuditedHeaders_invalidate_defaults(t *testing.T) {
609611
require.Equal(t, len(ahc.DefaultHeaders())+1, len(ahc.headerSettings)) // (defaults + 1 new header)
610612
_, ok := ahc.headerSettings["x-magic-header"]
611613
require.True(t, ok)
614+
612615
s, ok := ahc.headerSettings["x-correlation-id"]
613616
require.True(t, ok)
614617
require.False(t, s.HMAC)
615618

616-
// Add correlation ID specifically with HMAC and make sure it doesn't get blasted away.
617-
fakeHeaders1 = map[string]*headerSettings{"x-magic-header": {}, "X-Correlation-ID": {HMAC: true}}
619+
s, ok = ahc.headerSettings["user-agent"]
620+
require.True(t, ok)
621+
require.False(t, s.HMAC)
622+
623+
// Add correlation ID and user-agent specifically with HMAC and make sure it doesn't get blasted away.
624+
fakeHeaders1 = map[string]*headerSettings{
625+
"x-magic-header": {},
626+
"X-Correlation-ID": {
627+
HMAC: true,
628+
},
629+
"User-Agent": {
630+
HMAC: true,
631+
},
632+
}
633+
618634
fakeBytes1, err = json.Marshal(fakeHeaders1)
635+
619636
require.NoError(t, err)
620637
err = view.Put(context.Background(), &logical.StorageEntry{Key: auditedHeadersEntry, Value: fakeBytes1})
621638
require.NoError(t, err)
@@ -626,7 +643,12 @@ func TestAuditedHeaders_invalidate_defaults(t *testing.T) {
626643
require.Equal(t, len(ahc.DefaultHeaders())+1, len(ahc.headerSettings)) // (defaults + 1 new header, 1 is also a default)
627644
_, ok = ahc.headerSettings["x-magic-header"]
628645
require.True(t, ok)
646+
629647
s, ok = ahc.headerSettings["x-correlation-id"]
630648
require.True(t, ok)
631649
require.True(t, s.HMAC)
650+
651+
s, ok = ahc.headerSettings["user-agent"]
652+
require.True(t, ok)
653+
require.True(t, s.HMAC)
632654
}

changelog/28596.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
```release-note:improvement
2+
audit: Audit logs will contain User-Agent headers when they are present in the incoming request. They are not
3+
HMAC'ed by default but can be configured to be via the `/sys/config/auditing/request-headers/user-agent` endpoint.
4+
```

vault/external_tests/audit/audit_test.go

+88-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ func TestAudit_HMACFields(t *testing.T) {
5252
require.NoError(t, err)
5353

5454
// Request 1
55-
// Enable the audit device. A test probe request will audited along with the associated
56-
// to the creation response
55+
// Enable the audit device. A test probe request will audited along
56+
// with the associated creation response
5757
_, err = client.Logical().Write("sys/audit/"+devicePath, deviceData)
5858
require.NoError(t, err)
5959

@@ -212,3 +212,89 @@ func TestAudit_HMACFields(t *testing.T) {
212212
require.True(t, strings.HasPrefix(wrapInfo["token"].(string), hmacPrefix))
213213
require.Equal(t, wrapInfo["token"].(string), hashedWrapToken)
214214
}
215+
216+
// TestAudit_Headers validates that headers are audited correctly. This includes
217+
// the default headers (x-correlation-id and user-agent) along with user-specified
218+
// headers.
219+
func TestAudit_Headers(t *testing.T) {
220+
cluster := minimal.NewTestSoloCluster(t, nil)
221+
client := cluster.Cores[0].Client
222+
223+
tempDir := t.TempDir()
224+
logFile, err := os.CreateTemp(tempDir, "")
225+
require.NoError(t, err)
226+
devicePath := "file"
227+
deviceData := map[string]any{
228+
"type": "file",
229+
"description": "",
230+
"local": false,
231+
"options": map[string]any{
232+
"file_path": logFile.Name(),
233+
},
234+
}
235+
236+
_, err = client.Logical().Write("sys/config/auditing/request-headers/x-some-header", map[string]interface{}{
237+
"hmac": false,
238+
})
239+
require.NoError(t, err)
240+
241+
// User-Agent header is audited by default
242+
client.AddHeader("User-Agent", "foo-agent")
243+
244+
// X-Some-Header has been added to audited headers manually
245+
client.AddHeader("X-Some-Header", "some-value")
246+
247+
// X-Some-Other-Header will not be audited
248+
client.AddHeader("X-Some-Other-Header", "some-other-value")
249+
250+
// Request 1
251+
// Enable the audit device. A test probe request will audited along
252+
// with the associated creation response
253+
_, err = client.Logical().Write("sys/audit/"+devicePath, deviceData)
254+
require.NoError(t, err)
255+
256+
// Request 2
257+
// Ensure the device has been created.
258+
devices, err := client.Sys().ListAudit()
259+
require.NoError(t, err)
260+
require.Len(t, devices, 1)
261+
262+
// Request 3
263+
resp, err := client.Sys().SealStatus()
264+
require.NoError(t, err)
265+
require.NotEmpty(t, resp)
266+
267+
expectedHeaders := map[string]interface{}{
268+
"user-agent": []interface{}{"foo-agent"},
269+
"x-some-header": []interface{}{"some-value"},
270+
}
271+
272+
entries := make([]map[string]interface{}, 0)
273+
scanner := bufio.NewScanner(logFile)
274+
275+
for scanner.Scan() {
276+
entry := make(map[string]interface{})
277+
278+
err := json.Unmarshal(scanner.Bytes(), &entry)
279+
require.NoError(t, err)
280+
281+
request, ok := entry["request"].(map[string]interface{})
282+
require.True(t, ok)
283+
284+
// test probe will not have headers set
285+
requestPath, ok := request["path"].(string)
286+
require.True(t, ok)
287+
288+
if requestPath != "sys/audit/test" {
289+
headers, ok := request["headers"].(map[string]interface{})
290+
291+
require.True(t, ok)
292+
require.Equal(t, expectedHeaders, headers)
293+
}
294+
295+
entries = append(entries, entry)
296+
}
297+
298+
// This count includes the initial test probe upon creation of the audit device
299+
require.Equal(t, 4, len(entries))
300+
}

website/content/docs/audit/index.mdx

+4
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ curl \
121121
--data '{ "hmac": true }'
122122
```
123123

124+
Another way to identify the source of a request is through the User-Agent request header.
125+
Vault will automatically record this value as `user-agent` within the `headers` of a
126+
request entry within the audit log.
127+
124128

125129
## Enabling/Disabling audit devices
126130

0 commit comments

Comments
 (0)