mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 01:32:33 +00:00
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
This commit is contained in:
@@ -175,6 +175,7 @@ func (a *HeadersConfig) DefaultHeaders() map[string]*headerSettings {
|
|||||||
return map[string]*headerSettings{
|
return map[string]*headerSettings{
|
||||||
correlationID: {},
|
correlationID: {},
|
||||||
xCorrelationID: {},
|
xCorrelationID: {},
|
||||||
|
"user-agent": {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,9 +254,11 @@ func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hmacPrefix = "hmac-sha256:"
|
||||||
|
|
||||||
expected := map[string][]string{
|
expected := map[string][]string{
|
||||||
"x-test-header": {"foo"},
|
"x-test-header": {"foo"},
|
||||||
"x-vault-header": {"hmac-sha256:", "hmac-sha256:"},
|
"x-vault-header": {hmacPrefix, hmacPrefix},
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(expected) != len(result) {
|
if len(expected) != len(result) {
|
||||||
@@ -271,7 +273,7 @@ func TestAuditedHeadersConfig_ApplyConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, e := range expectedValues {
|
for i, e := range expectedValues {
|
||||||
if e == "hmac-sha256:" {
|
if e == hmacPrefix {
|
||||||
if !strings.HasPrefix(resultValues[i], e) {
|
if !strings.HasPrefix(resultValues[i], e) {
|
||||||
t.Fatalf("Expected headers did not match actual: Expected %#v...\n Got %#v\n", e, resultValues[i])
|
t.Fatalf("Expected headers did not match actual: Expected %#v...\n Got %#v\n", e, resultValues[i])
|
||||||
}
|
}
|
||||||
@@ -609,13 +611,28 @@ func TestAuditedHeaders_invalidate_defaults(t *testing.T) {
|
|||||||
require.Equal(t, len(ahc.DefaultHeaders())+1, len(ahc.headerSettings)) // (defaults + 1 new header)
|
require.Equal(t, len(ahc.DefaultHeaders())+1, len(ahc.headerSettings)) // (defaults + 1 new header)
|
||||||
_, ok := ahc.headerSettings["x-magic-header"]
|
_, ok := ahc.headerSettings["x-magic-header"]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
s, ok := ahc.headerSettings["x-correlation-id"]
|
s, ok := ahc.headerSettings["x-correlation-id"]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.False(t, s.HMAC)
|
require.False(t, s.HMAC)
|
||||||
|
|
||||||
// Add correlation ID specifically with HMAC and make sure it doesn't get blasted away.
|
s, ok = ahc.headerSettings["user-agent"]
|
||||||
fakeHeaders1 = map[string]*headerSettings{"x-magic-header": {}, "X-Correlation-ID": {HMAC: true}}
|
require.True(t, ok)
|
||||||
|
require.False(t, s.HMAC)
|
||||||
|
|
||||||
|
// Add correlation ID and user-agent specifically with HMAC and make sure it doesn't get blasted away.
|
||||||
|
fakeHeaders1 = map[string]*headerSettings{
|
||||||
|
"x-magic-header": {},
|
||||||
|
"X-Correlation-ID": {
|
||||||
|
HMAC: true,
|
||||||
|
},
|
||||||
|
"User-Agent": {
|
||||||
|
HMAC: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
fakeBytes1, err = json.Marshal(fakeHeaders1)
|
fakeBytes1, err = json.Marshal(fakeHeaders1)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = view.Put(context.Background(), &logical.StorageEntry{Key: auditedHeadersEntry, Value: fakeBytes1})
|
err = view.Put(context.Background(), &logical.StorageEntry{Key: auditedHeadersEntry, Value: fakeBytes1})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -626,7 +643,12 @@ func TestAuditedHeaders_invalidate_defaults(t *testing.T) {
|
|||||||
require.Equal(t, len(ahc.DefaultHeaders())+1, len(ahc.headerSettings)) // (defaults + 1 new header, 1 is also a default)
|
require.Equal(t, len(ahc.DefaultHeaders())+1, len(ahc.headerSettings)) // (defaults + 1 new header, 1 is also a default)
|
||||||
_, ok = ahc.headerSettings["x-magic-header"]
|
_, ok = ahc.headerSettings["x-magic-header"]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
s, ok = ahc.headerSettings["x-correlation-id"]
|
s, ok = ahc.headerSettings["x-correlation-id"]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.True(t, s.HMAC)
|
require.True(t, s.HMAC)
|
||||||
|
|
||||||
|
s, ok = ahc.headerSettings["user-agent"]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.True(t, s.HMAC)
|
||||||
}
|
}
|
||||||
|
|||||||
4
changelog/28596.txt
Normal file
4
changelog/28596.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
audit: Audit logs will contain User-Agent headers when they are present in the incoming request. They are not
|
||||||
|
HMAC'ed by default but can be configured to be via the `/sys/config/auditing/request-headers/user-agent` endpoint.
|
||||||
|
```
|
||||||
@@ -52,8 +52,8 @@ func TestAudit_HMACFields(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Request 1
|
// Request 1
|
||||||
// Enable the audit device. A test probe request will audited along with the associated
|
// Enable the audit device. A test probe request will audited along
|
||||||
// to the creation response
|
// with the associated creation response
|
||||||
_, err = client.Logical().Write("sys/audit/"+devicePath, deviceData)
|
_, err = client.Logical().Write("sys/audit/"+devicePath, deviceData)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -212,3 +212,89 @@ func TestAudit_HMACFields(t *testing.T) {
|
|||||||
require.True(t, strings.HasPrefix(wrapInfo["token"].(string), hmacPrefix))
|
require.True(t, strings.HasPrefix(wrapInfo["token"].(string), hmacPrefix))
|
||||||
require.Equal(t, wrapInfo["token"].(string), hashedWrapToken)
|
require.Equal(t, wrapInfo["token"].(string), hashedWrapToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAudit_Headers validates that headers are audited correctly. This includes
|
||||||
|
// the default headers (x-correlation-id and user-agent) along with user-specified
|
||||||
|
// headers.
|
||||||
|
func TestAudit_Headers(t *testing.T) {
|
||||||
|
cluster := minimal.NewTestSoloCluster(t, nil)
|
||||||
|
client := cluster.Cores[0].Client
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
logFile, err := os.CreateTemp(tempDir, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
devicePath := "file"
|
||||||
|
deviceData := map[string]any{
|
||||||
|
"type": "file",
|
||||||
|
"description": "",
|
||||||
|
"local": false,
|
||||||
|
"options": map[string]any{
|
||||||
|
"file_path": logFile.Name(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Logical().Write("sys/config/auditing/request-headers/x-some-header", map[string]interface{}{
|
||||||
|
"hmac": false,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// User-Agent header is audited by default
|
||||||
|
client.AddHeader("User-Agent", "foo-agent")
|
||||||
|
|
||||||
|
// X-Some-Header has been added to audited headers manually
|
||||||
|
client.AddHeader("X-Some-Header", "some-value")
|
||||||
|
|
||||||
|
// X-Some-Other-Header will not be audited
|
||||||
|
client.AddHeader("X-Some-Other-Header", "some-other-value")
|
||||||
|
|
||||||
|
// Request 1
|
||||||
|
// Enable the audit device. A test probe request will audited along
|
||||||
|
// with the associated creation response
|
||||||
|
_, err = client.Logical().Write("sys/audit/"+devicePath, deviceData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Request 2
|
||||||
|
// Ensure the device has been created.
|
||||||
|
devices, err := client.Sys().ListAudit()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, devices, 1)
|
||||||
|
|
||||||
|
// Request 3
|
||||||
|
resp, err := client.Sys().SealStatus()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, resp)
|
||||||
|
|
||||||
|
expectedHeaders := map[string]interface{}{
|
||||||
|
"user-agent": []interface{}{"foo-agent"},
|
||||||
|
"x-some-header": []interface{}{"some-value"},
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := make([]map[string]interface{}, 0)
|
||||||
|
scanner := bufio.NewScanner(logFile)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
entry := make(map[string]interface{})
|
||||||
|
|
||||||
|
err := json.Unmarshal(scanner.Bytes(), &entry)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
request, ok := entry["request"].(map[string]interface{})
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
// test probe will not have headers set
|
||||||
|
requestPath, ok := request["path"].(string)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
if requestPath != "sys/audit/test" {
|
||||||
|
headers, ok := request["headers"].(map[string]interface{})
|
||||||
|
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, expectedHeaders, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = append(entries, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This count includes the initial test probe upon creation of the audit device
|
||||||
|
require.Equal(t, 4, len(entries))
|
||||||
|
}
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ curl \
|
|||||||
--data '{ "hmac": true }'
|
--data '{ "hmac": true }'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Another way to identify the source of a request is through the User-Agent request header.
|
||||||
|
Vault will automatically record this value as `user-agent` within the `headers` of a
|
||||||
|
request entry within the audit log.
|
||||||
|
|
||||||
|
|
||||||
## Enabling/Disabling audit devices
|
## Enabling/Disabling audit devices
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user